From 037497939778c3fff8d721b60b7c85ac7f995683 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sun, 1 Mar 2026 13:52:39 -0800 Subject: [PATCH 001/141] use vendored raylib from dependencies repo (#37489) --- SConstruct | 3 +- pyproject.toml | 2 +- scripts/lint/check_raylib_includes.sh | 10 - selfdrive/ui/SConscript | 9 +- selfdrive/ui/installer/installer.cc | 2 +- third_party/raylib/.gitignore | 4 - third_party/raylib/Darwin/libraylib.a | 3 - third_party/raylib/build.sh | 93 - third_party/raylib/include/raygui.h | 5759 ------------------------ third_party/raylib/include/raylib.h | 1766 -------- third_party/raylib/include/raymath.h | 2949 ------------ third_party/raylib/include/rlgl.h | 5262 ---------------------- third_party/raylib/larch64/libraylib.a | 3 - third_party/raylib/x86_64/libraylib.a | 3 - uv.lock | 40 +- 15 files changed, 26 insertions(+), 15882 deletions(-) delete mode 100755 scripts/lint/check_raylib_includes.sh delete mode 100644 third_party/raylib/.gitignore delete mode 100644 third_party/raylib/Darwin/libraylib.a delete mode 100755 third_party/raylib/build.sh delete mode 100644 third_party/raylib/include/raygui.h delete mode 100644 third_party/raylib/include/raylib.h delete mode 100644 third_party/raylib/include/raymath.h delete mode 100644 third_party/raylib/include/rlgl.h delete mode 100644 third_party/raylib/larch64/libraylib.a delete mode 100644 third_party/raylib/x86_64/libraylib.a diff --git a/SConstruct b/SConstruct index da70bc3924..7003abbf05 100644 --- a/SConstruct +++ b/SConstruct @@ -48,9 +48,10 @@ if arch != "larch64": import ncurses import openssl3 import python3_dev + import raylib import zeromq import zstd - pkgs = [bzip2, capnproto, eigen, ffmpeg_pkg, libjpeg, libyuv, ncurses, openssl3, zeromq, zstd] + pkgs = [bzip2, capnproto, eigen, ffmpeg_pkg, libjpeg, libyuv, ncurses, openssl3, raylib, zeromq, zstd] py_include = python3_dev.INCLUDE_DIR else: # TODO: remove when AGNOS has our new vendor pkgs diff --git a/pyproject.toml b/pyproject.toml index 6516c8cd5b..d0020b21e7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -73,7 +73,7 @@ dependencies = [ "zstandard", # ui - "raylib > 5.5.0.3", + "raylib @ git+https://github.com/commaai/dependencies.git@releases#subdirectory=raylib", "qrcode", "jeepney", ] diff --git a/scripts/lint/check_raylib_includes.sh b/scripts/lint/check_raylib_includes.sh deleted file mode 100755 index e3be73a489..0000000000 --- a/scripts/lint/check_raylib_includes.sh +++ /dev/null @@ -1,10 +0,0 @@ -#!/usr/bin/env bash - -FAIL=0 - -if grep -n '#include "third_party/raylib/include/raylib\.h"' $@ | grep -v '^system/ui/raylib/raylib\.h'; then - echo -e "Bad raylib include found! Use '#include \"system/ui/raylib/raylib.h\"' instead\n" - FAIL=1 -fi - -exit $FAIL diff --git a/selfdrive/ui/SConscript b/selfdrive/ui/SConscript index 4d7448c62f..bebfd0011f 100644 --- a/selfdrive/ui/SConscript +++ b/selfdrive/ui/SConscript @@ -16,12 +16,17 @@ env.Command( action=f"python3 {generator}", ) +try: + import raylib +except ImportError: + raylib = None -if GetOption('extras') and arch == "larch64": +if GetOption('extras') and arch == "larch64" and raylib is not None: # build installers if arch != "Darwin": raylib_env = env.Clone() - raylib_env['LIBPATH'] += [f'#third_party/raylib/{arch}/'] + raylib_env['CPPPATH'] += [raylib.INCLUDE_DIR] + raylib_env['LIBPATH'] += [raylib.LIB_DIR] raylib_env['LINKFLAGS'].append('-Wl,-strip-debug') raylib_libs = common + ["raylib"] diff --git a/selfdrive/ui/installer/installer.cc b/selfdrive/ui/installer/installer.cc index 7599454194..338bcad34e 100644 --- a/selfdrive/ui/installer/installer.cc +++ b/selfdrive/ui/installer/installer.cc @@ -6,7 +6,7 @@ #include "common/swaglog.h" #include "common/util.h" #include "system/hardware/hw.h" -#include "third_party/raylib/include/raylib.h" +#include "raylib.h" int freshClone(); int cachedFetch(const std::string &cache); diff --git a/third_party/raylib/.gitignore b/third_party/raylib/.gitignore deleted file mode 100644 index 6b1d3ad748..0000000000 --- a/third_party/raylib/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -/raylib_repo/ -/raylib_python_repo/ -/wheel/ -!*.a diff --git a/third_party/raylib/Darwin/libraylib.a b/third_party/raylib/Darwin/libraylib.a deleted file mode 100644 index dd2e9b33f1..0000000000 --- a/third_party/raylib/Darwin/libraylib.a +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:fd045c1d4bca5c9b2ad044ea730826ff6cedeef0b64451b123717b136f1cd702 -size 6392532 diff --git a/third_party/raylib/build.sh b/third_party/raylib/build.sh deleted file mode 100755 index d20f9d33af..0000000000 --- a/third_party/raylib/build.sh +++ /dev/null @@ -1,93 +0,0 @@ -#!/usr/bin/env bash -set -e - -export SOURCE_DATE_EPOCH=0 -export ZERO_AR_DATE=1 - -SUDO="" - -# Use sudo if not root -if [[ ! $(id -u) -eq 0 ]]; then - if [[ -z $(which sudo) ]]; then - echo "Please install sudo or run as root" - exit 1 - fi - SUDO="sudo" -fi - -DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )" -cd $DIR - -RAYLIB_PLATFORM="PLATFORM_DESKTOP" - -ARCHNAME=$(uname -m) -if [ -f /TICI ]; then - ARCHNAME="larch64" - RAYLIB_PLATFORM="PLATFORM_COMMA" -elif [[ "$OSTYPE" == "linux"* ]]; then - # required dependencies on Linux PC - $SUDO apt install \ - libxcursor-dev \ - libxi-dev \ - libxinerama-dev \ - libxrandr-dev -fi - -if [[ "$OSTYPE" == "darwin"* ]]; then - ARCHNAME="Darwin" -fi - -INSTALL_DIR="$DIR/$ARCHNAME" -rm -rf $INSTALL_DIR -mkdir -p $INSTALL_DIR - -INSTALL_H_DIR="$DIR/include" -rm -rf $INSTALL_H_DIR -mkdir -p $INSTALL_H_DIR - -if [ ! -d raylib_repo ]; then - git clone -b master --no-tags https://github.com/commaai/raylib.git raylib_repo -fi - -cd raylib_repo - -COMMIT=${1:-3425bd9d1fb292ede4d80f97a1f4f258f614cffc} -git fetch origin $COMMIT -git reset --hard $COMMIT -git clean -xdff . - -cd src - -make -j$(nproc) PLATFORM=$RAYLIB_PLATFORM RAYLIB_RELEASE_PATH=$INSTALL_DIR -cp raylib.h raymath.h rlgl.h $INSTALL_H_DIR/ -echo "raylib development files installed/updated in $INSTALL_H_DIR" - -# this commit needs to be in line with raylib -set -x -RAYGUI_COMMIT="76b36b597edb70ffaf96f046076adc20d67e7827" -curl -fsSLo $INSTALL_H_DIR/raygui.h https://raw.githubusercontent.com/raysan5/raygui/$RAYGUI_COMMIT/src/raygui.h - -if [ -f /TICI ]; then - - # Building the python bindings - cd $DIR - - if [ ! -d raylib_python_repo ]; then - git clone -b master --no-tags https://github.com/commaai/raylib-python-cffi.git raylib_python_repo - fi - - cd raylib_python_repo - - BINDINGS_COMMIT="a0710d95af3c12fd7f4b639589be9a13dad93cb6" - git fetch origin $BINDINGS_COMMIT - git reset --hard $BINDINGS_COMMIT - git clean -xdff . - - RAYLIB_PLATFORM=$RAYLIB_PLATFORM RAYLIB_INCLUDE_PATH=$INSTALL_H_DIR RAYLIB_LIB_PATH=$INSTALL_DIR python setup.py bdist_wheel - cd $DIR - - rm -rf wheel - mkdir wheel - cp raylib_python_repo/dist/*.whl wheel/ - -fi diff --git a/third_party/raylib/include/raygui.h b/third_party/raylib/include/raygui.h deleted file mode 100644 index fe233a16cf..0000000000 --- a/third_party/raylib/include/raygui.h +++ /dev/null @@ -1,5759 +0,0 @@ -/******************************************************************************************* -* -* raygui v4.5-dev - A simple and easy-to-use immediate-mode gui library -* -* DESCRIPTION: -* raygui is a tools-dev-focused immediate-mode-gui library based on raylib but also -* available as a standalone library, as long as input and drawing functions are provided. -* -* FEATURES: -* - Immediate-mode gui, minimal retained data -* - +25 controls provided (basic and advanced) -* - Styling system for colors, font and metrics -* - Icons supported, embedded as a 1-bit icons pack -* - Standalone mode option (custom input/graphics backend) -* - Multiple support tools provided for raygui development -* -* POSSIBLE IMPROVEMENTS: -* - Better standalone mode API for easy plug of custom backends -* - Externalize required inputs, allow user easier customization -* -* LIMITATIONS: -* - No editable multi-line word-wraped text box supported -* - No auto-layout mechanism, up to the user to define controls position and size -* - Standalone mode requires library modification and some user work to plug another backend -* -* NOTES: -* - WARNING: GuiLoadStyle() and GuiLoadStyle{Custom}() functions, allocate memory for -* font atlas recs and glyphs, freeing that memory is (usually) up to the user, -* no unload function is explicitly provided... but note that GuiLoadStyleDefault() unloads -* by default any previously loaded font (texture, recs, glyphs). -* - Global UI alpha (guiAlpha) is applied inside GuiDrawRectangle() and GuiDrawText() functions -* -* CONTROLS PROVIDED: -* # Container/separators Controls -* - WindowBox --> StatusBar, Panel -* - GroupBox --> Line -* - Line -* - Panel --> StatusBar -* - ScrollPanel --> StatusBar -* - TabBar --> Button -* -* # Basic Controls -* - Label -* - LabelButton --> Label -* - Button -* - Toggle -* - ToggleGroup --> Toggle -* - ToggleSlider -* - CheckBox -* - ComboBox -* - DropdownBox -* - TextBox -* - ValueBox --> TextBox -* - Spinner --> Button, ValueBox -* - Slider -* - SliderBar --> Slider -* - ProgressBar -* - StatusBar -* - DummyRec -* - Grid -* -* # Advance Controls -* - ListView -* - ColorPicker --> ColorPanel, ColorBarHue -* - MessageBox --> Window, Label, Button -* - TextInputBox --> Window, Label, TextBox, Button -* -* It also provides a set of functions for styling the controls based on its properties (size, color). -* -* -* RAYGUI STYLE (guiStyle): -* raygui uses a global data array for all gui style properties (allocated on data segment by default), -* when a new style is loaded, it is loaded over the global style... but a default gui style could always be -* recovered with GuiLoadStyleDefault() function, that overwrites the current style to the default one -* -* The global style array size is fixed and depends on the number of controls and properties: -* -* static unsigned int guiStyle[RAYGUI_MAX_CONTROLS*(RAYGUI_MAX_PROPS_BASE + RAYGUI_MAX_PROPS_EXTENDED)]; -* -* guiStyle size is by default: 16*(16 + 8) = 384*4 = 1536 bytes = 1.5 KB -* -* Note that the first set of BASE properties (by default guiStyle[0..15]) belong to the generic style -* used for all controls, when any of those base values is set, it is automatically populated to all -* controls, so, specific control values overwriting generic style should be set after base values. -* -* After the first BASE set we have the EXTENDED properties (by default guiStyle[16..23]), those -* properties are actually common to all controls and can not be overwritten individually (like BASE ones) -* Some of those properties are: TEXT_SIZE, TEXT_SPACING, LINE_COLOR, BACKGROUND_COLOR -* -* Custom control properties can be defined using the EXTENDED properties for each independent control. -* -* TOOL: rGuiStyler is a visual tool to customize raygui style: github.com/raysan5/rguistyler -* -* -* RAYGUI ICONS (guiIcons): -* raygui could use a global array containing icons data (allocated on data segment by default), -* a custom icons set could be loaded over this array using GuiLoadIcons(), but loaded icons set -* must be same RAYGUI_ICON_SIZE and no more than RAYGUI_ICON_MAX_ICONS will be loaded -* -* Every icon is codified in binary form, using 1 bit per pixel, so, every 16x16 icon -* requires 8 integers (16*16/32) to be stored in memory. -* -* When the icon is draw, actually one quad per pixel is drawn if the bit for that pixel is set. -* -* The global icons array size is fixed and depends on the number of icons and size: -* -* static unsigned int guiIcons[RAYGUI_ICON_MAX_ICONS*RAYGUI_ICON_DATA_ELEMENTS]; -* -* guiIcons size is by default: 256*(16*16/32) = 2048*4 = 8192 bytes = 8 KB -* -* TOOL: rGuiIcons is a visual tool to customize/create raygui icons: github.com/raysan5/rguiicons -* -* RAYGUI LAYOUT: -* raygui currently does not provide an auto-layout mechanism like other libraries, -* layouts must be defined manually on controls drawing, providing the right bounds Rectangle for it. -* -* TOOL: rGuiLayout is a visual tool to create raygui layouts: github.com/raysan5/rguilayout -* -* CONFIGURATION: -* #define RAYGUI_IMPLEMENTATION -* Generates the implementation of the library into the included file. -* If not defined, the library is in header only mode and can be included in other headers -* or source files without problems. But only ONE file should hold the implementation. -* -* #define RAYGUI_STANDALONE -* Avoid raylib.h header inclusion in this file. Data types defined on raylib are defined -* internally in the library and input management and drawing functions must be provided by -* the user (check library implementation for further details). -* -* #define RAYGUI_NO_ICONS -* Avoid including embedded ricons data (256 icons, 16x16 pixels, 1-bit per pixel, 2KB) -* -* #define RAYGUI_CUSTOM_ICONS -* Includes custom ricons.h header defining a set of custom icons, -* this file can be generated using rGuiIcons tool -* -* #define RAYGUI_DEBUG_RECS_BOUNDS -* Draw control bounds rectangles for debug -* -* #define RAYGUI_DEBUG_TEXT_BOUNDS -* Draw text bounds rectangles for debug -* -* VERSIONS HISTORY: -* 4.5-dev (Sep-2024) Current dev version... -* ADDED: guiControlExclusiveMode and guiControlExclusiveRec for exclusive modes -* ADDED: GuiValueBoxFloat() -* ADDED: GuiDropdonwBox() properties: DROPDOWN_ARROW_HIDDEN, DROPDOWN_ROLL_UP -* ADDED: GuiListView() property: LIST_ITEMS_BORDER_WIDTH -* ADDED: Multiple new icons -* REVIEWED: GuiTabBar(), close tab with mouse middle button -* REVIEWED: GuiScrollPanel(), scroll speed proportional to content -* REVIEWED: GuiDropdownBox(), support roll up and hidden arrow -* REVIEWED: GuiTextBox(), cursor position initialization -* REVIEWED: GuiSliderPro(), control value change check -* REVIEWED: GuiGrid(), simplified implementation -* REVIEWED: GuiIconText(), increase buffer size and reviewed padding -* REVIEWED: GuiDrawText(), improved wrap mode drawing -* REVIEWED: GuiScrollBar(), minor tweaks -* REVIEWED: Functions descriptions, removed wrong return value reference -* REDESIGNED: GuiColorPanel(), improved HSV <-> RGBA convertion -* -* 4.0 (12-Sep-2023) ADDED: GuiToggleSlider() -* ADDED: GuiColorPickerHSV() and GuiColorPanelHSV() -* ADDED: Multiple new icons, mostly compiler related -* ADDED: New DEFAULT properties: TEXT_LINE_SPACING, TEXT_ALIGNMENT_VERTICAL, TEXT_WRAP_MODE -* ADDED: New enum values: GuiTextAlignment, GuiTextAlignmentVertical, GuiTextWrapMode -* ADDED: Support loading styles with custom font charset from external file -* REDESIGNED: GuiTextBox(), support mouse cursor positioning -* REDESIGNED: GuiDrawText(), support multiline and word-wrap modes (read only) -* REDESIGNED: GuiProgressBar() to be more visual, progress affects border color -* REDESIGNED: Global alpha consideration moved to GuiDrawRectangle() and GuiDrawText() -* REDESIGNED: GuiScrollPanel(), get parameters by reference and return result value -* REDESIGNED: GuiToggleGroup(), get parameters by reference and return result value -* REDESIGNED: GuiComboBox(), get parameters by reference and return result value -* REDESIGNED: GuiCheckBox(), get parameters by reference and return result value -* REDESIGNED: GuiSlider(), get parameters by reference and return result value -* REDESIGNED: GuiSliderBar(), get parameters by reference and return result value -* REDESIGNED: GuiProgressBar(), get parameters by reference and return result value -* REDESIGNED: GuiListView(), get parameters by reference and return result value -* REDESIGNED: GuiColorPicker(), get parameters by reference and return result value -* REDESIGNED: GuiColorPanel(), get parameters by reference and return result value -* REDESIGNED: GuiColorBarAlpha(), get parameters by reference and return result value -* REDESIGNED: GuiColorBarHue(), get parameters by reference and return result value -* REDESIGNED: GuiGrid(), get parameters by reference and return result value -* REDESIGNED: GuiGrid(), added extra parameter -* REDESIGNED: GuiListViewEx(), change parameters order -* REDESIGNED: All controls return result as int value -* REVIEWED: GuiScrollPanel() to avoid smallish scroll-bars -* REVIEWED: All examples and specially controls_test_suite -* RENAMED: gui_file_dialog module to gui_window_file_dialog -* UPDATED: All styles to include ISO-8859-15 charset (as much as possible) -* -* 3.6 (10-May-2023) ADDED: New icon: SAND_TIMER -* ADDED: GuiLoadStyleFromMemory() (binary only) -* REVIEWED: GuiScrollBar() horizontal movement key -* REVIEWED: GuiTextBox() crash on cursor movement -* REVIEWED: GuiTextBox(), additional inputs support -* REVIEWED: GuiLabelButton(), avoid text cut -* REVIEWED: GuiTextInputBox(), password input -* REVIEWED: Local GetCodepointNext(), aligned with raylib -* REDESIGNED: GuiSlider*()/GuiScrollBar() to support out-of-bounds -* -* 3.5 (20-Apr-2023) ADDED: GuiTabBar(), based on GuiToggle() -* ADDED: Helper functions to split text in separate lines -* ADDED: Multiple new icons, useful for code editing tools -* REMOVED: Unneeded icon editing functions -* REMOVED: GuiTextBoxMulti(), very limited and broken -* REMOVED: MeasureTextEx() dependency, logic directly implemented -* REMOVED: DrawTextEx() dependency, logic directly implemented -* REVIEWED: GuiScrollBar(), improve mouse-click behaviour -* REVIEWED: Library header info, more info, better organized -* REDESIGNED: GuiTextBox() to support cursor movement -* REDESIGNED: GuiDrawText() to divide drawing by lines -* -* 3.2 (22-May-2022) RENAMED: Some enum values, for unification, avoiding prefixes -* REMOVED: GuiScrollBar(), only internal -* REDESIGNED: GuiPanel() to support text parameter -* REDESIGNED: GuiScrollPanel() to support text parameter -* REDESIGNED: GuiColorPicker() to support text parameter -* REDESIGNED: GuiColorPanel() to support text parameter -* REDESIGNED: GuiColorBarAlpha() to support text parameter -* REDESIGNED: GuiColorBarHue() to support text parameter -* REDESIGNED: GuiTextInputBox() to support password -* -* 3.1 (12-Jan-2022) REVIEWED: Default style for consistency (aligned with rGuiLayout v2.5 tool) -* REVIEWED: GuiLoadStyle() to support compressed font atlas image data and unload previous textures -* REVIEWED: External icons usage logic -* REVIEWED: GuiLine() for centered alignment when including text -* RENAMED: Multiple controls properties definitions to prepend RAYGUI_ -* RENAMED: RICON_ references to RAYGUI_ICON_ for library consistency -* Projects updated and multiple tweaks -* -* 3.0 (04-Nov-2021) Integrated ricons data to avoid external file -* REDESIGNED: GuiTextBoxMulti() -* REMOVED: GuiImageButton*() -* Multiple minor tweaks and bugs corrected -* -* 2.9 (17-Mar-2021) REMOVED: Tooltip API -* 2.8 (03-May-2020) Centralized rectangles drawing to GuiDrawRectangle() -* 2.7 (20-Feb-2020) ADDED: Possible tooltips API -* 2.6 (09-Sep-2019) ADDED: GuiTextInputBox() -* REDESIGNED: GuiListView*(), GuiDropdownBox(), GuiSlider*(), GuiProgressBar(), GuiMessageBox() -* REVIEWED: GuiTextBox(), GuiSpinner(), GuiValueBox(), GuiLoadStyle() -* Replaced property INNER_PADDING by TEXT_PADDING, renamed some properties -* ADDED: 8 new custom styles ready to use -* Multiple minor tweaks and bugs corrected -* -* 2.5 (28-May-2019) Implemented extended GuiTextBox(), GuiValueBox(), GuiSpinner() -* 2.3 (29-Apr-2019) ADDED: rIcons auxiliar library and support for it, multiple controls reviewed -* Refactor all controls drawing mechanism to use control state -* 2.2 (05-Feb-2019) ADDED: GuiScrollBar(), GuiScrollPanel(), reviewed GuiListView(), removed Gui*Ex() controls -* 2.1 (26-Dec-2018) REDESIGNED: GuiCheckBox(), GuiComboBox(), GuiDropdownBox(), GuiToggleGroup() > Use combined text string -* REDESIGNED: Style system (breaking change) -* 2.0 (08-Nov-2018) ADDED: Support controls guiLock and custom fonts -* REVIEWED: GuiComboBox(), GuiListView()... -* 1.9 (09-Oct-2018) REVIEWED: GuiGrid(), GuiTextBox(), GuiTextBoxMulti(), GuiValueBox()... -* 1.8 (01-May-2018) Lot of rework and redesign to align with rGuiStyler and rGuiLayout -* 1.5 (21-Jun-2017) Working in an improved styles system -* 1.4 (15-Jun-2017) Rewritten all GUI functions (removed useless ones) -* 1.3 (12-Jun-2017) Complete redesign of style system -* 1.1 (01-Jun-2017) Complete review of the library -* 1.0 (07-Jun-2016) Converted to header-only by Ramon Santamaria. -* 0.9 (07-Mar-2016) Reviewed and tested by Albert Martos, Ian Eito, Sergio Martinez and Ramon Santamaria. -* 0.8 (27-Aug-2015) Initial release. Implemented by Kevin Gato, Daniel Nicolás and Ramon Santamaria. -* -* DEPENDENCIES: -* raylib 5.0 - Inputs reading (keyboard/mouse), shapes drawing, font loading and text drawing -* -* STANDALONE MODE: -* By default raygui depends on raylib mostly for the inputs and the drawing functionality but that dependency can be disabled -* with the config flag RAYGUI_STANDALONE. In that case is up to the user to provide another backend to cover library needs. -* -* The following functions should be redefined for a custom backend: -* -* - Vector2 GetMousePosition(void); -* - float GetMouseWheelMove(void); -* - bool IsMouseButtonDown(int button); -* - bool IsMouseButtonPressed(int button); -* - bool IsMouseButtonReleased(int button); -* - bool IsKeyDown(int key); -* - bool IsKeyPressed(int key); -* - int GetCharPressed(void); // -- GuiTextBox(), GuiValueBox() -* -* - void DrawRectangle(int x, int y, int width, int height, Color color); // -- GuiDrawRectangle() -* - void DrawRectangleGradientEx(Rectangle rec, Color col1, Color col2, Color col3, Color col4); // -- GuiColorPicker() -* -* - Font GetFontDefault(void); // -- GuiLoadStyleDefault() -* - Font LoadFontEx(const char *fileName, int fontSize, int *codepoints, int codepointCount); // -- GuiLoadStyle() -* - Texture2D LoadTextureFromImage(Image image); // -- GuiLoadStyle(), required to load texture from embedded font atlas image -* - void SetShapesTexture(Texture2D tex, Rectangle rec); // -- GuiLoadStyle(), required to set shapes rec to font white rec (optimization) -* - char *LoadFileText(const char *fileName); // -- GuiLoadStyle(), required to load charset data -* - void UnloadFileText(char *text); // -- GuiLoadStyle(), required to unload charset data -* - const char *GetDirectoryPath(const char *filePath); // -- GuiLoadStyle(), required to find charset/font file from text .rgs -* - int *LoadCodepoints(const char *text, int *count); // -- GuiLoadStyle(), required to load required font codepoints list -* - void UnloadCodepoints(int *codepoints); // -- GuiLoadStyle(), required to unload codepoints list -* - unsigned char *DecompressData(const unsigned char *compData, int compDataSize, int *dataSize); // -- GuiLoadStyle() -* -* CONTRIBUTORS: -* Ramon Santamaria: Supervision, review, redesign, update and maintenance -* Vlad Adrian: Complete rewrite of GuiTextBox() to support extended features (2019) -* Sergio Martinez: Review, testing (2015) and redesign of multiple controls (2018) -* Adria Arranz: Testing and implementation of additional controls (2018) -* Jordi Jorba: Testing and implementation of additional controls (2018) -* Albert Martos: Review and testing of the library (2015) -* Ian Eito: Review and testing of the library (2015) -* Kevin Gato: Initial implementation of basic components (2014) -* Daniel Nicolas: Initial implementation of basic components (2014) -* -* -* LICENSE: zlib/libpng -* -* Copyright (c) 2014-2025 Ramon Santamaria (@raysan5) -* -* This software is provided "as-is", without any express or implied warranty. In no event -* will the authors be held liable for any damages arising from the use of this software. -* -* Permission is granted to anyone to use this software for any purpose, including commercial -* applications, and to alter it and redistribute it freely, subject to the following restrictions: -* -* 1. The origin of this software must not be misrepresented; you must not claim that you -* wrote the original software. If you use this software in a product, an acknowledgment -* in the product documentation would be appreciated but is not required. -* -* 2. Altered source versions must be plainly marked as such, and must not be misrepresented -* as being the original software. -* -* 3. This notice may not be removed or altered from any source distribution. -* -**********************************************************************************************/ - -#ifndef RAYGUI_H -#define RAYGUI_H - -#define RAYGUI_VERSION_MAJOR 4 -#define RAYGUI_VERSION_MINOR 5 -#define RAYGUI_VERSION_PATCH 0 -#define RAYGUI_VERSION "4.5-dev" - -#if !defined(RAYGUI_STANDALONE) - #include "raylib.h" -#endif - -// Function specifiers in case library is build/used as a shared library (Windows) -// NOTE: Microsoft specifiers to tell compiler that symbols are imported/exported from a .dll -#if defined(_WIN32) - #if defined(BUILD_LIBTYPE_SHARED) - #define RAYGUIAPI __declspec(dllexport) // We are building the library as a Win32 shared library (.dll) - #elif defined(USE_LIBTYPE_SHARED) - #define RAYGUIAPI __declspec(dllimport) // We are using the library as a Win32 shared library (.dll) - #endif -#endif - -// Function specifiers definition -#ifndef RAYGUIAPI - #define RAYGUIAPI // Functions defined as 'extern' by default (implicit specifiers) -#endif - -//---------------------------------------------------------------------------------- -// Defines and Macros -//---------------------------------------------------------------------------------- -// Allow custom memory allocators -#ifndef RAYGUI_MALLOC - #define RAYGUI_MALLOC(sz) malloc(sz) -#endif -#ifndef RAYGUI_CALLOC - #define RAYGUI_CALLOC(n,sz) calloc(n,sz) -#endif -#ifndef RAYGUI_FREE - #define RAYGUI_FREE(p) free(p) -#endif - -// Simple log system to avoid printf() calls if required -// NOTE: Avoiding those calls, also avoids const strings memory usage -#define RAYGUI_SUPPORT_LOG_INFO -#if defined(RAYGUI_SUPPORT_LOG_INFO) - #define RAYGUI_LOG(...) printf(__VA_ARGS__) -#else - #define RAYGUI_LOG(...) -#endif - -//---------------------------------------------------------------------------------- -// Types and Structures Definition -// NOTE: Some types are required for RAYGUI_STANDALONE usage -//---------------------------------------------------------------------------------- -#if defined(RAYGUI_STANDALONE) - #ifndef __cplusplus - // Boolean type - #ifndef true - typedef enum { false, true } bool; - #endif - #endif - - // Vector2 type - typedef struct Vector2 { - float x; - float y; - } Vector2; - - // Vector3 type // -- ConvertHSVtoRGB(), ConvertRGBtoHSV() - typedef struct Vector3 { - float x; - float y; - float z; - } Vector3; - - // Color type, RGBA (32bit) - typedef struct Color { - unsigned char r; - unsigned char g; - unsigned char b; - unsigned char a; - } Color; - - // Rectangle type - typedef struct Rectangle { - float x; - float y; - float width; - float height; - } Rectangle; - - // TODO: Texture2D type is very coupled to raylib, required by Font type - // It should be redesigned to be provided by user - typedef struct Texture2D { - unsigned int id; // OpenGL texture id - int width; // Texture base width - int height; // Texture base height - int mipmaps; // Mipmap levels, 1 by default - int format; // Data format (PixelFormat type) - } Texture2D; - - // Image, pixel data stored in CPU memory (RAM) - typedef struct Image { - void *data; // Image raw data - int width; // Image base width - int height; // Image base height - int mipmaps; // Mipmap levels, 1 by default - int format; // Data format (PixelFormat type) - } Image; - - // GlyphInfo, font characters glyphs info - typedef struct GlyphInfo { - int value; // Character value (Unicode) - int offsetX; // Character offset X when drawing - int offsetY; // Character offset Y when drawing - int advanceX; // Character advance position X - Image image; // Character image data - } GlyphInfo; - - // TODO: Font type is very coupled to raylib, mostly required by GuiLoadStyle() - // It should be redesigned to be provided by user - typedef struct Font { - int baseSize; // Base size (default chars height) - int glyphCount; // Number of glyph characters - int glyphPadding; // Padding around the glyph characters - Texture2D texture; // Texture atlas containing the glyphs - Rectangle *recs; // Rectangles in texture for the glyphs - GlyphInfo *glyphs; // Glyphs info data - } Font; -#endif - -// Style property -// NOTE: Used when exporting style as code for convenience -typedef struct GuiStyleProp { - unsigned short controlId; // Control identifier - unsigned short propertyId; // Property identifier - int propertyValue; // Property value -} GuiStyleProp; - -/* -// Controls text style -NOT USED- -// NOTE: Text style is defined by control -typedef struct GuiTextStyle { - unsigned int size; - int charSpacing; - int lineSpacing; - int alignmentH; - int alignmentV; - int padding; -} GuiTextStyle; -*/ - -// Gui control state -typedef enum { - STATE_NORMAL = 0, - STATE_FOCUSED, - STATE_PRESSED, - STATE_DISABLED -} GuiState; - -// Gui control text alignment -typedef enum { - TEXT_ALIGN_LEFT = 0, - TEXT_ALIGN_CENTER, - TEXT_ALIGN_RIGHT -} GuiTextAlignment; - -// Gui control text alignment vertical -// NOTE: Text vertical position inside the text bounds -typedef enum { - TEXT_ALIGN_TOP = 0, - TEXT_ALIGN_MIDDLE, - TEXT_ALIGN_BOTTOM -} GuiTextAlignmentVertical; - -// Gui control text wrap mode -// NOTE: Useful for multiline text -typedef enum { - TEXT_WRAP_NONE = 0, - TEXT_WRAP_CHAR, - TEXT_WRAP_WORD -} GuiTextWrapMode; - -// Gui controls -typedef enum { - // Default -> populates to all controls when set - DEFAULT = 0, - - // Basic controls - LABEL, // Used also for: LABELBUTTON - BUTTON, - TOGGLE, // Used also for: TOGGLEGROUP - SLIDER, // Used also for: SLIDERBAR, TOGGLESLIDER - PROGRESSBAR, - CHECKBOX, - COMBOBOX, - DROPDOWNBOX, - TEXTBOX, // Used also for: TEXTBOXMULTI - VALUEBOX, - SPINNER, // Uses: BUTTON, VALUEBOX - LISTVIEW, - COLORPICKER, - SCROLLBAR, - STATUSBAR -} GuiControl; - -// Gui base properties for every control -// NOTE: RAYGUI_MAX_PROPS_BASE properties (by default 16 properties) -typedef enum { - BORDER_COLOR_NORMAL = 0, // Control border color in STATE_NORMAL - BASE_COLOR_NORMAL, // Control base color in STATE_NORMAL - TEXT_COLOR_NORMAL, // Control text color in STATE_NORMAL - BORDER_COLOR_FOCUSED, // Control border color in STATE_FOCUSED - BASE_COLOR_FOCUSED, // Control base color in STATE_FOCUSED - TEXT_COLOR_FOCUSED, // Control text color in STATE_FOCUSED - BORDER_COLOR_PRESSED, // Control border color in STATE_PRESSED - BASE_COLOR_PRESSED, // Control base color in STATE_PRESSED - TEXT_COLOR_PRESSED, // Control text color in STATE_PRESSED - BORDER_COLOR_DISABLED, // Control border color in STATE_DISABLED - BASE_COLOR_DISABLED, // Control base color in STATE_DISABLED - TEXT_COLOR_DISABLED, // Control text color in STATE_DISABLED - BORDER_WIDTH, // Control border size, 0 for no border - //TEXT_SIZE, // Control text size (glyphs max height) -> GLOBAL for all controls - //TEXT_SPACING, // Control text spacing between glyphs -> GLOBAL for all controls - //TEXT_LINE_SPACING // Control text spacing between lines -> GLOBAL for all controls - TEXT_PADDING, // Control text padding, not considering border - TEXT_ALIGNMENT, // Control text horizontal alignment inside control text bound (after border and padding) - //TEXT_WRAP_MODE // Control text wrap-mode inside text bounds -> GLOBAL for all controls -} GuiControlProperty; - -// TODO: Which text styling properties should be global or per-control? -// At this moment TEXT_PADDING and TEXT_ALIGNMENT is configured and saved per control while -// TEXT_SIZE, TEXT_SPACING, TEXT_LINE_SPACING, TEXT_ALIGNMENT_VERTICAL, TEXT_WRAP_MODE are global and -// should be configured by user as needed while defining the UI layout - -// Gui extended properties depend on control -// NOTE: RAYGUI_MAX_PROPS_EXTENDED properties (by default, max 8 properties) -//---------------------------------------------------------------------------------- -// DEFAULT extended properties -// NOTE: Those properties are common to all controls or global -// WARNING: We only have 8 slots for those properties by default!!! -> New global control: TEXT? -typedef enum { - TEXT_SIZE = 16, // Text size (glyphs max height) - TEXT_SPACING, // Text spacing between glyphs - LINE_COLOR, // Line control color - BACKGROUND_COLOR, // Background color - TEXT_LINE_SPACING, // Text spacing between lines - TEXT_ALIGNMENT_VERTICAL, // Text vertical alignment inside text bounds (after border and padding) - TEXT_WRAP_MODE // Text wrap-mode inside text bounds - //TEXT_DECORATION // Text decoration: 0-None, 1-Underline, 2-Line-through, 3-Overline - //TEXT_DECORATION_THICK // Text decoration line thickness -} GuiDefaultProperty; - -// Other possible text properties: -// TEXT_WEIGHT // Normal, Italic, Bold -> Requires specific font change -// TEXT_INDENT // Text indentation -> Now using TEXT_PADDING... - -// Label -//typedef enum { } GuiLabelProperty; - -// Button/Spinner -//typedef enum { } GuiButtonProperty; - -// Toggle/ToggleGroup -typedef enum { - GROUP_PADDING = 16, // ToggleGroup separation between toggles -} GuiToggleProperty; - -// Slider/SliderBar -typedef enum { - SLIDER_WIDTH = 16, // Slider size of internal bar - SLIDER_PADDING // Slider/SliderBar internal bar padding -} GuiSliderProperty; - -// ProgressBar -typedef enum { - PROGRESS_PADDING = 16, // ProgressBar internal padding -} GuiProgressBarProperty; - -// ScrollBar -typedef enum { - ARROWS_SIZE = 16, // ScrollBar arrows size - ARROWS_VISIBLE, // ScrollBar arrows visible - SCROLL_SLIDER_PADDING, // ScrollBar slider internal padding - SCROLL_SLIDER_SIZE, // ScrollBar slider size - SCROLL_PADDING, // ScrollBar scroll padding from arrows - SCROLL_SPEED, // ScrollBar scrolling speed -} GuiScrollBarProperty; - -// CheckBox -typedef enum { - CHECK_PADDING = 16 // CheckBox internal check padding -} GuiCheckBoxProperty; - -// ComboBox -typedef enum { - COMBO_BUTTON_WIDTH = 16, // ComboBox right button width - COMBO_BUTTON_SPACING // ComboBox button separation -} GuiComboBoxProperty; - -// DropdownBox -typedef enum { - ARROW_PADDING = 16, // DropdownBox arrow separation from border and items - DROPDOWN_ITEMS_SPACING, // DropdownBox items separation - DROPDOWN_ARROW_HIDDEN, // DropdownBox arrow hidden - DROPDOWN_ROLL_UP // DropdownBox roll up flag (default rolls down) -} GuiDropdownBoxProperty; - -// TextBox/TextBoxMulti/ValueBox/Spinner -typedef enum { - TEXT_READONLY = 16, // TextBox in read-only mode: 0-text editable, 1-text no-editable -} GuiTextBoxProperty; - -// Spinner -typedef enum { - SPIN_BUTTON_WIDTH = 16, // Spinner left/right buttons width - SPIN_BUTTON_SPACING, // Spinner buttons separation -} GuiSpinnerProperty; - -// ListView -typedef enum { - LIST_ITEMS_HEIGHT = 16, // ListView items height - LIST_ITEMS_SPACING, // ListView items separation - SCROLLBAR_WIDTH, // ListView scrollbar size (usually width) - SCROLLBAR_SIDE, // ListView scrollbar side (0-SCROLLBAR_LEFT_SIDE, 1-SCROLLBAR_RIGHT_SIDE) - LIST_ITEMS_BORDER_WIDTH // ListView items border width -} GuiListViewProperty; - -// ColorPicker -typedef enum { - COLOR_SELECTOR_SIZE = 16, - HUEBAR_WIDTH, // ColorPicker right hue bar width - HUEBAR_PADDING, // ColorPicker right hue bar separation from panel - HUEBAR_SELECTOR_HEIGHT, // ColorPicker right hue bar selector height - HUEBAR_SELECTOR_OVERFLOW // ColorPicker right hue bar selector overflow -} GuiColorPickerProperty; - -#define SCROLLBAR_LEFT_SIDE 0 -#define SCROLLBAR_RIGHT_SIDE 1 - -//---------------------------------------------------------------------------------- -// Global Variables Definition -//---------------------------------------------------------------------------------- -// ... - -//---------------------------------------------------------------------------------- -// Module Functions Declaration -//---------------------------------------------------------------------------------- - -#if defined(__cplusplus) -extern "C" { // Prevents name mangling of functions -#endif - -// Global gui state control functions -RAYGUIAPI void GuiEnable(void); // Enable gui controls (global state) -RAYGUIAPI void GuiDisable(void); // Disable gui controls (global state) -RAYGUIAPI void GuiLock(void); // Lock gui controls (global state) -RAYGUIAPI void GuiUnlock(void); // Unlock gui controls (global state) -RAYGUIAPI bool GuiIsLocked(void); // Check if gui is locked (global state) -RAYGUIAPI void GuiSetAlpha(float alpha); // Set gui controls alpha (global state), alpha goes from 0.0f to 1.0f -RAYGUIAPI void GuiSetState(int state); // Set gui state (global state) -RAYGUIAPI int GuiGetState(void); // Get gui state (global state) - -// Font set/get functions -RAYGUIAPI void GuiSetFont(Font font); // Set gui custom font (global state) -RAYGUIAPI Font GuiGetFont(void); // Get gui custom font (global state) - -// Style set/get functions -RAYGUIAPI void GuiSetStyle(int control, int property, int value); // Set one style property -RAYGUIAPI int GuiGetStyle(int control, int property); // Get one style property - -// Styles loading functions -RAYGUIAPI void GuiLoadStyle(const char *fileName); // Load style file over global style variable (.rgs) -RAYGUIAPI void GuiLoadStyleDefault(void); // Load style default over global style - -// Tooltips management functions -RAYGUIAPI void GuiEnableTooltip(void); // Enable gui tooltips (global state) -RAYGUIAPI void GuiDisableTooltip(void); // Disable gui tooltips (global state) -RAYGUIAPI void GuiSetTooltip(const char *tooltip); // Set tooltip string - -// Icons functionality -RAYGUIAPI const char *GuiIconText(int iconId, const char *text); // Get text with icon id prepended (if supported) -#if !defined(RAYGUI_NO_ICONS) -RAYGUIAPI void GuiSetIconScale(int scale); // Set default icon drawing size -RAYGUIAPI unsigned int *GuiGetIcons(void); // Get raygui icons data pointer -RAYGUIAPI char **GuiLoadIcons(const char *fileName, bool loadIconsName); // Load raygui icons file (.rgi) into internal icons data -RAYGUIAPI void GuiDrawIcon(int iconId, int posX, int posY, int pixelSize, Color color); // Draw icon using pixel size at specified position -#endif - -// Controls -//---------------------------------------------------------------------------------------------------------- -// Container/separator controls, useful for controls organization -RAYGUIAPI int GuiWindowBox(Rectangle bounds, const char *title); // Window Box control, shows a window that can be closed -RAYGUIAPI int GuiGroupBox(Rectangle bounds, const char *text); // Group Box control with text name -RAYGUIAPI int GuiLine(Rectangle bounds, const char *text); // Line separator control, could contain text -RAYGUIAPI int GuiPanel(Rectangle bounds, const char *text); // Panel control, useful to group controls -RAYGUIAPI int GuiTabBar(Rectangle bounds, const char **text, int count, int *active); // Tab Bar control, returns TAB to be closed or -1 -RAYGUIAPI int GuiScrollPanel(Rectangle bounds, const char *text, Rectangle content, Vector2 *scroll, Rectangle *view); // Scroll Panel control - -// Basic controls set -RAYGUIAPI int GuiLabel(Rectangle bounds, const char *text); // Label control -RAYGUIAPI int GuiButton(Rectangle bounds, const char *text); // Button control, returns true when clicked -RAYGUIAPI int GuiLabelButton(Rectangle bounds, const char *text); // Label button control, returns true when clicked -RAYGUIAPI int GuiToggle(Rectangle bounds, const char *text, bool *active); // Toggle Button control -RAYGUIAPI int GuiToggleGroup(Rectangle bounds, const char *text, int *active); // Toggle Group control -RAYGUIAPI int GuiToggleSlider(Rectangle bounds, const char *text, int *active); // Toggle Slider control -RAYGUIAPI int GuiCheckBox(Rectangle bounds, const char *text, bool *checked); // Check Box control, returns true when active -RAYGUIAPI int GuiComboBox(Rectangle bounds, const char *text, int *active); // Combo Box control - -RAYGUIAPI int GuiDropdownBox(Rectangle bounds, const char *text, int *active, bool editMode); // Dropdown Box control -RAYGUIAPI int GuiSpinner(Rectangle bounds, const char *text, int *value, int minValue, int maxValue, bool editMode); // Spinner control -RAYGUIAPI int GuiValueBox(Rectangle bounds, const char *text, int *value, int minValue, int maxValue, bool editMode); // Value Box control, updates input text with numbers -RAYGUIAPI int GuiValueBoxFloat(Rectangle bounds, const char *text, char *textValue, float *value, bool editMode); // Value box control for float values -RAYGUIAPI int GuiTextBox(Rectangle bounds, char *text, int textSize, bool editMode); // Text Box control, updates input text - -RAYGUIAPI int GuiSlider(Rectangle bounds, const char *textLeft, const char *textRight, float *value, float minValue, float maxValue); // Slider control -RAYGUIAPI int GuiSliderBar(Rectangle bounds, const char *textLeft, const char *textRight, float *value, float minValue, float maxValue); // Slider Bar control -RAYGUIAPI int GuiProgressBar(Rectangle bounds, const char *textLeft, const char *textRight, float *value, float minValue, float maxValue); // Progress Bar control -RAYGUIAPI int GuiStatusBar(Rectangle bounds, const char *text); // Status Bar control, shows info text -RAYGUIAPI int GuiDummyRec(Rectangle bounds, const char *text); // Dummy control for placeholders -RAYGUIAPI int GuiGrid(Rectangle bounds, const char *text, float spacing, int subdivs, Vector2 *mouseCell); // Grid control - -// Advance controls set -RAYGUIAPI int GuiListView(Rectangle bounds, const char *text, int *scrollIndex, int *active); // List View control -RAYGUIAPI int GuiListViewEx(Rectangle bounds, const char **text, int count, int *scrollIndex, int *active, int *focus); // List View with extended parameters -RAYGUIAPI int GuiMessageBox(Rectangle bounds, const char *title, const char *message, const char *buttons); // Message Box control, displays a message -RAYGUIAPI int GuiTextInputBox(Rectangle bounds, const char *title, const char *message, const char *buttons, char *text, int textMaxSize, bool *secretViewActive); // Text Input Box control, ask for text, supports secret -RAYGUIAPI int GuiColorPicker(Rectangle bounds, const char *text, Color *color); // Color Picker control (multiple color controls) -RAYGUIAPI int GuiColorPanel(Rectangle bounds, const char *text, Color *color); // Color Panel control -RAYGUIAPI int GuiColorBarAlpha(Rectangle bounds, const char *text, float *alpha); // Color Bar Alpha control -RAYGUIAPI int GuiColorBarHue(Rectangle bounds, const char *text, float *value); // Color Bar Hue control -RAYGUIAPI int GuiColorPickerHSV(Rectangle bounds, const char *text, Vector3 *colorHsv); // Color Picker control that avoids conversion to RGB on each call (multiple color controls) -RAYGUIAPI int GuiColorPanelHSV(Rectangle bounds, const char *text, Vector3 *colorHsv); // Color Panel control that updates Hue-Saturation-Value color value, used by GuiColorPickerHSV() -//---------------------------------------------------------------------------------------------------------- - -#if !defined(RAYGUI_NO_ICONS) - -#if !defined(RAYGUI_CUSTOM_ICONS) -//---------------------------------------------------------------------------------- -// Icons enumeration -//---------------------------------------------------------------------------------- -typedef enum { - ICON_NONE = 0, - ICON_FOLDER_FILE_OPEN = 1, - ICON_FILE_SAVE_CLASSIC = 2, - ICON_FOLDER_OPEN = 3, - ICON_FOLDER_SAVE = 4, - ICON_FILE_OPEN = 5, - ICON_FILE_SAVE = 6, - ICON_FILE_EXPORT = 7, - ICON_FILE_ADD = 8, - ICON_FILE_DELETE = 9, - ICON_FILETYPE_TEXT = 10, - ICON_FILETYPE_AUDIO = 11, - ICON_FILETYPE_IMAGE = 12, - ICON_FILETYPE_PLAY = 13, - ICON_FILETYPE_VIDEO = 14, - ICON_FILETYPE_INFO = 15, - ICON_FILE_COPY = 16, - ICON_FILE_CUT = 17, - ICON_FILE_PASTE = 18, - ICON_CURSOR_HAND = 19, - ICON_CURSOR_POINTER = 20, - ICON_CURSOR_CLASSIC = 21, - ICON_PENCIL = 22, - ICON_PENCIL_BIG = 23, - ICON_BRUSH_CLASSIC = 24, - ICON_BRUSH_PAINTER = 25, - ICON_WATER_DROP = 26, - ICON_COLOR_PICKER = 27, - ICON_RUBBER = 28, - ICON_COLOR_BUCKET = 29, - ICON_TEXT_T = 30, - ICON_TEXT_A = 31, - ICON_SCALE = 32, - ICON_RESIZE = 33, - ICON_FILTER_POINT = 34, - ICON_FILTER_BILINEAR = 35, - ICON_CROP = 36, - ICON_CROP_ALPHA = 37, - ICON_SQUARE_TOGGLE = 38, - ICON_SYMMETRY = 39, - ICON_SYMMETRY_HORIZONTAL = 40, - ICON_SYMMETRY_VERTICAL = 41, - ICON_LENS = 42, - ICON_LENS_BIG = 43, - ICON_EYE_ON = 44, - ICON_EYE_OFF = 45, - ICON_FILTER_TOP = 46, - ICON_FILTER = 47, - ICON_TARGET_POINT = 48, - ICON_TARGET_SMALL = 49, - ICON_TARGET_BIG = 50, - ICON_TARGET_MOVE = 51, - ICON_CURSOR_MOVE = 52, - ICON_CURSOR_SCALE = 53, - ICON_CURSOR_SCALE_RIGHT = 54, - ICON_CURSOR_SCALE_LEFT = 55, - ICON_UNDO = 56, - ICON_REDO = 57, - ICON_REREDO = 58, - ICON_MUTATE = 59, - ICON_ROTATE = 60, - ICON_REPEAT = 61, - ICON_SHUFFLE = 62, - ICON_EMPTYBOX = 63, - ICON_TARGET = 64, - ICON_TARGET_SMALL_FILL = 65, - ICON_TARGET_BIG_FILL = 66, - ICON_TARGET_MOVE_FILL = 67, - ICON_CURSOR_MOVE_FILL = 68, - ICON_CURSOR_SCALE_FILL = 69, - ICON_CURSOR_SCALE_RIGHT_FILL = 70, - ICON_CURSOR_SCALE_LEFT_FILL = 71, - ICON_UNDO_FILL = 72, - ICON_REDO_FILL = 73, - ICON_REREDO_FILL = 74, - ICON_MUTATE_FILL = 75, - ICON_ROTATE_FILL = 76, - ICON_REPEAT_FILL = 77, - ICON_SHUFFLE_FILL = 78, - ICON_EMPTYBOX_SMALL = 79, - ICON_BOX = 80, - ICON_BOX_TOP = 81, - ICON_BOX_TOP_RIGHT = 82, - ICON_BOX_RIGHT = 83, - ICON_BOX_BOTTOM_RIGHT = 84, - ICON_BOX_BOTTOM = 85, - ICON_BOX_BOTTOM_LEFT = 86, - ICON_BOX_LEFT = 87, - ICON_BOX_TOP_LEFT = 88, - ICON_BOX_CENTER = 89, - ICON_BOX_CIRCLE_MASK = 90, - ICON_POT = 91, - ICON_ALPHA_MULTIPLY = 92, - ICON_ALPHA_CLEAR = 93, - ICON_DITHERING = 94, - ICON_MIPMAPS = 95, - ICON_BOX_GRID = 96, - ICON_GRID = 97, - ICON_BOX_CORNERS_SMALL = 98, - ICON_BOX_CORNERS_BIG = 99, - ICON_FOUR_BOXES = 100, - ICON_GRID_FILL = 101, - ICON_BOX_MULTISIZE = 102, - ICON_ZOOM_SMALL = 103, - ICON_ZOOM_MEDIUM = 104, - ICON_ZOOM_BIG = 105, - ICON_ZOOM_ALL = 106, - ICON_ZOOM_CENTER = 107, - ICON_BOX_DOTS_SMALL = 108, - ICON_BOX_DOTS_BIG = 109, - ICON_BOX_CONCENTRIC = 110, - ICON_BOX_GRID_BIG = 111, - ICON_OK_TICK = 112, - ICON_CROSS = 113, - ICON_ARROW_LEFT = 114, - ICON_ARROW_RIGHT = 115, - ICON_ARROW_DOWN = 116, - ICON_ARROW_UP = 117, - ICON_ARROW_LEFT_FILL = 118, - ICON_ARROW_RIGHT_FILL = 119, - ICON_ARROW_DOWN_FILL = 120, - ICON_ARROW_UP_FILL = 121, - ICON_AUDIO = 122, - ICON_FX = 123, - ICON_WAVE = 124, - ICON_WAVE_SINUS = 125, - ICON_WAVE_SQUARE = 126, - ICON_WAVE_TRIANGULAR = 127, - ICON_CROSS_SMALL = 128, - ICON_PLAYER_PREVIOUS = 129, - ICON_PLAYER_PLAY_BACK = 130, - ICON_PLAYER_PLAY = 131, - ICON_PLAYER_PAUSE = 132, - ICON_PLAYER_STOP = 133, - ICON_PLAYER_NEXT = 134, - ICON_PLAYER_RECORD = 135, - ICON_MAGNET = 136, - ICON_LOCK_CLOSE = 137, - ICON_LOCK_OPEN = 138, - ICON_CLOCK = 139, - ICON_TOOLS = 140, - ICON_GEAR = 141, - ICON_GEAR_BIG = 142, - ICON_BIN = 143, - ICON_HAND_POINTER = 144, - ICON_LASER = 145, - ICON_COIN = 146, - ICON_EXPLOSION = 147, - ICON_1UP = 148, - ICON_PLAYER = 149, - ICON_PLAYER_JUMP = 150, - ICON_KEY = 151, - ICON_DEMON = 152, - ICON_TEXT_POPUP = 153, - ICON_GEAR_EX = 154, - ICON_CRACK = 155, - ICON_CRACK_POINTS = 156, - ICON_STAR = 157, - ICON_DOOR = 158, - ICON_EXIT = 159, - ICON_MODE_2D = 160, - ICON_MODE_3D = 161, - ICON_CUBE = 162, - ICON_CUBE_FACE_TOP = 163, - ICON_CUBE_FACE_LEFT = 164, - ICON_CUBE_FACE_FRONT = 165, - ICON_CUBE_FACE_BOTTOM = 166, - ICON_CUBE_FACE_RIGHT = 167, - ICON_CUBE_FACE_BACK = 168, - ICON_CAMERA = 169, - ICON_SPECIAL = 170, - ICON_LINK_NET = 171, - ICON_LINK_BOXES = 172, - ICON_LINK_MULTI = 173, - ICON_LINK = 174, - ICON_LINK_BROKE = 175, - ICON_TEXT_NOTES = 176, - ICON_NOTEBOOK = 177, - ICON_SUITCASE = 178, - ICON_SUITCASE_ZIP = 179, - ICON_MAILBOX = 180, - ICON_MONITOR = 181, - ICON_PRINTER = 182, - ICON_PHOTO_CAMERA = 183, - ICON_PHOTO_CAMERA_FLASH = 184, - ICON_HOUSE = 185, - ICON_HEART = 186, - ICON_CORNER = 187, - ICON_VERTICAL_BARS = 188, - ICON_VERTICAL_BARS_FILL = 189, - ICON_LIFE_BARS = 190, - ICON_INFO = 191, - ICON_CROSSLINE = 192, - ICON_HELP = 193, - ICON_FILETYPE_ALPHA = 194, - ICON_FILETYPE_HOME = 195, - ICON_LAYERS_VISIBLE = 196, - ICON_LAYERS = 197, - ICON_WINDOW = 198, - ICON_HIDPI = 199, - ICON_FILETYPE_BINARY = 200, - ICON_HEX = 201, - ICON_SHIELD = 202, - ICON_FILE_NEW = 203, - ICON_FOLDER_ADD = 204, - ICON_ALARM = 205, - ICON_CPU = 206, - ICON_ROM = 207, - ICON_STEP_OVER = 208, - ICON_STEP_INTO = 209, - ICON_STEP_OUT = 210, - ICON_RESTART = 211, - ICON_BREAKPOINT_ON = 212, - ICON_BREAKPOINT_OFF = 213, - ICON_BURGER_MENU = 214, - ICON_CASE_SENSITIVE = 215, - ICON_REG_EXP = 216, - ICON_FOLDER = 217, - ICON_FILE = 218, - ICON_SAND_TIMER = 219, - ICON_WARNING = 220, - ICON_HELP_BOX = 221, - ICON_INFO_BOX = 222, - ICON_PRIORITY = 223, - ICON_LAYERS_ISO = 224, - ICON_LAYERS2 = 225, - ICON_MLAYERS = 226, - ICON_MAPS = 227, - ICON_HOT = 228, - ICON_229 = 229, - ICON_230 = 230, - ICON_231 = 231, - ICON_232 = 232, - ICON_233 = 233, - ICON_234 = 234, - ICON_235 = 235, - ICON_236 = 236, - ICON_237 = 237, - ICON_238 = 238, - ICON_239 = 239, - ICON_240 = 240, - ICON_241 = 241, - ICON_242 = 242, - ICON_243 = 243, - ICON_244 = 244, - ICON_245 = 245, - ICON_246 = 246, - ICON_247 = 247, - ICON_248 = 248, - ICON_249 = 249, - ICON_250 = 250, - ICON_251 = 251, - ICON_252 = 252, - ICON_253 = 253, - ICON_254 = 254, - ICON_255 = 255, -} GuiIconName; -#endif - -#endif - -#if defined(__cplusplus) -} // Prevents name mangling of functions -#endif - -#endif // RAYGUI_H - -/*********************************************************************************** -* -* RAYGUI IMPLEMENTATION -* -************************************************************************************/ - -#if defined(RAYGUI_IMPLEMENTATION) - -#include // required for: isspace() [GuiTextBox()] -#include // Required for: FILE, fopen(), fclose(), fprintf(), feof(), fscanf(), vsprintf() [GuiLoadStyle(), GuiLoadIcons()] -#include // Required for: malloc(), calloc(), free() [GuiLoadStyle(), GuiLoadIcons()] -#include // Required for: strlen() [GuiTextBox(), GuiValueBox()], memset(), memcpy() -#include // Required for: va_list, va_start(), vfprintf(), va_end() [TextFormat()] -#include // Required for: roundf() [GuiColorPicker()] - -#ifdef __cplusplus - #define RAYGUI_CLITERAL(name) name -#else - #define RAYGUI_CLITERAL(name) (name) -#endif - -// Check if two rectangles are equal, used to validate a slider bounds as an id -#ifndef CHECK_BOUNDS_ID - #define CHECK_BOUNDS_ID(src, dst) ((src.x == dst.x) && (src.y == dst.y) && (src.width == dst.width) && (src.height == dst.height)) -#endif - -#if !defined(RAYGUI_NO_ICONS) && !defined(RAYGUI_CUSTOM_ICONS) - -// Embedded icons, no external file provided -#define RAYGUI_ICON_SIZE 16 // Size of icons in pixels (squared) -#define RAYGUI_ICON_MAX_ICONS 256 // Maximum number of icons -#define RAYGUI_ICON_MAX_NAME_LENGTH 32 // Maximum length of icon name id - -// Icons data is defined by bit array (every bit represents one pixel) -// Those arrays are stored as unsigned int data arrays, so, -// every array element defines 32 pixels (bits) of information -// One icon is defined by 8 int, (8 int * 32 bit = 256 bit = 16*16 pixels) -// NOTE: Number of elemens depend on RAYGUI_ICON_SIZE (by default 16x16 pixels) -#define RAYGUI_ICON_DATA_ELEMENTS (RAYGUI_ICON_SIZE*RAYGUI_ICON_SIZE/32) - -//---------------------------------------------------------------------------------- -// Icons data for all gui possible icons (allocated on data segment by default) -// -// NOTE 1: Every icon is codified in binary form, using 1 bit per pixel, so, -// every 16x16 icon requires 8 integers (16*16/32) to be stored -// -// NOTE 2: A different icon set could be loaded over this array using GuiLoadIcons(), -// but loaded icons set must be same RAYGUI_ICON_SIZE and no more than RAYGUI_ICON_MAX_ICONS -// -// guiIcons size is by default: 256*(16*16/32) = 2048*4 = 8192 bytes = 8 KB -//---------------------------------------------------------------------------------- -static unsigned int guiIcons[RAYGUI_ICON_MAX_ICONS*RAYGUI_ICON_DATA_ELEMENTS] = { - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_NONE - 0x3ff80000, 0x2f082008, 0x2042207e, 0x40027fc2, 0x40024002, 0x40024002, 0x40024002, 0x00007ffe, // ICON_FOLDER_FILE_OPEN - 0x3ffe0000, 0x44226422, 0x400247e2, 0x5ffa4002, 0x57ea500a, 0x500a500a, 0x40025ffa, 0x00007ffe, // ICON_FILE_SAVE_CLASSIC - 0x00000000, 0x0042007e, 0x40027fc2, 0x40024002, 0x41024002, 0x44424282, 0x793e4102, 0x00000100, // ICON_FOLDER_OPEN - 0x00000000, 0x0042007e, 0x40027fc2, 0x40024002, 0x41024102, 0x44424102, 0x793e4282, 0x00000000, // ICON_FOLDER_SAVE - 0x3ff00000, 0x201c2010, 0x20042004, 0x21042004, 0x24442284, 0x21042104, 0x20042104, 0x00003ffc, // ICON_FILE_OPEN - 0x3ff00000, 0x201c2010, 0x20042004, 0x21042004, 0x21042104, 0x22842444, 0x20042104, 0x00003ffc, // ICON_FILE_SAVE - 0x3ff00000, 0x201c2010, 0x00042004, 0x20041004, 0x20844784, 0x00841384, 0x20042784, 0x00003ffc, // ICON_FILE_EXPORT - 0x3ff00000, 0x201c2010, 0x20042004, 0x20042004, 0x22042204, 0x22042f84, 0x20042204, 0x00003ffc, // ICON_FILE_ADD - 0x3ff00000, 0x201c2010, 0x20042004, 0x20042004, 0x25042884, 0x25042204, 0x20042884, 0x00003ffc, // ICON_FILE_DELETE - 0x3ff00000, 0x201c2010, 0x20042004, 0x20042ff4, 0x20042ff4, 0x20042ff4, 0x20042004, 0x00003ffc, // ICON_FILETYPE_TEXT - 0x3ff00000, 0x201c2010, 0x27042004, 0x244424c4, 0x26442444, 0x20642664, 0x20042004, 0x00003ffc, // ICON_FILETYPE_AUDIO - 0x3ff00000, 0x201c2010, 0x26042604, 0x20042004, 0x35442884, 0x2414222c, 0x20042004, 0x00003ffc, // ICON_FILETYPE_IMAGE - 0x3ff00000, 0x201c2010, 0x20c42004, 0x22442144, 0x22442444, 0x20c42144, 0x20042004, 0x00003ffc, // ICON_FILETYPE_PLAY - 0x3ff00000, 0x3ffc2ff0, 0x3f3c2ff4, 0x3dbc2eb4, 0x3dbc2bb4, 0x3f3c2eb4, 0x3ffc2ff4, 0x00002ff4, // ICON_FILETYPE_VIDEO - 0x3ff00000, 0x201c2010, 0x21842184, 0x21842004, 0x21842184, 0x21842184, 0x20042184, 0x00003ffc, // ICON_FILETYPE_INFO - 0x0ff00000, 0x381c0810, 0x28042804, 0x28042804, 0x28042804, 0x28042804, 0x20102ffc, 0x00003ff0, // ICON_FILE_COPY - 0x00000000, 0x701c0000, 0x079c1e14, 0x55a000f0, 0x079c00f0, 0x701c1e14, 0x00000000, 0x00000000, // ICON_FILE_CUT - 0x01c00000, 0x13e41bec, 0x3f841004, 0x204420c4, 0x20442044, 0x20442044, 0x207c2044, 0x00003fc0, // ICON_FILE_PASTE - 0x00000000, 0x3aa00fe0, 0x2abc2aa0, 0x2aa42aa4, 0x20042aa4, 0x20042004, 0x3ffc2004, 0x00000000, // ICON_CURSOR_HAND - 0x00000000, 0x003c000c, 0x030800c8, 0x30100c10, 0x10202020, 0x04400840, 0x01800280, 0x00000000, // ICON_CURSOR_POINTER - 0x00000000, 0x00180000, 0x01f00078, 0x03e007f0, 0x07c003e0, 0x04000e40, 0x00000000, 0x00000000, // ICON_CURSOR_CLASSIC - 0x00000000, 0x04000000, 0x11000a00, 0x04400a80, 0x01100220, 0x00580088, 0x00000038, 0x00000000, // ICON_PENCIL - 0x04000000, 0x15000a00, 0x50402880, 0x14102820, 0x05040a08, 0x015c028c, 0x007c00bc, 0x00000000, // ICON_PENCIL_BIG - 0x01c00000, 0x01400140, 0x01400140, 0x0ff80140, 0x0ff80808, 0x0aa80808, 0x0aa80aa8, 0x00000ff8, // ICON_BRUSH_CLASSIC - 0x1ffc0000, 0x5ffc7ffe, 0x40004000, 0x00807f80, 0x01c001c0, 0x01c001c0, 0x01c001c0, 0x00000080, // ICON_BRUSH_PAINTER - 0x00000000, 0x00800000, 0x01c00080, 0x03e001c0, 0x07f003e0, 0x036006f0, 0x000001c0, 0x00000000, // ICON_WATER_DROP - 0x00000000, 0x3e003800, 0x1f803f80, 0x0c201e40, 0x02080c10, 0x00840104, 0x00380044, 0x00000000, // ICON_COLOR_PICKER - 0x00000000, 0x07800300, 0x1fe00fc0, 0x3f883fd0, 0x0e021f04, 0x02040402, 0x00f00108, 0x00000000, // ICON_RUBBER - 0x00c00000, 0x02800140, 0x08200440, 0x20081010, 0x2ffe3004, 0x03f807fc, 0x00e001f0, 0x00000040, // ICON_COLOR_BUCKET - 0x00000000, 0x21843ffc, 0x01800180, 0x01800180, 0x01800180, 0x01800180, 0x03c00180, 0x00000000, // ICON_TEXT_T - 0x00800000, 0x01400180, 0x06200340, 0x0c100620, 0x1ff80c10, 0x380c1808, 0x70067004, 0x0000f80f, // ICON_TEXT_A - 0x78000000, 0x50004000, 0x00004800, 0x03c003c0, 0x03c003c0, 0x00100000, 0x0002000a, 0x0000000e, // ICON_SCALE - 0x75560000, 0x5e004002, 0x54001002, 0x41001202, 0x408200fe, 0x40820082, 0x40820082, 0x00006afe, // ICON_RESIZE - 0x00000000, 0x3f003f00, 0x3f003f00, 0x3f003f00, 0x00400080, 0x001c0020, 0x001c001c, 0x00000000, // ICON_FILTER_POINT - 0x6d800000, 0x00004080, 0x40804080, 0x40800000, 0x00406d80, 0x001c0020, 0x001c001c, 0x00000000, // ICON_FILTER_BILINEAR - 0x40080000, 0x1ffe2008, 0x14081008, 0x11081208, 0x10481088, 0x10081028, 0x10047ff8, 0x00001002, // ICON_CROP - 0x00100000, 0x3ffc0010, 0x2ab03550, 0x22b02550, 0x20b02150, 0x20302050, 0x2000fff0, 0x00002000, // ICON_CROP_ALPHA - 0x40000000, 0x1ff82000, 0x04082808, 0x01082208, 0x00482088, 0x00182028, 0x35542008, 0x00000002, // ICON_SQUARE_TOGGLE - 0x00000000, 0x02800280, 0x06c006c0, 0x0ea00ee0, 0x1e901eb0, 0x3e883e98, 0x7efc7e8c, 0x00000000, // ICON_SYMMETRY - 0x01000000, 0x05600100, 0x1d480d50, 0x7d423d44, 0x3d447d42, 0x0d501d48, 0x01000560, 0x00000100, // ICON_SYMMETRY_HORIZONTAL - 0x01800000, 0x04200240, 0x10080810, 0x00001ff8, 0x00007ffe, 0x0ff01ff8, 0x03c007e0, 0x00000180, // ICON_SYMMETRY_VERTICAL - 0x00000000, 0x010800f0, 0x02040204, 0x02040204, 0x07f00308, 0x1c000e00, 0x30003800, 0x00000000, // ICON_LENS - 0x00000000, 0x061803f0, 0x08240c0c, 0x08040814, 0x0c0c0804, 0x23f01618, 0x18002400, 0x00000000, // ICON_LENS_BIG - 0x00000000, 0x00000000, 0x1c7007c0, 0x638e3398, 0x1c703398, 0x000007c0, 0x00000000, 0x00000000, // ICON_EYE_ON - 0x00000000, 0x10002000, 0x04700fc0, 0x610e3218, 0x1c703098, 0x001007a0, 0x00000008, 0x00000000, // ICON_EYE_OFF - 0x00000000, 0x00007ffc, 0x40047ffc, 0x10102008, 0x04400820, 0x02800280, 0x02800280, 0x00000100, // ICON_FILTER_TOP - 0x00000000, 0x40027ffe, 0x10082004, 0x04200810, 0x02400240, 0x02400240, 0x01400240, 0x000000c0, // ICON_FILTER - 0x00800000, 0x00800080, 0x00000080, 0x3c9e0000, 0x00000000, 0x00800080, 0x00800080, 0x00000000, // ICON_TARGET_POINT - 0x00800000, 0x00800080, 0x00800080, 0x3f7e01c0, 0x008001c0, 0x00800080, 0x00800080, 0x00000000, // ICON_TARGET_SMALL - 0x00800000, 0x00800080, 0x03e00080, 0x3e3e0220, 0x03e00220, 0x00800080, 0x00800080, 0x00000000, // ICON_TARGET_BIG - 0x01000000, 0x04400280, 0x01000100, 0x43842008, 0x43849ab2, 0x01002008, 0x04400100, 0x01000280, // ICON_TARGET_MOVE - 0x01000000, 0x04400280, 0x01000100, 0x41042108, 0x41049ff2, 0x01002108, 0x04400100, 0x01000280, // ICON_CURSOR_MOVE - 0x781e0000, 0x500a4002, 0x04204812, 0x00000240, 0x02400000, 0x48120420, 0x4002500a, 0x0000781e, // ICON_CURSOR_SCALE - 0x00000000, 0x20003c00, 0x24002800, 0x01000200, 0x00400080, 0x00140024, 0x003c0004, 0x00000000, // ICON_CURSOR_SCALE_RIGHT - 0x00000000, 0x0004003c, 0x00240014, 0x00800040, 0x02000100, 0x28002400, 0x3c002000, 0x00000000, // ICON_CURSOR_SCALE_LEFT - 0x00000000, 0x00100020, 0x10101fc8, 0x10001020, 0x10001000, 0x10001000, 0x00001fc0, 0x00000000, // ICON_UNDO - 0x00000000, 0x08000400, 0x080813f8, 0x00080408, 0x00080008, 0x00080008, 0x000003f8, 0x00000000, // ICON_REDO - 0x00000000, 0x3ffc0000, 0x20042004, 0x20002000, 0x20402000, 0x3f902020, 0x00400020, 0x00000000, // ICON_REREDO - 0x00000000, 0x3ffc0000, 0x20042004, 0x27fc2004, 0x20202000, 0x3fc82010, 0x00200010, 0x00000000, // ICON_MUTATE - 0x00000000, 0x0ff00000, 0x10081818, 0x11801008, 0x10001180, 0x18101020, 0x00100fc8, 0x00000020, // ICON_ROTATE - 0x00000000, 0x04000200, 0x240429fc, 0x20042204, 0x20442004, 0x3f942024, 0x00400020, 0x00000000, // ICON_REPEAT - 0x00000000, 0x20001000, 0x22104c0e, 0x00801120, 0x11200040, 0x4c0e2210, 0x10002000, 0x00000000, // ICON_SHUFFLE - 0x7ffe0000, 0x50024002, 0x44024802, 0x41024202, 0x40424082, 0x40124022, 0x4002400a, 0x00007ffe, // ICON_EMPTYBOX - 0x00800000, 0x03e00080, 0x08080490, 0x3c9e0808, 0x08080808, 0x03e00490, 0x00800080, 0x00000000, // ICON_TARGET - 0x00800000, 0x00800080, 0x00800080, 0x3ffe01c0, 0x008001c0, 0x00800080, 0x00800080, 0x00000000, // ICON_TARGET_SMALL_FILL - 0x00800000, 0x00800080, 0x03e00080, 0x3ffe03e0, 0x03e003e0, 0x00800080, 0x00800080, 0x00000000, // ICON_TARGET_BIG_FILL - 0x01000000, 0x07c00380, 0x01000100, 0x638c2008, 0x638cfbbe, 0x01002008, 0x07c00100, 0x01000380, // ICON_TARGET_MOVE_FILL - 0x01000000, 0x07c00380, 0x01000100, 0x610c2108, 0x610cfffe, 0x01002108, 0x07c00100, 0x01000380, // ICON_CURSOR_MOVE_FILL - 0x781e0000, 0x6006700e, 0x04204812, 0x00000240, 0x02400000, 0x48120420, 0x700e6006, 0x0000781e, // ICON_CURSOR_SCALE_FILL - 0x00000000, 0x38003c00, 0x24003000, 0x01000200, 0x00400080, 0x000c0024, 0x003c001c, 0x00000000, // ICON_CURSOR_SCALE_RIGHT_FILL - 0x00000000, 0x001c003c, 0x0024000c, 0x00800040, 0x02000100, 0x30002400, 0x3c003800, 0x00000000, // ICON_CURSOR_SCALE_LEFT_FILL - 0x00000000, 0x00300020, 0x10301ff8, 0x10001020, 0x10001000, 0x10001000, 0x00001fc0, 0x00000000, // ICON_UNDO_FILL - 0x00000000, 0x0c000400, 0x0c081ff8, 0x00080408, 0x00080008, 0x00080008, 0x000003f8, 0x00000000, // ICON_REDO_FILL - 0x00000000, 0x3ffc0000, 0x20042004, 0x20002000, 0x20402000, 0x3ff02060, 0x00400060, 0x00000000, // ICON_REREDO_FILL - 0x00000000, 0x3ffc0000, 0x20042004, 0x27fc2004, 0x20202000, 0x3ff82030, 0x00200030, 0x00000000, // ICON_MUTATE_FILL - 0x00000000, 0x0ff00000, 0x10081818, 0x11801008, 0x10001180, 0x18301020, 0x00300ff8, 0x00000020, // ICON_ROTATE_FILL - 0x00000000, 0x06000200, 0x26042ffc, 0x20042204, 0x20442004, 0x3ff42064, 0x00400060, 0x00000000, // ICON_REPEAT_FILL - 0x00000000, 0x30001000, 0x32107c0e, 0x00801120, 0x11200040, 0x7c0e3210, 0x10003000, 0x00000000, // ICON_SHUFFLE_FILL - 0x00000000, 0x30043ffc, 0x24042804, 0x21042204, 0x20442084, 0x20142024, 0x3ffc200c, 0x00000000, // ICON_EMPTYBOX_SMALL - 0x00000000, 0x20043ffc, 0x20042004, 0x20042004, 0x20042004, 0x20042004, 0x3ffc2004, 0x00000000, // ICON_BOX - 0x00000000, 0x23c43ffc, 0x23c423c4, 0x200423c4, 0x20042004, 0x20042004, 0x3ffc2004, 0x00000000, // ICON_BOX_TOP - 0x00000000, 0x3e043ffc, 0x3e043e04, 0x20043e04, 0x20042004, 0x20042004, 0x3ffc2004, 0x00000000, // ICON_BOX_TOP_RIGHT - 0x00000000, 0x20043ffc, 0x20042004, 0x3e043e04, 0x3e043e04, 0x20042004, 0x3ffc2004, 0x00000000, // ICON_BOX_RIGHT - 0x00000000, 0x20043ffc, 0x20042004, 0x20042004, 0x3e042004, 0x3e043e04, 0x3ffc3e04, 0x00000000, // ICON_BOX_BOTTOM_RIGHT - 0x00000000, 0x20043ffc, 0x20042004, 0x20042004, 0x23c42004, 0x23c423c4, 0x3ffc23c4, 0x00000000, // ICON_BOX_BOTTOM - 0x00000000, 0x20043ffc, 0x20042004, 0x20042004, 0x207c2004, 0x207c207c, 0x3ffc207c, 0x00000000, // ICON_BOX_BOTTOM_LEFT - 0x00000000, 0x20043ffc, 0x20042004, 0x207c207c, 0x207c207c, 0x20042004, 0x3ffc2004, 0x00000000, // ICON_BOX_LEFT - 0x00000000, 0x207c3ffc, 0x207c207c, 0x2004207c, 0x20042004, 0x20042004, 0x3ffc2004, 0x00000000, // ICON_BOX_TOP_LEFT - 0x00000000, 0x20043ffc, 0x20042004, 0x23c423c4, 0x23c423c4, 0x20042004, 0x3ffc2004, 0x00000000, // ICON_BOX_CENTER - 0x7ffe0000, 0x40024002, 0x47e24182, 0x4ff247e2, 0x47e24ff2, 0x418247e2, 0x40024002, 0x00007ffe, // ICON_BOX_CIRCLE_MASK - 0x7fff0000, 0x40014001, 0x40014001, 0x49555ddd, 0x4945495d, 0x400149c5, 0x40014001, 0x00007fff, // ICON_POT - 0x7ffe0000, 0x53327332, 0x44ce4cce, 0x41324332, 0x404e40ce, 0x48125432, 0x4006540e, 0x00007ffe, // ICON_ALPHA_MULTIPLY - 0x7ffe0000, 0x53327332, 0x44ce4cce, 0x41324332, 0x5c4e40ce, 0x44124432, 0x40065c0e, 0x00007ffe, // ICON_ALPHA_CLEAR - 0x7ffe0000, 0x42fe417e, 0x42fe417e, 0x42fe417e, 0x42fe417e, 0x42fe417e, 0x42fe417e, 0x00007ffe, // ICON_DITHERING - 0x07fe0000, 0x1ffa0002, 0x7fea000a, 0x402a402a, 0x5b2a512a, 0x5128552a, 0x40205128, 0x00007fe0, // ICON_MIPMAPS - 0x00000000, 0x1ff80000, 0x12481248, 0x12481ff8, 0x1ff81248, 0x12481248, 0x00001ff8, 0x00000000, // ICON_BOX_GRID - 0x12480000, 0x7ffe1248, 0x12481248, 0x12487ffe, 0x7ffe1248, 0x12481248, 0x12487ffe, 0x00001248, // ICON_GRID - 0x00000000, 0x1c380000, 0x1c3817e8, 0x08100810, 0x08100810, 0x17e81c38, 0x00001c38, 0x00000000, // ICON_BOX_CORNERS_SMALL - 0x700e0000, 0x700e5ffa, 0x20042004, 0x20042004, 0x20042004, 0x20042004, 0x5ffa700e, 0x0000700e, // ICON_BOX_CORNERS_BIG - 0x3f7e0000, 0x21422142, 0x21422142, 0x00003f7e, 0x21423f7e, 0x21422142, 0x3f7e2142, 0x00000000, // ICON_FOUR_BOXES - 0x00000000, 0x3bb80000, 0x3bb83bb8, 0x3bb80000, 0x3bb83bb8, 0x3bb80000, 0x3bb83bb8, 0x00000000, // ICON_GRID_FILL - 0x7ffe0000, 0x7ffe7ffe, 0x77fe7000, 0x77fe77fe, 0x777e7700, 0x777e777e, 0x777e777e, 0x0000777e, // ICON_BOX_MULTISIZE - 0x781e0000, 0x40024002, 0x00004002, 0x01800000, 0x00000180, 0x40020000, 0x40024002, 0x0000781e, // ICON_ZOOM_SMALL - 0x781e0000, 0x40024002, 0x00004002, 0x03c003c0, 0x03c003c0, 0x40020000, 0x40024002, 0x0000781e, // ICON_ZOOM_MEDIUM - 0x781e0000, 0x40024002, 0x07e04002, 0x07e007e0, 0x07e007e0, 0x400207e0, 0x40024002, 0x0000781e, // ICON_ZOOM_BIG - 0x781e0000, 0x5ffa4002, 0x1ff85ffa, 0x1ff81ff8, 0x1ff81ff8, 0x5ffa1ff8, 0x40025ffa, 0x0000781e, // ICON_ZOOM_ALL - 0x00000000, 0x2004381c, 0x00002004, 0x00000000, 0x00000000, 0x20040000, 0x381c2004, 0x00000000, // ICON_ZOOM_CENTER - 0x00000000, 0x1db80000, 0x10081008, 0x10080000, 0x00001008, 0x10081008, 0x00001db8, 0x00000000, // ICON_BOX_DOTS_SMALL - 0x35560000, 0x00002002, 0x00002002, 0x00002002, 0x00002002, 0x00002002, 0x35562002, 0x00000000, // ICON_BOX_DOTS_BIG - 0x7ffe0000, 0x40024002, 0x48124ff2, 0x49924812, 0x48124992, 0x4ff24812, 0x40024002, 0x00007ffe, // ICON_BOX_CONCENTRIC - 0x00000000, 0x10841ffc, 0x10841084, 0x1ffc1084, 0x10841084, 0x10841084, 0x00001ffc, 0x00000000, // ICON_BOX_GRID_BIG - 0x00000000, 0x00000000, 0x10000000, 0x04000800, 0x01040200, 0x00500088, 0x00000020, 0x00000000, // ICON_OK_TICK - 0x00000000, 0x10080000, 0x04200810, 0x01800240, 0x02400180, 0x08100420, 0x00001008, 0x00000000, // ICON_CROSS - 0x00000000, 0x02000000, 0x00800100, 0x00200040, 0x00200010, 0x00800040, 0x02000100, 0x00000000, // ICON_ARROW_LEFT - 0x00000000, 0x00400000, 0x01000080, 0x04000200, 0x04000800, 0x01000200, 0x00400080, 0x00000000, // ICON_ARROW_RIGHT - 0x00000000, 0x00000000, 0x00000000, 0x08081004, 0x02200410, 0x00800140, 0x00000000, 0x00000000, // ICON_ARROW_DOWN - 0x00000000, 0x00000000, 0x01400080, 0x04100220, 0x10040808, 0x00000000, 0x00000000, 0x00000000, // ICON_ARROW_UP - 0x00000000, 0x02000000, 0x03800300, 0x03e003c0, 0x03e003f0, 0x038003c0, 0x02000300, 0x00000000, // ICON_ARROW_LEFT_FILL - 0x00000000, 0x00400000, 0x01c000c0, 0x07c003c0, 0x07c00fc0, 0x01c003c0, 0x004000c0, 0x00000000, // ICON_ARROW_RIGHT_FILL - 0x00000000, 0x00000000, 0x00000000, 0x0ff81ffc, 0x03e007f0, 0x008001c0, 0x00000000, 0x00000000, // ICON_ARROW_DOWN_FILL - 0x00000000, 0x00000000, 0x01c00080, 0x07f003e0, 0x1ffc0ff8, 0x00000000, 0x00000000, 0x00000000, // ICON_ARROW_UP_FILL - 0x00000000, 0x18a008c0, 0x32881290, 0x24822686, 0x26862482, 0x12903288, 0x08c018a0, 0x00000000, // ICON_AUDIO - 0x00000000, 0x04800780, 0x004000c0, 0x662000f0, 0x08103c30, 0x130a0e18, 0x0000318e, 0x00000000, // ICON_FX - 0x00000000, 0x00800000, 0x08880888, 0x2aaa0a8a, 0x0a8a2aaa, 0x08880888, 0x00000080, 0x00000000, // ICON_WAVE - 0x00000000, 0x00600000, 0x01080090, 0x02040108, 0x42044204, 0x24022402, 0x00001800, 0x00000000, // ICON_WAVE_SINUS - 0x00000000, 0x07f80000, 0x04080408, 0x04080408, 0x04080408, 0x7c0e0408, 0x00000000, 0x00000000, // ICON_WAVE_SQUARE - 0x00000000, 0x00000000, 0x00a00040, 0x22084110, 0x08021404, 0x00000000, 0x00000000, 0x00000000, // ICON_WAVE_TRIANGULAR - 0x00000000, 0x00000000, 0x04200000, 0x01800240, 0x02400180, 0x00000420, 0x00000000, 0x00000000, // ICON_CROSS_SMALL - 0x00000000, 0x18380000, 0x12281428, 0x10a81128, 0x112810a8, 0x14281228, 0x00001838, 0x00000000, // ICON_PLAYER_PREVIOUS - 0x00000000, 0x18000000, 0x11801600, 0x10181060, 0x10601018, 0x16001180, 0x00001800, 0x00000000, // ICON_PLAYER_PLAY_BACK - 0x00000000, 0x00180000, 0x01880068, 0x18080608, 0x06081808, 0x00680188, 0x00000018, 0x00000000, // ICON_PLAYER_PLAY - 0x00000000, 0x1e780000, 0x12481248, 0x12481248, 0x12481248, 0x12481248, 0x00001e78, 0x00000000, // ICON_PLAYER_PAUSE - 0x00000000, 0x1ff80000, 0x10081008, 0x10081008, 0x10081008, 0x10081008, 0x00001ff8, 0x00000000, // ICON_PLAYER_STOP - 0x00000000, 0x1c180000, 0x14481428, 0x15081488, 0x14881508, 0x14281448, 0x00001c18, 0x00000000, // ICON_PLAYER_NEXT - 0x00000000, 0x03c00000, 0x08100420, 0x10081008, 0x10081008, 0x04200810, 0x000003c0, 0x00000000, // ICON_PLAYER_RECORD - 0x00000000, 0x0c3007e0, 0x13c81818, 0x14281668, 0x14281428, 0x1c381c38, 0x08102244, 0x00000000, // ICON_MAGNET - 0x07c00000, 0x08200820, 0x3ff80820, 0x23882008, 0x21082388, 0x20082108, 0x1ff02008, 0x00000000, // ICON_LOCK_CLOSE - 0x07c00000, 0x08000800, 0x3ff80800, 0x23882008, 0x21082388, 0x20082108, 0x1ff02008, 0x00000000, // ICON_LOCK_OPEN - 0x01c00000, 0x0c180770, 0x3086188c, 0x60832082, 0x60034781, 0x30062002, 0x0c18180c, 0x01c00770, // ICON_CLOCK - 0x0a200000, 0x1b201b20, 0x04200e20, 0x04200420, 0x04700420, 0x0e700e70, 0x0e700e70, 0x04200e70, // ICON_TOOLS - 0x01800000, 0x3bdc318c, 0x0ff01ff8, 0x7c3e1e78, 0x1e787c3e, 0x1ff80ff0, 0x318c3bdc, 0x00000180, // ICON_GEAR - 0x01800000, 0x3ffc318c, 0x1c381ff8, 0x781e1818, 0x1818781e, 0x1ff81c38, 0x318c3ffc, 0x00000180, // ICON_GEAR_BIG - 0x00000000, 0x08080ff8, 0x08081ffc, 0x0aa80aa8, 0x0aa80aa8, 0x0aa80aa8, 0x08080aa8, 0x00000ff8, // ICON_BIN - 0x00000000, 0x00000000, 0x20043ffc, 0x08043f84, 0x04040f84, 0x04040784, 0x000007fc, 0x00000000, // ICON_HAND_POINTER - 0x00000000, 0x24400400, 0x00001480, 0x6efe0e00, 0x00000e00, 0x24401480, 0x00000400, 0x00000000, // ICON_LASER - 0x00000000, 0x03c00000, 0x08300460, 0x11181118, 0x11181118, 0x04600830, 0x000003c0, 0x00000000, // ICON_COIN - 0x00000000, 0x10880080, 0x06c00810, 0x366c07e0, 0x07e00240, 0x00001768, 0x04200240, 0x00000000, // ICON_EXPLOSION - 0x00000000, 0x3d280000, 0x2528252c, 0x3d282528, 0x05280528, 0x05e80528, 0x00000000, 0x00000000, // ICON_1UP - 0x01800000, 0x03c003c0, 0x018003c0, 0x0ff007e0, 0x0bd00bd0, 0x0a500bd0, 0x02400240, 0x02400240, // ICON_PLAYER - 0x01800000, 0x03c003c0, 0x118013c0, 0x03c81ff8, 0x07c003c8, 0x04400440, 0x0c080478, 0x00000000, // ICON_PLAYER_JUMP - 0x3ff80000, 0x30183ff8, 0x30183018, 0x3ff83ff8, 0x03000300, 0x03c003c0, 0x03e00300, 0x000003e0, // ICON_KEY - 0x3ff80000, 0x3ff83ff8, 0x33983ff8, 0x3ff83398, 0x3ff83ff8, 0x00000540, 0x0fe00aa0, 0x00000fe0, // ICON_DEMON - 0x00000000, 0x0ff00000, 0x20041008, 0x25442004, 0x10082004, 0x06000bf0, 0x00000300, 0x00000000, // ICON_TEXT_POPUP - 0x00000000, 0x11440000, 0x07f00be8, 0x1c1c0e38, 0x1c1c0c18, 0x07f00e38, 0x11440be8, 0x00000000, // ICON_GEAR_EX - 0x00000000, 0x20080000, 0x0c601010, 0x07c00fe0, 0x07c007c0, 0x0c600fe0, 0x20081010, 0x00000000, // ICON_CRACK - 0x00000000, 0x20080000, 0x0c601010, 0x04400fe0, 0x04405554, 0x0c600fe0, 0x20081010, 0x00000000, // ICON_CRACK_POINTS - 0x00000000, 0x00800080, 0x01c001c0, 0x1ffc3ffe, 0x03e007f0, 0x07f003e0, 0x0c180770, 0x00000808, // ICON_STAR - 0x0ff00000, 0x08180810, 0x08100818, 0x0a100810, 0x08180810, 0x08100818, 0x08100810, 0x00001ff8, // ICON_DOOR - 0x0ff00000, 0x08100810, 0x08100810, 0x10100010, 0x4f902010, 0x10102010, 0x08100010, 0x00000ff0, // ICON_EXIT - 0x00040000, 0x001f000e, 0x0ef40004, 0x12f41284, 0x0ef41214, 0x10040004, 0x7ffc3004, 0x10003000, // ICON_MODE_2D - 0x78040000, 0x501f600e, 0x0ef44004, 0x12f41284, 0x0ef41284, 0x10140004, 0x7ffc300c, 0x10003000, // ICON_MODE_3D - 0x7fe00000, 0x50286030, 0x47fe4804, 0x44224402, 0x44224422, 0x241275e2, 0x0c06140a, 0x000007fe, // ICON_CUBE - 0x7fe00000, 0x5ff87ff0, 0x47fe4ffc, 0x44224402, 0x44224422, 0x241275e2, 0x0c06140a, 0x000007fe, // ICON_CUBE_FACE_TOP - 0x7fe00000, 0x50386030, 0x47c2483c, 0x443e443e, 0x443e443e, 0x241e75fe, 0x0c06140e, 0x000007fe, // ICON_CUBE_FACE_LEFT - 0x7fe00000, 0x50286030, 0x47fe4804, 0x47fe47fe, 0x47fe47fe, 0x27fe77fe, 0x0ffe17fe, 0x000007fe, // ICON_CUBE_FACE_FRONT - 0x7fe00000, 0x50286030, 0x47fe4804, 0x44224402, 0x44224422, 0x3bf27be2, 0x0bfe1bfa, 0x000007fe, // ICON_CUBE_FACE_BOTTOM - 0x7fe00000, 0x70286030, 0x7ffe7804, 0x7c227c02, 0x7c227c22, 0x3c127de2, 0x0c061c0a, 0x000007fe, // ICON_CUBE_FACE_RIGHT - 0x7fe00000, 0x6fe85ff0, 0x781e77e4, 0x7be27be2, 0x7be27be2, 0x24127be2, 0x0c06140a, 0x000007fe, // ICON_CUBE_FACE_BACK - 0x00000000, 0x2a0233fe, 0x22022602, 0x22022202, 0x2a022602, 0x00a033fe, 0x02080110, 0x00000000, // ICON_CAMERA - 0x00000000, 0x200c3ffc, 0x000c000c, 0x3ffc000c, 0x30003000, 0x30003000, 0x3ffc3004, 0x00000000, // ICON_SPECIAL - 0x00000000, 0x0022003e, 0x012201e2, 0x0100013e, 0x01000100, 0x79000100, 0x4f004900, 0x00007800, // ICON_LINK_NET - 0x00000000, 0x44007c00, 0x45004600, 0x00627cbe, 0x00620022, 0x45007cbe, 0x44004600, 0x00007c00, // ICON_LINK_BOXES - 0x00000000, 0x0044007c, 0x0010007c, 0x3f100010, 0x3f1021f0, 0x3f100010, 0x3f0021f0, 0x00000000, // ICON_LINK_MULTI - 0x00000000, 0x0044007c, 0x00440044, 0x0010007c, 0x00100010, 0x44107c10, 0x440047f0, 0x00007c00, // ICON_LINK - 0x00000000, 0x0044007c, 0x00440044, 0x0000007c, 0x00000010, 0x44007c10, 0x44004550, 0x00007c00, // ICON_LINK_BROKE - 0x02a00000, 0x22a43ffc, 0x20042004, 0x20042ff4, 0x20042ff4, 0x20042ff4, 0x20042004, 0x00003ffc, // ICON_TEXT_NOTES - 0x3ffc0000, 0x20042004, 0x245e27c4, 0x27c42444, 0x2004201e, 0x201e2004, 0x20042004, 0x00003ffc, // ICON_NOTEBOOK - 0x00000000, 0x07e00000, 0x04200420, 0x24243ffc, 0x24242424, 0x24242424, 0x3ffc2424, 0x00000000, // ICON_SUITCASE - 0x00000000, 0x0fe00000, 0x08200820, 0x40047ffc, 0x7ffc5554, 0x40045554, 0x7ffc4004, 0x00000000, // ICON_SUITCASE_ZIP - 0x00000000, 0x20043ffc, 0x3ffc2004, 0x13c81008, 0x100813c8, 0x10081008, 0x1ff81008, 0x00000000, // ICON_MAILBOX - 0x00000000, 0x40027ffe, 0x5ffa5ffa, 0x5ffa5ffa, 0x40025ffa, 0x03c07ffe, 0x1ff81ff8, 0x00000000, // ICON_MONITOR - 0x0ff00000, 0x6bfe7ffe, 0x7ffe7ffe, 0x68167ffe, 0x08106816, 0x08100810, 0x0ff00810, 0x00000000, // ICON_PRINTER - 0x3ff80000, 0xfffe2008, 0x870a8002, 0x904a888a, 0x904a904a, 0x870a888a, 0xfffe8002, 0x00000000, // ICON_PHOTO_CAMERA - 0x0fc00000, 0xfcfe0cd8, 0x8002fffe, 0x84428382, 0x84428442, 0x80028382, 0xfffe8002, 0x00000000, // ICON_PHOTO_CAMERA_FLASH - 0x00000000, 0x02400180, 0x08100420, 0x20041008, 0x23c42004, 0x22442244, 0x3ffc2244, 0x00000000, // ICON_HOUSE - 0x00000000, 0x1c700000, 0x3ff83ef8, 0x3ff83ff8, 0x0fe01ff0, 0x038007c0, 0x00000100, 0x00000000, // ICON_HEART - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x80000000, 0xe000c000, // ICON_CORNER - 0x00000000, 0x14001c00, 0x15c01400, 0x15401540, 0x155c1540, 0x15541554, 0x1ddc1554, 0x00000000, // ICON_VERTICAL_BARS - 0x00000000, 0x03000300, 0x1b001b00, 0x1b601b60, 0x1b6c1b60, 0x1b6c1b6c, 0x1b6c1b6c, 0x00000000, // ICON_VERTICAL_BARS_FILL - 0x00000000, 0x00000000, 0x403e7ffe, 0x7ffe403e, 0x7ffe0000, 0x43fe43fe, 0x00007ffe, 0x00000000, // ICON_LIFE_BARS - 0x7ffc0000, 0x43844004, 0x43844284, 0x43844004, 0x42844284, 0x42844284, 0x40044384, 0x00007ffc, // ICON_INFO - 0x40008000, 0x10002000, 0x04000800, 0x01000200, 0x00400080, 0x00100020, 0x00040008, 0x00010002, // ICON_CROSSLINE - 0x00000000, 0x1ff01ff0, 0x18301830, 0x1f001830, 0x03001f00, 0x00000300, 0x03000300, 0x00000000, // ICON_HELP - 0x3ff00000, 0x2abc3550, 0x2aac3554, 0x2aac3554, 0x2aac3554, 0x2aac3554, 0x2aac3554, 0x00003ffc, // ICON_FILETYPE_ALPHA - 0x3ff00000, 0x201c2010, 0x22442184, 0x28142424, 0x29942814, 0x2ff42994, 0x20042004, 0x00003ffc, // ICON_FILETYPE_HOME - 0x07fe0000, 0x04020402, 0x7fe20402, 0x44224422, 0x44224422, 0x402047fe, 0x40204020, 0x00007fe0, // ICON_LAYERS_VISIBLE - 0x07fe0000, 0x04020402, 0x7c020402, 0x44024402, 0x44024402, 0x402047fe, 0x40204020, 0x00007fe0, // ICON_LAYERS - 0x00000000, 0x40027ffe, 0x7ffe4002, 0x40024002, 0x40024002, 0x40024002, 0x7ffe4002, 0x00000000, // ICON_WINDOW - 0x09100000, 0x09f00910, 0x09100910, 0x00000910, 0x24a2779e, 0x27a224a2, 0x709e20a2, 0x00000000, // ICON_HIDPI - 0x3ff00000, 0x201c2010, 0x2a842e84, 0x2e842a84, 0x2ba42004, 0x2aa42aa4, 0x20042ba4, 0x00003ffc, // ICON_FILETYPE_BINARY - 0x00000000, 0x00000000, 0x00120012, 0x4a5e4bd2, 0x485233d2, 0x00004bd2, 0x00000000, 0x00000000, // ICON_HEX - 0x01800000, 0x381c0660, 0x23c42004, 0x23c42044, 0x13c82204, 0x08101008, 0x02400420, 0x00000180, // ICON_SHIELD - 0x007e0000, 0x20023fc2, 0x40227fe2, 0x400a403a, 0x400a400a, 0x400a400a, 0x4008400e, 0x00007ff8, // ICON_FILE_NEW - 0x00000000, 0x0042007e, 0x40027fc2, 0x44024002, 0x5f024402, 0x44024402, 0x7ffe4002, 0x00000000, // ICON_FOLDER_ADD - 0x44220000, 0x12482244, 0xf3cf0000, 0x14280420, 0x48122424, 0x08100810, 0x1ff81008, 0x03c00420, // ICON_ALARM - 0x0aa00000, 0x1ff80aa0, 0x1068700e, 0x1008706e, 0x1008700e, 0x1008700e, 0x0aa01ff8, 0x00000aa0, // ICON_CPU - 0x07e00000, 0x04201db8, 0x04a01c38, 0x04a01d38, 0x04a01d38, 0x04a01d38, 0x04201d38, 0x000007e0, // ICON_ROM - 0x00000000, 0x03c00000, 0x3c382ff0, 0x3c04380c, 0x01800000, 0x03c003c0, 0x00000180, 0x00000000, // ICON_STEP_OVER - 0x01800000, 0x01800180, 0x01800180, 0x03c007e0, 0x00000180, 0x01800000, 0x03c003c0, 0x00000180, // ICON_STEP_INTO - 0x01800000, 0x07e003c0, 0x01800180, 0x01800180, 0x00000180, 0x01800000, 0x03c003c0, 0x00000180, // ICON_STEP_OUT - 0x00000000, 0x0ff003c0, 0x181c1c34, 0x303c301c, 0x30003000, 0x1c301800, 0x03c00ff0, 0x00000000, // ICON_RESTART - 0x00000000, 0x00000000, 0x07e003c0, 0x0ff00ff0, 0x0ff00ff0, 0x03c007e0, 0x00000000, 0x00000000, // ICON_BREAKPOINT_ON - 0x00000000, 0x00000000, 0x042003c0, 0x08100810, 0x08100810, 0x03c00420, 0x00000000, 0x00000000, // ICON_BREAKPOINT_OFF - 0x00000000, 0x00000000, 0x1ff81ff8, 0x1ff80000, 0x00001ff8, 0x1ff81ff8, 0x00000000, 0x00000000, // ICON_BURGER_MENU - 0x00000000, 0x00000000, 0x00880070, 0x0c880088, 0x1e8810f8, 0x3e881288, 0x00000000, 0x00000000, // ICON_CASE_SENSITIVE - 0x00000000, 0x02000000, 0x07000a80, 0x07001fc0, 0x02000a80, 0x00300030, 0x00000000, 0x00000000, // ICON_REG_EXP - 0x00000000, 0x0042007e, 0x40027fc2, 0x40024002, 0x40024002, 0x40024002, 0x7ffe4002, 0x00000000, // ICON_FOLDER - 0x3ff00000, 0x201c2010, 0x20042004, 0x20042004, 0x20042004, 0x20042004, 0x20042004, 0x00003ffc, // ICON_FILE - 0x1ff00000, 0x20082008, 0x17d02fe8, 0x05400ba0, 0x09200540, 0x23881010, 0x2fe827c8, 0x00001ff0, // ICON_SAND_TIMER - 0x01800000, 0x02400240, 0x05a00420, 0x09900990, 0x11881188, 0x21842004, 0x40024182, 0x00003ffc, // ICON_WARNING - 0x7ffe0000, 0x4ff24002, 0x4c324ff2, 0x4f824c02, 0x41824f82, 0x41824002, 0x40024182, 0x00007ffe, // ICON_HELP_BOX - 0x7ffe0000, 0x41824002, 0x40024182, 0x41824182, 0x41824182, 0x41824182, 0x40024182, 0x00007ffe, // ICON_INFO_BOX - 0x01800000, 0x04200240, 0x10080810, 0x7bde2004, 0x0a500a50, 0x08500bd0, 0x08100850, 0x00000ff0, // ICON_PRIORITY - 0x01800000, 0x18180660, 0x80016006, 0x98196006, 0x99996666, 0x19986666, 0x01800660, 0x00000000, // ICON_LAYERS_ISO - 0x07fe0000, 0x1c020402, 0x74021402, 0x54025402, 0x54025402, 0x500857fe, 0x40205ff8, 0x00007fe0, // ICON_LAYERS2 - 0x0ffe0000, 0x3ffa0802, 0x7fea200a, 0x402a402a, 0x422a422a, 0x422e422a, 0x40384e28, 0x00007fe0, // ICON_MLAYERS - 0x0ffe0000, 0x3ffa0802, 0x7fea200a, 0x402a402a, 0x5b2a512a, 0x512e552a, 0x40385128, 0x00007fe0, // ICON_MAPS - 0x04200000, 0x1cf00c60, 0x11f019f0, 0x0f3807b8, 0x1e3c0f3c, 0x1c1c1e1c, 0x1e3c1c1c, 0x00000f70, // ICON_HOT - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_229 - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_230 - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_231 - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_232 - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_233 - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_234 - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_235 - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_236 - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_237 - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_238 - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_239 - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_240 - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_241 - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_242 - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_243 - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_244 - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_245 - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_246 - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_247 - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_248 - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_249 - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_250 - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_251 - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_252 - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_253 - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_254 - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_255 -}; - -// NOTE: A pointer to current icons array should be defined -static unsigned int *guiIconsPtr = guiIcons; - -#endif // !RAYGUI_NO_ICONS && !RAYGUI_CUSTOM_ICONS - -#ifndef RAYGUI_ICON_SIZE - #define RAYGUI_ICON_SIZE 0 -#endif - -// WARNING: Those values define the total size of the style data array, -// if changed, previous saved styles could become incompatible -#define RAYGUI_MAX_CONTROLS 16 // Maximum number of controls -#define RAYGUI_MAX_PROPS_BASE 16 // Maximum number of base properties -#define RAYGUI_MAX_PROPS_EXTENDED 8 // Maximum number of extended properties - -//---------------------------------------------------------------------------------- -// Types and Structures Definition -//---------------------------------------------------------------------------------- -// Gui control property style color element -typedef enum { BORDER = 0, BASE, TEXT, OTHER } GuiPropertyElement; - -//---------------------------------------------------------------------------------- -// Global Variables Definition -//---------------------------------------------------------------------------------- -static GuiState guiState = STATE_NORMAL; // Gui global state, if !STATE_NORMAL, forces defined state - -static Font guiFont = { 0 }; // Gui current font (WARNING: highly coupled to raylib) -static bool guiLocked = false; // Gui lock state (no inputs processed) -static float guiAlpha = 1.0f; // Gui controls transparency - -static unsigned int guiIconScale = 1; // Gui icon default scale (if icons enabled) - -static bool guiTooltip = false; // Tooltip enabled/disabled -static const char *guiTooltipPtr = NULL; // Tooltip string pointer (string provided by user) - -static bool guiControlExclusiveMode = false; // Gui control exclusive mode (no inputs processed except current control) -static Rectangle guiControlExclusiveRec = { 0 }; // Gui control exclusive bounds rectangle, used as an unique identifier - -static int textBoxCursorIndex = 0; // Cursor index, shared by all GuiTextBox*() -//static int blinkCursorFrameCounter = 0; // Frame counter for cursor blinking -static int autoCursorCooldownCounter = 0; // Cooldown frame counter for automatic cursor movement on key-down -static int autoCursorDelayCounter = 0; // Delay frame counter for automatic cursor movement - -//---------------------------------------------------------------------------------- -// Style data array for all gui style properties (allocated on data segment by default) -// -// NOTE 1: First set of BASE properties are generic to all controls but could be individually -// overwritten per control, first set of EXTENDED properties are generic to all controls and -// can not be overwritten individually but custom EXTENDED properties can be used by control -// -// NOTE 2: A new style set could be loaded over this array using GuiLoadStyle(), -// but default gui style could always be recovered with GuiLoadStyleDefault() -// -// guiStyle size is by default: 16*(16 + 8) = 384*4 = 1536 bytes = 1.5 KB -//---------------------------------------------------------------------------------- -static unsigned int guiStyle[RAYGUI_MAX_CONTROLS*(RAYGUI_MAX_PROPS_BASE + RAYGUI_MAX_PROPS_EXTENDED)] = { 0 }; - -static bool guiStyleLoaded = false; // Style loaded flag for lazy style initialization - -//---------------------------------------------------------------------------------- -// Standalone Mode Functions Declaration -// -// NOTE: raygui depend on some raylib input and drawing functions -// To use raygui as standalone library, below functions must be defined by the user -//---------------------------------------------------------------------------------- -#if defined(RAYGUI_STANDALONE) - -#define KEY_RIGHT 262 -#define KEY_LEFT 263 -#define KEY_DOWN 264 -#define KEY_UP 265 -#define KEY_BACKSPACE 259 -#define KEY_ENTER 257 - -#define MOUSE_LEFT_BUTTON 0 - -// Input required functions -//------------------------------------------------------------------------------- -static Vector2 GetMousePosition(void); -static float GetMouseWheelMove(void); -static bool IsMouseButtonDown(int button); -static bool IsMouseButtonPressed(int button); -static bool IsMouseButtonReleased(int button); - -static bool IsKeyDown(int key); -static bool IsKeyPressed(int key); -static int GetCharPressed(void); // -- GuiTextBox(), GuiValueBox() -//------------------------------------------------------------------------------- - -// Drawing required functions -//------------------------------------------------------------------------------- -static void DrawRectangle(int x, int y, int width, int height, Color color); // -- GuiDrawRectangle() -static void DrawRectangleGradientEx(Rectangle rec, Color col1, Color col2, Color col3, Color col4); // -- GuiColorPicker() -//------------------------------------------------------------------------------- - -// Text required functions -//------------------------------------------------------------------------------- -static Font GetFontDefault(void); // -- GuiLoadStyleDefault() -static Font LoadFontEx(const char *fileName, int fontSize, int *codepoints, int codepointCount); // -- GuiLoadStyle(), load font - -static Texture2D LoadTextureFromImage(Image image); // -- GuiLoadStyle(), required to load texture from embedded font atlas image -static void SetShapesTexture(Texture2D tex, Rectangle rec); // -- GuiLoadStyle(), required to set shapes rec to font white rec (optimization) - -static char *LoadFileText(const char *fileName); // -- GuiLoadStyle(), required to load charset data -static void UnloadFileText(char *text); // -- GuiLoadStyle(), required to unload charset data - -static const char *GetDirectoryPath(const char *filePath); // -- GuiLoadStyle(), required to find charset/font file from text .rgs - -static int *LoadCodepoints(const char *text, int *count); // -- GuiLoadStyle(), required to load required font codepoints list -static void UnloadCodepoints(int *codepoints); // -- GuiLoadStyle(), required to unload codepoints list - -static unsigned char *DecompressData(const unsigned char *compData, int compDataSize, int *dataSize); // -- GuiLoadStyle() -//------------------------------------------------------------------------------- - -// raylib functions already implemented in raygui -//------------------------------------------------------------------------------- -static Color GetColor(int hexValue); // Returns a Color struct from hexadecimal value -static int ColorToInt(Color color); // Returns hexadecimal value for a Color -static bool CheckCollisionPointRec(Vector2 point, Rectangle rec); // Check if point is inside rectangle -static const char *TextFormat(const char *text, ...); // Formatting of text with variables to 'embed' -static const char **TextSplit(const char *text, char delimiter, int *count); // Split text into multiple strings -static int TextToInteger(const char *text); // Get integer value from text -static float TextToFloat(const char *text); // Get float value from text - -static int GetCodepointNext(const char *text, int *codepointSize); // Get next codepoint in a UTF-8 encoded text -static const char *CodepointToUTF8(int codepoint, int *byteSize); // Encode codepoint into UTF-8 text (char array size returned as parameter) - -static void DrawRectangleGradientV(int posX, int posY, int width, int height, Color color1, Color color2); // Draw rectangle vertical gradient -//------------------------------------------------------------------------------- - -#endif // RAYGUI_STANDALONE - -//---------------------------------------------------------------------------------- -// Module specific Functions Declaration -//---------------------------------------------------------------------------------- -static void GuiLoadStyleFromMemory(const unsigned char *fileData, int dataSize); // Load style from memory (binary only) - -static int GetTextWidth(const char *text); // Gui get text width using gui font and style -static Rectangle GetTextBounds(int control, Rectangle bounds); // Get text bounds considering control bounds -static const char *GetTextIcon(const char *text, int *iconId); // Get text icon if provided and move text cursor - -static void GuiDrawText(const char *text, Rectangle textBounds, int alignment, Color tint); // Gui draw text using default font -static void GuiDrawRectangle(Rectangle rec, int borderWidth, Color borderColor, Color color); // Gui draw rectangle using default raygui style - -static const char **GuiTextSplit(const char *text, char delimiter, int *count, int *textRow); // Split controls text into multiple strings -static Vector3 ConvertHSVtoRGB(Vector3 hsv); // Convert color data from HSV to RGB -static Vector3 ConvertRGBtoHSV(Vector3 rgb); // Convert color data from RGB to HSV - -static int GuiScrollBar(Rectangle bounds, int value, int minValue, int maxValue); // Scroll bar control, used by GuiScrollPanel() -static void GuiTooltip(Rectangle controlRec); // Draw tooltip using control rec position - -static Color GuiFade(Color color, float alpha); // Fade color by an alpha factor - -//---------------------------------------------------------------------------------- -// Gui Setup Functions Definition -//---------------------------------------------------------------------------------- -// Enable gui global state -// NOTE: We check for STATE_DISABLED to avoid messing custom global state setups -void GuiEnable(void) { if (guiState == STATE_DISABLED) guiState = STATE_NORMAL; } - -// Disable gui global state -// NOTE: We check for STATE_NORMAL to avoid messing custom global state setups -void GuiDisable(void) { if (guiState == STATE_NORMAL) guiState = STATE_DISABLED; } - -// Lock gui global state -void GuiLock(void) { guiLocked = true; } - -// Unlock gui global state -void GuiUnlock(void) { guiLocked = false; } - -// Check if gui is locked (global state) -bool GuiIsLocked(void) { return guiLocked; } - -// Set gui controls alpha global state -void GuiSetAlpha(float alpha) -{ - if (alpha < 0.0f) alpha = 0.0f; - else if (alpha > 1.0f) alpha = 1.0f; - - guiAlpha = alpha; -} - -// Set gui state (global state) -void GuiSetState(int state) { guiState = (GuiState)state; } - -// Get gui state (global state) -int GuiGetState(void) { return guiState; } - -// Set custom gui font -// NOTE: Font loading/unloading is external to raygui -void GuiSetFont(Font font) -{ - if (font.texture.id > 0) - { - // NOTE: If we try to setup a font but default style has not been - // lazily loaded before, it will be overwritten, so we need to force - // default style loading first - if (!guiStyleLoaded) GuiLoadStyleDefault(); - - guiFont = font; - } -} - -// Get custom gui font -Font GuiGetFont(void) -{ - return guiFont; -} - -// Set control style property value -void GuiSetStyle(int control, int property, int value) -{ - if (!guiStyleLoaded) GuiLoadStyleDefault(); - guiStyle[control*(RAYGUI_MAX_PROPS_BASE + RAYGUI_MAX_PROPS_EXTENDED) + property] = value; - - // Default properties are propagated to all controls - if ((control == 0) && (property < RAYGUI_MAX_PROPS_BASE)) - { - for (int i = 1; i < RAYGUI_MAX_CONTROLS; i++) guiStyle[i*(RAYGUI_MAX_PROPS_BASE + RAYGUI_MAX_PROPS_EXTENDED) + property] = value; - } -} - -// Get control style property value -int GuiGetStyle(int control, int property) -{ - if (!guiStyleLoaded) GuiLoadStyleDefault(); - return guiStyle[control*(RAYGUI_MAX_PROPS_BASE + RAYGUI_MAX_PROPS_EXTENDED) + property]; -} - -//---------------------------------------------------------------------------------- -// Gui Controls Functions Definition -//---------------------------------------------------------------------------------- - -// Window Box control -int GuiWindowBox(Rectangle bounds, const char *title) -{ - // Window title bar height (including borders) - // NOTE: This define is also used by GuiMessageBox() and GuiTextInputBox() - #if !defined(RAYGUI_WINDOWBOX_STATUSBAR_HEIGHT) - #define RAYGUI_WINDOWBOX_STATUSBAR_HEIGHT 24 - #endif - - int result = 0; - //GuiState state = guiState; - - int statusBarHeight = RAYGUI_WINDOWBOX_STATUSBAR_HEIGHT; - - Rectangle statusBar = { bounds.x, bounds.y, bounds.width, (float)statusBarHeight }; - if (bounds.height < statusBarHeight*2.0f) bounds.height = statusBarHeight*2.0f; - - Rectangle windowPanel = { bounds.x, bounds.y + (float)statusBarHeight - 1, bounds.width, bounds.height - (float)statusBarHeight + 1 }; - Rectangle closeButtonRec = { statusBar.x + statusBar.width - GuiGetStyle(STATUSBAR, BORDER_WIDTH) - 20, - statusBar.y + statusBarHeight/2.0f - 18.0f/2.0f, 18, 18 }; - - // Update control - //-------------------------------------------------------------------- - // NOTE: Logic is directly managed by button - //-------------------------------------------------------------------- - - // Draw control - //-------------------------------------------------------------------- - GuiStatusBar(statusBar, title); // Draw window header as status bar - GuiPanel(windowPanel, NULL); // Draw window base - - // Draw window close button - int tempBorderWidth = GuiGetStyle(BUTTON, BORDER_WIDTH); - int tempTextAlignment = GuiGetStyle(BUTTON, TEXT_ALIGNMENT); - GuiSetStyle(BUTTON, BORDER_WIDTH, 1); - GuiSetStyle(BUTTON, TEXT_ALIGNMENT, TEXT_ALIGN_CENTER); -#if defined(RAYGUI_NO_ICONS) - result = GuiButton(closeButtonRec, "x"); -#else - result = GuiButton(closeButtonRec, GuiIconText(ICON_CROSS_SMALL, NULL)); -#endif - GuiSetStyle(BUTTON, BORDER_WIDTH, tempBorderWidth); - GuiSetStyle(BUTTON, TEXT_ALIGNMENT, tempTextAlignment); - //-------------------------------------------------------------------- - - return result; // Window close button clicked: result = 1 -} - -// Group Box control with text name -int GuiGroupBox(Rectangle bounds, const char *text) -{ - #if !defined(RAYGUI_GROUPBOX_LINE_THICK) - #define RAYGUI_GROUPBOX_LINE_THICK 1 - #endif - - int result = 0; - GuiState state = guiState; - - // Draw control - //-------------------------------------------------------------------- - GuiDrawRectangle(RAYGUI_CLITERAL(Rectangle){ bounds.x, bounds.y, RAYGUI_GROUPBOX_LINE_THICK, bounds.height }, 0, BLANK, GetColor(GuiGetStyle(DEFAULT, (state == STATE_DISABLED)? (int)BORDER_COLOR_DISABLED : (int)LINE_COLOR))); - GuiDrawRectangle(RAYGUI_CLITERAL(Rectangle){ bounds.x, bounds.y + bounds.height - 1, bounds.width, RAYGUI_GROUPBOX_LINE_THICK }, 0, BLANK, GetColor(GuiGetStyle(DEFAULT, (state == STATE_DISABLED)? (int)BORDER_COLOR_DISABLED : (int)LINE_COLOR))); - GuiDrawRectangle(RAYGUI_CLITERAL(Rectangle){ bounds.x + bounds.width - 1, bounds.y, RAYGUI_GROUPBOX_LINE_THICK, bounds.height }, 0, BLANK, GetColor(GuiGetStyle(DEFAULT, (state == STATE_DISABLED)? (int)BORDER_COLOR_DISABLED : (int)LINE_COLOR))); - - GuiLine(RAYGUI_CLITERAL(Rectangle){ bounds.x, bounds.y - GuiGetStyle(DEFAULT, TEXT_SIZE)/2, bounds.width, (float)GuiGetStyle(DEFAULT, TEXT_SIZE) }, text); - //-------------------------------------------------------------------- - - return result; -} - -// Line control -int GuiLine(Rectangle bounds, const char *text) -{ - #if !defined(RAYGUI_LINE_ORIGIN_SIZE) - #define RAYGUI_LINE_MARGIN_TEXT 12 - #endif - #if !defined(RAYGUI_LINE_TEXT_PADDING) - #define RAYGUI_LINE_TEXT_PADDING 4 - #endif - - int result = 0; - GuiState state = guiState; - - Color color = GetColor(GuiGetStyle(DEFAULT, (state == STATE_DISABLED)? (int)BORDER_COLOR_DISABLED : (int)LINE_COLOR)); - - // Draw control - //-------------------------------------------------------------------- - if (text == NULL) GuiDrawRectangle(RAYGUI_CLITERAL(Rectangle){ bounds.x, bounds.y + bounds.height/2, bounds.width, 1 }, 0, BLANK, color); - else - { - Rectangle textBounds = { 0 }; - textBounds.width = (float)GetTextWidth(text) + 2; - textBounds.height = bounds.height; - textBounds.x = bounds.x + RAYGUI_LINE_MARGIN_TEXT; - textBounds.y = bounds.y; - - // Draw line with embedded text label: "--- text --------------" - GuiDrawRectangle(RAYGUI_CLITERAL(Rectangle){ bounds.x, bounds.y + bounds.height/2, RAYGUI_LINE_MARGIN_TEXT - RAYGUI_LINE_TEXT_PADDING, 1 }, 0, BLANK, color); - GuiDrawText(text, textBounds, TEXT_ALIGN_LEFT, color); - GuiDrawRectangle(RAYGUI_CLITERAL(Rectangle){ bounds.x + 12 + textBounds.width + 4, bounds.y + bounds.height/2, bounds.width - textBounds.width - RAYGUI_LINE_MARGIN_TEXT - RAYGUI_LINE_TEXT_PADDING, 1 }, 0, BLANK, color); - } - //-------------------------------------------------------------------- - - return result; -} - -// Panel control -int GuiPanel(Rectangle bounds, const char *text) -{ - #if !defined(RAYGUI_PANEL_BORDER_WIDTH) - #define RAYGUI_PANEL_BORDER_WIDTH 1 - #endif - - int result = 0; - GuiState state = guiState; - - // Text will be drawn as a header bar (if provided) - Rectangle statusBar = { bounds.x, bounds.y, bounds.width, (float)RAYGUI_WINDOWBOX_STATUSBAR_HEIGHT }; - if ((text != NULL) && (bounds.height < RAYGUI_WINDOWBOX_STATUSBAR_HEIGHT*2.0f)) bounds.height = RAYGUI_WINDOWBOX_STATUSBAR_HEIGHT*2.0f; - - if (text != NULL) - { - // Move panel bounds after the header bar - bounds.y += (float)RAYGUI_WINDOWBOX_STATUSBAR_HEIGHT - 1; - bounds.height -= (float)RAYGUI_WINDOWBOX_STATUSBAR_HEIGHT - 1; - } - - // Draw control - //-------------------------------------------------------------------- - if (text != NULL) GuiStatusBar(statusBar, text); // Draw panel header as status bar - - GuiDrawRectangle(bounds, RAYGUI_PANEL_BORDER_WIDTH, GetColor(GuiGetStyle(DEFAULT, (state == STATE_DISABLED)? (int)BORDER_COLOR_DISABLED: (int)LINE_COLOR)), - GetColor(GuiGetStyle(DEFAULT, (state == STATE_DISABLED)? BASE_COLOR_DISABLED : BACKGROUND_COLOR))); - //-------------------------------------------------------------------- - - return result; -} - -// Tab Bar control -// NOTE: Using GuiToggle() for the TABS -int GuiTabBar(Rectangle bounds, const char **text, int count, int *active) -{ - #define RAYGUI_TABBAR_ITEM_WIDTH 160 - - int result = -1; - //GuiState state = guiState; - - Rectangle tabBounds = { bounds.x, bounds.y, RAYGUI_TABBAR_ITEM_WIDTH, bounds.height }; - - if (*active < 0) *active = 0; - else if (*active > count - 1) *active = count - 1; - - int offsetX = 0; // Required in case tabs go out of screen - offsetX = (*active + 2)*RAYGUI_TABBAR_ITEM_WIDTH - GetScreenWidth(); - if (offsetX < 0) offsetX = 0; - - bool toggle = false; // Required for individual toggles - - // Draw control - //-------------------------------------------------------------------- - for (int i = 0; i < count; i++) - { - tabBounds.x = bounds.x + (RAYGUI_TABBAR_ITEM_WIDTH + 4)*i - offsetX; - - if (tabBounds.x < GetScreenWidth()) - { - // Draw tabs as toggle controls - int textAlignment = GuiGetStyle(TOGGLE, TEXT_ALIGNMENT); - int textPadding = GuiGetStyle(TOGGLE, TEXT_PADDING); - GuiSetStyle(TOGGLE, TEXT_ALIGNMENT, TEXT_ALIGN_LEFT); - GuiSetStyle(TOGGLE, TEXT_PADDING, 8); - - if (i == (*active)) - { - toggle = true; - GuiToggle(tabBounds, GuiIconText(12, text[i]), &toggle); - } - else - { - toggle = false; - GuiToggle(tabBounds, GuiIconText(12, text[i]), &toggle); - if (toggle) *active = i; - } - - // Close tab with middle mouse button pressed - if (CheckCollisionPointRec(GetMousePosition(), tabBounds) && IsMouseButtonPressed(MOUSE_MIDDLE_BUTTON)) result = i; - - GuiSetStyle(TOGGLE, TEXT_PADDING, textPadding); - GuiSetStyle(TOGGLE, TEXT_ALIGNMENT, textAlignment); - - // Draw tab close button - // NOTE: Only draw close button for current tab: if (CheckCollisionPointRec(mousePosition, tabBounds)) - int tempBorderWidth = GuiGetStyle(BUTTON, BORDER_WIDTH); - int tempTextAlignment = GuiGetStyle(BUTTON, TEXT_ALIGNMENT); - GuiSetStyle(BUTTON, BORDER_WIDTH, 1); - GuiSetStyle(BUTTON, TEXT_ALIGNMENT, TEXT_ALIGN_CENTER); -#if defined(RAYGUI_NO_ICONS) - if (GuiButton(RAYGUI_CLITERAL(Rectangle){ tabBounds.x + tabBounds.width - 14 - 5, tabBounds.y + 5, 14, 14 }, "x")) result = i; -#else - if (GuiButton(RAYGUI_CLITERAL(Rectangle){ tabBounds.x + tabBounds.width - 14 - 5, tabBounds.y + 5, 14, 14 }, GuiIconText(ICON_CROSS_SMALL, NULL))) result = i; -#endif - GuiSetStyle(BUTTON, BORDER_WIDTH, tempBorderWidth); - GuiSetStyle(BUTTON, TEXT_ALIGNMENT, tempTextAlignment); - } - } - - // Draw tab-bar bottom line - GuiDrawRectangle(RAYGUI_CLITERAL(Rectangle){ bounds.x, bounds.y + bounds.height - 1, bounds.width, 1 }, 0, BLANK, GetColor(GuiGetStyle(TOGGLE, BORDER_COLOR_NORMAL))); - //-------------------------------------------------------------------- - - return result; // Return as result the current TAB closing requested -} - -// Scroll Panel control -int GuiScrollPanel(Rectangle bounds, const char *text, Rectangle content, Vector2 *scroll, Rectangle *view) -{ - #define RAYGUI_MIN_SCROLLBAR_WIDTH 40 - #define RAYGUI_MIN_SCROLLBAR_HEIGHT 40 - #define RAYGUI_MIN_MOUSE_WHEEL_SPEED 20 - - int result = 0; - GuiState state = guiState; - - Rectangle temp = { 0 }; - if (view == NULL) view = &temp; - - Vector2 scrollPos = { 0.0f, 0.0f }; - if (scroll != NULL) scrollPos = *scroll; - - // Text will be drawn as a header bar (if provided) - Rectangle statusBar = { bounds.x, bounds.y, bounds.width, (float)RAYGUI_WINDOWBOX_STATUSBAR_HEIGHT }; - if (bounds.height < RAYGUI_WINDOWBOX_STATUSBAR_HEIGHT*2.0f) bounds.height = RAYGUI_WINDOWBOX_STATUSBAR_HEIGHT*2.0f; - - if (text != NULL) - { - // Move panel bounds after the header bar - bounds.y += (float)RAYGUI_WINDOWBOX_STATUSBAR_HEIGHT - 1; - bounds.height -= (float)RAYGUI_WINDOWBOX_STATUSBAR_HEIGHT + 1; - } - - bool hasHorizontalScrollBar = (content.width > bounds.width - 2*GuiGetStyle(DEFAULT, BORDER_WIDTH))? true : false; - bool hasVerticalScrollBar = (content.height > bounds.height - 2*GuiGetStyle(DEFAULT, BORDER_WIDTH))? true : false; - - // Recheck to account for the other scrollbar being visible - if (!hasHorizontalScrollBar) hasHorizontalScrollBar = (hasVerticalScrollBar && (content.width > (bounds.width - 2*GuiGetStyle(DEFAULT, BORDER_WIDTH) - GuiGetStyle(LISTVIEW, SCROLLBAR_WIDTH))))? true : false; - if (!hasVerticalScrollBar) hasVerticalScrollBar = (hasHorizontalScrollBar && (content.height > (bounds.height - 2*GuiGetStyle(DEFAULT, BORDER_WIDTH) - GuiGetStyle(LISTVIEW, SCROLLBAR_WIDTH))))? true : false; - - int horizontalScrollBarWidth = hasHorizontalScrollBar? GuiGetStyle(LISTVIEW, SCROLLBAR_WIDTH) : 0; - int verticalScrollBarWidth = hasVerticalScrollBar? GuiGetStyle(LISTVIEW, SCROLLBAR_WIDTH) : 0; - Rectangle horizontalScrollBar = { - (float)((GuiGetStyle(LISTVIEW, SCROLLBAR_SIDE) == SCROLLBAR_LEFT_SIDE)? (float)bounds.x + verticalScrollBarWidth : (float)bounds.x) + GuiGetStyle(DEFAULT, BORDER_WIDTH), - (float)bounds.y + bounds.height - horizontalScrollBarWidth - GuiGetStyle(DEFAULT, BORDER_WIDTH), - (float)bounds.width - verticalScrollBarWidth - 2*GuiGetStyle(DEFAULT, BORDER_WIDTH), - (float)horizontalScrollBarWidth - }; - Rectangle verticalScrollBar = { - (float)((GuiGetStyle(LISTVIEW, SCROLLBAR_SIDE) == SCROLLBAR_LEFT_SIDE)? (float)bounds.x + GuiGetStyle(DEFAULT, BORDER_WIDTH) : (float)bounds.x + bounds.width - verticalScrollBarWidth - GuiGetStyle(DEFAULT, BORDER_WIDTH)), - (float)bounds.y + GuiGetStyle(DEFAULT, BORDER_WIDTH), - (float)verticalScrollBarWidth, - (float)bounds.height - horizontalScrollBarWidth - 2*GuiGetStyle(DEFAULT, BORDER_WIDTH) - }; - - // Make sure scroll bars have a minimum width/height - if (horizontalScrollBar.width < RAYGUI_MIN_SCROLLBAR_WIDTH) horizontalScrollBar.width = RAYGUI_MIN_SCROLLBAR_WIDTH; - if (verticalScrollBar.height < RAYGUI_MIN_SCROLLBAR_HEIGHT) verticalScrollBar.height = RAYGUI_MIN_SCROLLBAR_HEIGHT; - - // Calculate view area (area without the scrollbars) - *view = (GuiGetStyle(LISTVIEW, SCROLLBAR_SIDE) == SCROLLBAR_LEFT_SIDE)? - RAYGUI_CLITERAL(Rectangle){ bounds.x + verticalScrollBarWidth + GuiGetStyle(DEFAULT, BORDER_WIDTH), bounds.y + GuiGetStyle(DEFAULT, BORDER_WIDTH), bounds.width - 2*GuiGetStyle(DEFAULT, BORDER_WIDTH) - verticalScrollBarWidth, bounds.height - 2*GuiGetStyle(DEFAULT, BORDER_WIDTH) - horizontalScrollBarWidth } : - RAYGUI_CLITERAL(Rectangle){ bounds.x + GuiGetStyle(DEFAULT, BORDER_WIDTH), bounds.y + GuiGetStyle(DEFAULT, BORDER_WIDTH), bounds.width - 2*GuiGetStyle(DEFAULT, BORDER_WIDTH) - verticalScrollBarWidth, bounds.height - 2*GuiGetStyle(DEFAULT, BORDER_WIDTH) - horizontalScrollBarWidth }; - - // Clip view area to the actual content size - if (view->width > content.width) view->width = content.width; - if (view->height > content.height) view->height = content.height; - - float horizontalMin = hasHorizontalScrollBar? ((GuiGetStyle(LISTVIEW, SCROLLBAR_SIDE) == SCROLLBAR_LEFT_SIDE)? (float)-verticalScrollBarWidth : 0) - (float)GuiGetStyle(DEFAULT, BORDER_WIDTH) : (((float)GuiGetStyle(LISTVIEW, SCROLLBAR_SIDE) == SCROLLBAR_LEFT_SIDE)? (float)-verticalScrollBarWidth : 0) - (float)GuiGetStyle(DEFAULT, BORDER_WIDTH); - float horizontalMax = hasHorizontalScrollBar? content.width - bounds.width + (float)verticalScrollBarWidth + GuiGetStyle(DEFAULT, BORDER_WIDTH) - (((float)GuiGetStyle(LISTVIEW, SCROLLBAR_SIDE) == SCROLLBAR_LEFT_SIDE)? (float)verticalScrollBarWidth : 0) : (float)-GuiGetStyle(DEFAULT, BORDER_WIDTH); - float verticalMin = hasVerticalScrollBar? 0.0f : -1.0f; - float verticalMax = hasVerticalScrollBar? content.height - bounds.height + (float)horizontalScrollBarWidth + (float)GuiGetStyle(DEFAULT, BORDER_WIDTH) : (float)-GuiGetStyle(DEFAULT, BORDER_WIDTH); - - // Update control - //-------------------------------------------------------------------- - if ((state != STATE_DISABLED) && !guiLocked) - { - Vector2 mousePoint = GetMousePosition(); - - // Check button state - if (CheckCollisionPointRec(mousePoint, bounds)) - { - if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) state = STATE_PRESSED; - else state = STATE_FOCUSED; - -#if defined(SUPPORT_SCROLLBAR_KEY_INPUT) - if (hasHorizontalScrollBar) - { - if (IsKeyDown(KEY_RIGHT)) scrollPos.x -= GuiGetStyle(SCROLLBAR, SCROLL_SPEED); - if (IsKeyDown(KEY_LEFT)) scrollPos.x += GuiGetStyle(SCROLLBAR, SCROLL_SPEED); - } - - if (hasVerticalScrollBar) - { - if (IsKeyDown(KEY_DOWN)) scrollPos.y -= GuiGetStyle(SCROLLBAR, SCROLL_SPEED); - if (IsKeyDown(KEY_UP)) scrollPos.y += GuiGetStyle(SCROLLBAR, SCROLL_SPEED); - } -#endif - float wheelMove = GetMouseWheelMove(); - - // Set scrolling speed with mouse wheel based on ratio between bounds and content - Vector2 mouseWheelSpeed = { content.width/bounds.width, content.height/bounds.height }; - if (mouseWheelSpeed.x < RAYGUI_MIN_MOUSE_WHEEL_SPEED) mouseWheelSpeed.x = RAYGUI_MIN_MOUSE_WHEEL_SPEED; - if (mouseWheelSpeed.y < RAYGUI_MIN_MOUSE_WHEEL_SPEED) mouseWheelSpeed.y = RAYGUI_MIN_MOUSE_WHEEL_SPEED; - - // Horizontal and vertical scrolling with mouse wheel - if (hasHorizontalScrollBar && (IsKeyDown(KEY_LEFT_CONTROL) || IsKeyDown(KEY_LEFT_SHIFT))) scrollPos.x += wheelMove*mouseWheelSpeed.x; - else scrollPos.y += wheelMove*mouseWheelSpeed.y; // Vertical scroll - } - } - - // Normalize scroll values - if (scrollPos.x > -horizontalMin) scrollPos.x = -horizontalMin; - if (scrollPos.x < -horizontalMax) scrollPos.x = -horizontalMax; - if (scrollPos.y > -verticalMin) scrollPos.y = -verticalMin; - if (scrollPos.y < -verticalMax) scrollPos.y = -verticalMax; - //-------------------------------------------------------------------- - - // Draw control - //-------------------------------------------------------------------- - if (text != NULL) GuiStatusBar(statusBar, text); // Draw panel header as status bar - - GuiDrawRectangle(bounds, 0, BLANK, GetColor(GuiGetStyle(DEFAULT, BACKGROUND_COLOR))); // Draw background - - // Save size of the scrollbar slider - const int slider = GuiGetStyle(SCROLLBAR, SCROLL_SLIDER_SIZE); - - // Draw horizontal scrollbar if visible - if (hasHorizontalScrollBar) - { - // Change scrollbar slider size to show the diff in size between the content width and the widget width - GuiSetStyle(SCROLLBAR, SCROLL_SLIDER_SIZE, (int)(((bounds.width - 2*GuiGetStyle(DEFAULT, BORDER_WIDTH) - verticalScrollBarWidth)/(int)content.width)*((int)bounds.width - 2*GuiGetStyle(DEFAULT, BORDER_WIDTH) - verticalScrollBarWidth))); - scrollPos.x = (float)-GuiScrollBar(horizontalScrollBar, (int)-scrollPos.x, (int)horizontalMin, (int)horizontalMax); - } - else scrollPos.x = 0.0f; - - // Draw vertical scrollbar if visible - if (hasVerticalScrollBar) - { - // Change scrollbar slider size to show the diff in size between the content height and the widget height - GuiSetStyle(SCROLLBAR, SCROLL_SLIDER_SIZE, (int)(((bounds.height - 2*GuiGetStyle(DEFAULT, BORDER_WIDTH) - horizontalScrollBarWidth)/(int)content.height)*((int)bounds.height - 2*GuiGetStyle(DEFAULT, BORDER_WIDTH) - horizontalScrollBarWidth))); - scrollPos.y = (float)-GuiScrollBar(verticalScrollBar, (int)-scrollPos.y, (int)verticalMin, (int)verticalMax); - } - else scrollPos.y = 0.0f; - - // Draw detail corner rectangle if both scroll bars are visible - if (hasHorizontalScrollBar && hasVerticalScrollBar) - { - Rectangle corner = { (GuiGetStyle(LISTVIEW, SCROLLBAR_SIDE) == SCROLLBAR_LEFT_SIDE)? (bounds.x + GuiGetStyle(DEFAULT, BORDER_WIDTH) + 2) : (horizontalScrollBar.x + horizontalScrollBar.width + 2), verticalScrollBar.y + verticalScrollBar.height + 2, (float)horizontalScrollBarWidth - 4, (float)verticalScrollBarWidth - 4 }; - GuiDrawRectangle(corner, 0, BLANK, GetColor(GuiGetStyle(LISTVIEW, TEXT + (state*3)))); - } - - // Draw scrollbar lines depending on current state - GuiDrawRectangle(bounds, GuiGetStyle(LISTVIEW, BORDER_WIDTH), GetColor(GuiGetStyle(LISTVIEW, BORDER + (state*3))), BLANK); - - // Set scrollbar slider size back to the way it was before - GuiSetStyle(SCROLLBAR, SCROLL_SLIDER_SIZE, slider); - //-------------------------------------------------------------------- - - if (scroll != NULL) *scroll = scrollPos; - - return result; -} - -// Label control -int GuiLabel(Rectangle bounds, const char *text) -{ - int result = 0; - GuiState state = guiState; - - // Update control - //-------------------------------------------------------------------- - //... - //-------------------------------------------------------------------- - - // Draw control - //-------------------------------------------------------------------- - GuiDrawText(text, GetTextBounds(LABEL, bounds), GuiGetStyle(LABEL, TEXT_ALIGNMENT), GetColor(GuiGetStyle(LABEL, TEXT + (state*3)))); - //-------------------------------------------------------------------- - - return result; -} - -// Button control, returns true when clicked -int GuiButton(Rectangle bounds, const char *text) -{ - int result = 0; - GuiState state = guiState; - - // Update control - //-------------------------------------------------------------------- - if ((state != STATE_DISABLED) && !guiLocked && !guiControlExclusiveMode) - { - Vector2 mousePoint = GetMousePosition(); - - // Check button state - if (CheckCollisionPointRec(mousePoint, bounds)) - { - if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) state = STATE_PRESSED; - else state = STATE_FOCUSED; - - if (IsMouseButtonReleased(MOUSE_LEFT_BUTTON)) result = 1; - } - } - //-------------------------------------------------------------------- - - // Draw control - //-------------------------------------------------------------------- - GuiDrawRectangle(bounds, GuiGetStyle(BUTTON, BORDER_WIDTH), GetColor(GuiGetStyle(BUTTON, BORDER + (state*3))), GetColor(GuiGetStyle(BUTTON, BASE + (state*3)))); - GuiDrawText(text, GetTextBounds(BUTTON, bounds), GuiGetStyle(BUTTON, TEXT_ALIGNMENT), GetColor(GuiGetStyle(BUTTON, TEXT + (state*3)))); - - if (state == STATE_FOCUSED) GuiTooltip(bounds); - //------------------------------------------------------------------ - - return result; // Button pressed: result = 1 -} - -// Label button control -int GuiLabelButton(Rectangle bounds, const char *text) -{ - GuiState state = guiState; - bool pressed = false; - - // NOTE: We force bounds.width to be all text - float textWidth = (float)GetTextWidth(text); - if ((bounds.width - 2*GuiGetStyle(LABEL, BORDER_WIDTH) - 2*GuiGetStyle(LABEL, TEXT_PADDING)) < textWidth) bounds.width = textWidth + 2*GuiGetStyle(LABEL, BORDER_WIDTH) + 2*GuiGetStyle(LABEL, TEXT_PADDING) + 2; - - // Update control - //-------------------------------------------------------------------- - if ((state != STATE_DISABLED) && !guiLocked && !guiControlExclusiveMode) - { - Vector2 mousePoint = GetMousePosition(); - - // Check checkbox state - if (CheckCollisionPointRec(mousePoint, bounds)) - { - if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) state = STATE_PRESSED; - else state = STATE_FOCUSED; - - if (IsMouseButtonReleased(MOUSE_LEFT_BUTTON)) pressed = true; - } - } - //-------------------------------------------------------------------- - - // Draw control - //-------------------------------------------------------------------- - GuiDrawText(text, GetTextBounds(LABEL, bounds), GuiGetStyle(LABEL, TEXT_ALIGNMENT), GetColor(GuiGetStyle(LABEL, TEXT + (state*3)))); - //-------------------------------------------------------------------- - - return pressed; -} - -// Toggle Button control -int GuiToggle(Rectangle bounds, const char *text, bool *active) -{ - int result = 0; - GuiState state = guiState; - - bool temp = false; - if (active == NULL) active = &temp; - - // Update control - //-------------------------------------------------------------------- - if ((state != STATE_DISABLED) && !guiLocked && !guiControlExclusiveMode) - { - Vector2 mousePoint = GetMousePosition(); - - // Check toggle button state - if (CheckCollisionPointRec(mousePoint, bounds)) - { - if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) state = STATE_PRESSED; - else if (IsMouseButtonReleased(MOUSE_LEFT_BUTTON)) - { - state = STATE_NORMAL; - *active = !(*active); - } - else state = STATE_FOCUSED; - } - } - //-------------------------------------------------------------------- - - // Draw control - //-------------------------------------------------------------------- - if (state == STATE_NORMAL) - { - GuiDrawRectangle(bounds, GuiGetStyle(TOGGLE, BORDER_WIDTH), GetColor(GuiGetStyle(TOGGLE, ((*active)? BORDER_COLOR_PRESSED : (BORDER + state*3)))), GetColor(GuiGetStyle(TOGGLE, ((*active)? BASE_COLOR_PRESSED : (BASE + state*3))))); - GuiDrawText(text, GetTextBounds(TOGGLE, bounds), GuiGetStyle(TOGGLE, TEXT_ALIGNMENT), GetColor(GuiGetStyle(TOGGLE, ((*active)? TEXT_COLOR_PRESSED : (TEXT + state*3))))); - } - else - { - GuiDrawRectangle(bounds, GuiGetStyle(TOGGLE, BORDER_WIDTH), GetColor(GuiGetStyle(TOGGLE, BORDER + state*3)), GetColor(GuiGetStyle(TOGGLE, BASE + state*3))); - GuiDrawText(text, GetTextBounds(TOGGLE, bounds), GuiGetStyle(TOGGLE, TEXT_ALIGNMENT), GetColor(GuiGetStyle(TOGGLE, TEXT + state*3))); - } - - if (state == STATE_FOCUSED) GuiTooltip(bounds); - //-------------------------------------------------------------------- - - return result; -} - -// Toggle Group control -int GuiToggleGroup(Rectangle bounds, const char *text, int *active) -{ - #if !defined(RAYGUI_TOGGLEGROUP_MAX_ITEMS) - #define RAYGUI_TOGGLEGROUP_MAX_ITEMS 32 - #endif - - int result = 0; - float initBoundsX = bounds.x; - - int temp = 0; - if (active == NULL) active = &temp; - - bool toggle = false; // Required for individual toggles - - // Get substrings items from text (items pointers) - int rows[RAYGUI_TOGGLEGROUP_MAX_ITEMS] = { 0 }; - int itemCount = 0; - const char **items = GuiTextSplit(text, ';', &itemCount, rows); - - int prevRow = rows[0]; - - for (int i = 0; i < itemCount; i++) - { - if (prevRow != rows[i]) - { - bounds.x = initBoundsX; - bounds.y += (bounds.height + GuiGetStyle(TOGGLE, GROUP_PADDING)); - prevRow = rows[i]; - } - - if (i == (*active)) - { - toggle = true; - GuiToggle(bounds, items[i], &toggle); - } - else - { - toggle = false; - GuiToggle(bounds, items[i], &toggle); - if (toggle) *active = i; - } - - bounds.x += (bounds.width + GuiGetStyle(TOGGLE, GROUP_PADDING)); - } - - return result; -} - -// Toggle Slider control extended -int GuiToggleSlider(Rectangle bounds, const char *text, int *active) -{ - int result = 0; - GuiState state = guiState; - - int temp = 0; - if (active == NULL) active = &temp; - - //bool toggle = false; // Required for individual toggles - - // Get substrings items from text (items pointers) - int itemCount = 0; - const char **items = NULL; - - if (text != NULL) items = GuiTextSplit(text, ';', &itemCount, NULL); - - Rectangle slider = { - 0, // Calculated later depending on the active toggle - bounds.y + GuiGetStyle(SLIDER, BORDER_WIDTH) + GuiGetStyle(SLIDER, SLIDER_PADDING), - (bounds.width - 2*GuiGetStyle(SLIDER, BORDER_WIDTH) - (itemCount + 1)*GuiGetStyle(SLIDER, SLIDER_PADDING))/itemCount, - bounds.height - 2*GuiGetStyle(SLIDER, BORDER_WIDTH) - 2*GuiGetStyle(SLIDER, SLIDER_PADDING) }; - - // Update control - //-------------------------------------------------------------------- - if ((state != STATE_DISABLED) && !guiLocked) - { - Vector2 mousePoint = GetMousePosition(); - - if (CheckCollisionPointRec(mousePoint, bounds)) - { - if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) state = STATE_PRESSED; - else if (IsMouseButtonReleased(MOUSE_LEFT_BUTTON)) - { - state = STATE_PRESSED; - (*active)++; - result = 1; - } - else state = STATE_FOCUSED; - } - - if ((*active) && (state != STATE_FOCUSED)) state = STATE_PRESSED; - } - - if (*active >= itemCount) *active = 0; - slider.x = bounds.x + GuiGetStyle(SLIDER, BORDER_WIDTH) + (*active + 1)*GuiGetStyle(SLIDER, SLIDER_PADDING) + (*active)*slider.width; - //-------------------------------------------------------------------- - - // Draw control - //-------------------------------------------------------------------- - GuiDrawRectangle(bounds, GuiGetStyle(SLIDER, BORDER_WIDTH), GetColor(GuiGetStyle(TOGGLE, BORDER + (state*3))), - GetColor(GuiGetStyle(TOGGLE, BASE_COLOR_NORMAL))); - - // Draw internal slider - if (state == STATE_NORMAL) GuiDrawRectangle(slider, 0, BLANK, GetColor(GuiGetStyle(SLIDER, BASE_COLOR_PRESSED))); - else if (state == STATE_FOCUSED) GuiDrawRectangle(slider, 0, BLANK, GetColor(GuiGetStyle(SLIDER, BASE_COLOR_FOCUSED))); - else if (state == STATE_PRESSED) GuiDrawRectangle(slider, 0, BLANK, GetColor(GuiGetStyle(SLIDER, BASE_COLOR_PRESSED))); - - // Draw text in slider - if (text != NULL) - { - Rectangle textBounds = { 0 }; - textBounds.width = (float)GetTextWidth(text); - textBounds.height = (float)GuiGetStyle(DEFAULT, TEXT_SIZE); - textBounds.x = slider.x + slider.width/2 - textBounds.width/2; - textBounds.y = bounds.y + bounds.height/2 - GuiGetStyle(DEFAULT, TEXT_SIZE)/2; - - GuiDrawText(items[*active], textBounds, GuiGetStyle(TOGGLE, TEXT_ALIGNMENT), Fade(GetColor(GuiGetStyle(TOGGLE, TEXT + (state*3))), guiAlpha)); - } - //-------------------------------------------------------------------- - - return result; -} - -// Check Box control, returns 1 when state changed -int GuiCheckBox(Rectangle bounds, const char *text, bool *checked) -{ - int result = 0; - GuiState state = guiState; - - bool temp = false; - if (checked == NULL) checked = &temp; - - Rectangle textBounds = { 0 }; - - if (text != NULL) - { - textBounds.width = (float)GetTextWidth(text) + 2; - textBounds.height = (float)GuiGetStyle(DEFAULT, TEXT_SIZE); - textBounds.x = bounds.x + bounds.width + GuiGetStyle(CHECKBOX, TEXT_PADDING); - textBounds.y = bounds.y + bounds.height/2 - GuiGetStyle(DEFAULT, TEXT_SIZE)/2; - if (GuiGetStyle(CHECKBOX, TEXT_ALIGNMENT) == TEXT_ALIGN_LEFT) textBounds.x = bounds.x - textBounds.width - GuiGetStyle(CHECKBOX, TEXT_PADDING); - } - - // Update control - //-------------------------------------------------------------------- - if ((state != STATE_DISABLED) && !guiLocked && !guiControlExclusiveMode) - { - Vector2 mousePoint = GetMousePosition(); - - Rectangle totalBounds = { - (GuiGetStyle(CHECKBOX, TEXT_ALIGNMENT) == TEXT_ALIGN_LEFT)? textBounds.x : bounds.x, - bounds.y, - bounds.width + textBounds.width + GuiGetStyle(CHECKBOX, TEXT_PADDING), - bounds.height, - }; - - // Check checkbox state - if (CheckCollisionPointRec(mousePoint, totalBounds)) - { - if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) state = STATE_PRESSED; - else state = STATE_FOCUSED; - - if (IsMouseButtonReleased(MOUSE_LEFT_BUTTON)) - { - *checked = !(*checked); - result = 1; - } - } - } - //-------------------------------------------------------------------- - - // Draw control - //-------------------------------------------------------------------- - GuiDrawRectangle(bounds, GuiGetStyle(CHECKBOX, BORDER_WIDTH), GetColor(GuiGetStyle(CHECKBOX, BORDER + (state*3))), BLANK); - - if (*checked) - { - Rectangle check = { bounds.x + GuiGetStyle(CHECKBOX, BORDER_WIDTH) + GuiGetStyle(CHECKBOX, CHECK_PADDING), - bounds.y + GuiGetStyle(CHECKBOX, BORDER_WIDTH) + GuiGetStyle(CHECKBOX, CHECK_PADDING), - bounds.width - 2*(GuiGetStyle(CHECKBOX, BORDER_WIDTH) + GuiGetStyle(CHECKBOX, CHECK_PADDING)), - bounds.height - 2*(GuiGetStyle(CHECKBOX, BORDER_WIDTH) + GuiGetStyle(CHECKBOX, CHECK_PADDING)) }; - GuiDrawRectangle(check, 0, BLANK, GetColor(GuiGetStyle(CHECKBOX, TEXT + state*3))); - } - - GuiDrawText(text, textBounds, (GuiGetStyle(CHECKBOX, TEXT_ALIGNMENT) == TEXT_ALIGN_RIGHT)? TEXT_ALIGN_LEFT : TEXT_ALIGN_RIGHT, GetColor(GuiGetStyle(LABEL, TEXT + (state*3)))); - //-------------------------------------------------------------------- - - return result; -} - -// Combo Box control -int GuiComboBox(Rectangle bounds, const char *text, int *active) -{ - int result = 0; - GuiState state = guiState; - - int temp = 0; - if (active == NULL) active = &temp; - - bounds.width -= (GuiGetStyle(COMBOBOX, COMBO_BUTTON_WIDTH) + GuiGetStyle(COMBOBOX, COMBO_BUTTON_SPACING)); - - Rectangle selector = { (float)bounds.x + bounds.width + GuiGetStyle(COMBOBOX, COMBO_BUTTON_SPACING), - (float)bounds.y, (float)GuiGetStyle(COMBOBOX, COMBO_BUTTON_WIDTH), (float)bounds.height }; - - // Get substrings items from text (items pointers, lengths and count) - int itemCount = 0; - const char **items = GuiTextSplit(text, ';', &itemCount, NULL); - - if (*active < 0) *active = 0; - else if (*active > (itemCount - 1)) *active = itemCount - 1; - - // Update control - //-------------------------------------------------------------------- - if ((state != STATE_DISABLED) && !guiLocked && (itemCount > 1) && !guiControlExclusiveMode) - { - Vector2 mousePoint = GetMousePosition(); - - if (CheckCollisionPointRec(mousePoint, bounds) || - CheckCollisionPointRec(mousePoint, selector)) - { - if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) - { - *active += 1; - if (*active >= itemCount) *active = 0; // Cyclic combobox - } - - if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) state = STATE_PRESSED; - else state = STATE_FOCUSED; - } - } - //-------------------------------------------------------------------- - - // Draw control - //-------------------------------------------------------------------- - // Draw combo box main - GuiDrawRectangle(bounds, GuiGetStyle(COMBOBOX, BORDER_WIDTH), GetColor(GuiGetStyle(COMBOBOX, BORDER + (state*3))), GetColor(GuiGetStyle(COMBOBOX, BASE + (state*3)))); - GuiDrawText(items[*active], GetTextBounds(COMBOBOX, bounds), GuiGetStyle(COMBOBOX, TEXT_ALIGNMENT), GetColor(GuiGetStyle(COMBOBOX, TEXT + (state*3)))); - - // Draw selector using a custom button - // NOTE: BORDER_WIDTH and TEXT_ALIGNMENT forced values - int tempBorderWidth = GuiGetStyle(BUTTON, BORDER_WIDTH); - int tempTextAlign = GuiGetStyle(BUTTON, TEXT_ALIGNMENT); - GuiSetStyle(BUTTON, BORDER_WIDTH, 1); - GuiSetStyle(BUTTON, TEXT_ALIGNMENT, TEXT_ALIGN_CENTER); - - GuiButton(selector, TextFormat("%i/%i", *active + 1, itemCount)); - - GuiSetStyle(BUTTON, TEXT_ALIGNMENT, tempTextAlign); - GuiSetStyle(BUTTON, BORDER_WIDTH, tempBorderWidth); - //-------------------------------------------------------------------- - - return result; -} - -// Dropdown Box control -// NOTE: Returns mouse click -int GuiDropdownBox(Rectangle bounds, const char *text, int *active, bool editMode) -{ - int result = 0; - GuiState state = guiState; - - int temp = 0; - if (active == NULL) active = &temp; - - int itemSelected = *active; - int itemFocused = -1; - - int direction = 0; // Dropdown box open direction: down (default) - if (GuiGetStyle(DROPDOWNBOX, DROPDOWN_ROLL_UP) == 1) direction = 1; // Up - - // Get substrings items from text (items pointers, lengths and count) - int itemCount = 0; - const char **items = GuiTextSplit(text, ';', &itemCount, NULL); - - Rectangle boundsOpen = bounds; - boundsOpen.height = (itemCount + 1)*(bounds.height + GuiGetStyle(DROPDOWNBOX, DROPDOWN_ITEMS_SPACING)); - if (direction == 1) boundsOpen.y -= itemCount*(bounds.height + GuiGetStyle(DROPDOWNBOX, DROPDOWN_ITEMS_SPACING)) + GuiGetStyle(DROPDOWNBOX, DROPDOWN_ITEMS_SPACING); - - Rectangle itemBounds = bounds; - - // Update control - //-------------------------------------------------------------------- - if ((state != STATE_DISABLED) && (editMode || !guiLocked) && (itemCount > 1) && !guiControlExclusiveMode) - { - Vector2 mousePoint = GetMousePosition(); - - if (editMode) - { - state = STATE_PRESSED; - - // Check if mouse has been pressed or released outside limits - if (!CheckCollisionPointRec(mousePoint, boundsOpen)) - { - if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON) || IsMouseButtonReleased(MOUSE_LEFT_BUTTON)) result = 1; - } - - // Check if already selected item has been pressed again - if (CheckCollisionPointRec(mousePoint, bounds) && IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) result = 1; - - // Check focused and selected item - for (int i = 0; i < itemCount; i++) - { - // Update item rectangle y position for next item - if (direction == 0) itemBounds.y += (bounds.height + GuiGetStyle(DROPDOWNBOX, DROPDOWN_ITEMS_SPACING)); - else itemBounds.y -= (bounds.height + GuiGetStyle(DROPDOWNBOX, DROPDOWN_ITEMS_SPACING)); - - if (CheckCollisionPointRec(mousePoint, itemBounds)) - { - itemFocused = i; - if (IsMouseButtonReleased(MOUSE_LEFT_BUTTON)) - { - itemSelected = i; - result = 1; // Item selected - } - break; - } - } - - itemBounds = bounds; - } - else - { - if (CheckCollisionPointRec(mousePoint, bounds)) - { - if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) - { - result = 1; - state = STATE_PRESSED; - } - else state = STATE_FOCUSED; - } - } - } - //-------------------------------------------------------------------- - - // Draw control - //-------------------------------------------------------------------- - if (editMode) GuiPanel(boundsOpen, NULL); - - GuiDrawRectangle(bounds, GuiGetStyle(DROPDOWNBOX, BORDER_WIDTH), GetColor(GuiGetStyle(DROPDOWNBOX, BORDER + state*3)), GetColor(GuiGetStyle(DROPDOWNBOX, BASE + state*3))); - GuiDrawText(items[itemSelected], GetTextBounds(DROPDOWNBOX, bounds), GuiGetStyle(DROPDOWNBOX, TEXT_ALIGNMENT), GetColor(GuiGetStyle(DROPDOWNBOX, TEXT + state*3))); - - if (editMode) - { - // Draw visible items - for (int i = 0; i < itemCount; i++) - { - // Update item rectangle y position for next item - if (direction == 0) itemBounds.y += (bounds.height + GuiGetStyle(DROPDOWNBOX, DROPDOWN_ITEMS_SPACING)); - else itemBounds.y -= (bounds.height + GuiGetStyle(DROPDOWNBOX, DROPDOWN_ITEMS_SPACING)); - - if (i == itemSelected) - { - GuiDrawRectangle(itemBounds, GuiGetStyle(DROPDOWNBOX, BORDER_WIDTH), GetColor(GuiGetStyle(DROPDOWNBOX, BORDER_COLOR_PRESSED)), GetColor(GuiGetStyle(DROPDOWNBOX, BASE_COLOR_PRESSED))); - GuiDrawText(items[i], GetTextBounds(DROPDOWNBOX, itemBounds), GuiGetStyle(DROPDOWNBOX, TEXT_ALIGNMENT), GetColor(GuiGetStyle(DROPDOWNBOX, TEXT_COLOR_PRESSED))); - } - else if (i == itemFocused) - { - GuiDrawRectangle(itemBounds, GuiGetStyle(DROPDOWNBOX, BORDER_WIDTH), GetColor(GuiGetStyle(DROPDOWNBOX, BORDER_COLOR_FOCUSED)), GetColor(GuiGetStyle(DROPDOWNBOX, BASE_COLOR_FOCUSED))); - GuiDrawText(items[i], GetTextBounds(DROPDOWNBOX, itemBounds), GuiGetStyle(DROPDOWNBOX, TEXT_ALIGNMENT), GetColor(GuiGetStyle(DROPDOWNBOX, TEXT_COLOR_FOCUSED))); - } - else GuiDrawText(items[i], GetTextBounds(DROPDOWNBOX, itemBounds), GuiGetStyle(DROPDOWNBOX, TEXT_ALIGNMENT), GetColor(GuiGetStyle(DROPDOWNBOX, TEXT_COLOR_NORMAL))); - } - } - - if (!GuiGetStyle(DROPDOWNBOX, DROPDOWN_ARROW_HIDDEN)) - { - // Draw arrows (using icon if available) -#if defined(RAYGUI_NO_ICONS) - GuiDrawText("v", RAYGUI_CLITERAL(Rectangle){ bounds.x + bounds.width - GuiGetStyle(DROPDOWNBOX, ARROW_PADDING), bounds.y + bounds.height/2 - 2, 10, 10 }, - TEXT_ALIGN_CENTER, GetColor(GuiGetStyle(DROPDOWNBOX, TEXT + (state*3)))); -#else - GuiDrawText(direction? "#121#" : "#120#", RAYGUI_CLITERAL(Rectangle){ bounds.x + bounds.width - GuiGetStyle(DROPDOWNBOX, ARROW_PADDING), bounds.y + bounds.height/2 - 6, 10, 10 }, - TEXT_ALIGN_CENTER, GetColor(GuiGetStyle(DROPDOWNBOX, TEXT + (state*3)))); // ICON_ARROW_DOWN_FILL -#endif - } - //-------------------------------------------------------------------- - - *active = itemSelected; - - // TODO: Use result to return more internal states: mouse-press out-of-bounds, mouse-press over selected-item... - return result; // Mouse click: result = 1 -} - -// Text Box control -// NOTE: Returns true on ENTER pressed (useful for data validation) -int GuiTextBox(Rectangle bounds, char *text, int textSize, bool editMode) -{ - #if !defined(RAYGUI_TEXTBOX_AUTO_CURSOR_COOLDOWN) - #define RAYGUI_TEXTBOX_AUTO_CURSOR_COOLDOWN 40 // Frames to wait for autocursor movement - #endif - #if !defined(RAYGUI_TEXTBOX_AUTO_CURSOR_DELAY) - #define RAYGUI_TEXTBOX_AUTO_CURSOR_DELAY 1 // Frames delay for autocursor movement - #endif - - int result = 0; - GuiState state = guiState; - - bool multiline = false; // TODO: Consider multiline text input - int wrapMode = GuiGetStyle(DEFAULT, TEXT_WRAP_MODE); - - Rectangle textBounds = GetTextBounds(TEXTBOX, bounds); - int textLength = (int)strlen(text); // Get current text length - int thisCursorIndex = textBoxCursorIndex; - if (thisCursorIndex > textLength) thisCursorIndex = textLength; - int textWidth = GetTextWidth(text) - GetTextWidth(text + thisCursorIndex); - int textIndexOffset = 0; // Text index offset to start drawing in the box - - // Cursor rectangle - // NOTE: Position X value should be updated - Rectangle cursor = { - textBounds.x + textWidth + GuiGetStyle(DEFAULT, TEXT_SPACING), - textBounds.y + textBounds.height/2 - GuiGetStyle(DEFAULT, TEXT_SIZE), - 2, - (float)GuiGetStyle(DEFAULT, TEXT_SIZE)*2 - }; - - if (cursor.height >= bounds.height) cursor.height = bounds.height - GuiGetStyle(TEXTBOX, BORDER_WIDTH)*2; - if (cursor.y < (bounds.y + GuiGetStyle(TEXTBOX, BORDER_WIDTH))) cursor.y = bounds.y + GuiGetStyle(TEXTBOX, BORDER_WIDTH); - - // Mouse cursor rectangle - // NOTE: Initialized outside of screen - Rectangle mouseCursor = cursor; - mouseCursor.x = -1; - mouseCursor.width = 1; - - // Auto-cursor movement logic - // NOTE: Cursor moves automatically when key down after some time - if (IsKeyDown(KEY_LEFT) || IsKeyDown(KEY_RIGHT) || IsKeyDown(KEY_UP) || IsKeyDown(KEY_DOWN) || IsKeyDown(KEY_BACKSPACE) || IsKeyDown(KEY_DELETE)) autoCursorCooldownCounter++; - else - { - autoCursorCooldownCounter = 0; // GLOBAL: Cursor cooldown counter - autoCursorDelayCounter = 0; // GLOBAL: Cursor delay counter - } - - // Blink-cursor frame counter - //if (!autoCursorMode) blinkCursorFrameCounter++; - //else blinkCursorFrameCounter = 0; - - // Update control - //-------------------------------------------------------------------- - // WARNING: Text editing is only supported under certain conditions: - if ((state != STATE_DISABLED) && // Control not disabled - !GuiGetStyle(TEXTBOX, TEXT_READONLY) && // TextBox not on read-only mode - !guiLocked && // Gui not locked - !guiControlExclusiveMode && // No gui slider on dragging - (wrapMode == TEXT_WRAP_NONE)) // No wrap mode - { - Vector2 mousePosition = GetMousePosition(); - - if (editMode) - { - state = STATE_PRESSED; - - if (textBoxCursorIndex > textLength) textBoxCursorIndex = textLength; - - // If text does not fit in the textbox and current cursor position is out of bounds, - // we add an index offset to text for drawing only what requires depending on cursor - while (textWidth >= textBounds.width) - { - int nextCodepointSize = 0; - GetCodepointNext(text + textIndexOffset, &nextCodepointSize); - - textIndexOffset += nextCodepointSize; - - textWidth = GetTextWidth(text + textIndexOffset) - GetTextWidth(text + textBoxCursorIndex); - } - - int codepoint = GetCharPressed(); // Get Unicode codepoint - if (multiline && IsKeyPressed(KEY_ENTER)) codepoint = (int)'\n'; - - // Encode codepoint as UTF-8 - int codepointSize = 0; - const char *charEncoded = CodepointToUTF8(codepoint, &codepointSize); - - // Add codepoint to text, at current cursor position - // NOTE: Make sure we do not overflow buffer size - if (((multiline && (codepoint == (int)'\n')) || (codepoint >= 32)) && ((textLength + codepointSize) < textSize)) - { - // Move forward data from cursor position - for (int i = (textLength + codepointSize); i > textBoxCursorIndex; i--) text[i] = text[i - codepointSize]; - - // Add new codepoint in current cursor position - for (int i = 0; i < codepointSize; i++) text[textBoxCursorIndex + i] = charEncoded[i]; - - textBoxCursorIndex += codepointSize; - textLength += codepointSize; - - // Make sure text last character is EOL - text[textLength] = '\0'; - } - - // Move cursor to start - if ((textLength > 0) && IsKeyPressed(KEY_HOME)) textBoxCursorIndex = 0; - - // Move cursor to end - if ((textLength > textBoxCursorIndex) && IsKeyPressed(KEY_END)) textBoxCursorIndex = textLength; - - // Delete codepoint from text, after current cursor position - if ((textLength > textBoxCursorIndex) && (IsKeyPressed(KEY_DELETE) || (IsKeyDown(KEY_DELETE) && (autoCursorCooldownCounter >= RAYGUI_TEXTBOX_AUTO_CURSOR_COOLDOWN)))) - { - autoCursorDelayCounter++; - - if (IsKeyPressed(KEY_DELETE) || (autoCursorDelayCounter%RAYGUI_TEXTBOX_AUTO_CURSOR_DELAY) == 0) // Delay every movement some frames - { - int nextCodepointSize = 0; - GetCodepointNext(text + textBoxCursorIndex, &nextCodepointSize); - - // Move backward text from cursor position - for (int i = textBoxCursorIndex; i < textLength; i++) text[i] = text[i + nextCodepointSize]; - - textLength -= codepointSize; - if (textBoxCursorIndex > textLength) textBoxCursorIndex = textLength; - - // Make sure text last character is EOL - text[textLength] = '\0'; - } - } - - // Delete related codepoints from text, before current cursor position - if ((textLength > 0) && IsKeyPressed(KEY_BACKSPACE) && (IsKeyDown(KEY_LEFT_CONTROL) || IsKeyDown(KEY_RIGHT_CONTROL))) - { - int i = textBoxCursorIndex - 1; - int accCodepointSize = 0; - - // Move cursor to the end of word if on space already - while ((i > 0) && isspace(text[i])) - { - int prevCodepointSize = 0; - GetCodepointPrevious(text + i, &prevCodepointSize); - i -= prevCodepointSize; - accCodepointSize += prevCodepointSize; - } - - // Move cursor to the start of the word - while ((i > 0) && !isspace(text[i])) - { - int prevCodepointSize = 0; - GetCodepointPrevious(text + i, &prevCodepointSize); - i -= prevCodepointSize; - accCodepointSize += prevCodepointSize; - } - - // Move forward text from cursor position - for (int j = (textBoxCursorIndex - accCodepointSize); j < textLength; j++) text[j] = text[j + accCodepointSize]; - - // Prevent cursor index from decrementing past 0 - if (textBoxCursorIndex > 0) - { - textBoxCursorIndex -= accCodepointSize; - textLength -= accCodepointSize; - } - - // Make sure text last character is EOL - text[textLength] = '\0'; - } - else if ((textLength > 0) && (IsKeyPressed(KEY_BACKSPACE) || (IsKeyDown(KEY_BACKSPACE) && (autoCursorCooldownCounter >= RAYGUI_TEXTBOX_AUTO_CURSOR_COOLDOWN)))) - { - autoCursorDelayCounter++; - - if (IsKeyPressed(KEY_BACKSPACE) || (autoCursorDelayCounter%RAYGUI_TEXTBOX_AUTO_CURSOR_DELAY) == 0) // Delay every movement some frames - { - int prevCodepointSize = 0; - - // Prevent cursor index from decrementing past 0 - if (textBoxCursorIndex > 0) - { - GetCodepointPrevious(text + textBoxCursorIndex, &prevCodepointSize); - - // Move backward text from cursor position - for (int i = (textBoxCursorIndex - prevCodepointSize); i < textLength; i++) text[i] = text[i + prevCodepointSize]; - - textBoxCursorIndex -= codepointSize; - textLength -= codepointSize; - } - - // Make sure text last character is EOL - text[textLength] = '\0'; - } - } - - // Move cursor position with keys - if (IsKeyPressed(KEY_LEFT) || (IsKeyDown(KEY_LEFT) && (autoCursorCooldownCounter > RAYGUI_TEXTBOX_AUTO_CURSOR_COOLDOWN))) - { - autoCursorDelayCounter++; - - if (IsKeyPressed(KEY_LEFT) || (autoCursorDelayCounter%RAYGUI_TEXTBOX_AUTO_CURSOR_DELAY) == 0) // Delay every movement some frames - { - int prevCodepointSize = 0; - if (textBoxCursorIndex > 0) GetCodepointPrevious(text + textBoxCursorIndex, &prevCodepointSize); - - if (textBoxCursorIndex >= prevCodepointSize) textBoxCursorIndex -= prevCodepointSize; - } - } - else if (IsKeyPressed(KEY_RIGHT) || (IsKeyDown(KEY_RIGHT) && (autoCursorCooldownCounter > RAYGUI_TEXTBOX_AUTO_CURSOR_COOLDOWN))) - { - autoCursorDelayCounter++; - - if (IsKeyPressed(KEY_RIGHT) || (autoCursorDelayCounter%RAYGUI_TEXTBOX_AUTO_CURSOR_DELAY) == 0) // Delay every movement some frames - { - int nextCodepointSize = 0; - GetCodepointNext(text + textBoxCursorIndex, &nextCodepointSize); - - if ((textBoxCursorIndex + nextCodepointSize) <= textLength) textBoxCursorIndex += nextCodepointSize; - } - } - - // Move cursor position with mouse - if (CheckCollisionPointRec(mousePosition, textBounds)) // Mouse hover text - { - float scaleFactor = (float)GuiGetStyle(DEFAULT, TEXT_SIZE)/(float)guiFont.baseSize; - int codepointIndex = 0; - float glyphWidth = 0.0f; - float widthToMouseX = 0; - int mouseCursorIndex = 0; - - for (int i = textIndexOffset; i < textLength; i++) - { - codepoint = GetCodepointNext(&text[i], &codepointSize); - codepointIndex = GetGlyphIndex(guiFont, codepoint); - - if (guiFont.glyphs[codepointIndex].advanceX == 0) glyphWidth = ((float)guiFont.recs[codepointIndex].width*scaleFactor); - else glyphWidth = ((float)guiFont.glyphs[codepointIndex].advanceX*scaleFactor); - - if (mousePosition.x <= (textBounds.x + (widthToMouseX + glyphWidth/2))) - { - mouseCursor.x = textBounds.x + widthToMouseX; - mouseCursorIndex = i; - break; - } - - widthToMouseX += (glyphWidth + (float)GuiGetStyle(DEFAULT, TEXT_SPACING)); - } - - // Check if mouse cursor is at the last position - int textEndWidth = GetTextWidth(text + textIndexOffset); - if (GetMousePosition().x >= (textBounds.x + textEndWidth - glyphWidth/2)) - { - mouseCursor.x = textBounds.x + textEndWidth; - mouseCursorIndex = textLength; - } - - // Place cursor at required index on mouse click - if ((mouseCursor.x >= 0) && IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) - { - cursor.x = mouseCursor.x; - textBoxCursorIndex = mouseCursorIndex; - } - } - else mouseCursor.x = -1; - - // Recalculate cursor position.y depending on textBoxCursorIndex - cursor.x = bounds.x + GuiGetStyle(TEXTBOX, TEXT_PADDING) + GetTextWidth(text + textIndexOffset) - GetTextWidth(text + textBoxCursorIndex) + GuiGetStyle(DEFAULT, TEXT_SPACING); - //if (multiline) cursor.y = GetTextLines() - - // Finish text editing on ENTER or mouse click outside bounds - if ((!multiline && IsKeyPressed(KEY_ENTER)) || - (!CheckCollisionPointRec(mousePosition, bounds) && IsMouseButtonPressed(MOUSE_LEFT_BUTTON))) - { - textBoxCursorIndex = 0; // GLOBAL: Reset the shared cursor index - result = 1; - } - } - else - { - if (CheckCollisionPointRec(mousePosition, bounds)) - { - state = STATE_FOCUSED; - - if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) - { - textBoxCursorIndex = textLength; // GLOBAL: Place cursor index to the end of current text - result = 1; - } - } - } - } - //-------------------------------------------------------------------- - - // Draw control - //-------------------------------------------------------------------- - if (state == STATE_PRESSED) - { - GuiDrawRectangle(bounds, GuiGetStyle(TEXTBOX, BORDER_WIDTH), GetColor(GuiGetStyle(TEXTBOX, BORDER + (state*3))), GetColor(GuiGetStyle(TEXTBOX, BASE_COLOR_PRESSED))); - } - else if (state == STATE_DISABLED) - { - GuiDrawRectangle(bounds, GuiGetStyle(TEXTBOX, BORDER_WIDTH), GetColor(GuiGetStyle(TEXTBOX, BORDER + (state*3))), GetColor(GuiGetStyle(TEXTBOX, BASE_COLOR_DISABLED))); - } - else GuiDrawRectangle(bounds, GuiGetStyle(TEXTBOX, BORDER_WIDTH), GetColor(GuiGetStyle(TEXTBOX, BORDER + (state*3))), BLANK); - - // Draw text considering index offset if required - // NOTE: Text index offset depends on cursor position - GuiDrawText(text + textIndexOffset, textBounds, GuiGetStyle(TEXTBOX, TEXT_ALIGNMENT), GetColor(GuiGetStyle(TEXTBOX, TEXT + (state*3)))); - - // Draw cursor - if (editMode && !GuiGetStyle(TEXTBOX, TEXT_READONLY)) - { - //if (autoCursorMode || ((blinkCursorFrameCounter/40)%2 == 0)) - GuiDrawRectangle(cursor, 0, BLANK, GetColor(GuiGetStyle(TEXTBOX, BORDER_COLOR_PRESSED))); - - // Draw mouse position cursor (if required) - if (mouseCursor.x >= 0) GuiDrawRectangle(mouseCursor, 0, BLANK, GetColor(GuiGetStyle(TEXTBOX, BORDER_COLOR_PRESSED))); - } - else if (state == STATE_FOCUSED) GuiTooltip(bounds); - //-------------------------------------------------------------------- - - return result; // Mouse button pressed: result = 1 -} - -/* -// Text Box control with multiple lines and word-wrap -// NOTE: This text-box is readonly, no editing supported by default -bool GuiTextBoxMulti(Rectangle bounds, char *text, int textSize, bool editMode) -{ - bool pressed = false; - - GuiSetStyle(TEXTBOX, TEXT_READONLY, 1); - GuiSetStyle(DEFAULT, TEXT_WRAP_MODE, TEXT_WRAP_WORD); // WARNING: If wrap mode enabled, text editing is not supported - GuiSetStyle(DEFAULT, TEXT_ALIGNMENT_VERTICAL, TEXT_ALIGN_TOP); - - // TODO: Implement methods to calculate cursor position properly - pressed = GuiTextBox(bounds, text, textSize, editMode); - - GuiSetStyle(DEFAULT, TEXT_ALIGNMENT_VERTICAL, TEXT_ALIGN_MIDDLE); - GuiSetStyle(DEFAULT, TEXT_WRAP_MODE, TEXT_WRAP_NONE); - GuiSetStyle(TEXTBOX, TEXT_READONLY, 0); - - return pressed; -} -*/ - -// Spinner control, returns selected value -int GuiSpinner(Rectangle bounds, const char *text, int *value, int minValue, int maxValue, bool editMode) -{ - int result = 1; - GuiState state = guiState; - - int tempValue = *value; - - Rectangle spinner = { bounds.x + GuiGetStyle(SPINNER, SPIN_BUTTON_WIDTH) + GuiGetStyle(SPINNER, SPIN_BUTTON_SPACING), bounds.y, - bounds.width - 2*(GuiGetStyle(SPINNER, SPIN_BUTTON_WIDTH) + GuiGetStyle(SPINNER, SPIN_BUTTON_SPACING)), bounds.height }; - Rectangle leftButtonBound = { (float)bounds.x, (float)bounds.y, (float)GuiGetStyle(SPINNER, SPIN_BUTTON_WIDTH), (float)bounds.height }; - Rectangle rightButtonBound = { (float)bounds.x + bounds.width - GuiGetStyle(SPINNER, SPIN_BUTTON_WIDTH), (float)bounds.y, (float)GuiGetStyle(SPINNER, SPIN_BUTTON_WIDTH), (float)bounds.height }; - - Rectangle textBounds = { 0 }; - if (text != NULL) - { - textBounds.width = (float)GetTextWidth(text) + 2; - textBounds.height = (float)GuiGetStyle(DEFAULT, TEXT_SIZE); - textBounds.x = bounds.x + bounds.width + GuiGetStyle(SPINNER, TEXT_PADDING); - textBounds.y = bounds.y + bounds.height/2 - GuiGetStyle(DEFAULT, TEXT_SIZE)/2; - if (GuiGetStyle(SPINNER, TEXT_ALIGNMENT) == TEXT_ALIGN_LEFT) textBounds.x = bounds.x - textBounds.width - GuiGetStyle(SPINNER, TEXT_PADDING); - } - - // Update control - //-------------------------------------------------------------------- - if ((state != STATE_DISABLED) && !guiLocked && !guiControlExclusiveMode) - { - Vector2 mousePoint = GetMousePosition(); - - // Check spinner state - if (CheckCollisionPointRec(mousePoint, bounds)) - { - if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) state = STATE_PRESSED; - else state = STATE_FOCUSED; - } - } - -#if defined(RAYGUI_NO_ICONS) - if (GuiButton(leftButtonBound, "<")) tempValue--; - if (GuiButton(rightButtonBound, ">")) tempValue++; -#else - if (GuiButton(leftButtonBound, GuiIconText(ICON_ARROW_LEFT_FILL, NULL))) tempValue--; - if (GuiButton(rightButtonBound, GuiIconText(ICON_ARROW_RIGHT_FILL, NULL))) tempValue++; -#endif - - if (!editMode) - { - if (tempValue < minValue) tempValue = minValue; - if (tempValue > maxValue) tempValue = maxValue; - } - //-------------------------------------------------------------------- - - // Draw control - //-------------------------------------------------------------------- - result = GuiValueBox(spinner, NULL, &tempValue, minValue, maxValue, editMode); - - // Draw value selector custom buttons - // NOTE: BORDER_WIDTH and TEXT_ALIGNMENT forced values - int tempBorderWidth = GuiGetStyle(BUTTON, BORDER_WIDTH); - int tempTextAlign = GuiGetStyle(BUTTON, TEXT_ALIGNMENT); - GuiSetStyle(BUTTON, BORDER_WIDTH, GuiGetStyle(SPINNER, BORDER_WIDTH)); - GuiSetStyle(BUTTON, TEXT_ALIGNMENT, TEXT_ALIGN_CENTER); - - GuiSetStyle(BUTTON, TEXT_ALIGNMENT, tempTextAlign); - GuiSetStyle(BUTTON, BORDER_WIDTH, tempBorderWidth); - - // Draw text label if provided - GuiDrawText(text, textBounds, (GuiGetStyle(SPINNER, TEXT_ALIGNMENT) == TEXT_ALIGN_RIGHT)? TEXT_ALIGN_LEFT : TEXT_ALIGN_RIGHT, GetColor(GuiGetStyle(LABEL, TEXT + (state*3)))); - //-------------------------------------------------------------------- - - *value = tempValue; - return result; -} - -// Value Box control, updates input text with numbers -// NOTE: Requires static variables: frameCounter -int GuiValueBox(Rectangle bounds, const char *text, int *value, int minValue, int maxValue, bool editMode) -{ - #if !defined(RAYGUI_VALUEBOX_MAX_CHARS) - #define RAYGUI_VALUEBOX_MAX_CHARS 32 - #endif - - int result = 0; - GuiState state = guiState; - - char textValue[RAYGUI_VALUEBOX_MAX_CHARS + 1] = "\0"; - sprintf(textValue, "%i", *value); - - Rectangle textBounds = { 0 }; - if (text != NULL) - { - textBounds.width = (float)GetTextWidth(text) + 2; - textBounds.height = (float)GuiGetStyle(DEFAULT, TEXT_SIZE); - textBounds.x = bounds.x + bounds.width + GuiGetStyle(VALUEBOX, TEXT_PADDING); - textBounds.y = bounds.y + bounds.height/2 - GuiGetStyle(DEFAULT, TEXT_SIZE)/2; - if (GuiGetStyle(VALUEBOX, TEXT_ALIGNMENT) == TEXT_ALIGN_LEFT) textBounds.x = bounds.x - textBounds.width - GuiGetStyle(VALUEBOX, TEXT_PADDING); - } - - // Update control - //-------------------------------------------------------------------- - if ((state != STATE_DISABLED) && !guiLocked && !guiControlExclusiveMode) - { - Vector2 mousePoint = GetMousePosition(); - - bool valueHasChanged = false; - - if (editMode) - { - state = STATE_PRESSED; - - int keyCount = (int)strlen(textValue); - - // Only allow keys in range [48..57] - if (keyCount < RAYGUI_VALUEBOX_MAX_CHARS) - { - if (GetTextWidth(textValue) < bounds.width) - { - int key = GetCharPressed(); - if ((key >= 48) && (key <= 57)) - { - textValue[keyCount] = (char)key; - keyCount++; - valueHasChanged = true; - } - } - } - - // Delete text - if (keyCount > 0) - { - if (IsKeyPressed(KEY_BACKSPACE)) - { - keyCount--; - textValue[keyCount] = '\0'; - valueHasChanged = true; - } - } - - if (valueHasChanged) *value = TextToInteger(textValue); - - // NOTE: We are not clamp values until user input finishes - //if (*value > maxValue) *value = maxValue; - //else if (*value < minValue) *value = minValue; - - if ((IsKeyPressed(KEY_ENTER) || IsKeyPressed(KEY_KP_ENTER)) || (!CheckCollisionPointRec(mousePoint, bounds) && IsMouseButtonPressed(MOUSE_LEFT_BUTTON))) - { - if (*value > maxValue) *value = maxValue; - else if (*value < minValue) *value = minValue; - - result = 1; - } - } - else - { - if (*value > maxValue) *value = maxValue; - else if (*value < minValue) *value = minValue; - - if (CheckCollisionPointRec(mousePoint, bounds)) - { - state = STATE_FOCUSED; - if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) result = 1; - } - } - } - //-------------------------------------------------------------------- - - // Draw control - //-------------------------------------------------------------------- - Color baseColor = BLANK; - if (state == STATE_PRESSED) baseColor = GetColor(GuiGetStyle(VALUEBOX, BASE_COLOR_PRESSED)); - else if (state == STATE_DISABLED) baseColor = GetColor(GuiGetStyle(VALUEBOX, BASE_COLOR_DISABLED)); - - GuiDrawRectangle(bounds, GuiGetStyle(VALUEBOX, BORDER_WIDTH), GetColor(GuiGetStyle(VALUEBOX, BORDER + (state*3))), baseColor); - GuiDrawText(textValue, GetTextBounds(VALUEBOX, bounds), TEXT_ALIGN_CENTER, GetColor(GuiGetStyle(VALUEBOX, TEXT + (state*3)))); - - // Draw cursor - if (editMode) - { - // NOTE: ValueBox internal text is always centered - Rectangle cursor = { bounds.x + GetTextWidth(textValue)/2 + bounds.width/2 + 1, bounds.y + 2*GuiGetStyle(VALUEBOX, BORDER_WIDTH), 4, bounds.height - 4*GuiGetStyle(VALUEBOX, BORDER_WIDTH) }; - GuiDrawRectangle(cursor, 0, BLANK, GetColor(GuiGetStyle(VALUEBOX, BORDER_COLOR_PRESSED))); - } - - // Draw text label if provided - GuiDrawText(text, textBounds, (GuiGetStyle(VALUEBOX, TEXT_ALIGNMENT) == TEXT_ALIGN_RIGHT)? TEXT_ALIGN_LEFT : TEXT_ALIGN_RIGHT, GetColor(GuiGetStyle(LABEL, TEXT + (state*3)))); - //-------------------------------------------------------------------- - - return result; -} - -// Floating point Value Box control, updates input val_str with numbers -// NOTE: Requires static variables: frameCounter -int GuiValueBoxFloat(Rectangle bounds, const char *text, char *textValue, float *value, bool editMode) -{ - #if !defined(RAYGUI_VALUEBOX_MAX_CHARS) - #define RAYGUI_VALUEBOX_MAX_CHARS 32 - #endif - - int result = 0; - GuiState state = guiState; - - //char textValue[RAYGUI_VALUEBOX_MAX_CHARS + 1] = "\0"; - //sprintf(textValue, "%2.2f", *value); - - Rectangle textBounds = {0}; - if (text != NULL) - { - textBounds.width = (float)GetTextWidth(text) + 2; - textBounds.height = (float)GuiGetStyle(DEFAULT, TEXT_SIZE); - textBounds.x = bounds.x + bounds.width + GuiGetStyle(VALUEBOX, TEXT_PADDING); - textBounds.y = bounds.y + bounds.height/2 - GuiGetStyle(DEFAULT, TEXT_SIZE)/2; - if (GuiGetStyle(VALUEBOX, TEXT_ALIGNMENT) == TEXT_ALIGN_LEFT) textBounds.x = bounds.x - textBounds.width - GuiGetStyle(VALUEBOX, TEXT_PADDING); - } - - // Update control - //-------------------------------------------------------------------- - if ((state != STATE_DISABLED) && !guiLocked && !guiControlExclusiveMode) - { - Vector2 mousePoint = GetMousePosition(); - - bool valueHasChanged = false; - - if (editMode) - { - state = STATE_PRESSED; - - int keyCount = (int)strlen(textValue); - - // Only allow keys in range [48..57] - if (keyCount < RAYGUI_VALUEBOX_MAX_CHARS) - { - if (GetTextWidth(textValue) < bounds.width) - { - int key = GetCharPressed(); - if (((key >= 48) && (key <= 57)) || - (key == '.') || - ((keyCount == 0) && (key == '+')) || // NOTE: Sign can only be in first position - ((keyCount == 0) && (key == '-'))) - { - textValue[keyCount] = (char)key; - keyCount++; - - valueHasChanged = true; - } - } - } - - // Pressed backspace - if (IsKeyPressed(KEY_BACKSPACE)) - { - if (keyCount > 0) - { - keyCount--; - textValue[keyCount] = '\0'; - valueHasChanged = true; - } - } - - if (valueHasChanged) *value = TextToFloat(textValue); - - if ((IsKeyPressed(KEY_ENTER) || IsKeyPressed(KEY_KP_ENTER)) || (!CheckCollisionPointRec(mousePoint, bounds) && IsMouseButtonPressed(MOUSE_LEFT_BUTTON))) result = 1; - } - else - { - if (CheckCollisionPointRec(mousePoint, bounds)) - { - state = STATE_FOCUSED; - if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) result = 1; - } - } - } - //-------------------------------------------------------------------- - - // Draw control - //-------------------------------------------------------------------- - Color baseColor = BLANK; - if (state == STATE_PRESSED) baseColor = GetColor(GuiGetStyle(VALUEBOX, BASE_COLOR_PRESSED)); - else if (state == STATE_DISABLED) baseColor = GetColor(GuiGetStyle(VALUEBOX, BASE_COLOR_DISABLED)); - - GuiDrawRectangle(bounds, GuiGetStyle(VALUEBOX, BORDER_WIDTH), GetColor(GuiGetStyle(VALUEBOX, BORDER + (state*3))), baseColor); - GuiDrawText(textValue, GetTextBounds(VALUEBOX, bounds), TEXT_ALIGN_CENTER, GetColor(GuiGetStyle(VALUEBOX, TEXT + (state*3)))); - - // Draw cursor - if (editMode) - { - // NOTE: ValueBox internal text is always centered - Rectangle cursor = {bounds.x + GetTextWidth(textValue)/2 + bounds.width/2 + 1, - bounds.y + 2*GuiGetStyle(VALUEBOX, BORDER_WIDTH), 4, - bounds.height - 4*GuiGetStyle(VALUEBOX, BORDER_WIDTH)}; - GuiDrawRectangle(cursor, 0, BLANK, GetColor(GuiGetStyle(VALUEBOX, BORDER_COLOR_PRESSED))); - } - - // Draw text label if provided - GuiDrawText(text, textBounds, - (GuiGetStyle(VALUEBOX, TEXT_ALIGNMENT) == TEXT_ALIGN_RIGHT)? TEXT_ALIGN_LEFT : TEXT_ALIGN_RIGHT, - GetColor(GuiGetStyle(LABEL, TEXT + (state*3)))); - //-------------------------------------------------------------------- - - return result; -} - -// Slider control with pro parameters -// NOTE: Other GuiSlider*() controls use this one -int GuiSliderPro(Rectangle bounds, const char *textLeft, const char *textRight, float *value, float minValue, float maxValue, int sliderWidth) -{ - int result = 0; - GuiState state = guiState; - - float temp = (maxValue - minValue)/2.0f; - if (value == NULL) value = &temp; - float oldValue = *value; - - Rectangle slider = { bounds.x, bounds.y + GuiGetStyle(SLIDER, BORDER_WIDTH) + GuiGetStyle(SLIDER, SLIDER_PADDING), - 0, bounds.height - 2*GuiGetStyle(SLIDER, BORDER_WIDTH) - 2*GuiGetStyle(SLIDER, SLIDER_PADDING) }; - - // Update control - //-------------------------------------------------------------------- - if ((state != STATE_DISABLED) && !guiLocked) - { - Vector2 mousePoint = GetMousePosition(); - - if (guiControlExclusiveMode) // Allows to keep dragging outside of bounds - { - if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) - { - if (CHECK_BOUNDS_ID(bounds, guiControlExclusiveRec)) - { - state = STATE_PRESSED; - // Get equivalent value and slider position from mousePosition.x - *value = (maxValue - minValue)*((mousePoint.x - bounds.x - sliderWidth/2)/(bounds.width-sliderWidth)) + minValue; - } - } - else - { - guiControlExclusiveMode = false; - guiControlExclusiveRec = RAYGUI_CLITERAL(Rectangle){ 0, 0, 0, 0 }; - } - } - else if (CheckCollisionPointRec(mousePoint, bounds)) - { - if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) - { - state = STATE_PRESSED; - guiControlExclusiveMode = true; - guiControlExclusiveRec = bounds; // Store bounds as an identifier when dragging starts - - if (!CheckCollisionPointRec(mousePoint, slider)) - { - // Get equivalent value and slider position from mousePosition.x - *value = (maxValue - minValue)*((mousePoint.x - bounds.x - sliderWidth/2)/(bounds.width-sliderWidth)) + minValue; - } - } - else state = STATE_FOCUSED; - } - - if (*value > maxValue) *value = maxValue; - else if (*value < minValue) *value = minValue; - } - - // Control value change check - if (oldValue == *value) result = 0; - else result = 1; - - // Slider bar limits check - float sliderValue = (((*value - minValue)/(maxValue - minValue))*(bounds.width - sliderWidth - 2*GuiGetStyle(SLIDER, BORDER_WIDTH))); - if (sliderWidth > 0) // Slider - { - slider.x += sliderValue; - slider.width = (float)sliderWidth; - if (slider.x <= (bounds.x + GuiGetStyle(SLIDER, BORDER_WIDTH))) slider.x = bounds.x + GuiGetStyle(SLIDER, BORDER_WIDTH); - else if ((slider.x + slider.width) >= (bounds.x + bounds.width)) slider.x = bounds.x + bounds.width - slider.width - GuiGetStyle(SLIDER, BORDER_WIDTH); - } - else if (sliderWidth == 0) // SliderBar - { - slider.x += GuiGetStyle(SLIDER, BORDER_WIDTH); - slider.width = sliderValue; - if (slider.width > bounds.width) slider.width = bounds.width - 2*GuiGetStyle(SLIDER, BORDER_WIDTH); - } - //-------------------------------------------------------------------- - - // Draw control - //-------------------------------------------------------------------- - GuiDrawRectangle(bounds, GuiGetStyle(SLIDER, BORDER_WIDTH), GetColor(GuiGetStyle(SLIDER, BORDER + (state*3))), GetColor(GuiGetStyle(SLIDER, (state != STATE_DISABLED)? BASE_COLOR_NORMAL : BASE_COLOR_DISABLED))); - - // Draw slider internal bar (depends on state) - if (state == STATE_NORMAL) GuiDrawRectangle(slider, 0, BLANK, GetColor(GuiGetStyle(SLIDER, BASE_COLOR_PRESSED))); - else if (state == STATE_FOCUSED) GuiDrawRectangle(slider, 0, BLANK, GetColor(GuiGetStyle(SLIDER, TEXT_COLOR_FOCUSED))); - else if (state == STATE_PRESSED) GuiDrawRectangle(slider, 0, BLANK, GetColor(GuiGetStyle(SLIDER, TEXT_COLOR_PRESSED))); - - // Draw left/right text if provided - if (textLeft != NULL) - { - Rectangle textBounds = { 0 }; - textBounds.width = (float)GetTextWidth(textLeft); - textBounds.height = (float)GuiGetStyle(DEFAULT, TEXT_SIZE); - textBounds.x = bounds.x - textBounds.width - GuiGetStyle(SLIDER, TEXT_PADDING); - textBounds.y = bounds.y + bounds.height/2 - GuiGetStyle(DEFAULT, TEXT_SIZE)/2; - - GuiDrawText(textLeft, textBounds, TEXT_ALIGN_RIGHT, GetColor(GuiGetStyle(SLIDER, TEXT + (state*3)))); - } - - if (textRight != NULL) - { - Rectangle textBounds = { 0 }; - textBounds.width = (float)GetTextWidth(textRight); - textBounds.height = (float)GuiGetStyle(DEFAULT, TEXT_SIZE); - textBounds.x = bounds.x + bounds.width + GuiGetStyle(SLIDER, TEXT_PADDING); - textBounds.y = bounds.y + bounds.height/2 - GuiGetStyle(DEFAULT, TEXT_SIZE)/2; - - GuiDrawText(textRight, textBounds, TEXT_ALIGN_LEFT, GetColor(GuiGetStyle(SLIDER, TEXT + (state*3)))); - } - //-------------------------------------------------------------------- - - return result; -} - -// Slider control extended, returns selected value and has text -int GuiSlider(Rectangle bounds, const char *textLeft, const char *textRight, float *value, float minValue, float maxValue) -{ - return GuiSliderPro(bounds, textLeft, textRight, value, minValue, maxValue, GuiGetStyle(SLIDER, SLIDER_WIDTH)); -} - -// Slider Bar control extended, returns selected value -int GuiSliderBar(Rectangle bounds, const char *textLeft, const char *textRight, float *value, float minValue, float maxValue) -{ - return GuiSliderPro(bounds, textLeft, textRight, value, minValue, maxValue, 0); -} - -// Progress Bar control extended, shows current progress value -int GuiProgressBar(Rectangle bounds, const char *textLeft, const char *textRight, float *value, float minValue, float maxValue) -{ - int result = 0; - GuiState state = guiState; - - float temp = (maxValue - minValue)/2.0f; - if (value == NULL) value = &temp; - - // Progress bar - Rectangle progress = { bounds.x + GuiGetStyle(PROGRESSBAR, BORDER_WIDTH), - bounds.y + GuiGetStyle(PROGRESSBAR, BORDER_WIDTH) + GuiGetStyle(PROGRESSBAR, PROGRESS_PADDING), 0, - bounds.height - 2*GuiGetStyle(PROGRESSBAR, BORDER_WIDTH) - 2*GuiGetStyle(PROGRESSBAR, PROGRESS_PADDING) }; - - // Update control - //-------------------------------------------------------------------- - if (*value > maxValue) *value = maxValue; - - // WARNING: Working with floats could lead to rounding issues - if ((state != STATE_DISABLED)) progress.width = (float)(*value/(maxValue - minValue))*bounds.width - ((*value >= maxValue)? (float)(2*GuiGetStyle(PROGRESSBAR, BORDER_WIDTH)) : 0.0f); - //-------------------------------------------------------------------- - - // Draw control - //-------------------------------------------------------------------- - if (state == STATE_DISABLED) - { - GuiDrawRectangle(bounds, GuiGetStyle(PROGRESSBAR, BORDER_WIDTH), GetColor(GuiGetStyle(PROGRESSBAR, BORDER + (state*3))), BLANK); - } - else - { - if (*value > minValue) - { - // Draw progress bar with colored border, more visual - GuiDrawRectangle(RAYGUI_CLITERAL(Rectangle){ bounds.x, bounds.y, (int)progress.width + (float)GuiGetStyle(PROGRESSBAR, BORDER_WIDTH), (float)GuiGetStyle(PROGRESSBAR, BORDER_WIDTH) }, 0, BLANK, GetColor(GuiGetStyle(PROGRESSBAR, BORDER_COLOR_FOCUSED))); - GuiDrawRectangle(RAYGUI_CLITERAL(Rectangle){ bounds.x, bounds.y + 1, (float)GuiGetStyle(PROGRESSBAR, BORDER_WIDTH), bounds.height - 2 }, 0, BLANK, GetColor(GuiGetStyle(PROGRESSBAR, BORDER_COLOR_FOCUSED))); - GuiDrawRectangle(RAYGUI_CLITERAL(Rectangle){ bounds.x, bounds.y + bounds.height - 1, (int)progress.width + (float)GuiGetStyle(PROGRESSBAR, BORDER_WIDTH), (float)GuiGetStyle(PROGRESSBAR, BORDER_WIDTH) }, 0, BLANK, GetColor(GuiGetStyle(PROGRESSBAR, BORDER_COLOR_FOCUSED))); - } - else GuiDrawRectangle(RAYGUI_CLITERAL(Rectangle){ bounds.x, bounds.y, (float)GuiGetStyle(PROGRESSBAR, BORDER_WIDTH), bounds.height }, 0, BLANK, GetColor(GuiGetStyle(PROGRESSBAR, BORDER_COLOR_NORMAL))); - - if (*value >= maxValue) GuiDrawRectangle(RAYGUI_CLITERAL(Rectangle){ bounds.x + progress.width + 1, bounds.y, (float)GuiGetStyle(PROGRESSBAR, BORDER_WIDTH), bounds.height }, 0, BLANK, GetColor(GuiGetStyle(PROGRESSBAR, BORDER_COLOR_FOCUSED))); - else - { - // Draw borders not yet reached by value - GuiDrawRectangle(RAYGUI_CLITERAL(Rectangle){ bounds.x + (int)progress.width + 1, bounds.y, bounds.width - (int)progress.width - 1, (float)GuiGetStyle(PROGRESSBAR, BORDER_WIDTH) }, 0, BLANK, GetColor(GuiGetStyle(PROGRESSBAR, BORDER_COLOR_NORMAL))); - GuiDrawRectangle(RAYGUI_CLITERAL(Rectangle){ bounds.x + (int)progress.width + 1, bounds.y + bounds.height - 1, bounds.width - (int)progress.width - 1, (float)GuiGetStyle(PROGRESSBAR, BORDER_WIDTH) }, 0, BLANK, GetColor(GuiGetStyle(PROGRESSBAR, BORDER_COLOR_NORMAL))); - GuiDrawRectangle(RAYGUI_CLITERAL(Rectangle){ bounds.x + bounds.width - 1, bounds.y + 1, (float)GuiGetStyle(PROGRESSBAR, BORDER_WIDTH), bounds.height - 2 }, 0, BLANK, GetColor(GuiGetStyle(PROGRESSBAR, BORDER_COLOR_NORMAL))); - } - - // Draw slider internal progress bar (depends on state) - GuiDrawRectangle(progress, 0, BLANK, GetColor(GuiGetStyle(PROGRESSBAR, BASE_COLOR_PRESSED))); - } - - // Draw left/right text if provided - if (textLeft != NULL) - { - Rectangle textBounds = { 0 }; - textBounds.width = (float)GetTextWidth(textLeft); - textBounds.height = (float)GuiGetStyle(DEFAULT, TEXT_SIZE); - textBounds.x = bounds.x - textBounds.width - GuiGetStyle(PROGRESSBAR, TEXT_PADDING); - textBounds.y = bounds.y + bounds.height/2 - GuiGetStyle(DEFAULT, TEXT_SIZE)/2; - - GuiDrawText(textLeft, textBounds, TEXT_ALIGN_RIGHT, GetColor(GuiGetStyle(PROGRESSBAR, TEXT + (state*3)))); - } - - if (textRight != NULL) - { - Rectangle textBounds = { 0 }; - textBounds.width = (float)GetTextWidth(textRight); - textBounds.height = (float)GuiGetStyle(DEFAULT, TEXT_SIZE); - textBounds.x = bounds.x + bounds.width + GuiGetStyle(PROGRESSBAR, TEXT_PADDING); - textBounds.y = bounds.y + bounds.height/2 - GuiGetStyle(DEFAULT, TEXT_SIZE)/2; - - GuiDrawText(textRight, textBounds, TEXT_ALIGN_LEFT, GetColor(GuiGetStyle(PROGRESSBAR, TEXT + (state*3)))); - } - //-------------------------------------------------------------------- - - return result; -} - -// Status Bar control -int GuiStatusBar(Rectangle bounds, const char *text) -{ - int result = 0; - GuiState state = guiState; - - // Draw control - //-------------------------------------------------------------------- - GuiDrawRectangle(bounds, GuiGetStyle(STATUSBAR, BORDER_WIDTH), GetColor(GuiGetStyle(STATUSBAR, BORDER + (state*3))), GetColor(GuiGetStyle(STATUSBAR, BASE + (state*3)))); - GuiDrawText(text, GetTextBounds(STATUSBAR, bounds), GuiGetStyle(STATUSBAR, TEXT_ALIGNMENT), GetColor(GuiGetStyle(STATUSBAR, TEXT + (state*3)))); - //-------------------------------------------------------------------- - - return result; -} - -// Dummy rectangle control, intended for placeholding -int GuiDummyRec(Rectangle bounds, const char *text) -{ - int result = 0; - GuiState state = guiState; - - // Update control - //-------------------------------------------------------------------- - if ((state != STATE_DISABLED) && !guiLocked && !guiControlExclusiveMode) - { - Vector2 mousePoint = GetMousePosition(); - - // Check button state - if (CheckCollisionPointRec(mousePoint, bounds)) - { - if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) state = STATE_PRESSED; - else state = STATE_FOCUSED; - } - } - //-------------------------------------------------------------------- - - // Draw control - //-------------------------------------------------------------------- - GuiDrawRectangle(bounds, 0, BLANK, GetColor(GuiGetStyle(DEFAULT, (state != STATE_DISABLED)? BASE_COLOR_NORMAL : BASE_COLOR_DISABLED))); - GuiDrawText(text, GetTextBounds(DEFAULT, bounds), TEXT_ALIGN_CENTER, GetColor(GuiGetStyle(BUTTON, (state != STATE_DISABLED)? TEXT_COLOR_NORMAL : TEXT_COLOR_DISABLED))); - //------------------------------------------------------------------ - - return result; -} - -// List View control -int GuiListView(Rectangle bounds, const char *text, int *scrollIndex, int *active) -{ - int result = 0; - int itemCount = 0; - const char **items = NULL; - - if (text != NULL) items = GuiTextSplit(text, ';', &itemCount, NULL); - - result = GuiListViewEx(bounds, items, itemCount, scrollIndex, active, NULL); - - return result; -} - -// List View control with extended parameters -int GuiListViewEx(Rectangle bounds, const char **text, int count, int *scrollIndex, int *active, int *focus) -{ - int result = 0; - GuiState state = guiState; - - int itemFocused = (focus == NULL)? -1 : *focus; - int itemSelected = (active == NULL)? -1 : *active; - - // Check if we need a scroll bar - bool useScrollBar = false; - if ((GuiGetStyle(LISTVIEW, LIST_ITEMS_HEIGHT) + GuiGetStyle(LISTVIEW, LIST_ITEMS_SPACING))*count > bounds.height) useScrollBar = true; - - // Define base item rectangle [0] - Rectangle itemBounds = { 0 }; - itemBounds.x = bounds.x + GuiGetStyle(LISTVIEW, LIST_ITEMS_SPACING); - itemBounds.y = bounds.y + GuiGetStyle(LISTVIEW, LIST_ITEMS_SPACING) + GuiGetStyle(DEFAULT, BORDER_WIDTH); - itemBounds.width = bounds.width - 2*GuiGetStyle(LISTVIEW, LIST_ITEMS_SPACING) - GuiGetStyle(DEFAULT, BORDER_WIDTH); - itemBounds.height = (float)GuiGetStyle(LISTVIEW, LIST_ITEMS_HEIGHT); - if (useScrollBar) itemBounds.width -= GuiGetStyle(LISTVIEW, SCROLLBAR_WIDTH); - - // Get items on the list - int visibleItems = (int)bounds.height/(GuiGetStyle(LISTVIEW, LIST_ITEMS_HEIGHT) + GuiGetStyle(LISTVIEW, LIST_ITEMS_SPACING)); - if (visibleItems > count) visibleItems = count; - - int startIndex = (scrollIndex == NULL)? 0 : *scrollIndex; - if ((startIndex < 0) || (startIndex > (count - visibleItems))) startIndex = 0; - int endIndex = startIndex + visibleItems; - - // Update control - //-------------------------------------------------------------------- - if ((state != STATE_DISABLED) && !guiLocked && !guiControlExclusiveMode) - { - Vector2 mousePoint = GetMousePosition(); - - // Check mouse inside list view - if (CheckCollisionPointRec(mousePoint, bounds)) - { - state = STATE_FOCUSED; - - // Check focused and selected item - for (int i = 0; i < visibleItems; i++) - { - if (CheckCollisionPointRec(mousePoint, itemBounds)) - { - itemFocused = startIndex + i; - if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) - { - if (itemSelected == (startIndex + i)) itemSelected = -1; - else itemSelected = startIndex + i; - } - break; - } - - // Update item rectangle y position for next item - itemBounds.y += (GuiGetStyle(LISTVIEW, LIST_ITEMS_HEIGHT) + GuiGetStyle(LISTVIEW, LIST_ITEMS_SPACING)); - } - - if (useScrollBar) - { - int wheelMove = (int)GetMouseWheelMove(); - startIndex -= wheelMove; - - if (startIndex < 0) startIndex = 0; - else if (startIndex > (count - visibleItems)) startIndex = count - visibleItems; - - endIndex = startIndex + visibleItems; - if (endIndex > count) endIndex = count; - } - } - else itemFocused = -1; - - // Reset item rectangle y to [0] - itemBounds.y = bounds.y + GuiGetStyle(LISTVIEW, LIST_ITEMS_SPACING) + GuiGetStyle(DEFAULT, BORDER_WIDTH); - } - //-------------------------------------------------------------------- - - // Draw control - //-------------------------------------------------------------------- - GuiDrawRectangle(bounds, GuiGetStyle(LISTVIEW, BORDER_WIDTH), GetColor(GuiGetStyle(LISTVIEW, BORDER + state*3)), GetColor(GuiGetStyle(DEFAULT, BACKGROUND_COLOR))); // Draw background - - // Draw visible items - for (int i = 0; ((i < visibleItems) && (text != NULL)); i++) - { - GuiDrawRectangle(itemBounds, GuiGetStyle(LISTVIEW, LIST_ITEMS_BORDER_WIDTH), GetColor(GuiGetStyle(LISTVIEW, BORDER_COLOR_NORMAL)), BLANK); - - if (state == STATE_DISABLED) - { - if ((startIndex + i) == itemSelected) GuiDrawRectangle(itemBounds, GuiGetStyle(LISTVIEW, BORDER_WIDTH), GetColor(GuiGetStyle(LISTVIEW, BORDER_COLOR_DISABLED)), GetColor(GuiGetStyle(LISTVIEW, BASE_COLOR_DISABLED))); - - GuiDrawText(text[startIndex + i], GetTextBounds(DEFAULT, itemBounds), GuiGetStyle(LISTVIEW, TEXT_ALIGNMENT), GetColor(GuiGetStyle(LISTVIEW, TEXT_COLOR_DISABLED))); - } - else - { - if (((startIndex + i) == itemSelected) && (active != NULL)) - { - // Draw item selected - GuiDrawRectangle(itemBounds, GuiGetStyle(LISTVIEW, BORDER_WIDTH), GetColor(GuiGetStyle(LISTVIEW, BORDER_COLOR_PRESSED)), GetColor(GuiGetStyle(LISTVIEW, BASE_COLOR_PRESSED))); - GuiDrawText(text[startIndex + i], GetTextBounds(DEFAULT, itemBounds), GuiGetStyle(LISTVIEW, TEXT_ALIGNMENT), GetColor(GuiGetStyle(LISTVIEW, TEXT_COLOR_PRESSED))); - } - else if (((startIndex + i) == itemFocused)) // && (focus != NULL)) // NOTE: We want items focused, despite not returned! - { - // Draw item focused - GuiDrawRectangle(itemBounds, GuiGetStyle(LISTVIEW, BORDER_WIDTH), GetColor(GuiGetStyle(LISTVIEW, BORDER_COLOR_FOCUSED)), GetColor(GuiGetStyle(LISTVIEW, BASE_COLOR_FOCUSED))); - GuiDrawText(text[startIndex + i], GetTextBounds(DEFAULT, itemBounds), GuiGetStyle(LISTVIEW, TEXT_ALIGNMENT), GetColor(GuiGetStyle(LISTVIEW, TEXT_COLOR_FOCUSED))); - } - else - { - // Draw item normal - GuiDrawText(text[startIndex + i], GetTextBounds(DEFAULT, itemBounds), GuiGetStyle(LISTVIEW, TEXT_ALIGNMENT), GetColor(GuiGetStyle(LISTVIEW, TEXT_COLOR_NORMAL))); - } - } - - // Update item rectangle y position for next item - itemBounds.y += (GuiGetStyle(LISTVIEW, LIST_ITEMS_HEIGHT) + GuiGetStyle(LISTVIEW, LIST_ITEMS_SPACING)); - } - - if (useScrollBar) - { - Rectangle scrollBarBounds = { - bounds.x + bounds.width - GuiGetStyle(LISTVIEW, BORDER_WIDTH) - GuiGetStyle(LISTVIEW, SCROLLBAR_WIDTH), - bounds.y + GuiGetStyle(LISTVIEW, BORDER_WIDTH), (float)GuiGetStyle(LISTVIEW, SCROLLBAR_WIDTH), - bounds.height - 2*GuiGetStyle(DEFAULT, BORDER_WIDTH) - }; - - // Calculate percentage of visible items and apply same percentage to scrollbar - float percentVisible = (float)(endIndex - startIndex)/count; - float sliderSize = bounds.height*percentVisible; - - int prevSliderSize = GuiGetStyle(SCROLLBAR, SCROLL_SLIDER_SIZE); // Save default slider size - int prevScrollSpeed = GuiGetStyle(SCROLLBAR, SCROLL_SPEED); // Save default scroll speed - GuiSetStyle(SCROLLBAR, SCROLL_SLIDER_SIZE, (int)sliderSize); // Change slider size - GuiSetStyle(SCROLLBAR, SCROLL_SPEED, count - visibleItems); // Change scroll speed - - startIndex = GuiScrollBar(scrollBarBounds, startIndex, 0, count - visibleItems); - - GuiSetStyle(SCROLLBAR, SCROLL_SPEED, prevScrollSpeed); // Reset scroll speed to default - GuiSetStyle(SCROLLBAR, SCROLL_SLIDER_SIZE, prevSliderSize); // Reset slider size to default - } - //-------------------------------------------------------------------- - - if (active != NULL) *active = itemSelected; - if (focus != NULL) *focus = itemFocused; - if (scrollIndex != NULL) *scrollIndex = startIndex; - - return result; -} - -// Color Panel control - Color (RGBA) variant. -int GuiColorPanel(Rectangle bounds, const char *text, Color *color) -{ - int result = 0; - - Vector3 vcolor = { (float)color->r/255.0f, (float)color->g/255.0f, (float)color->b/255.0f }; - Vector3 hsv = ConvertRGBtoHSV(vcolor); - Vector3 prevHsv = hsv; // workaround to see if GuiColorPanelHSV modifies the hsv. - - GuiColorPanelHSV(bounds, text, &hsv); - - // Check if the hsv was changed, only then change the color. - // This is required, because the Color->HSV->Color conversion has precision errors. - // Thus the assignment from HSV to Color should only be made, if the HSV has a new user-entered value. - // Otherwise GuiColorPanel would often modify it's color without user input. - // TODO: GuiColorPanelHSV could return 1 if the slider was dragged, to simplify this check. - if (hsv.x != prevHsv.x || hsv.y != prevHsv.y || hsv.z != prevHsv.z) - { - Vector3 rgb = ConvertHSVtoRGB(hsv); - - // NOTE: Vector3ToColor() only available on raylib 1.8.1 - *color = RAYGUI_CLITERAL(Color){ (unsigned char)(255.0f*rgb.x), - (unsigned char)(255.0f*rgb.y), - (unsigned char)(255.0f*rgb.z), - color->a }; - } - return result; -} - -// Color Bar Alpha control -// NOTE: Returns alpha value normalized [0..1] -int GuiColorBarAlpha(Rectangle bounds, const char *text, float *alpha) -{ - #if !defined(RAYGUI_COLORBARALPHA_CHECKED_SIZE) - #define RAYGUI_COLORBARALPHA_CHECKED_SIZE 10 - #endif - - int result = 0; - GuiState state = guiState; - Rectangle selector = { (float)bounds.x + (*alpha)*bounds.width - GuiGetStyle(COLORPICKER, HUEBAR_SELECTOR_HEIGHT)/2, (float)bounds.y - GuiGetStyle(COLORPICKER, HUEBAR_SELECTOR_OVERFLOW), (float)GuiGetStyle(COLORPICKER, HUEBAR_SELECTOR_HEIGHT), (float)bounds.height + GuiGetStyle(COLORPICKER, HUEBAR_SELECTOR_OVERFLOW)*2 }; - - // Update control - //-------------------------------------------------------------------- - if ((state != STATE_DISABLED) && !guiLocked) - { - Vector2 mousePoint = GetMousePosition(); - - if (guiControlExclusiveMode) // Allows to keep dragging outside of bounds - { - if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) - { - if (CHECK_BOUNDS_ID(bounds, guiControlExclusiveRec)) - { - state = STATE_PRESSED; - - *alpha = (mousePoint.x - bounds.x)/bounds.width; - if (*alpha <= 0.0f) *alpha = 0.0f; - if (*alpha >= 1.0f) *alpha = 1.0f; - } - } - else - { - guiControlExclusiveMode = false; - guiControlExclusiveRec = RAYGUI_CLITERAL(Rectangle){ 0, 0, 0, 0 }; - } - } - else if (CheckCollisionPointRec(mousePoint, bounds) || CheckCollisionPointRec(mousePoint, selector)) - { - if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) - { - state = STATE_PRESSED; - guiControlExclusiveMode = true; - guiControlExclusiveRec = bounds; // Store bounds as an identifier when dragging starts - - *alpha = (mousePoint.x - bounds.x)/bounds.width; - if (*alpha <= 0.0f) *alpha = 0.0f; - if (*alpha >= 1.0f) *alpha = 1.0f; - //selector.x = bounds.x + (int)(((alpha - 0)/(100 - 0))*(bounds.width - 2*GuiGetStyle(SLIDER, BORDER_WIDTH))) - selector.width/2; - } - else state = STATE_FOCUSED; - } - } - //-------------------------------------------------------------------- - - // Draw control - //-------------------------------------------------------------------- - - // Draw alpha bar: checked background - if (state != STATE_DISABLED) - { - int checksX = (int)bounds.width/RAYGUI_COLORBARALPHA_CHECKED_SIZE; - int checksY = (int)bounds.height/RAYGUI_COLORBARALPHA_CHECKED_SIZE; - - for (int x = 0; x < checksX; x++) - { - for (int y = 0; y < checksY; y++) - { - Rectangle check = { bounds.x + x*RAYGUI_COLORBARALPHA_CHECKED_SIZE, bounds.y + y*RAYGUI_COLORBARALPHA_CHECKED_SIZE, RAYGUI_COLORBARALPHA_CHECKED_SIZE, RAYGUI_COLORBARALPHA_CHECKED_SIZE }; - GuiDrawRectangle(check, 0, BLANK, ((x + y)%2)? Fade(GetColor(GuiGetStyle(COLORPICKER, BORDER_COLOR_DISABLED)), 0.4f) : Fade(GetColor(GuiGetStyle(COLORPICKER, BASE_COLOR_DISABLED)), 0.4f)); - } - } - - DrawRectangleGradientEx(bounds, RAYGUI_CLITERAL(Color){ 255, 255, 255, 0 }, RAYGUI_CLITERAL(Color){ 255, 255, 255, 0 }, Fade(RAYGUI_CLITERAL(Color){ 0, 0, 0, 255 }, guiAlpha), Fade(RAYGUI_CLITERAL(Color){ 0, 0, 0, 255 }, guiAlpha)); - } - else DrawRectangleGradientEx(bounds, Fade(GetColor(GuiGetStyle(COLORPICKER, BASE_COLOR_DISABLED)), 0.1f), Fade(GetColor(GuiGetStyle(COLORPICKER, BASE_COLOR_DISABLED)), 0.1f), Fade(GetColor(GuiGetStyle(COLORPICKER, BORDER_COLOR_DISABLED)), guiAlpha), Fade(GetColor(GuiGetStyle(COLORPICKER, BORDER_COLOR_DISABLED)), guiAlpha)); - - GuiDrawRectangle(bounds, GuiGetStyle(COLORPICKER, BORDER_WIDTH), GetColor(GuiGetStyle(COLORPICKER, BORDER + state*3)), BLANK); - - // Draw alpha bar: selector - GuiDrawRectangle(selector, 0, BLANK, GetColor(GuiGetStyle(COLORPICKER, BORDER + state*3))); - //-------------------------------------------------------------------- - - return result; -} - -// Color Bar Hue control -// Returns hue value normalized [0..1] -// NOTE: Other similar bars (for reference): -// Color GuiColorBarSat() [WHITE->color] -// Color GuiColorBarValue() [BLACK->color], HSV/HSL -// float GuiColorBarLuminance() [BLACK->WHITE] -int GuiColorBarHue(Rectangle bounds, const char *text, float *hue) -{ - int result = 0; - GuiState state = guiState; - Rectangle selector = { (float)bounds.x - GuiGetStyle(COLORPICKER, HUEBAR_SELECTOR_OVERFLOW), (float)bounds.y + (*hue)/360.0f*bounds.height - GuiGetStyle(COLORPICKER, HUEBAR_SELECTOR_HEIGHT)/2, (float)bounds.width + GuiGetStyle(COLORPICKER, HUEBAR_SELECTOR_OVERFLOW)*2, (float)GuiGetStyle(COLORPICKER, HUEBAR_SELECTOR_HEIGHT) }; - - // Update control - //-------------------------------------------------------------------- - if ((state != STATE_DISABLED) && !guiLocked) - { - Vector2 mousePoint = GetMousePosition(); - - if (guiControlExclusiveMode) // Allows to keep dragging outside of bounds - { - if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) - { - if (CHECK_BOUNDS_ID(bounds, guiControlExclusiveRec)) - { - state = STATE_PRESSED; - - *hue = (mousePoint.y - bounds.y)*360/bounds.height; - if (*hue <= 0.0f) *hue = 0.0f; - if (*hue >= 359.0f) *hue = 359.0f; - } - } - else - { - guiControlExclusiveMode = false; - guiControlExclusiveRec = RAYGUI_CLITERAL(Rectangle){ 0, 0, 0, 0 }; - } - } - else if (CheckCollisionPointRec(mousePoint, bounds) || CheckCollisionPointRec(mousePoint, selector)) - { - if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) - { - state = STATE_PRESSED; - guiControlExclusiveMode = true; - guiControlExclusiveRec = bounds; // Store bounds as an identifier when dragging starts - - *hue = (mousePoint.y - bounds.y)*360/bounds.height; - if (*hue <= 0.0f) *hue = 0.0f; - if (*hue >= 359.0f) *hue = 359.0f; - - } - else state = STATE_FOCUSED; - - /*if (IsKeyDown(KEY_UP)) - { - hue -= 2.0f; - if (hue <= 0.0f) hue = 0.0f; - } - else if (IsKeyDown(KEY_DOWN)) - { - hue += 2.0f; - if (hue >= 360.0f) hue = 360.0f; - }*/ - } - } - //-------------------------------------------------------------------- - - // Draw control - //-------------------------------------------------------------------- - if (state != STATE_DISABLED) - { - // Draw hue bar:color bars - // TODO: Use directly DrawRectangleGradientEx(bounds, color1, color2, color2, color1); - DrawRectangleGradientV((int)bounds.x, (int)(bounds.y), (int)bounds.width, (int)ceilf(bounds.height/6), Fade(RAYGUI_CLITERAL(Color){ 255, 0, 0, 255 }, guiAlpha), Fade(RAYGUI_CLITERAL(Color){ 255, 255, 0, 255 }, guiAlpha)); - DrawRectangleGradientV((int)bounds.x, (int)(bounds.y + bounds.height/6), (int)bounds.width, (int)ceilf(bounds.height/6), Fade(RAYGUI_CLITERAL(Color){ 255, 255, 0, 255 }, guiAlpha), Fade(RAYGUI_CLITERAL(Color){ 0, 255, 0, 255 }, guiAlpha)); - DrawRectangleGradientV((int)bounds.x, (int)(bounds.y + 2*(bounds.height/6)), (int)bounds.width, (int)ceilf(bounds.height/6), Fade(RAYGUI_CLITERAL(Color){ 0, 255, 0, 255 }, guiAlpha), Fade(RAYGUI_CLITERAL(Color){ 0, 255, 255, 255 }, guiAlpha)); - DrawRectangleGradientV((int)bounds.x, (int)(bounds.y + 3*(bounds.height/6)), (int)bounds.width, (int)ceilf(bounds.height/6), Fade(RAYGUI_CLITERAL(Color){ 0, 255, 255, 255 }, guiAlpha), Fade(RAYGUI_CLITERAL(Color){ 0, 0, 255, 255 }, guiAlpha)); - DrawRectangleGradientV((int)bounds.x, (int)(bounds.y + 4*(bounds.height/6)), (int)bounds.width, (int)ceilf(bounds.height/6), Fade(RAYGUI_CLITERAL(Color){ 0, 0, 255, 255 }, guiAlpha), Fade(RAYGUI_CLITERAL(Color){ 255, 0, 255, 255 }, guiAlpha)); - DrawRectangleGradientV((int)bounds.x, (int)(bounds.y + 5*(bounds.height/6)), (int)bounds.width, (int)(bounds.height/6), Fade(RAYGUI_CLITERAL(Color){ 255, 0, 255, 255 }, guiAlpha), Fade(RAYGUI_CLITERAL(Color){ 255, 0, 0, 255 }, guiAlpha)); - } - else DrawRectangleGradientV((int)bounds.x, (int)bounds.y, (int)bounds.width, (int)bounds.height, Fade(Fade(GetColor(GuiGetStyle(COLORPICKER, BASE_COLOR_DISABLED)), 0.1f), guiAlpha), Fade(GetColor(GuiGetStyle(COLORPICKER, BORDER_COLOR_DISABLED)), guiAlpha)); - - GuiDrawRectangle(bounds, GuiGetStyle(COLORPICKER, BORDER_WIDTH), GetColor(GuiGetStyle(COLORPICKER, BORDER + state*3)), BLANK); - - // Draw hue bar: selector - GuiDrawRectangle(selector, 0, BLANK, GetColor(GuiGetStyle(COLORPICKER, BORDER + state*3))); - //-------------------------------------------------------------------- - - return result; -} - -// Color Picker control -// NOTE: It's divided in multiple controls: -// Color GuiColorPanel(Rectangle bounds, Color color) -// float GuiColorBarAlpha(Rectangle bounds, float alpha) -// float GuiColorBarHue(Rectangle bounds, float value) -// NOTE: bounds define GuiColorPanel() size -// NOTE: this picker converts RGB to HSV, which can cause the Hue control to jump. If you have this problem, consider using the HSV variant instead -int GuiColorPicker(Rectangle bounds, const char *text, Color *color) -{ - int result = 0; - - Color temp = { 200, 0, 0, 255 }; - if (color == NULL) color = &temp; - - GuiColorPanel(bounds, NULL, color); - - Rectangle boundsHue = { (float)bounds.x + bounds.width + GuiGetStyle(COLORPICKER, HUEBAR_PADDING), (float)bounds.y, (float)GuiGetStyle(COLORPICKER, HUEBAR_WIDTH), (float)bounds.height }; - //Rectangle boundsAlpha = { bounds.x, bounds.y + bounds.height + GuiGetStyle(COLORPICKER, BARS_PADDING), bounds.width, GuiGetStyle(COLORPICKER, BARS_THICK) }; - - // NOTE: this conversion can cause low hue-resolution, if the r, g and b value are very similar, which causes the hue bar to shift around when only the GuiColorPanel is used. - Vector3 hsv = ConvertRGBtoHSV(RAYGUI_CLITERAL(Vector3){ (*color).r/255.0f, (*color).g/255.0f, (*color).b/255.0f }); - - GuiColorBarHue(boundsHue, NULL, &hsv.x); - - //color.a = (unsigned char)(GuiColorBarAlpha(boundsAlpha, (float)color.a/255.0f)*255.0f); - Vector3 rgb = ConvertHSVtoRGB(hsv); - - *color = RAYGUI_CLITERAL(Color){ (unsigned char)roundf(rgb.x*255.0f), (unsigned char)roundf(rgb.y*255.0f), (unsigned char)roundf(rgb.z*255.0f), (*color).a }; - - return result; -} - -// Color Picker control that avoids conversion to RGB and back to HSV on each call, thus avoiding jittering. -// The user can call ConvertHSVtoRGB() to convert *colorHsv value to RGB. -// NOTE: It's divided in multiple controls: -// int GuiColorPanelHSV(Rectangle bounds, const char *text, Vector3 *colorHsv) -// int GuiColorBarAlpha(Rectangle bounds, const char *text, float *alpha) -// float GuiColorBarHue(Rectangle bounds, float value) -// NOTE: bounds define GuiColorPanelHSV() size -int GuiColorPickerHSV(Rectangle bounds, const char *text, Vector3 *colorHsv) -{ - int result = 0; - - Vector3 tempHsv = { 0 }; - - if (colorHsv == NULL) - { - const Vector3 tempColor = { 200.0f/255.0f, 0.0f, 0.0f }; - tempHsv = ConvertRGBtoHSV(tempColor); - colorHsv = &tempHsv; - } - - GuiColorPanelHSV(bounds, NULL, colorHsv); - - const Rectangle boundsHue = { (float)bounds.x + bounds.width + GuiGetStyle(COLORPICKER, HUEBAR_PADDING), (float)bounds.y, (float)GuiGetStyle(COLORPICKER, HUEBAR_WIDTH), (float)bounds.height }; - - GuiColorBarHue(boundsHue, NULL, &colorHsv->x); - - return result; -} - -// Color Panel control - HSV variant -int GuiColorPanelHSV(Rectangle bounds, const char *text, Vector3 *colorHsv) -{ - int result = 0; - GuiState state = guiState; - Vector2 pickerSelector = { 0 }; - - const Color colWhite = { 255, 255, 255, 255 }; - const Color colBlack = { 0, 0, 0, 255 }; - - pickerSelector.x = bounds.x + (float)colorHsv->y*bounds.width; // HSV: Saturation - pickerSelector.y = bounds.y + (1.0f - (float)colorHsv->z)*bounds.height; // HSV: Value - - Vector3 maxHue = { colorHsv->x, 1.0f, 1.0f }; - Vector3 rgbHue = ConvertHSVtoRGB(maxHue); - Color maxHueCol = { (unsigned char)(255.0f*rgbHue.x), - (unsigned char)(255.0f*rgbHue.y), - (unsigned char)(255.0f*rgbHue.z), 255 }; - - // Update control - //-------------------------------------------------------------------- - if ((state != STATE_DISABLED) && !guiLocked) - { - Vector2 mousePoint = GetMousePosition(); - - if (guiControlExclusiveMode) // Allows to keep dragging outside of bounds - { - if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) - { - if (CHECK_BOUNDS_ID(bounds, guiControlExclusiveRec)) - { - pickerSelector = mousePoint; - - if (pickerSelector.x < bounds.x) pickerSelector.x = bounds.x; - if (pickerSelector.x > bounds.x + bounds.width) pickerSelector.x = bounds.x + bounds.width; - if (pickerSelector.y < bounds.y) pickerSelector.y = bounds.y; - if (pickerSelector.y > bounds.y + bounds.height) pickerSelector.y = bounds.y + bounds.height; - - // Calculate color from picker - Vector2 colorPick = { pickerSelector.x - bounds.x, pickerSelector.y - bounds.y }; - - colorPick.x /= (float)bounds.width; // Get normalized value on x - colorPick.y /= (float)bounds.height; // Get normalized value on y - - colorHsv->y = colorPick.x; - colorHsv->z = 1.0f - colorPick.y; - - } - } - else - { - guiControlExclusiveMode = false; - guiControlExclusiveRec = RAYGUI_CLITERAL(Rectangle){ 0, 0, 0, 0 }; - } - } - else if (CheckCollisionPointRec(mousePoint, bounds)) - { - if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) - { - state = STATE_PRESSED; - guiControlExclusiveMode = true; - guiControlExclusiveRec = bounds; - pickerSelector = mousePoint; - - // Calculate color from picker - Vector2 colorPick = { pickerSelector.x - bounds.x, pickerSelector.y - bounds.y }; - - colorPick.x /= (float)bounds.width; // Get normalized value on x - colorPick.y /= (float)bounds.height; // Get normalized value on y - - colorHsv->y = colorPick.x; - colorHsv->z = 1.0f - colorPick.y; - } - else state = STATE_FOCUSED; - } - } - //-------------------------------------------------------------------- - - // Draw control - //-------------------------------------------------------------------- - if (state != STATE_DISABLED) - { - DrawRectangleGradientEx(bounds, Fade(colWhite, guiAlpha), Fade(colWhite, guiAlpha), Fade(maxHueCol, guiAlpha), Fade(maxHueCol, guiAlpha)); - DrawRectangleGradientEx(bounds, Fade(colBlack, 0), Fade(colBlack, guiAlpha), Fade(colBlack, guiAlpha), Fade(colBlack, 0)); - - // Draw color picker: selector - Rectangle selector = { pickerSelector.x - GuiGetStyle(COLORPICKER, COLOR_SELECTOR_SIZE)/2, pickerSelector.y - GuiGetStyle(COLORPICKER, COLOR_SELECTOR_SIZE)/2, (float)GuiGetStyle(COLORPICKER, COLOR_SELECTOR_SIZE), (float)GuiGetStyle(COLORPICKER, COLOR_SELECTOR_SIZE) }; - GuiDrawRectangle(selector, 0, BLANK, colWhite); - } - else - { - DrawRectangleGradientEx(bounds, Fade(Fade(GetColor(GuiGetStyle(COLORPICKER, BASE_COLOR_DISABLED)), 0.1f), guiAlpha), Fade(Fade(colBlack, 0.6f), guiAlpha), Fade(Fade(colBlack, 0.6f), guiAlpha), Fade(Fade(GetColor(GuiGetStyle(COLORPICKER, BORDER_COLOR_DISABLED)), 0.6f), guiAlpha)); - } - - GuiDrawRectangle(bounds, GuiGetStyle(COLORPICKER, BORDER_WIDTH), GetColor(GuiGetStyle(COLORPICKER, BORDER + state*3)), BLANK); - //-------------------------------------------------------------------- - - return result; -} - -// Message Box control -int GuiMessageBox(Rectangle bounds, const char *title, const char *message, const char *buttons) -{ - #if !defined(RAYGUI_MESSAGEBOX_BUTTON_HEIGHT) - #define RAYGUI_MESSAGEBOX_BUTTON_HEIGHT 24 - #endif - #if !defined(RAYGUI_MESSAGEBOX_BUTTON_PADDING) - #define RAYGUI_MESSAGEBOX_BUTTON_PADDING 12 - #endif - - int result = -1; // Returns clicked button from buttons list, 0 refers to closed window button - - int buttonCount = 0; - const char **buttonsText = GuiTextSplit(buttons, ';', &buttonCount, NULL); - Rectangle buttonBounds = { 0 }; - buttonBounds.x = bounds.x + RAYGUI_MESSAGEBOX_BUTTON_PADDING; - buttonBounds.y = bounds.y + bounds.height - RAYGUI_MESSAGEBOX_BUTTON_HEIGHT - RAYGUI_MESSAGEBOX_BUTTON_PADDING; - buttonBounds.width = (bounds.width - RAYGUI_MESSAGEBOX_BUTTON_PADDING*(buttonCount + 1))/buttonCount; - buttonBounds.height = RAYGUI_MESSAGEBOX_BUTTON_HEIGHT; - - //int textWidth = GetTextWidth(message) + 2; - - Rectangle textBounds = { 0 }; - textBounds.x = bounds.x + RAYGUI_MESSAGEBOX_BUTTON_PADDING; - textBounds.y = bounds.y + RAYGUI_WINDOWBOX_STATUSBAR_HEIGHT + RAYGUI_MESSAGEBOX_BUTTON_PADDING; - textBounds.width = bounds.width - RAYGUI_MESSAGEBOX_BUTTON_PADDING*2; - textBounds.height = bounds.height - RAYGUI_WINDOWBOX_STATUSBAR_HEIGHT - 3*RAYGUI_MESSAGEBOX_BUTTON_PADDING - RAYGUI_MESSAGEBOX_BUTTON_HEIGHT; - - // Draw control - //-------------------------------------------------------------------- - if (GuiWindowBox(bounds, title)) result = 0; - - int prevTextAlignment = GuiGetStyle(LABEL, TEXT_ALIGNMENT); - GuiSetStyle(LABEL, TEXT_ALIGNMENT, TEXT_ALIGN_CENTER); - GuiLabel(textBounds, message); - GuiSetStyle(LABEL, TEXT_ALIGNMENT, prevTextAlignment); - - prevTextAlignment = GuiGetStyle(BUTTON, TEXT_ALIGNMENT); - GuiSetStyle(BUTTON, TEXT_ALIGNMENT, TEXT_ALIGN_CENTER); - - for (int i = 0; i < buttonCount; i++) - { - if (GuiButton(buttonBounds, buttonsText[i])) result = i + 1; - buttonBounds.x += (buttonBounds.width + RAYGUI_MESSAGEBOX_BUTTON_PADDING); - } - - GuiSetStyle(BUTTON, TEXT_ALIGNMENT, prevTextAlignment); - //-------------------------------------------------------------------- - - return result; -} - -// Text Input Box control, ask for text -int GuiTextInputBox(Rectangle bounds, const char *title, const char *message, const char *buttons, char *text, int textMaxSize, bool *secretViewActive) -{ - #if !defined(RAYGUI_TEXTINPUTBOX_BUTTON_HEIGHT) - #define RAYGUI_TEXTINPUTBOX_BUTTON_HEIGHT 24 - #endif - #if !defined(RAYGUI_TEXTINPUTBOX_BUTTON_PADDING) - #define RAYGUI_TEXTINPUTBOX_BUTTON_PADDING 12 - #endif - #if !defined(RAYGUI_TEXTINPUTBOX_HEIGHT) - #define RAYGUI_TEXTINPUTBOX_HEIGHT 26 - #endif - - // Used to enable text edit mode - // WARNING: No more than one GuiTextInputBox() should be open at the same time - static bool textEditMode = false; - - int result = -1; - - int buttonCount = 0; - const char **buttonsText = GuiTextSplit(buttons, ';', &buttonCount, NULL); - Rectangle buttonBounds = { 0 }; - buttonBounds.x = bounds.x + RAYGUI_TEXTINPUTBOX_BUTTON_PADDING; - buttonBounds.y = bounds.y + bounds.height - RAYGUI_TEXTINPUTBOX_BUTTON_HEIGHT - RAYGUI_TEXTINPUTBOX_BUTTON_PADDING; - buttonBounds.width = (bounds.width - RAYGUI_TEXTINPUTBOX_BUTTON_PADDING*(buttonCount + 1))/buttonCount; - buttonBounds.height = RAYGUI_TEXTINPUTBOX_BUTTON_HEIGHT; - - int messageInputHeight = (int)bounds.height - RAYGUI_WINDOWBOX_STATUSBAR_HEIGHT - GuiGetStyle(STATUSBAR, BORDER_WIDTH) - RAYGUI_TEXTINPUTBOX_BUTTON_HEIGHT - 2*RAYGUI_TEXTINPUTBOX_BUTTON_PADDING; - - Rectangle textBounds = { 0 }; - if (message != NULL) - { - int textSize = GetTextWidth(message) + 2; - - textBounds.x = bounds.x + bounds.width/2 - textSize/2; - textBounds.y = bounds.y + RAYGUI_WINDOWBOX_STATUSBAR_HEIGHT + messageInputHeight/4 - (float)GuiGetStyle(DEFAULT, TEXT_SIZE)/2; - textBounds.width = (float)textSize; - textBounds.height = (float)GuiGetStyle(DEFAULT, TEXT_SIZE); - } - - Rectangle textBoxBounds = { 0 }; - textBoxBounds.x = bounds.x + RAYGUI_TEXTINPUTBOX_BUTTON_PADDING; - textBoxBounds.y = bounds.y + RAYGUI_WINDOWBOX_STATUSBAR_HEIGHT - RAYGUI_TEXTINPUTBOX_HEIGHT/2; - if (message == NULL) textBoxBounds.y = bounds.y + 24 + RAYGUI_TEXTINPUTBOX_BUTTON_PADDING; - else textBoxBounds.y += (messageInputHeight/2 + messageInputHeight/4); - textBoxBounds.width = bounds.width - RAYGUI_TEXTINPUTBOX_BUTTON_PADDING*2; - textBoxBounds.height = RAYGUI_TEXTINPUTBOX_HEIGHT; - - // Draw control - //-------------------------------------------------------------------- - if (GuiWindowBox(bounds, title)) result = 0; - - // Draw message if available - if (message != NULL) - { - int prevTextAlignment = GuiGetStyle(LABEL, TEXT_ALIGNMENT); - GuiSetStyle(LABEL, TEXT_ALIGNMENT, TEXT_ALIGN_CENTER); - GuiLabel(textBounds, message); - GuiSetStyle(LABEL, TEXT_ALIGNMENT, prevTextAlignment); - } - - if (secretViewActive != NULL) - { - static char stars[] = "****************"; - if (GuiTextBox(RAYGUI_CLITERAL(Rectangle){ textBoxBounds.x, textBoxBounds.y, textBoxBounds.width - 4 - RAYGUI_TEXTINPUTBOX_HEIGHT, textBoxBounds.height }, - ((*secretViewActive == 1) || textEditMode)? text : stars, textMaxSize, textEditMode)) textEditMode = !textEditMode; - - GuiToggle(RAYGUI_CLITERAL(Rectangle){ textBoxBounds.x + textBoxBounds.width - RAYGUI_TEXTINPUTBOX_HEIGHT, textBoxBounds.y, RAYGUI_TEXTINPUTBOX_HEIGHT, RAYGUI_TEXTINPUTBOX_HEIGHT }, (*secretViewActive == 1)? "#44#" : "#45#", secretViewActive); - } - else - { - if (GuiTextBox(textBoxBounds, text, textMaxSize, textEditMode)) textEditMode = !textEditMode; - } - - int prevBtnTextAlignment = GuiGetStyle(BUTTON, TEXT_ALIGNMENT); - GuiSetStyle(BUTTON, TEXT_ALIGNMENT, TEXT_ALIGN_CENTER); - - for (int i = 0; i < buttonCount; i++) - { - if (GuiButton(buttonBounds, buttonsText[i])) result = i + 1; - buttonBounds.x += (buttonBounds.width + RAYGUI_MESSAGEBOX_BUTTON_PADDING); - } - - if (result >= 0) textEditMode = false; - - GuiSetStyle(BUTTON, TEXT_ALIGNMENT, prevBtnTextAlignment); - //-------------------------------------------------------------------- - - return result; // Result is the pressed button index -} - -// Grid control -// NOTE: Returns grid mouse-hover selected cell -// About drawing lines at subpixel spacing, simple put, not easy solution: -// https://stackoverflow.com/questions/4435450/2d-opengl-drawing-lines-that-dont-exactly-fit-pixel-raster -int GuiGrid(Rectangle bounds, const char *text, float spacing, int subdivs, Vector2 *mouseCell) -{ - // Grid lines alpha amount - #if !defined(RAYGUI_GRID_ALPHA) - #define RAYGUI_GRID_ALPHA 0.15f - #endif - - int result = 0; - GuiState state = guiState; - - Vector2 mousePoint = GetMousePosition(); - Vector2 currentMouseCell = { -1, -1 }; - - float spaceWidth = spacing/(float)subdivs; - int linesV = (int)(bounds.width/spaceWidth) + 1; - int linesH = (int)(bounds.height/spaceWidth) + 1; - - int color = GuiGetStyle(DEFAULT, LINE_COLOR); - - // Update control - //-------------------------------------------------------------------- - if ((state != STATE_DISABLED) && !guiLocked && !guiControlExclusiveMode) - { - if (CheckCollisionPointRec(mousePoint, bounds)) - { - // NOTE: Cell values must be the upper left of the cell the mouse is in - currentMouseCell.x = floorf((mousePoint.x - bounds.x)/spacing); - currentMouseCell.y = floorf((mousePoint.y - bounds.y)/spacing); - } - } - //-------------------------------------------------------------------- - - // Draw control - //-------------------------------------------------------------------- - if (state == STATE_DISABLED) color = GuiGetStyle(DEFAULT, BORDER_COLOR_DISABLED); - - if (subdivs > 0) - { - // Draw vertical grid lines - for (int i = 0; i < linesV; i++) - { - Rectangle lineV = { bounds.x + spacing*i/subdivs, bounds.y, 1, bounds.height + 1 }; - GuiDrawRectangle(lineV, 0, BLANK, ((i%subdivs) == 0)? GuiFade(GetColor(color), RAYGUI_GRID_ALPHA*4) : GuiFade(GetColor(color), RAYGUI_GRID_ALPHA)); - } - - // Draw horizontal grid lines - for (int i = 0; i < linesH; i++) - { - Rectangle lineH = { bounds.x, bounds.y + spacing*i/subdivs, bounds.width + 1, 1 }; - GuiDrawRectangle(lineH, 0, BLANK, ((i%subdivs) == 0)? GuiFade(GetColor(color), RAYGUI_GRID_ALPHA*4) : GuiFade(GetColor(color), RAYGUI_GRID_ALPHA)); - } - } - - if (mouseCell != NULL) *mouseCell = currentMouseCell; - return result; -} - -//---------------------------------------------------------------------------------- -// Tooltip management functions -// NOTE: Tooltips requires some global variables: tooltipPtr -//---------------------------------------------------------------------------------- -// Enable gui tooltips (global state) -void GuiEnableTooltip(void) { guiTooltip = true; } - -// Disable gui tooltips (global state) -void GuiDisableTooltip(void) { guiTooltip = false; } - -// Set tooltip string -void GuiSetTooltip(const char *tooltip) { guiTooltipPtr = tooltip; } - -//---------------------------------------------------------------------------------- -// Styles loading functions -//---------------------------------------------------------------------------------- - -// Load raygui style file (.rgs) -// NOTE: By default a binary file is expected, that file could contain a custom font, -// in that case, custom font image atlas is GRAY+ALPHA and pixel data can be compressed (DEFLATE) -void GuiLoadStyle(const char *fileName) -{ - #define MAX_LINE_BUFFER_SIZE 256 - - bool tryBinary = false; - if (!guiStyleLoaded) GuiLoadStyleDefault(); - - // Try reading the files as text file first - FILE *rgsFile = fopen(fileName, "rt"); - - if (rgsFile != NULL) - { - char buffer[MAX_LINE_BUFFER_SIZE] = { 0 }; - fgets(buffer, MAX_LINE_BUFFER_SIZE, rgsFile); - - if (buffer[0] == '#') - { - int controlId = 0; - int propertyId = 0; - unsigned int propertyValue = 0; - - while (!feof(rgsFile)) - { - switch (buffer[0]) - { - case 'p': - { - // Style property: p - - sscanf(buffer, "p %d %d 0x%x", &controlId, &propertyId, &propertyValue); - GuiSetStyle(controlId, propertyId, (int)propertyValue); - - } break; - case 'f': - { - // Style font: f - - int fontSize = 0; - char charmapFileName[256] = { 0 }; - char fontFileName[256] = { 0 }; - sscanf(buffer, "f %d %s %[^\r\n]s", &fontSize, charmapFileName, fontFileName); - - Font font = { 0 }; - int *codepoints = NULL; - int codepointCount = 0; - - if (charmapFileName[0] != '0') - { - // Load text data from file - // NOTE: Expected an UTF-8 array of codepoints, no separation - char *textData = LoadFileText(TextFormat("%s/%s", GetDirectoryPath(fileName), charmapFileName)); - codepoints = LoadCodepoints(textData, &codepointCount); - UnloadFileText(textData); - } - - if (fontFileName[0] != '\0') - { - // In case a font is already loaded and it is not default internal font, unload it - if (font.texture.id != GetFontDefault().texture.id) UnloadTexture(font.texture); - - if (codepointCount > 0) font = LoadFontEx(TextFormat("%s/%s", GetDirectoryPath(fileName), fontFileName), fontSize, codepoints, codepointCount); - else font = LoadFontEx(TextFormat("%s/%s", GetDirectoryPath(fileName), fontFileName), fontSize, NULL, 0); // Default to 95 standard codepoints - } - - // If font texture not properly loaded, revert to default font and size/spacing - if (font.texture.id == 0) - { - font = GetFontDefault(); - GuiSetStyle(DEFAULT, TEXT_SIZE, 10); - GuiSetStyle(DEFAULT, TEXT_SPACING, 1); - } - - UnloadCodepoints(codepoints); - - if ((font.texture.id > 0) && (font.glyphCount > 0)) GuiSetFont(font); - - } break; - default: break; - } - - fgets(buffer, MAX_LINE_BUFFER_SIZE, rgsFile); - } - } - else tryBinary = true; - - fclose(rgsFile); - } - - if (tryBinary) - { - rgsFile = fopen(fileName, "rb"); - - if (rgsFile != NULL) - { - fseek(rgsFile, 0, SEEK_END); - int fileDataSize = ftell(rgsFile); - fseek(rgsFile, 0, SEEK_SET); - - if (fileDataSize > 0) - { - unsigned char *fileData = (unsigned char *)RAYGUI_MALLOC(fileDataSize*sizeof(unsigned char)); - fread(fileData, sizeof(unsigned char), fileDataSize, rgsFile); - - GuiLoadStyleFromMemory(fileData, fileDataSize); - - RAYGUI_FREE(fileData); - } - - fclose(rgsFile); - } - } -} - -// Load style default over global style -void GuiLoadStyleDefault(void) -{ - // We set this variable first to avoid cyclic function calls - // when calling GuiSetStyle() and GuiGetStyle() - guiStyleLoaded = true; - - // Initialize default LIGHT style property values - // WARNING: Default value are applied to all controls on set but - // they can be overwritten later on for every custom control - GuiSetStyle(DEFAULT, BORDER_COLOR_NORMAL, 0x838383ff); - GuiSetStyle(DEFAULT, BASE_COLOR_NORMAL, 0xc9c9c9ff); - GuiSetStyle(DEFAULT, TEXT_COLOR_NORMAL, 0x686868ff); - GuiSetStyle(DEFAULT, BORDER_COLOR_FOCUSED, 0x5bb2d9ff); - GuiSetStyle(DEFAULT, BASE_COLOR_FOCUSED, 0xc9effeff); - GuiSetStyle(DEFAULT, TEXT_COLOR_FOCUSED, 0x6c9bbcff); - GuiSetStyle(DEFAULT, BORDER_COLOR_PRESSED, 0x0492c7ff); - GuiSetStyle(DEFAULT, BASE_COLOR_PRESSED, 0x97e8ffff); - GuiSetStyle(DEFAULT, TEXT_COLOR_PRESSED, 0x368bafff); - GuiSetStyle(DEFAULT, BORDER_COLOR_DISABLED, 0xb5c1c2ff); - GuiSetStyle(DEFAULT, BASE_COLOR_DISABLED, 0xe6e9e9ff); - GuiSetStyle(DEFAULT, TEXT_COLOR_DISABLED, 0xaeb7b8ff); - GuiSetStyle(DEFAULT, BORDER_WIDTH, 1); - GuiSetStyle(DEFAULT, TEXT_PADDING, 0); - GuiSetStyle(DEFAULT, TEXT_ALIGNMENT, TEXT_ALIGN_CENTER); - - // Initialize default extended property values - // NOTE: By default, extended property values are initialized to 0 - GuiSetStyle(DEFAULT, TEXT_SIZE, 10); // DEFAULT, shared by all controls - GuiSetStyle(DEFAULT, TEXT_SPACING, 1); // DEFAULT, shared by all controls - GuiSetStyle(DEFAULT, LINE_COLOR, 0x90abb5ff); // DEFAULT specific property - GuiSetStyle(DEFAULT, BACKGROUND_COLOR, 0xf5f5f5ff); // DEFAULT specific property - GuiSetStyle(DEFAULT, TEXT_LINE_SPACING, 15); // DEFAULT, 15 pixels between lines - GuiSetStyle(DEFAULT, TEXT_ALIGNMENT_VERTICAL, TEXT_ALIGN_MIDDLE); // DEFAULT, text aligned vertically to middle of text-bounds - - // Initialize control-specific property values - // NOTE: Those properties are in default list but require specific values by control type - GuiSetStyle(LABEL, TEXT_ALIGNMENT, TEXT_ALIGN_LEFT); - GuiSetStyle(BUTTON, BORDER_WIDTH, 2); - GuiSetStyle(SLIDER, TEXT_PADDING, 4); - GuiSetStyle(PROGRESSBAR, TEXT_PADDING, 4); - GuiSetStyle(CHECKBOX, TEXT_PADDING, 4); - GuiSetStyle(CHECKBOX, TEXT_ALIGNMENT, TEXT_ALIGN_RIGHT); - GuiSetStyle(DROPDOWNBOX, TEXT_PADDING, 0); - GuiSetStyle(DROPDOWNBOX, TEXT_ALIGNMENT, TEXT_ALIGN_CENTER); - GuiSetStyle(TEXTBOX, TEXT_PADDING, 4); - GuiSetStyle(TEXTBOX, TEXT_ALIGNMENT, TEXT_ALIGN_LEFT); - GuiSetStyle(VALUEBOX, TEXT_PADDING, 0); - GuiSetStyle(VALUEBOX, TEXT_ALIGNMENT, TEXT_ALIGN_LEFT); - GuiSetStyle(SPINNER, TEXT_PADDING, 0); - GuiSetStyle(SPINNER, TEXT_ALIGNMENT, TEXT_ALIGN_LEFT); - GuiSetStyle(STATUSBAR, TEXT_PADDING, 8); - GuiSetStyle(STATUSBAR, TEXT_ALIGNMENT, TEXT_ALIGN_LEFT); - - // Initialize extended property values - // NOTE: By default, extended property values are initialized to 0 - GuiSetStyle(TOGGLE, GROUP_PADDING, 2); - GuiSetStyle(SLIDER, SLIDER_WIDTH, 16); - GuiSetStyle(SLIDER, SLIDER_PADDING, 1); - GuiSetStyle(PROGRESSBAR, PROGRESS_PADDING, 1); - GuiSetStyle(CHECKBOX, CHECK_PADDING, 1); - GuiSetStyle(COMBOBOX, COMBO_BUTTON_WIDTH, 32); - GuiSetStyle(COMBOBOX, COMBO_BUTTON_SPACING, 2); - GuiSetStyle(DROPDOWNBOX, ARROW_PADDING, 16); - GuiSetStyle(DROPDOWNBOX, DROPDOWN_ITEMS_SPACING, 2); - GuiSetStyle(SPINNER, SPIN_BUTTON_WIDTH, 24); - GuiSetStyle(SPINNER, SPIN_BUTTON_SPACING, 2); - GuiSetStyle(SCROLLBAR, BORDER_WIDTH, 0); - GuiSetStyle(SCROLLBAR, ARROWS_VISIBLE, 0); - GuiSetStyle(SCROLLBAR, ARROWS_SIZE, 6); - GuiSetStyle(SCROLLBAR, SCROLL_SLIDER_PADDING, 0); - GuiSetStyle(SCROLLBAR, SCROLL_SLIDER_SIZE, 16); - GuiSetStyle(SCROLLBAR, SCROLL_PADDING, 0); - GuiSetStyle(SCROLLBAR, SCROLL_SPEED, 12); - GuiSetStyle(LISTVIEW, LIST_ITEMS_HEIGHT, 28); - GuiSetStyle(LISTVIEW, LIST_ITEMS_SPACING, 2); - GuiSetStyle(LISTVIEW, SCROLLBAR_WIDTH, 12); - GuiSetStyle(LISTVIEW, SCROLLBAR_SIDE, SCROLLBAR_RIGHT_SIDE); - GuiSetStyle(COLORPICKER, COLOR_SELECTOR_SIZE, 8); - GuiSetStyle(COLORPICKER, HUEBAR_WIDTH, 16); - GuiSetStyle(COLORPICKER, HUEBAR_PADDING, 8); - GuiSetStyle(COLORPICKER, HUEBAR_SELECTOR_HEIGHT, 8); - GuiSetStyle(COLORPICKER, HUEBAR_SELECTOR_OVERFLOW, 2); - - if (guiFont.texture.id != GetFontDefault().texture.id) - { - // Unload previous font texture - UnloadTexture(guiFont.texture); - RL_FREE(guiFont.recs); - RL_FREE(guiFont.glyphs); - guiFont.recs = NULL; - guiFont.glyphs = NULL; - - // Setup default raylib font - guiFont = GetFontDefault(); - - // NOTE: Default raylib font character 95 is a white square - Rectangle whiteChar = guiFont.recs[95]; - - // NOTE: We set up a 1px padding on char rectangle to avoid pixel bleeding on MSAA filtering - SetShapesTexture(guiFont.texture, RAYGUI_CLITERAL(Rectangle){ whiteChar.x + 1, whiteChar.y + 1, whiteChar.width - 2, whiteChar.height - 2 }); - } -} - -// Get text with icon id prepended -// NOTE: Useful to add icons by name id (enum) instead of -// a number that can change between ricon versions -const char *GuiIconText(int iconId, const char *text) -{ -#if defined(RAYGUI_NO_ICONS) - return NULL; -#else - static char buffer[1024] = { 0 }; - static char iconBuffer[16] = { 0 }; - - if (text != NULL) - { - memset(buffer, 0, 1024); - sprintf(buffer, "#%03i#", iconId); - - for (int i = 5; i < 1024; i++) - { - buffer[i] = text[i - 5]; - if (text[i - 5] == '\0') break; - } - - return buffer; - } - else - { - sprintf(iconBuffer, "#%03i#", iconId); - - return iconBuffer; - } -#endif -} - -#if !defined(RAYGUI_NO_ICONS) -// Get full icons data pointer -unsigned int *GuiGetIcons(void) { return guiIconsPtr; } - -// Load raygui icons file (.rgi) -// NOTE: In case nameIds are required, they can be requested with loadIconsName, -// they are returned as a guiIconsName[iconCount][RAYGUI_ICON_MAX_NAME_LENGTH], -// WARNING: guiIconsName[]][] memory should be manually freed! -char **GuiLoadIcons(const char *fileName, bool loadIconsName) -{ - // Style File Structure (.rgi) - // ------------------------------------------------------ - // Offset | Size | Type | Description - // ------------------------------------------------------ - // 0 | 4 | char | Signature: "rGI " - // 4 | 2 | short | Version: 100 - // 6 | 2 | short | reserved - - // 8 | 2 | short | Num icons (N) - // 10 | 2 | short | Icons size (Options: 16, 32, 64) (S) - - // Icons name id (32 bytes per name id) - // foreach (icon) - // { - // 12+32*i | 32 | char | Icon NameId - // } - - // Icons data: One bit per pixel, stored as unsigned int array (depends on icon size) - // S*S pixels/32bit per unsigned int = K unsigned int per icon - // foreach (icon) - // { - // ... | K | unsigned int | Icon Data - // } - - FILE *rgiFile = fopen(fileName, "rb"); - - char **guiIconsName = NULL; - - if (rgiFile != NULL) - { - char signature[5] = { 0 }; - short version = 0; - short reserved = 0; - short iconCount = 0; - short iconSize = 0; - - fread(signature, 1, 4, rgiFile); - fread(&version, sizeof(short), 1, rgiFile); - fread(&reserved, sizeof(short), 1, rgiFile); - fread(&iconCount, sizeof(short), 1, rgiFile); - fread(&iconSize, sizeof(short), 1, rgiFile); - - if ((signature[0] == 'r') && - (signature[1] == 'G') && - (signature[2] == 'I') && - (signature[3] == ' ')) - { - if (loadIconsName) - { - guiIconsName = (char **)RAYGUI_MALLOC(iconCount*sizeof(char **)); - for (int i = 0; i < iconCount; i++) - { - guiIconsName[i] = (char *)RAYGUI_MALLOC(RAYGUI_ICON_MAX_NAME_LENGTH); - fread(guiIconsName[i], 1, RAYGUI_ICON_MAX_NAME_LENGTH, rgiFile); - } - } - else fseek(rgiFile, iconCount*RAYGUI_ICON_MAX_NAME_LENGTH, SEEK_CUR); - - // Read icons data directly over internal icons array - fread(guiIconsPtr, sizeof(unsigned int), iconCount*(iconSize*iconSize/32), rgiFile); - } - - fclose(rgiFile); - } - - return guiIconsName; -} - -// Draw selected icon using rectangles pixel-by-pixel -void GuiDrawIcon(int iconId, int posX, int posY, int pixelSize, Color color) -{ - #define BIT_CHECK(a,b) ((a) & (1u<<(b))) - - for (int i = 0, y = 0; i < RAYGUI_ICON_SIZE*RAYGUI_ICON_SIZE/32; i++) - { - for (int k = 0; k < 32; k++) - { - if (BIT_CHECK(guiIconsPtr[iconId*RAYGUI_ICON_DATA_ELEMENTS + i], k)) - { - #if !defined(RAYGUI_STANDALONE) - GuiDrawRectangle(RAYGUI_CLITERAL(Rectangle){ (float)posX + (k%RAYGUI_ICON_SIZE)*pixelSize, (float)posY + y*pixelSize, (float)pixelSize, (float)pixelSize }, 0, BLANK, color); - #endif - } - - if ((k == 15) || (k == 31)) y++; - } - } -} - -// Set icon drawing size -void GuiSetIconScale(int scale) -{ - if (scale >= 1) guiIconScale = scale; -} - -#endif // !RAYGUI_NO_ICONS - -//---------------------------------------------------------------------------------- -// Module specific Functions Definition -//---------------------------------------------------------------------------------- - -// Load style from memory -// WARNING: Binary files only -static void GuiLoadStyleFromMemory(const unsigned char *fileData, int dataSize) -{ - unsigned char *fileDataPtr = (unsigned char *)fileData; - - char signature[5] = { 0 }; - short version = 0; - short reserved = 0; - int propertyCount = 0; - - memcpy(signature, fileDataPtr, 4); - memcpy(&version, fileDataPtr + 4, sizeof(short)); - memcpy(&reserved, fileDataPtr + 4 + 2, sizeof(short)); - memcpy(&propertyCount, fileDataPtr + 4 + 2 + 2, sizeof(int)); - fileDataPtr += 12; - - if ((signature[0] == 'r') && - (signature[1] == 'G') && - (signature[2] == 'S') && - (signature[3] == ' ')) - { - short controlId = 0; - short propertyId = 0; - unsigned int propertyValue = 0; - - for (int i = 0; i < propertyCount; i++) - { - memcpy(&controlId, fileDataPtr, sizeof(short)); - memcpy(&propertyId, fileDataPtr + 2, sizeof(short)); - memcpy(&propertyValue, fileDataPtr + 2 + 2, sizeof(unsigned int)); - fileDataPtr += 8; - - if (controlId == 0) // DEFAULT control - { - // If a DEFAULT property is loaded, it is propagated to all controls - // NOTE: All DEFAULT properties should be defined first in the file - GuiSetStyle(0, (int)propertyId, propertyValue); - - if (propertyId < RAYGUI_MAX_PROPS_BASE) for (int j = 1; j < RAYGUI_MAX_CONTROLS; j++) GuiSetStyle(j, (int)propertyId, propertyValue); - } - else GuiSetStyle((int)controlId, (int)propertyId, propertyValue); - } - - // Font loading is highly dependant on raylib API to load font data and image - -#if !defined(RAYGUI_STANDALONE) - // Load custom font if available - int fontDataSize = 0; - memcpy(&fontDataSize, fileDataPtr, sizeof(int)); - fileDataPtr += 4; - - if (fontDataSize > 0) - { - Font font = { 0 }; - int fontType = 0; // 0-Normal, 1-SDF - - memcpy(&font.baseSize, fileDataPtr, sizeof(int)); - memcpy(&font.glyphCount, fileDataPtr + 4, sizeof(int)); - memcpy(&fontType, fileDataPtr + 4 + 4, sizeof(int)); - fileDataPtr += 12; - - // Load font white rectangle - Rectangle fontWhiteRec = { 0 }; - memcpy(&fontWhiteRec, fileDataPtr, sizeof(Rectangle)); - fileDataPtr += 16; - - // Load font image parameters - int fontImageUncompSize = 0; - int fontImageCompSize = 0; - memcpy(&fontImageUncompSize, fileDataPtr, sizeof(int)); - memcpy(&fontImageCompSize, fileDataPtr + 4, sizeof(int)); - fileDataPtr += 8; - - Image imFont = { 0 }; - imFont.mipmaps = 1; - memcpy(&imFont.width, fileDataPtr, sizeof(int)); - memcpy(&imFont.height, fileDataPtr + 4, sizeof(int)); - memcpy(&imFont.format, fileDataPtr + 4 + 4, sizeof(int)); - fileDataPtr += 12; - - if ((fontImageCompSize > 0) && (fontImageCompSize != fontImageUncompSize)) - { - // Compressed font atlas image data (DEFLATE), it requires DecompressData() - int dataUncompSize = 0; - unsigned char *compData = (unsigned char *)RAYGUI_MALLOC(fontImageCompSize); - memcpy(compData, fileDataPtr, fontImageCompSize); - fileDataPtr += fontImageCompSize; - - imFont.data = DecompressData(compData, fontImageCompSize, &dataUncompSize); - - // Security check, dataUncompSize must match the provided fontImageUncompSize - if (dataUncompSize != fontImageUncompSize) RAYGUI_LOG("WARNING: Uncompressed font atlas image data could be corrupted"); - - RAYGUI_FREE(compData); - } - else - { - // Font atlas image data is not compressed - imFont.data = (unsigned char *)RAYGUI_MALLOC(fontImageUncompSize); - memcpy(imFont.data, fileDataPtr, fontImageUncompSize); - fileDataPtr += fontImageUncompSize; - } - - if (font.texture.id != GetFontDefault().texture.id) UnloadTexture(font.texture); - font.texture = LoadTextureFromImage(imFont); - - RAYGUI_FREE(imFont.data); - - // Validate font atlas texture was loaded correctly - if (font.texture.id != 0) - { - // Load font recs data - int recsDataSize = font.glyphCount*sizeof(Rectangle); - int recsDataCompressedSize = 0; - - // WARNING: Version 400 adds the compression size parameter - if (version >= 400) - { - // RGS files version 400 support compressed recs data - memcpy(&recsDataCompressedSize, fileDataPtr, sizeof(int)); - fileDataPtr += sizeof(int); - } - - if ((recsDataCompressedSize > 0) && (recsDataCompressedSize != recsDataSize)) - { - // Recs data is compressed, uncompress it - unsigned char *recsDataCompressed = (unsigned char *)RAYGUI_MALLOC(recsDataCompressedSize); - - memcpy(recsDataCompressed, fileDataPtr, recsDataCompressedSize); - fileDataPtr += recsDataCompressedSize; - - int recsDataUncompSize = 0; - font.recs = (Rectangle *)DecompressData(recsDataCompressed, recsDataCompressedSize, &recsDataUncompSize); - - // Security check, data uncompressed size must match the expected original data size - if (recsDataUncompSize != recsDataSize) RAYGUI_LOG("WARNING: Uncompressed font recs data could be corrupted"); - - RAYGUI_FREE(recsDataCompressed); - } - else - { - // Recs data is uncompressed - font.recs = (Rectangle *)RAYGUI_CALLOC(font.glyphCount, sizeof(Rectangle)); - for (int i = 0; i < font.glyphCount; i++) - { - memcpy(&font.recs[i], fileDataPtr, sizeof(Rectangle)); - fileDataPtr += sizeof(Rectangle); - } - } - - // Load font glyphs info data - int glyphsDataSize = font.glyphCount*16; // 16 bytes data per glyph - int glyphsDataCompressedSize = 0; - - // WARNING: Version 400 adds the compression size parameter - if (version >= 400) - { - // RGS files version 400 support compressed glyphs data - memcpy(&glyphsDataCompressedSize, fileDataPtr, sizeof(int)); - fileDataPtr += sizeof(int); - } - - // Allocate required glyphs space to fill with data - font.glyphs = (GlyphInfo *)RAYGUI_CALLOC(font.glyphCount, sizeof(GlyphInfo)); - - if ((glyphsDataCompressedSize > 0) && (glyphsDataCompressedSize != glyphsDataSize)) - { - // Glyphs data is compressed, uncompress it - unsigned char *glypsDataCompressed = (unsigned char *)RAYGUI_MALLOC(glyphsDataCompressedSize); - - memcpy(glypsDataCompressed, fileDataPtr, glyphsDataCompressedSize); - fileDataPtr += glyphsDataCompressedSize; - - int glyphsDataUncompSize = 0; - unsigned char *glyphsDataUncomp = DecompressData(glypsDataCompressed, glyphsDataCompressedSize, &glyphsDataUncompSize); - - // Security check, data uncompressed size must match the expected original data size - if (glyphsDataUncompSize != glyphsDataSize) RAYGUI_LOG("WARNING: Uncompressed font glyphs data could be corrupted"); - - unsigned char *glyphsDataUncompPtr = glyphsDataUncomp; - - for (int i = 0; i < font.glyphCount; i++) - { - memcpy(&font.glyphs[i].value, glyphsDataUncompPtr, sizeof(int)); - memcpy(&font.glyphs[i].offsetX, glyphsDataUncompPtr + 4, sizeof(int)); - memcpy(&font.glyphs[i].offsetY, glyphsDataUncompPtr + 8, sizeof(int)); - memcpy(&font.glyphs[i].advanceX, glyphsDataUncompPtr + 12, sizeof(int)); - glyphsDataUncompPtr += 16; - } - - RAYGUI_FREE(glypsDataCompressed); - RAYGUI_FREE(glyphsDataUncomp); - } - else - { - // Glyphs data is uncompressed - for (int i = 0; i < font.glyphCount; i++) - { - memcpy(&font.glyphs[i].value, fileDataPtr, sizeof(int)); - memcpy(&font.glyphs[i].offsetX, fileDataPtr + 4, sizeof(int)); - memcpy(&font.glyphs[i].offsetY, fileDataPtr + 8, sizeof(int)); - memcpy(&font.glyphs[i].advanceX, fileDataPtr + 12, sizeof(int)); - fileDataPtr += 16; - } - } - } - else font = GetFontDefault(); // Fallback in case of errors loading font atlas texture - - GuiSetFont(font); - - // Set font texture source rectangle to be used as white texture to draw shapes - // NOTE: It makes possible to draw shapes and text (full UI) in a single draw call - if ((fontWhiteRec.x > 0) && - (fontWhiteRec.y > 0) && - (fontWhiteRec.width > 0) && - (fontWhiteRec.height > 0)) SetShapesTexture(font.texture, fontWhiteRec); - } -#endif - } -} - -// Gui get text width considering icon -static int GetTextWidth(const char *text) -{ - #if !defined(ICON_TEXT_PADDING) - #define ICON_TEXT_PADDING 4 - #endif - - Vector2 textSize = { 0 }; - int textIconOffset = 0; - - if ((text != NULL) && (text[0] != '\0')) - { - if (text[0] == '#') - { - for (int i = 1; (i < 5) && (text[i] != '\0'); i++) - { - if (text[i] == '#') - { - textIconOffset = i; - break; - } - } - } - - text += textIconOffset; - - // Make sure guiFont is set, GuiGetStyle() initializes it lazynessly - float fontSize = (float)GuiGetStyle(DEFAULT, TEXT_SIZE); - - // Custom MeasureText() implementation - if ((guiFont.texture.id > 0) && (text != NULL)) - { - // Get size in bytes of text, considering end of line and line break - int size = 0; - for (int i = 0; i < MAX_LINE_BUFFER_SIZE; i++) - { - if ((text[i] != '\0') && (text[i] != '\n')) size++; - else break; - } - - float scaleFactor = fontSize/(float)guiFont.baseSize; - textSize.y = (float)guiFont.baseSize*scaleFactor; - float glyphWidth = 0.0f; - - for (int i = 0, codepointSize = 0; i < size; i += codepointSize) - { - int codepoint = GetCodepointNext(&text[i], &codepointSize); - int codepointIndex = GetGlyphIndex(guiFont, codepoint); - - if (guiFont.glyphs[codepointIndex].advanceX == 0) glyphWidth = ((float)guiFont.recs[codepointIndex].width*scaleFactor); - else glyphWidth = ((float)guiFont.glyphs[codepointIndex].advanceX*scaleFactor); - - textSize.x += (glyphWidth + (float)GuiGetStyle(DEFAULT, TEXT_SPACING)); - } - } - - if (textIconOffset > 0) textSize.x += (RAYGUI_ICON_SIZE + ICON_TEXT_PADDING); - } - - return (int)textSize.x; -} - -// Get text bounds considering control bounds -static Rectangle GetTextBounds(int control, Rectangle bounds) -{ - Rectangle textBounds = bounds; - - textBounds.x = bounds.x + GuiGetStyle(control, BORDER_WIDTH); - textBounds.y = bounds.y + GuiGetStyle(control, BORDER_WIDTH) + GuiGetStyle(control, TEXT_PADDING); - textBounds.width = bounds.width - 2*GuiGetStyle(control, BORDER_WIDTH) - 2*GuiGetStyle(control, TEXT_PADDING); - textBounds.height = bounds.height - 2*GuiGetStyle(control, BORDER_WIDTH) - 2*GuiGetStyle(control, TEXT_PADDING); // NOTE: Text is processed line per line! - - // Depending on control, TEXT_PADDING and TEXT_ALIGNMENT properties could affect the text-bounds - switch (control) - { - case COMBOBOX: - case DROPDOWNBOX: - case LISTVIEW: - // TODO: Special cases (no label): COMBOBOX, DROPDOWNBOX, LISTVIEW - case SLIDER: - case CHECKBOX: - case VALUEBOX: - case SPINNER: - // TODO: More special cases (label on side): SLIDER, CHECKBOX, VALUEBOX, SPINNER - default: - { - // TODO: WARNING: TEXT_ALIGNMENT is already considered in GuiDrawText() - if (GuiGetStyle(control, TEXT_ALIGNMENT) == TEXT_ALIGN_RIGHT) textBounds.x -= GuiGetStyle(control, TEXT_PADDING); - else textBounds.x += GuiGetStyle(control, TEXT_PADDING); - } - break; - } - - return textBounds; -} - -// Get text icon if provided and move text cursor -// NOTE: We support up to 999 values for iconId -static const char *GetTextIcon(const char *text, int *iconId) -{ -#if !defined(RAYGUI_NO_ICONS) - *iconId = -1; - if (text[0] == '#') // Maybe we have an icon! - { - char iconValue[4] = { 0 }; // Maximum length for icon value: 3 digits + '\0' - - int pos = 1; - while ((pos < 4) && (text[pos] >= '0') && (text[pos] <= '9')) - { - iconValue[pos - 1] = text[pos]; - pos++; - } - - if (text[pos] == '#') - { - *iconId = TextToInteger(iconValue); - - // Move text pointer after icon - // WARNING: If only icon provided, it could point to EOL character: '\0' - if (*iconId >= 0) text += (pos + 1); - } - } -#endif - - return text; -} - -// Get text divided into lines (by line-breaks '\n') -const char **GetTextLines(const char *text, int *count) -{ - #define RAYGUI_MAX_TEXT_LINES 128 - - static const char *lines[RAYGUI_MAX_TEXT_LINES] = { 0 }; - for (int i = 0; i < RAYGUI_MAX_TEXT_LINES; i++) lines[i] = NULL; // Init NULL pointers to substrings - - int textSize = (int)strlen(text); - - lines[0] = text; - int len = 0; - *count = 1; - //int lineSize = 0; // Stores current line size, not returned - - for (int i = 0, k = 0; (i < textSize) && (*count < RAYGUI_MAX_TEXT_LINES); i++) - { - if (text[i] == '\n') - { - //lineSize = len; - k++; - lines[k] = &text[i + 1]; // WARNING: next value is valid? - len = 0; - *count += 1; - } - else len++; - } - - //lines[*count - 1].size = len; - - return lines; -} - -// Get text width to next space for provided string -static float GetNextSpaceWidth(const char *text, int *nextSpaceIndex) -{ - float width = 0; - int codepointByteCount = 0; - int codepoint = 0; - int index = 0; - float glyphWidth = 0; - float scaleFactor = (float)GuiGetStyle(DEFAULT, TEXT_SIZE)/guiFont.baseSize; - - for (int i = 0; text[i] != '\0'; i++) - { - if (text[i] != ' ') - { - codepoint = GetCodepoint(&text[i], &codepointByteCount); - index = GetGlyphIndex(guiFont, codepoint); - glyphWidth = (guiFont.glyphs[index].advanceX == 0)? guiFont.recs[index].width*scaleFactor : guiFont.glyphs[index].advanceX*scaleFactor; - width += (glyphWidth + (float)GuiGetStyle(DEFAULT, TEXT_SPACING)); - } - else - { - *nextSpaceIndex = i; - break; - } - } - - return width; -} - -// Gui draw text using default font -static void GuiDrawText(const char *text, Rectangle textBounds, int alignment, Color tint) -{ - #define TEXT_VALIGN_PIXEL_OFFSET(h) ((int)h%2) // Vertical alignment for pixel perfect - - #if !defined(ICON_TEXT_PADDING) - #define ICON_TEXT_PADDING 4 - #endif - - if ((text == NULL) || (text[0] == '\0')) return; // Security check - - // PROCEDURE: - // - Text is processed line per line - // - For every line, horizontal alignment is defined - // - For all text, vertical alignment is defined (multiline text only) - // - For every line, wordwrap mode is checked (useful for GuitextBox(), read-only) - - // Get text lines (using '\n' as delimiter) to be processed individually - // WARNING: We can't use GuiTextSplit() function because it can be already used - // before the GuiDrawText() call and its buffer is static, it would be overriden :( - int lineCount = 0; - const char **lines = GetTextLines(text, &lineCount); - - // Text style variables - //int alignment = GuiGetStyle(DEFAULT, TEXT_ALIGNMENT); - int alignmentVertical = GuiGetStyle(DEFAULT, TEXT_ALIGNMENT_VERTICAL); - int wrapMode = GuiGetStyle(DEFAULT, TEXT_WRAP_MODE); // Wrap-mode only available in read-only mode, no for text editing - - // TODO: WARNING: This totalHeight is not valid for vertical alignment in case of word-wrap - float totalHeight = (float)(lineCount*GuiGetStyle(DEFAULT, TEXT_SIZE) + (lineCount - 1)*GuiGetStyle(DEFAULT, TEXT_SIZE)/2); - float posOffsetY = 0.0f; - - for (int i = 0; i < lineCount; i++) - { - int iconId = 0; - lines[i] = GetTextIcon(lines[i], &iconId); // Check text for icon and move cursor - - // Get text position depending on alignment and iconId - //--------------------------------------------------------------------------------- - Vector2 textBoundsPosition = { textBounds.x, textBounds.y }; - float textBoundsWidthOffset = 0.0f; - - // NOTE: We get text size after icon has been processed - // WARNING: GetTextWidth() also processes text icon to get width! -> Really needed? - int textSizeX = GetTextWidth(lines[i]); - - // If text requires an icon, add size to measure - if (iconId >= 0) - { - textSizeX += RAYGUI_ICON_SIZE*guiIconScale; - - // WARNING: If only icon provided, text could be pointing to EOF character: '\0' -#if !defined(RAYGUI_NO_ICONS) - if ((lines[i] != NULL) && (lines[i][0] != '\0')) textSizeX += ICON_TEXT_PADDING; -#endif - } - - // Check guiTextAlign global variables - switch (alignment) - { - case TEXT_ALIGN_LEFT: textBoundsPosition.x = textBounds.x; break; - case TEXT_ALIGN_CENTER: textBoundsPosition.x = textBounds.x + textBounds.width/2 - textSizeX/2; break; - case TEXT_ALIGN_RIGHT: textBoundsPosition.x = textBounds.x + textBounds.width - textSizeX; break; - default: break; - } - - if (textSizeX > textBounds.width && (lines[i] != NULL) && (lines[i][0] != '\0')) textBoundsPosition.x = textBounds.x; - - switch (alignmentVertical) - { - // Only valid in case of wordWrap = 0; - case TEXT_ALIGN_TOP: textBoundsPosition.y = textBounds.y + posOffsetY; break; - case TEXT_ALIGN_MIDDLE: textBoundsPosition.y = textBounds.y + posOffsetY + textBounds.height/2 - totalHeight/2 + TEXT_VALIGN_PIXEL_OFFSET(textBounds.height); break; - case TEXT_ALIGN_BOTTOM: textBoundsPosition.y = textBounds.y + posOffsetY + textBounds.height - totalHeight + TEXT_VALIGN_PIXEL_OFFSET(textBounds.height); break; - default: break; - } - - // NOTE: Make sure we get pixel-perfect coordinates, - // In case of decimals we got weird text positioning - textBoundsPosition.x = (float)((int)textBoundsPosition.x); - textBoundsPosition.y = (float)((int)textBoundsPosition.y); - //--------------------------------------------------------------------------------- - - // Draw text (with icon if available) - //--------------------------------------------------------------------------------- -#if !defined(RAYGUI_NO_ICONS) - if (iconId >= 0) - { - // NOTE: We consider icon height, probably different than text size - GuiDrawIcon(iconId, (int)textBoundsPosition.x, (int)(textBounds.y + textBounds.height/2 - RAYGUI_ICON_SIZE*guiIconScale/2 + TEXT_VALIGN_PIXEL_OFFSET(textBounds.height)), guiIconScale, tint); - textBoundsPosition.x += (float)(RAYGUI_ICON_SIZE*guiIconScale + ICON_TEXT_PADDING); - textBoundsWidthOffset = (float)(RAYGUI_ICON_SIZE*guiIconScale + ICON_TEXT_PADDING); - } -#endif - // Get size in bytes of text, - // considering end of line and line break - int lineSize = 0; - for (int c = 0; (lines[i][c] != '\0') && (lines[i][c] != '\n') && (lines[i][c] != '\r'); c++, lineSize++){ } - float scaleFactor = (float)GuiGetStyle(DEFAULT, TEXT_SIZE)/guiFont.baseSize; - - int lastSpaceIndex = 0; - bool tempWrapCharMode = false; - - int textOffsetY = 0; - float textOffsetX = 0.0f; - float glyphWidth = 0; - - int ellipsisWidth = GetTextWidth("..."); - bool textOverflow = false; - for (int c = 0, codepointSize = 0; c < lineSize; c += codepointSize) - { - int codepoint = GetCodepointNext(&lines[i][c], &codepointSize); - int index = GetGlyphIndex(guiFont, codepoint); - - // NOTE: Normally we exit the decoding sequence as soon as a bad byte is found (and return 0x3f) - // but we need to draw all of the bad bytes using the '?' symbol moving one byte - if (codepoint == 0x3f) codepointSize = 1; // TODO: Review not recognized codepoints size - - // Get glyph width to check if it goes out of bounds - if (guiFont.glyphs[index].advanceX == 0) glyphWidth = ((float)guiFont.recs[index].width*scaleFactor); - else glyphWidth = (float)guiFont.glyphs[index].advanceX*scaleFactor; - - // Wrap mode text measuring, to validate if - // it can be drawn or a new line is required - if (wrapMode == TEXT_WRAP_CHAR) - { - // Jump to next line if current character reach end of the box limits - if ((textOffsetX + glyphWidth) > textBounds.width - textBoundsWidthOffset) - { - textOffsetX = 0.0f; - textOffsetY += GuiGetStyle(DEFAULT, TEXT_LINE_SPACING); - - if (tempWrapCharMode) // Wrap at char level when too long words - { - wrapMode = TEXT_WRAP_WORD; - tempWrapCharMode = false; - } - } - } - else if (wrapMode == TEXT_WRAP_WORD) - { - if (codepoint == 32) lastSpaceIndex = c; - - // Get width to next space in line - int nextSpaceIndex = 0; - float nextSpaceWidth = GetNextSpaceWidth(lines[i] + c, &nextSpaceIndex); - - int nextSpaceIndex2 = 0; - float nextWordSize = GetNextSpaceWidth(lines[i] + lastSpaceIndex + 1, &nextSpaceIndex2); - - if (nextWordSize > textBounds.width - textBoundsWidthOffset) - { - // Considering the case the next word is longer than bounds - tempWrapCharMode = true; - wrapMode = TEXT_WRAP_CHAR; - } - else if ((textOffsetX + nextSpaceWidth) > textBounds.width - textBoundsWidthOffset) - { - textOffsetX = 0.0f; - textOffsetY += GuiGetStyle(DEFAULT, TEXT_LINE_SPACING); - } - } - - if (codepoint == '\n') break; // WARNING: Lines are already processed manually, no need to keep drawing after this codepoint - else - { - // TODO: There are multiple types of spaces in Unicode, - // maybe it's a good idea to add support for more: http://jkorpela.fi/chars/spaces.html - if ((codepoint != ' ') && (codepoint != '\t')) // Do not draw codepoints with no glyph - { - if (wrapMode == TEXT_WRAP_NONE) - { - // Draw only required text glyphs fitting the textBounds.width - if (textSizeX > textBounds.width) - { - if (textOffsetX <= (textBounds.width - glyphWidth - textBoundsWidthOffset - ellipsisWidth)) - { - DrawTextCodepoint(guiFont, codepoint, RAYGUI_CLITERAL(Vector2){ textBoundsPosition.x + textOffsetX, textBoundsPosition.y + textOffsetY }, (float)GuiGetStyle(DEFAULT, TEXT_SIZE), GuiFade(tint, guiAlpha)); - } - else if (!textOverflow) - { - textOverflow = true; - - for (int j = 0; j < ellipsisWidth; j += ellipsisWidth/3) - { - DrawTextCodepoint(guiFont, '.', RAYGUI_CLITERAL(Vector2){ textBoundsPosition.x + textOffsetX + j, textBoundsPosition.y + textOffsetY }, (float)GuiGetStyle(DEFAULT, TEXT_SIZE), GuiFade(tint, guiAlpha)); - } - } - } - else - { - DrawTextCodepoint(guiFont, codepoint, RAYGUI_CLITERAL(Vector2){ textBoundsPosition.x + textOffsetX, textBoundsPosition.y + textOffsetY }, (float)GuiGetStyle(DEFAULT, TEXT_SIZE), GuiFade(tint, guiAlpha)); - } - } - else if ((wrapMode == TEXT_WRAP_CHAR) || (wrapMode == TEXT_WRAP_WORD)) - { - // Draw only glyphs inside the bounds - if ((textBoundsPosition.y + textOffsetY) <= (textBounds.y + textBounds.height - GuiGetStyle(DEFAULT, TEXT_SIZE))) - { - DrawTextCodepoint(guiFont, codepoint, RAYGUI_CLITERAL(Vector2){ textBoundsPosition.x + textOffsetX, textBoundsPosition.y + textOffsetY }, (float)GuiGetStyle(DEFAULT, TEXT_SIZE), GuiFade(tint, guiAlpha)); - } - } - } - - if (guiFont.glyphs[index].advanceX == 0) textOffsetX += ((float)guiFont.recs[index].width*scaleFactor + (float)GuiGetStyle(DEFAULT, TEXT_SPACING)); - else textOffsetX += ((float)guiFont.glyphs[index].advanceX*scaleFactor + (float)GuiGetStyle(DEFAULT, TEXT_SPACING)); - } - } - - if (wrapMode == TEXT_WRAP_NONE) posOffsetY += (float)GuiGetStyle(DEFAULT, TEXT_LINE_SPACING); - else if ((wrapMode == TEXT_WRAP_CHAR) || (wrapMode == TEXT_WRAP_WORD)) posOffsetY += (textOffsetY + (float)GuiGetStyle(DEFAULT, TEXT_LINE_SPACING)); - //--------------------------------------------------------------------------------- - } - -#if defined(RAYGUI_DEBUG_TEXT_BOUNDS) - GuiDrawRectangle(textBounds, 0, WHITE, Fade(BLUE, 0.4f)); -#endif -} - -// Gui draw rectangle using default raygui plain style with borders -static void GuiDrawRectangle(Rectangle rec, int borderWidth, Color borderColor, Color color) -{ - if (color.a > 0) - { - // Draw rectangle filled with color - DrawRectangle((int)rec.x, (int)rec.y, (int)rec.width, (int)rec.height, GuiFade(color, guiAlpha)); - } - - if (borderWidth > 0) - { - // Draw rectangle border lines with color - DrawRectangle((int)rec.x, (int)rec.y, (int)rec.width, borderWidth, GuiFade(borderColor, guiAlpha)); - DrawRectangle((int)rec.x, (int)rec.y + borderWidth, borderWidth, (int)rec.height - 2*borderWidth, GuiFade(borderColor, guiAlpha)); - DrawRectangle((int)rec.x + (int)rec.width - borderWidth, (int)rec.y + borderWidth, borderWidth, (int)rec.height - 2*borderWidth, GuiFade(borderColor, guiAlpha)); - DrawRectangle((int)rec.x, (int)rec.y + (int)rec.height - borderWidth, (int)rec.width, borderWidth, GuiFade(borderColor, guiAlpha)); - } - -#if defined(RAYGUI_DEBUG_RECS_BOUNDS) - DrawRectangle((int)rec.x, (int)rec.y, (int)rec.width, (int)rec.height, Fade(RED, 0.4f)); -#endif -} - -// Draw tooltip using control bounds -static void GuiTooltip(Rectangle controlRec) -{ - if (!guiLocked && guiTooltip && (guiTooltipPtr != NULL) && !guiControlExclusiveMode) - { - Vector2 textSize = MeasureTextEx(GuiGetFont(), guiTooltipPtr, (float)GuiGetStyle(DEFAULT, TEXT_SIZE), (float)GuiGetStyle(DEFAULT, TEXT_SPACING)); - - if ((controlRec.x + textSize.x + 16) > GetScreenWidth()) controlRec.x -= (textSize.x + 16 - controlRec.width); - - GuiPanel(RAYGUI_CLITERAL(Rectangle){ controlRec.x, controlRec.y + controlRec.height + 4, textSize.x + 16, GuiGetStyle(DEFAULT, TEXT_SIZE) + 8.f }, NULL); - - int textPadding = GuiGetStyle(LABEL, TEXT_PADDING); - int textAlignment = GuiGetStyle(LABEL, TEXT_ALIGNMENT); - GuiSetStyle(LABEL, TEXT_PADDING, 0); - GuiSetStyle(LABEL, TEXT_ALIGNMENT, TEXT_ALIGN_CENTER); - GuiLabel(RAYGUI_CLITERAL(Rectangle){ controlRec.x, controlRec.y + controlRec.height + 4, textSize.x + 16, GuiGetStyle(DEFAULT, TEXT_SIZE) + 8.f }, guiTooltipPtr); - GuiSetStyle(LABEL, TEXT_ALIGNMENT, textAlignment); - GuiSetStyle(LABEL, TEXT_PADDING, textPadding); - } -} - -// Split controls text into multiple strings -// Also check for multiple columns (required by GuiToggleGroup()) -static const char **GuiTextSplit(const char *text, char delimiter, int *count, int *textRow) -{ - // NOTE: Current implementation returns a copy of the provided string with '\0' (string end delimiter) - // inserted between strings defined by "delimiter" parameter. No memory is dynamically allocated, - // all used memory is static... it has some limitations: - // 1. Maximum number of possible split strings is set by RAYGUI_TEXTSPLIT_MAX_ITEMS - // 2. Maximum size of text to split is RAYGUI_TEXTSPLIT_MAX_TEXT_SIZE - // NOTE: Those definitions could be externally provided if required - - // TODO: HACK: GuiTextSplit() - Review how textRows are returned to user - // textRow is an externally provided array of integers that stores row number for every splitted string - - #if !defined(RAYGUI_TEXTSPLIT_MAX_ITEMS) - #define RAYGUI_TEXTSPLIT_MAX_ITEMS 128 - #endif - #if !defined(RAYGUI_TEXTSPLIT_MAX_TEXT_SIZE) - #define RAYGUI_TEXTSPLIT_MAX_TEXT_SIZE 1024 - #endif - - static const char *result[RAYGUI_TEXTSPLIT_MAX_ITEMS] = { NULL }; // String pointers array (points to buffer data) - static char buffer[RAYGUI_TEXTSPLIT_MAX_TEXT_SIZE] = { 0 }; // Buffer data (text input copy with '\0' added) - memset(buffer, 0, RAYGUI_TEXTSPLIT_MAX_TEXT_SIZE); - - result[0] = buffer; - int counter = 1; - - if (textRow != NULL) textRow[0] = 0; - - // Count how many substrings we have on text and point to every one - for (int i = 0; i < RAYGUI_TEXTSPLIT_MAX_TEXT_SIZE; i++) - { - buffer[i] = text[i]; - if (buffer[i] == '\0') break; - else if ((buffer[i] == delimiter) || (buffer[i] == '\n')) - { - result[counter] = buffer + i + 1; - - if (textRow != NULL) - { - if (buffer[i] == '\n') textRow[counter] = textRow[counter - 1] + 1; - else textRow[counter] = textRow[counter - 1]; - } - - buffer[i] = '\0'; // Set an end of string at this point - - counter++; - if (counter > RAYGUI_TEXTSPLIT_MAX_ITEMS) break; - } - } - - *count = counter; - - return result; -} - -// Convert color data from RGB to HSV -// NOTE: Color data should be passed normalized -static Vector3 ConvertRGBtoHSV(Vector3 rgb) -{ - Vector3 hsv = { 0 }; - float min = 0.0f; - float max = 0.0f; - float delta = 0.0f; - - min = (rgb.x < rgb.y)? rgb.x : rgb.y; - min = (min < rgb.z)? min : rgb.z; - - max = (rgb.x > rgb.y)? rgb.x : rgb.y; - max = (max > rgb.z)? max : rgb.z; - - hsv.z = max; // Value - delta = max - min; - - if (delta < 0.00001f) - { - hsv.y = 0.0f; - hsv.x = 0.0f; // Undefined, maybe NAN? - return hsv; - } - - if (max > 0.0f) - { - // NOTE: If max is 0, this divide would cause a crash - hsv.y = (delta/max); // Saturation - } - else - { - // NOTE: If max is 0, then r = g = b = 0, s = 0, h is undefined - hsv.y = 0.0f; - hsv.x = 0.0f; // Undefined, maybe NAN? - return hsv; - } - - // NOTE: Comparing float values could not work properly - if (rgb.x >= max) hsv.x = (rgb.y - rgb.z)/delta; // Between yellow & magenta - else - { - if (rgb.y >= max) hsv.x = 2.0f + (rgb.z - rgb.x)/delta; // Between cyan & yellow - else hsv.x = 4.0f + (rgb.x - rgb.y)/delta; // Between magenta & cyan - } - - hsv.x *= 60.0f; // Convert to degrees - - if (hsv.x < 0.0f) hsv.x += 360.0f; - - return hsv; -} - -// Convert color data from HSV to RGB -// NOTE: Color data should be passed normalized -static Vector3 ConvertHSVtoRGB(Vector3 hsv) -{ - Vector3 rgb = { 0 }; - float hh = 0.0f, p = 0.0f, q = 0.0f, t = 0.0f, ff = 0.0f; - long i = 0; - - // NOTE: Comparing float values could not work properly - if (hsv.y <= 0.0f) - { - rgb.x = hsv.z; - rgb.y = hsv.z; - rgb.z = hsv.z; - return rgb; - } - - hh = hsv.x; - if (hh >= 360.0f) hh = 0.0f; - hh /= 60.0f; - - i = (long)hh; - ff = hh - i; - p = hsv.z*(1.0f - hsv.y); - q = hsv.z*(1.0f - (hsv.y*ff)); - t = hsv.z*(1.0f - (hsv.y*(1.0f - ff))); - - switch (i) - { - case 0: - { - rgb.x = hsv.z; - rgb.y = t; - rgb.z = p; - } break; - case 1: - { - rgb.x = q; - rgb.y = hsv.z; - rgb.z = p; - } break; - case 2: - { - rgb.x = p; - rgb.y = hsv.z; - rgb.z = t; - } break; - case 3: - { - rgb.x = p; - rgb.y = q; - rgb.z = hsv.z; - } break; - case 4: - { - rgb.x = t; - rgb.y = p; - rgb.z = hsv.z; - } break; - case 5: - default: - { - rgb.x = hsv.z; - rgb.y = p; - rgb.z = q; - } break; - } - - return rgb; -} - -// Scroll bar control (used by GuiScrollPanel()) -static int GuiScrollBar(Rectangle bounds, int value, int minValue, int maxValue) -{ - GuiState state = guiState; - - // Is the scrollbar horizontal or vertical? - bool isVertical = (bounds.width > bounds.height)? false : true; - - // The size (width or height depending on scrollbar type) of the spinner buttons - const int spinnerSize = GuiGetStyle(SCROLLBAR, ARROWS_VISIBLE)? - (isVertical? (int)bounds.width - 2*GuiGetStyle(SCROLLBAR, BORDER_WIDTH) : - (int)bounds.height - 2*GuiGetStyle(SCROLLBAR, BORDER_WIDTH)) : 0; - - // Arrow buttons [<] [>] [∧] [∨] - Rectangle arrowUpLeft = { 0 }; - Rectangle arrowDownRight = { 0 }; - - // Actual area of the scrollbar excluding the arrow buttons - Rectangle scrollbar = { 0 }; - - // Slider bar that moves --[///]----- - Rectangle slider = { 0 }; - - // Normalize value - if (value > maxValue) value = maxValue; - if (value < minValue) value = minValue; - - int valueRange = maxValue - minValue; - if (valueRange <= 0) valueRange = 1; - - int sliderSize = GuiGetStyle(SCROLLBAR, SCROLL_SLIDER_SIZE); - if (sliderSize < 1) sliderSize = 1; // TODO: Consider a minimum slider size - - // Calculate rectangles for all of the components - arrowUpLeft = RAYGUI_CLITERAL(Rectangle){ - (float)bounds.x + GuiGetStyle(SCROLLBAR, BORDER_WIDTH), - (float)bounds.y + GuiGetStyle(SCROLLBAR, BORDER_WIDTH), - (float)spinnerSize, (float)spinnerSize }; - - if (isVertical) - { - arrowDownRight = RAYGUI_CLITERAL(Rectangle){ (float)bounds.x + GuiGetStyle(SCROLLBAR, BORDER_WIDTH), (float)bounds.y + bounds.height - spinnerSize - GuiGetStyle(SCROLLBAR, BORDER_WIDTH), (float)spinnerSize, (float)spinnerSize }; - scrollbar = RAYGUI_CLITERAL(Rectangle){ bounds.x + GuiGetStyle(SCROLLBAR, BORDER_WIDTH) + GuiGetStyle(SCROLLBAR, SCROLL_PADDING), arrowUpLeft.y + arrowUpLeft.height, bounds.width - 2*(GuiGetStyle(SCROLLBAR, BORDER_WIDTH) + GuiGetStyle(SCROLLBAR, SCROLL_PADDING)), bounds.height - arrowUpLeft.height - arrowDownRight.height - 2*GuiGetStyle(SCROLLBAR, BORDER_WIDTH) }; - - // Make sure the slider won't get outside of the scrollbar - sliderSize = (sliderSize >= scrollbar.height)? ((int)scrollbar.height - 2) : sliderSize; - slider = RAYGUI_CLITERAL(Rectangle){ - bounds.x + GuiGetStyle(SCROLLBAR, BORDER_WIDTH) + GuiGetStyle(SCROLLBAR, SCROLL_SLIDER_PADDING), - scrollbar.y + (int)(((float)(value - minValue)/valueRange)*(scrollbar.height - sliderSize)), - bounds.width - 2*(GuiGetStyle(SCROLLBAR, BORDER_WIDTH) + GuiGetStyle(SCROLLBAR, SCROLL_SLIDER_PADDING)), - (float)sliderSize }; - } - else // horizontal - { - arrowDownRight = RAYGUI_CLITERAL(Rectangle){ (float)bounds.x + bounds.width - spinnerSize - GuiGetStyle(SCROLLBAR, BORDER_WIDTH), (float)bounds.y + GuiGetStyle(SCROLLBAR, BORDER_WIDTH), (float)spinnerSize, (float)spinnerSize }; - scrollbar = RAYGUI_CLITERAL(Rectangle){ arrowUpLeft.x + arrowUpLeft.width, bounds.y + GuiGetStyle(SCROLLBAR, BORDER_WIDTH) + GuiGetStyle(SCROLLBAR, SCROLL_PADDING), bounds.width - arrowUpLeft.width - arrowDownRight.width - 2*GuiGetStyle(SCROLLBAR, BORDER_WIDTH), bounds.height - 2*(GuiGetStyle(SCROLLBAR, BORDER_WIDTH) + GuiGetStyle(SCROLLBAR, SCROLL_PADDING)) }; - - // Make sure the slider won't get outside of the scrollbar - sliderSize = (sliderSize >= scrollbar.width)? ((int)scrollbar.width - 2) : sliderSize; - slider = RAYGUI_CLITERAL(Rectangle){ - scrollbar.x + (int)(((float)(value - minValue)/valueRange)*(scrollbar.width - sliderSize)), - bounds.y + GuiGetStyle(SCROLLBAR, BORDER_WIDTH) + GuiGetStyle(SCROLLBAR, SCROLL_SLIDER_PADDING), - (float)sliderSize, - bounds.height - 2*(GuiGetStyle(SCROLLBAR, BORDER_WIDTH) + GuiGetStyle(SCROLLBAR, SCROLL_SLIDER_PADDING)) }; - } - - // Update control - //-------------------------------------------------------------------- - if ((state != STATE_DISABLED) && !guiLocked) - { - Vector2 mousePoint = GetMousePosition(); - - if (guiControlExclusiveMode) // Allows to keep dragging outside of bounds - { - if (IsMouseButtonDown(MOUSE_LEFT_BUTTON) && - !CheckCollisionPointRec(mousePoint, arrowUpLeft) && - !CheckCollisionPointRec(mousePoint, arrowDownRight)) - { - if (CHECK_BOUNDS_ID(bounds, guiControlExclusiveRec)) - { - state = STATE_PRESSED; - - if (isVertical) value = (int)(((float)(mousePoint.y - scrollbar.y - slider.height/2)*valueRange)/(scrollbar.height - slider.height) + minValue); - else value = (int)(((float)(mousePoint.x - scrollbar.x - slider.width/2)*valueRange)/(scrollbar.width - slider.width) + minValue); - } - } - else - { - guiControlExclusiveMode = false; - guiControlExclusiveRec = RAYGUI_CLITERAL(Rectangle){ 0, 0, 0, 0 }; - } - } - else if (CheckCollisionPointRec(mousePoint, bounds)) - { - state = STATE_FOCUSED; - - // Handle mouse wheel - int wheel = (int)GetMouseWheelMove(); - if (wheel != 0) value += wheel; - - // Handle mouse button down - if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) - { - guiControlExclusiveMode = true; - guiControlExclusiveRec = bounds; // Store bounds as an identifier when dragging starts - - // Check arrows click - if (CheckCollisionPointRec(mousePoint, arrowUpLeft)) value -= valueRange/GuiGetStyle(SCROLLBAR, SCROLL_SPEED); - else if (CheckCollisionPointRec(mousePoint, arrowDownRight)) value += valueRange/GuiGetStyle(SCROLLBAR, SCROLL_SPEED); - else if (!CheckCollisionPointRec(mousePoint, slider)) - { - // If click on scrollbar position but not on slider, place slider directly on that position - if (isVertical) value = (int)(((float)(mousePoint.y - scrollbar.y - slider.height/2)*valueRange)/(scrollbar.height - slider.height) + minValue); - else value = (int)(((float)(mousePoint.x - scrollbar.x - slider.width/2)*valueRange)/(scrollbar.width - slider.width) + minValue); - } - - state = STATE_PRESSED; - } - - // Keyboard control on mouse hover scrollbar - /* - if (isVertical) - { - if (IsKeyDown(KEY_DOWN)) value += 5; - else if (IsKeyDown(KEY_UP)) value -= 5; - } - else - { - if (IsKeyDown(KEY_RIGHT)) value += 5; - else if (IsKeyDown(KEY_LEFT)) value -= 5; - } - */ - } - - // Normalize value - if (value > maxValue) value = maxValue; - if (value < minValue) value = minValue; - } - //-------------------------------------------------------------------- - - // Draw control - //-------------------------------------------------------------------- - GuiDrawRectangle(bounds, GuiGetStyle(SCROLLBAR, BORDER_WIDTH), GetColor(GuiGetStyle(LISTVIEW, BORDER + state*3)), GetColor(GuiGetStyle(DEFAULT, BORDER_COLOR_DISABLED))); // Draw the background - - GuiDrawRectangle(scrollbar, 0, BLANK, GetColor(GuiGetStyle(BUTTON, BASE_COLOR_NORMAL))); // Draw the scrollbar active area background - GuiDrawRectangle(slider, 0, BLANK, GetColor(GuiGetStyle(SLIDER, BORDER + state*3))); // Draw the slider bar - - // Draw arrows (using icon if available) - if (GuiGetStyle(SCROLLBAR, ARROWS_VISIBLE)) - { -#if defined(RAYGUI_NO_ICONS) - GuiDrawText(isVertical? "^" : "<", - RAYGUI_CLITERAL(Rectangle){ arrowUpLeft.x, arrowUpLeft.y, isVertical? bounds.width : bounds.height, isVertical? bounds.width : bounds.height }, - TEXT_ALIGN_CENTER, GetColor(GuiGetStyle(DROPDOWNBOX, TEXT + (state*3)))); - GuiDrawText(isVertical? "v" : ">", - RAYGUI_CLITERAL(Rectangle){ arrowDownRight.x, arrowDownRight.y, isVertical? bounds.width : bounds.height, isVertical? bounds.width : bounds.height }, - TEXT_ALIGN_CENTER, GetColor(GuiGetStyle(DROPDOWNBOX, TEXT + (state*3)))); -#else - GuiDrawText(isVertical? "#121#" : "#118#", - RAYGUI_CLITERAL(Rectangle){ arrowUpLeft.x, arrowUpLeft.y, isVertical? bounds.width : bounds.height, isVertical? bounds.width : bounds.height }, - TEXT_ALIGN_CENTER, GetColor(GuiGetStyle(SCROLLBAR, TEXT + state*3))); // ICON_ARROW_UP_FILL / ICON_ARROW_LEFT_FILL - GuiDrawText(isVertical? "#120#" : "#119#", - RAYGUI_CLITERAL(Rectangle){ arrowDownRight.x, arrowDownRight.y, isVertical? bounds.width : bounds.height, isVertical? bounds.width : bounds.height }, - TEXT_ALIGN_CENTER, GetColor(GuiGetStyle(SCROLLBAR, TEXT + state*3))); // ICON_ARROW_DOWN_FILL / ICON_ARROW_RIGHT_FILL -#endif - } - //-------------------------------------------------------------------- - - return value; -} - -// Color fade-in or fade-out, alpha goes from 0.0f to 1.0f -// WARNING: It multiplies current alpha by alpha scale factor -static Color GuiFade(Color color, float alpha) -{ - if (alpha < 0.0f) alpha = 0.0f; - else if (alpha > 1.0f) alpha = 1.0f; - - Color result = { color.r, color.g, color.b, (unsigned char)(color.a*alpha) }; - - return result; -} - -#if defined(RAYGUI_STANDALONE) -// Returns a Color struct from hexadecimal value -static Color GetColor(int hexValue) -{ - Color color; - - color.r = (unsigned char)(hexValue >> 24) & 0xFF; - color.g = (unsigned char)(hexValue >> 16) & 0xFF; - color.b = (unsigned char)(hexValue >> 8) & 0xFF; - color.a = (unsigned char)hexValue & 0xFF; - - return color; -} - -// Returns hexadecimal value for a Color -static int ColorToInt(Color color) -{ - return (((int)color.r << 24) | ((int)color.g << 16) | ((int)color.b << 8) | (int)color.a); -} - -// Check if point is inside rectangle -static bool CheckCollisionPointRec(Vector2 point, Rectangle rec) -{ - bool collision = false; - - if ((point.x >= rec.x) && (point.x <= (rec.x + rec.width)) && - (point.y >= rec.y) && (point.y <= (rec.y + rec.height))) collision = true; - - return collision; -} - -// Formatting of text with variables to 'embed' -static const char *TextFormat(const char *text, ...) -{ - #if !defined(RAYGUI_TEXTFORMAT_MAX_SIZE) - #define RAYGUI_TEXTFORMAT_MAX_SIZE 256 - #endif - - static char buffer[RAYGUI_TEXTFORMAT_MAX_SIZE]; - - va_list args; - va_start(args, text); - vsprintf(buffer, text, args); - va_end(args); - - return buffer; -} - -// Draw rectangle with vertical gradient fill color -// NOTE: This function is only used by GuiColorPicker() -static void DrawRectangleGradientV(int posX, int posY, int width, int height, Color color1, Color color2) -{ - Rectangle bounds = { (float)posX, (float)posY, (float)width, (float)height }; - DrawRectangleGradientEx(bounds, color1, color2, color2, color1); -} - -// Split string into multiple strings -const char **TextSplit(const char *text, char delimiter, int *count) -{ - // NOTE: Current implementation returns a copy of the provided string with '\0' (string end delimiter) - // inserted between strings defined by "delimiter" parameter. No memory is dynamically allocated, - // all used memory is static... it has some limitations: - // 1. Maximum number of possible split strings is set by RAYGUI_TEXTSPLIT_MAX_ITEMS - // 2. Maximum size of text to split is RAYGUI_TEXTSPLIT_MAX_TEXT_SIZE - - #if !defined(RAYGUI_TEXTSPLIT_MAX_ITEMS) - #define RAYGUI_TEXTSPLIT_MAX_ITEMS 128 - #endif - #if !defined(RAYGUI_TEXTSPLIT_MAX_TEXT_SIZE) - #define RAYGUI_TEXTSPLIT_MAX_TEXT_SIZE 1024 - #endif - - static const char *result[RAYGUI_TEXTSPLIT_MAX_ITEMS] = { NULL }; - static char buffer[RAYGUI_TEXTSPLIT_MAX_TEXT_SIZE] = { 0 }; - memset(buffer, 0, RAYGUI_TEXTSPLIT_MAX_TEXT_SIZE); - - result[0] = buffer; - int counter = 0; - - if (text != NULL) - { - counter = 1; - - // Count how many substrings we have on text and point to every one - for (int i = 0; i < RAYGUI_TEXTSPLIT_MAX_TEXT_SIZE; i++) - { - buffer[i] = text[i]; - if (buffer[i] == '\0') break; - else if (buffer[i] == delimiter) - { - buffer[i] = '\0'; // Set an end of string at this point - result[counter] = buffer + i + 1; - counter++; - - if (counter == RAYGUI_TEXTSPLIT_MAX_ITEMS) break; - } - } - } - - *count = counter; - return result; -} - -// Get integer value from text -// NOTE: This function replaces atoi() [stdlib.h] -static int TextToInteger(const char *text) -{ - int value = 0; - int sign = 1; - - if ((text[0] == '+') || (text[0] == '-')) - { - if (text[0] == '-') sign = -1; - text++; - } - - for (int i = 0; ((text[i] >= '0') && (text[i] <= '9')); ++i) value = value*10 + (int)(text[i] - '0'); - - return value*sign; -} - -// Get float value from text -// NOTE: This function replaces atof() [stdlib.h] -// WARNING: Only '.' character is understood as decimal point -static float TextToFloat(const char *text) -{ - float value = 0.0f; - float sign = 1.0f; - - if ((text[0] == '+') || (text[0] == '-')) - { - if (text[0] == '-') sign = -1.0f; - text++; - } - - int i = 0; - for (; ((text[i] >= '0') && (text[i] <= '9')); i++) value = value*10.0f + (float)(text[i] - '0'); - - if (text[i++] != '.') value *= sign; - else - { - float divisor = 10.0f; - for (; ((text[i] >= '0') && (text[i] <= '9')); i++) - { - value += ((float)(text[i] - '0'))/divisor; - divisor = divisor*10.0f; - } - } - - return value; -} - -// Encode codepoint into UTF-8 text (char array size returned as parameter) -static const char *CodepointToUTF8(int codepoint, int *byteSize) -{ - static char utf8[6] = { 0 }; - int size = 0; - - if (codepoint <= 0x7f) - { - utf8[0] = (char)codepoint; - size = 1; - } - else if (codepoint <= 0x7ff) - { - utf8[0] = (char)(((codepoint >> 6) & 0x1f) | 0xc0); - utf8[1] = (char)((codepoint & 0x3f) | 0x80); - size = 2; - } - else if (codepoint <= 0xffff) - { - utf8[0] = (char)(((codepoint >> 12) & 0x0f) | 0xe0); - utf8[1] = (char)(((codepoint >> 6) & 0x3f) | 0x80); - utf8[2] = (char)((codepoint & 0x3f) | 0x80); - size = 3; - } - else if (codepoint <= 0x10ffff) - { - utf8[0] = (char)(((codepoint >> 18) & 0x07) | 0xf0); - utf8[1] = (char)(((codepoint >> 12) & 0x3f) | 0x80); - utf8[2] = (char)(((codepoint >> 6) & 0x3f) | 0x80); - utf8[3] = (char)((codepoint & 0x3f) | 0x80); - size = 4; - } - - *byteSize = size; - - return utf8; -} - -// Get next codepoint in a UTF-8 encoded text, scanning until '\0' is found -// When a invalid UTF-8 byte is encountered we exit as soon as possible and a '?'(0x3f) codepoint is returned -// Total number of bytes processed are returned as a parameter -// NOTE: the standard says U+FFFD should be returned in case of errors -// but that character is not supported by the default font in raylib -static int GetCodepointNext(const char *text, int *codepointSize) -{ - const char *ptr = text; - int codepoint = 0x3f; // Codepoint (defaults to '?') - *codepointSize = 1; - - // Get current codepoint and bytes processed - if (0xf0 == (0xf8 & ptr[0])) - { - // 4 byte UTF-8 codepoint - if (((ptr[1] & 0xC0) ^ 0x80) || ((ptr[2] & 0xC0) ^ 0x80) || ((ptr[3] & 0xC0) ^ 0x80)) { return codepoint; } //10xxxxxx checks - codepoint = ((0x07 & ptr[0]) << 18) | ((0x3f & ptr[1]) << 12) | ((0x3f & ptr[2]) << 6) | (0x3f & ptr[3]); - *codepointSize = 4; - } - else if (0xe0 == (0xf0 & ptr[0])) - { - // 3 byte UTF-8 codepoint */ - if (((ptr[1] & 0xC0) ^ 0x80) || ((ptr[2] & 0xC0) ^ 0x80)) { return codepoint; } //10xxxxxx checks - codepoint = ((0x0f & ptr[0]) << 12) | ((0x3f & ptr[1]) << 6) | (0x3f & ptr[2]); - *codepointSize = 3; - } - else if (0xc0 == (0xe0 & ptr[0])) - { - // 2 byte UTF-8 codepoint - if ((ptr[1] & 0xC0) ^ 0x80) { return codepoint; } //10xxxxxx checks - codepoint = ((0x1f & ptr[0]) << 6) | (0x3f & ptr[1]); - *codepointSize = 2; - } - else if (0x00 == (0x80 & ptr[0])) - { - // 1 byte UTF-8 codepoint - codepoint = ptr[0]; - *codepointSize = 1; - } - - return codepoint; -} -#endif // RAYGUI_STANDALONE - -#endif // RAYGUI_IMPLEMENTATION diff --git a/third_party/raylib/include/raylib.h b/third_party/raylib/include/raylib.h deleted file mode 100644 index ea973ff1ba..0000000000 --- a/third_party/raylib/include/raylib.h +++ /dev/null @@ -1,1766 +0,0 @@ -/********************************************************************************************** -* -* raylib v5.6-dev - A simple and easy-to-use library to enjoy videogames programming (www.raylib.com) -* -* FEATURES: -* - NO external dependencies, all required libraries included with raylib -* - Multiplatform: Windows, Linux, FreeBSD, OpenBSD, NetBSD, DragonFly, -* MacOS, Haiku, Android, Raspberry Pi, DRM native, HTML5. -* - Written in plain C code (C99) in PascalCase/camelCase notation -* - Hardware accelerated with OpenGL (1.1, 2.1, 3.3, 4.3, ES2, ES3 - choose at compile) -* - Unique OpenGL abstraction layer (usable as standalone module): [rlgl] -* - Multiple Fonts formats supported (TTF, OTF, FNT, BDF, Sprite fonts) -* - Outstanding texture formats support, including compressed formats (DXT, ETC, ASTC) -* - Full 3d support for 3d Shapes, Models, Billboards, Heightmaps and more! -* - Flexible Materials system, supporting classic maps and PBR maps -* - Animated 3D models supported (skeletal bones animation) (IQM, M3D, GLTF) -* - Shaders support, including Model shaders and Postprocessing shaders -* - Powerful math module for Vector, Matrix and Quaternion operations: [raymath] -* - Audio loading and playing with streaming support (WAV, OGG, MP3, FLAC, QOA, XM, MOD) -* - VR stereo rendering with configurable HMD device parameters -* - Bindings to multiple programming languages available! -* -* NOTES: -* - One default Font is loaded on InitWindow()->LoadFontDefault() [core, text] -* - One default Texture2D is loaded on rlglInit(), 1x1 white pixel R8G8B8A8 [rlgl] (OpenGL 3.3 or ES2) -* - One default Shader is loaded on rlglInit()->rlLoadShaderDefault() [rlgl] (OpenGL 3.3 or ES2) -* - One default RenderBatch is loaded on rlglInit()->rlLoadRenderBatch() [rlgl] (OpenGL 3.3 or ES2) -* -* DEPENDENCIES (included): -* [rcore][GLFW] rglfw (Camilla Löwy - github.com/glfw/glfw) for window/context management and input -* [rcore][RGFW] rgfw (ColleagueRiley - github.com/ColleagueRiley/RGFW) for window/context management and input -* [rlgl] glad/glad_gles2 (David Herberth - github.com/Dav1dde/glad) for OpenGL 3.3 extensions loading -* [raudio] miniaudio (David Reid - github.com/mackron/miniaudio) for audio device/context management -* -* OPTIONAL DEPENDENCIES (included): -* [rcore] msf_gif (Miles Fogle) for GIF recording -* [rcore] sinfl (Micha Mettke) for DEFLATE decompression algorithm -* [rcore] sdefl (Micha Mettke) for DEFLATE compression algorithm -* [rcore] rprand (Ramon Snatamaria) for pseudo-random numbers generation -* [rtextures] qoi (Dominic Szablewski - https://phoboslab.org) for QOI image manage -* [rtextures] stb_image (Sean Barret) for images loading (BMP, TGA, PNG, JPEG, HDR...) -* [rtextures] stb_image_write (Sean Barret) for image writing (BMP, TGA, PNG, JPG) -* [rtextures] stb_image_resize2 (Sean Barret) for image resizing algorithms -* [rtextures] stb_perlin (Sean Barret) for Perlin Noise image generation -* [rtext] stb_truetype (Sean Barret) for ttf fonts loading -* [rtext] stb_rect_pack (Sean Barret) for rectangles packing -* [rmodels] par_shapes (Philip Rideout) for parametric 3d shapes generation -* [rmodels] tinyobj_loader_c (Syoyo Fujita) for models loading (OBJ, MTL) -* [rmodels] cgltf (Johannes Kuhlmann) for models loading (glTF) -* [rmodels] m3d (bzt) for models loading (M3D, https://bztsrc.gitlab.io/model3d) -* [rmodels] vox_loader (Johann Nadalutti) for models loading (VOX) -* [raudio] dr_wav (David Reid) for WAV audio file loading -* [raudio] dr_flac (David Reid) for FLAC audio file loading -* [raudio] dr_mp3 (David Reid) for MP3 audio file loading -* [raudio] stb_vorbis (Sean Barret) for OGG audio loading -* [raudio] jar_xm (Joshua Reisenauer) for XM audio module loading -* [raudio] jar_mod (Joshua Reisenauer) for MOD audio module loading -* [raudio] qoa (Dominic Szablewski - https://phoboslab.org) for QOA audio manage -* -* -* LICENSE: zlib/libpng -* -* raylib is licensed under an unmodified zlib/libpng license, which is an OSI-certified, -* BSD-like license that allows static linking with closed source software: -* -* Copyright (c) 2013-2024 Ramon Santamaria (@raysan5) -* -* This software is provided "as-is", without any express or implied warranty. In no event -* will the authors be held liable for any damages arising from the use of this software. -* -* Permission is granted to anyone to use this software for any purpose, including commercial -* applications, and to alter it and redistribute it freely, subject to the following restrictions: -* -* 1. The origin of this software must not be misrepresented; you must not claim that you -* wrote the original software. If you use this software in a product, an acknowledgment -* in the product documentation would be appreciated but is not required. -* -* 2. Altered source versions must be plainly marked as such, and must not be misrepresented -* as being the original software. -* -* 3. This notice may not be removed or altered from any source distribution. -* -**********************************************************************************************/ - -#ifndef RAYLIB_H -#define RAYLIB_H - -#include // Required for: va_list - Only used by TraceLogCallback - -#define RAYLIB_VERSION_MAJOR 5 -#define RAYLIB_VERSION_MINOR 6 -#define RAYLIB_VERSION_PATCH 0 -#define RAYLIB_VERSION "5.6-dev" - -// Function specifiers in case library is build/used as a shared library -// NOTE: Microsoft specifiers to tell compiler that symbols are imported/exported from a .dll -// NOTE: visibility("default") attribute makes symbols "visible" when compiled with -fvisibility=hidden -#if defined(_WIN32) - #if defined(__TINYC__) - #define __declspec(x) __attribute__((x)) - #endif - #if defined(BUILD_LIBTYPE_SHARED) - #define RLAPI __declspec(dllexport) // We are building the library as a Win32 shared library (.dll) - #elif defined(USE_LIBTYPE_SHARED) - #define RLAPI __declspec(dllimport) // We are using the library as a Win32 shared library (.dll) - #endif -#else - #if defined(BUILD_LIBTYPE_SHARED) - #define RLAPI __attribute__((visibility("default"))) // We are building as a Unix shared library (.so/.dylib) - #endif -#endif - -#ifndef RLAPI - #define RLAPI // Functions defined as 'extern' by default (implicit specifiers) -#endif - -//---------------------------------------------------------------------------------- -// Some basic Defines -//---------------------------------------------------------------------------------- -#ifndef PI - #define PI 3.14159265358979323846f -#endif -#ifndef DEG2RAD - #define DEG2RAD (PI/180.0f) -#endif -#ifndef RAD2DEG - #define RAD2DEG (180.0f/PI) -#endif - -// Allow custom memory allocators -// NOTE: Require recompiling raylib sources -#ifndef RL_MALLOC - #define RL_MALLOC(sz) malloc(sz) -#endif -#ifndef RL_CALLOC - #define RL_CALLOC(n,sz) calloc(n,sz) -#endif -#ifndef RL_REALLOC - #define RL_REALLOC(ptr,sz) realloc(ptr,sz) -#endif -#ifndef RL_FREE - #define RL_FREE(ptr) free(ptr) -#endif - -// NOTE: MSVC C++ compiler does not support compound literals (C99 feature) -// Plain structures in C++ (without constructors) can be initialized with { } -// This is called aggregate initialization (C++11 feature) -#if defined(__cplusplus) - #define CLITERAL(type) type -#else - #define CLITERAL(type) (type) -#endif - -// Some compilers (mostly macos clang) default to C++98, -// where aggregate initialization can't be used -// So, give a more clear error stating how to fix this -#if !defined(_MSC_VER) && (defined(__cplusplus) && __cplusplus < 201103L) - #error "C++11 or later is required. Add -std=c++11" -#endif - -// NOTE: We set some defines with some data types declared by raylib -// Other modules (raymath, rlgl) also require some of those types, so, -// to be able to use those other modules as standalone (not depending on raylib) -// this defines are very useful for internal check and avoid type (re)definitions -#define RL_COLOR_TYPE -#define RL_RECTANGLE_TYPE -#define RL_VECTOR2_TYPE -#define RL_VECTOR3_TYPE -#define RL_VECTOR4_TYPE -#define RL_QUATERNION_TYPE -#define RL_MATRIX_TYPE - -// Some Basic Colors -// NOTE: Custom raylib color palette for amazing visuals on WHITE background -#define _rl_LIGHTGRAY CLITERAL(Color){ 200, 200, 200, 255 } // Light Gray -#define _rl_GRAY CLITERAL(Color){ 130, 130, 130, 255 } // Gray -#define _rl_DARKGRAY CLITERAL(Color){ 80, 80, 80, 255 } // Dark Gray -#define _rl_YELLOW CLITERAL(Color){ 253, 249, 0, 255 } // Yellow -#define _rl_GOLD CLITERAL(Color){ 255, 203, 0, 255 } // Gold -#define _rl_ORANGE CLITERAL(Color){ 255, 161, 0, 255 } // Orange -#define _rl_PINK CLITERAL(Color){ 255, 109, 194, 255 } // Pink -#define _rl_RED CLITERAL(Color){ 230, 41, 55, 255 } // Red -#define _rl_MAROON CLITERAL(Color){ 190, 33, 55, 255 } // Maroon -#define _rl_GREEN CLITERAL(Color){ 0, 228, 48, 255 } // Green -#define _rl_LIME CLITERAL(Color){ 0, 158, 47, 255 } // Lime -#define _rl_DARKGREEN CLITERAL(Color){ 0, 117, 44, 255 } // Dark Green -#define _rl_SKYBLUE CLITERAL(Color){ 102, 191, 255, 255 } // Sky Blue -#define _rl_BLUE CLITERAL(Color){ 0, 121, 241, 255 } // Blue -#define _rl_DARKBLUE CLITERAL(Color){ 0, 82, 172, 255 } // Dark Blue -#define _rl_PURPLE CLITERAL(Color){ 200, 122, 255, 255 } // Purple -#define _rl_VIOLET CLITERAL(Color){ 135, 60, 190, 255 } // Violet -#define _rl_DARKPURPLE CLITERAL(Color){ 112, 31, 126, 255 } // Dark Purple -#define _rl_BEIGE CLITERAL(Color){ 211, 176, 131, 255 } // Beige -#define _rl_BROWN CLITERAL(Color){ 127, 106, 79, 255 } // Brown -#define _rl_DARKBROWN CLITERAL(Color){ 76, 63, 47, 255 } // Dark Brown - -#define _rl_WHITE CLITERAL(Color){ 255, 255, 255, 255 } // White -#define _rl_BLACK CLITERAL(Color){ 0, 0, 0, 255 } // Black -#define _rl_BLANK CLITERAL(Color){ 0, 0, 0, 0 } // Blank (Transparent) -#define _rl_MAGENTA CLITERAL(Color){ 255, 0, 255, 255 } // Magenta -#define _rl_RAYWHITE CLITERAL(Color){ 245, 245, 245, 255 } // My own White (raylib logo) - -#ifndef OPENPILOT_RAYLIB - #define LIGHTGRAY _rl_LIGHTGRAY - #define GRAY _rl_GRAY - #define DARKGRAY _rl_DARKGRAY - #define YELLOW _rl_YELLOW - #define GOLD _rl_GOLD - #define ORANGE _rl_ORANGE - #define PINK _rl_PINK - #define RED _rl_RED - #define MAROON _rl_MAROON - #define GREEN _rl_GREEN - #define LIME _rl_LIME - #define DARKGREEN _rl_DARKGREEN - #define SKYBLUE _rl_SKYBLUE - #define BLUE _rl_BLUE - #define DARKBLUE _rl_DARKBLUE - #define PURPLE _rl_PURPLE - #define VIOLET _rl_VIOLET - #define DARKPURPLE _rl_DARKBLUE - #define BEIGE _rl_BEIGE - #define BROWN _rl_BROWN - #define DARKBROWN _rl_DARKBROWN - - #define WHITE _rl_WHITE - #define BLACK _rl_BLACK - #define BLANK _rl_BLANK - #define MAGENTA _rl_MAGENTA - #define RAYWHITE _rl_RAYWHITE -#else - #define RAYLIB_LIGHTGRAY _rl_LIGHTGRAY - #define RAYLIB_GRAY _rl_GRAY - #define RAYLIB_DARKGRAY _rl_DARKGRAY - #define RAYLIB_YELLOW _rl_YELLOW - #define RAYLIB_GOLD _rl_GOLD - #define RAYLIB_ORANGE _rl_ORANGE - #define RAYLIB_PINK _rl_PINK - #define RAYLIB_RED _rl_RED - #define RAYLIB_MAROON _rl_MAROON - #define RAYLIB_GREEN _rl_GREEN - #define RAYLIB_LIME _rl_LIME - #define RAYLIB_DARKGREEN _rl_DARKGREEN - #define RAYLIB_SKYBLUE _rl_SKYBLUE - #define RAYLIB_BLUE _rl_BLUE - #define RAYLIB_DARKBLUE _rl_DARKBLUE - #define RAYLIB_PURPLE _rl_PURPLE - #define RAYLIB_VIOLET _rl_VIOLET - #define RAYLIB_DARKPURPLE _rl_DARKBLUE - #define RAYLIB_BEIGE _rl_BEIGE - #define RAYLIB_BROWN _rl_BROWN - #define RAYLIB_DARKBROWN _rl_DARKBROWN - - #define RAYLIB_WHITE _rl_WHITE - #define RAYLIB_BLACK _rl_BLACK - #define RAYLIB_BLANK _rl_BLANK - #define RAYLIB_MAGENTA _rl_MAGENTA - #define RAYLIB_RAYWHITE _rl_RAYWHITE -#endif - -//---------------------------------------------------------------------------------- -// Structures Definition -//---------------------------------------------------------------------------------- -// Boolean type -#if (defined(__STDC__) && __STDC_VERSION__ >= 199901L) || (defined(_MSC_VER) && _MSC_VER >= 1800) - #include -#elif !defined(__cplusplus) && !defined(bool) - typedef enum bool { false = 0, true = !false } bool; - #define RL_BOOL_TYPE -#endif - -// Vector2, 2 components -typedef struct Vector2 { - float x; // Vector x component - float y; // Vector y component -} Vector2; - -// Vector3, 3 components -typedef struct Vector3 { - float x; // Vector x component - float y; // Vector y component - float z; // Vector z component -} Vector3; - -// Vector4, 4 components -typedef struct Vector4 { - float x; // Vector x component - float y; // Vector y component - float z; // Vector z component - float w; // Vector w component -} Vector4; - -// Quaternion, 4 components (Vector4 alias) -typedef Vector4 Quaternion; - -// Matrix, 4x4 components, column major, OpenGL style, right-handed -typedef struct Matrix { - float m0, m4, m8, m12; // Matrix first row (4 components) - float m1, m5, m9, m13; // Matrix second row (4 components) - float m2, m6, m10, m14; // Matrix third row (4 components) - float m3, m7, m11, m15; // Matrix fourth row (4 components) -} Matrix; - -// Color, 4 components, R8G8B8A8 (32bit) -typedef struct Color { - unsigned char r; // Color red value - unsigned char g; // Color green value - unsigned char b; // Color blue value - unsigned char a; // Color alpha value -} Color; - -// Rectangle, 4 components -typedef struct Rectangle { - float x; // Rectangle top-left corner position x - float y; // Rectangle top-left corner position y - float width; // Rectangle width - float height; // Rectangle height -} Rectangle; - -// Image, pixel data stored in CPU memory (RAM) -typedef struct Image { - void *data; // Image raw data - int width; // Image base width - int height; // Image base height - int mipmaps; // Mipmap levels, 1 by default - int format; // Data format (PixelFormat type) -} Image; - -// Texture, tex data stored in GPU memory (VRAM) -typedef struct Texture { - unsigned int id; // OpenGL texture id - int width; // Texture base width - int height; // Texture base height - int mipmaps; // Mipmap levels, 1 by default - int format; // Data format (PixelFormat type) -} Texture; - -// Texture2D, same as Texture -typedef Texture Texture2D; - -// TextureCubemap, same as Texture -typedef Texture TextureCubemap; - -// RenderTexture, fbo for texture rendering -typedef struct RenderTexture { - unsigned int id; // OpenGL framebuffer object id - Texture texture; // Color buffer attachment texture - Texture depth; // Depth buffer attachment texture -} RenderTexture; - -// RenderTexture2D, same as RenderTexture -typedef RenderTexture RenderTexture2D; - -// NPatchInfo, n-patch layout info -typedef struct NPatchInfo { - Rectangle source; // Texture source rectangle - int left; // Left border offset - int top; // Top border offset - int right; // Right border offset - int bottom; // Bottom border offset - int layout; // Layout of the n-patch: 3x3, 1x3 or 3x1 -} NPatchInfo; - -// GlyphInfo, font characters glyphs info -typedef struct GlyphInfo { - int value; // Character value (Unicode) - int offsetX; // Character offset X when drawing - int offsetY; // Character offset Y when drawing - int advanceX; // Character advance position X - Image image; // Character image data -} GlyphInfo; - -// Font, font texture and GlyphInfo array data -typedef struct Font { - int baseSize; // Base size (default chars height) - int glyphCount; // Number of glyph characters - int glyphPadding; // Padding around the glyph characters - Texture2D texture; // Texture atlas containing the glyphs - Rectangle *recs; // Rectangles in texture for the glyphs - GlyphInfo *glyphs; // Glyphs info data -} Font; - -// Camera, defines position/orientation in 3d space -typedef struct Camera3D { - Vector3 position; // Camera position - Vector3 target; // Camera target it looks-at - Vector3 up; // Camera up vector (rotation over its axis) - float fovy; // Camera field-of-view aperture in Y (degrees) in perspective, used as near plane width in orthographic - int projection; // Camera projection: CAMERA_PERSPECTIVE or CAMERA_ORTHOGRAPHIC -} Camera3D; - -typedef Camera3D Camera; // Camera type fallback, defaults to Camera3D - -// Camera2D, defines position/orientation in 2d space -typedef struct Camera2D { - Vector2 offset; // Camera offset (displacement from target) - Vector2 target; // Camera target (rotation and zoom origin) - float rotation; // Camera rotation in degrees - float zoom; // Camera zoom (scaling), should be 1.0f by default -} Camera2D; - -// Mesh, vertex data and vao/vbo -typedef struct Mesh { - int vertexCount; // Number of vertices stored in arrays - int triangleCount; // Number of triangles stored (indexed or not) - - // Vertex attributes data - float *vertices; // Vertex position (XYZ - 3 components per vertex) (shader-location = 0) - float *texcoords; // Vertex texture coordinates (UV - 2 components per vertex) (shader-location = 1) - float *texcoords2; // Vertex texture second coordinates (UV - 2 components per vertex) (shader-location = 5) - float *normals; // Vertex normals (XYZ - 3 components per vertex) (shader-location = 2) - float *tangents; // Vertex tangents (XYZW - 4 components per vertex) (shader-location = 4) - unsigned char *colors; // Vertex colors (RGBA - 4 components per vertex) (shader-location = 3) - unsigned short *indices; // Vertex indices (in case vertex data comes indexed) - - // Animation vertex data - float *animVertices; // Animated vertex positions (after bones transformations) - float *animNormals; // Animated normals (after bones transformations) - unsigned char *boneIds; // Vertex bone ids, max 255 bone ids, up to 4 bones influence by vertex (skinning) (shader-location = 6) - float *boneWeights; // Vertex bone weight, up to 4 bones influence by vertex (skinning) (shader-location = 7) - Matrix *boneMatrices; // Bones animated transformation matrices - int boneCount; // Number of bones - - // OpenGL identifiers - unsigned int vaoId; // OpenGL Vertex Array Object id - unsigned int *vboId; // OpenGL Vertex Buffer Objects id (default vertex data) -} Mesh; - -// Shader -typedef struct Shader { - unsigned int id; // Shader program id - int *locs; // Shader locations array (RL_MAX_SHADER_LOCATIONS) -} Shader; - -// MaterialMap -typedef struct MaterialMap { - Texture2D texture; // Material map texture - Color color; // Material map color - float value; // Material map value -} MaterialMap; - -// Material, includes shader and maps -typedef struct Material { - Shader shader; // Material shader - MaterialMap *maps; // Material maps array (MAX_MATERIAL_MAPS) - float params[4]; // Material generic parameters (if required) -} Material; - -// Transform, vertex transformation data -typedef struct Transform { - Vector3 translation; // Translation - Quaternion rotation; // Rotation - Vector3 scale; // Scale -} Transform; - -// Bone, skeletal animation bone -typedef struct BoneInfo { - char name[32]; // Bone name - int parent; // Bone parent -} BoneInfo; - -// Model, meshes, materials and animation data -typedef struct Model { - Matrix transform; // Local transform matrix - - int meshCount; // Number of meshes - int materialCount; // Number of materials - Mesh *meshes; // Meshes array - Material *materials; // Materials array - int *meshMaterial; // Mesh material number - - // Animation data - int boneCount; // Number of bones - BoneInfo *bones; // Bones information (skeleton) - Transform *bindPose; // Bones base transformation (pose) -} Model; - -// ModelAnimation -typedef struct ModelAnimation { - int boneCount; // Number of bones - int frameCount; // Number of animation frames - BoneInfo *bones; // Bones information (skeleton) - Transform **framePoses; // Poses array by frame - char name[32]; // Animation name -} ModelAnimation; - -// Ray, ray for raycasting -typedef struct Ray { - Vector3 position; // Ray position (origin) - Vector3 direction; // Ray direction (normalized) -} Ray; - -// RayCollision, ray hit information -typedef struct RayCollision { - bool hit; // Did the ray hit something? - float distance; // Distance to the nearest hit - Vector3 point; // Point of the nearest hit - Vector3 normal; // Surface normal of hit -} RayCollision; - -// BoundingBox -typedef struct BoundingBox { - Vector3 min; // Minimum vertex box-corner - Vector3 max; // Maximum vertex box-corner -} BoundingBox; - -// Wave, audio wave data -typedef struct Wave { - unsigned int frameCount; // Total number of frames (considering channels) - unsigned int sampleRate; // Frequency (samples per second) - unsigned int sampleSize; // Bit depth (bits per sample): 8, 16, 32 (24 not supported) - unsigned int channels; // Number of channels (1-mono, 2-stereo, ...) - void *data; // Buffer data pointer -} Wave; - -// Opaque structs declaration -// NOTE: Actual structs are defined internally in raudio module -typedef struct rAudioBuffer rAudioBuffer; -typedef struct rAudioProcessor rAudioProcessor; - -// AudioStream, custom audio stream -typedef struct AudioStream { - rAudioBuffer *buffer; // Pointer to internal data used by the audio system - rAudioProcessor *processor; // Pointer to internal data processor, useful for audio effects - - unsigned int sampleRate; // Frequency (samples per second) - unsigned int sampleSize; // Bit depth (bits per sample): 8, 16, 32 (24 not supported) - unsigned int channels; // Number of channels (1-mono, 2-stereo, ...) -} AudioStream; - -// Sound -typedef struct Sound { - AudioStream stream; // Audio stream - unsigned int frameCount; // Total number of frames (considering channels) -} Sound; - -// Music, audio stream, anything longer than ~10 seconds should be streamed -typedef struct Music { - AudioStream stream; // Audio stream - unsigned int frameCount; // Total number of frames (considering channels) - bool looping; // Music looping enable - - int ctxType; // Type of music context (audio filetype) - void *ctxData; // Audio context data, depends on type -} Music; - -// VrDeviceInfo, Head-Mounted-Display device parameters -typedef struct VrDeviceInfo { - int hResolution; // Horizontal resolution in pixels - int vResolution; // Vertical resolution in pixels - float hScreenSize; // Horizontal size in meters - float vScreenSize; // Vertical size in meters - float eyeToScreenDistance; // Distance between eye and display in meters - float lensSeparationDistance; // Lens separation distance in meters - float interpupillaryDistance; // IPD (distance between pupils) in meters - float lensDistortionValues[4]; // Lens distortion constant parameters - float chromaAbCorrection[4]; // Chromatic aberration correction parameters -} VrDeviceInfo; - -// VrStereoConfig, VR stereo rendering configuration for simulator -typedef struct VrStereoConfig { - Matrix projection[2]; // VR projection matrices (per eye) - Matrix viewOffset[2]; // VR view offset matrices (per eye) - float leftLensCenter[2]; // VR left lens center - float rightLensCenter[2]; // VR right lens center - float leftScreenCenter[2]; // VR left screen center - float rightScreenCenter[2]; // VR right screen center - float scale[2]; // VR distortion scale - float scaleIn[2]; // VR distortion scale in -} VrStereoConfig; - -// File path list -typedef struct FilePathList { - unsigned int capacity; // Filepaths max entries - unsigned int count; // Filepaths entries count - char **paths; // Filepaths entries -} FilePathList; - -// Automation event -typedef struct AutomationEvent { - unsigned int frame; // Event frame - unsigned int type; // Event type (AutomationEventType) - int params[4]; // Event parameters (if required) -} AutomationEvent; - -// Automation event list -typedef struct AutomationEventList { - unsigned int capacity; // Events max entries (MAX_AUTOMATION_EVENTS) - unsigned int count; // Events entries count - AutomationEvent *events; // Events entries -} AutomationEventList; - -//---------------------------------------------------------------------------------- -// Enumerators Definition -//---------------------------------------------------------------------------------- -// System/Window config flags -// NOTE: Every bit registers one state (use it with bit masks) -// By default all flags are set to 0 -typedef enum { - FLAG_VSYNC_HINT = 0x00000040, // Set to try enabling V-Sync on GPU - FLAG_FULLSCREEN_MODE = 0x00000002, // Set to run program in fullscreen - FLAG_WINDOW_RESIZABLE = 0x00000004, // Set to allow resizable window - FLAG_WINDOW_UNDECORATED = 0x00000008, // Set to disable window decoration (frame and buttons) - FLAG_WINDOW_HIDDEN = 0x00000080, // Set to hide window - FLAG_WINDOW_MINIMIZED = 0x00000200, // Set to minimize window (iconify) - FLAG_WINDOW_MAXIMIZED = 0x00000400, // Set to maximize window (expanded to monitor) - FLAG_WINDOW_UNFOCUSED = 0x00000800, // Set to window non focused - FLAG_WINDOW_TOPMOST = 0x00001000, // Set to window always on top - FLAG_WINDOW_ALWAYS_RUN = 0x00000100, // Set to allow windows running while minimized - FLAG_WINDOW_TRANSPARENT = 0x00000010, // Set to allow transparent framebuffer - FLAG_WINDOW_HIGHDPI = 0x00002000, // Set to support HighDPI - FLAG_WINDOW_MOUSE_PASSTHROUGH = 0x00004000, // Set to support mouse passthrough, only supported when FLAG_WINDOW_UNDECORATED - FLAG_BORDERLESS_WINDOWED_MODE = 0x00008000, // Set to run program in borderless windowed mode - FLAG_MSAA_4X_HINT = 0x00000020, // Set to try enabling MSAA 4X - FLAG_INTERLACED_HINT = 0x00010000 // Set to try enabling interlaced video format (for V3D) -} ConfigFlags; - -// Trace log level -// NOTE: Organized by priority level -typedef enum { - LOG_ALL = 0, // Display all logs - LOG_TRACE, // Trace logging, intended for internal use only - LOG_DEBUG, // Debug logging, used for internal debugging, it should be disabled on release builds - LOG_INFO, // Info logging, used for program execution info - LOG_WARNING, // Warning logging, used on recoverable failures - LOG_ERROR, // Error logging, used on unrecoverable failures - LOG_FATAL, // Fatal logging, used to abort program: exit(EXIT_FAILURE) - LOG_NONE // Disable logging -} TraceLogLevel; - -// Keyboard keys (US keyboard layout) -// NOTE: Use GetKeyPressed() to allow redefining -// required keys for alternative layouts -typedef enum { - KEY_NULL = 0, // Key: NULL, used for no key pressed - // Alphanumeric keys - KEY_APOSTROPHE = 39, // Key: ' - KEY_COMMA = 44, // Key: , - KEY_MINUS = 45, // Key: - - KEY_PERIOD = 46, // Key: . - KEY_SLASH = 47, // Key: / - KEY_ZERO = 48, // Key: 0 - KEY_ONE = 49, // Key: 1 - KEY_TWO = 50, // Key: 2 - KEY_THREE = 51, // Key: 3 - KEY_FOUR = 52, // Key: 4 - KEY_FIVE = 53, // Key: 5 - KEY_SIX = 54, // Key: 6 - KEY_SEVEN = 55, // Key: 7 - KEY_EIGHT = 56, // Key: 8 - KEY_NINE = 57, // Key: 9 - KEY_SEMICOLON = 59, // Key: ; - KEY_EQUAL = 61, // Key: = - KEY_A = 65, // Key: A | a - KEY_B = 66, // Key: B | b - KEY_C = 67, // Key: C | c - KEY_D = 68, // Key: D | d - KEY_E = 69, // Key: E | e - KEY_F = 70, // Key: F | f - KEY_G = 71, // Key: G | g - KEY_H = 72, // Key: H | h - KEY_I = 73, // Key: I | i - KEY_J = 74, // Key: J | j - KEY_K = 75, // Key: K | k - KEY_L = 76, // Key: L | l - KEY_M = 77, // Key: M | m - KEY_N = 78, // Key: N | n - KEY_O = 79, // Key: O | o - KEY_P = 80, // Key: P | p - KEY_Q = 81, // Key: Q | q - KEY_R = 82, // Key: R | r - KEY_S = 83, // Key: S | s - KEY_T = 84, // Key: T | t - KEY_U = 85, // Key: U | u - KEY_V = 86, // Key: V | v - KEY_W = 87, // Key: W | w - KEY_X = 88, // Key: X | x - KEY_Y = 89, // Key: Y | y - KEY_Z = 90, // Key: Z | z - KEY_LEFT_BRACKET = 91, // Key: [ - KEY_BACKSLASH = 92, // Key: '\' - KEY_RIGHT_BRACKET = 93, // Key: ] - KEY_GRAVE = 96, // Key: ` - // Function keys - KEY_SPACE = 32, // Key: Space - KEY_ESCAPE = 256, // Key: Esc - KEY_ENTER = 257, // Key: Enter - KEY_TAB = 258, // Key: Tab - KEY_BACKSPACE = 259, // Key: Backspace - KEY_INSERT = 260, // Key: Ins - KEY_DELETE = 261, // Key: Del - KEY_RIGHT = 262, // Key: Cursor right - KEY_LEFT = 263, // Key: Cursor left - KEY_DOWN = 264, // Key: Cursor down - KEY_UP = 265, // Key: Cursor up - KEY_PAGE_UP = 266, // Key: Page up - KEY_PAGE_DOWN = 267, // Key: Page down - KEY_HOME = 268, // Key: Home - KEY_END = 269, // Key: End - KEY_CAPS_LOCK = 280, // Key: Caps lock - KEY_SCROLL_LOCK = 281, // Key: Scroll down - KEY_NUM_LOCK = 282, // Key: Num lock - KEY_PRINT_SCREEN = 283, // Key: Print screen - KEY_PAUSE = 284, // Key: Pause - KEY_F1 = 290, // Key: F1 - KEY_F2 = 291, // Key: F2 - KEY_F3 = 292, // Key: F3 - KEY_F4 = 293, // Key: F4 - KEY_F5 = 294, // Key: F5 - KEY_F6 = 295, // Key: F6 - KEY_F7 = 296, // Key: F7 - KEY_F8 = 297, // Key: F8 - KEY_F9 = 298, // Key: F9 - KEY_F10 = 299, // Key: F10 - KEY_F11 = 300, // Key: F11 - KEY_F12 = 301, // Key: F12 - KEY_LEFT_SHIFT = 340, // Key: Shift left - KEY_LEFT_CONTROL = 341, // Key: Control left - KEY_LEFT_ALT = 342, // Key: Alt left - KEY_LEFT_SUPER = 343, // Key: Super left - KEY_RIGHT_SHIFT = 344, // Key: Shift right - KEY_RIGHT_CONTROL = 345, // Key: Control right - KEY_RIGHT_ALT = 346, // Key: Alt right - KEY_RIGHT_SUPER = 347, // Key: Super right - KEY_KB_MENU = 348, // Key: KB menu - // Keypad keys - KEY_KP_0 = 320, // Key: Keypad 0 - KEY_KP_1 = 321, // Key: Keypad 1 - KEY_KP_2 = 322, // Key: Keypad 2 - KEY_KP_3 = 323, // Key: Keypad 3 - KEY_KP_4 = 324, // Key: Keypad 4 - KEY_KP_5 = 325, // Key: Keypad 5 - KEY_KP_6 = 326, // Key: Keypad 6 - KEY_KP_7 = 327, // Key: Keypad 7 - KEY_KP_8 = 328, // Key: Keypad 8 - KEY_KP_9 = 329, // Key: Keypad 9 - KEY_KP_DECIMAL = 330, // Key: Keypad . - KEY_KP_DIVIDE = 331, // Key: Keypad / - KEY_KP_MULTIPLY = 332, // Key: Keypad * - KEY_KP_SUBTRACT = 333, // Key: Keypad - - KEY_KP_ADD = 334, // Key: Keypad + - KEY_KP_ENTER = 335, // Key: Keypad Enter - KEY_KP_EQUAL = 336, // Key: Keypad = - // Android key buttons - KEY_BACK = 4, // Key: Android back button - KEY_MENU = 5, // Key: Android menu button - KEY_VOLUME_UP = 24, // Key: Android volume up button - KEY_VOLUME_DOWN = 25 // Key: Android volume down button -} KeyboardKey; - -// Add backwards compatibility support for deprecated names -#define MOUSE_LEFT_BUTTON MOUSE_BUTTON_LEFT -#define MOUSE_RIGHT_BUTTON MOUSE_BUTTON_RIGHT -#define MOUSE_MIDDLE_BUTTON MOUSE_BUTTON_MIDDLE - -// Mouse buttons -typedef enum { - MOUSE_BUTTON_LEFT = 0, // Mouse button left - MOUSE_BUTTON_RIGHT = 1, // Mouse button right - MOUSE_BUTTON_MIDDLE = 2, // Mouse button middle (pressed wheel) - MOUSE_BUTTON_SIDE = 3, // Mouse button side (advanced mouse device) - MOUSE_BUTTON_EXTRA = 4, // Mouse button extra (advanced mouse device) - MOUSE_BUTTON_FORWARD = 5, // Mouse button forward (advanced mouse device) - MOUSE_BUTTON_BACK = 6, // Mouse button back (advanced mouse device) -} MouseButton; - -// Mouse cursor -typedef enum { - MOUSE_CURSOR_DEFAULT = 0, // Default pointer shape - MOUSE_CURSOR_ARROW = 1, // Arrow shape - MOUSE_CURSOR_IBEAM = 2, // Text writing cursor shape - MOUSE_CURSOR_CROSSHAIR = 3, // Cross shape - MOUSE_CURSOR_POINTING_HAND = 4, // Pointing hand cursor - MOUSE_CURSOR_RESIZE_EW = 5, // Horizontal resize/move arrow shape - MOUSE_CURSOR_RESIZE_NS = 6, // Vertical resize/move arrow shape - MOUSE_CURSOR_RESIZE_NWSE = 7, // Top-left to bottom-right diagonal resize/move arrow shape - MOUSE_CURSOR_RESIZE_NESW = 8, // The top-right to bottom-left diagonal resize/move arrow shape - MOUSE_CURSOR_RESIZE_ALL = 9, // The omnidirectional resize/move cursor shape - MOUSE_CURSOR_NOT_ALLOWED = 10 // The operation-not-allowed shape -} MouseCursor; - -// Gamepad buttons -typedef enum { - GAMEPAD_BUTTON_UNKNOWN = 0, // Unknown button, just for error checking - GAMEPAD_BUTTON_LEFT_FACE_UP, // Gamepad left DPAD up button - GAMEPAD_BUTTON_LEFT_FACE_RIGHT, // Gamepad left DPAD right button - GAMEPAD_BUTTON_LEFT_FACE_DOWN, // Gamepad left DPAD down button - GAMEPAD_BUTTON_LEFT_FACE_LEFT, // Gamepad left DPAD left button - GAMEPAD_BUTTON_RIGHT_FACE_UP, // Gamepad right button up (i.e. PS3: Triangle, Xbox: Y) - GAMEPAD_BUTTON_RIGHT_FACE_RIGHT, // Gamepad right button right (i.e. PS3: Circle, Xbox: B) - GAMEPAD_BUTTON_RIGHT_FACE_DOWN, // Gamepad right button down (i.e. PS3: Cross, Xbox: A) - GAMEPAD_BUTTON_RIGHT_FACE_LEFT, // Gamepad right button left (i.e. PS3: Square, Xbox: X) - GAMEPAD_BUTTON_LEFT_TRIGGER_1, // Gamepad top/back trigger left (first), it could be a trailing button - GAMEPAD_BUTTON_LEFT_TRIGGER_2, // Gamepad top/back trigger left (second), it could be a trailing button - GAMEPAD_BUTTON_RIGHT_TRIGGER_1, // Gamepad top/back trigger right (first), it could be a trailing button - GAMEPAD_BUTTON_RIGHT_TRIGGER_2, // Gamepad top/back trigger right (second), it could be a trailing button - GAMEPAD_BUTTON_MIDDLE_LEFT, // Gamepad center buttons, left one (i.e. PS3: Select) - GAMEPAD_BUTTON_MIDDLE, // Gamepad center buttons, middle one (i.e. PS3: PS, Xbox: XBOX) - GAMEPAD_BUTTON_MIDDLE_RIGHT, // Gamepad center buttons, right one (i.e. PS3: Start) - GAMEPAD_BUTTON_LEFT_THUMB, // Gamepad joystick pressed button left - GAMEPAD_BUTTON_RIGHT_THUMB // Gamepad joystick pressed button right -} GamepadButton; - -// Gamepad axis -typedef enum { - GAMEPAD_AXIS_LEFT_X = 0, // Gamepad left stick X axis - GAMEPAD_AXIS_LEFT_Y = 1, // Gamepad left stick Y axis - GAMEPAD_AXIS_RIGHT_X = 2, // Gamepad right stick X axis - GAMEPAD_AXIS_RIGHT_Y = 3, // Gamepad right stick Y axis - GAMEPAD_AXIS_LEFT_TRIGGER = 4, // Gamepad back trigger left, pressure level: [1..-1] - GAMEPAD_AXIS_RIGHT_TRIGGER = 5 // Gamepad back trigger right, pressure level: [1..-1] -} GamepadAxis; - -// Material map index -typedef enum { - MATERIAL_MAP_ALBEDO = 0, // Albedo material (same as: MATERIAL_MAP_DIFFUSE) - MATERIAL_MAP_METALNESS, // Metalness material (same as: MATERIAL_MAP_SPECULAR) - MATERIAL_MAP_NORMAL, // Normal material - MATERIAL_MAP_ROUGHNESS, // Roughness material - MATERIAL_MAP_OCCLUSION, // Ambient occlusion material - MATERIAL_MAP_EMISSION, // Emission material - MATERIAL_MAP_HEIGHT, // Heightmap material - MATERIAL_MAP_CUBEMAP, // Cubemap material (NOTE: Uses GL_TEXTURE_CUBE_MAP) - MATERIAL_MAP_IRRADIANCE, // Irradiance material (NOTE: Uses GL_TEXTURE_CUBE_MAP) - MATERIAL_MAP_PREFILTER, // Prefilter material (NOTE: Uses GL_TEXTURE_CUBE_MAP) - MATERIAL_MAP_BRDF // Brdf material -} MaterialMapIndex; - -#define MATERIAL_MAP_DIFFUSE MATERIAL_MAP_ALBEDO -#define MATERIAL_MAP_SPECULAR MATERIAL_MAP_METALNESS - -// Shader location index -typedef enum { - SHADER_LOC_VERTEX_POSITION = 0, // Shader location: vertex attribute: position - SHADER_LOC_VERTEX_TEXCOORD01, // Shader location: vertex attribute: texcoord01 - SHADER_LOC_VERTEX_TEXCOORD02, // Shader location: vertex attribute: texcoord02 - SHADER_LOC_VERTEX_NORMAL, // Shader location: vertex attribute: normal - SHADER_LOC_VERTEX_TANGENT, // Shader location: vertex attribute: tangent - SHADER_LOC_VERTEX_COLOR, // Shader location: vertex attribute: color - SHADER_LOC_MATRIX_MVP, // Shader location: matrix uniform: model-view-projection - SHADER_LOC_MATRIX_VIEW, // Shader location: matrix uniform: view (camera transform) - SHADER_LOC_MATRIX_PROJECTION, // Shader location: matrix uniform: projection - SHADER_LOC_MATRIX_MODEL, // Shader location: matrix uniform: model (transform) - SHADER_LOC_MATRIX_NORMAL, // Shader location: matrix uniform: normal - SHADER_LOC_VECTOR_VIEW, // Shader location: vector uniform: view - SHADER_LOC_COLOR_DIFFUSE, // Shader location: vector uniform: diffuse color - SHADER_LOC_COLOR_SPECULAR, // Shader location: vector uniform: specular color - SHADER_LOC_COLOR_AMBIENT, // Shader location: vector uniform: ambient color - SHADER_LOC_MAP_ALBEDO, // Shader location: sampler2d texture: albedo (same as: SHADER_LOC_MAP_DIFFUSE) - SHADER_LOC_MAP_METALNESS, // Shader location: sampler2d texture: metalness (same as: SHADER_LOC_MAP_SPECULAR) - SHADER_LOC_MAP_NORMAL, // Shader location: sampler2d texture: normal - SHADER_LOC_MAP_ROUGHNESS, // Shader location: sampler2d texture: roughness - SHADER_LOC_MAP_OCCLUSION, // Shader location: sampler2d texture: occlusion - SHADER_LOC_MAP_EMISSION, // Shader location: sampler2d texture: emission - SHADER_LOC_MAP_HEIGHT, // Shader location: sampler2d texture: height - SHADER_LOC_MAP_CUBEMAP, // Shader location: samplerCube texture: cubemap - SHADER_LOC_MAP_IRRADIANCE, // Shader location: samplerCube texture: irradiance - SHADER_LOC_MAP_PREFILTER, // Shader location: samplerCube texture: prefilter - SHADER_LOC_MAP_BRDF, // Shader location: sampler2d texture: brdf - SHADER_LOC_VERTEX_BONEIDS, // Shader location: vertex attribute: boneIds - SHADER_LOC_VERTEX_BONEWEIGHTS, // Shader location: vertex attribute: boneWeights - SHADER_LOC_BONE_MATRICES // Shader location: array of matrices uniform: boneMatrices -} ShaderLocationIndex; - -#define SHADER_LOC_MAP_DIFFUSE SHADER_LOC_MAP_ALBEDO -#define SHADER_LOC_MAP_SPECULAR SHADER_LOC_MAP_METALNESS - -// Shader uniform data type -typedef enum { - SHADER_UNIFORM_FLOAT = 0, // Shader uniform type: float - SHADER_UNIFORM_VEC2, // Shader uniform type: vec2 (2 float) - SHADER_UNIFORM_VEC3, // Shader uniform type: vec3 (3 float) - SHADER_UNIFORM_VEC4, // Shader uniform type: vec4 (4 float) - SHADER_UNIFORM_INT, // Shader uniform type: int - SHADER_UNIFORM_IVEC2, // Shader uniform type: ivec2 (2 int) - SHADER_UNIFORM_IVEC3, // Shader uniform type: ivec3 (3 int) - SHADER_UNIFORM_IVEC4, // Shader uniform type: ivec4 (4 int) - SHADER_UNIFORM_SAMPLER2D // Shader uniform type: sampler2d -} ShaderUniformDataType; - -// Shader attribute data types -typedef enum { - SHADER_ATTRIB_FLOAT = 0, // Shader attribute type: float - SHADER_ATTRIB_VEC2, // Shader attribute type: vec2 (2 float) - SHADER_ATTRIB_VEC3, // Shader attribute type: vec3 (3 float) - SHADER_ATTRIB_VEC4 // Shader attribute type: vec4 (4 float) -} ShaderAttributeDataType; - -// Pixel formats -// NOTE: Support depends on OpenGL version and platform -typedef enum { - PIXELFORMAT_UNCOMPRESSED_GRAYSCALE = 1, // 8 bit per pixel (no alpha) - PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA, // 8*2 bpp (2 channels) - PIXELFORMAT_UNCOMPRESSED_R5G6B5, // 16 bpp - PIXELFORMAT_UNCOMPRESSED_R8G8B8, // 24 bpp - PIXELFORMAT_UNCOMPRESSED_R5G5B5A1, // 16 bpp (1 bit alpha) - PIXELFORMAT_UNCOMPRESSED_R4G4B4A4, // 16 bpp (4 bit alpha) - PIXELFORMAT_UNCOMPRESSED_R8G8B8A8, // 32 bpp - PIXELFORMAT_UNCOMPRESSED_R32, // 32 bpp (1 channel - float) - PIXELFORMAT_UNCOMPRESSED_R32G32B32, // 32*3 bpp (3 channels - float) - PIXELFORMAT_UNCOMPRESSED_R32G32B32A32, // 32*4 bpp (4 channels - float) - PIXELFORMAT_UNCOMPRESSED_R16, // 16 bpp (1 channel - half float) - PIXELFORMAT_UNCOMPRESSED_R16G16B16, // 16*3 bpp (3 channels - half float) - PIXELFORMAT_UNCOMPRESSED_R16G16B16A16, // 16*4 bpp (4 channels - half float) - PIXELFORMAT_COMPRESSED_DXT1_RGB, // 4 bpp (no alpha) - PIXELFORMAT_COMPRESSED_DXT1_RGBA, // 4 bpp (1 bit alpha) - PIXELFORMAT_COMPRESSED_DXT3_RGBA, // 8 bpp - PIXELFORMAT_COMPRESSED_DXT5_RGBA, // 8 bpp - PIXELFORMAT_COMPRESSED_ETC1_RGB, // 4 bpp - PIXELFORMAT_COMPRESSED_ETC2_RGB, // 4 bpp - PIXELFORMAT_COMPRESSED_ETC2_EAC_RGBA, // 8 bpp - PIXELFORMAT_COMPRESSED_PVRT_RGB, // 4 bpp - PIXELFORMAT_COMPRESSED_PVRT_RGBA, // 4 bpp - PIXELFORMAT_COMPRESSED_ASTC_4x4_RGBA, // 8 bpp - PIXELFORMAT_COMPRESSED_ASTC_8x8_RGBA // 2 bpp -} PixelFormat; - -// Texture parameters: filter mode -// NOTE 1: Filtering considers mipmaps if available in the texture -// NOTE 2: Filter is accordingly set for minification and magnification -typedef enum { - TEXTURE_FILTER_POINT = 0, // No filter, just pixel approximation - TEXTURE_FILTER_BILINEAR, // Linear filtering - TEXTURE_FILTER_TRILINEAR, // Trilinear filtering (linear with mipmaps) - TEXTURE_FILTER_ANISOTROPIC_4X, // Anisotropic filtering 4x - TEXTURE_FILTER_ANISOTROPIC_8X, // Anisotropic filtering 8x - TEXTURE_FILTER_ANISOTROPIC_16X, // Anisotropic filtering 16x -} TextureFilter; - -// Texture parameters: wrap mode -typedef enum { - TEXTURE_WRAP_REPEAT = 0, // Repeats texture in tiled mode - TEXTURE_WRAP_CLAMP, // Clamps texture to edge pixel in tiled mode - TEXTURE_WRAP_MIRROR_REPEAT, // Mirrors and repeats the texture in tiled mode - TEXTURE_WRAP_MIRROR_CLAMP // Mirrors and clamps to border the texture in tiled mode -} TextureWrap; - -// Cubemap layouts -typedef enum { - CUBEMAP_LAYOUT_AUTO_DETECT = 0, // Automatically detect layout type - CUBEMAP_LAYOUT_LINE_VERTICAL, // Layout is defined by a vertical line with faces - CUBEMAP_LAYOUT_LINE_HORIZONTAL, // Layout is defined by a horizontal line with faces - CUBEMAP_LAYOUT_CROSS_THREE_BY_FOUR, // Layout is defined by a 3x4 cross with cubemap faces - CUBEMAP_LAYOUT_CROSS_FOUR_BY_THREE // Layout is defined by a 4x3 cross with cubemap faces -} CubemapLayout; - -// Font type, defines generation method -typedef enum { - FONT_DEFAULT = 0, // Default font generation, anti-aliased - FONT_BITMAP, // Bitmap font generation, no anti-aliasing - FONT_SDF // SDF font generation, requires external shader -} FontType; - -// Color blending modes (pre-defined) -typedef enum { - BLEND_ALPHA = 0, // Blend textures considering alpha (default) - BLEND_ADDITIVE, // Blend textures adding colors - BLEND_MULTIPLIED, // Blend textures multiplying colors - BLEND_ADD_COLORS, // Blend textures adding colors (alternative) - BLEND_SUBTRACT_COLORS, // Blend textures subtracting colors (alternative) - BLEND_ALPHA_PREMULTIPLY, // Blend premultiplied textures considering alpha - BLEND_CUSTOM, // Blend textures using custom src/dst factors (use rlSetBlendFactors()) - BLEND_CUSTOM_SEPARATE // Blend textures using custom rgb/alpha separate src/dst factors (use rlSetBlendFactorsSeparate()) -} BlendMode; - -// Gesture -// NOTE: Provided as bit-wise flags to enable only desired gestures -typedef enum { - GESTURE_NONE = 0, // No gesture - GESTURE_TAP = 1, // Tap gesture - GESTURE_DOUBLETAP = 2, // Double tap gesture - GESTURE_HOLD = 4, // Hold gesture - GESTURE_DRAG = 8, // Drag gesture - GESTURE_SWIPE_RIGHT = 16, // Swipe right gesture - GESTURE_SWIPE_LEFT = 32, // Swipe left gesture - GESTURE_SWIPE_UP = 64, // Swipe up gesture - GESTURE_SWIPE_DOWN = 128, // Swipe down gesture - GESTURE_PINCH_IN = 256, // Pinch in gesture - GESTURE_PINCH_OUT = 512 // Pinch out gesture -} Gesture; - -// Camera system modes -typedef enum { - CAMERA_CUSTOM = 0, // Camera custom, controlled by user (UpdateCamera() does nothing) - CAMERA_FREE, // Camera free mode - CAMERA_ORBITAL, // Camera orbital, around target, zoom supported - CAMERA_FIRST_PERSON, // Camera first person - CAMERA_THIRD_PERSON // Camera third person -} CameraMode; - -// Camera projection -typedef enum { - CAMERA_PERSPECTIVE = 0, // Perspective projection - CAMERA_ORTHOGRAPHIC // Orthographic projection -} CameraProjection; - -// N-patch layout -typedef enum { - NPATCH_NINE_PATCH = 0, // Npatch layout: 3x3 tiles - NPATCH_THREE_PATCH_VERTICAL, // Npatch layout: 1x3 tiles - NPATCH_THREE_PATCH_HORIZONTAL // Npatch layout: 3x1 tiles -} NPatchLayout; - -// Callbacks to hook some internal functions -// WARNING: These callbacks are intended for advanced users -typedef void (*TraceLogCallback)(int logLevel, const char *text, va_list args); // Logging: Redirect trace log messages -typedef unsigned char *(*LoadFileDataCallback)(const char *fileName, int *dataSize); // FileIO: Load binary data -typedef bool (*SaveFileDataCallback)(const char *fileName, void *data, int dataSize); // FileIO: Save binary data -typedef char *(*LoadFileTextCallback)(const char *fileName); // FileIO: Load text data -typedef bool (*SaveFileTextCallback)(const char *fileName, char *text); // FileIO: Save text data - -//------------------------------------------------------------------------------------ -// Global Variables Definition -//------------------------------------------------------------------------------------ -// It's lonely here... - -//------------------------------------------------------------------------------------ -// Window and Graphics Device Functions (Module: core) -//------------------------------------------------------------------------------------ - -#if defined(__cplusplus) -extern "C" { // Prevents name mangling of functions -#endif - -// Window-related functions -RLAPI void InitWindow(int width, int height, const char *title); // Initialize window and OpenGL context -RLAPI void CloseWindow(void); // Close window and unload OpenGL context -RLAPI bool WindowShouldClose(void); // Check if application should close (KEY_ESCAPE pressed or windows close icon clicked) -RLAPI bool IsWindowReady(void); // Check if window has been initialized successfully -RLAPI bool IsWindowFullscreen(void); // Check if window is currently fullscreen -RLAPI bool IsWindowHidden(void); // Check if window is currently hidden -RLAPI bool IsWindowMinimized(void); // Check if window is currently minimized -RLAPI bool IsWindowMaximized(void); // Check if window is currently maximized -RLAPI bool IsWindowFocused(void); // Check if window is currently focused -RLAPI bool IsWindowResized(void); // Check if window has been resized last frame -RLAPI bool IsWindowState(unsigned int flag); // Check if one specific window flag is enabled -RLAPI void SetWindowState(unsigned int flags); // Set window configuration state using flags -RLAPI void ClearWindowState(unsigned int flags); // Clear window configuration state flags -RLAPI void ToggleFullscreen(void); // Toggle window state: fullscreen/windowed, resizes monitor to match window resolution -RLAPI void ToggleBorderlessWindowed(void); // Toggle window state: borderless windowed, resizes window to match monitor resolution -RLAPI void MaximizeWindow(void); // Set window state: maximized, if resizable -RLAPI void MinimizeWindow(void); // Set window state: minimized, if resizable -RLAPI void RestoreWindow(void); // Set window state: not minimized/maximized -RLAPI void SetWindowIcon(Image image); // Set icon for window (single image, RGBA 32bit) -RLAPI void SetWindowIcons(Image *images, int count); // Set icon for window (multiple images, RGBA 32bit) -RLAPI void SetWindowTitle(const char *title); // Set title for window -RLAPI void SetWindowPosition(int x, int y); // Set window position on screen -RLAPI void SetWindowMonitor(int monitor); // Set monitor for the current window -RLAPI void SetWindowMinSize(int width, int height); // Set window minimum dimensions (for FLAG_WINDOW_RESIZABLE) -RLAPI void SetWindowMaxSize(int width, int height); // Set window maximum dimensions (for FLAG_WINDOW_RESIZABLE) -RLAPI void SetWindowSize(int width, int height); // Set window dimensions -RLAPI void SetWindowOpacity(float opacity); // Set window opacity [0.0f..1.0f] -RLAPI void SetWindowFocused(void); // Set window focused -RLAPI void *GetWindowHandle(void); // Get native window handle -RLAPI int GetScreenWidth(void); // Get current screen width -RLAPI int GetScreenHeight(void); // Get current screen height -RLAPI int GetRenderWidth(void); // Get current render width (it considers HiDPI) -RLAPI int GetRenderHeight(void); // Get current render height (it considers HiDPI) -RLAPI int GetMonitorCount(void); // Get number of connected monitors -RLAPI int GetCurrentMonitor(void); // Get current monitor where window is placed -RLAPI Vector2 GetMonitorPosition(int monitor); // Get specified monitor position -RLAPI int GetMonitorWidth(int monitor); // Get specified monitor width (current video mode used by monitor) -RLAPI int GetMonitorHeight(int monitor); // Get specified monitor height (current video mode used by monitor) -RLAPI int GetMonitorPhysicalWidth(int monitor); // Get specified monitor physical width in millimetres -RLAPI int GetMonitorPhysicalHeight(int monitor); // Get specified monitor physical height in millimetres -RLAPI int GetMonitorRefreshRate(int monitor); // Get specified monitor refresh rate -RLAPI Vector2 GetWindowPosition(void); // Get window position XY on monitor -RLAPI Vector2 GetWindowScaleDPI(void); // Get window scale DPI factor -RLAPI const char *GetMonitorName(int monitor); // Get the human-readable, UTF-8 encoded name of the specified monitor -RLAPI void SetClipboardText(const char *text); // Set clipboard text content -RLAPI const char *GetClipboardText(void); // Get clipboard text content -RLAPI Image GetClipboardImage(void); // Get clipboard image content -RLAPI void EnableEventWaiting(void); // Enable waiting for events on EndDrawing(), no automatic event polling -RLAPI void DisableEventWaiting(void); // Disable waiting for events on EndDrawing(), automatic events polling - -// Cursor-related functions -RLAPI void ShowCursor(void); // Shows cursor -RLAPI void HideCursor(void); // Hides cursor -RLAPI bool IsCursorHidden(void); // Check if cursor is not visible -RLAPI void EnableCursor(void); // Enables cursor (unlock cursor) -RLAPI void DisableCursor(void); // Disables cursor (lock cursor) -RLAPI bool IsCursorOnScreen(void); // Check if cursor is on the screen - -// Drawing-related functions -RLAPI void ClearBackground(Color color); // Set background color (framebuffer clear color) -RLAPI void BeginDrawing(void); // Setup canvas (framebuffer) to start drawing -RLAPI void EndDrawing(void); // End canvas drawing and swap buffers (double buffering) -RLAPI void BeginMode2D(Camera2D camera); // Begin 2D mode with custom camera (2D) -RLAPI void EndMode2D(void); // Ends 2D mode with custom camera -RLAPI void BeginMode3D(Camera3D camera); // Begin 3D mode with custom camera (3D) -RLAPI void EndMode3D(void); // Ends 3D mode and returns to default 2D orthographic mode -RLAPI void BeginTextureMode(RenderTexture2D target); // Begin drawing to render texture -RLAPI void EndTextureMode(void); // Ends drawing to render texture -RLAPI void BeginShaderMode(Shader shader); // Begin custom shader drawing -RLAPI void EndShaderMode(void); // End custom shader drawing (use default shader) -RLAPI void BeginBlendMode(int mode); // Begin blending mode (alpha, additive, multiplied, subtract, custom) -RLAPI void EndBlendMode(void); // End blending mode (reset to default: alpha blending) -RLAPI void BeginScissorMode(int x, int y, int width, int height); // Begin scissor mode (define screen area for following drawing) -RLAPI void EndScissorMode(void); // End scissor mode -RLAPI void BeginVrStereoMode(VrStereoConfig config); // Begin stereo rendering (requires VR simulator) -RLAPI void EndVrStereoMode(void); // End stereo rendering (requires VR simulator) - -// VR stereo config functions for VR simulator -RLAPI VrStereoConfig LoadVrStereoConfig(VrDeviceInfo device); // Load VR stereo config for VR simulator device parameters -RLAPI void UnloadVrStereoConfig(VrStereoConfig config); // Unload VR stereo config - -// Shader management functions -// NOTE: Shader functionality is not available on OpenGL 1.1 -RLAPI Shader LoadShader(const char *vsFileName, const char *fsFileName); // Load shader from files and bind default locations -RLAPI Shader LoadShaderFromMemory(const char *vsCode, const char *fsCode); // Load shader from code strings and bind default locations -RLAPI bool IsShaderValid(Shader shader); // Check if a shader is valid (loaded on GPU) -RLAPI int GetShaderLocation(Shader shader, const char *uniformName); // Get shader uniform location -RLAPI int GetShaderLocationAttrib(Shader shader, const char *attribName); // Get shader attribute location -RLAPI void SetShaderValue(Shader shader, int locIndex, const void *value, int uniformType); // Set shader uniform value -RLAPI void SetShaderValueV(Shader shader, int locIndex, const void *value, int uniformType, int count); // Set shader uniform value vector -RLAPI void SetShaderValueMatrix(Shader shader, int locIndex, Matrix mat); // Set shader uniform value (matrix 4x4) -RLAPI void SetShaderValueTexture(Shader shader, int locIndex, Texture2D texture); // Set shader uniform value for texture (sampler2d) -RLAPI void UnloadShader(Shader shader); // Unload shader from GPU memory (VRAM) - -// Screen-space-related functions -#define GetMouseRay GetScreenToWorldRay // Compatibility hack for previous raylib versions -RLAPI Ray GetScreenToWorldRay(Vector2 position, Camera camera); // Get a ray trace from screen position (i.e mouse) -RLAPI Ray GetScreenToWorldRayEx(Vector2 position, Camera camera, int width, int height); // Get a ray trace from screen position (i.e mouse) in a viewport -RLAPI Vector2 GetWorldToScreen(Vector3 position, Camera camera); // Get the screen space position for a 3d world space position -RLAPI Vector2 GetWorldToScreenEx(Vector3 position, Camera camera, int width, int height); // Get size position for a 3d world space position -RLAPI Vector2 GetWorldToScreen2D(Vector2 position, Camera2D camera); // Get the screen space position for a 2d camera world space position -RLAPI Vector2 GetScreenToWorld2D(Vector2 position, Camera2D camera); // Get the world space position for a 2d camera screen space position -RLAPI Matrix GetCameraMatrix(Camera camera); // Get camera transform matrix (view matrix) -RLAPI Matrix GetCameraMatrix2D(Camera2D camera); // Get camera 2d transform matrix - -// Timing-related functions -RLAPI void SetTargetFPS(int fps); // Set target FPS (maximum) -RLAPI float GetFrameTime(void); // Get time in seconds for last frame drawn (delta time) -RLAPI double GetTime(void); // Get elapsed time in seconds since InitWindow() -RLAPI int GetFPS(void); // Get current FPS - -// Custom frame control functions -// NOTE: Those functions are intended for advanced users that want full control over the frame processing -// By default EndDrawing() does this job: draws everything + SwapScreenBuffer() + manage frame timing + PollInputEvents() -// To avoid that behaviour and control frame processes manually, enable in config.h: SUPPORT_CUSTOM_FRAME_CONTROL -RLAPI void SwapScreenBuffer(void); // Swap back buffer with front buffer (screen drawing) -RLAPI void PollInputEvents(void); // Register all input events -RLAPI void WaitTime(double seconds); // Wait for some time (halt program execution) - -// Random values generation functions -RLAPI void SetRandomSeed(unsigned int seed); // Set the seed for the random number generator -RLAPI int GetRandomValue(int min, int max); // Get a random value between min and max (both included) -RLAPI int *LoadRandomSequence(unsigned int count, int min, int max); // Load random values sequence, no values repeated -RLAPI void UnloadRandomSequence(int *sequence); // Unload random values sequence - -// Misc. functions -RLAPI void TakeScreenshot(const char *fileName); // Takes a screenshot of current screen (filename extension defines format) -RLAPI void SetConfigFlags(unsigned int flags); // Setup init configuration flags (view FLAGS) -RLAPI void OpenURL(const char *url); // Open URL with default system browser (if available) - -// NOTE: Following functions implemented in module [utils] -//------------------------------------------------------------------ -RLAPI void TraceLog(int logLevel, const char *text, ...); // Show trace log messages (LOG_DEBUG, LOG_INFO, LOG_WARNING, LOG_ERROR...) -RLAPI void SetTraceLogLevel(int logLevel); // Set the current threshold (minimum) log level -RLAPI void *MemAlloc(unsigned int size); // Internal memory allocator -RLAPI void *MemRealloc(void *ptr, unsigned int size); // Internal memory reallocator -RLAPI void MemFree(void *ptr); // Internal memory free - -// Set custom callbacks -// WARNING: Callbacks setup is intended for advanced users -RLAPI void SetTraceLogCallback(TraceLogCallback callback); // Set custom trace log -RLAPI void SetLoadFileDataCallback(LoadFileDataCallback callback); // Set custom file binary data loader -RLAPI void SetSaveFileDataCallback(SaveFileDataCallback callback); // Set custom file binary data saver -RLAPI void SetLoadFileTextCallback(LoadFileTextCallback callback); // Set custom file text data loader -RLAPI void SetSaveFileTextCallback(SaveFileTextCallback callback); // Set custom file text data saver - -// Files management functions -RLAPI unsigned char *LoadFileData(const char *fileName, int *dataSize); // Load file data as byte array (read) -RLAPI void UnloadFileData(unsigned char *data); // Unload file data allocated by LoadFileData() -RLAPI bool SaveFileData(const char *fileName, void *data, int dataSize); // Save data to file from byte array (write), returns true on success -RLAPI bool ExportDataAsCode(const unsigned char *data, int dataSize, const char *fileName); // Export data to code (.h), returns true on success -RLAPI char *LoadFileText(const char *fileName); // Load text data from file (read), returns a '\0' terminated string -RLAPI void UnloadFileText(char *text); // Unload file text data allocated by LoadFileText() -RLAPI bool SaveFileText(const char *fileName, char *text); // Save text data to file (write), string must be '\0' terminated, returns true on success -//------------------------------------------------------------------ - -// File system functions -RLAPI bool FileExists(const char *fileName); // Check if file exists -RLAPI bool DirectoryExists(const char *dirPath); // Check if a directory path exists -RLAPI bool IsFileExtension(const char *fileName, const char *ext); // Check file extension (including point: .png, .wav) -RLAPI int GetFileLength(const char *fileName); // Get file length in bytes (NOTE: GetFileSize() conflicts with windows.h) -RLAPI const char *GetFileExtension(const char *fileName); // Get pointer to extension for a filename string (includes dot: '.png') -RLAPI const char *GetFileName(const char *filePath); // Get pointer to filename for a path string -RLAPI const char *GetFileNameWithoutExt(const char *filePath); // Get filename string without extension (uses static string) -RLAPI const char *GetDirectoryPath(const char *filePath); // Get full path for a given fileName with path (uses static string) -RLAPI const char *GetPrevDirectoryPath(const char *dirPath); // Get previous directory path for a given path (uses static string) -RLAPI const char *GetWorkingDirectory(void); // Get current working directory (uses static string) -RLAPI const char *GetApplicationDirectory(void); // Get the directory of the running application (uses static string) -RLAPI int MakeDirectory(const char *dirPath); // Create directories (including full path requested), returns 0 on success -RLAPI bool ChangeDirectory(const char *dir); // Change working directory, return true on success -RLAPI bool IsPathFile(const char *path); // Check if a given path is a file or a directory -RLAPI bool IsFileNameValid(const char *fileName); // Check if fileName is valid for the platform/OS -RLAPI FilePathList LoadDirectoryFiles(const char *dirPath); // Load directory filepaths -RLAPI FilePathList LoadDirectoryFilesEx(const char *basePath, const char *filter, bool scanSubdirs); // Load directory filepaths with extension filtering and recursive directory scan. Use 'DIR' in the filter string to include directories in the result -RLAPI void UnloadDirectoryFiles(FilePathList files); // Unload filepaths -RLAPI bool IsFileDropped(void); // Check if a file has been dropped into window -RLAPI FilePathList LoadDroppedFiles(void); // Load dropped filepaths -RLAPI void UnloadDroppedFiles(FilePathList files); // Unload dropped filepaths -RLAPI long GetFileModTime(const char *fileName); // Get file modification time (last write time) - -// Compression/Encoding functionality -RLAPI unsigned char *CompressData(const unsigned char *data, int dataSize, int *compDataSize); // Compress data (DEFLATE algorithm), memory must be MemFree() -RLAPI unsigned char *DecompressData(const unsigned char *compData, int compDataSize, int *dataSize); // Decompress data (DEFLATE algorithm), memory must be MemFree() -RLAPI char *EncodeDataBase64(const unsigned char *data, int dataSize, int *outputSize); // Encode data to Base64 string, memory must be MemFree() -RLAPI unsigned char *DecodeDataBase64(const unsigned char *data, int *outputSize); // Decode Base64 string data, memory must be MemFree() -RLAPI unsigned int ComputeCRC32(unsigned char *data, int dataSize); // Compute CRC32 hash code -RLAPI unsigned int *ComputeMD5(unsigned char *data, int dataSize); // Compute MD5 hash code, returns static int[4] (16 bytes) -RLAPI unsigned int *ComputeSHA1(unsigned char *data, int dataSize); // Compute SHA1 hash code, returns static int[5] (20 bytes) - - -// Automation events functionality -RLAPI AutomationEventList LoadAutomationEventList(const char *fileName); // Load automation events list from file, NULL for empty list, capacity = MAX_AUTOMATION_EVENTS -RLAPI void UnloadAutomationEventList(AutomationEventList list); // Unload automation events list from file -RLAPI bool ExportAutomationEventList(AutomationEventList list, const char *fileName); // Export automation events list as text file -RLAPI void SetAutomationEventList(AutomationEventList *list); // Set automation event list to record to -RLAPI void SetAutomationEventBaseFrame(int frame); // Set automation event internal base frame to start recording -RLAPI void StartAutomationEventRecording(void); // Start recording automation events (AutomationEventList must be set) -RLAPI void StopAutomationEventRecording(void); // Stop recording automation events -RLAPI void PlayAutomationEvent(AutomationEvent event); // Play a recorded automation event - -//------------------------------------------------------------------------------------ -// Input Handling Functions (Module: core) -//------------------------------------------------------------------------------------ - -// Input-related functions: keyboard -RLAPI bool IsKeyPressed(int key); // Check if a key has been pressed once -RLAPI bool IsKeyPressedRepeat(int key); // Check if a key has been pressed again -RLAPI bool IsKeyDown(int key); // Check if a key is being pressed -RLAPI bool IsKeyReleased(int key); // Check if a key has been released once -RLAPI bool IsKeyUp(int key); // Check if a key is NOT being pressed -RLAPI int GetKeyPressed(void); // Get key pressed (keycode), call it multiple times for keys queued, returns 0 when the queue is empty -RLAPI int GetCharPressed(void); // Get char pressed (unicode), call it multiple times for chars queued, returns 0 when the queue is empty -RLAPI void SetExitKey(int key); // Set a custom key to exit program (default is ESC) - -// Input-related functions: gamepads -RLAPI bool IsGamepadAvailable(int gamepad); // Check if a gamepad is available -RLAPI const char *GetGamepadName(int gamepad); // Get gamepad internal name id -RLAPI bool IsGamepadButtonPressed(int gamepad, int button); // Check if a gamepad button has been pressed once -RLAPI bool IsGamepadButtonDown(int gamepad, int button); // Check if a gamepad button is being pressed -RLAPI bool IsGamepadButtonReleased(int gamepad, int button); // Check if a gamepad button has been released once -RLAPI bool IsGamepadButtonUp(int gamepad, int button); // Check if a gamepad button is NOT being pressed -RLAPI int GetGamepadButtonPressed(void); // Get the last gamepad button pressed -RLAPI int GetGamepadAxisCount(int gamepad); // Get gamepad axis count for a gamepad -RLAPI float GetGamepadAxisMovement(int gamepad, int axis); // Get axis movement value for a gamepad axis -RLAPI int SetGamepadMappings(const char *mappings); // Set internal gamepad mappings (SDL_GameControllerDB) -RLAPI void SetGamepadVibration(int gamepad, float leftMotor, float rightMotor, float duration); // Set gamepad vibration for both motors (duration in seconds) - -// Input-related functions: mouse -RLAPI bool IsMouseButtonPressed(int button); // Check if a mouse button has been pressed once -RLAPI bool IsMouseButtonDown(int button); // Check if a mouse button is being pressed -RLAPI bool IsMouseButtonReleased(int button); // Check if a mouse button has been released once -RLAPI bool IsMouseButtonUp(int button); // Check if a mouse button is NOT being pressed -RLAPI int GetMouseX(void); // Get mouse position X -RLAPI int GetMouseY(void); // Get mouse position Y -RLAPI Vector2 GetMousePosition(void); // Get mouse position XY -RLAPI Vector2 GetMouseDelta(void); // Get mouse delta between frames -RLAPI void SetMousePosition(int x, int y); // Set mouse position XY -RLAPI void SetMouseOffset(int offsetX, int offsetY); // Set mouse offset -RLAPI void SetMouseScale(float scaleX, float scaleY); // Set mouse scaling -RLAPI float GetMouseWheelMove(void); // Get mouse wheel movement for X or Y, whichever is larger -RLAPI Vector2 GetMouseWheelMoveV(void); // Get mouse wheel movement for both X and Y -RLAPI void SetMouseCursor(int cursor); // Set mouse cursor - -// Input-related functions: touch -RLAPI int GetTouchX(void); // Get touch position X for touch point 0 (relative to screen size) -RLAPI int GetTouchY(void); // Get touch position Y for touch point 0 (relative to screen size) -RLAPI Vector2 GetTouchPosition(int index); // Get touch position XY for a touch point index (relative to screen size) -RLAPI int GetTouchPointId(int index); // Get touch point identifier for given index -RLAPI int GetTouchPointCount(void); // Get number of touch points - -//------------------------------------------------------------------------------------ -// Gestures and Touch Handling Functions (Module: rgestures) -//------------------------------------------------------------------------------------ -RLAPI void SetGesturesEnabled(unsigned int flags); // Enable a set of gestures using flags -RLAPI bool IsGestureDetected(unsigned int gesture); // Check if a gesture have been detected -RLAPI int GetGestureDetected(void); // Get latest detected gesture -RLAPI float GetGestureHoldDuration(void); // Get gesture hold time in seconds -RLAPI Vector2 GetGestureDragVector(void); // Get gesture drag vector -RLAPI float GetGestureDragAngle(void); // Get gesture drag angle -RLAPI Vector2 GetGesturePinchVector(void); // Get gesture pinch delta -RLAPI float GetGesturePinchAngle(void); // Get gesture pinch angle - -//------------------------------------------------------------------------------------ -// Camera System Functions (Module: rcamera) -//------------------------------------------------------------------------------------ -RLAPI void UpdateCamera(Camera *camera, int mode); // Update camera position for selected mode -RLAPI void UpdateCameraPro(Camera *camera, Vector3 movement, Vector3 rotation, float zoom); // Update camera movement/rotation - -//------------------------------------------------------------------------------------ -// Basic Shapes Drawing Functions (Module: shapes) -//------------------------------------------------------------------------------------ -// Set texture and rectangle to be used on shapes drawing -// NOTE: It can be useful when using basic shapes and one single font, -// defining a font char white rectangle would allow drawing everything in a single draw call -RLAPI void SetShapesTexture(Texture2D texture, Rectangle source); // Set texture and rectangle to be used on shapes drawing -RLAPI Texture2D GetShapesTexture(void); // Get texture that is used for shapes drawing -RLAPI Rectangle GetShapesTextureRectangle(void); // Get texture source rectangle that is used for shapes drawing - -// Basic shapes drawing functions -RLAPI void DrawPixel(int posX, int posY, Color color); // Draw a pixel using geometry [Can be slow, use with care] -RLAPI void DrawPixelV(Vector2 position, Color color); // Draw a pixel using geometry (Vector version) [Can be slow, use with care] -RLAPI void DrawLine(int startPosX, int startPosY, int endPosX, int endPosY, Color color); // Draw a line -RLAPI void DrawLineV(Vector2 startPos, Vector2 endPos, Color color); // Draw a line (using gl lines) -RLAPI void DrawLineEx(Vector2 startPos, Vector2 endPos, float thick, Color color); // Draw a line (using triangles/quads) -RLAPI void DrawLineStrip(const Vector2 *points, int pointCount, Color color); // Draw lines sequence (using gl lines) -RLAPI void DrawLineBezier(Vector2 startPos, Vector2 endPos, float thick, Color color); // Draw line segment cubic-bezier in-out interpolation -RLAPI void DrawCircle(int centerX, int centerY, float radius, Color color); // Draw a color-filled circle -RLAPI void DrawCircleSector(Vector2 center, float radius, float startAngle, float endAngle, int segments, Color color); // Draw a piece of a circle -RLAPI void DrawCircleSectorLines(Vector2 center, float radius, float startAngle, float endAngle, int segments, Color color); // Draw circle sector outline -RLAPI void DrawCircleGradient(int centerX, int centerY, float radius, Color inner, Color outer); // Draw a gradient-filled circle -RLAPI void DrawCircleV(Vector2 center, float radius, Color color); // Draw a color-filled circle (Vector version) -RLAPI void DrawCircleLines(int centerX, int centerY, float radius, Color color); // Draw circle outline -RLAPI void DrawCircleLinesV(Vector2 center, float radius, Color color); // Draw circle outline (Vector version) -RLAPI void DrawEllipse(int centerX, int centerY, float radiusH, float radiusV, Color color); // Draw ellipse -RLAPI void DrawEllipseLines(int centerX, int centerY, float radiusH, float radiusV, Color color); // Draw ellipse outline -RLAPI void DrawRing(Vector2 center, float innerRadius, float outerRadius, float startAngle, float endAngle, int segments, Color color); // Draw ring -RLAPI void DrawRingLines(Vector2 center, float innerRadius, float outerRadius, float startAngle, float endAngle, int segments, Color color); // Draw ring outline -RLAPI void DrawRectangle(int posX, int posY, int width, int height, Color color); // Draw a color-filled rectangle -RLAPI void DrawRectangleV(Vector2 position, Vector2 size, Color color); // Draw a color-filled rectangle (Vector version) -RLAPI void DrawRectangleRec(Rectangle rec, Color color); // Draw a color-filled rectangle -RLAPI void DrawRectanglePro(Rectangle rec, Vector2 origin, float rotation, Color color); // Draw a color-filled rectangle with pro parameters -RLAPI void DrawRectangleGradientV(int posX, int posY, int width, int height, Color top, Color bottom); // Draw a vertical-gradient-filled rectangle -RLAPI void DrawRectangleGradientH(int posX, int posY, int width, int height, Color left, Color right); // Draw a horizontal-gradient-filled rectangle -RLAPI void DrawRectangleGradientEx(Rectangle rec, Color topLeft, Color bottomLeft, Color topRight, Color bottomRight); // Draw a gradient-filled rectangle with custom vertex colors -RLAPI void DrawRectangleLines(int posX, int posY, int width, int height, Color color); // Draw rectangle outline -RLAPI void DrawRectangleLinesEx(Rectangle rec, float lineThick, Color color); // Draw rectangle outline with extended parameters -RLAPI void DrawRectangleRounded(Rectangle rec, float roundness, int segments, Color color); // Draw rectangle with rounded edges -RLAPI void DrawRectangleRoundedLines(Rectangle rec, float roundness, int segments, Color color); // Draw rectangle lines with rounded edges -RLAPI void DrawRectangleRoundedLinesEx(Rectangle rec, float roundness, int segments, float lineThick, Color color); // Draw rectangle with rounded edges outline -RLAPI void DrawTriangle(Vector2 v1, Vector2 v2, Vector2 v3, Color color); // Draw a color-filled triangle (vertex in counter-clockwise order!) -RLAPI void DrawTriangleLines(Vector2 v1, Vector2 v2, Vector2 v3, Color color); // Draw triangle outline (vertex in counter-clockwise order!) -RLAPI void DrawTriangleFan(const Vector2 *points, int pointCount, Color color); // Draw a triangle fan defined by points (first vertex is the center) -RLAPI void DrawTriangleStrip(const Vector2 *points, int pointCount, Color color); // Draw a triangle strip defined by points -RLAPI void DrawPoly(Vector2 center, int sides, float radius, float rotation, Color color); // Draw a regular polygon (Vector version) -RLAPI void DrawPolyLines(Vector2 center, int sides, float radius, float rotation, Color color); // Draw a polygon outline of n sides -RLAPI void DrawPolyLinesEx(Vector2 center, int sides, float radius, float rotation, float lineThick, Color color); // Draw a polygon outline of n sides with extended parameters - -// Splines drawing functions -RLAPI void DrawSplineLinear(const Vector2 *points, int pointCount, float thick, Color color); // Draw spline: Linear, minimum 2 points -RLAPI void DrawSplineBasis(const Vector2 *points, int pointCount, float thick, Color color); // Draw spline: B-Spline, minimum 4 points -RLAPI void DrawSplineCatmullRom(const Vector2 *points, int pointCount, float thick, Color color); // Draw spline: Catmull-Rom, minimum 4 points -RLAPI void DrawSplineBezierQuadratic(const Vector2 *points, int pointCount, float thick, Color color); // Draw spline: Quadratic Bezier, minimum 3 points (1 control point): [p1, c2, p3, c4...] -RLAPI void DrawSplineBezierCubic(const Vector2 *points, int pointCount, float thick, Color color); // Draw spline: Cubic Bezier, minimum 4 points (2 control points): [p1, c2, c3, p4, c5, c6...] -RLAPI void DrawSplineSegmentLinear(Vector2 p1, Vector2 p2, float thick, Color color); // Draw spline segment: Linear, 2 points -RLAPI void DrawSplineSegmentBasis(Vector2 p1, Vector2 p2, Vector2 p3, Vector2 p4, float thick, Color color); // Draw spline segment: B-Spline, 4 points -RLAPI void DrawSplineSegmentCatmullRom(Vector2 p1, Vector2 p2, Vector2 p3, Vector2 p4, float thick, Color color); // Draw spline segment: Catmull-Rom, 4 points -RLAPI void DrawSplineSegmentBezierQuadratic(Vector2 p1, Vector2 c2, Vector2 p3, float thick, Color color); // Draw spline segment: Quadratic Bezier, 2 points, 1 control point -RLAPI void DrawSplineSegmentBezierCubic(Vector2 p1, Vector2 c2, Vector2 c3, Vector2 p4, float thick, Color color); // Draw spline segment: Cubic Bezier, 2 points, 2 control points - -// Spline segment point evaluation functions, for a given t [0.0f .. 1.0f] -RLAPI Vector2 GetSplinePointLinear(Vector2 startPos, Vector2 endPos, float t); // Get (evaluate) spline point: Linear -RLAPI Vector2 GetSplinePointBasis(Vector2 p1, Vector2 p2, Vector2 p3, Vector2 p4, float t); // Get (evaluate) spline point: B-Spline -RLAPI Vector2 GetSplinePointCatmullRom(Vector2 p1, Vector2 p2, Vector2 p3, Vector2 p4, float t); // Get (evaluate) spline point: Catmull-Rom -RLAPI Vector2 GetSplinePointBezierQuad(Vector2 p1, Vector2 c2, Vector2 p3, float t); // Get (evaluate) spline point: Quadratic Bezier -RLAPI Vector2 GetSplinePointBezierCubic(Vector2 p1, Vector2 c2, Vector2 c3, Vector2 p4, float t); // Get (evaluate) spline point: Cubic Bezier - -// Basic shapes collision detection functions -RLAPI bool CheckCollisionRecs(Rectangle rec1, Rectangle rec2); // Check collision between two rectangles -RLAPI bool CheckCollisionCircles(Vector2 center1, float radius1, Vector2 center2, float radius2); // Check collision between two circles -RLAPI bool CheckCollisionCircleRec(Vector2 center, float radius, Rectangle rec); // Check collision between circle and rectangle -RLAPI bool CheckCollisionCircleLine(Vector2 center, float radius, Vector2 p1, Vector2 p2); // Check if circle collides with a line created betweeen two points [p1] and [p2] -RLAPI bool CheckCollisionPointRec(Vector2 point, Rectangle rec); // Check if point is inside rectangle -RLAPI bool CheckCollisionPointCircle(Vector2 point, Vector2 center, float radius); // Check if point is inside circle -RLAPI bool CheckCollisionPointTriangle(Vector2 point, Vector2 p1, Vector2 p2, Vector2 p3); // Check if point is inside a triangle -RLAPI bool CheckCollisionPointLine(Vector2 point, Vector2 p1, Vector2 p2, int threshold); // Check if point belongs to line created between two points [p1] and [p2] with defined margin in pixels [threshold] -RLAPI bool CheckCollisionPointPoly(Vector2 point, const Vector2 *points, int pointCount); // Check if point is within a polygon described by array of vertices -RLAPI bool CheckCollisionLines(Vector2 startPos1, Vector2 endPos1, Vector2 startPos2, Vector2 endPos2, Vector2 *collisionPoint); // Check the collision between two lines defined by two points each, returns collision point by reference -RLAPI Rectangle GetCollisionRec(Rectangle rec1, Rectangle rec2); // Get collision rectangle for two rectangles collision - -//------------------------------------------------------------------------------------ -// Texture Loading and Drawing Functions (Module: textures) -//------------------------------------------------------------------------------------ - -// Image loading functions -// NOTE: These functions do not require GPU access -RLAPI Image LoadImage(const char *fileName); // Load image from file into CPU memory (RAM) -RLAPI Image LoadImageRaw(const char *fileName, int width, int height, int format, int headerSize); // Load image from RAW file data -RLAPI Image LoadImageAnim(const char *fileName, int *frames); // Load image sequence from file (frames appended to image.data) -RLAPI Image LoadImageAnimFromMemory(const char *fileType, const unsigned char *fileData, int dataSize, int *frames); // Load image sequence from memory buffer -RLAPI Image LoadImageFromMemory(const char *fileType, const unsigned char *fileData, int dataSize); // Load image from memory buffer, fileType refers to extension: i.e. '.png' -RLAPI Image LoadImageFromTexture(Texture2D texture); // Load image from GPU texture data -RLAPI Image LoadImageFromScreen(void); // Load image from screen buffer and (screenshot) -RLAPI bool IsImageValid(Image image); // Check if an image is valid (data and parameters) -RLAPI void UnloadImage(Image image); // Unload image from CPU memory (RAM) -RLAPI bool ExportImage(Image image, const char *fileName); // Export image data to file, returns true on success -RLAPI unsigned char *ExportImageToMemory(Image image, const char *fileType, int *fileSize); // Export image to memory buffer -RLAPI bool ExportImageAsCode(Image image, const char *fileName); // Export image as code file defining an array of bytes, returns true on success - -// Image generation functions -RLAPI Image GenImageColor(int width, int height, Color color); // Generate image: plain color -RLAPI Image GenImageGradientLinear(int width, int height, int direction, Color start, Color end); // Generate image: linear gradient, direction in degrees [0..360], 0=Vertical gradient -RLAPI Image GenImageGradientRadial(int width, int height, float density, Color inner, Color outer); // Generate image: radial gradient -RLAPI Image GenImageGradientSquare(int width, int height, float density, Color inner, Color outer); // Generate image: square gradient -RLAPI Image GenImageChecked(int width, int height, int checksX, int checksY, Color col1, Color col2); // Generate image: checked -RLAPI Image GenImageWhiteNoise(int width, int height, float factor); // Generate image: white noise -RLAPI Image GenImagePerlinNoise(int width, int height, int offsetX, int offsetY, float scale); // Generate image: perlin noise -RLAPI Image GenImageCellular(int width, int height, int tileSize); // Generate image: cellular algorithm, bigger tileSize means bigger cells -RLAPI Image GenImageText(int width, int height, const char *text); // Generate image: grayscale image from text data - -// Image manipulation functions -RLAPI Image ImageCopy(Image image); // Create an image duplicate (useful for transformations) -RLAPI Image ImageFromImage(Image image, Rectangle rec); // Create an image from another image piece -RLAPI Image ImageFromChannel(Image image, int selectedChannel); // Create an image from a selected channel of another image (GRAYSCALE) -RLAPI Image ImageText(const char *text, int fontSize, Color color); // Create an image from text (default font) -RLAPI Image ImageTextEx(Font font, const char *text, float fontSize, float spacing, Color tint); // Create an image from text (custom sprite font) -RLAPI void ImageFormat(Image *image, int newFormat); // Convert image data to desired format -RLAPI void ImageToPOT(Image *image, Color fill); // Convert image to POT (power-of-two) -RLAPI void ImageCrop(Image *image, Rectangle crop); // Crop an image to a defined rectangle -RLAPI void ImageAlphaCrop(Image *image, float threshold); // Crop image depending on alpha value -RLAPI void ImageAlphaClear(Image *image, Color color, float threshold); // Clear alpha channel to desired color -RLAPI void ImageAlphaMask(Image *image, Image alphaMask); // Apply alpha mask to image -RLAPI void ImageAlphaPremultiply(Image *image); // Premultiply alpha channel -RLAPI void ImageBlurGaussian(Image *image, int blurSize); // Apply Gaussian blur using a box blur approximation -RLAPI void ImageKernelConvolution(Image *image, const float *kernel, int kernelSize); // Apply custom square convolution kernel to image -RLAPI void ImageResize(Image *image, int newWidth, int newHeight); // Resize image (Bicubic scaling algorithm) -RLAPI void ImageResizeNN(Image *image, int newWidth,int newHeight); // Resize image (Nearest-Neighbor scaling algorithm) -RLAPI void ImageResizeCanvas(Image *image, int newWidth, int newHeight, int offsetX, int offsetY, Color fill); // Resize canvas and fill with color -RLAPI void ImageMipmaps(Image *image); // Compute all mipmap levels for a provided image -RLAPI void ImageDither(Image *image, int rBpp, int gBpp, int bBpp, int aBpp); // Dither image data to 16bpp or lower (Floyd-Steinberg dithering) -RLAPI void ImageFlipVertical(Image *image); // Flip image vertically -RLAPI void ImageFlipHorizontal(Image *image); // Flip image horizontally -RLAPI void ImageRotate(Image *image, int degrees); // Rotate image by input angle in degrees (-359 to 359) -RLAPI void ImageRotateCW(Image *image); // Rotate image clockwise 90deg -RLAPI void ImageRotateCCW(Image *image); // Rotate image counter-clockwise 90deg -RLAPI void ImageColorTint(Image *image, Color color); // Modify image color: tint -RLAPI void ImageColorInvert(Image *image); // Modify image color: invert -RLAPI void ImageColorGrayscale(Image *image); // Modify image color: grayscale -RLAPI void ImageColorContrast(Image *image, float contrast); // Modify image color: contrast (-100 to 100) -RLAPI void ImageColorBrightness(Image *image, int brightness); // Modify image color: brightness (-255 to 255) -RLAPI void ImageColorReplace(Image *image, Color color, Color replace); // Modify image color: replace color -RLAPI Color *LoadImageColors(Image image); // Load color data from image as a Color array (RGBA - 32bit) -RLAPI Color *LoadImagePalette(Image image, int maxPaletteSize, int *colorCount); // Load colors palette from image as a Color array (RGBA - 32bit) -RLAPI void UnloadImageColors(Color *colors); // Unload color data loaded with LoadImageColors() -RLAPI void UnloadImagePalette(Color *colors); // Unload colors palette loaded with LoadImagePalette() -RLAPI Rectangle GetImageAlphaBorder(Image image, float threshold); // Get image alpha border rectangle -RLAPI Color GetImageColor(Image image, int x, int y); // Get image pixel color at (x, y) position - -// Image drawing functions -// NOTE: Image software-rendering functions (CPU) -RLAPI void ImageClearBackground(Image *dst, Color color); // Clear image background with given color -RLAPI void ImageDrawPixel(Image *dst, int posX, int posY, Color color); // Draw pixel within an image -RLAPI void ImageDrawPixelV(Image *dst, Vector2 position, Color color); // Draw pixel within an image (Vector version) -RLAPI void ImageDrawLine(Image *dst, int startPosX, int startPosY, int endPosX, int endPosY, Color color); // Draw line within an image -RLAPI void ImageDrawLineV(Image *dst, Vector2 start, Vector2 end, Color color); // Draw line within an image (Vector version) -RLAPI void ImageDrawLineEx(Image *dst, Vector2 start, Vector2 end, int thick, Color color); // Draw a line defining thickness within an image -RLAPI void ImageDrawCircle(Image *dst, int centerX, int centerY, int radius, Color color); // Draw a filled circle within an image -RLAPI void ImageDrawCircleV(Image *dst, Vector2 center, int radius, Color color); // Draw a filled circle within an image (Vector version) -RLAPI void ImageDrawCircleLines(Image *dst, int centerX, int centerY, int radius, Color color); // Draw circle outline within an image -RLAPI void ImageDrawCircleLinesV(Image *dst, Vector2 center, int radius, Color color); // Draw circle outline within an image (Vector version) -RLAPI void ImageDrawRectangle(Image *dst, int posX, int posY, int width, int height, Color color); // Draw rectangle within an image -RLAPI void ImageDrawRectangleV(Image *dst, Vector2 position, Vector2 size, Color color); // Draw rectangle within an image (Vector version) -RLAPI void ImageDrawRectangleRec(Image *dst, Rectangle rec, Color color); // Draw rectangle within an image -RLAPI void ImageDrawRectangleLines(Image *dst, Rectangle rec, int thick, Color color); // Draw rectangle lines within an image -RLAPI void ImageDrawTriangle(Image *dst, Vector2 v1, Vector2 v2, Vector2 v3, Color color); // Draw triangle within an image -RLAPI void ImageDrawTriangleEx(Image *dst, Vector2 v1, Vector2 v2, Vector2 v3, Color c1, Color c2, Color c3); // Draw triangle with interpolated colors within an image -RLAPI void ImageDrawTriangleLines(Image *dst, Vector2 v1, Vector2 v2, Vector2 v3, Color color); // Draw triangle outline within an image -RLAPI void ImageDrawTriangleFan(Image *dst, Vector2 *points, int pointCount, Color color); // Draw a triangle fan defined by points within an image (first vertex is the center) -RLAPI void ImageDrawTriangleStrip(Image *dst, Vector2 *points, int pointCount, Color color); // Draw a triangle strip defined by points within an image -RLAPI void ImageDraw(Image *dst, Image src, Rectangle srcRec, Rectangle dstRec, Color tint); // Draw a source image within a destination image (tint applied to source) -RLAPI void ImageDrawText(Image *dst, const char *text, int posX, int posY, int fontSize, Color color); // Draw text (using default font) within an image (destination) -RLAPI void ImageDrawTextEx(Image *dst, Font font, const char *text, Vector2 position, float fontSize, float spacing, Color tint); // Draw text (custom sprite font) within an image (destination) - -// Texture loading functions -// NOTE: These functions require GPU access -RLAPI Texture2D LoadTexture(const char *fileName); // Load texture from file into GPU memory (VRAM) -RLAPI Texture2D LoadTextureFromImage(Image image); // Load texture from image data -RLAPI TextureCubemap LoadTextureCubemap(Image image, int layout); // Load cubemap from image, multiple image cubemap layouts supported -RLAPI RenderTexture2D LoadRenderTexture(int width, int height); // Load texture for rendering (framebuffer) -RLAPI bool IsTextureValid(Texture2D texture); // Check if a texture is valid (loaded in GPU) -RLAPI void UnloadTexture(Texture2D texture); // Unload texture from GPU memory (VRAM) -RLAPI bool IsRenderTextureValid(RenderTexture2D target); // Check if a render texture is valid (loaded in GPU) -RLAPI void UnloadRenderTexture(RenderTexture2D target); // Unload render texture from GPU memory (VRAM) -RLAPI void UpdateTexture(Texture2D texture, const void *pixels); // Update GPU texture with new data -RLAPI void UpdateTextureRec(Texture2D texture, Rectangle rec, const void *pixels); // Update GPU texture rectangle with new data - -// Texture configuration functions -RLAPI void GenTextureMipmaps(Texture2D *texture); // Generate GPU mipmaps for a texture -RLAPI void SetTextureFilter(Texture2D texture, int filter); // Set texture scaling filter mode -RLAPI void SetTextureWrap(Texture2D texture, int wrap); // Set texture wrapping mode - -// Texture drawing functions -RLAPI void DrawTexture(Texture2D texture, int posX, int posY, Color tint); // Draw a Texture2D -RLAPI void DrawTextureV(Texture2D texture, Vector2 position, Color tint); // Draw a Texture2D with position defined as Vector2 -RLAPI void DrawTextureEx(Texture2D texture, Vector2 position, float rotation, float scale, Color tint); // Draw a Texture2D with extended parameters -RLAPI void DrawTextureRec(Texture2D texture, Rectangle source, Vector2 position, Color tint); // Draw a part of a texture defined by a rectangle -RLAPI void DrawTexturePro(Texture2D texture, Rectangle source, Rectangle dest, Vector2 origin, float rotation, Color tint); // Draw a part of a texture defined by a rectangle with 'pro' parameters -RLAPI void DrawTextureNPatch(Texture2D texture, NPatchInfo nPatchInfo, Rectangle dest, Vector2 origin, float rotation, Color tint); // Draws a texture (or part of it) that stretches or shrinks nicely - -// Color/pixel related functions -RLAPI bool ColorIsEqual(Color col1, Color col2); // Check if two colors are equal -RLAPI Color Fade(Color color, float alpha); // Get color with alpha applied, alpha goes from 0.0f to 1.0f -RLAPI int ColorToInt(Color color); // Get hexadecimal value for a Color (0xRRGGBBAA) -RLAPI Vector4 ColorNormalize(Color color); // Get Color normalized as float [0..1] -RLAPI Color ColorFromNormalized(Vector4 normalized); // Get Color from normalized values [0..1] -RLAPI Vector3 ColorToHSV(Color color); // Get HSV values for a Color, hue [0..360], saturation/value [0..1] -RLAPI Color ColorFromHSV(float hue, float saturation, float value); // Get a Color from HSV values, hue [0..360], saturation/value [0..1] -RLAPI Color ColorTint(Color color, Color tint); // Get color multiplied with another color -RLAPI Color ColorBrightness(Color color, float factor); // Get color with brightness correction, brightness factor goes from -1.0f to 1.0f -RLAPI Color ColorContrast(Color color, float contrast); // Get color with contrast correction, contrast values between -1.0f and 1.0f -RLAPI Color ColorAlpha(Color color, float alpha); // Get color with alpha applied, alpha goes from 0.0f to 1.0f -RLAPI Color ColorAlphaBlend(Color dst, Color src, Color tint); // Get src alpha-blended into dst color with tint -RLAPI Color ColorLerp(Color color1, Color color2, float factor); // Get color lerp interpolation between two colors, factor [0.0f..1.0f] -RLAPI Color GetColor(unsigned int hexValue); // Get Color structure from hexadecimal value -RLAPI Color GetPixelColor(void *srcPtr, int format); // Get Color from a source pixel pointer of certain format -RLAPI void SetPixelColor(void *dstPtr, Color color, int format); // Set color formatted into destination pixel pointer -RLAPI int GetPixelDataSize(int width, int height, int format); // Get pixel data size in bytes for certain format - -//------------------------------------------------------------------------------------ -// Font Loading and Text Drawing Functions (Module: text) -//------------------------------------------------------------------------------------ - -// Font loading/unloading functions -RLAPI Font GetFontDefault(void); // Get the default Font -RLAPI Font LoadFont(const char *fileName); // Load font from file into GPU memory (VRAM) -RLAPI Font LoadFontEx(const char *fileName, int fontSize, int *codepoints, int codepointCount); // Load font from file with extended parameters, use NULL for codepoints and 0 for codepointCount to load the default character set, font size is provided in pixels height -RLAPI Font LoadFontFromImage(Image image, Color key, int firstChar); // Load font from Image (XNA style) -RLAPI Font LoadFontFromMemory(const char *fileType, const unsigned char *fileData, int dataSize, int fontSize, int *codepoints, int codepointCount); // Load font from memory buffer, fileType refers to extension: i.e. '.ttf' -RLAPI bool IsFontValid(Font font); // Check if a font is valid (font data loaded, WARNING: GPU texture not checked) -RLAPI GlyphInfo *LoadFontData(const unsigned char *fileData, int dataSize, int fontSize, int *codepoints, int codepointCount, int type); // Load font data for further use -RLAPI Image GenImageFontAtlas(const GlyphInfo *glyphs, Rectangle **glyphRecs, int glyphCount, int fontSize, int padding, int packMethod); // Generate image font atlas using chars info -RLAPI void UnloadFontData(GlyphInfo *glyphs, int glyphCount); // Unload font chars info data (RAM) -RLAPI void UnloadFont(Font font); // Unload font from GPU memory (VRAM) -RLAPI bool ExportFontAsCode(Font font, const char *fileName); // Export font as code file, returns true on success - -// Text drawing functions -RLAPI void DrawFPS(int posX, int posY); // Draw current FPS -RLAPI void DrawText(const char *text, int posX, int posY, int fontSize, Color color); // Draw text (using default font) -RLAPI void DrawTextEx(Font font, const char *text, Vector2 position, float fontSize, float spacing, Color tint); // Draw text using font and additional parameters -RLAPI void DrawTextPro(Font font, const char *text, Vector2 position, Vector2 origin, float rotation, float fontSize, float spacing, Color tint); // Draw text using Font and pro parameters (rotation) -RLAPI void DrawTextCodepoint(Font font, int codepoint, Vector2 position, float fontSize, Color tint); // Draw one character (codepoint) -RLAPI void DrawTextCodepoints(Font font, const int *codepoints, int codepointCount, Vector2 position, float fontSize, float spacing, Color tint); // Draw multiple character (codepoint) - -// Text font info functions -RLAPI void SetTextLineSpacing(int spacing); // Set vertical line spacing when drawing with line-breaks -RLAPI int MeasureText(const char *text, int fontSize); // Measure string width for default font -RLAPI Vector2 MeasureTextEx(Font font, const char *text, float fontSize, float spacing); // Measure string size for Font -RLAPI int GetGlyphIndex(Font font, int codepoint); // Get glyph index position in font for a codepoint (unicode character), fallback to '?' if not found -RLAPI GlyphInfo GetGlyphInfo(Font font, int codepoint); // Get glyph font info data for a codepoint (unicode character), fallback to '?' if not found -RLAPI Rectangle GetGlyphAtlasRec(Font font, int codepoint); // Get glyph rectangle in font atlas for a codepoint (unicode character), fallback to '?' if not found - -// Text codepoints management functions (unicode characters) -RLAPI char *LoadUTF8(const int *codepoints, int length); // Load UTF-8 text encoded from codepoints array -RLAPI void UnloadUTF8(char *text); // Unload UTF-8 text encoded from codepoints array -RLAPI int *LoadCodepoints(const char *text, int *count); // Load all codepoints from a UTF-8 text string, codepoints count returned by parameter -RLAPI void UnloadCodepoints(int *codepoints); // Unload codepoints data from memory -RLAPI int GetCodepointCount(const char *text); // Get total number of codepoints in a UTF-8 encoded string -RLAPI int GetCodepoint(const char *text, int *codepointSize); // Get next codepoint in a UTF-8 encoded string, 0x3f('?') is returned on failure -RLAPI int GetCodepointNext(const char *text, int *codepointSize); // Get next codepoint in a UTF-8 encoded string, 0x3f('?') is returned on failure -RLAPI int GetCodepointPrevious(const char *text, int *codepointSize); // Get previous codepoint in a UTF-8 encoded string, 0x3f('?') is returned on failure -RLAPI const char *CodepointToUTF8(int codepoint, int *utf8Size); // Encode one codepoint into UTF-8 byte array (array length returned as parameter) - -// Text strings management functions (no UTF-8 strings, only byte chars) -// NOTE: Some strings allocate memory internally for returned strings, just be careful! -RLAPI int TextCopy(char *dst, const char *src); // Copy one string to another, returns bytes copied -RLAPI bool TextIsEqual(const char *text1, const char *text2); // Check if two text string are equal -RLAPI unsigned int TextLength(const char *text); // Get text length, checks for '\0' ending -RLAPI const char *TextFormat(const char *text, ...); // Text formatting with variables (sprintf() style) -RLAPI const char *TextSubtext(const char *text, int position, int length); // Get a piece of a text string -RLAPI char *TextReplace(const char *text, const char *replace, const char *by); // Replace text string (WARNING: memory must be freed!) -RLAPI char *TextInsert(const char *text, const char *insert, int position); // Insert text in a position (WARNING: memory must be freed!) -RLAPI const char *TextJoin(const char **textList, int count, const char *delimiter); // Join text strings with delimiter -RLAPI const char **TextSplit(const char *text, char delimiter, int *count); // Split text into multiple strings -RLAPI void TextAppend(char *text, const char *append, int *position); // Append text at specific position and move cursor! -RLAPI int TextFindIndex(const char *text, const char *find); // Find first text occurrence within a string -RLAPI const char *TextToUpper(const char *text); // Get upper case version of provided string -RLAPI const char *TextToLower(const char *text); // Get lower case version of provided string -RLAPI const char *TextToPascal(const char *text); // Get Pascal case notation version of provided string -RLAPI const char *TextToSnake(const char *text); // Get Snake case notation version of provided string -RLAPI const char *TextToCamel(const char *text); // Get Camel case notation version of provided string - -RLAPI int TextToInteger(const char *text); // Get integer value from text (negative values not supported) -RLAPI float TextToFloat(const char *text); // Get float value from text (negative values not supported) - -//------------------------------------------------------------------------------------ -// Basic 3d Shapes Drawing Functions (Module: models) -//------------------------------------------------------------------------------------ - -// Basic geometric 3D shapes drawing functions -RLAPI void DrawLine3D(Vector3 startPos, Vector3 endPos, Color color); // Draw a line in 3D world space -RLAPI void DrawPoint3D(Vector3 position, Color color); // Draw a point in 3D space, actually a small line -RLAPI void DrawCircle3D(Vector3 center, float radius, Vector3 rotationAxis, float rotationAngle, Color color); // Draw a circle in 3D world space -RLAPI void DrawTriangle3D(Vector3 v1, Vector3 v2, Vector3 v3, Color color); // Draw a color-filled triangle (vertex in counter-clockwise order!) -RLAPI void DrawTriangleStrip3D(const Vector3 *points, int pointCount, Color color); // Draw a triangle strip defined by points -RLAPI void DrawCube(Vector3 position, float width, float height, float length, Color color); // Draw cube -RLAPI void DrawCubeV(Vector3 position, Vector3 size, Color color); // Draw cube (Vector version) -RLAPI void DrawCubeWires(Vector3 position, float width, float height, float length, Color color); // Draw cube wires -RLAPI void DrawCubeWiresV(Vector3 position, Vector3 size, Color color); // Draw cube wires (Vector version) -RLAPI void DrawSphere(Vector3 centerPos, float radius, Color color); // Draw sphere -RLAPI void DrawSphereEx(Vector3 centerPos, float radius, int rings, int slices, Color color); // Draw sphere with extended parameters -RLAPI void DrawSphereWires(Vector3 centerPos, float radius, int rings, int slices, Color color); // Draw sphere wires -RLAPI void DrawCylinder(Vector3 position, float radiusTop, float radiusBottom, float height, int slices, Color color); // Draw a cylinder/cone -RLAPI void DrawCylinderEx(Vector3 startPos, Vector3 endPos, float startRadius, float endRadius, int sides, Color color); // Draw a cylinder with base at startPos and top at endPos -RLAPI void DrawCylinderWires(Vector3 position, float radiusTop, float radiusBottom, float height, int slices, Color color); // Draw a cylinder/cone wires -RLAPI void DrawCylinderWiresEx(Vector3 startPos, Vector3 endPos, float startRadius, float endRadius, int sides, Color color); // Draw a cylinder wires with base at startPos and top at endPos -RLAPI void DrawCapsule(Vector3 startPos, Vector3 endPos, float radius, int slices, int rings, Color color); // Draw a capsule with the center of its sphere caps at startPos and endPos -RLAPI void DrawCapsuleWires(Vector3 startPos, Vector3 endPos, float radius, int slices, int rings, Color color); // Draw capsule wireframe with the center of its sphere caps at startPos and endPos -RLAPI void DrawPlane(Vector3 centerPos, Vector2 size, Color color); // Draw a plane XZ -RLAPI void DrawRay(Ray ray, Color color); // Draw a ray line -RLAPI void DrawGrid(int slices, float spacing); // Draw a grid (centered at (0, 0, 0)) - -//------------------------------------------------------------------------------------ -// Model 3d Loading and Drawing Functions (Module: models) -//------------------------------------------------------------------------------------ - -// Model management functions -RLAPI Model LoadModel(const char *fileName); // Load model from files (meshes and materials) -RLAPI Model LoadModelFromMesh(Mesh mesh); // Load model from generated mesh (default material) -RLAPI bool IsModelValid(Model model); // Check if a model is valid (loaded in GPU, VAO/VBOs) -RLAPI void UnloadModel(Model model); // Unload model (including meshes) from memory (RAM and/or VRAM) -RLAPI BoundingBox GetModelBoundingBox(Model model); // Compute model bounding box limits (considers all meshes) - -// Model drawing functions -RLAPI void DrawModel(Model model, Vector3 position, float scale, Color tint); // Draw a model (with texture if set) -RLAPI void DrawModelEx(Model model, Vector3 position, Vector3 rotationAxis, float rotationAngle, Vector3 scale, Color tint); // Draw a model with extended parameters -RLAPI void DrawModelWires(Model model, Vector3 position, float scale, Color tint); // Draw a model wires (with texture if set) -RLAPI void DrawModelWiresEx(Model model, Vector3 position, Vector3 rotationAxis, float rotationAngle, Vector3 scale, Color tint); // Draw a model wires (with texture if set) with extended parameters -RLAPI void DrawModelPoints(Model model, Vector3 position, float scale, Color tint); // Draw a model as points -RLAPI void DrawModelPointsEx(Model model, Vector3 position, Vector3 rotationAxis, float rotationAngle, Vector3 scale, Color tint); // Draw a model as points with extended parameters -RLAPI void DrawBoundingBox(BoundingBox box, Color color); // Draw bounding box (wires) -RLAPI void DrawBillboard(Camera camera, Texture2D texture, Vector3 position, float scale, Color tint); // Draw a billboard texture -RLAPI void DrawBillboardRec(Camera camera, Texture2D texture, Rectangle source, Vector3 position, Vector2 size, Color tint); // Draw a billboard texture defined by source -RLAPI void DrawBillboardPro(Camera camera, Texture2D texture, Rectangle source, Vector3 position, Vector3 up, Vector2 size, Vector2 origin, float rotation, Color tint); // Draw a billboard texture defined by source and rotation - -// Mesh management functions -RLAPI void UploadMesh(Mesh *mesh, bool dynamic); // Upload mesh vertex data in GPU and provide VAO/VBO ids -RLAPI void UpdateMeshBuffer(Mesh mesh, int index, const void *data, int dataSize, int offset); // Update mesh vertex data in GPU for a specific buffer index -RLAPI void UnloadMesh(Mesh mesh); // Unload mesh data from CPU and GPU -RLAPI void DrawMesh(Mesh mesh, Material material, Matrix transform); // Draw a 3d mesh with material and transform -RLAPI void DrawMeshInstanced(Mesh mesh, Material material, const Matrix *transforms, int instances); // Draw multiple mesh instances with material and different transforms -RLAPI BoundingBox GetMeshBoundingBox(Mesh mesh); // Compute mesh bounding box limits -RLAPI void GenMeshTangents(Mesh *mesh); // Compute mesh tangents -RLAPI bool ExportMesh(Mesh mesh, const char *fileName); // Export mesh data to file, returns true on success -RLAPI bool ExportMeshAsCode(Mesh mesh, const char *fileName); // Export mesh as code file (.h) defining multiple arrays of vertex attributes - -// Mesh generation functions -RLAPI Mesh GenMeshPoly(int sides, float radius); // Generate polygonal mesh -RLAPI Mesh GenMeshPlane(float width, float length, int resX, int resZ); // Generate plane mesh (with subdivisions) -RLAPI Mesh GenMeshCube(float width, float height, float length); // Generate cuboid mesh -RLAPI Mesh GenMeshSphere(float radius, int rings, int slices); // Generate sphere mesh (standard sphere) -RLAPI Mesh GenMeshHemiSphere(float radius, int rings, int slices); // Generate half-sphere mesh (no bottom cap) -RLAPI Mesh GenMeshCylinder(float radius, float height, int slices); // Generate cylinder mesh -RLAPI Mesh GenMeshCone(float radius, float height, int slices); // Generate cone/pyramid mesh -RLAPI Mesh GenMeshTorus(float radius, float size, int radSeg, int sides); // Generate torus mesh -RLAPI Mesh GenMeshKnot(float radius, float size, int radSeg, int sides); // Generate trefoil knot mesh -RLAPI Mesh GenMeshHeightmap(Image heightmap, Vector3 size); // Generate heightmap mesh from image data -RLAPI Mesh GenMeshCubicmap(Image cubicmap, Vector3 cubeSize); // Generate cubes-based map mesh from image data - -// Material loading/unloading functions -RLAPI Material *LoadMaterials(const char *fileName, int *materialCount); // Load materials from model file -RLAPI Material LoadMaterialDefault(void); // Load default material (Supports: DIFFUSE, SPECULAR, NORMAL maps) -RLAPI bool IsMaterialValid(Material material); // Check if a material is valid (shader assigned, map textures loaded in GPU) -RLAPI void UnloadMaterial(Material material); // Unload material from GPU memory (VRAM) -RLAPI void SetMaterialTexture(Material *material, int mapType, Texture2D texture); // Set texture for a material map type (MATERIAL_MAP_DIFFUSE, MATERIAL_MAP_SPECULAR...) -RLAPI void SetModelMeshMaterial(Model *model, int meshId, int materialId); // Set material for a mesh - -// Model animations loading/unloading functions -RLAPI ModelAnimation *LoadModelAnimations(const char *fileName, int *animCount); // Load model animations from file -RLAPI void UpdateModelAnimation(Model model, ModelAnimation anim, int frame); // Update model animation pose (CPU) -RLAPI void UpdateModelAnimationBones(Model model, ModelAnimation anim, int frame); // Update model animation mesh bone matrices (GPU skinning) -RLAPI void UnloadModelAnimation(ModelAnimation anim); // Unload animation data -RLAPI void UnloadModelAnimations(ModelAnimation *animations, int animCount); // Unload animation array data -RLAPI bool IsModelAnimationValid(Model model, ModelAnimation anim); // Check model animation skeleton match - -// Collision detection functions -RLAPI bool CheckCollisionSpheres(Vector3 center1, float radius1, Vector3 center2, float radius2); // Check collision between two spheres -RLAPI bool CheckCollisionBoxes(BoundingBox box1, BoundingBox box2); // Check collision between two bounding boxes -RLAPI bool CheckCollisionBoxSphere(BoundingBox box, Vector3 center, float radius); // Check collision between box and sphere -RLAPI RayCollision GetRayCollisionSphere(Ray ray, Vector3 center, float radius); // Get collision info between ray and sphere -RLAPI RayCollision GetRayCollisionBox(Ray ray, BoundingBox box); // Get collision info between ray and box -RLAPI RayCollision GetRayCollisionMesh(Ray ray, Mesh mesh, Matrix transform); // Get collision info between ray and mesh -RLAPI RayCollision GetRayCollisionTriangle(Ray ray, Vector3 p1, Vector3 p2, Vector3 p3); // Get collision info between ray and triangle -RLAPI RayCollision GetRayCollisionQuad(Ray ray, Vector3 p1, Vector3 p2, Vector3 p3, Vector3 p4); // Get collision info between ray and quad - -//------------------------------------------------------------------------------------ -// Audio Loading and Playing Functions (Module: audio) -//------------------------------------------------------------------------------------ -typedef void (*AudioCallback)(void *bufferData, unsigned int frames); - -// Audio device management functions -RLAPI void InitAudioDevice(void); // Initialize audio device and context -RLAPI void CloseAudioDevice(void); // Close the audio device and context -RLAPI bool IsAudioDeviceReady(void); // Check if audio device has been initialized successfully -RLAPI void SetMasterVolume(float volume); // Set master volume (listener) -RLAPI float GetMasterVolume(void); // Get master volume (listener) - -// Wave/Sound loading/unloading functions -RLAPI Wave LoadWave(const char *fileName); // Load wave data from file -RLAPI Wave LoadWaveFromMemory(const char *fileType, const unsigned char *fileData, int dataSize); // Load wave from memory buffer, fileType refers to extension: i.e. '.wav' -RLAPI bool IsWaveValid(Wave wave); // Checks if wave data is valid (data loaded and parameters) -RLAPI Sound LoadSound(const char *fileName); // Load sound from file -RLAPI Sound LoadSoundFromWave(Wave wave); // Load sound from wave data -RLAPI Sound LoadSoundAlias(Sound source); // Create a new sound that shares the same sample data as the source sound, does not own the sound data -RLAPI bool IsSoundValid(Sound sound); // Checks if a sound is valid (data loaded and buffers initialized) -RLAPI void UpdateSound(Sound sound, const void *data, int sampleCount); // Update sound buffer with new data -RLAPI void UnloadWave(Wave wave); // Unload wave data -RLAPI void UnloadSound(Sound sound); // Unload sound -RLAPI void UnloadSoundAlias(Sound alias); // Unload a sound alias (does not deallocate sample data) -RLAPI bool ExportWave(Wave wave, const char *fileName); // Export wave data to file, returns true on success -RLAPI bool ExportWaveAsCode(Wave wave, const char *fileName); // Export wave sample data to code (.h), returns true on success - -// Wave/Sound management functions -RLAPI void PlaySound(Sound sound); // Play a sound -RLAPI void StopSound(Sound sound); // Stop playing a sound -RLAPI void PauseSound(Sound sound); // Pause a sound -RLAPI void ResumeSound(Sound sound); // Resume a paused sound -RLAPI bool IsSoundPlaying(Sound sound); // Check if a sound is currently playing -RLAPI void SetSoundVolume(Sound sound, float volume); // Set volume for a sound (1.0 is max level) -RLAPI void SetSoundPitch(Sound sound, float pitch); // Set pitch for a sound (1.0 is base level) -RLAPI void SetSoundPan(Sound sound, float pan); // Set pan for a sound (0.5 is center) -RLAPI Wave WaveCopy(Wave wave); // Copy a wave to a new wave -RLAPI void WaveCrop(Wave *wave, int initFrame, int finalFrame); // Crop a wave to defined frames range -RLAPI void WaveFormat(Wave *wave, int sampleRate, int sampleSize, int channels); // Convert wave data to desired format -RLAPI float *LoadWaveSamples(Wave wave); // Load samples data from wave as a 32bit float data array -RLAPI void UnloadWaveSamples(float *samples); // Unload samples data loaded with LoadWaveSamples() - -// Music management functions -RLAPI Music LoadMusicStream(const char *fileName); // Load music stream from file -RLAPI Music LoadMusicStreamFromMemory(const char *fileType, const unsigned char *data, int dataSize); // Load music stream from data -RLAPI bool IsMusicValid(Music music); // Checks if a music stream is valid (context and buffers initialized) -RLAPI void UnloadMusicStream(Music music); // Unload music stream -RLAPI void PlayMusicStream(Music music); // Start music playing -RLAPI bool IsMusicStreamPlaying(Music music); // Check if music is playing -RLAPI void UpdateMusicStream(Music music); // Updates buffers for music streaming -RLAPI void StopMusicStream(Music music); // Stop music playing -RLAPI void PauseMusicStream(Music music); // Pause music playing -RLAPI void ResumeMusicStream(Music music); // Resume playing paused music -RLAPI void SeekMusicStream(Music music, float position); // Seek music to a position (in seconds) -RLAPI void SetMusicVolume(Music music, float volume); // Set volume for music (1.0 is max level) -RLAPI void SetMusicPitch(Music music, float pitch); // Set pitch for a music (1.0 is base level) -RLAPI void SetMusicPan(Music music, float pan); // Set pan for a music (0.5 is center) -RLAPI float GetMusicTimeLength(Music music); // Get music time length (in seconds) -RLAPI float GetMusicTimePlayed(Music music); // Get current music time played (in seconds) - -// AudioStream management functions -RLAPI AudioStream LoadAudioStream(unsigned int sampleRate, unsigned int sampleSize, unsigned int channels); // Load audio stream (to stream raw audio pcm data) -RLAPI bool IsAudioStreamValid(AudioStream stream); // Checks if an audio stream is valid (buffers initialized) -RLAPI void UnloadAudioStream(AudioStream stream); // Unload audio stream and free memory -RLAPI void UpdateAudioStream(AudioStream stream, const void *data, int frameCount); // Update audio stream buffers with data -RLAPI bool IsAudioStreamProcessed(AudioStream stream); // Check if any audio stream buffers requires refill -RLAPI void PlayAudioStream(AudioStream stream); // Play audio stream -RLAPI void PauseAudioStream(AudioStream stream); // Pause audio stream -RLAPI void ResumeAudioStream(AudioStream stream); // Resume audio stream -RLAPI bool IsAudioStreamPlaying(AudioStream stream); // Check if audio stream is playing -RLAPI void StopAudioStream(AudioStream stream); // Stop audio stream -RLAPI void SetAudioStreamVolume(AudioStream stream, float volume); // Set volume for audio stream (1.0 is max level) -RLAPI void SetAudioStreamPitch(AudioStream stream, float pitch); // Set pitch for audio stream (1.0 is base level) -RLAPI void SetAudioStreamPan(AudioStream stream, float pan); // Set pan for audio stream (0.5 is centered) -RLAPI void SetAudioStreamBufferSizeDefault(int size); // Default size for new audio streams -RLAPI void SetAudioStreamCallback(AudioStream stream, AudioCallback callback); // Audio thread callback to request new data - -RLAPI void AttachAudioStreamProcessor(AudioStream stream, AudioCallback processor); // Attach audio stream processor to stream, receives the samples as 'float' -RLAPI void DetachAudioStreamProcessor(AudioStream stream, AudioCallback processor); // Detach audio stream processor from stream - -RLAPI void AttachAudioMixedProcessor(AudioCallback processor); // Attach audio stream processor to the entire audio pipeline, receives the samples as 'float' -RLAPI void DetachAudioMixedProcessor(AudioCallback processor); // Detach audio stream processor from the entire audio pipeline - -#if defined(__cplusplus) -} -#endif - -#endif // RAYLIB_H diff --git a/third_party/raylib/include/raymath.h b/third_party/raylib/include/raymath.h deleted file mode 100644 index 5b5e4c74ff..0000000000 --- a/third_party/raylib/include/raymath.h +++ /dev/null @@ -1,2949 +0,0 @@ -/********************************************************************************************** -* -* raymath v2.0 - Math functions to work with Vector2, Vector3, Matrix and Quaternions -* -* CONVENTIONS: -* - Matrix structure is defined as row-major (memory layout) but parameters naming AND all -* math operations performed by the library consider the structure as it was column-major -* It is like transposed versions of the matrices are used for all the maths -* It benefits some functions making them cache-friendly and also avoids matrix -* transpositions sometimes required by OpenGL -* Example: In memory order, row0 is [m0 m4 m8 m12] but in semantic math row0 is [m0 m1 m2 m3] -* - Functions are always self-contained, no function use another raymath function inside, -* required code is directly re-implemented inside -* - Functions input parameters are always received by value (2 unavoidable exceptions) -* - Functions use always a "result" variable for return (except C++ operators) -* - Functions are always defined inline -* - Angles are always in radians (DEG2RAD/RAD2DEG macros provided for convenience) -* - No compound literals used to make sure libray is compatible with C++ -* -* CONFIGURATION: -* #define RAYMATH_IMPLEMENTATION -* Generates the implementation of the library into the included file. -* If not defined, the library is in header only mode and can be included in other headers -* or source files without problems. But only ONE file should hold the implementation. -* -* #define RAYMATH_STATIC_INLINE -* Define static inline functions code, so #include header suffices for use. -* This may use up lots of memory. -* -* #define RAYMATH_DISABLE_CPP_OPERATORS -* Disables C++ operator overloads for raymath types. -* -* LICENSE: zlib/libpng -* -* Copyright (c) 2015-2024 Ramon Santamaria (@raysan5) -* -* This software is provided "as-is", without any express or implied warranty. In no event -* will the authors be held liable for any damages arising from the use of this software. -* -* Permission is granted to anyone to use this software for any purpose, including commercial -* applications, and to alter it and redistribute it freely, subject to the following restrictions: -* -* 1. The origin of this software must not be misrepresented; you must not claim that you -* wrote the original software. If you use this software in a product, an acknowledgment -* in the product documentation would be appreciated but is not required. -* -* 2. Altered source versions must be plainly marked as such, and must not be misrepresented -* as being the original software. -* -* 3. This notice may not be removed or altered from any source distribution. -* -**********************************************************************************************/ - -#ifndef RAYMATH_H -#define RAYMATH_H - -#if defined(RAYMATH_IMPLEMENTATION) && defined(RAYMATH_STATIC_INLINE) - #error "Specifying both RAYMATH_IMPLEMENTATION and RAYMATH_STATIC_INLINE is contradictory" -#endif - -// Function specifiers definition -#if defined(RAYMATH_IMPLEMENTATION) - #if defined(_WIN32) && defined(BUILD_LIBTYPE_SHARED) - #define RMAPI __declspec(dllexport) extern inline // We are building raylib as a Win32 shared library (.dll) - #elif defined(BUILD_LIBTYPE_SHARED) - #define RMAPI __attribute__((visibility("default"))) // We are building raylib as a Unix shared library (.so/.dylib) - #elif defined(_WIN32) && defined(USE_LIBTYPE_SHARED) - #define RMAPI __declspec(dllimport) // We are using raylib as a Win32 shared library (.dll) - #else - #define RMAPI extern inline // Provide external definition - #endif -#elif defined(RAYMATH_STATIC_INLINE) - #define RMAPI static inline // Functions may be inlined, no external out-of-line definition -#else - #if defined(__TINYC__) - #define RMAPI static inline // plain inline not supported by tinycc (See issue #435) - #else - #define RMAPI inline // Functions may be inlined or external definition used - #endif -#endif - - -//---------------------------------------------------------------------------------- -// Defines and Macros -//---------------------------------------------------------------------------------- -#ifndef PI - #define PI 3.14159265358979323846f -#endif - -#ifndef EPSILON - #define EPSILON 0.000001f -#endif - -#ifndef DEG2RAD - #define DEG2RAD (PI/180.0f) -#endif - -#ifndef RAD2DEG - #define RAD2DEG (180.0f/PI) -#endif - -// Get float vector for Matrix -#ifndef MatrixToFloat - #define MatrixToFloat(mat) (MatrixToFloatV(mat).v) -#endif - -// Get float vector for Vector3 -#ifndef Vector3ToFloat - #define Vector3ToFloat(vec) (Vector3ToFloatV(vec).v) -#endif - -//---------------------------------------------------------------------------------- -// Types and Structures Definition -//---------------------------------------------------------------------------------- -#if !defined(RL_VECTOR2_TYPE) -// Vector2 type -typedef struct Vector2 { - float x; - float y; -} Vector2; -#define RL_VECTOR2_TYPE -#endif - -#if !defined(RL_VECTOR3_TYPE) -// Vector3 type -typedef struct Vector3 { - float x; - float y; - float z; -} Vector3; -#define RL_VECTOR3_TYPE -#endif - -#if !defined(RL_VECTOR4_TYPE) -// Vector4 type -typedef struct Vector4 { - float x; - float y; - float z; - float w; -} Vector4; -#define RL_VECTOR4_TYPE -#endif - -#if !defined(RL_QUATERNION_TYPE) -// Quaternion type -typedef Vector4 Quaternion; -#define RL_QUATERNION_TYPE -#endif - -#if !defined(RL_MATRIX_TYPE) -// Matrix type (OpenGL style 4x4 - right handed, column major) -typedef struct Matrix { - float m0, m4, m8, m12; // Matrix first row (4 components) - float m1, m5, m9, m13; // Matrix second row (4 components) - float m2, m6, m10, m14; // Matrix third row (4 components) - float m3, m7, m11, m15; // Matrix fourth row (4 components) -} Matrix; -#define RL_MATRIX_TYPE -#endif - -// NOTE: Helper types to be used instead of array return types for *ToFloat functions -typedef struct float3 { - float v[3]; -} float3; - -typedef struct float16 { - float v[16]; -} float16; - -#include // Required for: sinf(), cosf(), tan(), atan2f(), sqrtf(), floor(), fminf(), fmaxf(), fabsf() - -//---------------------------------------------------------------------------------- -// Module Functions Definition - Utils math -//---------------------------------------------------------------------------------- - -// Clamp float value -RMAPI float Clamp(float value, float min, float max) -{ - float result = (value < min)? min : value; - - if (result > max) result = max; - - return result; -} - -// Calculate linear interpolation between two floats -RMAPI float Lerp(float start, float end, float amount) -{ - float result = start + amount*(end - start); - - return result; -} - -// Normalize input value within input range -RMAPI float Normalize(float value, float start, float end) -{ - float result = (value - start)/(end - start); - - return result; -} - -// Remap input value within input range to output range -RMAPI float Remap(float value, float inputStart, float inputEnd, float outputStart, float outputEnd) -{ - float result = (value - inputStart)/(inputEnd - inputStart)*(outputEnd - outputStart) + outputStart; - - return result; -} - -// Wrap input value from min to max -RMAPI float Wrap(float value, float min, float max) -{ - float result = value - (max - min)*floorf((value - min)/(max - min)); - - return result; -} - -// Check whether two given floats are almost equal -RMAPI int FloatEquals(float x, float y) -{ -#if !defined(EPSILON) - #define EPSILON 0.000001f -#endif - - int result = (fabsf(x - y)) <= (EPSILON*fmaxf(1.0f, fmaxf(fabsf(x), fabsf(y)))); - - return result; -} - -//---------------------------------------------------------------------------------- -// Module Functions Definition - Vector2 math -//---------------------------------------------------------------------------------- - -// Vector with components value 0.0f -RMAPI Vector2 Vector2Zero(void) -{ - Vector2 result = { 0.0f, 0.0f }; - - return result; -} - -// Vector with components value 1.0f -RMAPI Vector2 Vector2One(void) -{ - Vector2 result = { 1.0f, 1.0f }; - - return result; -} - -// Add two vectors (v1 + v2) -RMAPI Vector2 Vector2Add(Vector2 v1, Vector2 v2) -{ - Vector2 result = { v1.x + v2.x, v1.y + v2.y }; - - return result; -} - -// Add vector and float value -RMAPI Vector2 Vector2AddValue(Vector2 v, float add) -{ - Vector2 result = { v.x + add, v.y + add }; - - return result; -} - -// Subtract two vectors (v1 - v2) -RMAPI Vector2 Vector2Subtract(Vector2 v1, Vector2 v2) -{ - Vector2 result = { v1.x - v2.x, v1.y - v2.y }; - - return result; -} - -// Subtract vector by float value -RMAPI Vector2 Vector2SubtractValue(Vector2 v, float sub) -{ - Vector2 result = { v.x - sub, v.y - sub }; - - return result; -} - -// Calculate vector length -RMAPI float Vector2Length(Vector2 v) -{ - float result = sqrtf((v.x*v.x) + (v.y*v.y)); - - return result; -} - -// Calculate vector square length -RMAPI float Vector2LengthSqr(Vector2 v) -{ - float result = (v.x*v.x) + (v.y*v.y); - - return result; -} - -// Calculate two vectors dot product -RMAPI float Vector2DotProduct(Vector2 v1, Vector2 v2) -{ - float result = (v1.x*v2.x + v1.y*v2.y); - - return result; -} - -// Calculate two vectors cross product -RMAPI float Vector2CrossProduct(Vector2 v1, Vector2 v2) -{ - float result = (v1.x*v2.y - v1.y*v2.x); - - return result; -} - -// Calculate distance between two vectors -RMAPI float Vector2Distance(Vector2 v1, Vector2 v2) -{ - float result = sqrtf((v1.x - v2.x)*(v1.x - v2.x) + (v1.y - v2.y)*(v1.y - v2.y)); - - return result; -} - -// Calculate square distance between two vectors -RMAPI float Vector2DistanceSqr(Vector2 v1, Vector2 v2) -{ - float result = ((v1.x - v2.x)*(v1.x - v2.x) + (v1.y - v2.y)*(v1.y - v2.y)); - - return result; -} - -// Calculate angle between two vectors -// NOTE: Angle is calculated from origin point (0, 0) -RMAPI float Vector2Angle(Vector2 v1, Vector2 v2) -{ - float result = 0.0f; - - float dot = v1.x*v2.x + v1.y*v2.y; - float det = v1.x*v2.y - v1.y*v2.x; - - result = atan2f(det, dot); - - return result; -} - -// Calculate angle defined by a two vectors line -// NOTE: Parameters need to be normalized -// Current implementation should be aligned with glm::angle -RMAPI float Vector2LineAngle(Vector2 start, Vector2 end) -{ - float result = 0.0f; - - // TODO(10/9/2023): Currently angles move clockwise, determine if this is wanted behavior - result = -atan2f(end.y - start.y, end.x - start.x); - - return result; -} - -// Scale vector (multiply by value) -RMAPI Vector2 Vector2Scale(Vector2 v, float scale) -{ - Vector2 result = { v.x*scale, v.y*scale }; - - return result; -} - -// Multiply vector by vector -RMAPI Vector2 Vector2Multiply(Vector2 v1, Vector2 v2) -{ - Vector2 result = { v1.x*v2.x, v1.y*v2.y }; - - return result; -} - -// Negate vector -RMAPI Vector2 Vector2Negate(Vector2 v) -{ - Vector2 result = { -v.x, -v.y }; - - return result; -} - -// Divide vector by vector -RMAPI Vector2 Vector2Divide(Vector2 v1, Vector2 v2) -{ - Vector2 result = { v1.x/v2.x, v1.y/v2.y }; - - return result; -} - -// Normalize provided vector -RMAPI Vector2 Vector2Normalize(Vector2 v) -{ - Vector2 result = { 0 }; - float length = sqrtf((v.x*v.x) + (v.y*v.y)); - - if (length > 0) - { - float ilength = 1.0f/length; - result.x = v.x*ilength; - result.y = v.y*ilength; - } - - return result; -} - -// Transforms a Vector2 by a given Matrix -RMAPI Vector2 Vector2Transform(Vector2 v, Matrix mat) -{ - Vector2 result = { 0 }; - - float x = v.x; - float y = v.y; - float z = 0; - - result.x = mat.m0*x + mat.m4*y + mat.m8*z + mat.m12; - result.y = mat.m1*x + mat.m5*y + mat.m9*z + mat.m13; - - return result; -} - -// Calculate linear interpolation between two vectors -RMAPI Vector2 Vector2Lerp(Vector2 v1, Vector2 v2, float amount) -{ - Vector2 result = { 0 }; - - result.x = v1.x + amount*(v2.x - v1.x); - result.y = v1.y + amount*(v2.y - v1.y); - - return result; -} - -// Calculate reflected vector to normal -RMAPI Vector2 Vector2Reflect(Vector2 v, Vector2 normal) -{ - Vector2 result = { 0 }; - - float dotProduct = (v.x*normal.x + v.y*normal.y); // Dot product - - result.x = v.x - (2.0f*normal.x)*dotProduct; - result.y = v.y - (2.0f*normal.y)*dotProduct; - - return result; -} - -// Get min value for each pair of components -RMAPI Vector2 Vector2Min(Vector2 v1, Vector2 v2) -{ - Vector2 result = { 0 }; - - result.x = fminf(v1.x, v2.x); - result.y = fminf(v1.y, v2.y); - - return result; -} - -// Get max value for each pair of components -RMAPI Vector2 Vector2Max(Vector2 v1, Vector2 v2) -{ - Vector2 result = { 0 }; - - result.x = fmaxf(v1.x, v2.x); - result.y = fmaxf(v1.y, v2.y); - - return result; -} - -// Rotate vector by angle -RMAPI Vector2 Vector2Rotate(Vector2 v, float angle) -{ - Vector2 result = { 0 }; - - float cosres = cosf(angle); - float sinres = sinf(angle); - - result.x = v.x*cosres - v.y*sinres; - result.y = v.x*sinres + v.y*cosres; - - return result; -} - -// Move Vector towards target -RMAPI Vector2 Vector2MoveTowards(Vector2 v, Vector2 target, float maxDistance) -{ - Vector2 result = { 0 }; - - float dx = target.x - v.x; - float dy = target.y - v.y; - float value = (dx*dx) + (dy*dy); - - if ((value == 0) || ((maxDistance >= 0) && (value <= maxDistance*maxDistance))) return target; - - float dist = sqrtf(value); - - result.x = v.x + dx/dist*maxDistance; - result.y = v.y + dy/dist*maxDistance; - - return result; -} - -// Invert the given vector -RMAPI Vector2 Vector2Invert(Vector2 v) -{ - Vector2 result = { 1.0f/v.x, 1.0f/v.y }; - - return result; -} - -// Clamp the components of the vector between -// min and max values specified by the given vectors -RMAPI Vector2 Vector2Clamp(Vector2 v, Vector2 min, Vector2 max) -{ - Vector2 result = { 0 }; - - result.x = fminf(max.x, fmaxf(min.x, v.x)); - result.y = fminf(max.y, fmaxf(min.y, v.y)); - - return result; -} - -// Clamp the magnitude of the vector between two min and max values -RMAPI Vector2 Vector2ClampValue(Vector2 v, float min, float max) -{ - Vector2 result = v; - - float length = (v.x*v.x) + (v.y*v.y); - if (length > 0.0f) - { - length = sqrtf(length); - - float scale = 1; // By default, 1 as the neutral element. - if (length < min) - { - scale = min/length; - } - else if (length > max) - { - scale = max/length; - } - - result.x = v.x*scale; - result.y = v.y*scale; - } - - return result; -} - -// Check whether two given vectors are almost equal -RMAPI int Vector2Equals(Vector2 p, Vector2 q) -{ -#if !defined(EPSILON) - #define EPSILON 0.000001f -#endif - - int result = ((fabsf(p.x - q.x)) <= (EPSILON*fmaxf(1.0f, fmaxf(fabsf(p.x), fabsf(q.x))))) && - ((fabsf(p.y - q.y)) <= (EPSILON*fmaxf(1.0f, fmaxf(fabsf(p.y), fabsf(q.y))))); - - return result; -} - -// Compute the direction of a refracted ray -// v: normalized direction of the incoming ray -// n: normalized normal vector of the interface of two optical media -// r: ratio of the refractive index of the medium from where the ray comes -// to the refractive index of the medium on the other side of the surface -RMAPI Vector2 Vector2Refract(Vector2 v, Vector2 n, float r) -{ - Vector2 result = { 0 }; - - float dot = v.x*n.x + v.y*n.y; - float d = 1.0f - r*r*(1.0f - dot*dot); - - if (d >= 0.0f) - { - d = sqrtf(d); - v.x = r*v.x - (r*dot + d)*n.x; - v.y = r*v.y - (r*dot + d)*n.y; - - result = v; - } - - return result; -} - - -//---------------------------------------------------------------------------------- -// Module Functions Definition - Vector3 math -//---------------------------------------------------------------------------------- - -// Vector with components value 0.0f -RMAPI Vector3 Vector3Zero(void) -{ - Vector3 result = { 0.0f, 0.0f, 0.0f }; - - return result; -} - -// Vector with components value 1.0f -RMAPI Vector3 Vector3One(void) -{ - Vector3 result = { 1.0f, 1.0f, 1.0f }; - - return result; -} - -// Add two vectors -RMAPI Vector3 Vector3Add(Vector3 v1, Vector3 v2) -{ - Vector3 result = { v1.x + v2.x, v1.y + v2.y, v1.z + v2.z }; - - return result; -} - -// Add vector and float value -RMAPI Vector3 Vector3AddValue(Vector3 v, float add) -{ - Vector3 result = { v.x + add, v.y + add, v.z + add }; - - return result; -} - -// Subtract two vectors -RMAPI Vector3 Vector3Subtract(Vector3 v1, Vector3 v2) -{ - Vector3 result = { v1.x - v2.x, v1.y - v2.y, v1.z - v2.z }; - - return result; -} - -// Subtract vector by float value -RMAPI Vector3 Vector3SubtractValue(Vector3 v, float sub) -{ - Vector3 result = { v.x - sub, v.y - sub, v.z - sub }; - - return result; -} - -// Multiply vector by scalar -RMAPI Vector3 Vector3Scale(Vector3 v, float scalar) -{ - Vector3 result = { v.x*scalar, v.y*scalar, v.z*scalar }; - - return result; -} - -// Multiply vector by vector -RMAPI Vector3 Vector3Multiply(Vector3 v1, Vector3 v2) -{ - Vector3 result = { v1.x*v2.x, v1.y*v2.y, v1.z*v2.z }; - - return result; -} - -// Calculate two vectors cross product -RMAPI Vector3 Vector3CrossProduct(Vector3 v1, Vector3 v2) -{ - Vector3 result = { v1.y*v2.z - v1.z*v2.y, v1.z*v2.x - v1.x*v2.z, v1.x*v2.y - v1.y*v2.x }; - - return result; -} - -// Calculate one vector perpendicular vector -RMAPI Vector3 Vector3Perpendicular(Vector3 v) -{ - Vector3 result = { 0 }; - - float min = fabsf(v.x); - Vector3 cardinalAxis = {1.0f, 0.0f, 0.0f}; - - if (fabsf(v.y) < min) - { - min = fabsf(v.y); - Vector3 tmp = {0.0f, 1.0f, 0.0f}; - cardinalAxis = tmp; - } - - if (fabsf(v.z) < min) - { - Vector3 tmp = {0.0f, 0.0f, 1.0f}; - cardinalAxis = tmp; - } - - // Cross product between vectors - result.x = v.y*cardinalAxis.z - v.z*cardinalAxis.y; - result.y = v.z*cardinalAxis.x - v.x*cardinalAxis.z; - result.z = v.x*cardinalAxis.y - v.y*cardinalAxis.x; - - return result; -} - -// Calculate vector length -RMAPI float Vector3Length(const Vector3 v) -{ - float result = sqrtf(v.x*v.x + v.y*v.y + v.z*v.z); - - return result; -} - -// Calculate vector square length -RMAPI float Vector3LengthSqr(const Vector3 v) -{ - float result = v.x*v.x + v.y*v.y + v.z*v.z; - - return result; -} - -// Calculate two vectors dot product -RMAPI float Vector3DotProduct(Vector3 v1, Vector3 v2) -{ - float result = (v1.x*v2.x + v1.y*v2.y + v1.z*v2.z); - - return result; -} - -// Calculate distance between two vectors -RMAPI float Vector3Distance(Vector3 v1, Vector3 v2) -{ - float result = 0.0f; - - float dx = v2.x - v1.x; - float dy = v2.y - v1.y; - float dz = v2.z - v1.z; - result = sqrtf(dx*dx + dy*dy + dz*dz); - - return result; -} - -// Calculate square distance between two vectors -RMAPI float Vector3DistanceSqr(Vector3 v1, Vector3 v2) -{ - float result = 0.0f; - - float dx = v2.x - v1.x; - float dy = v2.y - v1.y; - float dz = v2.z - v1.z; - result = dx*dx + dy*dy + dz*dz; - - return result; -} - -// Calculate angle between two vectors -RMAPI float Vector3Angle(Vector3 v1, Vector3 v2) -{ - float result = 0.0f; - - Vector3 cross = { v1.y*v2.z - v1.z*v2.y, v1.z*v2.x - v1.x*v2.z, v1.x*v2.y - v1.y*v2.x }; - float len = sqrtf(cross.x*cross.x + cross.y*cross.y + cross.z*cross.z); - float dot = (v1.x*v2.x + v1.y*v2.y + v1.z*v2.z); - result = atan2f(len, dot); - - return result; -} - -// Negate provided vector (invert direction) -RMAPI Vector3 Vector3Negate(Vector3 v) -{ - Vector3 result = { -v.x, -v.y, -v.z }; - - return result; -} - -// Divide vector by vector -RMAPI Vector3 Vector3Divide(Vector3 v1, Vector3 v2) -{ - Vector3 result = { v1.x/v2.x, v1.y/v2.y, v1.z/v2.z }; - - return result; -} - -// Normalize provided vector -RMAPI Vector3 Vector3Normalize(Vector3 v) -{ - Vector3 result = v; - - float length = sqrtf(v.x*v.x + v.y*v.y + v.z*v.z); - if (length != 0.0f) - { - float ilength = 1.0f/length; - - result.x *= ilength; - result.y *= ilength; - result.z *= ilength; - } - - return result; -} - -//Calculate the projection of the vector v1 on to v2 -RMAPI Vector3 Vector3Project(Vector3 v1, Vector3 v2) -{ - Vector3 result = { 0 }; - - float v1dv2 = (v1.x*v2.x + v1.y*v2.y + v1.z*v2.z); - float v2dv2 = (v2.x*v2.x + v2.y*v2.y + v2.z*v2.z); - - float mag = v1dv2/v2dv2; - - result.x = v2.x*mag; - result.y = v2.y*mag; - result.z = v2.z*mag; - - return result; -} - -//Calculate the rejection of the vector v1 on to v2 -RMAPI Vector3 Vector3Reject(Vector3 v1, Vector3 v2) -{ - Vector3 result = { 0 }; - - float v1dv2 = (v1.x*v2.x + v1.y*v2.y + v1.z*v2.z); - float v2dv2 = (v2.x*v2.x + v2.y*v2.y + v2.z*v2.z); - - float mag = v1dv2/v2dv2; - - result.x = v1.x - (v2.x*mag); - result.y = v1.y - (v2.y*mag); - result.z = v1.z - (v2.z*mag); - - return result; -} - -// Orthonormalize provided vectors -// Makes vectors normalized and orthogonal to each other -// Gram-Schmidt function implementation -RMAPI void Vector3OrthoNormalize(Vector3 *v1, Vector3 *v2) -{ - float length = 0.0f; - float ilength = 0.0f; - - // Vector3Normalize(*v1); - Vector3 v = *v1; - length = sqrtf(v.x*v.x + v.y*v.y + v.z*v.z); - if (length == 0.0f) length = 1.0f; - ilength = 1.0f/length; - v1->x *= ilength; - v1->y *= ilength; - v1->z *= ilength; - - // Vector3CrossProduct(*v1, *v2) - Vector3 vn1 = { v1->y*v2->z - v1->z*v2->y, v1->z*v2->x - v1->x*v2->z, v1->x*v2->y - v1->y*v2->x }; - - // Vector3Normalize(vn1); - v = vn1; - length = sqrtf(v.x*v.x + v.y*v.y + v.z*v.z); - if (length == 0.0f) length = 1.0f; - ilength = 1.0f/length; - vn1.x *= ilength; - vn1.y *= ilength; - vn1.z *= ilength; - - // Vector3CrossProduct(vn1, *v1) - Vector3 vn2 = { vn1.y*v1->z - vn1.z*v1->y, vn1.z*v1->x - vn1.x*v1->z, vn1.x*v1->y - vn1.y*v1->x }; - - *v2 = vn2; -} - -// Transforms a Vector3 by a given Matrix -RMAPI Vector3 Vector3Transform(Vector3 v, Matrix mat) -{ - Vector3 result = { 0 }; - - float x = v.x; - float y = v.y; - float z = v.z; - - result.x = mat.m0*x + mat.m4*y + mat.m8*z + mat.m12; - result.y = mat.m1*x + mat.m5*y + mat.m9*z + mat.m13; - result.z = mat.m2*x + mat.m6*y + mat.m10*z + mat.m14; - - return result; -} - -// Transform a vector by quaternion rotation -RMAPI Vector3 Vector3RotateByQuaternion(Vector3 v, Quaternion q) -{ - Vector3 result = { 0 }; - - result.x = v.x*(q.x*q.x + q.w*q.w - q.y*q.y - q.z*q.z) + v.y*(2*q.x*q.y - 2*q.w*q.z) + v.z*(2*q.x*q.z + 2*q.w*q.y); - result.y = v.x*(2*q.w*q.z + 2*q.x*q.y) + v.y*(q.w*q.w - q.x*q.x + q.y*q.y - q.z*q.z) + v.z*(-2*q.w*q.x + 2*q.y*q.z); - result.z = v.x*(-2*q.w*q.y + 2*q.x*q.z) + v.y*(2*q.w*q.x + 2*q.y*q.z)+ v.z*(q.w*q.w - q.x*q.x - q.y*q.y + q.z*q.z); - - return result; -} - -// Rotates a vector around an axis -RMAPI Vector3 Vector3RotateByAxisAngle(Vector3 v, Vector3 axis, float angle) -{ - // Using Euler-Rodrigues Formula - // Ref.: https://en.wikipedia.org/w/index.php?title=Euler%E2%80%93Rodrigues_formula - - Vector3 result = v; - - // Vector3Normalize(axis); - float length = sqrtf(axis.x*axis.x + axis.y*axis.y + axis.z*axis.z); - if (length == 0.0f) length = 1.0f; - float ilength = 1.0f/length; - axis.x *= ilength; - axis.y *= ilength; - axis.z *= ilength; - - angle /= 2.0f; - float a = sinf(angle); - float b = axis.x*a; - float c = axis.y*a; - float d = axis.z*a; - a = cosf(angle); - Vector3 w = { b, c, d }; - - // Vector3CrossProduct(w, v) - Vector3 wv = { w.y*v.z - w.z*v.y, w.z*v.x - w.x*v.z, w.x*v.y - w.y*v.x }; - - // Vector3CrossProduct(w, wv) - Vector3 wwv = { w.y*wv.z - w.z*wv.y, w.z*wv.x - w.x*wv.z, w.x*wv.y - w.y*wv.x }; - - // Vector3Scale(wv, 2*a) - a *= 2; - wv.x *= a; - wv.y *= a; - wv.z *= a; - - // Vector3Scale(wwv, 2) - wwv.x *= 2; - wwv.y *= 2; - wwv.z *= 2; - - result.x += wv.x; - result.y += wv.y; - result.z += wv.z; - - result.x += wwv.x; - result.y += wwv.y; - result.z += wwv.z; - - return result; -} - -// Move Vector towards target -RMAPI Vector3 Vector3MoveTowards(Vector3 v, Vector3 target, float maxDistance) -{ - Vector3 result = { 0 }; - - float dx = target.x - v.x; - float dy = target.y - v.y; - float dz = target.z - v.z; - float value = (dx*dx) + (dy*dy) + (dz*dz); - - if ((value == 0) || ((maxDistance >= 0) && (value <= maxDistance*maxDistance))) return target; - - float dist = sqrtf(value); - - result.x = v.x + dx/dist*maxDistance; - result.y = v.y + dy/dist*maxDistance; - result.z = v.z + dz/dist*maxDistance; - - return result; -} - -// Calculate linear interpolation between two vectors -RMAPI Vector3 Vector3Lerp(Vector3 v1, Vector3 v2, float amount) -{ - Vector3 result = { 0 }; - - result.x = v1.x + amount*(v2.x - v1.x); - result.y = v1.y + amount*(v2.y - v1.y); - result.z = v1.z + amount*(v2.z - v1.z); - - return result; -} - -// Calculate cubic hermite interpolation between two vectors and their tangents -// as described in the GLTF 2.0 specification: https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#interpolation-cubic -RMAPI Vector3 Vector3CubicHermite(Vector3 v1, Vector3 tangent1, Vector3 v2, Vector3 tangent2, float amount) -{ - Vector3 result = { 0 }; - - float amountPow2 = amount*amount; - float amountPow3 = amount*amount*amount; - - result.x = (2*amountPow3 - 3*amountPow2 + 1)*v1.x + (amountPow3 - 2*amountPow2 + amount)*tangent1.x + (-2*amountPow3 + 3*amountPow2)*v2.x + (amountPow3 - amountPow2)*tangent2.x; - result.y = (2*amountPow3 - 3*amountPow2 + 1)*v1.y + (amountPow3 - 2*amountPow2 + amount)*tangent1.y + (-2*amountPow3 + 3*amountPow2)*v2.y + (amountPow3 - amountPow2)*tangent2.y; - result.z = (2*amountPow3 - 3*amountPow2 + 1)*v1.z + (amountPow3 - 2*amountPow2 + amount)*tangent1.z + (-2*amountPow3 + 3*amountPow2)*v2.z + (amountPow3 - amountPow2)*tangent2.z; - - return result; -} - -// Calculate reflected vector to normal -RMAPI Vector3 Vector3Reflect(Vector3 v, Vector3 normal) -{ - Vector3 result = { 0 }; - - // I is the original vector - // N is the normal of the incident plane - // R = I - (2*N*(DotProduct[I, N])) - - float dotProduct = (v.x*normal.x + v.y*normal.y + v.z*normal.z); - - result.x = v.x - (2.0f*normal.x)*dotProduct; - result.y = v.y - (2.0f*normal.y)*dotProduct; - result.z = v.z - (2.0f*normal.z)*dotProduct; - - return result; -} - -// Get min value for each pair of components -RMAPI Vector3 Vector3Min(Vector3 v1, Vector3 v2) -{ - Vector3 result = { 0 }; - - result.x = fminf(v1.x, v2.x); - result.y = fminf(v1.y, v2.y); - result.z = fminf(v1.z, v2.z); - - return result; -} - -// Get max value for each pair of components -RMAPI Vector3 Vector3Max(Vector3 v1, Vector3 v2) -{ - Vector3 result = { 0 }; - - result.x = fmaxf(v1.x, v2.x); - result.y = fmaxf(v1.y, v2.y); - result.z = fmaxf(v1.z, v2.z); - - return result; -} - -// Compute barycenter coordinates (u, v, w) for point p with respect to triangle (a, b, c) -// NOTE: Assumes P is on the plane of the triangle -RMAPI Vector3 Vector3Barycenter(Vector3 p, Vector3 a, Vector3 b, Vector3 c) -{ - Vector3 result = { 0 }; - - Vector3 v0 = { b.x - a.x, b.y - a.y, b.z - a.z }; // Vector3Subtract(b, a) - Vector3 v1 = { c.x - a.x, c.y - a.y, c.z - a.z }; // Vector3Subtract(c, a) - Vector3 v2 = { p.x - a.x, p.y - a.y, p.z - a.z }; // Vector3Subtract(p, a) - float d00 = (v0.x*v0.x + v0.y*v0.y + v0.z*v0.z); // Vector3DotProduct(v0, v0) - float d01 = (v0.x*v1.x + v0.y*v1.y + v0.z*v1.z); // Vector3DotProduct(v0, v1) - float d11 = (v1.x*v1.x + v1.y*v1.y + v1.z*v1.z); // Vector3DotProduct(v1, v1) - float d20 = (v2.x*v0.x + v2.y*v0.y + v2.z*v0.z); // Vector3DotProduct(v2, v0) - float d21 = (v2.x*v1.x + v2.y*v1.y + v2.z*v1.z); // Vector3DotProduct(v2, v1) - - float denom = d00*d11 - d01*d01; - - result.y = (d11*d20 - d01*d21)/denom; - result.z = (d00*d21 - d01*d20)/denom; - result.x = 1.0f - (result.z + result.y); - - return result; -} - -// Projects a Vector3 from screen space into object space -// NOTE: We are avoiding calling other raymath functions despite available -RMAPI Vector3 Vector3Unproject(Vector3 source, Matrix projection, Matrix view) -{ - Vector3 result = { 0 }; - - // Calculate unprojected matrix (multiply view matrix by projection matrix) and invert it - Matrix matViewProj = { // MatrixMultiply(view, projection); - view.m0*projection.m0 + view.m1*projection.m4 + view.m2*projection.m8 + view.m3*projection.m12, - view.m0*projection.m1 + view.m1*projection.m5 + view.m2*projection.m9 + view.m3*projection.m13, - view.m0*projection.m2 + view.m1*projection.m6 + view.m2*projection.m10 + view.m3*projection.m14, - view.m0*projection.m3 + view.m1*projection.m7 + view.m2*projection.m11 + view.m3*projection.m15, - view.m4*projection.m0 + view.m5*projection.m4 + view.m6*projection.m8 + view.m7*projection.m12, - view.m4*projection.m1 + view.m5*projection.m5 + view.m6*projection.m9 + view.m7*projection.m13, - view.m4*projection.m2 + view.m5*projection.m6 + view.m6*projection.m10 + view.m7*projection.m14, - view.m4*projection.m3 + view.m5*projection.m7 + view.m6*projection.m11 + view.m7*projection.m15, - view.m8*projection.m0 + view.m9*projection.m4 + view.m10*projection.m8 + view.m11*projection.m12, - view.m8*projection.m1 + view.m9*projection.m5 + view.m10*projection.m9 + view.m11*projection.m13, - view.m8*projection.m2 + view.m9*projection.m6 + view.m10*projection.m10 + view.m11*projection.m14, - view.m8*projection.m3 + view.m9*projection.m7 + view.m10*projection.m11 + view.m11*projection.m15, - view.m12*projection.m0 + view.m13*projection.m4 + view.m14*projection.m8 + view.m15*projection.m12, - view.m12*projection.m1 + view.m13*projection.m5 + view.m14*projection.m9 + view.m15*projection.m13, - view.m12*projection.m2 + view.m13*projection.m6 + view.m14*projection.m10 + view.m15*projection.m14, - view.m12*projection.m3 + view.m13*projection.m7 + view.m14*projection.m11 + view.m15*projection.m15 }; - - // Calculate inverted matrix -> MatrixInvert(matViewProj); - // Cache the matrix values (speed optimization) - float a00 = matViewProj.m0, a01 = matViewProj.m1, a02 = matViewProj.m2, a03 = matViewProj.m3; - float a10 = matViewProj.m4, a11 = matViewProj.m5, a12 = matViewProj.m6, a13 = matViewProj.m7; - float a20 = matViewProj.m8, a21 = matViewProj.m9, a22 = matViewProj.m10, a23 = matViewProj.m11; - float a30 = matViewProj.m12, a31 = matViewProj.m13, a32 = matViewProj.m14, a33 = matViewProj.m15; - - float b00 = a00*a11 - a01*a10; - float b01 = a00*a12 - a02*a10; - float b02 = a00*a13 - a03*a10; - float b03 = a01*a12 - a02*a11; - float b04 = a01*a13 - a03*a11; - float b05 = a02*a13 - a03*a12; - float b06 = a20*a31 - a21*a30; - float b07 = a20*a32 - a22*a30; - float b08 = a20*a33 - a23*a30; - float b09 = a21*a32 - a22*a31; - float b10 = a21*a33 - a23*a31; - float b11 = a22*a33 - a23*a32; - - // Calculate the invert determinant (inlined to avoid double-caching) - float invDet = 1.0f/(b00*b11 - b01*b10 + b02*b09 + b03*b08 - b04*b07 + b05*b06); - - Matrix matViewProjInv = { - (a11*b11 - a12*b10 + a13*b09)*invDet, - (-a01*b11 + a02*b10 - a03*b09)*invDet, - (a31*b05 - a32*b04 + a33*b03)*invDet, - (-a21*b05 + a22*b04 - a23*b03)*invDet, - (-a10*b11 + a12*b08 - a13*b07)*invDet, - (a00*b11 - a02*b08 + a03*b07)*invDet, - (-a30*b05 + a32*b02 - a33*b01)*invDet, - (a20*b05 - a22*b02 + a23*b01)*invDet, - (a10*b10 - a11*b08 + a13*b06)*invDet, - (-a00*b10 + a01*b08 - a03*b06)*invDet, - (a30*b04 - a31*b02 + a33*b00)*invDet, - (-a20*b04 + a21*b02 - a23*b00)*invDet, - (-a10*b09 + a11*b07 - a12*b06)*invDet, - (a00*b09 - a01*b07 + a02*b06)*invDet, - (-a30*b03 + a31*b01 - a32*b00)*invDet, - (a20*b03 - a21*b01 + a22*b00)*invDet }; - - // Create quaternion from source point - Quaternion quat = { source.x, source.y, source.z, 1.0f }; - - // Multiply quat point by unprojecte matrix - Quaternion qtransformed = { // QuaternionTransform(quat, matViewProjInv) - matViewProjInv.m0*quat.x + matViewProjInv.m4*quat.y + matViewProjInv.m8*quat.z + matViewProjInv.m12*quat.w, - matViewProjInv.m1*quat.x + matViewProjInv.m5*quat.y + matViewProjInv.m9*quat.z + matViewProjInv.m13*quat.w, - matViewProjInv.m2*quat.x + matViewProjInv.m6*quat.y + matViewProjInv.m10*quat.z + matViewProjInv.m14*quat.w, - matViewProjInv.m3*quat.x + matViewProjInv.m7*quat.y + matViewProjInv.m11*quat.z + matViewProjInv.m15*quat.w }; - - // Normalized world points in vectors - result.x = qtransformed.x/qtransformed.w; - result.y = qtransformed.y/qtransformed.w; - result.z = qtransformed.z/qtransformed.w; - - return result; -} - -// Get Vector3 as float array -RMAPI float3 Vector3ToFloatV(Vector3 v) -{ - float3 buffer = { 0 }; - - buffer.v[0] = v.x; - buffer.v[1] = v.y; - buffer.v[2] = v.z; - - return buffer; -} - -// Invert the given vector -RMAPI Vector3 Vector3Invert(Vector3 v) -{ - Vector3 result = { 1.0f/v.x, 1.0f/v.y, 1.0f/v.z }; - - return result; -} - -// Clamp the components of the vector between -// min and max values specified by the given vectors -RMAPI Vector3 Vector3Clamp(Vector3 v, Vector3 min, Vector3 max) -{ - Vector3 result = { 0 }; - - result.x = fminf(max.x, fmaxf(min.x, v.x)); - result.y = fminf(max.y, fmaxf(min.y, v.y)); - result.z = fminf(max.z, fmaxf(min.z, v.z)); - - return result; -} - -// Clamp the magnitude of the vector between two values -RMAPI Vector3 Vector3ClampValue(Vector3 v, float min, float max) -{ - Vector3 result = v; - - float length = (v.x*v.x) + (v.y*v.y) + (v.z*v.z); - if (length > 0.0f) - { - length = sqrtf(length); - - float scale = 1; // By default, 1 as the neutral element. - if (length < min) - { - scale = min/length; - } - else if (length > max) - { - scale = max/length; - } - - result.x = v.x*scale; - result.y = v.y*scale; - result.z = v.z*scale; - } - - return result; -} - -// Check whether two given vectors are almost equal -RMAPI int Vector3Equals(Vector3 p, Vector3 q) -{ -#if !defined(EPSILON) - #define EPSILON 0.000001f -#endif - - int result = ((fabsf(p.x - q.x)) <= (EPSILON*fmaxf(1.0f, fmaxf(fabsf(p.x), fabsf(q.x))))) && - ((fabsf(p.y - q.y)) <= (EPSILON*fmaxf(1.0f, fmaxf(fabsf(p.y), fabsf(q.y))))) && - ((fabsf(p.z - q.z)) <= (EPSILON*fmaxf(1.0f, fmaxf(fabsf(p.z), fabsf(q.z))))); - - return result; -} - -// Compute the direction of a refracted ray -// v: normalized direction of the incoming ray -// n: normalized normal vector of the interface of two optical media -// r: ratio of the refractive index of the medium from where the ray comes -// to the refractive index of the medium on the other side of the surface -RMAPI Vector3 Vector3Refract(Vector3 v, Vector3 n, float r) -{ - Vector3 result = { 0 }; - - float dot = v.x*n.x + v.y*n.y + v.z*n.z; - float d = 1.0f - r*r*(1.0f - dot*dot); - - if (d >= 0.0f) - { - d = sqrtf(d); - v.x = r*v.x - (r*dot + d)*n.x; - v.y = r*v.y - (r*dot + d)*n.y; - v.z = r*v.z - (r*dot + d)*n.z; - - result = v; - } - - return result; -} - - -//---------------------------------------------------------------------------------- -// Module Functions Definition - Vector4 math -//---------------------------------------------------------------------------------- - -RMAPI Vector4 Vector4Zero(void) -{ - Vector4 result = { 0.0f, 0.0f, 0.0f, 0.0f }; - return result; -} - -RMAPI Vector4 Vector4One(void) -{ - Vector4 result = { 1.0f, 1.0f, 1.0f, 1.0f }; - return result; -} - -RMAPI Vector4 Vector4Add(Vector4 v1, Vector4 v2) -{ - Vector4 result = { - v1.x + v2.x, - v1.y + v2.y, - v1.z + v2.z, - v1.w + v2.w - }; - return result; -} - -RMAPI Vector4 Vector4AddValue(Vector4 v, float add) -{ - Vector4 result = { - v.x + add, - v.y + add, - v.z + add, - v.w + add - }; - return result; -} - -RMAPI Vector4 Vector4Subtract(Vector4 v1, Vector4 v2) -{ - Vector4 result = { - v1.x - v2.x, - v1.y - v2.y, - v1.z - v2.z, - v1.w - v2.w - }; - return result; -} - -RMAPI Vector4 Vector4SubtractValue(Vector4 v, float add) -{ - Vector4 result = { - v.x - add, - v.y - add, - v.z - add, - v.w - add - }; - return result; -} - -RMAPI float Vector4Length(Vector4 v) -{ - float result = sqrtf((v.x*v.x) + (v.y*v.y) + (v.z*v.z) + (v.w*v.w)); - return result; -} - -RMAPI float Vector4LengthSqr(Vector4 v) -{ - float result = (v.x*v.x) + (v.y*v.y) + (v.z*v.z) + (v.w*v.w); - return result; -} - -RMAPI float Vector4DotProduct(Vector4 v1, Vector4 v2) -{ - float result = (v1.x*v2.x + v1.y*v2.y + v1.z*v2.z + v1.w*v2.w); - return result; -} - -// Calculate distance between two vectors -RMAPI float Vector4Distance(Vector4 v1, Vector4 v2) -{ - float result = sqrtf( - (v1.x - v2.x)*(v1.x - v2.x) + (v1.y - v2.y)*(v1.y - v2.y) + - (v1.z - v2.z)*(v1.z - v2.z) + (v1.w - v2.w)*(v1.w - v2.w)); - return result; -} - -// Calculate square distance between two vectors -RMAPI float Vector4DistanceSqr(Vector4 v1, Vector4 v2) -{ - float result = - (v1.x - v2.x)*(v1.x - v2.x) + (v1.y - v2.y)*(v1.y - v2.y) + - (v1.z - v2.z)*(v1.z - v2.z) + (v1.w - v2.w)*(v1.w - v2.w); - - return result; -} - -RMAPI Vector4 Vector4Scale(Vector4 v, float scale) -{ - Vector4 result = { v.x*scale, v.y*scale, v.z*scale, v.w*scale }; - return result; -} - -// Multiply vector by vector -RMAPI Vector4 Vector4Multiply(Vector4 v1, Vector4 v2) -{ - Vector4 result = { v1.x*v2.x, v1.y*v2.y, v1.z*v2.z, v1.w*v2.w }; - return result; -} - -// Negate vector -RMAPI Vector4 Vector4Negate(Vector4 v) -{ - Vector4 result = { -v.x, -v.y, -v.z, -v.w }; - return result; -} - -// Divide vector by vector -RMAPI Vector4 Vector4Divide(Vector4 v1, Vector4 v2) -{ - Vector4 result = { v1.x/v2.x, v1.y/v2.y, v1.z/v2.z, v1.w/v2.w }; - return result; -} - -// Normalize provided vector -RMAPI Vector4 Vector4Normalize(Vector4 v) -{ - Vector4 result = { 0 }; - float length = sqrtf((v.x*v.x) + (v.y*v.y) + (v.z*v.z) + (v.w*v.w)); - - if (length > 0) - { - float ilength = 1.0f/length; - result.x = v.x*ilength; - result.y = v.y*ilength; - result.z = v.z*ilength; - result.w = v.w*ilength; - } - - return result; -} - -// Get min value for each pair of components -RMAPI Vector4 Vector4Min(Vector4 v1, Vector4 v2) -{ - Vector4 result = { 0 }; - - result.x = fminf(v1.x, v2.x); - result.y = fminf(v1.y, v2.y); - result.z = fminf(v1.z, v2.z); - result.w = fminf(v1.w, v2.w); - - return result; -} - -// Get max value for each pair of components -RMAPI Vector4 Vector4Max(Vector4 v1, Vector4 v2) -{ - Vector4 result = { 0 }; - - result.x = fmaxf(v1.x, v2.x); - result.y = fmaxf(v1.y, v2.y); - result.z = fmaxf(v1.z, v2.z); - result.w = fmaxf(v1.w, v2.w); - - return result; -} - -// Calculate linear interpolation between two vectors -RMAPI Vector4 Vector4Lerp(Vector4 v1, Vector4 v2, float amount) -{ - Vector4 result = { 0 }; - - result.x = v1.x + amount*(v2.x - v1.x); - result.y = v1.y + amount*(v2.y - v1.y); - result.z = v1.z + amount*(v2.z - v1.z); - result.w = v1.w + amount*(v2.w - v1.w); - - return result; -} - -// Move Vector towards target -RMAPI Vector4 Vector4MoveTowards(Vector4 v, Vector4 target, float maxDistance) -{ - Vector4 result = { 0 }; - - float dx = target.x - v.x; - float dy = target.y - v.y; - float dz = target.z - v.z; - float dw = target.w - v.w; - float value = (dx*dx) + (dy*dy) + (dz*dz) + (dw*dw); - - if ((value == 0) || ((maxDistance >= 0) && (value <= maxDistance*maxDistance))) return target; - - float dist = sqrtf(value); - - result.x = v.x + dx/dist*maxDistance; - result.y = v.y + dy/dist*maxDistance; - result.z = v.z + dz/dist*maxDistance; - result.w = v.w + dw/dist*maxDistance; - - return result; -} - -// Invert the given vector -RMAPI Vector4 Vector4Invert(Vector4 v) -{ - Vector4 result = { 1.0f/v.x, 1.0f/v.y, 1.0f/v.z, 1.0f/v.w }; - return result; -} - -// Check whether two given vectors are almost equal -RMAPI int Vector4Equals(Vector4 p, Vector4 q) -{ -#if !defined(EPSILON) - #define EPSILON 0.000001f -#endif - - int result = ((fabsf(p.x - q.x)) <= (EPSILON*fmaxf(1.0f, fmaxf(fabsf(p.x), fabsf(q.x))))) && - ((fabsf(p.y - q.y)) <= (EPSILON*fmaxf(1.0f, fmaxf(fabsf(p.y), fabsf(q.y))))) && - ((fabsf(p.z - q.z)) <= (EPSILON*fmaxf(1.0f, fmaxf(fabsf(p.z), fabsf(q.z))))) && - ((fabsf(p.w - q.w)) <= (EPSILON*fmaxf(1.0f, fmaxf(fabsf(p.w), fabsf(q.w))))); - return result; -} - - -//---------------------------------------------------------------------------------- -// Module Functions Definition - Matrix math -//---------------------------------------------------------------------------------- - -// Compute matrix determinant -RMAPI float MatrixDeterminant(Matrix mat) -{ - float result = 0.0f; - - // Cache the matrix values (speed optimization) - float a00 = mat.m0, a01 = mat.m1, a02 = mat.m2, a03 = mat.m3; - float a10 = mat.m4, a11 = mat.m5, a12 = mat.m6, a13 = mat.m7; - float a20 = mat.m8, a21 = mat.m9, a22 = mat.m10, a23 = mat.m11; - float a30 = mat.m12, a31 = mat.m13, a32 = mat.m14, a33 = mat.m15; - - result = a30*a21*a12*a03 - a20*a31*a12*a03 - a30*a11*a22*a03 + a10*a31*a22*a03 + - a20*a11*a32*a03 - a10*a21*a32*a03 - a30*a21*a02*a13 + a20*a31*a02*a13 + - a30*a01*a22*a13 - a00*a31*a22*a13 - a20*a01*a32*a13 + a00*a21*a32*a13 + - a30*a11*a02*a23 - a10*a31*a02*a23 - a30*a01*a12*a23 + a00*a31*a12*a23 + - a10*a01*a32*a23 - a00*a11*a32*a23 - a20*a11*a02*a33 + a10*a21*a02*a33 + - a20*a01*a12*a33 - a00*a21*a12*a33 - a10*a01*a22*a33 + a00*a11*a22*a33; - - return result; -} - -// Get the trace of the matrix (sum of the values along the diagonal) -RMAPI float MatrixTrace(Matrix mat) -{ - float result = (mat.m0 + mat.m5 + mat.m10 + mat.m15); - - return result; -} - -// Transposes provided matrix -RMAPI Matrix MatrixTranspose(Matrix mat) -{ - Matrix result = { 0 }; - - result.m0 = mat.m0; - result.m1 = mat.m4; - result.m2 = mat.m8; - result.m3 = mat.m12; - result.m4 = mat.m1; - result.m5 = mat.m5; - result.m6 = mat.m9; - result.m7 = mat.m13; - result.m8 = mat.m2; - result.m9 = mat.m6; - result.m10 = mat.m10; - result.m11 = mat.m14; - result.m12 = mat.m3; - result.m13 = mat.m7; - result.m14 = mat.m11; - result.m15 = mat.m15; - - return result; -} - -// Invert provided matrix -RMAPI Matrix MatrixInvert(Matrix mat) -{ - Matrix result = { 0 }; - - // Cache the matrix values (speed optimization) - float a00 = mat.m0, a01 = mat.m1, a02 = mat.m2, a03 = mat.m3; - float a10 = mat.m4, a11 = mat.m5, a12 = mat.m6, a13 = mat.m7; - float a20 = mat.m8, a21 = mat.m9, a22 = mat.m10, a23 = mat.m11; - float a30 = mat.m12, a31 = mat.m13, a32 = mat.m14, a33 = mat.m15; - - float b00 = a00*a11 - a01*a10; - float b01 = a00*a12 - a02*a10; - float b02 = a00*a13 - a03*a10; - float b03 = a01*a12 - a02*a11; - float b04 = a01*a13 - a03*a11; - float b05 = a02*a13 - a03*a12; - float b06 = a20*a31 - a21*a30; - float b07 = a20*a32 - a22*a30; - float b08 = a20*a33 - a23*a30; - float b09 = a21*a32 - a22*a31; - float b10 = a21*a33 - a23*a31; - float b11 = a22*a33 - a23*a32; - - // Calculate the invert determinant (inlined to avoid double-caching) - float invDet = 1.0f/(b00*b11 - b01*b10 + b02*b09 + b03*b08 - b04*b07 + b05*b06); - - result.m0 = (a11*b11 - a12*b10 + a13*b09)*invDet; - result.m1 = (-a01*b11 + a02*b10 - a03*b09)*invDet; - result.m2 = (a31*b05 - a32*b04 + a33*b03)*invDet; - result.m3 = (-a21*b05 + a22*b04 - a23*b03)*invDet; - result.m4 = (-a10*b11 + a12*b08 - a13*b07)*invDet; - result.m5 = (a00*b11 - a02*b08 + a03*b07)*invDet; - result.m6 = (-a30*b05 + a32*b02 - a33*b01)*invDet; - result.m7 = (a20*b05 - a22*b02 + a23*b01)*invDet; - result.m8 = (a10*b10 - a11*b08 + a13*b06)*invDet; - result.m9 = (-a00*b10 + a01*b08 - a03*b06)*invDet; - result.m10 = (a30*b04 - a31*b02 + a33*b00)*invDet; - result.m11 = (-a20*b04 + a21*b02 - a23*b00)*invDet; - result.m12 = (-a10*b09 + a11*b07 - a12*b06)*invDet; - result.m13 = (a00*b09 - a01*b07 + a02*b06)*invDet; - result.m14 = (-a30*b03 + a31*b01 - a32*b00)*invDet; - result.m15 = (a20*b03 - a21*b01 + a22*b00)*invDet; - - return result; -} - -// Get identity matrix -RMAPI Matrix MatrixIdentity(void) -{ - Matrix result = { 1.0f, 0.0f, 0.0f, 0.0f, - 0.0f, 1.0f, 0.0f, 0.0f, - 0.0f, 0.0f, 1.0f, 0.0f, - 0.0f, 0.0f, 0.0f, 1.0f }; - - return result; -} - -// Add two matrices -RMAPI Matrix MatrixAdd(Matrix left, Matrix right) -{ - Matrix result = { 0 }; - - result.m0 = left.m0 + right.m0; - result.m1 = left.m1 + right.m1; - result.m2 = left.m2 + right.m2; - result.m3 = left.m3 + right.m3; - result.m4 = left.m4 + right.m4; - result.m5 = left.m5 + right.m5; - result.m6 = left.m6 + right.m6; - result.m7 = left.m7 + right.m7; - result.m8 = left.m8 + right.m8; - result.m9 = left.m9 + right.m9; - result.m10 = left.m10 + right.m10; - result.m11 = left.m11 + right.m11; - result.m12 = left.m12 + right.m12; - result.m13 = left.m13 + right.m13; - result.m14 = left.m14 + right.m14; - result.m15 = left.m15 + right.m15; - - return result; -} - -// Subtract two matrices (left - right) -RMAPI Matrix MatrixSubtract(Matrix left, Matrix right) -{ - Matrix result = { 0 }; - - result.m0 = left.m0 - right.m0; - result.m1 = left.m1 - right.m1; - result.m2 = left.m2 - right.m2; - result.m3 = left.m3 - right.m3; - result.m4 = left.m4 - right.m4; - result.m5 = left.m5 - right.m5; - result.m6 = left.m6 - right.m6; - result.m7 = left.m7 - right.m7; - result.m8 = left.m8 - right.m8; - result.m9 = left.m9 - right.m9; - result.m10 = left.m10 - right.m10; - result.m11 = left.m11 - right.m11; - result.m12 = left.m12 - right.m12; - result.m13 = left.m13 - right.m13; - result.m14 = left.m14 - right.m14; - result.m15 = left.m15 - right.m15; - - return result; -} - -// Get two matrix multiplication -// NOTE: When multiplying matrices... the order matters! -RMAPI Matrix MatrixMultiply(Matrix left, Matrix right) -{ - Matrix result = { 0 }; - - result.m0 = left.m0*right.m0 + left.m1*right.m4 + left.m2*right.m8 + left.m3*right.m12; - result.m1 = left.m0*right.m1 + left.m1*right.m5 + left.m2*right.m9 + left.m3*right.m13; - result.m2 = left.m0*right.m2 + left.m1*right.m6 + left.m2*right.m10 + left.m3*right.m14; - result.m3 = left.m0*right.m3 + left.m1*right.m7 + left.m2*right.m11 + left.m3*right.m15; - result.m4 = left.m4*right.m0 + left.m5*right.m4 + left.m6*right.m8 + left.m7*right.m12; - result.m5 = left.m4*right.m1 + left.m5*right.m5 + left.m6*right.m9 + left.m7*right.m13; - result.m6 = left.m4*right.m2 + left.m5*right.m6 + left.m6*right.m10 + left.m7*right.m14; - result.m7 = left.m4*right.m3 + left.m5*right.m7 + left.m6*right.m11 + left.m7*right.m15; - result.m8 = left.m8*right.m0 + left.m9*right.m4 + left.m10*right.m8 + left.m11*right.m12; - result.m9 = left.m8*right.m1 + left.m9*right.m5 + left.m10*right.m9 + left.m11*right.m13; - result.m10 = left.m8*right.m2 + left.m9*right.m6 + left.m10*right.m10 + left.m11*right.m14; - result.m11 = left.m8*right.m3 + left.m9*right.m7 + left.m10*right.m11 + left.m11*right.m15; - result.m12 = left.m12*right.m0 + left.m13*right.m4 + left.m14*right.m8 + left.m15*right.m12; - result.m13 = left.m12*right.m1 + left.m13*right.m5 + left.m14*right.m9 + left.m15*right.m13; - result.m14 = left.m12*right.m2 + left.m13*right.m6 + left.m14*right.m10 + left.m15*right.m14; - result.m15 = left.m12*right.m3 + left.m13*right.m7 + left.m14*right.m11 + left.m15*right.m15; - - return result; -} - -// Get translation matrix -RMAPI Matrix MatrixTranslate(float x, float y, float z) -{ - Matrix result = { 1.0f, 0.0f, 0.0f, x, - 0.0f, 1.0f, 0.0f, y, - 0.0f, 0.0f, 1.0f, z, - 0.0f, 0.0f, 0.0f, 1.0f }; - - return result; -} - -// Create rotation matrix from axis and angle -// NOTE: Angle should be provided in radians -RMAPI Matrix MatrixRotate(Vector3 axis, float angle) -{ - Matrix result = { 0 }; - - float x = axis.x, y = axis.y, z = axis.z; - - float lengthSquared = x*x + y*y + z*z; - - if ((lengthSquared != 1.0f) && (lengthSquared != 0.0f)) - { - float ilength = 1.0f/sqrtf(lengthSquared); - x *= ilength; - y *= ilength; - z *= ilength; - } - - float sinres = sinf(angle); - float cosres = cosf(angle); - float t = 1.0f - cosres; - - result.m0 = x*x*t + cosres; - result.m1 = y*x*t + z*sinres; - result.m2 = z*x*t - y*sinres; - result.m3 = 0.0f; - - result.m4 = x*y*t - z*sinres; - result.m5 = y*y*t + cosres; - result.m6 = z*y*t + x*sinres; - result.m7 = 0.0f; - - result.m8 = x*z*t + y*sinres; - result.m9 = y*z*t - x*sinres; - result.m10 = z*z*t + cosres; - result.m11 = 0.0f; - - result.m12 = 0.0f; - result.m13 = 0.0f; - result.m14 = 0.0f; - result.m15 = 1.0f; - - return result; -} - -// Get x-rotation matrix -// NOTE: Angle must be provided in radians -RMAPI Matrix MatrixRotateX(float angle) -{ - Matrix result = { 1.0f, 0.0f, 0.0f, 0.0f, - 0.0f, 1.0f, 0.0f, 0.0f, - 0.0f, 0.0f, 1.0f, 0.0f, - 0.0f, 0.0f, 0.0f, 1.0f }; // MatrixIdentity() - - float cosres = cosf(angle); - float sinres = sinf(angle); - - result.m5 = cosres; - result.m6 = sinres; - result.m9 = -sinres; - result.m10 = cosres; - - return result; -} - -// Get y-rotation matrix -// NOTE: Angle must be provided in radians -RMAPI Matrix MatrixRotateY(float angle) -{ - Matrix result = { 1.0f, 0.0f, 0.0f, 0.0f, - 0.0f, 1.0f, 0.0f, 0.0f, - 0.0f, 0.0f, 1.0f, 0.0f, - 0.0f, 0.0f, 0.0f, 1.0f }; // MatrixIdentity() - - float cosres = cosf(angle); - float sinres = sinf(angle); - - result.m0 = cosres; - result.m2 = -sinres; - result.m8 = sinres; - result.m10 = cosres; - - return result; -} - -// Get z-rotation matrix -// NOTE: Angle must be provided in radians -RMAPI Matrix MatrixRotateZ(float angle) -{ - Matrix result = { 1.0f, 0.0f, 0.0f, 0.0f, - 0.0f, 1.0f, 0.0f, 0.0f, - 0.0f, 0.0f, 1.0f, 0.0f, - 0.0f, 0.0f, 0.0f, 1.0f }; // MatrixIdentity() - - float cosres = cosf(angle); - float sinres = sinf(angle); - - result.m0 = cosres; - result.m1 = sinres; - result.m4 = -sinres; - result.m5 = cosres; - - return result; -} - - -// Get xyz-rotation matrix -// NOTE: Angle must be provided in radians -RMAPI Matrix MatrixRotateXYZ(Vector3 angle) -{ - Matrix result = { 1.0f, 0.0f, 0.0f, 0.0f, - 0.0f, 1.0f, 0.0f, 0.0f, - 0.0f, 0.0f, 1.0f, 0.0f, - 0.0f, 0.0f, 0.0f, 1.0f }; // MatrixIdentity() - - float cosz = cosf(-angle.z); - float sinz = sinf(-angle.z); - float cosy = cosf(-angle.y); - float siny = sinf(-angle.y); - float cosx = cosf(-angle.x); - float sinx = sinf(-angle.x); - - result.m0 = cosz*cosy; - result.m1 = (cosz*siny*sinx) - (sinz*cosx); - result.m2 = (cosz*siny*cosx) + (sinz*sinx); - - result.m4 = sinz*cosy; - result.m5 = (sinz*siny*sinx) + (cosz*cosx); - result.m6 = (sinz*siny*cosx) - (cosz*sinx); - - result.m8 = -siny; - result.m9 = cosy*sinx; - result.m10= cosy*cosx; - - return result; -} - -// Get zyx-rotation matrix -// NOTE: Angle must be provided in radians -RMAPI Matrix MatrixRotateZYX(Vector3 angle) -{ - Matrix result = { 0 }; - - float cz = cosf(angle.z); - float sz = sinf(angle.z); - float cy = cosf(angle.y); - float sy = sinf(angle.y); - float cx = cosf(angle.x); - float sx = sinf(angle.x); - - result.m0 = cz*cy; - result.m4 = cz*sy*sx - cx*sz; - result.m8 = sz*sx + cz*cx*sy; - result.m12 = 0; - - result.m1 = cy*sz; - result.m5 = cz*cx + sz*sy*sx; - result.m9 = cx*sz*sy - cz*sx; - result.m13 = 0; - - result.m2 = -sy; - result.m6 = cy*sx; - result.m10 = cy*cx; - result.m14 = 0; - - result.m3 = 0; - result.m7 = 0; - result.m11 = 0; - result.m15 = 1; - - return result; -} - -// Get scaling matrix -RMAPI Matrix MatrixScale(float x, float y, float z) -{ - Matrix result = { x, 0.0f, 0.0f, 0.0f, - 0.0f, y, 0.0f, 0.0f, - 0.0f, 0.0f, z, 0.0f, - 0.0f, 0.0f, 0.0f, 1.0f }; - - return result; -} - -// Get perspective projection matrix -RMAPI Matrix MatrixFrustum(double left, double right, double bottom, double top, double nearPlane, double farPlane) -{ - Matrix result = { 0 }; - - float rl = (float)(right - left); - float tb = (float)(top - bottom); - float fn = (float)(farPlane - nearPlane); - - result.m0 = ((float)nearPlane*2.0f)/rl; - result.m1 = 0.0f; - result.m2 = 0.0f; - result.m3 = 0.0f; - - result.m4 = 0.0f; - result.m5 = ((float)nearPlane*2.0f)/tb; - result.m6 = 0.0f; - result.m7 = 0.0f; - - result.m8 = ((float)right + (float)left)/rl; - result.m9 = ((float)top + (float)bottom)/tb; - result.m10 = -((float)farPlane + (float)nearPlane)/fn; - result.m11 = -1.0f; - - result.m12 = 0.0f; - result.m13 = 0.0f; - result.m14 = -((float)farPlane*(float)nearPlane*2.0f)/fn; - result.m15 = 0.0f; - - return result; -} - -// Get perspective projection matrix -// NOTE: Fovy angle must be provided in radians -RMAPI Matrix MatrixPerspective(double fovY, double aspect, double nearPlane, double farPlane) -{ - Matrix result = { 0 }; - - double top = nearPlane*tan(fovY*0.5); - double bottom = -top; - double right = top*aspect; - double left = -right; - - // MatrixFrustum(-right, right, -top, top, near, far); - float rl = (float)(right - left); - float tb = (float)(top - bottom); - float fn = (float)(farPlane - nearPlane); - - result.m0 = ((float)nearPlane*2.0f)/rl; - result.m5 = ((float)nearPlane*2.0f)/tb; - result.m8 = ((float)right + (float)left)/rl; - result.m9 = ((float)top + (float)bottom)/tb; - result.m10 = -((float)farPlane + (float)nearPlane)/fn; - result.m11 = -1.0f; - result.m14 = -((float)farPlane*(float)nearPlane*2.0f)/fn; - - return result; -} - -// Get orthographic projection matrix -RMAPI Matrix MatrixOrtho(double left, double right, double bottom, double top, double nearPlane, double farPlane) -{ - Matrix result = { 0 }; - - float rl = (float)(right - left); - float tb = (float)(top - bottom); - float fn = (float)(farPlane - nearPlane); - - result.m0 = 2.0f/rl; - result.m1 = 0.0f; - result.m2 = 0.0f; - result.m3 = 0.0f; - result.m4 = 0.0f; - result.m5 = 2.0f/tb; - result.m6 = 0.0f; - result.m7 = 0.0f; - result.m8 = 0.0f; - result.m9 = 0.0f; - result.m10 = -2.0f/fn; - result.m11 = 0.0f; - result.m12 = -((float)left + (float)right)/rl; - result.m13 = -((float)top + (float)bottom)/tb; - result.m14 = -((float)farPlane + (float)nearPlane)/fn; - result.m15 = 1.0f; - - return result; -} - -// Get camera look-at matrix (view matrix) -RMAPI Matrix MatrixLookAt(Vector3 eye, Vector3 target, Vector3 up) -{ - Matrix result = { 0 }; - - float length = 0.0f; - float ilength = 0.0f; - - // Vector3Subtract(eye, target) - Vector3 vz = { eye.x - target.x, eye.y - target.y, eye.z - target.z }; - - // Vector3Normalize(vz) - Vector3 v = vz; - length = sqrtf(v.x*v.x + v.y*v.y + v.z*v.z); - if (length == 0.0f) length = 1.0f; - ilength = 1.0f/length; - vz.x *= ilength; - vz.y *= ilength; - vz.z *= ilength; - - // Vector3CrossProduct(up, vz) - Vector3 vx = { up.y*vz.z - up.z*vz.y, up.z*vz.x - up.x*vz.z, up.x*vz.y - up.y*vz.x }; - - // Vector3Normalize(x) - v = vx; - length = sqrtf(v.x*v.x + v.y*v.y + v.z*v.z); - if (length == 0.0f) length = 1.0f; - ilength = 1.0f/length; - vx.x *= ilength; - vx.y *= ilength; - vx.z *= ilength; - - // Vector3CrossProduct(vz, vx) - Vector3 vy = { vz.y*vx.z - vz.z*vx.y, vz.z*vx.x - vz.x*vx.z, vz.x*vx.y - vz.y*vx.x }; - - result.m0 = vx.x; - result.m1 = vy.x; - result.m2 = vz.x; - result.m3 = 0.0f; - result.m4 = vx.y; - result.m5 = vy.y; - result.m6 = vz.y; - result.m7 = 0.0f; - result.m8 = vx.z; - result.m9 = vy.z; - result.m10 = vz.z; - result.m11 = 0.0f; - result.m12 = -(vx.x*eye.x + vx.y*eye.y + vx.z*eye.z); // Vector3DotProduct(vx, eye) - result.m13 = -(vy.x*eye.x + vy.y*eye.y + vy.z*eye.z); // Vector3DotProduct(vy, eye) - result.m14 = -(vz.x*eye.x + vz.y*eye.y + vz.z*eye.z); // Vector3DotProduct(vz, eye) - result.m15 = 1.0f; - - return result; -} - -// Get float array of matrix data -RMAPI float16 MatrixToFloatV(Matrix mat) -{ - float16 result = { 0 }; - - result.v[0] = mat.m0; - result.v[1] = mat.m1; - result.v[2] = mat.m2; - result.v[3] = mat.m3; - result.v[4] = mat.m4; - result.v[5] = mat.m5; - result.v[6] = mat.m6; - result.v[7] = mat.m7; - result.v[8] = mat.m8; - result.v[9] = mat.m9; - result.v[10] = mat.m10; - result.v[11] = mat.m11; - result.v[12] = mat.m12; - result.v[13] = mat.m13; - result.v[14] = mat.m14; - result.v[15] = mat.m15; - - return result; -} - -//---------------------------------------------------------------------------------- -// Module Functions Definition - Quaternion math -//---------------------------------------------------------------------------------- - -// Add two quaternions -RMAPI Quaternion QuaternionAdd(Quaternion q1, Quaternion q2) -{ - Quaternion result = {q1.x + q2.x, q1.y + q2.y, q1.z + q2.z, q1.w + q2.w}; - - return result; -} - -// Add quaternion and float value -RMAPI Quaternion QuaternionAddValue(Quaternion q, float add) -{ - Quaternion result = {q.x + add, q.y + add, q.z + add, q.w + add}; - - return result; -} - -// Subtract two quaternions -RMAPI Quaternion QuaternionSubtract(Quaternion q1, Quaternion q2) -{ - Quaternion result = {q1.x - q2.x, q1.y - q2.y, q1.z - q2.z, q1.w - q2.w}; - - return result; -} - -// Subtract quaternion and float value -RMAPI Quaternion QuaternionSubtractValue(Quaternion q, float sub) -{ - Quaternion result = {q.x - sub, q.y - sub, q.z - sub, q.w - sub}; - - return result; -} - -// Get identity quaternion -RMAPI Quaternion QuaternionIdentity(void) -{ - Quaternion result = { 0.0f, 0.0f, 0.0f, 1.0f }; - - return result; -} - -// Computes the length of a quaternion -RMAPI float QuaternionLength(Quaternion q) -{ - float result = sqrtf(q.x*q.x + q.y*q.y + q.z*q.z + q.w*q.w); - - return result; -} - -// Normalize provided quaternion -RMAPI Quaternion QuaternionNormalize(Quaternion q) -{ - Quaternion result = { 0 }; - - float length = sqrtf(q.x*q.x + q.y*q.y + q.z*q.z + q.w*q.w); - if (length == 0.0f) length = 1.0f; - float ilength = 1.0f/length; - - result.x = q.x*ilength; - result.y = q.y*ilength; - result.z = q.z*ilength; - result.w = q.w*ilength; - - return result; -} - -// Invert provided quaternion -RMAPI Quaternion QuaternionInvert(Quaternion q) -{ - Quaternion result = q; - - float lengthSq = q.x*q.x + q.y*q.y + q.z*q.z + q.w*q.w; - - if (lengthSq != 0.0f) - { - float invLength = 1.0f/lengthSq; - - result.x *= -invLength; - result.y *= -invLength; - result.z *= -invLength; - result.w *= invLength; - } - - return result; -} - -// Calculate two quaternion multiplication -RMAPI Quaternion QuaternionMultiply(Quaternion q1, Quaternion q2) -{ - Quaternion result = { 0 }; - - float qax = q1.x, qay = q1.y, qaz = q1.z, qaw = q1.w; - float qbx = q2.x, qby = q2.y, qbz = q2.z, qbw = q2.w; - - result.x = qax*qbw + qaw*qbx + qay*qbz - qaz*qby; - result.y = qay*qbw + qaw*qby + qaz*qbx - qax*qbz; - result.z = qaz*qbw + qaw*qbz + qax*qby - qay*qbx; - result.w = qaw*qbw - qax*qbx - qay*qby - qaz*qbz; - - return result; -} - -// Scale quaternion by float value -RMAPI Quaternion QuaternionScale(Quaternion q, float mul) -{ - Quaternion result = { 0 }; - - result.x = q.x*mul; - result.y = q.y*mul; - result.z = q.z*mul; - result.w = q.w*mul; - - return result; -} - -// Divide two quaternions -RMAPI Quaternion QuaternionDivide(Quaternion q1, Quaternion q2) -{ - Quaternion result = { q1.x/q2.x, q1.y/q2.y, q1.z/q2.z, q1.w/q2.w }; - - return result; -} - -// Calculate linear interpolation between two quaternions -RMAPI Quaternion QuaternionLerp(Quaternion q1, Quaternion q2, float amount) -{ - Quaternion result = { 0 }; - - result.x = q1.x + amount*(q2.x - q1.x); - result.y = q1.y + amount*(q2.y - q1.y); - result.z = q1.z + amount*(q2.z - q1.z); - result.w = q1.w + amount*(q2.w - q1.w); - - return result; -} - -// Calculate slerp-optimized interpolation between two quaternions -RMAPI Quaternion QuaternionNlerp(Quaternion q1, Quaternion q2, float amount) -{ - Quaternion result = { 0 }; - - // QuaternionLerp(q1, q2, amount) - result.x = q1.x + amount*(q2.x - q1.x); - result.y = q1.y + amount*(q2.y - q1.y); - result.z = q1.z + amount*(q2.z - q1.z); - result.w = q1.w + amount*(q2.w - q1.w); - - // QuaternionNormalize(q); - Quaternion q = result; - float length = sqrtf(q.x*q.x + q.y*q.y + q.z*q.z + q.w*q.w); - if (length == 0.0f) length = 1.0f; - float ilength = 1.0f/length; - - result.x = q.x*ilength; - result.y = q.y*ilength; - result.z = q.z*ilength; - result.w = q.w*ilength; - - return result; -} - -// Calculates spherical linear interpolation between two quaternions -RMAPI Quaternion QuaternionSlerp(Quaternion q1, Quaternion q2, float amount) -{ - Quaternion result = { 0 }; - -#if !defined(EPSILON) - #define EPSILON 0.000001f -#endif - - float cosHalfTheta = q1.x*q2.x + q1.y*q2.y + q1.z*q2.z + q1.w*q2.w; - - if (cosHalfTheta < 0) - { - q2.x = -q2.x; q2.y = -q2.y; q2.z = -q2.z; q2.w = -q2.w; - cosHalfTheta = -cosHalfTheta; - } - - if (fabsf(cosHalfTheta) >= 1.0f) result = q1; - else if (cosHalfTheta > 0.95f) result = QuaternionNlerp(q1, q2, amount); - else - { - float halfTheta = acosf(cosHalfTheta); - float sinHalfTheta = sqrtf(1.0f - cosHalfTheta*cosHalfTheta); - - if (fabsf(sinHalfTheta) < EPSILON) - { - result.x = (q1.x*0.5f + q2.x*0.5f); - result.y = (q1.y*0.5f + q2.y*0.5f); - result.z = (q1.z*0.5f + q2.z*0.5f); - result.w = (q1.w*0.5f + q2.w*0.5f); - } - else - { - float ratioA = sinf((1 - amount)*halfTheta)/sinHalfTheta; - float ratioB = sinf(amount*halfTheta)/sinHalfTheta; - - result.x = (q1.x*ratioA + q2.x*ratioB); - result.y = (q1.y*ratioA + q2.y*ratioB); - result.z = (q1.z*ratioA + q2.z*ratioB); - result.w = (q1.w*ratioA + q2.w*ratioB); - } - } - - return result; -} - -// Calculate quaternion cubic spline interpolation using Cubic Hermite Spline algorithm -// as described in the GLTF 2.0 specification: https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#interpolation-cubic -RMAPI Quaternion QuaternionCubicHermiteSpline(Quaternion q1, Quaternion outTangent1, Quaternion q2, Quaternion inTangent2, float t) -{ - float t2 = t*t; - float t3 = t2*t; - float h00 = 2*t3 - 3*t2 + 1; - float h10 = t3 - 2*t2 + t; - float h01 = -2*t3 + 3*t2; - float h11 = t3 - t2; - - Quaternion p0 = QuaternionScale(q1, h00); - Quaternion m0 = QuaternionScale(outTangent1, h10); - Quaternion p1 = QuaternionScale(q2, h01); - Quaternion m1 = QuaternionScale(inTangent2, h11); - - Quaternion result = { 0 }; - - result = QuaternionAdd(p0, m0); - result = QuaternionAdd(result, p1); - result = QuaternionAdd(result, m1); - result = QuaternionNormalize(result); - - return result; -} - -// Calculate quaternion based on the rotation from one vector to another -RMAPI Quaternion QuaternionFromVector3ToVector3(Vector3 from, Vector3 to) -{ - Quaternion result = { 0 }; - - float cos2Theta = (from.x*to.x + from.y*to.y + from.z*to.z); // Vector3DotProduct(from, to) - Vector3 cross = { from.y*to.z - from.z*to.y, from.z*to.x - from.x*to.z, from.x*to.y - from.y*to.x }; // Vector3CrossProduct(from, to) - - result.x = cross.x; - result.y = cross.y; - result.z = cross.z; - result.w = 1.0f + cos2Theta; - - // QuaternionNormalize(q); - // NOTE: Normalize to essentially nlerp the original and identity to 0.5 - Quaternion q = result; - float length = sqrtf(q.x*q.x + q.y*q.y + q.z*q.z + q.w*q.w); - if (length == 0.0f) length = 1.0f; - float ilength = 1.0f/length; - - result.x = q.x*ilength; - result.y = q.y*ilength; - result.z = q.z*ilength; - result.w = q.w*ilength; - - return result; -} - -// Get a quaternion for a given rotation matrix -RMAPI Quaternion QuaternionFromMatrix(Matrix mat) -{ - Quaternion result = { 0 }; - - float fourWSquaredMinus1 = mat.m0 + mat.m5 + mat.m10; - float fourXSquaredMinus1 = mat.m0 - mat.m5 - mat.m10; - float fourYSquaredMinus1 = mat.m5 - mat.m0 - mat.m10; - float fourZSquaredMinus1 = mat.m10 - mat.m0 - mat.m5; - - int biggestIndex = 0; - float fourBiggestSquaredMinus1 = fourWSquaredMinus1; - if (fourXSquaredMinus1 > fourBiggestSquaredMinus1) - { - fourBiggestSquaredMinus1 = fourXSquaredMinus1; - biggestIndex = 1; - } - - if (fourYSquaredMinus1 > fourBiggestSquaredMinus1) - { - fourBiggestSquaredMinus1 = fourYSquaredMinus1; - biggestIndex = 2; - } - - if (fourZSquaredMinus1 > fourBiggestSquaredMinus1) - { - fourBiggestSquaredMinus1 = fourZSquaredMinus1; - biggestIndex = 3; - } - - float biggestVal = sqrtf(fourBiggestSquaredMinus1 + 1.0f)*0.5f; - float mult = 0.25f/biggestVal; - - switch (biggestIndex) - { - case 0: - result.w = biggestVal; - result.x = (mat.m6 - mat.m9)*mult; - result.y = (mat.m8 - mat.m2)*mult; - result.z = (mat.m1 - mat.m4)*mult; - break; - case 1: - result.x = biggestVal; - result.w = (mat.m6 - mat.m9)*mult; - result.y = (mat.m1 + mat.m4)*mult; - result.z = (mat.m8 + mat.m2)*mult; - break; - case 2: - result.y = biggestVal; - result.w = (mat.m8 - mat.m2)*mult; - result.x = (mat.m1 + mat.m4)*mult; - result.z = (mat.m6 + mat.m9)*mult; - break; - case 3: - result.z = biggestVal; - result.w = (mat.m1 - mat.m4)*mult; - result.x = (mat.m8 + mat.m2)*mult; - result.y = (mat.m6 + mat.m9)*mult; - break; - } - - return result; -} - -// Get a matrix for a given quaternion -RMAPI Matrix QuaternionToMatrix(Quaternion q) -{ - Matrix result = { 1.0f, 0.0f, 0.0f, 0.0f, - 0.0f, 1.0f, 0.0f, 0.0f, - 0.0f, 0.0f, 1.0f, 0.0f, - 0.0f, 0.0f, 0.0f, 1.0f }; // MatrixIdentity() - - float a2 = q.x*q.x; - float b2 = q.y*q.y; - float c2 = q.z*q.z; - float ac = q.x*q.z; - float ab = q.x*q.y; - float bc = q.y*q.z; - float ad = q.w*q.x; - float bd = q.w*q.y; - float cd = q.w*q.z; - - result.m0 = 1 - 2*(b2 + c2); - result.m1 = 2*(ab + cd); - result.m2 = 2*(ac - bd); - - result.m4 = 2*(ab - cd); - result.m5 = 1 - 2*(a2 + c2); - result.m6 = 2*(bc + ad); - - result.m8 = 2*(ac + bd); - result.m9 = 2*(bc - ad); - result.m10 = 1 - 2*(a2 + b2); - - return result; -} - -// Get rotation quaternion for an angle and axis -// NOTE: Angle must be provided in radians -RMAPI Quaternion QuaternionFromAxisAngle(Vector3 axis, float angle) -{ - Quaternion result = { 0.0f, 0.0f, 0.0f, 1.0f }; - - float axisLength = sqrtf(axis.x*axis.x + axis.y*axis.y + axis.z*axis.z); - - if (axisLength != 0.0f) - { - angle *= 0.5f; - - float length = 0.0f; - float ilength = 0.0f; - - // Vector3Normalize(axis) - length = axisLength; - if (length == 0.0f) length = 1.0f; - ilength = 1.0f/length; - axis.x *= ilength; - axis.y *= ilength; - axis.z *= ilength; - - float sinres = sinf(angle); - float cosres = cosf(angle); - - result.x = axis.x*sinres; - result.y = axis.y*sinres; - result.z = axis.z*sinres; - result.w = cosres; - - // QuaternionNormalize(q); - Quaternion q = result; - length = sqrtf(q.x*q.x + q.y*q.y + q.z*q.z + q.w*q.w); - if (length == 0.0f) length = 1.0f; - ilength = 1.0f/length; - result.x = q.x*ilength; - result.y = q.y*ilength; - result.z = q.z*ilength; - result.w = q.w*ilength; - } - - return result; -} - -// Get the rotation angle and axis for a given quaternion -RMAPI void QuaternionToAxisAngle(Quaternion q, Vector3 *outAxis, float *outAngle) -{ - if (fabsf(q.w) > 1.0f) - { - // QuaternionNormalize(q); - float length = sqrtf(q.x*q.x + q.y*q.y + q.z*q.z + q.w*q.w); - if (length == 0.0f) length = 1.0f; - float ilength = 1.0f/length; - - q.x = q.x*ilength; - q.y = q.y*ilength; - q.z = q.z*ilength; - q.w = q.w*ilength; - } - - Vector3 resAxis = { 0.0f, 0.0f, 0.0f }; - float resAngle = 2.0f*acosf(q.w); - float den = sqrtf(1.0f - q.w*q.w); - - if (den > EPSILON) - { - resAxis.x = q.x/den; - resAxis.y = q.y/den; - resAxis.z = q.z/den; - } - else - { - // This occurs when the angle is zero. - // Not a problem: just set an arbitrary normalized axis. - resAxis.x = 1.0f; - } - - *outAxis = resAxis; - *outAngle = resAngle; -} - -// Get the quaternion equivalent to Euler angles -// NOTE: Rotation order is ZYX -RMAPI Quaternion QuaternionFromEuler(float pitch, float yaw, float roll) -{ - Quaternion result = { 0 }; - - float x0 = cosf(pitch*0.5f); - float x1 = sinf(pitch*0.5f); - float y0 = cosf(yaw*0.5f); - float y1 = sinf(yaw*0.5f); - float z0 = cosf(roll*0.5f); - float z1 = sinf(roll*0.5f); - - result.x = x1*y0*z0 - x0*y1*z1; - result.y = x0*y1*z0 + x1*y0*z1; - result.z = x0*y0*z1 - x1*y1*z0; - result.w = x0*y0*z0 + x1*y1*z1; - - return result; -} - -// Get the Euler angles equivalent to quaternion (roll, pitch, yaw) -// NOTE: Angles are returned in a Vector3 struct in radians -RMAPI Vector3 QuaternionToEuler(Quaternion q) -{ - Vector3 result = { 0 }; - - // Roll (x-axis rotation) - float x0 = 2.0f*(q.w*q.x + q.y*q.z); - float x1 = 1.0f - 2.0f*(q.x*q.x + q.y*q.y); - result.x = atan2f(x0, x1); - - // Pitch (y-axis rotation) - float y0 = 2.0f*(q.w*q.y - q.z*q.x); - y0 = y0 > 1.0f ? 1.0f : y0; - y0 = y0 < -1.0f ? -1.0f : y0; - result.y = asinf(y0); - - // Yaw (z-axis rotation) - float z0 = 2.0f*(q.w*q.z + q.x*q.y); - float z1 = 1.0f - 2.0f*(q.y*q.y + q.z*q.z); - result.z = atan2f(z0, z1); - - return result; -} - -// Transform a quaternion given a transformation matrix -RMAPI Quaternion QuaternionTransform(Quaternion q, Matrix mat) -{ - Quaternion result = { 0 }; - - result.x = mat.m0*q.x + mat.m4*q.y + mat.m8*q.z + mat.m12*q.w; - result.y = mat.m1*q.x + mat.m5*q.y + mat.m9*q.z + mat.m13*q.w; - result.z = mat.m2*q.x + mat.m6*q.y + mat.m10*q.z + mat.m14*q.w; - result.w = mat.m3*q.x + mat.m7*q.y + mat.m11*q.z + mat.m15*q.w; - - return result; -} - -// Check whether two given quaternions are almost equal -RMAPI int QuaternionEquals(Quaternion p, Quaternion q) -{ -#if !defined(EPSILON) - #define EPSILON 0.000001f -#endif - - int result = (((fabsf(p.x - q.x)) <= (EPSILON*fmaxf(1.0f, fmaxf(fabsf(p.x), fabsf(q.x))))) && - ((fabsf(p.y - q.y)) <= (EPSILON*fmaxf(1.0f, fmaxf(fabsf(p.y), fabsf(q.y))))) && - ((fabsf(p.z - q.z)) <= (EPSILON*fmaxf(1.0f, fmaxf(fabsf(p.z), fabsf(q.z))))) && - ((fabsf(p.w - q.w)) <= (EPSILON*fmaxf(1.0f, fmaxf(fabsf(p.w), fabsf(q.w)))))) || - (((fabsf(p.x + q.x)) <= (EPSILON*fmaxf(1.0f, fmaxf(fabsf(p.x), fabsf(q.x))))) && - ((fabsf(p.y + q.y)) <= (EPSILON*fmaxf(1.0f, fmaxf(fabsf(p.y), fabsf(q.y))))) && - ((fabsf(p.z + q.z)) <= (EPSILON*fmaxf(1.0f, fmaxf(fabsf(p.z), fabsf(q.z))))) && - ((fabsf(p.w + q.w)) <= (EPSILON*fmaxf(1.0f, fmaxf(fabsf(p.w), fabsf(q.w)))))); - - return result; -} - -// Decompose a transformation matrix into its rotational, translational and scaling components -RMAPI void MatrixDecompose(Matrix mat, Vector3 *translation, Quaternion *rotation, Vector3 *scale) -{ - // Extract translation. - translation->x = mat.m12; - translation->y = mat.m13; - translation->z = mat.m14; - - // Extract upper-left for determinant computation - const float a = mat.m0; - const float b = mat.m4; - const float c = mat.m8; - const float d = mat.m1; - const float e = mat.m5; - const float f = mat.m9; - const float g = mat.m2; - const float h = mat.m6; - const float i = mat.m10; - const float A = e*i - f*h; - const float B = f*g - d*i; - const float C = d*h - e*g; - - // Extract scale - const float det = a*A + b*B + c*C; - Vector3 abc = { a, b, c }; - Vector3 def = { d, e, f }; - Vector3 ghi = { g, h, i }; - - float scalex = Vector3Length(abc); - float scaley = Vector3Length(def); - float scalez = Vector3Length(ghi); - Vector3 s = { scalex, scaley, scalez }; - - if (det < 0) s = Vector3Negate(s); - - *scale = s; - - // Remove scale from the matrix if it is not close to zero - Matrix clone = mat; - if (!FloatEquals(det, 0)) - { - clone.m0 /= s.x; - clone.m4 /= s.x; - clone.m8 /= s.x; - clone.m1 /= s.y; - clone.m5 /= s.y; - clone.m9 /= s.y; - clone.m2 /= s.z; - clone.m6 /= s.z; - clone.m10 /= s.z; - - // Extract rotation - *rotation = QuaternionFromMatrix(clone); - } - else - { - // Set to identity if close to zero - *rotation = QuaternionIdentity(); - } -} - -#if defined(__cplusplus) && !defined(RAYMATH_DISABLE_CPP_OPERATORS) - -// Optional C++ math operators -//------------------------------------------------------------------------------- - -// Vector2 operators -static constexpr Vector2 Vector2Zeros = { 0, 0 }; -static constexpr Vector2 Vector2Ones = { 1, 1 }; -static constexpr Vector2 Vector2UnitX = { 1, 0 }; -static constexpr Vector2 Vector2UnitY = { 0, 1 }; - -inline Vector2 operator + (const Vector2& lhs, const Vector2& rhs) -{ - return Vector2Add(lhs, rhs); -} - -inline const Vector2& operator += (Vector2& lhs, const Vector2& rhs) -{ - lhs = Vector2Add(lhs, rhs); - return lhs; -} - -inline Vector2 operator - (const Vector2& lhs, const Vector2& rhs) -{ - return Vector2Subtract(lhs, rhs); -} - -inline const Vector2& operator -= (Vector2& lhs, const Vector2& rhs) -{ - lhs = Vector2Subtract(lhs, rhs); - return lhs; -} - -inline Vector2 operator * (const Vector2& lhs, const float& rhs) -{ - return Vector2Scale(lhs, rhs); -} - -inline const Vector2& operator *= (Vector2& lhs, const float& rhs) -{ - lhs = Vector2Scale(lhs, rhs); - return lhs; -} - -inline Vector2 operator * (const Vector2& lhs, const Vector2& rhs) -{ - return Vector2Multiply(lhs, rhs); -} - -inline const Vector2& operator *= (Vector2& lhs, const Vector2& rhs) -{ - lhs = Vector2Multiply(lhs, rhs); - return lhs; -} - -inline Vector2 operator * (const Vector2& lhs, const Matrix& rhs) -{ - return Vector2Transform(lhs, rhs); -} - -inline const Vector2& operator *= (Vector2& lhs, const Matrix& rhs) -{ - lhs = Vector2Transform(lhs, rhs); - return lhs; -} - -inline Vector2 operator / (const Vector2& lhs, const float& rhs) -{ - return Vector2Scale(lhs, 1.0f / rhs); -} - -inline const Vector2& operator /= (Vector2& lhs, const float& rhs) -{ - lhs = Vector2Scale(lhs, 1.0f / rhs); - return lhs; -} - -inline Vector2 operator / (const Vector2& lhs, const Vector2& rhs) -{ - return Vector2Divide(lhs, rhs); -} - -inline const Vector2& operator /= (Vector2& lhs, const Vector2& rhs) -{ - lhs = Vector2Divide(lhs, rhs); - return lhs; -} - -inline bool operator == (const Vector2& lhs, const Vector2& rhs) -{ - return FloatEquals(lhs.x, rhs.x) && FloatEquals(lhs.y, rhs.y); -} - -inline bool operator != (const Vector2& lhs, const Vector2& rhs) -{ - return !FloatEquals(lhs.x, rhs.x) || !FloatEquals(lhs.y, rhs.y); -} - -// Vector3 operators -static constexpr Vector3 Vector3Zeros = { 0, 0, 0 }; -static constexpr Vector3 Vector3Ones = { 1, 1, 1 }; -static constexpr Vector3 Vector3UnitX = { 1, 0, 0 }; -static constexpr Vector3 Vector3UnitY = { 0, 1, 0 }; -static constexpr Vector3 Vector3UnitZ = { 0, 0, 1 }; - -inline Vector3 operator + (const Vector3& lhs, const Vector3& rhs) -{ - return Vector3Add(lhs, rhs); -} - -inline const Vector3& operator += (Vector3& lhs, const Vector3& rhs) -{ - lhs = Vector3Add(lhs, rhs); - return lhs; -} - -inline Vector3 operator - (const Vector3& lhs, const Vector3& rhs) -{ - return Vector3Subtract(lhs, rhs); -} - -inline const Vector3& operator -= (Vector3& lhs, const Vector3& rhs) -{ - lhs = Vector3Subtract(lhs, rhs); - return lhs; -} - -inline Vector3 operator * (const Vector3& lhs, const float& rhs) -{ - return Vector3Scale(lhs, rhs); -} - -inline const Vector3& operator *= (Vector3& lhs, const float& rhs) -{ - lhs = Vector3Scale(lhs, rhs); - return lhs; -} - -inline Vector3 operator * (const Vector3& lhs, const Vector3& rhs) -{ - return Vector3Multiply(lhs, rhs); -} - -inline const Vector3& operator *= (Vector3& lhs, const Vector3& rhs) -{ - lhs = Vector3Multiply(lhs, rhs); - return lhs; -} - -inline Vector3 operator * (const Vector3& lhs, const Matrix& rhs) -{ - return Vector3Transform(lhs, rhs); -} - -inline const Vector3& operator *= (Vector3& lhs, const Matrix& rhs) -{ - lhs = Vector3Transform(lhs, rhs); - return lhs; -} - -inline Vector3 operator / (const Vector3& lhs, const float& rhs) -{ - return Vector3Scale(lhs, 1.0f / rhs); -} - -inline const Vector3& operator /= (Vector3& lhs, const float& rhs) -{ - lhs = Vector3Scale(lhs, 1.0f / rhs); - return lhs; -} - -inline Vector3 operator / (const Vector3& lhs, const Vector3& rhs) -{ - return Vector3Divide(lhs, rhs); -} - -inline const Vector3& operator /= (Vector3& lhs, const Vector3& rhs) -{ - lhs = Vector3Divide(lhs, rhs); - return lhs; -} - -inline bool operator == (const Vector3& lhs, const Vector3& rhs) -{ - return FloatEquals(lhs.x, rhs.x) && FloatEquals(lhs.y, rhs.y) && FloatEquals(lhs.z, rhs.z); -} - -inline bool operator != (const Vector3& lhs, const Vector3& rhs) -{ - return !FloatEquals(lhs.x, rhs.x) || !FloatEquals(lhs.y, rhs.y) || !FloatEquals(lhs.z, rhs.z); -} - -// Vector4 operators -static constexpr Vector4 Vector4Zeros = { 0, 0, 0, 0 }; -static constexpr Vector4 Vector4Ones = { 1, 1, 1, 1 }; -static constexpr Vector4 Vector4UnitX = { 1, 0, 0, 0 }; -static constexpr Vector4 Vector4UnitY = { 0, 1, 0, 0 }; -static constexpr Vector4 Vector4UnitZ = { 0, 0, 1, 0 }; -static constexpr Vector4 Vector4UnitW = { 0, 0, 0, 1 }; - -inline Vector4 operator + (const Vector4& lhs, const Vector4& rhs) -{ - return Vector4Add(lhs, rhs); -} - -inline const Vector4& operator += (Vector4& lhs, const Vector4& rhs) -{ - lhs = Vector4Add(lhs, rhs); - return lhs; -} - -inline Vector4 operator - (const Vector4& lhs, const Vector4& rhs) -{ - return Vector4Subtract(lhs, rhs); -} - -inline const Vector4& operator -= (Vector4& lhs, const Vector4& rhs) -{ - lhs = Vector4Subtract(lhs, rhs); - return lhs; -} - -inline Vector4 operator * (const Vector4& lhs, const float& rhs) -{ - return Vector4Scale(lhs, rhs); -} - -inline const Vector4& operator *= (Vector4& lhs, const float& rhs) -{ - lhs = Vector4Scale(lhs, rhs); - return lhs; -} - -inline Vector4 operator * (const Vector4& lhs, const Vector4& rhs) -{ - return Vector4Multiply(lhs, rhs); -} - -inline const Vector4& operator *= (Vector4& lhs, const Vector4& rhs) -{ - lhs = Vector4Multiply(lhs, rhs); - return lhs; -} - -inline Vector4 operator / (const Vector4& lhs, const float& rhs) -{ - return Vector4Scale(lhs, 1.0f / rhs); -} - -inline const Vector4& operator /= (Vector4& lhs, const float& rhs) -{ - lhs = Vector4Scale(lhs, 1.0f / rhs); - return lhs; -} - -inline Vector4 operator / (const Vector4& lhs, const Vector4& rhs) -{ - return Vector4Divide(lhs, rhs); -} - -inline const Vector4& operator /= (Vector4& lhs, const Vector4& rhs) -{ - lhs = Vector4Divide(lhs, rhs); - return lhs; -} - -inline bool operator == (const Vector4& lhs, const Vector4& rhs) -{ - return FloatEquals(lhs.x, rhs.x) && FloatEquals(lhs.y, rhs.y) && FloatEquals(lhs.z, rhs.z) && FloatEquals(lhs.w, rhs.w); -} - -inline bool operator != (const Vector4& lhs, const Vector4& rhs) -{ - return !FloatEquals(lhs.x, rhs.x) || !FloatEquals(lhs.y, rhs.y) || !FloatEquals(lhs.z, rhs.z) || !FloatEquals(lhs.w, rhs.w); -} - -// Quaternion operators -static constexpr Quaternion QuaternionZeros = { 0, 0, 0, 0 }; -static constexpr Quaternion QuaternionOnes = { 1, 1, 1, 1 }; -static constexpr Quaternion QuaternionUnitX = { 0, 0, 0, 1 }; - -inline Quaternion operator + (const Quaternion& lhs, const float& rhs) -{ - return QuaternionAddValue(lhs, rhs); -} - -inline const Quaternion& operator += (Quaternion& lhs, const float& rhs) -{ - lhs = QuaternionAddValue(lhs, rhs); - return lhs; -} - -inline Quaternion operator - (const Quaternion& lhs, const float& rhs) -{ - return QuaternionSubtractValue(lhs, rhs); -} - -inline const Quaternion& operator -= (Quaternion& lhs, const float& rhs) -{ - lhs = QuaternionSubtractValue(lhs, rhs); - return lhs; -} - -inline Quaternion operator * (const Quaternion& lhs, const Matrix& rhs) -{ - return QuaternionTransform(lhs, rhs); -} - -inline const Quaternion& operator *= (Quaternion& lhs, const Matrix& rhs) -{ - lhs = QuaternionTransform(lhs, rhs); - return lhs; -} - -// Matrix operators -inline Matrix operator + (const Matrix& lhs, const Matrix& rhs) -{ - return MatrixAdd(lhs, rhs); -} - -inline const Matrix& operator += (Matrix& lhs, const Matrix& rhs) -{ - lhs = MatrixAdd(lhs, rhs); - return lhs; -} - -inline Matrix operator - (const Matrix& lhs, const Matrix& rhs) -{ - return MatrixSubtract(lhs, rhs); -} - -inline const Matrix& operator -= (Matrix& lhs, const Matrix& rhs) -{ - lhs = MatrixSubtract(lhs, rhs); - return lhs; -} - -inline Matrix operator * (const Matrix& lhs, const Matrix& rhs) -{ - return MatrixMultiply(lhs, rhs); -} - -inline const Matrix& operator *= (Matrix& lhs, const Matrix& rhs) -{ - lhs = MatrixMultiply(lhs, rhs); - return lhs; -} -//------------------------------------------------------------------------------- -#endif // C++ operators - -#endif // RAYMATH_H diff --git a/third_party/raylib/include/rlgl.h b/third_party/raylib/include/rlgl.h deleted file mode 100644 index 92971df627..0000000000 --- a/third_party/raylib/include/rlgl.h +++ /dev/null @@ -1,5262 +0,0 @@ -/********************************************************************************************** -* -* rlgl v5.0 - A multi-OpenGL abstraction layer with an immediate-mode style API -* -* DESCRIPTION: -* An abstraction layer for multiple OpenGL versions (1.1, 2.1, 3.3 Core, 4.3 Core, ES 2.0, ES 3.0) -* that provides a pseudo-OpenGL 1.1 immediate-mode style API (rlVertex, rlTranslate, rlRotate...) -* -* ADDITIONAL NOTES: -* When choosing an OpenGL backend different than OpenGL 1.1, some internal buffer are -* initialized on rlglInit() to accumulate vertex data -* -* When an internal state change is required all the stored vertex data is renderer in batch, -* additionally, rlDrawRenderBatchActive() could be called to force flushing of the batch -* -* Some resources are also loaded for convenience, here the complete list: -* - Default batch (RLGL.defaultBatch): RenderBatch system to accumulate vertex data -* - Default texture (RLGL.defaultTextureId): 1x1 white pixel R8G8B8A8 -* - Default shader (RLGL.State.defaultShaderId, RLGL.State.defaultShaderLocs) -* -* Internal buffer (and resources) must be manually unloaded calling rlglClose() -* -* CONFIGURATION: -* #define GRAPHICS_API_OPENGL_11 -* #define GRAPHICS_API_OPENGL_21 -* #define GRAPHICS_API_OPENGL_33 -* #define GRAPHICS_API_OPENGL_43 -* #define GRAPHICS_API_OPENGL_ES2 -* #define GRAPHICS_API_OPENGL_ES3 -* Use selected OpenGL graphics backend, should be supported by platform -* Those preprocessor defines are only used on rlgl module, if OpenGL version is -* required by any other module, use rlGetVersion() to check it -* -* #define RLGL_IMPLEMENTATION -* Generates the implementation of the library into the included file -* If not defined, the library is in header only mode and can be included in other headers -* or source files without problems. But only ONE file should hold the implementation -* -* #define RLGL_RENDER_TEXTURES_HINT -* Enable framebuffer objects (fbo) support (enabled by default) -* Some GPUs could not support them despite the OpenGL version -* -* #define RLGL_SHOW_GL_DETAILS_INFO -* Show OpenGL extensions and capabilities detailed logs on init -* -* #define RLGL_ENABLE_OPENGL_DEBUG_CONTEXT -* Enable debug context (only available on OpenGL 4.3) -* -* rlgl capabilities could be customized just defining some internal -* values before library inclusion (default values listed): -* -* #define RL_DEFAULT_BATCH_BUFFER_ELEMENTS 8192 // Default internal render batch elements limits -* #define RL_DEFAULT_BATCH_BUFFERS 1 // Default number of batch buffers (multi-buffering) -* #define RL_DEFAULT_BATCH_DRAWCALLS 256 // Default number of batch draw calls (by state changes: mode, texture) -* #define RL_DEFAULT_BATCH_MAX_TEXTURE_UNITS 4 // Maximum number of textures units that can be activated on batch drawing (SetShaderValueTexture()) -* -* #define RL_MAX_MATRIX_STACK_SIZE 32 // Maximum size of internal Matrix stack -* #define RL_MAX_SHADER_LOCATIONS 32 // Maximum number of shader locations supported -* #define RL_CULL_DISTANCE_NEAR 0.01 // Default projection matrix near cull distance -* #define RL_CULL_DISTANCE_FAR 1000.0 // Default projection matrix far cull distance -* -* When loading a shader, the following vertex attributes and uniform -* location names are tried to be set automatically: -* -* #define RL_DEFAULT_SHADER_ATTRIB_NAME_POSITION "vertexPosition" // Bound by default to shader location: RL_DEFAULT_SHADER_ATTRIB_LOCATION_POSITION -* #define RL_DEFAULT_SHADER_ATTRIB_NAME_TEXCOORD "vertexTexCoord" // Bound by default to shader location: RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD -* #define RL_DEFAULT_SHADER_ATTRIB_NAME_NORMAL "vertexNormal" // Bound by default to shader location: RL_DEFAULT_SHADER_ATTRIB_LOCATION_NORMAL -* #define RL_DEFAULT_SHADER_ATTRIB_NAME_COLOR "vertexColor" // Bound by default to shader location: RL_DEFAULT_SHADER_ATTRIB_LOCATION_COLOR -* #define RL_DEFAULT_SHADER_ATTRIB_NAME_TANGENT "vertexTangent" // Bound by default to shader location: RL_DEFAULT_SHADER_ATTRIB_LOCATION_TANGENT -* #define RL_DEFAULT_SHADER_ATTRIB_NAME_TEXCOORD2 "vertexTexCoord2" // Bound by default to shader location: RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD2 -* #define RL_DEFAULT_SHADER_ATTRIB_NAME_BONEIDS "vertexBoneIds" // Bound by default to shader location: RL_DEFAULT_SHADER_ATTRIB_LOCATION_BONEIDS -* #define RL_DEFAULT_SHADER_ATTRIB_NAME_BONEWEIGHTS "vertexBoneWeights" // Bound by default to shader location: RL_DEFAULT_SHADER_ATTRIB_LOCATION_BONEWEIGHTS -* #define RL_DEFAULT_SHADER_UNIFORM_NAME_MVP "mvp" // model-view-projection matrix -* #define RL_DEFAULT_SHADER_UNIFORM_NAME_VIEW "matView" // view matrix -* #define RL_DEFAULT_SHADER_UNIFORM_NAME_PROJECTION "matProjection" // projection matrix -* #define RL_DEFAULT_SHADER_UNIFORM_NAME_MODEL "matModel" // model matrix -* #define RL_DEFAULT_SHADER_UNIFORM_NAME_NORMAL "matNormal" // normal matrix (transpose(inverse(matModelView))) -* #define RL_DEFAULT_SHADER_UNIFORM_NAME_COLOR "colDiffuse" // color diffuse (base tint color, multiplied by texture color) -* #define RL_DEFAULT_SHADER_UNIFORM_NAME_BONE_MATRICES "boneMatrices" // bone matrices -* #define RL_DEFAULT_SHADER_SAMPLER2D_NAME_TEXTURE0 "texture0" // texture0 (texture slot active 0) -* #define RL_DEFAULT_SHADER_SAMPLER2D_NAME_TEXTURE1 "texture1" // texture1 (texture slot active 1) -* #define RL_DEFAULT_SHADER_SAMPLER2D_NAME_TEXTURE2 "texture2" // texture2 (texture slot active 2) -* -* DEPENDENCIES: -* - OpenGL libraries (depending on platform and OpenGL version selected) -* - GLAD OpenGL extensions loading library (only for OpenGL 3.3 Core, 4.3 Core) -* -* -* LICENSE: zlib/libpng -* -* Copyright (c) 2014-2024 Ramon Santamaria (@raysan5) -* -* This software is provided "as-is", without any express or implied warranty. In no event -* will the authors be held liable for any damages arising from the use of this software. -* -* Permission is granted to anyone to use this software for any purpose, including commercial -* applications, and to alter it and redistribute it freely, subject to the following restrictions: -* -* 1. The origin of this software must not be misrepresented; you must not claim that you -* wrote the original software. If you use this software in a product, an acknowledgment -* in the product documentation would be appreciated but is not required. -* -* 2. Altered source versions must be plainly marked as such, and must not be misrepresented -* as being the original software. -* -* 3. This notice may not be removed or altered from any source distribution. -* -**********************************************************************************************/ - -#ifndef RLGL_H -#define RLGL_H - -#define RLGL_VERSION "5.0" - -// Function specifiers in case library is build/used as a shared library -// NOTE: Microsoft specifiers to tell compiler that symbols are imported/exported from a .dll -// NOTE: visibility(default) attribute makes symbols "visible" when compiled with -fvisibility=hidden -#if defined(_WIN32) && defined(BUILD_LIBTYPE_SHARED) - #define RLAPI __declspec(dllexport) // We are building the library as a Win32 shared library (.dll) -#elif defined(BUILD_LIBTYPE_SHARED) - #define RLAPI __attribute__((visibility("default"))) // We are building the library as a Unix shared library (.so/.dylib) -#elif defined(_WIN32) && defined(USE_LIBTYPE_SHARED) - #define RLAPI __declspec(dllimport) // We are using the library as a Win32 shared library (.dll) -#endif - -// Function specifiers definition -#ifndef RLAPI - #define RLAPI // Functions defined as 'extern' by default (implicit specifiers) -#endif - -// Support TRACELOG macros -#ifndef TRACELOG - #define TRACELOG(level, ...) (void)0 - #define TRACELOGD(...) (void)0 -#endif - -// Allow custom memory allocators -#ifndef RL_MALLOC - #define RL_MALLOC(sz) malloc(sz) -#endif -#ifndef RL_CALLOC - #define RL_CALLOC(n,sz) calloc(n,sz) -#endif -#ifndef RL_REALLOC - #define RL_REALLOC(n,sz) realloc(n,sz) -#endif -#ifndef RL_FREE - #define RL_FREE(p) free(p) -#endif - -// Security check in case no GRAPHICS_API_OPENGL_* defined -#if !defined(GRAPHICS_API_OPENGL_11) && \ - !defined(GRAPHICS_API_OPENGL_21) && \ - !defined(GRAPHICS_API_OPENGL_33) && \ - !defined(GRAPHICS_API_OPENGL_43) && \ - !defined(GRAPHICS_API_OPENGL_ES2) && \ - !defined(GRAPHICS_API_OPENGL_ES3) - #define GRAPHICS_API_OPENGL_33 -#endif - -// Security check in case multiple GRAPHICS_API_OPENGL_* defined -#if defined(GRAPHICS_API_OPENGL_11) - #if defined(GRAPHICS_API_OPENGL_21) - #undef GRAPHICS_API_OPENGL_21 - #endif - #if defined(GRAPHICS_API_OPENGL_33) - #undef GRAPHICS_API_OPENGL_33 - #endif - #if defined(GRAPHICS_API_OPENGL_43) - #undef GRAPHICS_API_OPENGL_43 - #endif - #if defined(GRAPHICS_API_OPENGL_ES2) - #undef GRAPHICS_API_OPENGL_ES2 - #endif -#endif - -// OpenGL 2.1 uses most of OpenGL 3.3 Core functionality -// WARNING: Specific parts are checked with #if defines -#if defined(GRAPHICS_API_OPENGL_21) - #define GRAPHICS_API_OPENGL_33 -#endif - -// OpenGL 4.3 uses OpenGL 3.3 Core functionality -#if defined(GRAPHICS_API_OPENGL_43) - #define GRAPHICS_API_OPENGL_33 -#endif - -// OpenGL ES 3.0 uses OpenGL ES 2.0 functionality (and more) -#if defined(GRAPHICS_API_OPENGL_ES3) - #define GRAPHICS_API_OPENGL_ES2 -#endif - -// Support framebuffer objects by default -// NOTE: Some driver implementation do not support it, despite they should -#define RLGL_RENDER_TEXTURES_HINT - -//---------------------------------------------------------------------------------- -// Defines and Macros -//---------------------------------------------------------------------------------- - -// Default internal render batch elements limits -#ifndef RL_DEFAULT_BATCH_BUFFER_ELEMENTS - #if defined(GRAPHICS_API_OPENGL_11) || defined(GRAPHICS_API_OPENGL_33) - // This is the maximum amount of elements (quads) per batch - // NOTE: Be careful with text, every letter maps to a quad - #define RL_DEFAULT_BATCH_BUFFER_ELEMENTS 8192 - #endif - #if defined(GRAPHICS_API_OPENGL_ES2) - // We reduce memory sizes for embedded systems (RPI and HTML5) - // NOTE: On HTML5 (emscripten) this is allocated on heap, - // by default it's only 16MB!...just take care... - #define RL_DEFAULT_BATCH_BUFFER_ELEMENTS 2048 - #endif -#endif -#ifndef RL_DEFAULT_BATCH_BUFFERS - #define RL_DEFAULT_BATCH_BUFFERS 1 // Default number of batch buffers (multi-buffering) -#endif -#ifndef RL_DEFAULT_BATCH_DRAWCALLS - #define RL_DEFAULT_BATCH_DRAWCALLS 256 // Default number of batch draw calls (by state changes: mode, texture) -#endif -#ifndef RL_DEFAULT_BATCH_MAX_TEXTURE_UNITS - #define RL_DEFAULT_BATCH_MAX_TEXTURE_UNITS 4 // Maximum number of textures units that can be activated on batch drawing (SetShaderValueTexture()) -#endif - -// Internal Matrix stack -#ifndef RL_MAX_MATRIX_STACK_SIZE - #define RL_MAX_MATRIX_STACK_SIZE 32 // Maximum size of Matrix stack -#endif - -// Shader limits -#ifndef RL_MAX_SHADER_LOCATIONS - #define RL_MAX_SHADER_LOCATIONS 32 // Maximum number of shader locations supported -#endif - -// Projection matrix culling -#ifndef RL_CULL_DISTANCE_NEAR - #define RL_CULL_DISTANCE_NEAR 0.01 // Default near cull distance -#endif -#ifndef RL_CULL_DISTANCE_FAR - #define RL_CULL_DISTANCE_FAR 1000.0 // Default far cull distance -#endif - -// Texture parameters (equivalent to OpenGL defines) -#define RL_TEXTURE_WRAP_S 0x2802 // GL_TEXTURE_WRAP_S -#define RL_TEXTURE_WRAP_T 0x2803 // GL_TEXTURE_WRAP_T -#define RL_TEXTURE_MAG_FILTER 0x2800 // GL_TEXTURE_MAG_FILTER -#define RL_TEXTURE_MIN_FILTER 0x2801 // GL_TEXTURE_MIN_FILTER - -#define RL_TEXTURE_FILTER_NEAREST 0x2600 // GL_NEAREST -#define RL_TEXTURE_FILTER_LINEAR 0x2601 // GL_LINEAR -#define RL_TEXTURE_FILTER_MIP_NEAREST 0x2700 // GL_NEAREST_MIPMAP_NEAREST -#define RL_TEXTURE_FILTER_NEAREST_MIP_LINEAR 0x2702 // GL_NEAREST_MIPMAP_LINEAR -#define RL_TEXTURE_FILTER_LINEAR_MIP_NEAREST 0x2701 // GL_LINEAR_MIPMAP_NEAREST -#define RL_TEXTURE_FILTER_MIP_LINEAR 0x2703 // GL_LINEAR_MIPMAP_LINEAR -#define RL_TEXTURE_FILTER_ANISOTROPIC 0x3000 // Anisotropic filter (custom identifier) -#define RL_TEXTURE_MIPMAP_BIAS_RATIO 0x4000 // Texture mipmap bias, percentage ratio (custom identifier) - -#define RL_TEXTURE_WRAP_REPEAT 0x2901 // GL_REPEAT -#define RL_TEXTURE_WRAP_CLAMP 0x812F // GL_CLAMP_TO_EDGE -#define RL_TEXTURE_WRAP_MIRROR_REPEAT 0x8370 // GL_MIRRORED_REPEAT -#define RL_TEXTURE_WRAP_MIRROR_CLAMP 0x8742 // GL_MIRROR_CLAMP_EXT - -// Matrix modes (equivalent to OpenGL) -#define RL_MODELVIEW 0x1700 // GL_MODELVIEW -#define RL_PROJECTION 0x1701 // GL_PROJECTION -#define RL_TEXTURE 0x1702 // GL_TEXTURE - -// Primitive assembly draw modes -#define RL_LINES 0x0001 // GL_LINES -#define RL_TRIANGLES 0x0004 // GL_TRIANGLES -#define RL_QUADS 0x0007 // GL_QUADS - -// GL equivalent data types -#define RL_UNSIGNED_BYTE 0x1401 // GL_UNSIGNED_BYTE -#define RL_FLOAT 0x1406 // GL_FLOAT - -// GL buffer usage hint -#define RL_STREAM_DRAW 0x88E0 // GL_STREAM_DRAW -#define RL_STREAM_READ 0x88E1 // GL_STREAM_READ -#define RL_STREAM_COPY 0x88E2 // GL_STREAM_COPY -#define RL_STATIC_DRAW 0x88E4 // GL_STATIC_DRAW -#define RL_STATIC_READ 0x88E5 // GL_STATIC_READ -#define RL_STATIC_COPY 0x88E6 // GL_STATIC_COPY -#define RL_DYNAMIC_DRAW 0x88E8 // GL_DYNAMIC_DRAW -#define RL_DYNAMIC_READ 0x88E9 // GL_DYNAMIC_READ -#define RL_DYNAMIC_COPY 0x88EA // GL_DYNAMIC_COPY - -// GL Shader type -#define RL_FRAGMENT_SHADER 0x8B30 // GL_FRAGMENT_SHADER -#define RL_VERTEX_SHADER 0x8B31 // GL_VERTEX_SHADER -#define RL_COMPUTE_SHADER 0x91B9 // GL_COMPUTE_SHADER - -// GL blending factors -#define RL_ZERO 0 // GL_ZERO -#define RL_ONE 1 // GL_ONE -#define RL_SRC_COLOR 0x0300 // GL_SRC_COLOR -#define RL_ONE_MINUS_SRC_COLOR 0x0301 // GL_ONE_MINUS_SRC_COLOR -#define RL_SRC_ALPHA 0x0302 // GL_SRC_ALPHA -#define RL_ONE_MINUS_SRC_ALPHA 0x0303 // GL_ONE_MINUS_SRC_ALPHA -#define RL_DST_ALPHA 0x0304 // GL_DST_ALPHA -#define RL_ONE_MINUS_DST_ALPHA 0x0305 // GL_ONE_MINUS_DST_ALPHA -#define RL_DST_COLOR 0x0306 // GL_DST_COLOR -#define RL_ONE_MINUS_DST_COLOR 0x0307 // GL_ONE_MINUS_DST_COLOR -#define RL_SRC_ALPHA_SATURATE 0x0308 // GL_SRC_ALPHA_SATURATE -#define RL_CONSTANT_COLOR 0x8001 // GL_CONSTANT_COLOR -#define RL_ONE_MINUS_CONSTANT_COLOR 0x8002 // GL_ONE_MINUS_CONSTANT_COLOR -#define RL_CONSTANT_ALPHA 0x8003 // GL_CONSTANT_ALPHA -#define RL_ONE_MINUS_CONSTANT_ALPHA 0x8004 // GL_ONE_MINUS_CONSTANT_ALPHA - -// GL blending functions/equations -#define RL_FUNC_ADD 0x8006 // GL_FUNC_ADD -#define RL_MIN 0x8007 // GL_MIN -#define RL_MAX 0x8008 // GL_MAX -#define RL_FUNC_SUBTRACT 0x800A // GL_FUNC_SUBTRACT -#define RL_FUNC_REVERSE_SUBTRACT 0x800B // GL_FUNC_REVERSE_SUBTRACT -#define RL_BLEND_EQUATION 0x8009 // GL_BLEND_EQUATION -#define RL_BLEND_EQUATION_RGB 0x8009 // GL_BLEND_EQUATION_RGB // (Same as BLEND_EQUATION) -#define RL_BLEND_EQUATION_ALPHA 0x883D // GL_BLEND_EQUATION_ALPHA -#define RL_BLEND_DST_RGB 0x80C8 // GL_BLEND_DST_RGB -#define RL_BLEND_SRC_RGB 0x80C9 // GL_BLEND_SRC_RGB -#define RL_BLEND_DST_ALPHA 0x80CA // GL_BLEND_DST_ALPHA -#define RL_BLEND_SRC_ALPHA 0x80CB // GL_BLEND_SRC_ALPHA -#define RL_BLEND_COLOR 0x8005 // GL_BLEND_COLOR - -#define RL_READ_FRAMEBUFFER 0x8CA8 // GL_READ_FRAMEBUFFER -#define RL_DRAW_FRAMEBUFFER 0x8CA9 // GL_DRAW_FRAMEBUFFER - -// Default shader vertex attribute locations -#ifndef RL_DEFAULT_SHADER_ATTRIB_LOCATION_POSITION - #define RL_DEFAULT_SHADER_ATTRIB_LOCATION_POSITION 0 -#endif -#ifndef RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD - #define RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD 1 -#endif -#ifndef RL_DEFAULT_SHADER_ATTRIB_LOCATION_NORMAL - #define RL_DEFAULT_SHADER_ATTRIB_LOCATION_NORMAL 2 -#endif -#ifndef RL_DEFAULT_SHADER_ATTRIB_LOCATION_COLOR - #define RL_DEFAULT_SHADER_ATTRIB_LOCATION_COLOR 3 -#endif - #ifndef RL_DEFAULT_SHADER_ATTRIB_LOCATION_TANGENT -#define RL_DEFAULT_SHADER_ATTRIB_LOCATION_TANGENT 4 -#endif -#ifndef RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD2 - #define RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD2 5 -#endif -#ifndef RL_DEFAULT_SHADER_ATTRIB_LOCATION_INDICES - #define RL_DEFAULT_SHADER_ATTRIB_LOCATION_INDICES 6 -#endif -#ifdef RL_SUPPORT_MESH_GPU_SKINNING -#ifndef RL_DEFAULT_SHADER_ATTRIB_LOCATION_BONEIDS - #define RL_DEFAULT_SHADER_ATTRIB_LOCATION_BONEIDS 7 -#endif -#ifndef RL_DEFAULT_SHADER_ATTRIB_LOCATION_BONEWEIGHTS - #define RL_DEFAULT_SHADER_ATTRIB_LOCATION_BONEWEIGHTS 8 -#endif -#endif - -//---------------------------------------------------------------------------------- -// Types and Structures Definition -//---------------------------------------------------------------------------------- -#if (defined(__STDC__) && __STDC_VERSION__ >= 199901L) || (defined(_MSC_VER) && _MSC_VER >= 1800) - #include -#elif !defined(__cplusplus) && !defined(bool) && !defined(RL_BOOL_TYPE) - // Boolean type -typedef enum bool { false = 0, true = !false } bool; -#endif - -#if !defined(RL_MATRIX_TYPE) -// Matrix, 4x4 components, column major, OpenGL style, right handed -typedef struct Matrix { - float m0, m4, m8, m12; // Matrix first row (4 components) - float m1, m5, m9, m13; // Matrix second row (4 components) - float m2, m6, m10, m14; // Matrix third row (4 components) - float m3, m7, m11, m15; // Matrix fourth row (4 components) -} Matrix; -#define RL_MATRIX_TYPE -#endif - -// Dynamic vertex buffers (position + texcoords + colors + indices arrays) -typedef struct rlVertexBuffer { - int elementCount; // Number of elements in the buffer (QUADS) - - float *vertices; // Vertex position (XYZ - 3 components per vertex) (shader-location = 0) - float *texcoords; // Vertex texture coordinates (UV - 2 components per vertex) (shader-location = 1) - float *normals; // Vertex normal (XYZ - 3 components per vertex) (shader-location = 2) - unsigned char *colors; // Vertex colors (RGBA - 4 components per vertex) (shader-location = 3) -#if defined(GRAPHICS_API_OPENGL_11) || defined(GRAPHICS_API_OPENGL_33) - unsigned int *indices; // Vertex indices (in case vertex data comes indexed) (6 indices per quad) -#endif -#if defined(GRAPHICS_API_OPENGL_ES2) - unsigned short *indices; // Vertex indices (in case vertex data comes indexed) (6 indices per quad) -#endif - unsigned int vaoId; // OpenGL Vertex Array Object id - unsigned int vboId[5]; // OpenGL Vertex Buffer Objects id (5 types of vertex data) -} rlVertexBuffer; - -// Draw call type -// NOTE: Only texture changes register a new draw, other state-change-related elements are not -// used at this moment (vaoId, shaderId, matrices), raylib just forces a batch draw call if any -// of those state-change happens (this is done in core module) -typedef struct rlDrawCall { - int mode; // Drawing mode: LINES, TRIANGLES, QUADS - int vertexCount; // Number of vertex of the draw - int vertexAlignment; // Number of vertex required for index alignment (LINES, TRIANGLES) - //unsigned int vaoId; // Vertex array id to be used on the draw -> Using RLGL.currentBatch->vertexBuffer.vaoId - //unsigned int shaderId; // Shader id to be used on the draw -> Using RLGL.currentShaderId - unsigned int textureId; // Texture id to be used on the draw -> Use to create new draw call if changes - - //Matrix projection; // Projection matrix for this draw -> Using RLGL.projection by default - //Matrix modelview; // Modelview matrix for this draw -> Using RLGL.modelview by default -} rlDrawCall; - -// rlRenderBatch type -typedef struct rlRenderBatch { - int bufferCount; // Number of vertex buffers (multi-buffering support) - int currentBuffer; // Current buffer tracking in case of multi-buffering - rlVertexBuffer *vertexBuffer; // Dynamic buffer(s) for vertex data - - rlDrawCall *draws; // Draw calls array, depends on textureId - int drawCounter; // Draw calls counter - float currentDepth; // Current depth value for next draw -} rlRenderBatch; - -// OpenGL version -typedef enum { - RL_OPENGL_11 = 1, // OpenGL 1.1 - RL_OPENGL_21, // OpenGL 2.1 (GLSL 120) - RL_OPENGL_33, // OpenGL 3.3 (GLSL 330) - RL_OPENGL_43, // OpenGL 4.3 (using GLSL 330) - RL_OPENGL_ES_20, // OpenGL ES 2.0 (GLSL 100) - RL_OPENGL_ES_30 // OpenGL ES 3.0 (GLSL 300 es) -} rlGlVersion; - -// Trace log level -// NOTE: Organized by priority level -typedef enum { - RL_LOG_ALL = 0, // Display all logs - RL_LOG_TRACE, // Trace logging, intended for internal use only - RL_LOG_DEBUG, // Debug logging, used for internal debugging, it should be disabled on release builds - RL_LOG_INFO, // Info logging, used for program execution info - RL_LOG_WARNING, // Warning logging, used on recoverable failures - RL_LOG_ERROR, // Error logging, used on unrecoverable failures - RL_LOG_FATAL, // Fatal logging, used to abort program: exit(EXIT_FAILURE) - RL_LOG_NONE // Disable logging -} rlTraceLogLevel; - -// Texture pixel formats -// NOTE: Support depends on OpenGL version -typedef enum { - RL_PIXELFORMAT_UNCOMPRESSED_GRAYSCALE = 1, // 8 bit per pixel (no alpha) - RL_PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA, // 8*2 bpp (2 channels) - RL_PIXELFORMAT_UNCOMPRESSED_R5G6B5, // 16 bpp - RL_PIXELFORMAT_UNCOMPRESSED_R8G8B8, // 24 bpp - RL_PIXELFORMAT_UNCOMPRESSED_R5G5B5A1, // 16 bpp (1 bit alpha) - RL_PIXELFORMAT_UNCOMPRESSED_R4G4B4A4, // 16 bpp (4 bit alpha) - RL_PIXELFORMAT_UNCOMPRESSED_R8G8B8A8, // 32 bpp - RL_PIXELFORMAT_UNCOMPRESSED_R32, // 32 bpp (1 channel - float) - RL_PIXELFORMAT_UNCOMPRESSED_R32G32B32, // 32*3 bpp (3 channels - float) - RL_PIXELFORMAT_UNCOMPRESSED_R32G32B32A32, // 32*4 bpp (4 channels - float) - RL_PIXELFORMAT_UNCOMPRESSED_R16, // 16 bpp (1 channel - half float) - RL_PIXELFORMAT_UNCOMPRESSED_R16G16B16, // 16*3 bpp (3 channels - half float) - RL_PIXELFORMAT_UNCOMPRESSED_R16G16B16A16, // 16*4 bpp (4 channels - half float) - RL_PIXELFORMAT_COMPRESSED_DXT1_RGB, // 4 bpp (no alpha) - RL_PIXELFORMAT_COMPRESSED_DXT1_RGBA, // 4 bpp (1 bit alpha) - RL_PIXELFORMAT_COMPRESSED_DXT3_RGBA, // 8 bpp - RL_PIXELFORMAT_COMPRESSED_DXT5_RGBA, // 8 bpp - RL_PIXELFORMAT_COMPRESSED_ETC1_RGB, // 4 bpp - RL_PIXELFORMAT_COMPRESSED_ETC2_RGB, // 4 bpp - RL_PIXELFORMAT_COMPRESSED_ETC2_EAC_RGBA, // 8 bpp - RL_PIXELFORMAT_COMPRESSED_PVRT_RGB, // 4 bpp - RL_PIXELFORMAT_COMPRESSED_PVRT_RGBA, // 4 bpp - RL_PIXELFORMAT_COMPRESSED_ASTC_4x4_RGBA, // 8 bpp - RL_PIXELFORMAT_COMPRESSED_ASTC_8x8_RGBA // 2 bpp -} rlPixelFormat; - -// Texture parameters: filter mode -// NOTE 1: Filtering considers mipmaps if available in the texture -// NOTE 2: Filter is accordingly set for minification and magnification -typedef enum { - RL_TEXTURE_FILTER_POINT = 0, // No filter, just pixel approximation - RL_TEXTURE_FILTER_BILINEAR, // Linear filtering - RL_TEXTURE_FILTER_TRILINEAR, // Trilinear filtering (linear with mipmaps) - RL_TEXTURE_FILTER_ANISOTROPIC_4X, // Anisotropic filtering 4x - RL_TEXTURE_FILTER_ANISOTROPIC_8X, // Anisotropic filtering 8x - RL_TEXTURE_FILTER_ANISOTROPIC_16X, // Anisotropic filtering 16x -} rlTextureFilter; - -// Color blending modes (pre-defined) -typedef enum { - RL_BLEND_ALPHA = 0, // Blend textures considering alpha (default) - RL_BLEND_ADDITIVE, // Blend textures adding colors - RL_BLEND_MULTIPLIED, // Blend textures multiplying colors - RL_BLEND_ADD_COLORS, // Blend textures adding colors (alternative) - RL_BLEND_SUBTRACT_COLORS, // Blend textures subtracting colors (alternative) - RL_BLEND_ALPHA_PREMULTIPLY, // Blend premultiplied textures considering alpha - RL_BLEND_CUSTOM, // Blend textures using custom src/dst factors (use rlSetBlendFactors()) - RL_BLEND_CUSTOM_SEPARATE // Blend textures using custom src/dst factors (use rlSetBlendFactorsSeparate()) -} rlBlendMode; - -// Shader location point type -typedef enum { - RL_SHADER_LOC_VERTEX_POSITION = 0, // Shader location: vertex attribute: position - RL_SHADER_LOC_VERTEX_TEXCOORD01, // Shader location: vertex attribute: texcoord01 - RL_SHADER_LOC_VERTEX_TEXCOORD02, // Shader location: vertex attribute: texcoord02 - RL_SHADER_LOC_VERTEX_NORMAL, // Shader location: vertex attribute: normal - RL_SHADER_LOC_VERTEX_TANGENT, // Shader location: vertex attribute: tangent - RL_SHADER_LOC_VERTEX_COLOR, // Shader location: vertex attribute: color - RL_SHADER_LOC_MATRIX_MVP, // Shader location: matrix uniform: model-view-projection - RL_SHADER_LOC_MATRIX_VIEW, // Shader location: matrix uniform: view (camera transform) - RL_SHADER_LOC_MATRIX_PROJECTION, // Shader location: matrix uniform: projection - RL_SHADER_LOC_MATRIX_MODEL, // Shader location: matrix uniform: model (transform) - RL_SHADER_LOC_MATRIX_NORMAL, // Shader location: matrix uniform: normal - RL_SHADER_LOC_VECTOR_VIEW, // Shader location: vector uniform: view - RL_SHADER_LOC_COLOR_DIFFUSE, // Shader location: vector uniform: diffuse color - RL_SHADER_LOC_COLOR_SPECULAR, // Shader location: vector uniform: specular color - RL_SHADER_LOC_COLOR_AMBIENT, // Shader location: vector uniform: ambient color - RL_SHADER_LOC_MAP_ALBEDO, // Shader location: sampler2d texture: albedo (same as: RL_SHADER_LOC_MAP_DIFFUSE) - RL_SHADER_LOC_MAP_METALNESS, // Shader location: sampler2d texture: metalness (same as: RL_SHADER_LOC_MAP_SPECULAR) - RL_SHADER_LOC_MAP_NORMAL, // Shader location: sampler2d texture: normal - RL_SHADER_LOC_MAP_ROUGHNESS, // Shader location: sampler2d texture: roughness - RL_SHADER_LOC_MAP_OCCLUSION, // Shader location: sampler2d texture: occlusion - RL_SHADER_LOC_MAP_EMISSION, // Shader location: sampler2d texture: emission - RL_SHADER_LOC_MAP_HEIGHT, // Shader location: sampler2d texture: height - RL_SHADER_LOC_MAP_CUBEMAP, // Shader location: samplerCube texture: cubemap - RL_SHADER_LOC_MAP_IRRADIANCE, // Shader location: samplerCube texture: irradiance - RL_SHADER_LOC_MAP_PREFILTER, // Shader location: samplerCube texture: prefilter - RL_SHADER_LOC_MAP_BRDF // Shader location: sampler2d texture: brdf -} rlShaderLocationIndex; - -#define RL_SHADER_LOC_MAP_DIFFUSE RL_SHADER_LOC_MAP_ALBEDO -#define RL_SHADER_LOC_MAP_SPECULAR RL_SHADER_LOC_MAP_METALNESS - -// Shader uniform data type -typedef enum { - RL_SHADER_UNIFORM_FLOAT = 0, // Shader uniform type: float - RL_SHADER_UNIFORM_VEC2, // Shader uniform type: vec2 (2 float) - RL_SHADER_UNIFORM_VEC3, // Shader uniform type: vec3 (3 float) - RL_SHADER_UNIFORM_VEC4, // Shader uniform type: vec4 (4 float) - RL_SHADER_UNIFORM_INT, // Shader uniform type: int - RL_SHADER_UNIFORM_IVEC2, // Shader uniform type: ivec2 (2 int) - RL_SHADER_UNIFORM_IVEC3, // Shader uniform type: ivec3 (3 int) - RL_SHADER_UNIFORM_IVEC4, // Shader uniform type: ivec4 (4 int) - RL_SHADER_UNIFORM_UINT, // Shader uniform type: unsigned int - RL_SHADER_UNIFORM_UIVEC2, // Shader uniform type: uivec2 (2 unsigned int) - RL_SHADER_UNIFORM_UIVEC3, // Shader uniform type: uivec3 (3 unsigned int) - RL_SHADER_UNIFORM_UIVEC4, // Shader uniform type: uivec4 (4 unsigned int) - RL_SHADER_UNIFORM_SAMPLER2D // Shader uniform type: sampler2d -} rlShaderUniformDataType; - -// Shader attribute data types -typedef enum { - RL_SHADER_ATTRIB_FLOAT = 0, // Shader attribute type: float - RL_SHADER_ATTRIB_VEC2, // Shader attribute type: vec2 (2 float) - RL_SHADER_ATTRIB_VEC3, // Shader attribute type: vec3 (3 float) - RL_SHADER_ATTRIB_VEC4 // Shader attribute type: vec4 (4 float) -} rlShaderAttributeDataType; - -// Framebuffer attachment type -// NOTE: By default up to 8 color channels defined, but it can be more -typedef enum { - RL_ATTACHMENT_COLOR_CHANNEL0 = 0, // Framebuffer attachment type: color 0 - RL_ATTACHMENT_COLOR_CHANNEL1 = 1, // Framebuffer attachment type: color 1 - RL_ATTACHMENT_COLOR_CHANNEL2 = 2, // Framebuffer attachment type: color 2 - RL_ATTACHMENT_COLOR_CHANNEL3 = 3, // Framebuffer attachment type: color 3 - RL_ATTACHMENT_COLOR_CHANNEL4 = 4, // Framebuffer attachment type: color 4 - RL_ATTACHMENT_COLOR_CHANNEL5 = 5, // Framebuffer attachment type: color 5 - RL_ATTACHMENT_COLOR_CHANNEL6 = 6, // Framebuffer attachment type: color 6 - RL_ATTACHMENT_COLOR_CHANNEL7 = 7, // Framebuffer attachment type: color 7 - RL_ATTACHMENT_DEPTH = 100, // Framebuffer attachment type: depth - RL_ATTACHMENT_STENCIL = 200, // Framebuffer attachment type: stencil -} rlFramebufferAttachType; - -// Framebuffer texture attachment type -typedef enum { - RL_ATTACHMENT_CUBEMAP_POSITIVE_X = 0, // Framebuffer texture attachment type: cubemap, +X side - RL_ATTACHMENT_CUBEMAP_NEGATIVE_X = 1, // Framebuffer texture attachment type: cubemap, -X side - RL_ATTACHMENT_CUBEMAP_POSITIVE_Y = 2, // Framebuffer texture attachment type: cubemap, +Y side - RL_ATTACHMENT_CUBEMAP_NEGATIVE_Y = 3, // Framebuffer texture attachment type: cubemap, -Y side - RL_ATTACHMENT_CUBEMAP_POSITIVE_Z = 4, // Framebuffer texture attachment type: cubemap, +Z side - RL_ATTACHMENT_CUBEMAP_NEGATIVE_Z = 5, // Framebuffer texture attachment type: cubemap, -Z side - RL_ATTACHMENT_TEXTURE2D = 100, // Framebuffer texture attachment type: texture2d - RL_ATTACHMENT_RENDERBUFFER = 200, // Framebuffer texture attachment type: renderbuffer -} rlFramebufferAttachTextureType; - -// Face culling mode -typedef enum { - RL_CULL_FACE_FRONT = 0, - RL_CULL_FACE_BACK -} rlCullMode; - -//------------------------------------------------------------------------------------ -// Functions Declaration - Matrix operations -//------------------------------------------------------------------------------------ - -#if defined(__cplusplus) -extern "C" { // Prevents name mangling of functions -#endif - -RLAPI void rlMatrixMode(int mode); // Choose the current matrix to be transformed -RLAPI void rlPushMatrix(void); // Push the current matrix to stack -RLAPI void rlPopMatrix(void); // Pop latest inserted matrix from stack -RLAPI void rlLoadIdentity(void); // Reset current matrix to identity matrix -RLAPI void rlTranslatef(float x, float y, float z); // Multiply the current matrix by a translation matrix -RLAPI void rlRotatef(float angle, float x, float y, float z); // Multiply the current matrix by a rotation matrix -RLAPI void rlScalef(float x, float y, float z); // Multiply the current matrix by a scaling matrix -RLAPI void rlMultMatrixf(const float *matf); // Multiply the current matrix by another matrix -RLAPI void rlFrustum(double left, double right, double bottom, double top, double znear, double zfar); -RLAPI void rlOrtho(double left, double right, double bottom, double top, double znear, double zfar); -RLAPI void rlViewport(int x, int y, int width, int height); // Set the viewport area -RLAPI void rlSetClipPlanes(double nearPlane, double farPlane); // Set clip planes distances -RLAPI double rlGetCullDistanceNear(void); // Get cull plane distance near -RLAPI double rlGetCullDistanceFar(void); // Get cull plane distance far - -//------------------------------------------------------------------------------------ -// Functions Declaration - Vertex level operations -//------------------------------------------------------------------------------------ -RLAPI void rlBegin(int mode); // Initialize drawing mode (how to organize vertex) -RLAPI void rlEnd(void); // Finish vertex providing -RLAPI void rlVertex2i(int x, int y); // Define one vertex (position) - 2 int -RLAPI void rlVertex2f(float x, float y); // Define one vertex (position) - 2 float -RLAPI void rlVertex3f(float x, float y, float z); // Define one vertex (position) - 3 float -RLAPI void rlTexCoord2f(float x, float y); // Define one vertex (texture coordinate) - 2 float -RLAPI void rlNormal3f(float x, float y, float z); // Define one vertex (normal) - 3 float -RLAPI void rlColor4ub(unsigned char r, unsigned char g, unsigned char b, unsigned char a); // Define one vertex (color) - 4 byte -RLAPI void rlColor3f(float x, float y, float z); // Define one vertex (color) - 3 float -RLAPI void rlColor4f(float x, float y, float z, float w); // Define one vertex (color) - 4 float - -//------------------------------------------------------------------------------------ -// Functions Declaration - OpenGL style functions (common to 1.1, 3.3+, ES2) -// NOTE: This functions are used to completely abstract raylib code from OpenGL layer, -// some of them are direct wrappers over OpenGL calls, some others are custom -//------------------------------------------------------------------------------------ - -// Vertex buffers state -RLAPI bool rlEnableVertexArray(unsigned int vaoId); // Enable vertex array (VAO, if supported) -RLAPI void rlDisableVertexArray(void); // Disable vertex array (VAO, if supported) -RLAPI void rlEnableVertexBuffer(unsigned int id); // Enable vertex buffer (VBO) -RLAPI void rlDisableVertexBuffer(void); // Disable vertex buffer (VBO) -RLAPI void rlEnableVertexBufferElement(unsigned int id); // Enable vertex buffer element (VBO element) -RLAPI void rlDisableVertexBufferElement(void); // Disable vertex buffer element (VBO element) -RLAPI void rlEnableVertexAttribute(unsigned int index); // Enable vertex attribute index -RLAPI void rlDisableVertexAttribute(unsigned int index); // Disable vertex attribute index -#if defined(GRAPHICS_API_OPENGL_11) -RLAPI void rlEnableStatePointer(int vertexAttribType, void *buffer); // Enable attribute state pointer -RLAPI void rlDisableStatePointer(int vertexAttribType); // Disable attribute state pointer -#endif - -// Textures state -RLAPI void rlActiveTextureSlot(int slot); // Select and active a texture slot -RLAPI void rlEnableTexture(unsigned int id); // Enable texture -RLAPI void rlDisableTexture(void); // Disable texture -RLAPI void rlEnableTextureCubemap(unsigned int id); // Enable texture cubemap -RLAPI void rlDisableTextureCubemap(void); // Disable texture cubemap -RLAPI void rlTextureParameters(unsigned int id, int param, int value); // Set texture parameters (filter, wrap) -RLAPI void rlCubemapParameters(unsigned int id, int param, int value); // Set cubemap parameters (filter, wrap) - -// Shader state -RLAPI void rlEnableShader(unsigned int id); // Enable shader program -RLAPI void rlDisableShader(void); // Disable shader program - -// Framebuffer state -RLAPI void rlEnableFramebuffer(unsigned int id); // Enable render texture (fbo) -RLAPI void rlDisableFramebuffer(void); // Disable render texture (fbo), return to default framebuffer -RLAPI unsigned int rlGetActiveFramebuffer(void); // Get the currently active render texture (fbo), 0 for default framebuffer -RLAPI void rlActiveDrawBuffers(int count); // Activate multiple draw color buffers -RLAPI void rlBlitFramebuffer(int srcX, int srcY, int srcWidth, int srcHeight, int dstX, int dstY, int dstWidth, int dstHeight, int bufferMask); // Blit active framebuffer to main framebuffer -RLAPI void rlBindFramebuffer(unsigned int target, unsigned int framebuffer); // Bind framebuffer (FBO) - -// General render state -RLAPI void rlEnableColorBlend(void); // Enable color blending -RLAPI void rlDisableColorBlend(void); // Disable color blending -RLAPI void rlEnableDepthTest(void); // Enable depth test -RLAPI void rlDisableDepthTest(void); // Disable depth test -RLAPI void rlEnableDepthMask(void); // Enable depth write -RLAPI void rlDisableDepthMask(void); // Disable depth write -RLAPI void rlEnableBackfaceCulling(void); // Enable backface culling -RLAPI void rlDisableBackfaceCulling(void); // Disable backface culling -RLAPI void rlColorMask(bool r, bool g, bool b, bool a); // Color mask control -RLAPI void rlSetCullFace(int mode); // Set face culling mode -RLAPI void rlEnableScissorTest(void); // Enable scissor test -RLAPI void rlDisableScissorTest(void); // Disable scissor test -RLAPI void rlScissor(int x, int y, int width, int height); // Scissor test -RLAPI void rlEnableWireMode(void); // Enable wire mode -RLAPI void rlEnablePointMode(void); // Enable point mode -RLAPI void rlDisableWireMode(void); // Disable wire (and point) mode -RLAPI void rlSetLineWidth(float width); // Set the line drawing width -RLAPI float rlGetLineWidth(void); // Get the line drawing width -RLAPI void rlEnableSmoothLines(void); // Enable line aliasing -RLAPI void rlDisableSmoothLines(void); // Disable line aliasing -RLAPI void rlEnableStereoRender(void); // Enable stereo rendering -RLAPI void rlDisableStereoRender(void); // Disable stereo rendering -RLAPI bool rlIsStereoRenderEnabled(void); // Check if stereo render is enabled - -RLAPI void rlClearColor(unsigned char r, unsigned char g, unsigned char b, unsigned char a); // Clear color buffer with color -RLAPI void rlClearScreenBuffers(void); // Clear used screen buffers (color and depth) -RLAPI void rlCheckErrors(void); // Check and log OpenGL error codes -RLAPI void rlSetBlendMode(int mode); // Set blending mode -RLAPI void rlSetBlendFactors(int glSrcFactor, int glDstFactor, int glEquation); // Set blending mode factor and equation (using OpenGL factors) -RLAPI void rlSetBlendFactorsSeparate(int glSrcRGB, int glDstRGB, int glSrcAlpha, int glDstAlpha, int glEqRGB, int glEqAlpha); // Set blending mode factors and equations separately (using OpenGL factors) - -//------------------------------------------------------------------------------------ -// Functions Declaration - rlgl functionality -//------------------------------------------------------------------------------------ -// rlgl initialization functions -RLAPI void rlglInit(int width, int height); // Initialize rlgl (buffers, shaders, textures, states) -RLAPI void rlglClose(void); // De-initialize rlgl (buffers, shaders, textures) -RLAPI void rlLoadExtensions(void *loader); // Load OpenGL extensions (loader function required) -RLAPI int rlGetVersion(void); // Get current OpenGL version -RLAPI void rlSetFramebufferWidth(int width); // Set current framebuffer width -RLAPI int rlGetFramebufferWidth(void); // Get default framebuffer width -RLAPI void rlSetFramebufferHeight(int height); // Set current framebuffer height -RLAPI int rlGetFramebufferHeight(void); // Get default framebuffer height - -RLAPI unsigned int rlGetTextureIdDefault(void); // Get default texture id -RLAPI unsigned int rlGetShaderIdDefault(void); // Get default shader id -RLAPI int *rlGetShaderLocsDefault(void); // Get default shader locations - -// Render batch management -// NOTE: rlgl provides a default render batch to behave like OpenGL 1.1 immediate mode -// but this render batch API is exposed in case of custom batches are required -RLAPI rlRenderBatch rlLoadRenderBatch(int numBuffers, int bufferElements); // Load a render batch system -RLAPI void rlUnloadRenderBatch(rlRenderBatch batch); // Unload render batch system -RLAPI void rlDrawRenderBatch(rlRenderBatch *batch); // Draw render batch data (Update->Draw->Reset) -RLAPI void rlSetRenderBatchActive(rlRenderBatch *batch); // Set the active render batch for rlgl (NULL for default internal) -RLAPI void rlDrawRenderBatchActive(void); // Update and draw internal render batch -RLAPI bool rlCheckRenderBatchLimit(int vCount); // Check internal buffer overflow for a given number of vertex - -RLAPI void rlSetTexture(unsigned int id); // Set current texture for render batch and check buffers limits - -//------------------------------------------------------------------------------------------------------------------------ - -// Vertex buffers management -RLAPI unsigned int rlLoadVertexArray(void); // Load vertex array (vao) if supported -RLAPI unsigned int rlLoadVertexBuffer(const void *buffer, int size, bool dynamic); // Load a vertex buffer object -RLAPI unsigned int rlLoadVertexBufferElement(const void *buffer, int size, bool dynamic); // Load vertex buffer elements object -RLAPI void rlUpdateVertexBuffer(unsigned int bufferId, const void *data, int dataSize, int offset); // Update vertex buffer object data on GPU buffer -RLAPI void rlUpdateVertexBufferElements(unsigned int id, const void *data, int dataSize, int offset); // Update vertex buffer elements data on GPU buffer -RLAPI void rlUnloadVertexArray(unsigned int vaoId); // Unload vertex array (vao) -RLAPI void rlUnloadVertexBuffer(unsigned int vboId); // Unload vertex buffer object -RLAPI void rlSetVertexAttribute(unsigned int index, int compSize, int type, bool normalized, int stride, int offset); // Set vertex attribute data configuration -RLAPI void rlSetVertexAttributeDivisor(unsigned int index, int divisor); // Set vertex attribute data divisor -RLAPI void rlSetVertexAttributeDefault(int locIndex, const void *value, int attribType, int count); // Set vertex attribute default value, when attribute to provided -RLAPI void rlDrawVertexArray(int offset, int count); // Draw vertex array (currently active vao) -RLAPI void rlDrawVertexArrayElements(int offset, int count, const void *buffer); // Draw vertex array elements -RLAPI void rlDrawVertexArrayInstanced(int offset, int count, int instances); // Draw vertex array (currently active vao) with instancing -RLAPI void rlDrawVertexArrayElementsInstanced(int offset, int count, const void *buffer, int instances); // Draw vertex array elements with instancing - -// Textures management -RLAPI unsigned int rlLoadTexture(const void *data, int width, int height, int format, int mipmapCount); // Load texture data -RLAPI unsigned int rlLoadTextureDepth(int width, int height, bool useRenderBuffer); // Load depth texture/renderbuffer (to be attached to fbo) -RLAPI unsigned int rlLoadTextureCubemap(const void *data, int size, int format, int mipmapCount); // Load texture cubemap data -RLAPI void rlUpdateTexture(unsigned int id, int offsetX, int offsetY, int width, int height, int format, const void *data); // Update texture with new data on GPU -RLAPI void rlGetGlTextureFormats(int format, unsigned int *glInternalFormat, unsigned int *glFormat, unsigned int *glType); // Get OpenGL internal formats -RLAPI const char *rlGetPixelFormatName(unsigned int format); // Get name string for pixel format -RLAPI void rlUnloadTexture(unsigned int id); // Unload texture from GPU memory -RLAPI void rlGenTextureMipmaps(unsigned int id, int width, int height, int format, int *mipmaps); // Generate mipmap data for selected texture -RLAPI void *rlReadTexturePixels(unsigned int id, int width, int height, int format); // Read texture pixel data -RLAPI unsigned char *rlReadScreenPixels(int width, int height); // Read screen pixel data (color buffer) - -// Framebuffer management (fbo) -RLAPI unsigned int rlLoadFramebuffer(void); // Load an empty framebuffer -RLAPI void rlFramebufferAttach(unsigned int fboId, unsigned int texId, int attachType, int texType, int mipLevel); // Attach texture/renderbuffer to a framebuffer -RLAPI bool rlFramebufferComplete(unsigned int id); // Verify framebuffer is complete -RLAPI void rlUnloadFramebuffer(unsigned int id); // Delete framebuffer from GPU - -// Shaders management -RLAPI unsigned int rlLoadShaderCode(const char *vsCode, const char *fsCode); // Load shader from code strings -RLAPI unsigned int rlCompileShader(const char *shaderCode, int type); // Compile custom shader and return shader id (type: RL_VERTEX_SHADER, RL_FRAGMENT_SHADER, RL_COMPUTE_SHADER) -RLAPI unsigned int rlLoadShaderProgram(unsigned int vShaderId, unsigned int fShaderId); // Load custom shader program -RLAPI void rlUnloadShaderProgram(unsigned int id); // Unload shader program -RLAPI int rlGetLocationUniform(unsigned int shaderId, const char *uniformName); // Get shader location uniform -RLAPI int rlGetLocationAttrib(unsigned int shaderId, const char *attribName); // Get shader location attribute -RLAPI void rlSetUniform(int locIndex, const void *value, int uniformType, int count); // Set shader value uniform -RLAPI void rlSetUniformMatrix(int locIndex, Matrix mat); // Set shader value matrix -RLAPI void rlSetUniformMatrices(int locIndex, const Matrix *mat, int count); // Set shader value matrices -RLAPI void rlSetUniformSampler(int locIndex, unsigned int textureId); // Set shader value sampler -RLAPI void rlSetShader(unsigned int id, int *locs); // Set shader currently active (id and locations) - -// Compute shader management -RLAPI unsigned int rlLoadComputeShaderProgram(unsigned int shaderId); // Load compute shader program -RLAPI void rlComputeShaderDispatch(unsigned int groupX, unsigned int groupY, unsigned int groupZ); // Dispatch compute shader (equivalent to *draw* for graphics pipeline) - -// Shader buffer storage object management (ssbo) -RLAPI unsigned int rlLoadShaderBuffer(unsigned int size, const void *data, int usageHint); // Load shader storage buffer object (SSBO) -RLAPI void rlUnloadShaderBuffer(unsigned int ssboId); // Unload shader storage buffer object (SSBO) -RLAPI void rlUpdateShaderBuffer(unsigned int id, const void *data, unsigned int dataSize, unsigned int offset); // Update SSBO buffer data -RLAPI void rlBindShaderBuffer(unsigned int id, unsigned int index); // Bind SSBO buffer -RLAPI void rlReadShaderBuffer(unsigned int id, void *dest, unsigned int count, unsigned int offset); // Read SSBO buffer data (GPU->CPU) -RLAPI void rlCopyShaderBuffer(unsigned int destId, unsigned int srcId, unsigned int destOffset, unsigned int srcOffset, unsigned int count); // Copy SSBO data between buffers -RLAPI unsigned int rlGetShaderBufferSize(unsigned int id); // Get SSBO buffer size - -// Buffer management -RLAPI void rlBindImageTexture(unsigned int id, unsigned int index, int format, bool readonly); // Bind image texture - -// Matrix state management -RLAPI Matrix rlGetMatrixModelview(void); // Get internal modelview matrix -RLAPI Matrix rlGetMatrixProjection(void); // Get internal projection matrix -RLAPI Matrix rlGetMatrixTransform(void); // Get internal accumulated transform matrix -RLAPI Matrix rlGetMatrixProjectionStereo(int eye); // Get internal projection matrix for stereo render (selected eye) -RLAPI Matrix rlGetMatrixViewOffsetStereo(int eye); // Get internal view offset matrix for stereo render (selected eye) -RLAPI void rlSetMatrixProjection(Matrix proj); // Set a custom projection matrix (replaces internal projection matrix) -RLAPI void rlSetMatrixModelview(Matrix view); // Set a custom modelview matrix (replaces internal modelview matrix) -RLAPI void rlSetMatrixProjectionStereo(Matrix right, Matrix left); // Set eyes projection matrices for stereo rendering -RLAPI void rlSetMatrixViewOffsetStereo(Matrix right, Matrix left); // Set eyes view offsets matrices for stereo rendering - -// Quick and dirty cube/quad buffers load->draw->unload -RLAPI void rlLoadDrawCube(void); // Load and draw a cube -RLAPI void rlLoadDrawQuad(void); // Load and draw a quad - -#if defined(__cplusplus) -} -#endif - -#endif // RLGL_H - -/*********************************************************************************** -* -* RLGL IMPLEMENTATION -* -************************************************************************************/ - -#if defined(RLGL_IMPLEMENTATION) - -// Expose OpenGL functions from glad in raylib -#if defined(BUILD_LIBTYPE_SHARED) - #define GLAD_API_CALL_EXPORT - #define GLAD_API_CALL_EXPORT_BUILD -#endif - -#if defined(GRAPHICS_API_OPENGL_11) - #if defined(__APPLE__) - #include // OpenGL 1.1 library for OSX - #include // OpenGL extensions library - #else - // APIENTRY for OpenGL function pointer declarations is required - #if !defined(APIENTRY) - #if defined(_WIN32) - #define APIENTRY __stdcall - #else - #define APIENTRY - #endif - #endif - // WINGDIAPI definition. Some Windows OpenGL headers need it - #if !defined(WINGDIAPI) && defined(_WIN32) - #define WINGDIAPI __declspec(dllimport) - #endif - - #include // OpenGL 1.1 library - #endif -#endif - -#if defined(GRAPHICS_API_OPENGL_33) - #define GLAD_MALLOC RL_MALLOC - #define GLAD_FREE RL_FREE - - #define GLAD_GL_IMPLEMENTATION - #include "external/glad.h" // GLAD extensions loading library, includes OpenGL headers -#endif - -#if defined(GRAPHICS_API_OPENGL_ES3) - #include // OpenGL ES 3.0 library - #define GL_GLEXT_PROTOTYPES - #include // OpenGL ES 2.0 extensions library -#elif defined(GRAPHICS_API_OPENGL_ES2) - // NOTE: OpenGL ES 2.0 can be enabled on Desktop platforms, - // in that case, functions are loaded from a custom glad for OpenGL ES 2.0 - #if defined(PLATFORM_DESKTOP_GLFW) || defined(PLATFORM_DESKTOP_SDL) - #define GLAD_GLES2_IMPLEMENTATION - #include "external/glad_gles2.h" - #else - #define GL_GLEXT_PROTOTYPES - //#include // EGL library -> not required, platform layer - #include // OpenGL ES 2.0 library - #include // OpenGL ES 2.0 extensions library - #endif - - // It seems OpenGL ES 2.0 instancing entry points are not defined on Raspberry Pi - // provided headers (despite being defined in official Khronos GLES2 headers) - #if defined(PLATFORM_DRM) - typedef void (GL_APIENTRYP PFNGLDRAWARRAYSINSTANCEDEXTPROC) (GLenum mode, GLint start, GLsizei count, GLsizei primcount); - typedef void (GL_APIENTRYP PFNGLDRAWELEMENTSINSTANCEDEXTPROC) (GLenum mode, GLsizei count, GLenum type, const void *indices, GLsizei primcount); - typedef void (GL_APIENTRYP PFNGLVERTEXATTRIBDIVISOREXTPROC) (GLuint index, GLuint divisor); - #endif -#endif - -#include // Required for: malloc(), free() -#include // Required for: strcmp(), strlen() [Used in rlglInit(), on extensions loading] -#include // Required for: sqrtf(), sinf(), cosf(), floor(), log() - -//---------------------------------------------------------------------------------- -// Defines and Macros -//---------------------------------------------------------------------------------- -#ifndef PI - #define PI 3.14159265358979323846f -#endif -#ifndef DEG2RAD - #define DEG2RAD (PI/180.0f) -#endif -#ifndef RAD2DEG - #define RAD2DEG (180.0f/PI) -#endif - -#ifndef GL_SHADING_LANGUAGE_VERSION - #define GL_SHADING_LANGUAGE_VERSION 0x8B8C -#endif - -#ifndef GL_COMPRESSED_RGB_S3TC_DXT1_EXT - #define GL_COMPRESSED_RGB_S3TC_DXT1_EXT 0x83F0 -#endif -#ifndef GL_COMPRESSED_RGBA_S3TC_DXT1_EXT - #define GL_COMPRESSED_RGBA_S3TC_DXT1_EXT 0x83F1 -#endif -#ifndef GL_COMPRESSED_RGBA_S3TC_DXT3_EXT - #define GL_COMPRESSED_RGBA_S3TC_DXT3_EXT 0x83F2 -#endif -#ifndef GL_COMPRESSED_RGBA_S3TC_DXT5_EXT - #define GL_COMPRESSED_RGBA_S3TC_DXT5_EXT 0x83F3 -#endif -#ifndef GL_ETC1_RGB8_OES - #define GL_ETC1_RGB8_OES 0x8D64 -#endif -#ifndef GL_COMPRESSED_RGB8_ETC2 - #define GL_COMPRESSED_RGB8_ETC2 0x9274 -#endif -#ifndef GL_COMPRESSED_RGBA8_ETC2_EAC - #define GL_COMPRESSED_RGBA8_ETC2_EAC 0x9278 -#endif -#ifndef GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG - #define GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG 0x8C00 -#endif -#ifndef GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG - #define GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG 0x8C02 -#endif -#ifndef GL_COMPRESSED_RGBA_ASTC_4x4_KHR - #define GL_COMPRESSED_RGBA_ASTC_4x4_KHR 0x93b0 -#endif -#ifndef GL_COMPRESSED_RGBA_ASTC_8x8_KHR - #define GL_COMPRESSED_RGBA_ASTC_8x8_KHR 0x93b7 -#endif - -#ifndef GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT - #define GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT 0x84FF -#endif -#ifndef GL_TEXTURE_MAX_ANISOTROPY_EXT - #define GL_TEXTURE_MAX_ANISOTROPY_EXT 0x84FE -#endif - -#ifndef GL_PROGRAM_POINT_SIZE - #define GL_PROGRAM_POINT_SIZE 0x8642 -#endif - -#ifndef GL_LINE_WIDTH - #define GL_LINE_WIDTH 0x0B21 -#endif - -#if defined(GRAPHICS_API_OPENGL_11) - #define GL_UNSIGNED_SHORT_5_6_5 0x8363 - #define GL_UNSIGNED_SHORT_5_5_5_1 0x8034 - #define GL_UNSIGNED_SHORT_4_4_4_4 0x8033 -#endif - -#if defined(GRAPHICS_API_OPENGL_21) - #define GL_LUMINANCE 0x1909 - #define GL_LUMINANCE_ALPHA 0x190A -#endif - -#if defined(GRAPHICS_API_OPENGL_ES2) - #define glClearDepth glClearDepthf - #if !defined(GRAPHICS_API_OPENGL_ES3) - #define GL_READ_FRAMEBUFFER GL_FRAMEBUFFER - #define GL_DRAW_FRAMEBUFFER GL_FRAMEBUFFER - #endif -#endif - -// Default shader vertex attribute names to set location points -#ifndef RL_DEFAULT_SHADER_ATTRIB_NAME_POSITION - #define RL_DEFAULT_SHADER_ATTRIB_NAME_POSITION "vertexPosition" // Bound by default to shader location: RL_DEFAULT_SHADER_ATTRIB_NAME_POSITION -#endif -#ifndef RL_DEFAULT_SHADER_ATTRIB_NAME_TEXCOORD - #define RL_DEFAULT_SHADER_ATTRIB_NAME_TEXCOORD "vertexTexCoord" // Bound by default to shader location: RL_DEFAULT_SHADER_ATTRIB_NAME_TEXCOORD -#endif -#ifndef RL_DEFAULT_SHADER_ATTRIB_NAME_NORMAL - #define RL_DEFAULT_SHADER_ATTRIB_NAME_NORMAL "vertexNormal" // Bound by default to shader location: RL_DEFAULT_SHADER_ATTRIB_NAME_NORMAL -#endif -#ifndef RL_DEFAULT_SHADER_ATTRIB_NAME_COLOR - #define RL_DEFAULT_SHADER_ATTRIB_NAME_COLOR "vertexColor" // Bound by default to shader location: RL_DEFAULT_SHADER_ATTRIB_NAME_COLOR -#endif -#ifndef RL_DEFAULT_SHADER_ATTRIB_NAME_TANGENT - #define RL_DEFAULT_SHADER_ATTRIB_NAME_TANGENT "vertexTangent" // Bound by default to shader location: RL_DEFAULT_SHADER_ATTRIB_NAME_TANGENT -#endif -#ifndef RL_DEFAULT_SHADER_ATTRIB_NAME_TEXCOORD2 - #define RL_DEFAULT_SHADER_ATTRIB_NAME_TEXCOORD2 "vertexTexCoord2" // Bound by default to shader location: RL_DEFAULT_SHADER_ATTRIB_NAME_TEXCOORD2 -#endif -#ifndef RL_DEFAULT_SHADER_ATTRIB_NAME_BONEIDS - #define RL_DEFAULT_SHADER_ATTRIB_NAME_BONEIDS "vertexBoneIds" // Bound by default to shader location: RL_DEFAULT_SHADER_ATTRIB_NAME_BONEIDS -#endif -#ifndef RL_DEFAULT_SHADER_ATTRIB_NAME_BONEWEIGHTS - #define RL_DEFAULT_SHADER_ATTRIB_NAME_BONEWEIGHTS "vertexBoneWeights" // Bound by default to shader location: RL_DEFAULT_SHADER_ATTRIB_NAME_BONEWEIGHTS -#endif - -#ifndef RL_DEFAULT_SHADER_UNIFORM_NAME_MVP - #define RL_DEFAULT_SHADER_UNIFORM_NAME_MVP "mvp" // model-view-projection matrix -#endif -#ifndef RL_DEFAULT_SHADER_UNIFORM_NAME_VIEW - #define RL_DEFAULT_SHADER_UNIFORM_NAME_VIEW "matView" // view matrix -#endif -#ifndef RL_DEFAULT_SHADER_UNIFORM_NAME_PROJECTION - #define RL_DEFAULT_SHADER_UNIFORM_NAME_PROJECTION "matProjection" // projection matrix -#endif -#ifndef RL_DEFAULT_SHADER_UNIFORM_NAME_MODEL - #define RL_DEFAULT_SHADER_UNIFORM_NAME_MODEL "matModel" // model matrix -#endif -#ifndef RL_DEFAULT_SHADER_UNIFORM_NAME_NORMAL - #define RL_DEFAULT_SHADER_UNIFORM_NAME_NORMAL "matNormal" // normal matrix (transpose(inverse(matModelView)) -#endif -#ifndef RL_DEFAULT_SHADER_UNIFORM_NAME_COLOR - #define RL_DEFAULT_SHADER_UNIFORM_NAME_COLOR "colDiffuse" // color diffuse (base tint color, multiplied by texture color) -#endif -#ifndef RL_DEFAULT_SHADER_UNIFORM_NAME_BONE_MATRICES - #define RL_DEFAULT_SHADER_UNIFORM_NAME_BONE_MATRICES "boneMatrices" // bone matrices -#endif -#ifndef RL_DEFAULT_SHADER_SAMPLER2D_NAME_TEXTURE0 - #define RL_DEFAULT_SHADER_SAMPLER2D_NAME_TEXTURE0 "texture0" // texture0 (texture slot active 0) -#endif -#ifndef RL_DEFAULT_SHADER_SAMPLER2D_NAME_TEXTURE1 - #define RL_DEFAULT_SHADER_SAMPLER2D_NAME_TEXTURE1 "texture1" // texture1 (texture slot active 1) -#endif -#ifndef RL_DEFAULT_SHADER_SAMPLER2D_NAME_TEXTURE2 - #define RL_DEFAULT_SHADER_SAMPLER2D_NAME_TEXTURE2 "texture2" // texture2 (texture slot active 2) -#endif - -//---------------------------------------------------------------------------------- -// Types and Structures Definition -//---------------------------------------------------------------------------------- -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) -typedef struct rlglData { - rlRenderBatch *currentBatch; // Current render batch - rlRenderBatch defaultBatch; // Default internal render batch - - struct { - int vertexCounter; // Current active render batch vertex counter (generic, used for all batches) - float texcoordx, texcoordy; // Current active texture coordinate (added on glVertex*()) - float normalx, normaly, normalz; // Current active normal (added on glVertex*()) - unsigned char colorr, colorg, colorb, colora; // Current active color (added on glVertex*()) - - int currentMatrixMode; // Current matrix mode - Matrix *currentMatrix; // Current matrix pointer - Matrix modelview; // Default modelview matrix - Matrix projection; // Default projection matrix - Matrix transform; // Transform matrix to be used with rlTranslate, rlRotate, rlScale - bool transformRequired; // Require transform matrix application to current draw-call vertex (if required) - Matrix stack[RL_MAX_MATRIX_STACK_SIZE];// Matrix stack for push/pop - int stackCounter; // Matrix stack counter - - unsigned int defaultTextureId; // Default texture used on shapes/poly drawing (required by shader) - unsigned int activeTextureId[RL_DEFAULT_BATCH_MAX_TEXTURE_UNITS]; // Active texture ids to be enabled on batch drawing (0 active by default) - unsigned int defaultVShaderId; // Default vertex shader id (used by default shader program) - unsigned int defaultFShaderId; // Default fragment shader id (used by default shader program) - unsigned int defaultShaderId; // Default shader program id, supports vertex color and diffuse texture - int *defaultShaderLocs; // Default shader locations pointer to be used on rendering - unsigned int currentShaderId; // Current shader id to be used on rendering (by default, defaultShaderId) - int *currentShaderLocs; // Current shader locations pointer to be used on rendering (by default, defaultShaderLocs) - - bool stereoRender; // Stereo rendering flag - Matrix projectionStereo[2]; // VR stereo rendering eyes projection matrices - Matrix viewOffsetStereo[2]; // VR stereo rendering eyes view offset matrices - - // Blending variables - int currentBlendMode; // Blending mode active - int glBlendSrcFactor; // Blending source factor - int glBlendDstFactor; // Blending destination factor - int glBlendEquation; // Blending equation - int glBlendSrcFactorRGB; // Blending source RGB factor - int glBlendDestFactorRGB; // Blending destination RGB factor - int glBlendSrcFactorAlpha; // Blending source alpha factor - int glBlendDestFactorAlpha; // Blending destination alpha factor - int glBlendEquationRGB; // Blending equation for RGB - int glBlendEquationAlpha; // Blending equation for alpha - bool glCustomBlendModeModified; // Custom blending factor and equation modification status - - int framebufferWidth; // Current framebuffer width - int framebufferHeight; // Current framebuffer height - - } State; // Renderer state - struct { - bool vao; // VAO support (OpenGL ES2 could not support VAO extension) (GL_ARB_vertex_array_object) - bool instancing; // Instancing supported (GL_ANGLE_instanced_arrays, GL_EXT_draw_instanced + GL_EXT_instanced_arrays) - bool texNPOT; // NPOT textures full support (GL_ARB_texture_non_power_of_two, GL_OES_texture_npot) - bool texDepth; // Depth textures supported (GL_ARB_depth_texture, GL_OES_depth_texture) - bool texDepthWebGL; // Depth textures supported WebGL specific (GL_WEBGL_depth_texture) - bool texFloat32; // float textures support (32 bit per channel) (GL_OES_texture_float) - bool texFloat16; // half float textures support (16 bit per channel) (GL_OES_texture_half_float) - bool texCompDXT; // DDS texture compression support (GL_EXT_texture_compression_s3tc, GL_WEBGL_compressed_texture_s3tc, GL_WEBKIT_WEBGL_compressed_texture_s3tc) - bool texCompETC1; // ETC1 texture compression support (GL_OES_compressed_ETC1_RGB8_texture, GL_WEBGL_compressed_texture_etc1) - bool texCompETC2; // ETC2/EAC texture compression support (GL_ARB_ES3_compatibility) - bool texCompPVRT; // PVR texture compression support (GL_IMG_texture_compression_pvrtc) - bool texCompASTC; // ASTC texture compression support (GL_KHR_texture_compression_astc_hdr, GL_KHR_texture_compression_astc_ldr) - bool texMirrorClamp; // Clamp mirror wrap mode supported (GL_EXT_texture_mirror_clamp) - bool texAnisoFilter; // Anisotropic texture filtering support (GL_EXT_texture_filter_anisotropic) - bool computeShader; // Compute shaders support (GL_ARB_compute_shader) - bool ssbo; // Shader storage buffer object support (GL_ARB_shader_storage_buffer_object) - - float maxAnisotropyLevel; // Maximum anisotropy level supported (minimum is 2.0f) - int maxDepthBits; // Maximum bits for depth component - - } ExtSupported; // Extensions supported flags -} rlglData; - -typedef void *(*rlglLoadProc)(const char *name); // OpenGL extension functions loader signature (same as GLADloadproc) - -#endif // GRAPHICS_API_OPENGL_33 || GRAPHICS_API_OPENGL_ES2 - -//---------------------------------------------------------------------------------- -// Global Variables Definition -//---------------------------------------------------------------------------------- -static double rlCullDistanceNear = RL_CULL_DISTANCE_NEAR; -static double rlCullDistanceFar = RL_CULL_DISTANCE_FAR; - -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) -static rlglData RLGL = { 0 }; -#endif // GRAPHICS_API_OPENGL_33 || GRAPHICS_API_OPENGL_ES2 - -#if defined(GRAPHICS_API_OPENGL_ES2) && !defined(GRAPHICS_API_OPENGL_ES3) -// NOTE: VAO functionality is exposed through extensions (OES) -static PFNGLGENVERTEXARRAYSOESPROC glGenVertexArrays = NULL; -static PFNGLBINDVERTEXARRAYOESPROC glBindVertexArray = NULL; -static PFNGLDELETEVERTEXARRAYSOESPROC glDeleteVertexArrays = NULL; - -// NOTE: Instancing functionality could also be available through extension -static PFNGLDRAWARRAYSINSTANCEDEXTPROC glDrawArraysInstanced = NULL; -static PFNGLDRAWELEMENTSINSTANCEDEXTPROC glDrawElementsInstanced = NULL; -static PFNGLVERTEXATTRIBDIVISOREXTPROC glVertexAttribDivisor = NULL; -#endif - -//---------------------------------------------------------------------------------- -// Module specific Functions Declaration -//---------------------------------------------------------------------------------- -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) -static void rlLoadShaderDefault(void); // Load default shader -static void rlUnloadShaderDefault(void); // Unload default shader -#if defined(RLGL_SHOW_GL_DETAILS_INFO) -static const char *rlGetCompressedFormatName(int format); // Get compressed format official GL identifier name -#endif // RLGL_SHOW_GL_DETAILS_INFO -#endif // GRAPHICS_API_OPENGL_33 || GRAPHICS_API_OPENGL_ES2 - -static int rlGetPixelDataSize(int width, int height, int format); // Get pixel data size in bytes (image or texture) - -// Auxiliar matrix math functions -typedef struct rl_float16 { - float v[16]; -} rl_float16; -static rl_float16 rlMatrixToFloatV(Matrix mat); // Get float array of matrix data -#define rlMatrixToFloat(mat) (rlMatrixToFloatV(mat).v) // Get float vector for Matrix -static Matrix rlMatrixIdentity(void); // Get identity matrix -static Matrix rlMatrixMultiply(Matrix left, Matrix right); // Multiply two matrices -static Matrix rlMatrixTranspose(Matrix mat); // Transposes provided matrix -static Matrix rlMatrixInvert(Matrix mat); // Invert provided matrix - -//---------------------------------------------------------------------------------- -// Module Functions Definition - Matrix operations -//---------------------------------------------------------------------------------- - -#if defined(GRAPHICS_API_OPENGL_11) -// Fallback to OpenGL 1.1 function calls -//--------------------------------------- -void rlMatrixMode(int mode) -{ - switch (mode) - { - case RL_PROJECTION: glMatrixMode(GL_PROJECTION); break; - case RL_MODELVIEW: glMatrixMode(GL_MODELVIEW); break; - case RL_TEXTURE: glMatrixMode(GL_TEXTURE); break; - default: break; - } -} - -void rlFrustum(double left, double right, double bottom, double top, double znear, double zfar) -{ - glFrustum(left, right, bottom, top, znear, zfar); -} - -void rlOrtho(double left, double right, double bottom, double top, double znear, double zfar) -{ - glOrtho(left, right, bottom, top, znear, zfar); -} - -void rlPushMatrix(void) { glPushMatrix(); } -void rlPopMatrix(void) { glPopMatrix(); } -void rlLoadIdentity(void) { glLoadIdentity(); } -void rlTranslatef(float x, float y, float z) { glTranslatef(x, y, z); } -void rlRotatef(float angle, float x, float y, float z) { glRotatef(angle, x, y, z); } -void rlScalef(float x, float y, float z) { glScalef(x, y, z); } -void rlMultMatrixf(const float *matf) { glMultMatrixf(matf); } -#endif -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) -// Choose the current matrix to be transformed -void rlMatrixMode(int mode) -{ - if (mode == RL_PROJECTION) RLGL.State.currentMatrix = &RLGL.State.projection; - else if (mode == RL_MODELVIEW) RLGL.State.currentMatrix = &RLGL.State.modelview; - //else if (mode == RL_TEXTURE) // Not supported - - RLGL.State.currentMatrixMode = mode; -} - -// Push the current matrix into RLGL.State.stack -void rlPushMatrix(void) -{ - if (RLGL.State.stackCounter >= RL_MAX_MATRIX_STACK_SIZE) TRACELOG(RL_LOG_ERROR, "RLGL: Matrix stack overflow (RL_MAX_MATRIX_STACK_SIZE)"); - - if (RLGL.State.currentMatrixMode == RL_MODELVIEW) - { - RLGL.State.transformRequired = true; - RLGL.State.currentMatrix = &RLGL.State.transform; - } - - RLGL.State.stack[RLGL.State.stackCounter] = *RLGL.State.currentMatrix; - RLGL.State.stackCounter++; -} - -// Pop lattest inserted matrix from RLGL.State.stack -void rlPopMatrix(void) -{ - if (RLGL.State.stackCounter > 0) - { - Matrix mat = RLGL.State.stack[RLGL.State.stackCounter - 1]; - *RLGL.State.currentMatrix = mat; - RLGL.State.stackCounter--; - } - - if ((RLGL.State.stackCounter == 0) && (RLGL.State.currentMatrixMode == RL_MODELVIEW)) - { - RLGL.State.currentMatrix = &RLGL.State.modelview; - RLGL.State.transformRequired = false; - } -} - -// Reset current matrix to identity matrix -void rlLoadIdentity(void) -{ - *RLGL.State.currentMatrix = rlMatrixIdentity(); -} - -// Multiply the current matrix by a translation matrix -void rlTranslatef(float x, float y, float z) -{ - Matrix matTranslation = { - 1.0f, 0.0f, 0.0f, x, - 0.0f, 1.0f, 0.0f, y, - 0.0f, 0.0f, 1.0f, z, - 0.0f, 0.0f, 0.0f, 1.0f - }; - - // NOTE: We transpose matrix with multiplication order - *RLGL.State.currentMatrix = rlMatrixMultiply(matTranslation, *RLGL.State.currentMatrix); -} - -// Multiply the current matrix by a rotation matrix -// NOTE: The provided angle must be in degrees -void rlRotatef(float angle, float x, float y, float z) -{ - Matrix matRotation = rlMatrixIdentity(); - - // Axis vector (x, y, z) normalization - float lengthSquared = x*x + y*y + z*z; - if ((lengthSquared != 1.0f) && (lengthSquared != 0.0f)) - { - float inverseLength = 1.0f/sqrtf(lengthSquared); - x *= inverseLength; - y *= inverseLength; - z *= inverseLength; - } - - // Rotation matrix generation - float sinres = sinf(DEG2RAD*angle); - float cosres = cosf(DEG2RAD*angle); - float t = 1.0f - cosres; - - matRotation.m0 = x*x*t + cosres; - matRotation.m1 = y*x*t + z*sinres; - matRotation.m2 = z*x*t - y*sinres; - matRotation.m3 = 0.0f; - - matRotation.m4 = x*y*t - z*sinres; - matRotation.m5 = y*y*t + cosres; - matRotation.m6 = z*y*t + x*sinres; - matRotation.m7 = 0.0f; - - matRotation.m8 = x*z*t + y*sinres; - matRotation.m9 = y*z*t - x*sinres; - matRotation.m10 = z*z*t + cosres; - matRotation.m11 = 0.0f; - - matRotation.m12 = 0.0f; - matRotation.m13 = 0.0f; - matRotation.m14 = 0.0f; - matRotation.m15 = 1.0f; - - // NOTE: We transpose matrix with multiplication order - *RLGL.State.currentMatrix = rlMatrixMultiply(matRotation, *RLGL.State.currentMatrix); -} - -// Multiply the current matrix by a scaling matrix -void rlScalef(float x, float y, float z) -{ - Matrix matScale = { - x, 0.0f, 0.0f, 0.0f, - 0.0f, y, 0.0f, 0.0f, - 0.0f, 0.0f, z, 0.0f, - 0.0f, 0.0f, 0.0f, 1.0f - }; - - // NOTE: We transpose matrix with multiplication order - *RLGL.State.currentMatrix = rlMatrixMultiply(matScale, *RLGL.State.currentMatrix); -} - -// Multiply the current matrix by another matrix -void rlMultMatrixf(const float *matf) -{ - // Matrix creation from array - Matrix mat = { matf[0], matf[4], matf[8], matf[12], - matf[1], matf[5], matf[9], matf[13], - matf[2], matf[6], matf[10], matf[14], - matf[3], matf[7], matf[11], matf[15] }; - - *RLGL.State.currentMatrix = rlMatrixMultiply(mat, *RLGL.State.currentMatrix); -} - -// Multiply the current matrix by a perspective matrix generated by parameters -void rlFrustum(double left, double right, double bottom, double top, double znear, double zfar) -{ - Matrix matFrustum = { 0 }; - - float rl = (float)(right - left); - float tb = (float)(top - bottom); - float fn = (float)(zfar - znear); - - matFrustum.m0 = ((float) znear*2.0f)/rl; - matFrustum.m1 = 0.0f; - matFrustum.m2 = 0.0f; - matFrustum.m3 = 0.0f; - - matFrustum.m4 = 0.0f; - matFrustum.m5 = ((float) znear*2.0f)/tb; - matFrustum.m6 = 0.0f; - matFrustum.m7 = 0.0f; - - matFrustum.m8 = ((float)right + (float)left)/rl; - matFrustum.m9 = ((float)top + (float)bottom)/tb; - matFrustum.m10 = -((float)zfar + (float)znear)/fn; - matFrustum.m11 = -1.0f; - - matFrustum.m12 = 0.0f; - matFrustum.m13 = 0.0f; - matFrustum.m14 = -((float)zfar*(float)znear*2.0f)/fn; - matFrustum.m15 = 0.0f; - - *RLGL.State.currentMatrix = rlMatrixMultiply(*RLGL.State.currentMatrix, matFrustum); -} - -// Multiply the current matrix by an orthographic matrix generated by parameters -void rlOrtho(double left, double right, double bottom, double top, double znear, double zfar) -{ - // NOTE: If left-right and top-botton values are equal it could create a division by zero, - // response to it is platform/compiler dependant - Matrix matOrtho = { 0 }; - - float rl = (float)(right - left); - float tb = (float)(top - bottom); - float fn = (float)(zfar - znear); - - matOrtho.m0 = 2.0f/rl; - matOrtho.m1 = 0.0f; - matOrtho.m2 = 0.0f; - matOrtho.m3 = 0.0f; - matOrtho.m4 = 0.0f; - matOrtho.m5 = 2.0f/tb; - matOrtho.m6 = 0.0f; - matOrtho.m7 = 0.0f; - matOrtho.m8 = 0.0f; - matOrtho.m9 = 0.0f; - matOrtho.m10 = -2.0f/fn; - matOrtho.m11 = 0.0f; - matOrtho.m12 = -((float)left + (float)right)/rl; - matOrtho.m13 = -((float)top + (float)bottom)/tb; - matOrtho.m14 = -((float)zfar + (float)znear)/fn; - matOrtho.m15 = 1.0f; - - *RLGL.State.currentMatrix = rlMatrixMultiply(*RLGL.State.currentMatrix, matOrtho); -} -#endif - -// Set the viewport area (transformation from normalized device coordinates to window coordinates) -// NOTE: We store current viewport dimensions -void rlViewport(int x, int y, int width, int height) -{ - glViewport(x, y, width, height); -} - -// Set clip planes distances -void rlSetClipPlanes(double nearPlane, double farPlane) -{ - rlCullDistanceNear = nearPlane; - rlCullDistanceFar = farPlane; -} - -// Get cull plane distance near -double rlGetCullDistanceNear(void) -{ - return rlCullDistanceNear; -} - -// Get cull plane distance far -double rlGetCullDistanceFar(void) -{ - return rlCullDistanceFar; -} - -//---------------------------------------------------------------------------------- -// Module Functions Definition - Vertex level operations -//---------------------------------------------------------------------------------- -#if defined(GRAPHICS_API_OPENGL_11) -// Fallback to OpenGL 1.1 function calls -//--------------------------------------- -void rlBegin(int mode) -{ - switch (mode) - { - case RL_LINES: glBegin(GL_LINES); break; - case RL_TRIANGLES: glBegin(GL_TRIANGLES); break; - case RL_QUADS: glBegin(GL_QUADS); break; - default: break; - } -} - -void rlEnd(void) { glEnd(); } -void rlVertex2i(int x, int y) { glVertex2i(x, y); } -void rlVertex2f(float x, float y) { glVertex2f(x, y); } -void rlVertex3f(float x, float y, float z) { glVertex3f(x, y, z); } -void rlTexCoord2f(float x, float y) { glTexCoord2f(x, y); } -void rlNormal3f(float x, float y, float z) { glNormal3f(x, y, z); } -void rlColor4ub(unsigned char r, unsigned char g, unsigned char b, unsigned char a) { glColor4ub(r, g, b, a); } -void rlColor3f(float x, float y, float z) { glColor3f(x, y, z); } -void rlColor4f(float x, float y, float z, float w) { glColor4f(x, y, z, w); } -#endif -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) -// Initialize drawing mode (how to organize vertex) -void rlBegin(int mode) -{ - // Draw mode can be RL_LINES, RL_TRIANGLES and RL_QUADS - // NOTE: In all three cases, vertex are accumulated over default internal vertex buffer - if (RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].mode != mode) - { - if (RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexCount > 0) - { - // Make sure current RLGL.currentBatch->draws[i].vertexCount is aligned a multiple of 4, - // that way, following QUADS drawing will keep aligned with index processing - // It implies adding some extra alignment vertex at the end of the draw, - // those vertex are not processed but they are considered as an additional offset - // for the next set of vertex to be drawn - if (RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].mode == RL_LINES) RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexAlignment = ((RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexCount < 4)? RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexCount : RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexCount%4); - else if (RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].mode == RL_TRIANGLES) RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexAlignment = ((RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexCount < 4)? 1 : (4 - (RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexCount%4))); - else RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexAlignment = 0; - - if (!rlCheckRenderBatchLimit(RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexAlignment)) - { - RLGL.State.vertexCounter += RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexAlignment; - RLGL.currentBatch->drawCounter++; - } - } - - if (RLGL.currentBatch->drawCounter >= RL_DEFAULT_BATCH_DRAWCALLS) rlDrawRenderBatch(RLGL.currentBatch); - - RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].mode = mode; - RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexCount = 0; - RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].textureId = RLGL.State.defaultTextureId; - } -} - -// Finish vertex providing -void rlEnd(void) -{ - // NOTE: Depth increment is dependant on rlOrtho(): z-near and z-far values, - // as well as depth buffer bit-depth (16bit or 24bit or 32bit) - // Correct increment formula would be: depthInc = (zfar - znear)/pow(2, bits) - RLGL.currentBatch->currentDepth += (1.0f/20000.0f); -} - -// Define one vertex (position) -// NOTE: Vertex position data is the basic information required for drawing -void rlVertex3f(float x, float y, float z) -{ - float tx = x; - float ty = y; - float tz = z; - - // Transform provided vector if required - if (RLGL.State.transformRequired) - { - tx = RLGL.State.transform.m0*x + RLGL.State.transform.m4*y + RLGL.State.transform.m8*z + RLGL.State.transform.m12; - ty = RLGL.State.transform.m1*x + RLGL.State.transform.m5*y + RLGL.State.transform.m9*z + RLGL.State.transform.m13; - tz = RLGL.State.transform.m2*x + RLGL.State.transform.m6*y + RLGL.State.transform.m10*z + RLGL.State.transform.m14; - } - - // WARNING: We can't break primitives when launching a new batch - // RL_LINES comes in pairs, RL_TRIANGLES come in groups of 3 vertices and RL_QUADS come in groups of 4 vertices - // We must check current draw.mode when a new vertex is required and finish the batch only if the draw.mode draw.vertexCount is %2, %3 or %4 - if (RLGL.State.vertexCounter > (RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].elementCount*4 - 4)) - { - if ((RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].mode == RL_LINES) && - (RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexCount%2 == 0)) - { - // Reached the maximum number of vertices for RL_LINES drawing - // Launch a draw call but keep current state for next vertices comming - // NOTE: We add +1 vertex to the check for security - rlCheckRenderBatchLimit(2 + 1); - } - else if ((RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].mode == RL_TRIANGLES) && - (RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexCount%3 == 0)) - { - rlCheckRenderBatchLimit(3 + 1); - } - else if ((RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].mode == RL_QUADS) && - (RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexCount%4 == 0)) - { - rlCheckRenderBatchLimit(4 + 1); - } - } - - // Add vertices - RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].vertices[3*RLGL.State.vertexCounter] = tx; - RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].vertices[3*RLGL.State.vertexCounter + 1] = ty; - RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].vertices[3*RLGL.State.vertexCounter + 2] = tz; - - // Add current texcoord - RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].texcoords[2*RLGL.State.vertexCounter] = RLGL.State.texcoordx; - RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].texcoords[2*RLGL.State.vertexCounter + 1] = RLGL.State.texcoordy; - - // Add current normal - RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].normals[3*RLGL.State.vertexCounter] = RLGL.State.normalx; - RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].normals[3*RLGL.State.vertexCounter + 1] = RLGL.State.normaly; - RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].normals[3*RLGL.State.vertexCounter + 2] = RLGL.State.normalz; - - // Add current color - RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].colors[4*RLGL.State.vertexCounter] = RLGL.State.colorr; - RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].colors[4*RLGL.State.vertexCounter + 1] = RLGL.State.colorg; - RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].colors[4*RLGL.State.vertexCounter + 2] = RLGL.State.colorb; - RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].colors[4*RLGL.State.vertexCounter + 3] = RLGL.State.colora; - - RLGL.State.vertexCounter++; - RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexCount++; -} - -// Define one vertex (position) -void rlVertex2f(float x, float y) -{ - rlVertex3f(x, y, RLGL.currentBatch->currentDepth); -} - -// Define one vertex (position) -void rlVertex2i(int x, int y) -{ - rlVertex3f((float)x, (float)y, RLGL.currentBatch->currentDepth); -} - -// Define one vertex (texture coordinate) -// NOTE: Texture coordinates are limited to QUADS only -void rlTexCoord2f(float x, float y) -{ - RLGL.State.texcoordx = x; - RLGL.State.texcoordy = y; -} - -// Define one vertex (normal) -// NOTE: Normals limited to TRIANGLES only? -void rlNormal3f(float x, float y, float z) -{ - float normalx = x; - float normaly = y; - float normalz = z; - if (RLGL.State.transformRequired) - { - normalx = RLGL.State.transform.m0*x + RLGL.State.transform.m4*y + RLGL.State.transform.m8*z; - normaly = RLGL.State.transform.m1*x + RLGL.State.transform.m5*y + RLGL.State.transform.m9*z; - normalz = RLGL.State.transform.m2*x + RLGL.State.transform.m6*y + RLGL.State.transform.m10*z; - } - float length = sqrtf(normalx*normalx + normaly*normaly + normalz*normalz); - if (length != 0.0f) - { - float ilength = 1.0f/length; - normalx *= ilength; - normaly *= ilength; - normalz *= ilength; - } - RLGL.State.normalx = normalx; - RLGL.State.normaly = normaly; - RLGL.State.normalz = normalz; -} - -// Define one vertex (color) -void rlColor4ub(unsigned char x, unsigned char y, unsigned char z, unsigned char w) -{ - RLGL.State.colorr = x; - RLGL.State.colorg = y; - RLGL.State.colorb = z; - RLGL.State.colora = w; -} - -// Define one vertex (color) -void rlColor4f(float r, float g, float b, float a) -{ - rlColor4ub((unsigned char)(r*255), (unsigned char)(g*255), (unsigned char)(b*255), (unsigned char)(a*255)); -} - -// Define one vertex (color) -void rlColor3f(float x, float y, float z) -{ - rlColor4ub((unsigned char)(x*255), (unsigned char)(y*255), (unsigned char)(z*255), 255); -} - -#endif - -//-------------------------------------------------------------------------------------- -// Module Functions Definition - OpenGL style functions (common to 1.1, 3.3+, ES2) -//-------------------------------------------------------------------------------------- - -// Set current texture to use -void rlSetTexture(unsigned int id) -{ - if (id == 0) - { -#if defined(GRAPHICS_API_OPENGL_11) - rlDisableTexture(); -#else - // NOTE: If quads batch limit is reached, we force a draw call and next batch starts - if (RLGL.State.vertexCounter >= - RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].elementCount*4) - { - rlDrawRenderBatch(RLGL.currentBatch); - } -#endif - } - else - { -#if defined(GRAPHICS_API_OPENGL_11) - rlEnableTexture(id); -#else - if (RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].textureId != id) - { - if (RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexCount > 0) - { - // Make sure current RLGL.currentBatch->draws[i].vertexCount is aligned a multiple of 4, - // that way, following QUADS drawing will keep aligned with index processing - // It implies adding some extra alignment vertex at the end of the draw, - // those vertex are not processed but they are considered as an additional offset - // for the next set of vertex to be drawn - if (RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].mode == RL_LINES) RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexAlignment = ((RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexCount < 4)? RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexCount : RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexCount%4); - else if (RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].mode == RL_TRIANGLES) RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexAlignment = ((RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexCount < 4)? 1 : (4 - (RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexCount%4))); - else RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexAlignment = 0; - - if (!rlCheckRenderBatchLimit(RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexAlignment)) - { - RLGL.State.vertexCounter += RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexAlignment; - - RLGL.currentBatch->drawCounter++; - } - } - - if (RLGL.currentBatch->drawCounter >= RL_DEFAULT_BATCH_DRAWCALLS) rlDrawRenderBatch(RLGL.currentBatch); - - RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].textureId = id; - RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexCount = 0; - } -#endif - } -} - -// Select and active a texture slot -void rlActiveTextureSlot(int slot) -{ -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - glActiveTexture(GL_TEXTURE0 + slot); -#endif -} - -// Enable texture -void rlEnableTexture(unsigned int id) -{ -#if defined(GRAPHICS_API_OPENGL_11) - glEnable(GL_TEXTURE_2D); -#endif - glBindTexture(GL_TEXTURE_2D, id); -} - -// Disable texture -void rlDisableTexture(void) -{ -#if defined(GRAPHICS_API_OPENGL_11) - glDisable(GL_TEXTURE_2D); -#endif - glBindTexture(GL_TEXTURE_2D, 0); -} - -// Enable texture cubemap -void rlEnableTextureCubemap(unsigned int id) -{ -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - glBindTexture(GL_TEXTURE_CUBE_MAP, id); -#endif -} - -// Disable texture cubemap -void rlDisableTextureCubemap(void) -{ -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - glBindTexture(GL_TEXTURE_CUBE_MAP, 0); -#endif -} - -// Set texture parameters (wrap mode/filter mode) -void rlTextureParameters(unsigned int id, int param, int value) -{ - glBindTexture(GL_TEXTURE_2D, id); - -#if !defined(GRAPHICS_API_OPENGL_11) - // Reset anisotropy filter, in case it was set - glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, 1.0f); -#endif - - switch (param) - { - case RL_TEXTURE_WRAP_S: - case RL_TEXTURE_WRAP_T: - { - if (value == RL_TEXTURE_WRAP_MIRROR_CLAMP) - { -#if !defined(GRAPHICS_API_OPENGL_11) - if (RLGL.ExtSupported.texMirrorClamp) glTexParameteri(GL_TEXTURE_2D, param, value); - else TRACELOG(RL_LOG_WARNING, "GL: Clamp mirror wrap mode not supported (GL_MIRROR_CLAMP_EXT)"); -#endif - } - else glTexParameteri(GL_TEXTURE_2D, param, value); - - } break; - case RL_TEXTURE_MAG_FILTER: - case RL_TEXTURE_MIN_FILTER: glTexParameteri(GL_TEXTURE_2D, param, value); break; - case RL_TEXTURE_FILTER_ANISOTROPIC: - { -#if !defined(GRAPHICS_API_OPENGL_11) - if (value <= RLGL.ExtSupported.maxAnisotropyLevel) glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, (float)value); - else if (RLGL.ExtSupported.maxAnisotropyLevel > 0.0f) - { - TRACELOG(RL_LOG_WARNING, "GL: Maximum anisotropic filter level supported is %iX", id, (int)RLGL.ExtSupported.maxAnisotropyLevel); - glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, (float)value); - } - else TRACELOG(RL_LOG_WARNING, "GL: Anisotropic filtering not supported"); -#endif - } break; -#if defined(GRAPHICS_API_OPENGL_33) - case RL_TEXTURE_MIPMAP_BIAS_RATIO: glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_LOD_BIAS, value/100.0f); -#endif - default: break; - } - - glBindTexture(GL_TEXTURE_2D, 0); -} - -// Set cubemap parameters (wrap mode/filter mode) -void rlCubemapParameters(unsigned int id, int param, int value) -{ -#if !defined(GRAPHICS_API_OPENGL_11) - glBindTexture(GL_TEXTURE_CUBE_MAP, id); - - // Reset anisotropy filter, in case it was set - glTexParameterf(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAX_ANISOTROPY_EXT, 1.0f); - - switch (param) - { - case RL_TEXTURE_WRAP_S: - case RL_TEXTURE_WRAP_T: - { - if (value == RL_TEXTURE_WRAP_MIRROR_CLAMP) - { - if (RLGL.ExtSupported.texMirrorClamp) glTexParameteri(GL_TEXTURE_CUBE_MAP, param, value); - else TRACELOG(RL_LOG_WARNING, "GL: Clamp mirror wrap mode not supported (GL_MIRROR_CLAMP_EXT)"); - } - else glTexParameteri(GL_TEXTURE_CUBE_MAP, param, value); - - } break; - case RL_TEXTURE_MAG_FILTER: - case RL_TEXTURE_MIN_FILTER: glTexParameteri(GL_TEXTURE_CUBE_MAP, param, value); break; - case RL_TEXTURE_FILTER_ANISOTROPIC: - { - if (value <= RLGL.ExtSupported.maxAnisotropyLevel) glTexParameterf(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAX_ANISOTROPY_EXT, (float)value); - else if (RLGL.ExtSupported.maxAnisotropyLevel > 0.0f) - { - TRACELOG(RL_LOG_WARNING, "GL: Maximum anisotropic filter level supported is %iX", id, (int)RLGL.ExtSupported.maxAnisotropyLevel); - glTexParameterf(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAX_ANISOTROPY_EXT, (float)value); - } - else TRACELOG(RL_LOG_WARNING, "GL: Anisotropic filtering not supported"); - } break; -#if defined(GRAPHICS_API_OPENGL_33) - case RL_TEXTURE_MIPMAP_BIAS_RATIO: glTexParameterf(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_LOD_BIAS, value/100.0f); -#endif - default: break; - } - - glBindTexture(GL_TEXTURE_CUBE_MAP, 0); -#endif -} - -// Enable shader program -void rlEnableShader(unsigned int id) -{ -#if (defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)) - glUseProgram(id); -#endif -} - -// Disable shader program -void rlDisableShader(void) -{ -#if (defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)) - glUseProgram(0); -#endif -} - -// Enable rendering to texture (fbo) -void rlEnableFramebuffer(unsigned int id) -{ -#if (defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)) && defined(RLGL_RENDER_TEXTURES_HINT) - glBindFramebuffer(GL_FRAMEBUFFER, id); -#endif -} - -// return the active render texture (fbo) -unsigned int rlGetActiveFramebuffer(void) -{ - GLint fboId = 0; -#if (defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES3)) && defined(RLGL_RENDER_TEXTURES_HINT) - glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &fboId); -#endif - return fboId; -} - -// Disable rendering to texture -void rlDisableFramebuffer(void) -{ -#if (defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)) && defined(RLGL_RENDER_TEXTURES_HINT) - glBindFramebuffer(GL_FRAMEBUFFER, 0); -#endif -} - -// Blit active framebuffer to main framebuffer -void rlBlitFramebuffer(int srcX, int srcY, int srcWidth, int srcHeight, int dstX, int dstY, int dstWidth, int dstHeight, int bufferMask) -{ -#if (defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES3)) && defined(RLGL_RENDER_TEXTURES_HINT) - glBlitFramebuffer(srcX, srcY, srcWidth, srcHeight, dstX, dstY, dstWidth, dstHeight, bufferMask, GL_NEAREST); -#endif -} - -// Bind framebuffer object (fbo) -void rlBindFramebuffer(unsigned int target, unsigned int framebuffer) -{ -#if (defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)) && defined(RLGL_RENDER_TEXTURES_HINT) - glBindFramebuffer(target, framebuffer); -#endif -} - -// Activate multiple draw color buffers -// NOTE: One color buffer is always active by default -void rlActiveDrawBuffers(int count) -{ -#if ((defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES3)) && defined(RLGL_RENDER_TEXTURES_HINT)) - // NOTE: Maximum number of draw buffers supported is implementation dependant, - // it can be queried with glGet*() but it must be at least 8 - //GLint maxDrawBuffers = 0; - //glGetIntegerv(GL_MAX_DRAW_BUFFERS, &maxDrawBuffers); - - if (count > 0) - { - if (count > 8) TRACELOG(LOG_WARNING, "GL: Max color buffers limited to 8"); - else - { - unsigned int buffers[8] = { -#if defined(GRAPHICS_API_OPENGL_ES3) - GL_COLOR_ATTACHMENT0_EXT, - GL_COLOR_ATTACHMENT1_EXT, - GL_COLOR_ATTACHMENT2_EXT, - GL_COLOR_ATTACHMENT3_EXT, - GL_COLOR_ATTACHMENT4_EXT, - GL_COLOR_ATTACHMENT5_EXT, - GL_COLOR_ATTACHMENT6_EXT, - GL_COLOR_ATTACHMENT7_EXT, -#else - GL_COLOR_ATTACHMENT0, - GL_COLOR_ATTACHMENT1, - GL_COLOR_ATTACHMENT2, - GL_COLOR_ATTACHMENT3, - GL_COLOR_ATTACHMENT4, - GL_COLOR_ATTACHMENT5, - GL_COLOR_ATTACHMENT6, - GL_COLOR_ATTACHMENT7, -#endif - }; - -#if defined(GRAPHICS_API_OPENGL_ES3) - glDrawBuffersEXT(count, buffers); -#else - glDrawBuffers(count, buffers); -#endif - } - } - else TRACELOG(LOG_WARNING, "GL: One color buffer active by default"); -#endif -} - -//---------------------------------------------------------------------------------- -// General render state configuration -//---------------------------------------------------------------------------------- - -// Enable color blending -void rlEnableColorBlend(void) { glEnable(GL_BLEND); } - -// Disable color blending -void rlDisableColorBlend(void) { glDisable(GL_BLEND); } - -// Enable depth test -void rlEnableDepthTest(void) { glEnable(GL_DEPTH_TEST); } - -// Disable depth test -void rlDisableDepthTest(void) { glDisable(GL_DEPTH_TEST); } - -// Enable depth write -void rlEnableDepthMask(void) { glDepthMask(GL_TRUE); } - -// Disable depth write -void rlDisableDepthMask(void) { glDepthMask(GL_FALSE); } - -// Enable backface culling -void rlEnableBackfaceCulling(void) { glEnable(GL_CULL_FACE); } - -// Disable backface culling -void rlDisableBackfaceCulling(void) { glDisable(GL_CULL_FACE); } - -// Set color mask active for screen read/draw -void rlColorMask(bool r, bool g, bool b, bool a) { glColorMask(r, g, b, a); } - -// Set face culling mode -void rlSetCullFace(int mode) -{ - switch (mode) - { - case RL_CULL_FACE_BACK: glCullFace(GL_BACK); break; - case RL_CULL_FACE_FRONT: glCullFace(GL_FRONT); break; - default: break; - } -} - -// Enable scissor test -void rlEnableScissorTest(void) { glEnable(GL_SCISSOR_TEST); } - -// Disable scissor test -void rlDisableScissorTest(void) { glDisable(GL_SCISSOR_TEST); } - -// Scissor test -void rlScissor(int x, int y, int width, int height) { glScissor(x, y, width, height); } - -// Enable wire mode -void rlEnableWireMode(void) -{ -#if defined(GRAPHICS_API_OPENGL_11) || defined(GRAPHICS_API_OPENGL_33) - // NOTE: glPolygonMode() not available on OpenGL ES - glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); -#endif -} - -// Enable point mode -void rlEnablePointMode(void) -{ -#if defined(GRAPHICS_API_OPENGL_11) || defined(GRAPHICS_API_OPENGL_33) - // NOTE: glPolygonMode() not available on OpenGL ES - glPolygonMode(GL_FRONT_AND_BACK, GL_POINT); - glEnable(GL_PROGRAM_POINT_SIZE); -#endif -} - -// Disable wire mode -void rlDisableWireMode(void) -{ -#if defined(GRAPHICS_API_OPENGL_11) || defined(GRAPHICS_API_OPENGL_33) - // NOTE: glPolygonMode() not available on OpenGL ES - glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); -#endif -} - -// Set the line drawing width -void rlSetLineWidth(float width) { glLineWidth(width); } - -// Get the line drawing width -float rlGetLineWidth(void) -{ - float width = 0; - glGetFloatv(GL_LINE_WIDTH, &width); - return width; -} - -// Enable line aliasing -void rlEnableSmoothLines(void) -{ -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_11) - glEnable(GL_LINE_SMOOTH); -#endif -} - -// Disable line aliasing -void rlDisableSmoothLines(void) -{ -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_11) - glDisable(GL_LINE_SMOOTH); -#endif -} - -// Enable stereo rendering -void rlEnableStereoRender(void) -{ -#if (defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)) - RLGL.State.stereoRender = true; -#endif -} - -// Disable stereo rendering -void rlDisableStereoRender(void) -{ -#if (defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)) - RLGL.State.stereoRender = false; -#endif -} - -// Check if stereo render is enabled -bool rlIsStereoRenderEnabled(void) -{ -#if (defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)) - return RLGL.State.stereoRender; -#else - return false; -#endif -} - -// Clear color buffer with color -void rlClearColor(unsigned char r, unsigned char g, unsigned char b, unsigned char a) -{ - // Color values clamp to 0.0f(0) and 1.0f(255) - float cr = (float)r/255; - float cg = (float)g/255; - float cb = (float)b/255; - float ca = (float)a/255; - - glClearColor(cr, cg, cb, ca); -} - -// Clear used screen buffers (color and depth) -void rlClearScreenBuffers(void) -{ - glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // Clear used buffers: Color and Depth (Depth is used for 3D) - //glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); // Stencil buffer not used... -} - -// Check and log OpenGL error codes -void rlCheckErrors(void) -{ -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - int check = 1; - while (check) - { - const GLenum err = glGetError(); - switch (err) - { - case GL_NO_ERROR: check = 0; break; - case 0x0500: TRACELOG(RL_LOG_WARNING, "GL: Error detected: GL_INVALID_ENUM"); break; - case 0x0501: TRACELOG(RL_LOG_WARNING, "GL: Error detected: GL_INVALID_VALUE"); break; - case 0x0502: TRACELOG(RL_LOG_WARNING, "GL: Error detected: GL_INVALID_OPERATION"); break; - case 0x0503: TRACELOG(RL_LOG_WARNING, "GL: Error detected: GL_STACK_OVERFLOW"); break; - case 0x0504: TRACELOG(RL_LOG_WARNING, "GL: Error detected: GL_STACK_UNDERFLOW"); break; - case 0x0505: TRACELOG(RL_LOG_WARNING, "GL: Error detected: GL_OUT_OF_MEMORY"); break; - case 0x0506: TRACELOG(RL_LOG_WARNING, "GL: Error detected: GL_INVALID_FRAMEBUFFER_OPERATION"); break; - default: TRACELOG(RL_LOG_WARNING, "GL: Error detected: Unknown error code: %x", err); break; - } - } -#endif -} - -// Set blend mode -void rlSetBlendMode(int mode) -{ -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - if ((RLGL.State.currentBlendMode != mode) || ((mode == RL_BLEND_CUSTOM || mode == RL_BLEND_CUSTOM_SEPARATE) && RLGL.State.glCustomBlendModeModified)) - { - rlDrawRenderBatch(RLGL.currentBatch); - - switch (mode) - { - case RL_BLEND_ALPHA: glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glBlendEquation(GL_FUNC_ADD); break; - case RL_BLEND_ADDITIVE: glBlendFunc(GL_SRC_ALPHA, GL_ONE); glBlendEquation(GL_FUNC_ADD); break; - case RL_BLEND_MULTIPLIED: glBlendFunc(GL_DST_COLOR, GL_ONE_MINUS_SRC_ALPHA); glBlendEquation(GL_FUNC_ADD); break; - case RL_BLEND_ADD_COLORS: glBlendFunc(GL_ONE, GL_ONE); glBlendEquation(GL_FUNC_ADD); break; - case RL_BLEND_SUBTRACT_COLORS: glBlendFunc(GL_ONE, GL_ONE); glBlendEquation(GL_FUNC_SUBTRACT); break; - case RL_BLEND_ALPHA_PREMULTIPLY: glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); glBlendEquation(GL_FUNC_ADD); break; - case RL_BLEND_CUSTOM: - { - // NOTE: Using GL blend src/dst factors and GL equation configured with rlSetBlendFactors() - glBlendFunc(RLGL.State.glBlendSrcFactor, RLGL.State.glBlendDstFactor); glBlendEquation(RLGL.State.glBlendEquation); - - } break; - case RL_BLEND_CUSTOM_SEPARATE: - { - // NOTE: Using GL blend src/dst factors and GL equation configured with rlSetBlendFactorsSeparate() - glBlendFuncSeparate(RLGL.State.glBlendSrcFactorRGB, RLGL.State.glBlendDestFactorRGB, RLGL.State.glBlendSrcFactorAlpha, RLGL.State.glBlendDestFactorAlpha); - glBlendEquationSeparate(RLGL.State.glBlendEquationRGB, RLGL.State.glBlendEquationAlpha); - - } break; - default: break; - } - - RLGL.State.currentBlendMode = mode; - RLGL.State.glCustomBlendModeModified = false; - } -#endif -} - -// Set blending mode factor and equation -void rlSetBlendFactors(int glSrcFactor, int glDstFactor, int glEquation) -{ -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - if ((RLGL.State.glBlendSrcFactor != glSrcFactor) || - (RLGL.State.glBlendDstFactor != glDstFactor) || - (RLGL.State.glBlendEquation != glEquation)) - { - RLGL.State.glBlendSrcFactor = glSrcFactor; - RLGL.State.glBlendDstFactor = glDstFactor; - RLGL.State.glBlendEquation = glEquation; - - RLGL.State.glCustomBlendModeModified = true; - } -#endif -} - -// Set blending mode factor and equation separately for RGB and alpha -void rlSetBlendFactorsSeparate(int glSrcRGB, int glDstRGB, int glSrcAlpha, int glDstAlpha, int glEqRGB, int glEqAlpha) -{ -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - if ((RLGL.State.glBlendSrcFactorRGB != glSrcRGB) || - (RLGL.State.glBlendDestFactorRGB != glDstRGB) || - (RLGL.State.glBlendSrcFactorAlpha != glSrcAlpha) || - (RLGL.State.glBlendDestFactorAlpha != glDstAlpha) || - (RLGL.State.glBlendEquationRGB != glEqRGB) || - (RLGL.State.glBlendEquationAlpha != glEqAlpha)) - { - RLGL.State.glBlendSrcFactorRGB = glSrcRGB; - RLGL.State.glBlendDestFactorRGB = glDstRGB; - RLGL.State.glBlendSrcFactorAlpha = glSrcAlpha; - RLGL.State.glBlendDestFactorAlpha = glDstAlpha; - RLGL.State.glBlendEquationRGB = glEqRGB; - RLGL.State.glBlendEquationAlpha = glEqAlpha; - - RLGL.State.glCustomBlendModeModified = true; - } -#endif -} - -//---------------------------------------------------------------------------------- -// Module Functions Definition - OpenGL Debug -//---------------------------------------------------------------------------------- -#if defined(RLGL_ENABLE_OPENGL_DEBUG_CONTEXT) && defined(GRAPHICS_API_OPENGL_43) -static void GLAPIENTRY rlDebugMessageCallback(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar *message, const void *userParam) -{ - // Ignore non-significant error/warning codes (NVidia drivers) - // NOTE: Here there are the details with a sample output: - // - #131169 - Framebuffer detailed info: The driver allocated storage for renderbuffer 2. (severity: low) - // - #131185 - Buffer detailed info: Buffer object 1 (bound to GL_ELEMENT_ARRAY_BUFFER_ARB, usage hint is GL_ENUM_88e4) - // will use VIDEO memory as the source for buffer object operations. (severity: low) - // - #131218 - Program/shader state performance warning: Vertex shader in program 7 is being recompiled based on GL state. (severity: medium) - // - #131204 - Texture state usage warning: The texture object (0) bound to texture image unit 0 does not have - // a defined base level and cannot be used for texture mapping. (severity: low) - if ((id == 131169) || (id == 131185) || (id == 131218) || (id == 131204)) return; - - const char *msgSource = NULL; - switch (source) - { - case GL_DEBUG_SOURCE_API: msgSource = "API"; break; - case GL_DEBUG_SOURCE_WINDOW_SYSTEM: msgSource = "WINDOW_SYSTEM"; break; - case GL_DEBUG_SOURCE_SHADER_COMPILER: msgSource = "SHADER_COMPILER"; break; - case GL_DEBUG_SOURCE_THIRD_PARTY: msgSource = "THIRD_PARTY"; break; - case GL_DEBUG_SOURCE_APPLICATION: msgSource = "APPLICATION"; break; - case GL_DEBUG_SOURCE_OTHER: msgSource = "OTHER"; break; - default: break; - } - - const char *msgType = NULL; - switch (type) - { - case GL_DEBUG_TYPE_ERROR: msgType = "ERROR"; break; - case GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR: msgType = "DEPRECATED_BEHAVIOR"; break; - case GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR: msgType = "UNDEFINED_BEHAVIOR"; break; - case GL_DEBUG_TYPE_PORTABILITY: msgType = "PORTABILITY"; break; - case GL_DEBUG_TYPE_PERFORMANCE: msgType = "PERFORMANCE"; break; - case GL_DEBUG_TYPE_MARKER: msgType = "MARKER"; break; - case GL_DEBUG_TYPE_PUSH_GROUP: msgType = "PUSH_GROUP"; break; - case GL_DEBUG_TYPE_POP_GROUP: msgType = "POP_GROUP"; break; - case GL_DEBUG_TYPE_OTHER: msgType = "OTHER"; break; - default: break; - } - - const char *msgSeverity = "DEFAULT"; - switch (severity) - { - case GL_DEBUG_SEVERITY_LOW: msgSeverity = "LOW"; break; - case GL_DEBUG_SEVERITY_MEDIUM: msgSeverity = "MEDIUM"; break; - case GL_DEBUG_SEVERITY_HIGH: msgSeverity = "HIGH"; break; - case GL_DEBUG_SEVERITY_NOTIFICATION: msgSeverity = "NOTIFICATION"; break; - default: break; - } - - TRACELOG(LOG_WARNING, "GL: OpenGL debug message: %s", message); - TRACELOG(LOG_WARNING, " > Type: %s", msgType); - TRACELOG(LOG_WARNING, " > Source = %s", msgSource); - TRACELOG(LOG_WARNING, " > Severity = %s", msgSeverity); -} -#endif - -//---------------------------------------------------------------------------------- -// Module Functions Definition - rlgl functionality -//---------------------------------------------------------------------------------- - -// Initialize rlgl: OpenGL extensions, default buffers/shaders/textures, OpenGL states -void rlglInit(int width, int height) -{ - // Enable OpenGL debug context if required -#if defined(RLGL_ENABLE_OPENGL_DEBUG_CONTEXT) && defined(GRAPHICS_API_OPENGL_43) - if ((glDebugMessageCallback != NULL) && (glDebugMessageControl != NULL)) - { - glDebugMessageCallback(rlDebugMessageCallback, 0); - // glDebugMessageControl(GL_DEBUG_SOURCE_API, GL_DEBUG_TYPE_ERROR, GL_DEBUG_SEVERITY_HIGH, 0, 0, GL_TRUE); - - // Debug context options: - // - GL_DEBUG_OUTPUT - Faster version but not useful for breakpoints - // - GL_DEBUG_OUTPUT_SYNCHRONUS - Callback is in sync with errors, so a breakpoint can be placed on the callback in order to get a stacktrace for the GL error - glEnable(GL_DEBUG_OUTPUT); - glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS); - } -#endif - -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - // Init default white texture - unsigned char pixels[4] = { 255, 255, 255, 255 }; // 1 pixel RGBA (4 bytes) - RLGL.State.defaultTextureId = rlLoadTexture(pixels, 1, 1, RL_PIXELFORMAT_UNCOMPRESSED_R8G8B8A8, 1); - - if (RLGL.State.defaultTextureId != 0) TRACELOG(RL_LOG_INFO, "TEXTURE: [ID %i] Default texture loaded successfully", RLGL.State.defaultTextureId); - else TRACELOG(RL_LOG_WARNING, "TEXTURE: Failed to load default texture"); - - // Init default Shader (customized for GL 3.3 and ES2) - // Loaded: RLGL.State.defaultShaderId + RLGL.State.defaultShaderLocs - rlLoadShaderDefault(); - RLGL.State.currentShaderId = RLGL.State.defaultShaderId; - RLGL.State.currentShaderLocs = RLGL.State.defaultShaderLocs; - - // Init default vertex arrays buffers - // Simulate that the default shader has the location RL_SHADER_LOC_VERTEX_NORMAL to bind the normal buffer for the default render batch - RLGL.State.currentShaderLocs[RL_SHADER_LOC_VERTEX_NORMAL] = RL_DEFAULT_SHADER_ATTRIB_LOCATION_NORMAL; - RLGL.defaultBatch = rlLoadRenderBatch(RL_DEFAULT_BATCH_BUFFERS, RL_DEFAULT_BATCH_BUFFER_ELEMENTS); - RLGL.State.currentShaderLocs[RL_SHADER_LOC_VERTEX_NORMAL] = -1; - RLGL.currentBatch = &RLGL.defaultBatch; - - // Init stack matrices (emulating OpenGL 1.1) - for (int i = 0; i < RL_MAX_MATRIX_STACK_SIZE; i++) RLGL.State.stack[i] = rlMatrixIdentity(); - - // Init internal matrices - RLGL.State.transform = rlMatrixIdentity(); - RLGL.State.projection = rlMatrixIdentity(); - RLGL.State.modelview = rlMatrixIdentity(); - RLGL.State.currentMatrix = &RLGL.State.modelview; -#endif // GRAPHICS_API_OPENGL_33 || GRAPHICS_API_OPENGL_ES2 - - // Initialize OpenGL default states - //---------------------------------------------------------- - // Init state: Depth test - glDepthFunc(GL_LEQUAL); // Type of depth testing to apply - glDisable(GL_DEPTH_TEST); // Disable depth testing for 2D (only used for 3D) - - // Init state: Blending mode - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); // Color blending function (how colors are mixed) - glEnable(GL_BLEND); // Enable color blending (required to work with transparencies) - - // Init state: Culling - // NOTE: All shapes/models triangles are drawn CCW - glCullFace(GL_BACK); // Cull the back face (default) - glFrontFace(GL_CCW); // Front face are defined counter clockwise (default) - glEnable(GL_CULL_FACE); // Enable backface culling - - // Init state: Cubemap seamless -#if defined(GRAPHICS_API_OPENGL_33) - glEnable(GL_TEXTURE_CUBE_MAP_SEAMLESS); // Seamless cubemaps (not supported on OpenGL ES 2.0) -#endif - -#if defined(GRAPHICS_API_OPENGL_11) - // Init state: Color hints (deprecated in OpenGL 3.0+) - glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST); // Improve quality of color and texture coordinate interpolation - glShadeModel(GL_SMOOTH); // Smooth shading between vertex (vertex colors interpolation) -#endif - -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - // Store screen size into global variables - RLGL.State.framebufferWidth = width; - RLGL.State.framebufferHeight = height; - - TRACELOG(RL_LOG_INFO, "RLGL: Default OpenGL state initialized successfully"); - //---------------------------------------------------------- -#endif - - // Init state: Color/Depth buffers clear - glClearColor(0.0f, 0.0f, 0.0f, 1.0f); // Set clear color (black) - glClearDepth(1.0f); // Set clear depth value (default) - glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // Clear color and depth buffers (depth buffer required for 3D) -} - -// Vertex Buffer Object deinitialization (memory free) -void rlglClose(void) -{ -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - rlUnloadRenderBatch(RLGL.defaultBatch); - - rlUnloadShaderDefault(); // Unload default shader - - glDeleteTextures(1, &RLGL.State.defaultTextureId); // Unload default texture - TRACELOG(RL_LOG_INFO, "TEXTURE: [ID %i] Default texture unloaded successfully", RLGL.State.defaultTextureId); -#endif -} - -// Load OpenGL extensions -// NOTE: External loader function must be provided -void rlLoadExtensions(void *loader) -{ -#if defined(GRAPHICS_API_OPENGL_33) // Also defined for GRAPHICS_API_OPENGL_21 - // NOTE: glad is generated and contains only required OpenGL 3.3 Core extensions (and lower versions) - if (gladLoadGL((GLADloadfunc)loader) == 0) TRACELOG(RL_LOG_WARNING, "GLAD: Cannot load OpenGL extensions"); - else TRACELOG(RL_LOG_INFO, "GLAD: OpenGL extensions loaded successfully"); - - // Get number of supported extensions - GLint numExt = 0; - glGetIntegerv(GL_NUM_EXTENSIONS, &numExt); - TRACELOG(RL_LOG_INFO, "GL: Supported extensions count: %i", numExt); - -#if defined(RLGL_SHOW_GL_DETAILS_INFO) - // Get supported extensions list - // WARNING: glGetStringi() not available on OpenGL 2.1 - TRACELOG(RL_LOG_INFO, "GL: OpenGL extensions:"); - for (int i = 0; i < numExt; i++) TRACELOG(RL_LOG_INFO, " %s", glGetStringi(GL_EXTENSIONS, i)); -#endif - -#if defined(GRAPHICS_API_OPENGL_21) - // Register supported extensions flags - // Optional OpenGL 2.1 extensions - RLGL.ExtSupported.vao = GLAD_GL_ARB_vertex_array_object; - RLGL.ExtSupported.instancing = (GLAD_GL_EXT_draw_instanced && GLAD_GL_ARB_instanced_arrays); - RLGL.ExtSupported.texNPOT = GLAD_GL_ARB_texture_non_power_of_two; - RLGL.ExtSupported.texFloat32 = GLAD_GL_ARB_texture_float; - RLGL.ExtSupported.texFloat16 = GLAD_GL_ARB_texture_float; - RLGL.ExtSupported.texDepth = GLAD_GL_ARB_depth_texture; - RLGL.ExtSupported.maxDepthBits = 32; - RLGL.ExtSupported.texAnisoFilter = GLAD_GL_EXT_texture_filter_anisotropic; - RLGL.ExtSupported.texMirrorClamp = GLAD_GL_EXT_texture_mirror_clamp; -#else - // Register supported extensions flags - // OpenGL 3.3 extensions supported by default (core) - RLGL.ExtSupported.vao = true; - RLGL.ExtSupported.instancing = true; - RLGL.ExtSupported.texNPOT = true; - RLGL.ExtSupported.texFloat32 = true; - RLGL.ExtSupported.texFloat16 = true; - RLGL.ExtSupported.texDepth = true; - RLGL.ExtSupported.maxDepthBits = 32; - RLGL.ExtSupported.texAnisoFilter = true; - RLGL.ExtSupported.texMirrorClamp = true; -#endif - - // Optional OpenGL 3.3 extensions - RLGL.ExtSupported.texCompASTC = GLAD_GL_KHR_texture_compression_astc_hdr && GLAD_GL_KHR_texture_compression_astc_ldr; - RLGL.ExtSupported.texCompDXT = GLAD_GL_EXT_texture_compression_s3tc; // Texture compression: DXT - RLGL.ExtSupported.texCompETC2 = GLAD_GL_ARB_ES3_compatibility; // Texture compression: ETC2/EAC - #if defined(GRAPHICS_API_OPENGL_43) - RLGL.ExtSupported.computeShader = GLAD_GL_ARB_compute_shader; - RLGL.ExtSupported.ssbo = GLAD_GL_ARB_shader_storage_buffer_object; - #endif - -#endif // GRAPHICS_API_OPENGL_33 - -#if defined(GRAPHICS_API_OPENGL_ES3) - // Register supported extensions flags - // OpenGL ES 3.0 extensions supported by default (or it should be) - RLGL.ExtSupported.vao = true; - RLGL.ExtSupported.instancing = true; - RLGL.ExtSupported.texNPOT = true; - RLGL.ExtSupported.texFloat32 = true; - RLGL.ExtSupported.texFloat16 = true; - RLGL.ExtSupported.texDepth = true; - RLGL.ExtSupported.texDepthWebGL = true; - RLGL.ExtSupported.maxDepthBits = 24; - RLGL.ExtSupported.texAnisoFilter = true; - RLGL.ExtSupported.texMirrorClamp = true; - // TODO: Check for additional OpenGL ES 3.0 supported extensions: - //RLGL.ExtSupported.texCompDXT = true; - //RLGL.ExtSupported.texCompETC1 = true; - //RLGL.ExtSupported.texCompETC2 = true; - //RLGL.ExtSupported.texCompPVRT = true; - //RLGL.ExtSupported.texCompASTC = true; - //RLGL.ExtSupported.maxAnisotropyLevel = true; - //RLGL.ExtSupported.computeShader = true; - //RLGL.ExtSupported.ssbo = true; - -#elif defined(GRAPHICS_API_OPENGL_ES2) - - #if defined(PLATFORM_DESKTOP_GLFW) || defined(PLATFORM_DESKTOP_SDL) - // TODO: Support GLAD loader for OpenGL ES 3.0 - if (gladLoadGLES2((GLADloadfunc)loader) == 0) TRACELOG(RL_LOG_WARNING, "GLAD: Cannot load OpenGL ES2.0 functions"); - else TRACELOG(RL_LOG_INFO, "GLAD: OpenGL ES 2.0 loaded successfully"); - #endif - - // Get supported extensions list - GLint numExt = 0; - const char **extList = RL_MALLOC(512*sizeof(const char *)); // Allocate 512 strings pointers (2 KB) - const char *extensions = (const char *)glGetString(GL_EXTENSIONS); // One big const string - - // NOTE: We have to duplicate string because glGetString() returns a const string - int size = strlen(extensions) + 1; // Get extensions string size in bytes - char *extensionsDup = (char *)RL_CALLOC(size, sizeof(char)); - strcpy(extensionsDup, extensions); - extList[numExt] = extensionsDup; - - for (int i = 0; i < size; i++) - { - if (extensionsDup[i] == ' ') - { - extensionsDup[i] = '\0'; - numExt++; - extList[numExt] = &extensionsDup[i + 1]; - } - } - - TRACELOG(RL_LOG_INFO, "GL: Supported extensions count: %i", numExt); - -#if defined(RLGL_SHOW_GL_DETAILS_INFO) - TRACELOG(RL_LOG_INFO, "GL: OpenGL extensions:"); - for (int i = 0; i < numExt; i++) TRACELOG(RL_LOG_INFO, " %s", extList[i]); -#endif - - // Check required extensions - for (int i = 0; i < numExt; i++) - { - // Check VAO support - // NOTE: Only check on OpenGL ES, OpenGL 3.3 has VAO support as core feature - if (strcmp(extList[i], (const char *)"GL_OES_vertex_array_object") == 0) - { - // The extension is supported by our hardware and driver, try to get related functions pointers - // NOTE: emscripten does not support VAOs natively, it uses emulation and it reduces overall performance... - glGenVertexArrays = (PFNGLGENVERTEXARRAYSOESPROC)((rlglLoadProc)loader)("glGenVertexArraysOES"); - glBindVertexArray = (PFNGLBINDVERTEXARRAYOESPROC)((rlglLoadProc)loader)("glBindVertexArrayOES"); - glDeleteVertexArrays = (PFNGLDELETEVERTEXARRAYSOESPROC)((rlglLoadProc)loader)("glDeleteVertexArraysOES"); - //glIsVertexArray = (PFNGLISVERTEXARRAYOESPROC)loader("glIsVertexArrayOES"); // NOTE: Fails in WebGL, omitted - - if ((glGenVertexArrays != NULL) && (glBindVertexArray != NULL) && (glDeleteVertexArrays != NULL)) RLGL.ExtSupported.vao = true; - } - - // Check instanced rendering support - if (strstr(extList[i], (const char*)"instanced_arrays") != NULL) // Broad check for instanced_arrays - { - // Specific check - if (strcmp(extList[i], (const char *)"GL_ANGLE_instanced_arrays") == 0) // ANGLE - { - glDrawArraysInstanced = (PFNGLDRAWARRAYSINSTANCEDEXTPROC)((rlglLoadProc)loader)("glDrawArraysInstancedANGLE"); - glDrawElementsInstanced = (PFNGLDRAWELEMENTSINSTANCEDEXTPROC)((rlglLoadProc)loader)("glDrawElementsInstancedANGLE"); - glVertexAttribDivisor = (PFNGLVERTEXATTRIBDIVISOREXTPROC)((rlglLoadProc)loader)("glVertexAttribDivisorANGLE"); - } - else if (strcmp(extList[i], (const char *)"GL_EXT_instanced_arrays") == 0) // EXT - { - glDrawArraysInstanced = (PFNGLDRAWARRAYSINSTANCEDEXTPROC)((rlglLoadProc)loader)("glDrawArraysInstancedEXT"); - glDrawElementsInstanced = (PFNGLDRAWELEMENTSINSTANCEDEXTPROC)((rlglLoadProc)loader)("glDrawElementsInstancedEXT"); - glVertexAttribDivisor = (PFNGLVERTEXATTRIBDIVISOREXTPROC)((rlglLoadProc)loader)("glVertexAttribDivisorEXT"); - } - else if (strcmp(extList[i], (const char *)"GL_NV_instanced_arrays") == 0) // NVIDIA GLES - { - glDrawArraysInstanced = (PFNGLDRAWARRAYSINSTANCEDEXTPROC)((rlglLoadProc)loader)("glDrawArraysInstancedNV"); - glDrawElementsInstanced = (PFNGLDRAWELEMENTSINSTANCEDEXTPROC)((rlglLoadProc)loader)("glDrawElementsInstancedNV"); - glVertexAttribDivisor = (PFNGLVERTEXATTRIBDIVISOREXTPROC)((rlglLoadProc)loader)("glVertexAttribDivisorNV"); - } - - // The feature will only be marked as supported if the elements from GL_XXX_instanced_arrays are present - if ((glDrawArraysInstanced != NULL) && (glDrawElementsInstanced != NULL) && (glVertexAttribDivisor != NULL)) RLGL.ExtSupported.instancing = true; - } - else if (strstr(extList[i], (const char *)"draw_instanced") != NULL) - { - // GL_ANGLE_draw_instanced doesn't exist - if (strcmp(extList[i], (const char *)"GL_EXT_draw_instanced") == 0) - { - glDrawArraysInstanced = (PFNGLDRAWARRAYSINSTANCEDEXTPROC)((rlglLoadProc)loader)("glDrawArraysInstancedEXT"); - glDrawElementsInstanced = (PFNGLDRAWELEMENTSINSTANCEDEXTPROC)((rlglLoadProc)loader)("glDrawElementsInstancedEXT"); - } - else if (strcmp(extList[i], (const char*)"GL_NV_draw_instanced") == 0) - { - glDrawArraysInstanced = (PFNGLDRAWARRAYSINSTANCEDEXTPROC)((rlglLoadProc)loader)("glDrawArraysInstancedNV"); - glDrawElementsInstanced = (PFNGLDRAWELEMENTSINSTANCEDEXTPROC)((rlglLoadProc)loader)("glDrawElementsInstancedNV"); - } - - // But the functions will at least be loaded if only GL_XX_EXT_draw_instanced exist - if ((glDrawArraysInstanced != NULL) && (glDrawElementsInstanced != NULL) && (glVertexAttribDivisor != NULL)) RLGL.ExtSupported.instancing = true; - } - - // Check NPOT textures support - // NOTE: Only check on OpenGL ES, OpenGL 3.3 has NPOT textures full support as core feature - if (strcmp(extList[i], (const char *)"GL_OES_texture_npot") == 0) RLGL.ExtSupported.texNPOT = true; - - // Check texture float support - if (strcmp(extList[i], (const char *)"GL_OES_texture_float") == 0) RLGL.ExtSupported.texFloat32 = true; - if (strcmp(extList[i], (const char *)"GL_OES_texture_half_float") == 0) RLGL.ExtSupported.texFloat16 = true; - - // Check depth texture support - if (strcmp(extList[i], (const char *)"GL_OES_depth_texture") == 0) RLGL.ExtSupported.texDepth = true; - if (strcmp(extList[i], (const char *)"GL_WEBGL_depth_texture") == 0) RLGL.ExtSupported.texDepthWebGL = true; // WebGL requires unsized internal format - if (RLGL.ExtSupported.texDepthWebGL) RLGL.ExtSupported.texDepth = true; - - if (strcmp(extList[i], (const char *)"GL_OES_depth24") == 0) RLGL.ExtSupported.maxDepthBits = 24; // Not available on WebGL - if (strcmp(extList[i], (const char *)"GL_OES_depth32") == 0) RLGL.ExtSupported.maxDepthBits = 32; // Not available on WebGL - - // Check texture compression support: DXT - if ((strcmp(extList[i], (const char *)"GL_EXT_texture_compression_s3tc") == 0) || - (strcmp(extList[i], (const char *)"GL_WEBGL_compressed_texture_s3tc") == 0) || - (strcmp(extList[i], (const char *)"GL_WEBKIT_WEBGL_compressed_texture_s3tc") == 0)) RLGL.ExtSupported.texCompDXT = true; - - // Check texture compression support: ETC1 - if ((strcmp(extList[i], (const char *)"GL_OES_compressed_ETC1_RGB8_texture") == 0) || - (strcmp(extList[i], (const char *)"GL_WEBGL_compressed_texture_etc1") == 0)) RLGL.ExtSupported.texCompETC1 = true; - - // Check texture compression support: ETC2/EAC - if (strcmp(extList[i], (const char *)"GL_ARB_ES3_compatibility") == 0) RLGL.ExtSupported.texCompETC2 = true; - - // Check texture compression support: PVR - if (strcmp(extList[i], (const char *)"GL_IMG_texture_compression_pvrtc") == 0) RLGL.ExtSupported.texCompPVRT = true; - - // Check texture compression support: ASTC - if (strcmp(extList[i], (const char *)"GL_KHR_texture_compression_astc_hdr") == 0) RLGL.ExtSupported.texCompASTC = true; - - // Check anisotropic texture filter support - if (strcmp(extList[i], (const char *)"GL_EXT_texture_filter_anisotropic") == 0) RLGL.ExtSupported.texAnisoFilter = true; - - // Check clamp mirror wrap mode support - if (strcmp(extList[i], (const char *)"GL_EXT_texture_mirror_clamp") == 0) RLGL.ExtSupported.texMirrorClamp = true; - } - - // Free extensions pointers - RL_FREE(extList); - RL_FREE(extensionsDup); // Duplicated string must be deallocated -#endif // GRAPHICS_API_OPENGL_ES2 - - // Check OpenGL information and capabilities - //------------------------------------------------------------------------------ - // Show current OpenGL and GLSL version - TRACELOG(RL_LOG_INFO, "GL: OpenGL device information:"); - TRACELOG(RL_LOG_INFO, " > Vendor: %s", glGetString(GL_VENDOR)); - TRACELOG(RL_LOG_INFO, " > Renderer: %s", glGetString(GL_RENDERER)); - TRACELOG(RL_LOG_INFO, " > Version: %s", glGetString(GL_VERSION)); - TRACELOG(RL_LOG_INFO, " > GLSL: %s", glGetString(GL_SHADING_LANGUAGE_VERSION)); - -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - // NOTE: Anisotropy levels capability is an extension - #ifndef GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT - #define GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT 0x84FF - #endif - glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &RLGL.ExtSupported.maxAnisotropyLevel); - -#if defined(RLGL_SHOW_GL_DETAILS_INFO) - // Show some OpenGL GPU capabilities - TRACELOG(RL_LOG_INFO, "GL: OpenGL capabilities:"); - GLint capability = 0; - glGetIntegerv(GL_MAX_TEXTURE_SIZE, &capability); - TRACELOG(RL_LOG_INFO, " GL_MAX_TEXTURE_SIZE: %i", capability); - glGetIntegerv(GL_MAX_CUBE_MAP_TEXTURE_SIZE, &capability); - TRACELOG(RL_LOG_INFO, " GL_MAX_CUBE_MAP_TEXTURE_SIZE: %i", capability); - glGetIntegerv(GL_MAX_TEXTURE_IMAGE_UNITS, &capability); - TRACELOG(RL_LOG_INFO, " GL_MAX_TEXTURE_IMAGE_UNITS: %i", capability); - glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &capability); - TRACELOG(RL_LOG_INFO, " GL_MAX_VERTEX_ATTRIBS: %i", capability); - #if !defined(GRAPHICS_API_OPENGL_ES2) - glGetIntegerv(GL_MAX_UNIFORM_BLOCK_SIZE, &capability); - TRACELOG(RL_LOG_INFO, " GL_MAX_UNIFORM_BLOCK_SIZE: %i", capability); - glGetIntegerv(GL_MAX_DRAW_BUFFERS, &capability); - TRACELOG(RL_LOG_INFO, " GL_MAX_DRAW_BUFFERS: %i", capability); - if (RLGL.ExtSupported.texAnisoFilter) TRACELOG(RL_LOG_INFO, " GL_MAX_TEXTURE_MAX_ANISOTROPY: %.0f", RLGL.ExtSupported.maxAnisotropyLevel); - #endif - glGetIntegerv(GL_NUM_COMPRESSED_TEXTURE_FORMATS, &capability); - TRACELOG(RL_LOG_INFO, " GL_NUM_COMPRESSED_TEXTURE_FORMATS: %i", capability); - GLint *compFormats = (GLint *)RL_CALLOC(capability, sizeof(GLint)); - glGetIntegerv(GL_COMPRESSED_TEXTURE_FORMATS, compFormats); - for (int i = 0; i < capability; i++) TRACELOG(RL_LOG_INFO, " %s", rlGetCompressedFormatName(compFormats[i])); - RL_FREE(compFormats); - -#if defined(GRAPHICS_API_OPENGL_43) - glGetIntegerv(GL_MAX_VERTEX_ATTRIB_BINDINGS, &capability); - TRACELOG(RL_LOG_INFO, " GL_MAX_VERTEX_ATTRIB_BINDINGS: %i", capability); - glGetIntegerv(GL_MAX_UNIFORM_LOCATIONS, &capability); - TRACELOG(RL_LOG_INFO, " GL_MAX_UNIFORM_LOCATIONS: %i", capability); -#endif // GRAPHICS_API_OPENGL_43 -#else // RLGL_SHOW_GL_DETAILS_INFO - - // Show some basic info about GL supported features - if (RLGL.ExtSupported.vao) TRACELOG(RL_LOG_INFO, "GL: VAO extension detected, VAO functions loaded successfully"); - else TRACELOG(RL_LOG_WARNING, "GL: VAO extension not found, VAO not supported"); - if (RLGL.ExtSupported.texNPOT) TRACELOG(RL_LOG_INFO, "GL: NPOT textures extension detected, full NPOT textures supported"); - else TRACELOG(RL_LOG_WARNING, "GL: NPOT textures extension not found, limited NPOT support (no-mipmaps, no-repeat)"); - if (RLGL.ExtSupported.texCompDXT) TRACELOG(RL_LOG_INFO, "GL: DXT compressed textures supported"); - if (RLGL.ExtSupported.texCompETC1) TRACELOG(RL_LOG_INFO, "GL: ETC1 compressed textures supported"); - if (RLGL.ExtSupported.texCompETC2) TRACELOG(RL_LOG_INFO, "GL: ETC2/EAC compressed textures supported"); - if (RLGL.ExtSupported.texCompPVRT) TRACELOG(RL_LOG_INFO, "GL: PVRT compressed textures supported"); - if (RLGL.ExtSupported.texCompASTC) TRACELOG(RL_LOG_INFO, "GL: ASTC compressed textures supported"); - if (RLGL.ExtSupported.computeShader) TRACELOG(RL_LOG_INFO, "GL: Compute shaders supported"); - if (RLGL.ExtSupported.ssbo) TRACELOG(RL_LOG_INFO, "GL: Shader storage buffer objects supported"); -#endif // RLGL_SHOW_GL_DETAILS_INFO - -#endif // GRAPHICS_API_OPENGL_33 || GRAPHICS_API_OPENGL_ES2 -} - -// Get current OpenGL version -int rlGetVersion(void) -{ - int glVersion = 0; -#if defined(GRAPHICS_API_OPENGL_11) - glVersion = RL_OPENGL_11; -#endif -#if defined(GRAPHICS_API_OPENGL_21) - glVersion = RL_OPENGL_21; -#elif defined(GRAPHICS_API_OPENGL_43) - glVersion = RL_OPENGL_43; -#elif defined(GRAPHICS_API_OPENGL_33) - glVersion = RL_OPENGL_33; -#endif -#if defined(GRAPHICS_API_OPENGL_ES3) - glVersion = RL_OPENGL_ES_30; -#elif defined(GRAPHICS_API_OPENGL_ES2) - glVersion = RL_OPENGL_ES_20; -#endif - - return glVersion; -} - -// Set current framebuffer width -void rlSetFramebufferWidth(int width) -{ -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - RLGL.State.framebufferWidth = width; -#endif -} - -// Set current framebuffer height -void rlSetFramebufferHeight(int height) -{ -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - RLGL.State.framebufferHeight = height; -#endif -} - -// Get default framebuffer width -int rlGetFramebufferWidth(void) -{ - int width = 0; -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - width = RLGL.State.framebufferWidth; -#endif - return width; -} - -// Get default framebuffer height -int rlGetFramebufferHeight(void) -{ - int height = 0; -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - height = RLGL.State.framebufferHeight; -#endif - return height; -} - -// Get default internal texture (white texture) -// NOTE: Default texture is a 1x1 pixel UNCOMPRESSED_R8G8B8A8 -unsigned int rlGetTextureIdDefault(void) -{ - unsigned int id = 0; -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - id = RLGL.State.defaultTextureId; -#endif - return id; -} - -// Get default shader id -unsigned int rlGetShaderIdDefault(void) -{ - unsigned int id = 0; -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - id = RLGL.State.defaultShaderId; -#endif - return id; -} - -// Get default shader locs -int *rlGetShaderLocsDefault(void) -{ - int *locs = NULL; -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - locs = RLGL.State.defaultShaderLocs; -#endif - return locs; -} - -// Render batch management -//------------------------------------------------------------------------------------------------ -// Load render batch -rlRenderBatch rlLoadRenderBatch(int numBuffers, int bufferElements) -{ - rlRenderBatch batch = { 0 }; - -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - // Initialize CPU (RAM) vertex buffers (position, texcoord, color data and indexes) - //-------------------------------------------------------------------------------------------- - batch.vertexBuffer = (rlVertexBuffer *)RL_MALLOC(numBuffers*sizeof(rlVertexBuffer)); - - for (int i = 0; i < numBuffers; i++) - { - batch.vertexBuffer[i].elementCount = bufferElements; - - batch.vertexBuffer[i].vertices = (float *)RL_MALLOC(bufferElements*3*4*sizeof(float)); // 3 float by vertex, 4 vertex by quad - batch.vertexBuffer[i].texcoords = (float *)RL_MALLOC(bufferElements*2*4*sizeof(float)); // 2 float by texcoord, 4 texcoord by quad - batch.vertexBuffer[i].normals = (float *)RL_MALLOC(bufferElements*3*4*sizeof(float)); // 3 float by vertex, 4 vertex by quad - batch.vertexBuffer[i].colors = (unsigned char *)RL_MALLOC(bufferElements*4*4*sizeof(unsigned char)); // 4 float by color, 4 colors by quad -#if defined(GRAPHICS_API_OPENGL_33) - batch.vertexBuffer[i].indices = (unsigned int *)RL_MALLOC(bufferElements*6*sizeof(unsigned int)); // 6 int by quad (indices) -#endif -#if defined(GRAPHICS_API_OPENGL_ES2) - batch.vertexBuffer[i].indices = (unsigned short *)RL_MALLOC(bufferElements*6*sizeof(unsigned short)); // 6 int by quad (indices) -#endif - - for (int j = 0; j < (3*4*bufferElements); j++) batch.vertexBuffer[i].vertices[j] = 0.0f; - for (int j = 0; j < (2*4*bufferElements); j++) batch.vertexBuffer[i].texcoords[j] = 0.0f; - for (int j = 0; j < (3*4*bufferElements); j++) batch.vertexBuffer[i].normals[j] = 0.0f; - for (int j = 0; j < (4*4*bufferElements); j++) batch.vertexBuffer[i].colors[j] = 0; - - int k = 0; - - // Indices can be initialized right now - for (int j = 0; j < (6*bufferElements); j += 6) - { - batch.vertexBuffer[i].indices[j] = 4*k; - batch.vertexBuffer[i].indices[j + 1] = 4*k + 1; - batch.vertexBuffer[i].indices[j + 2] = 4*k + 2; - batch.vertexBuffer[i].indices[j + 3] = 4*k; - batch.vertexBuffer[i].indices[j + 4] = 4*k + 2; - batch.vertexBuffer[i].indices[j + 5] = 4*k + 3; - - k++; - } - - RLGL.State.vertexCounter = 0; - } - - TRACELOG(RL_LOG_INFO, "RLGL: Render batch vertex buffers loaded successfully in RAM (CPU)"); - //-------------------------------------------------------------------------------------------- - - // Upload to GPU (VRAM) vertex data and initialize VAOs/VBOs - //-------------------------------------------------------------------------------------------- - for (int i = 0; i < numBuffers; i++) - { - if (RLGL.ExtSupported.vao) - { - // Initialize Quads VAO - glGenVertexArrays(1, &batch.vertexBuffer[i].vaoId); - glBindVertexArray(batch.vertexBuffer[i].vaoId); - } - - // Quads - Vertex buffers binding and attributes enable - // Vertex position buffer (shader-location = 0) - glGenBuffers(1, &batch.vertexBuffer[i].vboId[0]); - glBindBuffer(GL_ARRAY_BUFFER, batch.vertexBuffer[i].vboId[0]); - glBufferData(GL_ARRAY_BUFFER, bufferElements*3*4*sizeof(float), batch.vertexBuffer[i].vertices, GL_DYNAMIC_DRAW); - glEnableVertexAttribArray(RLGL.State.currentShaderLocs[RL_SHADER_LOC_VERTEX_POSITION]); - glVertexAttribPointer(RLGL.State.currentShaderLocs[RL_SHADER_LOC_VERTEX_POSITION], 3, GL_FLOAT, 0, 0, 0); - - // Vertex texcoord buffer (shader-location = 1) - glGenBuffers(1, &batch.vertexBuffer[i].vboId[1]); - glBindBuffer(GL_ARRAY_BUFFER, batch.vertexBuffer[i].vboId[1]); - glBufferData(GL_ARRAY_BUFFER, bufferElements*2*4*sizeof(float), batch.vertexBuffer[i].texcoords, GL_DYNAMIC_DRAW); - glEnableVertexAttribArray(RLGL.State.currentShaderLocs[RL_SHADER_LOC_VERTEX_TEXCOORD01]); - glVertexAttribPointer(RLGL.State.currentShaderLocs[RL_SHADER_LOC_VERTEX_TEXCOORD01], 2, GL_FLOAT, 0, 0, 0); - - // Vertex normal buffer (shader-location = 2) - glGenBuffers(1, &batch.vertexBuffer[i].vboId[2]); - glBindBuffer(GL_ARRAY_BUFFER, batch.vertexBuffer[i].vboId[2]); - glBufferData(GL_ARRAY_BUFFER, bufferElements*3*4*sizeof(float), batch.vertexBuffer[i].normals, GL_DYNAMIC_DRAW); - glEnableVertexAttribArray(RLGL.State.currentShaderLocs[RL_SHADER_LOC_VERTEX_NORMAL]); - glVertexAttribPointer(RLGL.State.currentShaderLocs[RL_SHADER_LOC_VERTEX_NORMAL], 3, GL_FLOAT, 0, 0, 0); - - // Vertex color buffer (shader-location = 3) - glGenBuffers(1, &batch.vertexBuffer[i].vboId[3]); - glBindBuffer(GL_ARRAY_BUFFER, batch.vertexBuffer[i].vboId[3]); - glBufferData(GL_ARRAY_BUFFER, bufferElements*4*4*sizeof(unsigned char), batch.vertexBuffer[i].colors, GL_DYNAMIC_DRAW); - glEnableVertexAttribArray(RLGL.State.currentShaderLocs[RL_SHADER_LOC_VERTEX_COLOR]); - glVertexAttribPointer(RLGL.State.currentShaderLocs[RL_SHADER_LOC_VERTEX_COLOR], 4, GL_UNSIGNED_BYTE, GL_TRUE, 0, 0); - - // Fill index buffer - glGenBuffers(1, &batch.vertexBuffer[i].vboId[4]); - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, batch.vertexBuffer[i].vboId[4]); -#if defined(GRAPHICS_API_OPENGL_33) - glBufferData(GL_ELEMENT_ARRAY_BUFFER, bufferElements*6*sizeof(int), batch.vertexBuffer[i].indices, GL_STATIC_DRAW); -#endif -#if defined(GRAPHICS_API_OPENGL_ES2) - glBufferData(GL_ELEMENT_ARRAY_BUFFER, bufferElements*6*sizeof(short), batch.vertexBuffer[i].indices, GL_STATIC_DRAW); -#endif - } - - TRACELOG(RL_LOG_INFO, "RLGL: Render batch vertex buffers loaded successfully in VRAM (GPU)"); - - // Unbind the current VAO - if (RLGL.ExtSupported.vao) glBindVertexArray(0); - //-------------------------------------------------------------------------------------------- - - // Init draw calls tracking system - //-------------------------------------------------------------------------------------------- - batch.draws = (rlDrawCall *)RL_MALLOC(RL_DEFAULT_BATCH_DRAWCALLS*sizeof(rlDrawCall)); - - for (int i = 0; i < RL_DEFAULT_BATCH_DRAWCALLS; i++) - { - batch.draws[i].mode = RL_QUADS; - batch.draws[i].vertexCount = 0; - batch.draws[i].vertexAlignment = 0; - //batch.draws[i].vaoId = 0; - //batch.draws[i].shaderId = 0; - batch.draws[i].textureId = RLGL.State.defaultTextureId; - //batch.draws[i].RLGL.State.projection = rlMatrixIdentity(); - //batch.draws[i].RLGL.State.modelview = rlMatrixIdentity(); - } - - batch.bufferCount = numBuffers; // Record buffer count - batch.drawCounter = 1; // Reset draws counter - batch.currentDepth = -1.0f; // Reset depth value - //-------------------------------------------------------------------------------------------- -#endif - - return batch; -} - -// Unload default internal buffers vertex data from CPU and GPU -void rlUnloadRenderBatch(rlRenderBatch batch) -{ -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - // Unbind everything - glBindBuffer(GL_ARRAY_BUFFER, 0); - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); - - // Unload all vertex buffers data - for (int i = 0; i < batch.bufferCount; i++) - { - // Unbind VAO attribs data - if (RLGL.ExtSupported.vao) - { - glBindVertexArray(batch.vertexBuffer[i].vaoId); - glDisableVertexAttribArray(RL_DEFAULT_SHADER_ATTRIB_LOCATION_POSITION); - glDisableVertexAttribArray(RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD); - glDisableVertexAttribArray(RL_DEFAULT_SHADER_ATTRIB_LOCATION_NORMAL); - glDisableVertexAttribArray(RL_DEFAULT_SHADER_ATTRIB_LOCATION_COLOR); - glBindVertexArray(0); - } - - // Delete VBOs from GPU (VRAM) - glDeleteBuffers(1, &batch.vertexBuffer[i].vboId[0]); - glDeleteBuffers(1, &batch.vertexBuffer[i].vboId[1]); - glDeleteBuffers(1, &batch.vertexBuffer[i].vboId[2]); - glDeleteBuffers(1, &batch.vertexBuffer[i].vboId[3]); - glDeleteBuffers(1, &batch.vertexBuffer[i].vboId[4]); - - // Delete VAOs from GPU (VRAM) - if (RLGL.ExtSupported.vao) glDeleteVertexArrays(1, &batch.vertexBuffer[i].vaoId); - - // Free vertex arrays memory from CPU (RAM) - RL_FREE(batch.vertexBuffer[i].vertices); - RL_FREE(batch.vertexBuffer[i].texcoords); - RL_FREE(batch.vertexBuffer[i].normals); - RL_FREE(batch.vertexBuffer[i].colors); - RL_FREE(batch.vertexBuffer[i].indices); - } - - // Unload arrays - RL_FREE(batch.vertexBuffer); - RL_FREE(batch.draws); -#endif -} - -// Draw render batch -// NOTE: We require a pointer to reset batch and increase current buffer (multi-buffer) -void rlDrawRenderBatch(rlRenderBatch *batch) -{ -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - // Update batch vertex buffers - //------------------------------------------------------------------------------------------------------------ - // NOTE: If there is not vertex data, buffers doesn't need to be updated (vertexCount > 0) - // TODO: If no data changed on the CPU arrays --> No need to re-update GPU arrays (use a change detector flag?) - if (RLGL.State.vertexCounter > 0) - { - // Activate elements VAO - if (RLGL.ExtSupported.vao) glBindVertexArray(batch->vertexBuffer[batch->currentBuffer].vaoId); - - // Vertex positions buffer - glBindBuffer(GL_ARRAY_BUFFER, batch->vertexBuffer[batch->currentBuffer].vboId[0]); - glBufferSubData(GL_ARRAY_BUFFER, 0, RLGL.State.vertexCounter*3*sizeof(float), batch->vertexBuffer[batch->currentBuffer].vertices); - //glBufferData(GL_ARRAY_BUFFER, sizeof(float)*3*4*batch->vertexBuffer[batch->currentBuffer].elementCount, batch->vertexBuffer[batch->currentBuffer].vertices, GL_DYNAMIC_DRAW); // Update all buffer - - // Texture coordinates buffer - glBindBuffer(GL_ARRAY_BUFFER, batch->vertexBuffer[batch->currentBuffer].vboId[1]); - glBufferSubData(GL_ARRAY_BUFFER, 0, RLGL.State.vertexCounter*2*sizeof(float), batch->vertexBuffer[batch->currentBuffer].texcoords); - //glBufferData(GL_ARRAY_BUFFER, sizeof(float)*2*4*batch->vertexBuffer[batch->currentBuffer].elementCount, batch->vertexBuffer[batch->currentBuffer].texcoords, GL_DYNAMIC_DRAW); // Update all buffer - - // Normals buffer - glBindBuffer(GL_ARRAY_BUFFER, batch->vertexBuffer[batch->currentBuffer].vboId[2]); - glBufferSubData(GL_ARRAY_BUFFER, 0, RLGL.State.vertexCounter*3*sizeof(float), batch->vertexBuffer[batch->currentBuffer].normals); - //glBufferData(GL_ARRAY_BUFFER, sizeof(float)*3*4*batch->vertexBuffer[batch->currentBuffer].elementCount, batch->vertexBuffer[batch->currentBuffer].normals, GL_DYNAMIC_DRAW); // Update all buffer - - // Colors buffer - glBindBuffer(GL_ARRAY_BUFFER, batch->vertexBuffer[batch->currentBuffer].vboId[3]); - glBufferSubData(GL_ARRAY_BUFFER, 0, RLGL.State.vertexCounter*4*sizeof(unsigned char), batch->vertexBuffer[batch->currentBuffer].colors); - //glBufferData(GL_ARRAY_BUFFER, sizeof(float)*4*4*batch->vertexBuffer[batch->currentBuffer].elementCount, batch->vertexBuffer[batch->currentBuffer].colors, GL_DYNAMIC_DRAW); // Update all buffer - - // NOTE: glMapBuffer() causes sync issue - // If GPU is working with this buffer, glMapBuffer() will wait(stall) until GPU to finish its job - // To avoid waiting (idle), you can call first glBufferData() with NULL pointer before glMapBuffer() - // If you do that, the previous data in PBO will be discarded and glMapBuffer() returns a new - // allocated pointer immediately even if GPU is still working with the previous data - - // Another option: map the buffer object into client's memory - // Probably this code could be moved somewhere else... - // batch->vertexBuffer[batch->currentBuffer].vertices = (float *)glMapBuffer(GL_ARRAY_BUFFER, GL_READ_WRITE); - // if (batch->vertexBuffer[batch->currentBuffer].vertices) - // { - // Update vertex data - // } - // glUnmapBuffer(GL_ARRAY_BUFFER); - - // Unbind the current VAO - if (RLGL.ExtSupported.vao) glBindVertexArray(0); - } - //------------------------------------------------------------------------------------------------------------ - - // Draw batch vertex buffers (considering VR stereo if required) - //------------------------------------------------------------------------------------------------------------ - Matrix matProjection = RLGL.State.projection; - Matrix matModelView = RLGL.State.modelview; - - int eyeCount = 1; - if (RLGL.State.stereoRender) eyeCount = 2; - - for (int eye = 0; eye < eyeCount; eye++) - { - if (eyeCount == 2) - { - // Setup current eye viewport (half screen width) - rlViewport(eye*RLGL.State.framebufferWidth/2, 0, RLGL.State.framebufferWidth/2, RLGL.State.framebufferHeight); - - // Set current eye view offset to modelview matrix - rlSetMatrixModelview(rlMatrixMultiply(matModelView, RLGL.State.viewOffsetStereo[eye])); - // Set current eye projection matrix - rlSetMatrixProjection(RLGL.State.projectionStereo[eye]); - } - - // Draw buffers - if (RLGL.State.vertexCounter > 0) - { - // Set current shader and upload current MVP matrix - glUseProgram(RLGL.State.currentShaderId); - - // Create modelview-projection matrix and upload to shader - Matrix matMVP = rlMatrixMultiply(RLGL.State.modelview, RLGL.State.projection); - glUniformMatrix4fv(RLGL.State.currentShaderLocs[RL_SHADER_LOC_MATRIX_MVP], 1, false, rlMatrixToFloat(matMVP)); - - if (RLGL.State.currentShaderLocs[RL_SHADER_LOC_MATRIX_PROJECTION] != -1) - { - glUniformMatrix4fv(RLGL.State.currentShaderLocs[RL_SHADER_LOC_MATRIX_PROJECTION], 1, false, rlMatrixToFloat(RLGL.State.projection)); - } - - // WARNING: For the following setup of the view, model, and normal matrices, it is expected that - // transformations and rendering occur between rlPushMatrix() and rlPopMatrix() - - if (RLGL.State.currentShaderLocs[RL_SHADER_LOC_MATRIX_VIEW] != -1) - { - glUniformMatrix4fv(RLGL.State.currentShaderLocs[RL_SHADER_LOC_MATRIX_VIEW], 1, false, rlMatrixToFloat(RLGL.State.modelview)); - } - - if (RLGL.State.currentShaderLocs[RL_SHADER_LOC_MATRIX_MODEL] != -1) - { - glUniformMatrix4fv(RLGL.State.currentShaderLocs[RL_SHADER_LOC_MATRIX_MODEL], 1, false, rlMatrixToFloat(RLGL.State.transform)); - } - - if (RLGL.State.currentShaderLocs[RL_SHADER_LOC_MATRIX_NORMAL] != -1) - { - glUniformMatrix4fv(RLGL.State.currentShaderLocs[RL_SHADER_LOC_MATRIX_NORMAL], 1, false, rlMatrixToFloat(rlMatrixTranspose(rlMatrixInvert(RLGL.State.transform)))); - } - - if (RLGL.ExtSupported.vao) glBindVertexArray(batch->vertexBuffer[batch->currentBuffer].vaoId); - else - { - // Bind vertex attrib: position (shader-location = 0) - glBindBuffer(GL_ARRAY_BUFFER, batch->vertexBuffer[batch->currentBuffer].vboId[0]); - glVertexAttribPointer(RLGL.State.currentShaderLocs[RL_SHADER_LOC_VERTEX_POSITION], 3, GL_FLOAT, 0, 0, 0); - glEnableVertexAttribArray(RLGL.State.currentShaderLocs[RL_SHADER_LOC_VERTEX_POSITION]); - - // Bind vertex attrib: texcoord (shader-location = 1) - glBindBuffer(GL_ARRAY_BUFFER, batch->vertexBuffer[batch->currentBuffer].vboId[1]); - glVertexAttribPointer(RLGL.State.currentShaderLocs[RL_SHADER_LOC_VERTEX_TEXCOORD01], 2, GL_FLOAT, 0, 0, 0); - glEnableVertexAttribArray(RLGL.State.currentShaderLocs[RL_SHADER_LOC_VERTEX_TEXCOORD01]); - - // Bind vertex attrib: normal (shader-location = 2) - glBindBuffer(GL_ARRAY_BUFFER, batch->vertexBuffer[batch->currentBuffer].vboId[2]); - glVertexAttribPointer(RLGL.State.currentShaderLocs[RL_SHADER_LOC_VERTEX_NORMAL], 3, GL_FLOAT, 0, 0, 0); - glEnableVertexAttribArray(RLGL.State.currentShaderLocs[RL_SHADER_LOC_VERTEX_NORMAL]); - - // Bind vertex attrib: color (shader-location = 3) - glBindBuffer(GL_ARRAY_BUFFER, batch->vertexBuffer[batch->currentBuffer].vboId[3]); - glVertexAttribPointer(RLGL.State.currentShaderLocs[RL_SHADER_LOC_VERTEX_COLOR], 4, GL_UNSIGNED_BYTE, GL_TRUE, 0, 0); - glEnableVertexAttribArray(RLGL.State.currentShaderLocs[RL_SHADER_LOC_VERTEX_COLOR]); - - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, batch->vertexBuffer[batch->currentBuffer].vboId[4]); - } - - // Setup some default shader values - glUniform4f(RLGL.State.currentShaderLocs[RL_SHADER_LOC_COLOR_DIFFUSE], 1.0f, 1.0f, 1.0f, 1.0f); - glUniform1i(RLGL.State.currentShaderLocs[RL_SHADER_LOC_MAP_DIFFUSE], 0); // Active default sampler2D: texture0 - - // Activate additional sampler textures - // Those additional textures will be common for all draw calls of the batch - for (int i = 0; i < RL_DEFAULT_BATCH_MAX_TEXTURE_UNITS; i++) - { - if (RLGL.State.activeTextureId[i] > 0) - { - glActiveTexture(GL_TEXTURE0 + 1 + i); - glBindTexture(GL_TEXTURE_2D, RLGL.State.activeTextureId[i]); - } - } - - // Activate default sampler2D texture0 (one texture is always active for default batch shader) - // NOTE: Batch system accumulates calls by texture0 changes, additional textures are enabled for all the draw calls - glActiveTexture(GL_TEXTURE0); - - for (int i = 0, vertexOffset = 0; i < batch->drawCounter; i++) - { - // Bind current draw call texture, activated as GL_TEXTURE0 and Bound to sampler2D texture0 by default - glBindTexture(GL_TEXTURE_2D, batch->draws[i].textureId); - - if ((batch->draws[i].mode == RL_LINES) || (batch->draws[i].mode == RL_TRIANGLES)) glDrawArrays(batch->draws[i].mode, vertexOffset, batch->draws[i].vertexCount); - else - { - #if defined(GRAPHICS_API_OPENGL_33) - // We need to define the number of indices to be processed: elementCount*6 - // NOTE: The final parameter tells the GPU the offset in bytes from the - // start of the index buffer to the location of the first index to process - glDrawElements(GL_TRIANGLES, batch->draws[i].vertexCount/4*6, GL_UNSIGNED_INT, (GLvoid *)(vertexOffset/4*6*sizeof(GLuint))); - #endif - #if defined(GRAPHICS_API_OPENGL_ES2) - glDrawElements(GL_TRIANGLES, batch->draws[i].vertexCount/4*6, GL_UNSIGNED_SHORT, (GLvoid *)(vertexOffset/4*6*sizeof(GLushort))); - #endif - } - - vertexOffset += (batch->draws[i].vertexCount + batch->draws[i].vertexAlignment); - } - - if (!RLGL.ExtSupported.vao) - { - glBindBuffer(GL_ARRAY_BUFFER, 0); - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); - } - - glBindTexture(GL_TEXTURE_2D, 0); // Unbind textures - } - - if (RLGL.ExtSupported.vao) glBindVertexArray(0); // Unbind VAO - - glUseProgram(0); // Unbind shader program - } - - // Restore viewport to default measures - if (eyeCount == 2) rlViewport(0, 0, RLGL.State.framebufferWidth, RLGL.State.framebufferHeight); - //------------------------------------------------------------------------------------------------------------ - - // Reset batch buffers - //------------------------------------------------------------------------------------------------------------ - // Reset vertex counter for next frame - RLGL.State.vertexCounter = 0; - - // Reset depth for next draw - batch->currentDepth = -1.0f; - - // Restore projection/modelview matrices - RLGL.State.projection = matProjection; - RLGL.State.modelview = matModelView; - - // Reset RLGL.currentBatch->draws array - for (int i = 0; i < RL_DEFAULT_BATCH_DRAWCALLS; i++) - { - batch->draws[i].mode = RL_QUADS; - batch->draws[i].vertexCount = 0; - batch->draws[i].textureId = RLGL.State.defaultTextureId; - } - - // Reset active texture units for next batch - for (int i = 0; i < RL_DEFAULT_BATCH_MAX_TEXTURE_UNITS; i++) RLGL.State.activeTextureId[i] = 0; - - // Reset draws counter to one draw for the batch - batch->drawCounter = 1; - //------------------------------------------------------------------------------------------------------------ - - // Change to next buffer in the list (in case of multi-buffering) - batch->currentBuffer++; - if (batch->currentBuffer >= batch->bufferCount) batch->currentBuffer = 0; -#endif -} - -// Set the active render batch for rlgl -void rlSetRenderBatchActive(rlRenderBatch *batch) -{ -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - rlDrawRenderBatch(RLGL.currentBatch); - - if (batch != NULL) RLGL.currentBatch = batch; - else RLGL.currentBatch = &RLGL.defaultBatch; -#endif -} - -// Update and draw internal render batch -void rlDrawRenderBatchActive(void) -{ -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - rlDrawRenderBatch(RLGL.currentBatch); // NOTE: Stereo rendering is checked inside -#endif -} - -// Check internal buffer overflow for a given number of vertex -// and force a rlRenderBatch draw call if required -bool rlCheckRenderBatchLimit(int vCount) -{ - bool overflow = false; - -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - if ((RLGL.State.vertexCounter + vCount) >= - (RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].elementCount*4)) - { - overflow = true; - - // Store current primitive drawing mode and texture id - int currentMode = RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].mode; - int currentTexture = RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].textureId; - - rlDrawRenderBatch(RLGL.currentBatch); // NOTE: Stereo rendering is checked inside - - // Restore state of last batch so we can continue adding vertices - RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].mode = currentMode; - RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].textureId = currentTexture; - } -#endif - - return overflow; -} - -// Textures data management -//----------------------------------------------------------------------------------------- -// Convert image data to OpenGL texture (returns OpenGL valid Id) -unsigned int rlLoadTexture(const void *data, int width, int height, int format, int mipmapCount) -{ - unsigned int id = 0; - - glBindTexture(GL_TEXTURE_2D, 0); // Free any old binding - - // Check texture format support by OpenGL 1.1 (compressed textures not supported) -#if defined(GRAPHICS_API_OPENGL_11) - if (format >= RL_PIXELFORMAT_COMPRESSED_DXT1_RGB) - { - TRACELOG(RL_LOG_WARNING, "GL: OpenGL 1.1 does not support GPU compressed texture formats"); - return id; - } -#else - if ((!RLGL.ExtSupported.texCompDXT) && ((format == RL_PIXELFORMAT_COMPRESSED_DXT1_RGB) || (format == RL_PIXELFORMAT_COMPRESSED_DXT1_RGBA) || - (format == RL_PIXELFORMAT_COMPRESSED_DXT3_RGBA) || (format == RL_PIXELFORMAT_COMPRESSED_DXT5_RGBA))) - { - TRACELOG(RL_LOG_WARNING, "GL: DXT compressed texture format not supported"); - return id; - } -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - if ((!RLGL.ExtSupported.texCompETC1) && (format == RL_PIXELFORMAT_COMPRESSED_ETC1_RGB)) - { - TRACELOG(RL_LOG_WARNING, "GL: ETC1 compressed texture format not supported"); - return id; - } - - if ((!RLGL.ExtSupported.texCompETC2) && ((format == RL_PIXELFORMAT_COMPRESSED_ETC2_RGB) || (format == RL_PIXELFORMAT_COMPRESSED_ETC2_EAC_RGBA))) - { - TRACELOG(RL_LOG_WARNING, "GL: ETC2 compressed texture format not supported"); - return id; - } - - if ((!RLGL.ExtSupported.texCompPVRT) && ((format == RL_PIXELFORMAT_COMPRESSED_PVRT_RGB) || (format == RL_PIXELFORMAT_COMPRESSED_PVRT_RGBA))) - { - TRACELOG(RL_LOG_WARNING, "GL: PVRT compressed texture format not supported"); - return id; - } - - if ((!RLGL.ExtSupported.texCompASTC) && ((format == RL_PIXELFORMAT_COMPRESSED_ASTC_4x4_RGBA) || (format == RL_PIXELFORMAT_COMPRESSED_ASTC_8x8_RGBA))) - { - TRACELOG(RL_LOG_WARNING, "GL: ASTC compressed texture format not supported"); - return id; - } -#endif -#endif // GRAPHICS_API_OPENGL_11 - - glPixelStorei(GL_UNPACK_ALIGNMENT, 1); - - glGenTextures(1, &id); // Generate texture id - - glBindTexture(GL_TEXTURE_2D, id); - - int mipWidth = width; - int mipHeight = height; - int mipOffset = 0; // Mipmap data offset, only used for tracelog - - // NOTE: Added pointer math separately from function to avoid UBSAN complaining - unsigned char *dataPtr = NULL; - if (data != NULL) dataPtr = (unsigned char *)data; - - // Load the different mipmap levels - for (int i = 0; i < mipmapCount; i++) - { - unsigned int mipSize = rlGetPixelDataSize(mipWidth, mipHeight, format); - - unsigned int glInternalFormat, glFormat, glType; - rlGetGlTextureFormats(format, &glInternalFormat, &glFormat, &glType); - - TRACELOGD("TEXTURE: Load mipmap level %i (%i x %i), size: %i, offset: %i", i, mipWidth, mipHeight, mipSize, mipOffset); - - if (glInternalFormat != 0) - { - if (format < RL_PIXELFORMAT_COMPRESSED_DXT1_RGB) glTexImage2D(GL_TEXTURE_2D, i, glInternalFormat, mipWidth, mipHeight, 0, glFormat, glType, dataPtr); -#if !defined(GRAPHICS_API_OPENGL_11) - else glCompressedTexImage2D(GL_TEXTURE_2D, i, glInternalFormat, mipWidth, mipHeight, 0, mipSize, dataPtr); -#endif - -#if defined(GRAPHICS_API_OPENGL_33) - if (format == RL_PIXELFORMAT_UNCOMPRESSED_GRAYSCALE) - { - GLint swizzleMask[] = { GL_RED, GL_RED, GL_RED, GL_ONE }; - glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_RGBA, swizzleMask); - } - else if (format == RL_PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA) - { -#if defined(GRAPHICS_API_OPENGL_21) - GLint swizzleMask[] = { GL_RED, GL_RED, GL_RED, GL_ALPHA }; -#elif defined(GRAPHICS_API_OPENGL_33) - GLint swizzleMask[] = { GL_RED, GL_RED, GL_RED, GL_GREEN }; -#endif - glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_RGBA, swizzleMask); - } -#endif - } - - mipWidth /= 2; - mipHeight /= 2; - mipOffset += mipSize; // Increment offset position to next mipmap - if (data != NULL) dataPtr += mipSize; // Increment data pointer to next mipmap - - // Security check for NPOT textures - if (mipWidth < 1) mipWidth = 1; - if (mipHeight < 1) mipHeight = 1; - } - - // Texture parameters configuration - // NOTE: glTexParameteri does NOT affect texture uploading, just the way it's used -#if defined(GRAPHICS_API_OPENGL_ES2) - // NOTE: OpenGL ES 2.0 with no GL_OES_texture_npot support (i.e. WebGL) has limited NPOT support, so CLAMP_TO_EDGE must be used - if (RLGL.ExtSupported.texNPOT) - { - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); // Set texture to repeat on x-axis - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); // Set texture to repeat on y-axis - } - else - { - // NOTE: If using negative texture coordinates (LoadOBJ()), it does not work! - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); // Set texture to clamp on x-axis - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); // Set texture to clamp on y-axis - } -#else - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); // Set texture to repeat on x-axis - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); // Set texture to repeat on y-axis -#endif - - // Magnification and minification filters - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); // Alternative: GL_LINEAR - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); // Alternative: GL_LINEAR - -#if defined(GRAPHICS_API_OPENGL_33) - if (mipmapCount > 1) - { - // Activate Trilinear filtering if mipmaps are available - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); - } -#endif - - // At this point we have the texture loaded in GPU and texture parameters configured - - // NOTE: If mipmaps were not in data, they are not generated automatically - - // Unbind current texture - glBindTexture(GL_TEXTURE_2D, 0); - - if (id > 0) TRACELOG(RL_LOG_INFO, "TEXTURE: [ID %i] Texture loaded successfully (%ix%i | %s | %i mipmaps)", id, width, height, rlGetPixelFormatName(format), mipmapCount); - else TRACELOG(RL_LOG_WARNING, "TEXTURE: Failed to load texture"); - - return id; -} - -// Load depth texture/renderbuffer (to be attached to fbo) -// WARNING: OpenGL ES 2.0 requires GL_OES_depth_texture and WebGL requires WEBGL_depth_texture extensions -unsigned int rlLoadTextureDepth(int width, int height, bool useRenderBuffer) -{ - unsigned int id = 0; - -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - // In case depth textures not supported, we force renderbuffer usage - if (!RLGL.ExtSupported.texDepth) useRenderBuffer = true; - - // NOTE: We let the implementation to choose the best bit-depth - // Possible formats: GL_DEPTH_COMPONENT16, GL_DEPTH_COMPONENT24, GL_DEPTH_COMPONENT32 and GL_DEPTH_COMPONENT32F - unsigned int glInternalFormat = GL_DEPTH_COMPONENT; - -#if (defined(GRAPHICS_API_OPENGL_ES2) || defined(GRAPHICS_API_OPENGL_ES3)) - // WARNING: WebGL platform requires unsized internal format definition (GL_DEPTH_COMPONENT) - // while other platforms using OpenGL ES 2.0 require/support sized internal formats depending on the GPU capabilities - if (!RLGL.ExtSupported.texDepthWebGL || useRenderBuffer) - { - if (RLGL.ExtSupported.maxDepthBits == 32) glInternalFormat = GL_DEPTH_COMPONENT32_OES; - else if (RLGL.ExtSupported.maxDepthBits == 24) glInternalFormat = GL_DEPTH_COMPONENT24_OES; - else glInternalFormat = GL_DEPTH_COMPONENT16; - } -#endif - - if (!useRenderBuffer && RLGL.ExtSupported.texDepth) - { - glGenTextures(1, &id); - glBindTexture(GL_TEXTURE_2D, id); - glTexImage2D(GL_TEXTURE_2D, 0, glInternalFormat, width, height, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_INT, NULL); - - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - - glBindTexture(GL_TEXTURE_2D, 0); - - TRACELOG(RL_LOG_INFO, "TEXTURE: Depth texture loaded successfully"); - } - else - { - // Create the renderbuffer that will serve as the depth attachment for the framebuffer - // NOTE: A renderbuffer is simpler than a texture and could offer better performance on embedded devices - glGenRenderbuffers(1, &id); - glBindRenderbuffer(GL_RENDERBUFFER, id); - glRenderbufferStorage(GL_RENDERBUFFER, glInternalFormat, width, height); - - glBindRenderbuffer(GL_RENDERBUFFER, 0); - - TRACELOG(RL_LOG_INFO, "TEXTURE: [ID %i] Depth renderbuffer loaded successfully (%i bits)", id, (RLGL.ExtSupported.maxDepthBits >= 24)? RLGL.ExtSupported.maxDepthBits : 16); - } -#endif - - return id; -} - -// Load texture cubemap -// NOTE: Cubemap data is expected to be 6 images in a single data array (one after the other), -// expected the following convention: +X, -X, +Y, -Y, +Z, -Z -unsigned int rlLoadTextureCubemap(const void *data, int size, int format, int mipmapCount) -{ - unsigned int id = 0; - -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - int mipSize = size; - - // NOTE: Added pointer math separately from function to avoid UBSAN complaining - unsigned char *dataPtr = NULL; - if (data != NULL) dataPtr = (unsigned char *)data; - - unsigned int dataSize = rlGetPixelDataSize(size, size, format); - - glGenTextures(1, &id); - glBindTexture(GL_TEXTURE_CUBE_MAP, id); - - unsigned int glInternalFormat, glFormat, glType; - rlGetGlTextureFormats(format, &glInternalFormat, &glFormat, &glType); - - if (glInternalFormat != 0) - { - // Load cubemap faces/mipmaps - for (int i = 0; i < 6*mipmapCount; i++) - { - int mipmapLevel = i/6; - int face = i%6; - - if (data == NULL) - { - if (format < RL_PIXELFORMAT_COMPRESSED_DXT1_RGB) - { - if ((format == RL_PIXELFORMAT_UNCOMPRESSED_R32) || - (format == RL_PIXELFORMAT_UNCOMPRESSED_R32G32B32A32) || - (format == RL_PIXELFORMAT_UNCOMPRESSED_R16) || - (format == RL_PIXELFORMAT_UNCOMPRESSED_R16G16B16A16)) TRACELOG(RL_LOG_WARNING, "TEXTURES: Cubemap requested format not supported"); - else glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + face, mipmapLevel, glInternalFormat, mipSize, mipSize, 0, glFormat, glType, NULL); - } - else TRACELOG(RL_LOG_WARNING, "TEXTURES: Empty cubemap creation does not support compressed format"); - } - else - { - if (format < RL_PIXELFORMAT_COMPRESSED_DXT1_RGB) glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + face, mipmapLevel, glInternalFormat, mipSize, mipSize, 0, glFormat, glType, (unsigned char *)dataPtr + face*dataSize); - else glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + face, mipmapLevel, glInternalFormat, mipSize, mipSize, 0, dataSize, (unsigned char *)dataPtr + face*dataSize); - } - -#if defined(GRAPHICS_API_OPENGL_33) - if (format == RL_PIXELFORMAT_UNCOMPRESSED_GRAYSCALE) - { - GLint swizzleMask[] = { GL_RED, GL_RED, GL_RED, GL_ONE }; - glTexParameteriv(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_SWIZZLE_RGBA, swizzleMask); - } - else if (format == RL_PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA) - { -#if defined(GRAPHICS_API_OPENGL_21) - GLint swizzleMask[] = { GL_RED, GL_RED, GL_RED, GL_ALPHA }; -#elif defined(GRAPHICS_API_OPENGL_33) - GLint swizzleMask[] = { GL_RED, GL_RED, GL_RED, GL_GREEN }; -#endif - glTexParameteriv(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_SWIZZLE_RGBA, swizzleMask); - } -#endif - if (face == 5) - { - mipSize /= 2; - if (data != NULL) dataPtr += dataSize*6; // Increment data pointer to next mipmap - - // Security check for NPOT textures - if (mipSize < 1) mipSize = 1; - - dataSize = rlGetPixelDataSize(mipSize, mipSize, format); - } - } - } - - // Set cubemap texture sampling parameters - if (mipmapCount > 1) glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); - else glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - - glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); -#if defined(GRAPHICS_API_OPENGL_33) - glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); // Flag not supported on OpenGL ES 2.0 -#endif - - glBindTexture(GL_TEXTURE_CUBE_MAP, 0); -#endif - - if (id > 0) TRACELOG(RL_LOG_INFO, "TEXTURE: [ID %i] Cubemap texture loaded successfully (%ix%i)", id, size, size); - else TRACELOG(RL_LOG_WARNING, "TEXTURE: Failed to load cubemap texture"); - - return id; -} - -// Update already loaded texture in GPU with new data -// NOTE: We don't know safely if internal texture format is the expected one... -void rlUpdateTexture(unsigned int id, int offsetX, int offsetY, int width, int height, int format, const void *data) -{ - glBindTexture(GL_TEXTURE_2D, id); - - unsigned int glInternalFormat, glFormat, glType; - rlGetGlTextureFormats(format, &glInternalFormat, &glFormat, &glType); - - if ((glInternalFormat != 0) && (format < RL_PIXELFORMAT_COMPRESSED_DXT1_RGB)) - { - glTexSubImage2D(GL_TEXTURE_2D, 0, offsetX, offsetY, width, height, glFormat, glType, data); - } - else TRACELOG(RL_LOG_WARNING, "TEXTURE: [ID %i] Failed to update for current texture format (%i)", id, format); -} - -// Get OpenGL internal formats and data type from raylib PixelFormat -void rlGetGlTextureFormats(int format, unsigned int *glInternalFormat, unsigned int *glFormat, unsigned int *glType) -{ - *glInternalFormat = 0; - *glFormat = 0; - *glType = 0; - - switch (format) - { - #if defined(GRAPHICS_API_OPENGL_11) || defined(GRAPHICS_API_OPENGL_21) || defined(GRAPHICS_API_OPENGL_ES2) - // NOTE: on OpenGL ES 2.0 (WebGL), internalFormat must match format and options allowed are: GL_LUMINANCE, GL_RGB, GL_RGBA - case RL_PIXELFORMAT_UNCOMPRESSED_GRAYSCALE: *glInternalFormat = GL_LUMINANCE; *glFormat = GL_LUMINANCE; *glType = GL_UNSIGNED_BYTE; break; - case RL_PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA: *glInternalFormat = GL_LUMINANCE_ALPHA; *glFormat = GL_LUMINANCE_ALPHA; *glType = GL_UNSIGNED_BYTE; break; - case RL_PIXELFORMAT_UNCOMPRESSED_R5G6B5: *glInternalFormat = GL_RGB; *glFormat = GL_RGB; *glType = GL_UNSIGNED_SHORT_5_6_5; break; - case RL_PIXELFORMAT_UNCOMPRESSED_R8G8B8: *glInternalFormat = GL_RGB; *glFormat = GL_RGB; *glType = GL_UNSIGNED_BYTE; break; - case RL_PIXELFORMAT_UNCOMPRESSED_R5G5B5A1: *glInternalFormat = GL_RGBA; *glFormat = GL_RGBA; *glType = GL_UNSIGNED_SHORT_5_5_5_1; break; - case RL_PIXELFORMAT_UNCOMPRESSED_R4G4B4A4: *glInternalFormat = GL_RGBA; *glFormat = GL_RGBA; *glType = GL_UNSIGNED_SHORT_4_4_4_4; break; - case RL_PIXELFORMAT_UNCOMPRESSED_R8G8B8A8: *glInternalFormat = GL_RGBA; *glFormat = GL_RGBA; *glType = GL_UNSIGNED_BYTE; break; - #if !defined(GRAPHICS_API_OPENGL_11) - #if defined(GRAPHICS_API_OPENGL_ES3) - case RL_PIXELFORMAT_UNCOMPRESSED_R32: if (RLGL.ExtSupported.texFloat32) *glInternalFormat = GL_R32F_EXT; *glFormat = GL_RED_EXT; *glType = GL_FLOAT; break; - case RL_PIXELFORMAT_UNCOMPRESSED_R32G32B32: if (RLGL.ExtSupported.texFloat32) *glInternalFormat = GL_RGB32F_EXT; *glFormat = GL_RGB; *glType = GL_FLOAT; break; - case RL_PIXELFORMAT_UNCOMPRESSED_R32G32B32A32: if (RLGL.ExtSupported.texFloat32) *glInternalFormat = GL_RGBA32F_EXT; *glFormat = GL_RGBA; *glType = GL_FLOAT; break; - case RL_PIXELFORMAT_UNCOMPRESSED_R16: if (RLGL.ExtSupported.texFloat16) *glInternalFormat = GL_R16F_EXT; *glFormat = GL_RED_EXT; *glType = GL_HALF_FLOAT; break; - case RL_PIXELFORMAT_UNCOMPRESSED_R16G16B16: if (RLGL.ExtSupported.texFloat16) *glInternalFormat = GL_RGB16F_EXT; *glFormat = GL_RGB; *glType = GL_HALF_FLOAT; break; - case RL_PIXELFORMAT_UNCOMPRESSED_R16G16B16A16: if (RLGL.ExtSupported.texFloat16) *glInternalFormat = GL_RGBA16F_EXT; *glFormat = GL_RGBA; *glType = GL_HALF_FLOAT; break; - #else - case RL_PIXELFORMAT_UNCOMPRESSED_R32: if (RLGL.ExtSupported.texFloat32) *glInternalFormat = GL_LUMINANCE; *glFormat = GL_LUMINANCE; *glType = GL_FLOAT; break; // NOTE: Requires extension OES_texture_float - case RL_PIXELFORMAT_UNCOMPRESSED_R32G32B32: if (RLGL.ExtSupported.texFloat32) *glInternalFormat = GL_RGB; *glFormat = GL_RGB; *glType = GL_FLOAT; break; // NOTE: Requires extension OES_texture_float - case RL_PIXELFORMAT_UNCOMPRESSED_R32G32B32A32: if (RLGL.ExtSupported.texFloat32) *glInternalFormat = GL_RGBA; *glFormat = GL_RGBA; *glType = GL_FLOAT; break; // NOTE: Requires extension OES_texture_float - #if defined(GRAPHICS_API_OPENGL_21) - case RL_PIXELFORMAT_UNCOMPRESSED_R16: if (RLGL.ExtSupported.texFloat16) *glInternalFormat = GL_LUMINANCE; *glFormat = GL_LUMINANCE; *glType = GL_HALF_FLOAT_ARB; break; - case RL_PIXELFORMAT_UNCOMPRESSED_R16G16B16: if (RLGL.ExtSupported.texFloat16) *glInternalFormat = GL_RGB; *glFormat = GL_RGB; *glType = GL_HALF_FLOAT_ARB; break; - case RL_PIXELFORMAT_UNCOMPRESSED_R16G16B16A16: if (RLGL.ExtSupported.texFloat16) *glInternalFormat = GL_RGBA; *glFormat = GL_RGBA; *glType = GL_HALF_FLOAT_ARB; break; - #else // defined(GRAPHICS_API_OPENGL_ES2) - case RL_PIXELFORMAT_UNCOMPRESSED_R16: if (RLGL.ExtSupported.texFloat16) *glInternalFormat = GL_LUMINANCE; *glFormat = GL_LUMINANCE; *glType = GL_HALF_FLOAT_OES; break; // NOTE: Requires extension OES_texture_half_float - case RL_PIXELFORMAT_UNCOMPRESSED_R16G16B16: if (RLGL.ExtSupported.texFloat16) *glInternalFormat = GL_RGB; *glFormat = GL_RGB; *glType = GL_HALF_FLOAT_OES; break; // NOTE: Requires extension OES_texture_half_float - case RL_PIXELFORMAT_UNCOMPRESSED_R16G16B16A16: if (RLGL.ExtSupported.texFloat16) *glInternalFormat = GL_RGBA; *glFormat = GL_RGBA; *glType = GL_HALF_FLOAT_OES; break; // NOTE: Requires extension OES_texture_half_float - #endif - #endif - #endif - #elif defined(GRAPHICS_API_OPENGL_33) - case RL_PIXELFORMAT_UNCOMPRESSED_GRAYSCALE: *glInternalFormat = GL_R8; *glFormat = GL_RED; *glType = GL_UNSIGNED_BYTE; break; - case RL_PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA: *glInternalFormat = GL_RG8; *glFormat = GL_RG; *glType = GL_UNSIGNED_BYTE; break; - case RL_PIXELFORMAT_UNCOMPRESSED_R5G6B5: *glInternalFormat = GL_RGB565; *glFormat = GL_RGB; *glType = GL_UNSIGNED_SHORT_5_6_5; break; - case RL_PIXELFORMAT_UNCOMPRESSED_R8G8B8: *glInternalFormat = GL_RGB8; *glFormat = GL_RGB; *glType = GL_UNSIGNED_BYTE; break; - case RL_PIXELFORMAT_UNCOMPRESSED_R5G5B5A1: *glInternalFormat = GL_RGB5_A1; *glFormat = GL_RGBA; *glType = GL_UNSIGNED_SHORT_5_5_5_1; break; - case RL_PIXELFORMAT_UNCOMPRESSED_R4G4B4A4: *glInternalFormat = GL_RGBA4; *glFormat = GL_RGBA; *glType = GL_UNSIGNED_SHORT_4_4_4_4; break; - case RL_PIXELFORMAT_UNCOMPRESSED_R8G8B8A8: *glInternalFormat = GL_RGBA8; *glFormat = GL_RGBA; *glType = GL_UNSIGNED_BYTE; break; - case RL_PIXELFORMAT_UNCOMPRESSED_R32: if (RLGL.ExtSupported.texFloat32) *glInternalFormat = GL_R32F; *glFormat = GL_RED; *glType = GL_FLOAT; break; - case RL_PIXELFORMAT_UNCOMPRESSED_R32G32B32: if (RLGL.ExtSupported.texFloat32) *glInternalFormat = GL_RGB32F; *glFormat = GL_RGB; *glType = GL_FLOAT; break; - case RL_PIXELFORMAT_UNCOMPRESSED_R32G32B32A32: if (RLGL.ExtSupported.texFloat32) *glInternalFormat = GL_RGBA32F; *glFormat = GL_RGBA; *glType = GL_FLOAT; break; - case RL_PIXELFORMAT_UNCOMPRESSED_R16: if (RLGL.ExtSupported.texFloat16) *glInternalFormat = GL_R16F; *glFormat = GL_RED; *glType = GL_HALF_FLOAT; break; - case RL_PIXELFORMAT_UNCOMPRESSED_R16G16B16: if (RLGL.ExtSupported.texFloat16) *glInternalFormat = GL_RGB16F; *glFormat = GL_RGB; *glType = GL_HALF_FLOAT; break; - case RL_PIXELFORMAT_UNCOMPRESSED_R16G16B16A16: if (RLGL.ExtSupported.texFloat16) *glInternalFormat = GL_RGBA16F; *glFormat = GL_RGBA; *glType = GL_HALF_FLOAT; break; - #endif - #if !defined(GRAPHICS_API_OPENGL_11) - case RL_PIXELFORMAT_COMPRESSED_DXT1_RGB: if (RLGL.ExtSupported.texCompDXT) *glInternalFormat = GL_COMPRESSED_RGB_S3TC_DXT1_EXT; break; - case RL_PIXELFORMAT_COMPRESSED_DXT1_RGBA: if (RLGL.ExtSupported.texCompDXT) *glInternalFormat = GL_COMPRESSED_RGBA_S3TC_DXT1_EXT; break; - case RL_PIXELFORMAT_COMPRESSED_DXT3_RGBA: if (RLGL.ExtSupported.texCompDXT) *glInternalFormat = GL_COMPRESSED_RGBA_S3TC_DXT3_EXT; break; - case RL_PIXELFORMAT_COMPRESSED_DXT5_RGBA: if (RLGL.ExtSupported.texCompDXT) *glInternalFormat = GL_COMPRESSED_RGBA_S3TC_DXT5_EXT; break; - case RL_PIXELFORMAT_COMPRESSED_ETC1_RGB: if (RLGL.ExtSupported.texCompETC1) *glInternalFormat = GL_ETC1_RGB8_OES; break; // NOTE: Requires OpenGL ES 2.0 or OpenGL 4.3 - case RL_PIXELFORMAT_COMPRESSED_ETC2_RGB: if (RLGL.ExtSupported.texCompETC2) *glInternalFormat = GL_COMPRESSED_RGB8_ETC2; break; // NOTE: Requires OpenGL ES 3.0 or OpenGL 4.3 - case RL_PIXELFORMAT_COMPRESSED_ETC2_EAC_RGBA: if (RLGL.ExtSupported.texCompETC2) *glInternalFormat = GL_COMPRESSED_RGBA8_ETC2_EAC; break; // NOTE: Requires OpenGL ES 3.0 or OpenGL 4.3 - case RL_PIXELFORMAT_COMPRESSED_PVRT_RGB: if (RLGL.ExtSupported.texCompPVRT) *glInternalFormat = GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG; break; // NOTE: Requires PowerVR GPU - case RL_PIXELFORMAT_COMPRESSED_PVRT_RGBA: if (RLGL.ExtSupported.texCompPVRT) *glInternalFormat = GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG; break; // NOTE: Requires PowerVR GPU - case RL_PIXELFORMAT_COMPRESSED_ASTC_4x4_RGBA: if (RLGL.ExtSupported.texCompASTC) *glInternalFormat = GL_COMPRESSED_RGBA_ASTC_4x4_KHR; break; // NOTE: Requires OpenGL ES 3.1 or OpenGL 4.3 - case RL_PIXELFORMAT_COMPRESSED_ASTC_8x8_RGBA: if (RLGL.ExtSupported.texCompASTC) *glInternalFormat = GL_COMPRESSED_RGBA_ASTC_8x8_KHR; break; // NOTE: Requires OpenGL ES 3.1 or OpenGL 4.3 - #endif - default: TRACELOG(RL_LOG_WARNING, "TEXTURE: Current format not supported (%i)", format); break; - } -} - -// Unload texture from GPU memory -void rlUnloadTexture(unsigned int id) -{ - glDeleteTextures(1, &id); -} - -// Generate mipmap data for selected texture -// NOTE: Only supports GPU mipmap generation -void rlGenTextureMipmaps(unsigned int id, int width, int height, int format, int *mipmaps) -{ -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - glBindTexture(GL_TEXTURE_2D, id); - - // Check if texture is power-of-two (POT) - bool texIsPOT = false; - - if (((width > 0) && ((width & (width - 1)) == 0)) && - ((height > 0) && ((height & (height - 1)) == 0))) texIsPOT = true; - - if ((texIsPOT) || (RLGL.ExtSupported.texNPOT)) - { - //glHint(GL_GENERATE_MIPMAP_HINT, GL_DONT_CARE); // Hint for mipmaps generation algorithm: GL_FASTEST, GL_NICEST, GL_DONT_CARE - glGenerateMipmap(GL_TEXTURE_2D); // Generate mipmaps automatically - - #define MIN(a,b) (((a)<(b))? (a):(b)) - #define MAX(a,b) (((a)>(b))? (a):(b)) - - *mipmaps = 1 + (int)floor(log(MAX(width, height))/log(2)); - TRACELOG(RL_LOG_INFO, "TEXTURE: [ID %i] Mipmaps generated automatically, total: %i", id, *mipmaps); - } - else TRACELOG(RL_LOG_WARNING, "TEXTURE: [ID %i] Failed to generate mipmaps", id); - - glBindTexture(GL_TEXTURE_2D, 0); -#else - TRACELOG(RL_LOG_WARNING, "TEXTURE: [ID %i] GPU mipmap generation not supported", id); -#endif -} - -// Read texture pixel data -void *rlReadTexturePixels(unsigned int id, int width, int height, int format) -{ - void *pixels = NULL; - -#if defined(GRAPHICS_API_OPENGL_11) || defined(GRAPHICS_API_OPENGL_33) - glBindTexture(GL_TEXTURE_2D, id); - - // NOTE: Using texture id, we can retrieve some texture info (but not on OpenGL ES 2.0) - // Possible texture info: GL_TEXTURE_RED_SIZE, GL_TEXTURE_GREEN_SIZE, GL_TEXTURE_BLUE_SIZE, GL_TEXTURE_ALPHA_SIZE - //int width, height, format; - //glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &width); - //glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &height); - //glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_INTERNAL_FORMAT, &format); - - // NOTE: Each row written to or read from by OpenGL pixel operations like glGetTexImage are aligned to a 4 byte boundary by default, which may add some padding - // Use glPixelStorei to modify padding with the GL_[UN]PACK_ALIGNMENT setting - // GL_PACK_ALIGNMENT affects operations that read from OpenGL memory (glReadPixels, glGetTexImage, etc.) - // GL_UNPACK_ALIGNMENT affects operations that write to OpenGL memory (glTexImage, etc.) - glPixelStorei(GL_PACK_ALIGNMENT, 1); - - unsigned int glInternalFormat, glFormat, glType; - rlGetGlTextureFormats(format, &glInternalFormat, &glFormat, &glType); - unsigned int size = rlGetPixelDataSize(width, height, format); - - if ((glInternalFormat != 0) && (format < RL_PIXELFORMAT_COMPRESSED_DXT1_RGB)) - { - pixels = RL_MALLOC(size); - glGetTexImage(GL_TEXTURE_2D, 0, glFormat, glType, pixels); - } - else TRACELOG(RL_LOG_WARNING, "TEXTURE: [ID %i] Data retrieval not suported for pixel format (%i)", id, format); - - glBindTexture(GL_TEXTURE_2D, 0); -#endif - -#if defined(GRAPHICS_API_OPENGL_ES2) - // glGetTexImage() is not available on OpenGL ES 2.0 - // Texture width and height are required on OpenGL ES 2.0, there is no way to get it from texture id - // Two possible Options: - // 1 - Bind texture to color fbo attachment and glReadPixels() - // 2 - Create an fbo, activate it, render quad with texture, glReadPixels() - // We are using Option 1, just need to care for texture format on retrieval - // NOTE: This behaviour could be conditioned by graphic driver... - unsigned int fboId = rlLoadFramebuffer(); - - glBindFramebuffer(GL_FRAMEBUFFER, fboId); - glBindTexture(GL_TEXTURE_2D, 0); - - // Attach our texture to FBO - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, id, 0); - - // We read data as RGBA because FBO texture is configured as RGBA, despite binding another texture format - pixels = (unsigned char *)RL_MALLOC(rlGetPixelDataSize(width, height, RL_PIXELFORMAT_UNCOMPRESSED_R8G8B8A8)); - glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, pixels); - - glBindFramebuffer(GL_FRAMEBUFFER, 0); - - // Clean up temporal fbo - rlUnloadFramebuffer(fboId); -#endif - - return pixels; -} - -// Read screen pixel data (color buffer) -unsigned char *rlReadScreenPixels(int width, int height) -{ - unsigned char *screenData = (unsigned char *)RL_CALLOC(width*height*4, sizeof(unsigned char)); - - // NOTE 1: glReadPixels returns image flipped vertically -> (0,0) is the bottom left corner of the framebuffer - // NOTE 2: We are getting alpha channel! Be careful, it can be transparent if not cleared properly! - glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, screenData); - - // Flip image vertically! - unsigned char *imgData = (unsigned char *)RL_MALLOC(width*height*4*sizeof(unsigned char)); - - for (int y = height - 1; y >= 0; y--) - { - for (int x = 0; x < (width*4); x++) - { - imgData[((height - 1) - y)*width*4 + x] = screenData[(y*width*4) + x]; // Flip line - - // Set alpha component value to 255 (no trasparent image retrieval) - // NOTE: Alpha value has already been applied to RGB in framebuffer, we don't need it! - if (((x + 1)%4) == 0) imgData[((height - 1) - y)*width*4 + x] = 255; - } - } - - RL_FREE(screenData); - - return imgData; // NOTE: image data should be freed -} - -// Framebuffer management (fbo) -//----------------------------------------------------------------------------------------- -// Load a framebuffer to be used for rendering -// NOTE: No textures attached -unsigned int rlLoadFramebuffer(void) -{ - unsigned int fboId = 0; - -#if (defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)) && defined(RLGL_RENDER_TEXTURES_HINT) - glGenFramebuffers(1, &fboId); // Create the framebuffer object - glBindFramebuffer(GL_FRAMEBUFFER, 0); // Unbind any framebuffer -#endif - - return fboId; -} - -// Attach color buffer texture to an fbo (unloads previous attachment) -// NOTE: Attach type: 0-Color, 1-Depth renderbuffer, 2-Depth texture -void rlFramebufferAttach(unsigned int fboId, unsigned int texId, int attachType, int texType, int mipLevel) -{ -#if (defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)) && defined(RLGL_RENDER_TEXTURES_HINT) - glBindFramebuffer(GL_FRAMEBUFFER, fboId); - - switch (attachType) - { - case RL_ATTACHMENT_COLOR_CHANNEL0: - case RL_ATTACHMENT_COLOR_CHANNEL1: - case RL_ATTACHMENT_COLOR_CHANNEL2: - case RL_ATTACHMENT_COLOR_CHANNEL3: - case RL_ATTACHMENT_COLOR_CHANNEL4: - case RL_ATTACHMENT_COLOR_CHANNEL5: - case RL_ATTACHMENT_COLOR_CHANNEL6: - case RL_ATTACHMENT_COLOR_CHANNEL7: - { - if (texType == RL_ATTACHMENT_TEXTURE2D) glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + attachType, GL_TEXTURE_2D, texId, mipLevel); - else if (texType == RL_ATTACHMENT_RENDERBUFFER) glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + attachType, GL_RENDERBUFFER, texId); - else if (texType >= RL_ATTACHMENT_CUBEMAP_POSITIVE_X) glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + attachType, GL_TEXTURE_CUBE_MAP_POSITIVE_X + texType, texId, mipLevel); - - } break; - case RL_ATTACHMENT_DEPTH: - { - if (texType == RL_ATTACHMENT_TEXTURE2D) glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, texId, mipLevel); - else if (texType == RL_ATTACHMENT_RENDERBUFFER) glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, texId); - - } break; - case RL_ATTACHMENT_STENCIL: - { - if (texType == RL_ATTACHMENT_TEXTURE2D) glFramebufferTexture2D(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_TEXTURE_2D, texId, mipLevel); - else if (texType == RL_ATTACHMENT_RENDERBUFFER) glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, texId); - - } break; - default: break; - } - - glBindFramebuffer(GL_FRAMEBUFFER, 0); -#endif -} - -// Verify render texture is complete -bool rlFramebufferComplete(unsigned int id) -{ - bool result = false; - -#if (defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)) && defined(RLGL_RENDER_TEXTURES_HINT) - glBindFramebuffer(GL_FRAMEBUFFER, id); - - GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER); - - if (status != GL_FRAMEBUFFER_COMPLETE) - { - switch (status) - { - case GL_FRAMEBUFFER_UNSUPPORTED: TRACELOG(RL_LOG_WARNING, "FBO: [ID %i] Framebuffer is unsupported", id); break; - case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT: TRACELOG(RL_LOG_WARNING, "FBO: [ID %i] Framebuffer has incomplete attachment", id); break; -#if defined(GRAPHICS_API_OPENGL_ES2) - case GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS: TRACELOG(RL_LOG_WARNING, "FBO: [ID %i] Framebuffer has incomplete dimensions", id); break; -#endif - case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT: TRACELOG(RL_LOG_WARNING, "FBO: [ID %i] Framebuffer has a missing attachment", id); break; - default: break; - } - } - - glBindFramebuffer(GL_FRAMEBUFFER, 0); - - result = (status == GL_FRAMEBUFFER_COMPLETE); -#endif - - return result; -} - -// Unload framebuffer from GPU memory -// NOTE: All attached textures/cubemaps/renderbuffers are also deleted -void rlUnloadFramebuffer(unsigned int id) -{ -#if (defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)) && defined(RLGL_RENDER_TEXTURES_HINT) - // Query depth attachment to automatically delete texture/renderbuffer - int depthType = 0, depthId = 0; - glBindFramebuffer(GL_FRAMEBUFFER, id); // Bind framebuffer to query depth texture type - glGetFramebufferAttachmentParameteriv(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE, &depthType); - - // TODO: Review warning retrieving object name in WebGL - // WARNING: WebGL: INVALID_ENUM: getFramebufferAttachmentParameter: invalid parameter name - // https://registry.khronos.org/webgl/specs/latest/1.0/ - glGetFramebufferAttachmentParameteriv(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME, &depthId); - - unsigned int depthIdU = (unsigned int)depthId; - if (depthType == GL_RENDERBUFFER) glDeleteRenderbuffers(1, &depthIdU); - else if (depthType == GL_TEXTURE) glDeleteTextures(1, &depthIdU); - - // NOTE: If a texture object is deleted while its image is attached to the *currently bound* framebuffer, - // the texture image is automatically detached from the currently bound framebuffer - - glBindFramebuffer(GL_FRAMEBUFFER, 0); - glDeleteFramebuffers(1, &id); - - TRACELOG(RL_LOG_INFO, "FBO: [ID %i] Unloaded framebuffer from VRAM (GPU)", id); -#endif -} - -// Vertex data management -//----------------------------------------------------------------------------------------- -// Load a new attributes buffer -unsigned int rlLoadVertexBuffer(const void *buffer, int size, bool dynamic) -{ - unsigned int id = 0; - -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - glGenBuffers(1, &id); - glBindBuffer(GL_ARRAY_BUFFER, id); - glBufferData(GL_ARRAY_BUFFER, size, buffer, dynamic? GL_DYNAMIC_DRAW : GL_STATIC_DRAW); -#endif - - return id; -} - -// Load a new attributes element buffer -unsigned int rlLoadVertexBufferElement(const void *buffer, int size, bool dynamic) -{ - unsigned int id = 0; - -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - glGenBuffers(1, &id); - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, id); - glBufferData(GL_ELEMENT_ARRAY_BUFFER, size, buffer, dynamic? GL_DYNAMIC_DRAW : GL_STATIC_DRAW); -#endif - - return id; -} - -// Enable vertex buffer (VBO) -void rlEnableVertexBuffer(unsigned int id) -{ -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - glBindBuffer(GL_ARRAY_BUFFER, id); -#endif -} - -// Disable vertex buffer (VBO) -void rlDisableVertexBuffer(void) -{ -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - glBindBuffer(GL_ARRAY_BUFFER, 0); -#endif -} - -// Enable vertex buffer element (VBO element) -void rlEnableVertexBufferElement(unsigned int id) -{ -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, id); -#endif -} - -// Disable vertex buffer element (VBO element) -void rlDisableVertexBufferElement(void) -{ -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); -#endif -} - -// Update vertex buffer with new data -// NOTE: dataSize and offset must be provided in bytes -void rlUpdateVertexBuffer(unsigned int id, const void *data, int dataSize, int offset) -{ -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - glBindBuffer(GL_ARRAY_BUFFER, id); - glBufferSubData(GL_ARRAY_BUFFER, offset, dataSize, data); -#endif -} - -// Update vertex buffer elements with new data -// NOTE: dataSize and offset must be provided in bytes -void rlUpdateVertexBufferElements(unsigned int id, const void *data, int dataSize, int offset) -{ -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, id); - glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, offset, dataSize, data); -#endif -} - -// Enable vertex array object (VAO) -bool rlEnableVertexArray(unsigned int vaoId) -{ - bool result = false; -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - if (RLGL.ExtSupported.vao) - { - glBindVertexArray(vaoId); - result = true; - } -#endif - return result; -} - -// Disable vertex array object (VAO) -void rlDisableVertexArray(void) -{ -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - if (RLGL.ExtSupported.vao) glBindVertexArray(0); -#endif -} - -// Enable vertex attribute index -void rlEnableVertexAttribute(unsigned int index) -{ -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - glEnableVertexAttribArray(index); -#endif -} - -// Disable vertex attribute index -void rlDisableVertexAttribute(unsigned int index) -{ -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - glDisableVertexAttribArray(index); -#endif -} - -// Draw vertex array -void rlDrawVertexArray(int offset, int count) -{ - glDrawArrays(GL_TRIANGLES, offset, count); -} - -// Draw vertex array elements -void rlDrawVertexArrayElements(int offset, int count, const void *buffer) -{ - // NOTE: Added pointer math separately from function to avoid UBSAN complaining - unsigned short *bufferPtr = (unsigned short *)buffer; - if (offset > 0) bufferPtr += offset; - - glDrawElements(GL_TRIANGLES, count, GL_UNSIGNED_SHORT, (const unsigned short *)bufferPtr); -} - -// Draw vertex array instanced -void rlDrawVertexArrayInstanced(int offset, int count, int instances) -{ -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - glDrawArraysInstanced(GL_TRIANGLES, 0, count, instances); -#endif -} - -// Draw vertex array elements instanced -void rlDrawVertexArrayElementsInstanced(int offset, int count, const void *buffer, int instances) -{ -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - // NOTE: Added pointer math separately from function to avoid UBSAN complaining - unsigned short *bufferPtr = (unsigned short *)buffer; - if (offset > 0) bufferPtr += offset; - - glDrawElementsInstanced(GL_TRIANGLES, count, GL_UNSIGNED_SHORT, (const unsigned short *)bufferPtr, instances); -#endif -} - -#if defined(GRAPHICS_API_OPENGL_11) -// Enable vertex state pointer -void rlEnableStatePointer(int vertexAttribType, void *buffer) -{ - if (buffer != NULL) glEnableClientState(vertexAttribType); - switch (vertexAttribType) - { - case GL_VERTEX_ARRAY: glVertexPointer(3, GL_FLOAT, 0, buffer); break; - case GL_TEXTURE_COORD_ARRAY: glTexCoordPointer(2, GL_FLOAT, 0, buffer); break; - case GL_NORMAL_ARRAY: if (buffer != NULL) glNormalPointer(GL_FLOAT, 0, buffer); break; - case GL_COLOR_ARRAY: if (buffer != NULL) glColorPointer(4, GL_UNSIGNED_BYTE, 0, buffer); break; - //case GL_INDEX_ARRAY: if (buffer != NULL) glIndexPointer(GL_SHORT, 0, buffer); break; // Indexed colors - default: break; - } -} - -// Disable vertex state pointer -void rlDisableStatePointer(int vertexAttribType) -{ - glDisableClientState(vertexAttribType); -} -#endif - -// Load vertex array object (VAO) -unsigned int rlLoadVertexArray(void) -{ - unsigned int vaoId = 0; -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - if (RLGL.ExtSupported.vao) - { - glGenVertexArrays(1, &vaoId); - } -#endif - return vaoId; -} - -// Set vertex attribute -void rlSetVertexAttribute(unsigned int index, int compSize, int type, bool normalized, int stride, int offset) -{ -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - // NOTE: Data type could be: GL_BYTE, GL_UNSIGNED_BYTE, GL_SHORT, GL_UNSIGNED_SHORT, GL_INT, GL_UNSIGNED_INT - // Additional types (depends on OpenGL version or extensions): - // - GL_HALF_FLOAT, GL_FLOAT, GL_DOUBLE, GL_FIXED, - // - GL_INT_2_10_10_10_REV, GL_UNSIGNED_INT_2_10_10_10_REV, GL_UNSIGNED_INT_10F_11F_11F_REV - - size_t offsetNative = offset; - glVertexAttribPointer(index, compSize, type, normalized, stride, (void *)offsetNative); -#endif -} - -// Set vertex attribute divisor -void rlSetVertexAttributeDivisor(unsigned int index, int divisor) -{ -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - glVertexAttribDivisor(index, divisor); -#endif -} - -// Unload vertex array object (VAO) -void rlUnloadVertexArray(unsigned int vaoId) -{ -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - if (RLGL.ExtSupported.vao) - { - glBindVertexArray(0); - glDeleteVertexArrays(1, &vaoId); - TRACELOG(RL_LOG_INFO, "VAO: [ID %i] Unloaded vertex array data from VRAM (GPU)", vaoId); - } -#endif -} - -// Unload vertex buffer (VBO) -void rlUnloadVertexBuffer(unsigned int vboId) -{ -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - glDeleteBuffers(1, &vboId); - //TRACELOG(RL_LOG_INFO, "VBO: Unloaded vertex data from VRAM (GPU)"); -#endif -} - -// Shaders management -//----------------------------------------------------------------------------------------------- -// Load shader from code strings -// NOTE: If shader string is NULL, using default vertex/fragment shaders -unsigned int rlLoadShaderCode(const char *vsCode, const char *fsCode) -{ - unsigned int id = 0; - -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - unsigned int vertexShaderId = 0; - unsigned int fragmentShaderId = 0; - - // Compile vertex shader (if provided) - // NOTE: If not vertex shader is provided, use default one - if (vsCode != NULL) vertexShaderId = rlCompileShader(vsCode, GL_VERTEX_SHADER); - else vertexShaderId = RLGL.State.defaultVShaderId; - - // Compile fragment shader (if provided) - // NOTE: If not vertex shader is provided, use default one - if (fsCode != NULL) fragmentShaderId = rlCompileShader(fsCode, GL_FRAGMENT_SHADER); - else fragmentShaderId = RLGL.State.defaultFShaderId; - - // In case vertex and fragment shader are the default ones, no need to recompile, we can just assign the default shader program id - if ((vertexShaderId == RLGL.State.defaultVShaderId) && (fragmentShaderId == RLGL.State.defaultFShaderId)) id = RLGL.State.defaultShaderId; - else if ((vertexShaderId > 0) && (fragmentShaderId > 0)) - { - // One of or both shader are new, we need to compile a new shader program - id = rlLoadShaderProgram(vertexShaderId, fragmentShaderId); - - // We can detach and delete vertex/fragment shaders (if not default ones) - // NOTE: We detach shader before deletion to make sure memory is freed - if (vertexShaderId != RLGL.State.defaultVShaderId) - { - // WARNING: Shader program linkage could fail and returned id is 0 - if (id > 0) glDetachShader(id, vertexShaderId); - glDeleteShader(vertexShaderId); - } - if (fragmentShaderId != RLGL.State.defaultFShaderId) - { - // WARNING: Shader program linkage could fail and returned id is 0 - if (id > 0) glDetachShader(id, fragmentShaderId); - glDeleteShader(fragmentShaderId); - } - - // In case shader program loading failed, we assign default shader - if (id == 0) - { - // In case shader loading fails, we return the default shader - TRACELOG(RL_LOG_WARNING, "SHADER: Failed to load custom shader code, using default shader"); - id = RLGL.State.defaultShaderId; - } - /* - else - { - // Get available shader uniforms - // NOTE: This information is useful for debug... - int uniformCount = -1; - glGetProgramiv(id, GL_ACTIVE_UNIFORMS, &uniformCount); - - for (int i = 0; i < uniformCount; i++) - { - int namelen = -1; - int num = -1; - char name[256] = { 0 }; // Assume no variable names longer than 256 - GLenum type = GL_ZERO; - - // Get the name of the uniforms - glGetActiveUniform(id, i, sizeof(name) - 1, &namelen, &num, &type, name); - - name[namelen] = 0; - TRACELOGD("SHADER: [ID %i] Active uniform (%s) set at location: %i", id, name, glGetUniformLocation(id, name)); - } - } - */ - } -#endif - - return id; -} - -// Compile custom shader and return shader id -unsigned int rlCompileShader(const char *shaderCode, int type) -{ - unsigned int shader = 0; - -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - shader = glCreateShader(type); - glShaderSource(shader, 1, &shaderCode, NULL); - - GLint success = 0; - glCompileShader(shader); - glGetShaderiv(shader, GL_COMPILE_STATUS, &success); - - if (success == GL_FALSE) - { - switch (type) - { - case GL_VERTEX_SHADER: TRACELOG(RL_LOG_WARNING, "SHADER: [ID %i] Failed to compile vertex shader code", shader); break; - case GL_FRAGMENT_SHADER: TRACELOG(RL_LOG_WARNING, "SHADER: [ID %i] Failed to compile fragment shader code", shader); break; - //case GL_GEOMETRY_SHADER: - #if defined(GRAPHICS_API_OPENGL_43) - case GL_COMPUTE_SHADER: TRACELOG(RL_LOG_WARNING, "SHADER: [ID %i] Failed to compile compute shader code", shader); break; - #elif defined(GRAPHICS_API_OPENGL_33) - case GL_COMPUTE_SHADER: TRACELOG(RL_LOG_WARNING, "SHADER: Compute shaders not enabled. Define GRAPHICS_API_OPENGL_43", shader); break; - #endif - default: break; - } - - int maxLength = 0; - glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &maxLength); - - if (maxLength > 0) - { - int length = 0; - char *log = (char *)RL_CALLOC(maxLength, sizeof(char)); - glGetShaderInfoLog(shader, maxLength, &length, log); - TRACELOG(RL_LOG_WARNING, "SHADER: [ID %i] Compile error: %s", shader, log); - RL_FREE(log); - } - - shader = 0; - } - else - { - switch (type) - { - case GL_VERTEX_SHADER: TRACELOG(RL_LOG_INFO, "SHADER: [ID %i] Vertex shader compiled successfully", shader); break; - case GL_FRAGMENT_SHADER: TRACELOG(RL_LOG_INFO, "SHADER: [ID %i] Fragment shader compiled successfully", shader); break; - //case GL_GEOMETRY_SHADER: - #if defined(GRAPHICS_API_OPENGL_43) - case GL_COMPUTE_SHADER: TRACELOG(RL_LOG_INFO, "SHADER: [ID %i] Compute shader compiled successfully", shader); break; - #elif defined(GRAPHICS_API_OPENGL_33) - case GL_COMPUTE_SHADER: TRACELOG(RL_LOG_WARNING, "SHADER: Compute shaders not enabled. Define GRAPHICS_API_OPENGL_43", shader); break; - #endif - default: break; - } - } -#endif - - return shader; -} - -// Load custom shader strings and return program id -unsigned int rlLoadShaderProgram(unsigned int vShaderId, unsigned int fShaderId) -{ - unsigned int program = 0; - -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - GLint success = 0; - program = glCreateProgram(); - - glAttachShader(program, vShaderId); - glAttachShader(program, fShaderId); - - // NOTE: Default attribute shader locations must be Bound before linking - glBindAttribLocation(program, RL_DEFAULT_SHADER_ATTRIB_LOCATION_POSITION, RL_DEFAULT_SHADER_ATTRIB_NAME_POSITION); - glBindAttribLocation(program, RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD, RL_DEFAULT_SHADER_ATTRIB_NAME_TEXCOORD); - glBindAttribLocation(program, RL_DEFAULT_SHADER_ATTRIB_LOCATION_NORMAL, RL_DEFAULT_SHADER_ATTRIB_NAME_NORMAL); - glBindAttribLocation(program, RL_DEFAULT_SHADER_ATTRIB_LOCATION_COLOR, RL_DEFAULT_SHADER_ATTRIB_NAME_COLOR); - glBindAttribLocation(program, RL_DEFAULT_SHADER_ATTRIB_LOCATION_TANGENT, RL_DEFAULT_SHADER_ATTRIB_NAME_TANGENT); - glBindAttribLocation(program, RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD2, RL_DEFAULT_SHADER_ATTRIB_NAME_TEXCOORD2); - -#ifdef RL_SUPPORT_MESH_GPU_SKINNING - glBindAttribLocation(program, RL_DEFAULT_SHADER_ATTRIB_LOCATION_BONEIDS, RL_DEFAULT_SHADER_ATTRIB_NAME_BONEIDS); - glBindAttribLocation(program, RL_DEFAULT_SHADER_ATTRIB_LOCATION_BONEWEIGHTS, RL_DEFAULT_SHADER_ATTRIB_NAME_BONEWEIGHTS); -#endif - - // NOTE: If some attrib name is no found on the shader, it locations becomes -1 - - glLinkProgram(program); - - // NOTE: All uniform variables are intitialised to 0 when a program links - - glGetProgramiv(program, GL_LINK_STATUS, &success); - - if (success == GL_FALSE) - { - TRACELOG(RL_LOG_WARNING, "SHADER: [ID %i] Failed to link shader program", program); - - int maxLength = 0; - glGetProgramiv(program, GL_INFO_LOG_LENGTH, &maxLength); - - if (maxLength > 0) - { - int length = 0; - char *log = (char *)RL_CALLOC(maxLength, sizeof(char)); - glGetProgramInfoLog(program, maxLength, &length, log); - TRACELOG(RL_LOG_WARNING, "SHADER: [ID %i] Link error: %s", program, log); - RL_FREE(log); - } - - glDeleteProgram(program); - - program = 0; - } - else - { - // Get the size of compiled shader program (not available on OpenGL ES 2.0) - // NOTE: If GL_LINK_STATUS is GL_FALSE, program binary length is zero - //GLint binarySize = 0; - //glGetProgramiv(id, GL_PROGRAM_BINARY_LENGTH, &binarySize); - - TRACELOG(RL_LOG_INFO, "SHADER: [ID %i] Program shader loaded successfully", program); - } -#endif - return program; -} - -// Unload shader program -void rlUnloadShaderProgram(unsigned int id) -{ -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - glDeleteProgram(id); - - TRACELOG(RL_LOG_INFO, "SHADER: [ID %i] Unloaded shader program data from VRAM (GPU)", id); -#endif -} - -// Get shader location uniform -int rlGetLocationUniform(unsigned int shaderId, const char *uniformName) -{ - int location = -1; -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - location = glGetUniformLocation(shaderId, uniformName); - - //if (location == -1) TRACELOG(RL_LOG_WARNING, "SHADER: [ID %i] Failed to find shader uniform: %s", shaderId, uniformName); - //else TRACELOG(RL_LOG_INFO, "SHADER: [ID %i] Shader uniform (%s) set at location: %i", shaderId, uniformName, location); -#endif - return location; -} - -// Get shader location attribute -int rlGetLocationAttrib(unsigned int shaderId, const char *attribName) -{ - int location = -1; -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - location = glGetAttribLocation(shaderId, attribName); - - //if (location == -1) TRACELOG(RL_LOG_WARNING, "SHADER: [ID %i] Failed to find shader attribute: %s", shaderId, attribName); - //else TRACELOG(RL_LOG_INFO, "SHADER: [ID %i] Shader attribute (%s) set at location: %i", shaderId, attribName, location); -#endif - return location; -} - -// Set shader value uniform -void rlSetUniform(int locIndex, const void *value, int uniformType, int count) -{ -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - switch (uniformType) - { - case RL_SHADER_UNIFORM_FLOAT: glUniform1fv(locIndex, count, (float *)value); break; - case RL_SHADER_UNIFORM_VEC2: glUniform2fv(locIndex, count, (float *)value); break; - case RL_SHADER_UNIFORM_VEC3: glUniform3fv(locIndex, count, (float *)value); break; - case RL_SHADER_UNIFORM_VEC4: glUniform4fv(locIndex, count, (float *)value); break; - case RL_SHADER_UNIFORM_INT: glUniform1iv(locIndex, count, (int *)value); break; - case RL_SHADER_UNIFORM_IVEC2: glUniform2iv(locIndex, count, (int *)value); break; - case RL_SHADER_UNIFORM_IVEC3: glUniform3iv(locIndex, count, (int *)value); break; - case RL_SHADER_UNIFORM_IVEC4: glUniform4iv(locIndex, count, (int *)value); break; - #if !defined(GRAPHICS_API_OPENGL_ES2) - case RL_SHADER_UNIFORM_UINT: glUniform1uiv(locIndex, count, (unsigned int *)value); break; - case RL_SHADER_UNIFORM_UIVEC2: glUniform2uiv(locIndex, count, (unsigned int *)value); break; - case RL_SHADER_UNIFORM_UIVEC3: glUniform3uiv(locIndex, count, (unsigned int *)value); break; - case RL_SHADER_UNIFORM_UIVEC4: glUniform4uiv(locIndex, count, (unsigned int *)value); break; - #endif - case RL_SHADER_UNIFORM_SAMPLER2D: glUniform1iv(locIndex, count, (int *)value); break; - default: TRACELOG(RL_LOG_WARNING, "SHADER: Failed to set uniform value, data type not recognized"); - - // TODO: Support glUniform1uiv(), glUniform2uiv(), glUniform3uiv(), glUniform4uiv() - } -#endif -} - -// Set shader value attribute -void rlSetVertexAttributeDefault(int locIndex, const void *value, int attribType, int count) -{ -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - switch (attribType) - { - case RL_SHADER_ATTRIB_FLOAT: if (count == 1) glVertexAttrib1fv(locIndex, (float *)value); break; - case RL_SHADER_ATTRIB_VEC2: if (count == 2) glVertexAttrib2fv(locIndex, (float *)value); break; - case RL_SHADER_ATTRIB_VEC3: if (count == 3) glVertexAttrib3fv(locIndex, (float *)value); break; - case RL_SHADER_ATTRIB_VEC4: if (count == 4) glVertexAttrib4fv(locIndex, (float *)value); break; - default: TRACELOG(RL_LOG_WARNING, "SHADER: Failed to set attrib default value, data type not recognized"); - } -#endif -} - -// Set shader value uniform matrix -void rlSetUniformMatrix(int locIndex, Matrix mat) -{ -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - float matfloat[16] = { - mat.m0, mat.m1, mat.m2, mat.m3, - mat.m4, mat.m5, mat.m6, mat.m7, - mat.m8, mat.m9, mat.m10, mat.m11, - mat.m12, mat.m13, mat.m14, mat.m15 - }; - glUniformMatrix4fv(locIndex, 1, false, matfloat); -#endif -} - -// Set shader value uniform matrix -void rlSetUniformMatrices(int locIndex, const Matrix *matrices, int count) -{ -#if defined(GRAPHICS_API_OPENGL_33) - glUniformMatrix4fv(locIndex, count, true, (const float *)matrices); -#elif defined(GRAPHICS_API_OPENGL_ES2) - // WARNING: WebGL does not support Matrix transpose ("true" parameter) - // REF: https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/uniformMatrix - glUniformMatrix4fv(locIndex, count, false, (const float *)matrices); -#endif -} - -// Set shader value uniform sampler -void rlSetUniformSampler(int locIndex, unsigned int textureId) -{ -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - // Check if texture is already active - for (int i = 0; i < RL_DEFAULT_BATCH_MAX_TEXTURE_UNITS; i++) - { - if (RLGL.State.activeTextureId[i] == textureId) - { - glUniform1i(locIndex, 1 + i); - return; - } - } - - // Register a new active texture for the internal batch system - // NOTE: Default texture is always activated as GL_TEXTURE0 - for (int i = 0; i < RL_DEFAULT_BATCH_MAX_TEXTURE_UNITS; i++) - { - if (RLGL.State.activeTextureId[i] == 0) - { - glUniform1i(locIndex, 1 + i); // Activate new texture unit - RLGL.State.activeTextureId[i] = textureId; // Save texture id for binding on drawing - break; - } - } -#endif -} - -// Set shader currently active (id and locations) -void rlSetShader(unsigned int id, int *locs) -{ -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - if (RLGL.State.currentShaderId != id) - { - rlDrawRenderBatch(RLGL.currentBatch); - RLGL.State.currentShaderId = id; - RLGL.State.currentShaderLocs = locs; - } -#endif -} - -// Load compute shader program -unsigned int rlLoadComputeShaderProgram(unsigned int shaderId) -{ - unsigned int program = 0; - -#if defined(GRAPHICS_API_OPENGL_43) - GLint success = 0; - program = glCreateProgram(); - glAttachShader(program, shaderId); - glLinkProgram(program); - - // NOTE: All uniform variables are intitialised to 0 when a program links - - glGetProgramiv(program, GL_LINK_STATUS, &success); - - if (success == GL_FALSE) - { - TRACELOG(RL_LOG_WARNING, "SHADER: [ID %i] Failed to link compute shader program", program); - - int maxLength = 0; - glGetProgramiv(program, GL_INFO_LOG_LENGTH, &maxLength); - - if (maxLength > 0) - { - int length = 0; - char *log = (char *)RL_CALLOC(maxLength, sizeof(char)); - glGetProgramInfoLog(program, maxLength, &length, log); - TRACELOG(RL_LOG_WARNING, "SHADER: [ID %i] Link error: %s", program, log); - RL_FREE(log); - } - - glDeleteProgram(program); - - program = 0; - } - else - { - // Get the size of compiled shader program (not available on OpenGL ES 2.0) - // NOTE: If GL_LINK_STATUS is GL_FALSE, program binary length is zero - //GLint binarySize = 0; - //glGetProgramiv(id, GL_PROGRAM_BINARY_LENGTH, &binarySize); - - TRACELOG(RL_LOG_INFO, "SHADER: [ID %i] Compute shader program loaded successfully", program); - } -#else - TRACELOG(RL_LOG_WARNING, "SHADER: Compute shaders not enabled. Define GRAPHICS_API_OPENGL_43"); -#endif - - return program; -} - -// Dispatch compute shader (equivalent to *draw* for graphics pilepine) -void rlComputeShaderDispatch(unsigned int groupX, unsigned int groupY, unsigned int groupZ) -{ -#if defined(GRAPHICS_API_OPENGL_43) - glDispatchCompute(groupX, groupY, groupZ); -#endif -} - -// Load shader storage buffer object (SSBO) -unsigned int rlLoadShaderBuffer(unsigned int size, const void *data, int usageHint) -{ - unsigned int ssbo = 0; - -#if defined(GRAPHICS_API_OPENGL_43) - glGenBuffers(1, &ssbo); - glBindBuffer(GL_SHADER_STORAGE_BUFFER, ssbo); - glBufferData(GL_SHADER_STORAGE_BUFFER, size, data, usageHint? usageHint : RL_STREAM_COPY); - if (data == NULL) glClearBufferData(GL_SHADER_STORAGE_BUFFER, GL_R8UI, GL_RED_INTEGER, GL_UNSIGNED_BYTE, NULL); // Clear buffer data to 0 - glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0); -#else - TRACELOG(RL_LOG_WARNING, "SSBO: SSBO not enabled. Define GRAPHICS_API_OPENGL_43"); -#endif - - return ssbo; -} - -// Unload shader storage buffer object (SSBO) -void rlUnloadShaderBuffer(unsigned int ssboId) -{ -#if defined(GRAPHICS_API_OPENGL_43) - glDeleteBuffers(1, &ssboId); -#else - TRACELOG(RL_LOG_WARNING, "SSBO: SSBO not enabled. Define GRAPHICS_API_OPENGL_43"); -#endif - -} - -// Update SSBO buffer data -void rlUpdateShaderBuffer(unsigned int id, const void *data, unsigned int dataSize, unsigned int offset) -{ -#if defined(GRAPHICS_API_OPENGL_43) - glBindBuffer(GL_SHADER_STORAGE_BUFFER, id); - glBufferSubData(GL_SHADER_STORAGE_BUFFER, offset, dataSize, data); -#endif -} - -// Get SSBO buffer size -unsigned int rlGetShaderBufferSize(unsigned int id) -{ -#if defined(GRAPHICS_API_OPENGL_43) - GLint64 size = 0; - glBindBuffer(GL_SHADER_STORAGE_BUFFER, id); - glGetBufferParameteri64v(GL_SHADER_STORAGE_BUFFER, GL_BUFFER_SIZE, &size); - return (size > 0)? (unsigned int)size : 0; -#else - return 0; -#endif -} - -// Read SSBO buffer data (GPU->CPU) -void rlReadShaderBuffer(unsigned int id, void *dest, unsigned int count, unsigned int offset) -{ -#if defined(GRAPHICS_API_OPENGL_43) - glBindBuffer(GL_SHADER_STORAGE_BUFFER, id); - glGetBufferSubData(GL_SHADER_STORAGE_BUFFER, offset, count, dest); -#endif -} - -// Bind SSBO buffer -void rlBindShaderBuffer(unsigned int id, unsigned int index) -{ -#if defined(GRAPHICS_API_OPENGL_43) - glBindBufferBase(GL_SHADER_STORAGE_BUFFER, index, id); -#endif -} - -// Copy SSBO buffer data -void rlCopyShaderBuffer(unsigned int destId, unsigned int srcId, unsigned int destOffset, unsigned int srcOffset, unsigned int count) -{ -#if defined(GRAPHICS_API_OPENGL_43) - glBindBuffer(GL_COPY_READ_BUFFER, srcId); - glBindBuffer(GL_COPY_WRITE_BUFFER, destId); - glCopyBufferSubData(GL_COPY_READ_BUFFER, GL_COPY_WRITE_BUFFER, srcOffset, destOffset, count); -#endif -} - -// Bind image texture -void rlBindImageTexture(unsigned int id, unsigned int index, int format, bool readonly) -{ -#if defined(GRAPHICS_API_OPENGL_43) - unsigned int glInternalFormat = 0, glFormat = 0, glType = 0; - - rlGetGlTextureFormats(format, &glInternalFormat, &glFormat, &glType); - glBindImageTexture(index, id, 0, 0, 0, readonly? GL_READ_ONLY : GL_READ_WRITE, glInternalFormat); -#else - TRACELOG(RL_LOG_WARNING, "TEXTURE: Image texture binding not enabled. Define GRAPHICS_API_OPENGL_43"); -#endif -} - -// Matrix state management -//----------------------------------------------------------------------------------------- -// Get internal modelview matrix -Matrix rlGetMatrixModelview(void) -{ - Matrix matrix = rlMatrixIdentity(); -#if defined(GRAPHICS_API_OPENGL_11) - float mat[16]; - glGetFloatv(GL_MODELVIEW_MATRIX, mat); - matrix.m0 = mat[0]; - matrix.m1 = mat[1]; - matrix.m2 = mat[2]; - matrix.m3 = mat[3]; - matrix.m4 = mat[4]; - matrix.m5 = mat[5]; - matrix.m6 = mat[6]; - matrix.m7 = mat[7]; - matrix.m8 = mat[8]; - matrix.m9 = mat[9]; - matrix.m10 = mat[10]; - matrix.m11 = mat[11]; - matrix.m12 = mat[12]; - matrix.m13 = mat[13]; - matrix.m14 = mat[14]; - matrix.m15 = mat[15]; -#else - matrix = RLGL.State.modelview; -#endif - return matrix; -} - -// Get internal projection matrix -Matrix rlGetMatrixProjection(void) -{ -#if defined(GRAPHICS_API_OPENGL_11) - float mat[16]; - glGetFloatv(GL_PROJECTION_MATRIX,mat); - Matrix m; - m.m0 = mat[0]; - m.m1 = mat[1]; - m.m2 = mat[2]; - m.m3 = mat[3]; - m.m4 = mat[4]; - m.m5 = mat[5]; - m.m6 = mat[6]; - m.m7 = mat[7]; - m.m8 = mat[8]; - m.m9 = mat[9]; - m.m10 = mat[10]; - m.m11 = mat[11]; - m.m12 = mat[12]; - m.m13 = mat[13]; - m.m14 = mat[14]; - m.m15 = mat[15]; - return m; -#else - return RLGL.State.projection; -#endif -} - -// Get internal accumulated transform matrix -Matrix rlGetMatrixTransform(void) -{ - Matrix mat = rlMatrixIdentity(); -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - // TODO: Consider possible transform matrices in the RLGL.State.stack - // Is this the right order? or should we start with the first stored matrix instead of the last one? - //Matrix matStackTransform = rlMatrixIdentity(); - //for (int i = RLGL.State.stackCounter; i > 0; i--) matStackTransform = rlMatrixMultiply(RLGL.State.stack[i], matStackTransform); - mat = RLGL.State.transform; -#endif - return mat; -} - -// Get internal projection matrix for stereo render (selected eye) -Matrix rlGetMatrixProjectionStereo(int eye) -{ - Matrix mat = rlMatrixIdentity(); -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - mat = RLGL.State.projectionStereo[eye]; -#endif - return mat; -} - -// Get internal view offset matrix for stereo render (selected eye) -Matrix rlGetMatrixViewOffsetStereo(int eye) -{ - Matrix mat = rlMatrixIdentity(); -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - mat = RLGL.State.viewOffsetStereo[eye]; -#endif - return mat; -} - -// Set a custom modelview matrix (replaces internal modelview matrix) -void rlSetMatrixModelview(Matrix view) -{ -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - RLGL.State.modelview = view; -#endif -} - -// Set a custom projection matrix (replaces internal projection matrix) -void rlSetMatrixProjection(Matrix projection) -{ -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - RLGL.State.projection = projection; -#endif -} - -// Set eyes projection matrices for stereo rendering -void rlSetMatrixProjectionStereo(Matrix right, Matrix left) -{ -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - RLGL.State.projectionStereo[0] = right; - RLGL.State.projectionStereo[1] = left; -#endif -} - -// Set eyes view offsets matrices for stereo rendering -void rlSetMatrixViewOffsetStereo(Matrix right, Matrix left) -{ -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - RLGL.State.viewOffsetStereo[0] = right; - RLGL.State.viewOffsetStereo[1] = left; -#endif -} - -// Load and draw a quad in NDC -void rlLoadDrawQuad(void) -{ -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - unsigned int quadVAO = 0; - unsigned int quadVBO = 0; - - float vertices[] = { - // Positions Texcoords - -1.0f, 1.0f, 0.0f, 0.0f, 1.0f, - -1.0f, -1.0f, 0.0f, 0.0f, 0.0f, - 1.0f, 1.0f, 0.0f, 1.0f, 1.0f, - 1.0f, -1.0f, 0.0f, 1.0f, 0.0f, - }; - - // Gen VAO to contain VBO - glGenVertexArrays(1, &quadVAO); - glBindVertexArray(quadVAO); - - // Gen and fill vertex buffer (VBO) - glGenBuffers(1, &quadVBO); - glBindBuffer(GL_ARRAY_BUFFER, quadVBO); - glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), &vertices, GL_STATIC_DRAW); - - // Bind vertex attributes (position, texcoords) - glEnableVertexAttribArray(RL_DEFAULT_SHADER_ATTRIB_LOCATION_POSITION); - glVertexAttribPointer(RL_DEFAULT_SHADER_ATTRIB_LOCATION_POSITION, 3, GL_FLOAT, GL_FALSE, 5*sizeof(float), (void *)0); // Positions - glEnableVertexAttribArray(RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD); - glVertexAttribPointer(RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD, 2, GL_FLOAT, GL_FALSE, 5*sizeof(float), (void *)(3*sizeof(float))); // Texcoords - - // Draw quad - glBindVertexArray(quadVAO); - glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); - glBindVertexArray(0); - - // Delete buffers (VBO and VAO) - glDeleteBuffers(1, &quadVBO); - glDeleteVertexArrays(1, &quadVAO); -#endif -} - -// Load and draw a cube in NDC -void rlLoadDrawCube(void) -{ -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - unsigned int cubeVAO = 0; - unsigned int cubeVBO = 0; - - float vertices[] = { - // Positions Normals Texcoords - -1.0f, -1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, - 1.0f, 1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 1.0f, 1.0f, - 1.0f, -1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 1.0f, 0.0f, - 1.0f, 1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 1.0f, 1.0f, - -1.0f, -1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, - -1.0f, 1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 0.0f, 1.0f, - -1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, - 1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, - 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, - 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, - -1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, - -1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, - -1.0f, 1.0f, 1.0f, -1.0f, 0.0f, 0.0f, 1.0f, 0.0f, - -1.0f, 1.0f, -1.0f, -1.0f, 0.0f, 0.0f, 1.0f, 1.0f, - -1.0f, -1.0f, -1.0f, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f, - -1.0f, -1.0f, -1.0f, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f, - -1.0f, -1.0f, 1.0f, -1.0f, 0.0f, 0.0f, 0.0f, 0.0f, - -1.0f, 1.0f, 1.0f, -1.0f, 0.0f, 0.0f, 1.0f, 0.0f, - 1.0f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, - 1.0f, -1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, - 1.0f, 1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, - 1.0f, -1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, - 1.0f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, - 1.0f, -1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, - -1.0f, -1.0f, -1.0f, 0.0f, -1.0f, 0.0f, 0.0f, 1.0f, - 1.0f, -1.0f, -1.0f, 0.0f, -1.0f, 0.0f, 1.0f, 1.0f, - 1.0f, -1.0f, 1.0f, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f, - 1.0f, -1.0f, 1.0f, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f, - -1.0f, -1.0f, 1.0f, 0.0f, -1.0f, 0.0f, 0.0f, 0.0f, - -1.0f, -1.0f, -1.0f, 0.0f, -1.0f, 0.0f, 0.0f, 1.0f, - -1.0f, 1.0f, -1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, - 1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, - 1.0f, 1.0f, -1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, - 1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, - -1.0f, 1.0f, -1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, - -1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f - }; - - // Gen VAO to contain VBO - glGenVertexArrays(1, &cubeVAO); - glBindVertexArray(cubeVAO); - - // Gen and fill vertex buffer (VBO) - glGenBuffers(1, &cubeVBO); - glBindBuffer(GL_ARRAY_BUFFER, cubeVBO); - glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); - - // Bind vertex attributes (position, normals, texcoords) - glBindVertexArray(cubeVAO); - glEnableVertexAttribArray(RL_DEFAULT_SHADER_ATTRIB_LOCATION_POSITION); - glVertexAttribPointer(RL_DEFAULT_SHADER_ATTRIB_LOCATION_POSITION, 3, GL_FLOAT, GL_FALSE, 8*sizeof(float), (void *)0); // Positions - glEnableVertexAttribArray(RL_DEFAULT_SHADER_ATTRIB_LOCATION_NORMAL); - glVertexAttribPointer(RL_DEFAULT_SHADER_ATTRIB_LOCATION_NORMAL, 3, GL_FLOAT, GL_FALSE, 8*sizeof(float), (void *)(3*sizeof(float))); // Normals - glEnableVertexAttribArray(RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD); - glVertexAttribPointer(RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD, 2, GL_FLOAT, GL_FALSE, 8*sizeof(float), (void *)(6*sizeof(float))); // Texcoords - glBindBuffer(GL_ARRAY_BUFFER, 0); - glBindVertexArray(0); - - // Draw cube - glBindVertexArray(cubeVAO); - glDrawArrays(GL_TRIANGLES, 0, 36); - glBindVertexArray(0); - - // Delete VBO and VAO - glDeleteBuffers(1, &cubeVBO); - glDeleteVertexArrays(1, &cubeVAO); -#endif -} - -// Get name string for pixel format -const char *rlGetPixelFormatName(unsigned int format) -{ - switch (format) - { - case RL_PIXELFORMAT_UNCOMPRESSED_GRAYSCALE: return "GRAYSCALE"; break; // 8 bit per pixel (no alpha) - case RL_PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA: return "GRAY_ALPHA"; break; // 8*2 bpp (2 channels) - case RL_PIXELFORMAT_UNCOMPRESSED_R5G6B5: return "R5G6B5"; break; // 16 bpp - case RL_PIXELFORMAT_UNCOMPRESSED_R8G8B8: return "R8G8B8"; break; // 24 bpp - case RL_PIXELFORMAT_UNCOMPRESSED_R5G5B5A1: return "R5G5B5A1"; break; // 16 bpp (1 bit alpha) - case RL_PIXELFORMAT_UNCOMPRESSED_R4G4B4A4: return "R4G4B4A4"; break; // 16 bpp (4 bit alpha) - case RL_PIXELFORMAT_UNCOMPRESSED_R8G8B8A8: return "R8G8B8A8"; break; // 32 bpp - case RL_PIXELFORMAT_UNCOMPRESSED_R32: return "R32"; break; // 32 bpp (1 channel - float) - case RL_PIXELFORMAT_UNCOMPRESSED_R32G32B32: return "R32G32B32"; break; // 32*3 bpp (3 channels - float) - case RL_PIXELFORMAT_UNCOMPRESSED_R32G32B32A32: return "R32G32B32A32"; break; // 32*4 bpp (4 channels - float) - case RL_PIXELFORMAT_UNCOMPRESSED_R16: return "R16"; break; // 16 bpp (1 channel - half float) - case RL_PIXELFORMAT_UNCOMPRESSED_R16G16B16: return "R16G16B16"; break; // 16*3 bpp (3 channels - half float) - case RL_PIXELFORMAT_UNCOMPRESSED_R16G16B16A16: return "R16G16B16A16"; break; // 16*4 bpp (4 channels - half float) - case RL_PIXELFORMAT_COMPRESSED_DXT1_RGB: return "DXT1_RGB"; break; // 4 bpp (no alpha) - case RL_PIXELFORMAT_COMPRESSED_DXT1_RGBA: return "DXT1_RGBA"; break; // 4 bpp (1 bit alpha) - case RL_PIXELFORMAT_COMPRESSED_DXT3_RGBA: return "DXT3_RGBA"; break; // 8 bpp - case RL_PIXELFORMAT_COMPRESSED_DXT5_RGBA: return "DXT5_RGBA"; break; // 8 bpp - case RL_PIXELFORMAT_COMPRESSED_ETC1_RGB: return "ETC1_RGB"; break; // 4 bpp - case RL_PIXELFORMAT_COMPRESSED_ETC2_RGB: return "ETC2_RGB"; break; // 4 bpp - case RL_PIXELFORMAT_COMPRESSED_ETC2_EAC_RGBA: return "ETC2_RGBA"; break; // 8 bpp - case RL_PIXELFORMAT_COMPRESSED_PVRT_RGB: return "PVRT_RGB"; break; // 4 bpp - case RL_PIXELFORMAT_COMPRESSED_PVRT_RGBA: return "PVRT_RGBA"; break; // 4 bpp - case RL_PIXELFORMAT_COMPRESSED_ASTC_4x4_RGBA: return "ASTC_4x4_RGBA"; break; // 8 bpp - case RL_PIXELFORMAT_COMPRESSED_ASTC_8x8_RGBA: return "ASTC_8x8_RGBA"; break; // 2 bpp - default: return "UNKNOWN"; break; - } -} - -//---------------------------------------------------------------------------------- -// Module specific Functions Definition -//---------------------------------------------------------------------------------- -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) -// Load default shader (just vertex positioning and texture coloring) -// NOTE: This shader program is used for internal buffers -// NOTE: Loaded: RLGL.State.defaultShaderId, RLGL.State.defaultShaderLocs -static void rlLoadShaderDefault(void) -{ - RLGL.State.defaultShaderLocs = (int *)RL_CALLOC(RL_MAX_SHADER_LOCATIONS, sizeof(int)); - - // NOTE: All locations must be reseted to -1 (no location) - for (int i = 0; i < RL_MAX_SHADER_LOCATIONS; i++) RLGL.State.defaultShaderLocs[i] = -1; - - // Vertex shader directly defined, no external file required - const char *defaultVShaderCode = -#if defined(GRAPHICS_API_OPENGL_21) - "#version 120 \n" - "attribute vec3 vertexPosition; \n" - "attribute vec2 vertexTexCoord; \n" - "attribute vec4 vertexColor; \n" - "varying vec2 fragTexCoord; \n" - "varying vec4 fragColor; \n" -#elif defined(GRAPHICS_API_OPENGL_33) - "#version 330 \n" - "in vec3 vertexPosition; \n" - "in vec2 vertexTexCoord; \n" - "in vec4 vertexColor; \n" - "out vec2 fragTexCoord; \n" - "out vec4 fragColor; \n" -#endif - -#if defined(GRAPHICS_API_OPENGL_ES3) - "#version 300 es \n" - "precision mediump float; \n" // Precision required for OpenGL ES3 (WebGL 2) (on some browsers) - "in vec3 vertexPosition; \n" - "in vec2 vertexTexCoord; \n" - "in vec4 vertexColor; \n" - "out vec2 fragTexCoord; \n" - "out vec4 fragColor; \n" -#elif defined(GRAPHICS_API_OPENGL_ES2) - "#version 100 \n" - "precision mediump float; \n" // Precision required for OpenGL ES2 (WebGL) (on some browsers) - "attribute vec3 vertexPosition; \n" - "attribute vec2 vertexTexCoord; \n" - "attribute vec4 vertexColor; \n" - "varying vec2 fragTexCoord; \n" - "varying vec4 fragColor; \n" -#endif - - "uniform mat4 mvp; \n" - "void main() \n" - "{ \n" - " fragTexCoord = vertexTexCoord; \n" - " fragColor = vertexColor; \n" - " gl_Position = mvp*vec4(vertexPosition, 1.0); \n" - "} \n"; - - // Fragment shader directly defined, no external file required - const char *defaultFShaderCode = -#if defined(GRAPHICS_API_OPENGL_21) - "#version 120 \n" - "varying vec2 fragTexCoord; \n" - "varying vec4 fragColor; \n" - "uniform sampler2D texture0; \n" - "uniform vec4 colDiffuse; \n" - "void main() \n" - "{ \n" - " vec4 texelColor = texture2D(texture0, fragTexCoord); \n" - " gl_FragColor = texelColor*colDiffuse*fragColor; \n" - "} \n"; -#elif defined(GRAPHICS_API_OPENGL_33) - "#version 330 \n" - "in vec2 fragTexCoord; \n" - "in vec4 fragColor; \n" - "out vec4 finalColor; \n" - "uniform sampler2D texture0; \n" - "uniform vec4 colDiffuse; \n" - "void main() \n" - "{ \n" - " vec4 texelColor = texture(texture0, fragTexCoord); \n" - " finalColor = texelColor*colDiffuse*fragColor; \n" - "} \n"; -#endif - -#if defined(GRAPHICS_API_OPENGL_ES3) - "#version 300 es \n" - "precision mediump float; \n" // Precision required for OpenGL ES3 (WebGL 2) - "in vec2 fragTexCoord; \n" - "in vec4 fragColor; \n" - "out vec4 finalColor; \n" - "uniform sampler2D texture0; \n" - "uniform vec4 colDiffuse; \n" - "void main() \n" - "{ \n" - " vec4 texelColor = texture(texture0, fragTexCoord); \n" - " finalColor = texelColor*colDiffuse*fragColor; \n" - "} \n"; -#elif defined(GRAPHICS_API_OPENGL_ES2) - "#version 100 \n" - "precision mediump float; \n" // Precision required for OpenGL ES2 (WebGL) - "varying vec2 fragTexCoord; \n" - "varying vec4 fragColor; \n" - "uniform sampler2D texture0; \n" - "uniform vec4 colDiffuse; \n" - "void main() \n" - "{ \n" - " vec4 texelColor = texture2D(texture0, fragTexCoord); \n" - " gl_FragColor = texelColor*colDiffuse*fragColor; \n" - "} \n"; -#endif - - // NOTE: Compiled vertex/fragment shaders are not deleted, - // they are kept for re-use as default shaders in case some shader loading fails - RLGL.State.defaultVShaderId = rlCompileShader(defaultVShaderCode, GL_VERTEX_SHADER); // Compile default vertex shader - RLGL.State.defaultFShaderId = rlCompileShader(defaultFShaderCode, GL_FRAGMENT_SHADER); // Compile default fragment shader - - RLGL.State.defaultShaderId = rlLoadShaderProgram(RLGL.State.defaultVShaderId, RLGL.State.defaultFShaderId); - - if (RLGL.State.defaultShaderId > 0) - { - TRACELOG(RL_LOG_INFO, "SHADER: [ID %i] Default shader loaded successfully", RLGL.State.defaultShaderId); - - // Set default shader locations: attributes locations - RLGL.State.defaultShaderLocs[RL_SHADER_LOC_VERTEX_POSITION] = glGetAttribLocation(RLGL.State.defaultShaderId, RL_DEFAULT_SHADER_ATTRIB_NAME_POSITION); - RLGL.State.defaultShaderLocs[RL_SHADER_LOC_VERTEX_TEXCOORD01] = glGetAttribLocation(RLGL.State.defaultShaderId, RL_DEFAULT_SHADER_ATTRIB_NAME_TEXCOORD); - RLGL.State.defaultShaderLocs[RL_SHADER_LOC_VERTEX_COLOR] = glGetAttribLocation(RLGL.State.defaultShaderId, RL_DEFAULT_SHADER_ATTRIB_NAME_COLOR); - - // Set default shader locations: uniform locations - RLGL.State.defaultShaderLocs[RL_SHADER_LOC_MATRIX_MVP] = glGetUniformLocation(RLGL.State.defaultShaderId, RL_DEFAULT_SHADER_UNIFORM_NAME_MVP); - RLGL.State.defaultShaderLocs[RL_SHADER_LOC_COLOR_DIFFUSE] = glGetUniformLocation(RLGL.State.defaultShaderId, RL_DEFAULT_SHADER_UNIFORM_NAME_COLOR); - RLGL.State.defaultShaderLocs[RL_SHADER_LOC_MAP_DIFFUSE] = glGetUniformLocation(RLGL.State.defaultShaderId, RL_DEFAULT_SHADER_SAMPLER2D_NAME_TEXTURE0); - } - else TRACELOG(RL_LOG_WARNING, "SHADER: [ID %i] Failed to load default shader", RLGL.State.defaultShaderId); -} - -// Unload default shader -// NOTE: Unloads: RLGL.State.defaultShaderId, RLGL.State.defaultShaderLocs -static void rlUnloadShaderDefault(void) -{ - glUseProgram(0); - - glDetachShader(RLGL.State.defaultShaderId, RLGL.State.defaultVShaderId); - glDetachShader(RLGL.State.defaultShaderId, RLGL.State.defaultFShaderId); - glDeleteShader(RLGL.State.defaultVShaderId); - glDeleteShader(RLGL.State.defaultFShaderId); - - glDeleteProgram(RLGL.State.defaultShaderId); - - RL_FREE(RLGL.State.defaultShaderLocs); - - TRACELOG(RL_LOG_INFO, "SHADER: [ID %i] Default shader unloaded successfully", RLGL.State.defaultShaderId); -} - -#if defined(RLGL_SHOW_GL_DETAILS_INFO) -// Get compressed format official GL identifier name -static const char *rlGetCompressedFormatName(int format) -{ - switch (format) - { - // GL_EXT_texture_compression_s3tc - case 0x83F0: return "GL_COMPRESSED_RGB_S3TC_DXT1_EXT"; break; - case 0x83F1: return "GL_COMPRESSED_RGBA_S3TC_DXT1_EXT"; break; - case 0x83F2: return "GL_COMPRESSED_RGBA_S3TC_DXT3_EXT"; break; - case 0x83F3: return "GL_COMPRESSED_RGBA_S3TC_DXT5_EXT"; break; - // GL_3DFX_texture_compression_FXT1 - case 0x86B0: return "GL_COMPRESSED_RGB_FXT1_3DFX"; break; - case 0x86B1: return "GL_COMPRESSED_RGBA_FXT1_3DFX"; break; - // GL_IMG_texture_compression_pvrtc - case 0x8C00: return "GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG"; break; - case 0x8C01: return "GL_COMPRESSED_RGB_PVRTC_2BPPV1_IMG"; break; - case 0x8C02: return "GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG"; break; - case 0x8C03: return "GL_COMPRESSED_RGBA_PVRTC_2BPPV1_IMG"; break; - // GL_OES_compressed_ETC1_RGB8_texture - case 0x8D64: return "GL_ETC1_RGB8_OES"; break; - // GL_ARB_texture_compression_rgtc - case 0x8DBB: return "GL_COMPRESSED_RED_RGTC1"; break; - case 0x8DBC: return "GL_COMPRESSED_SIGNED_RED_RGTC1"; break; - case 0x8DBD: return "GL_COMPRESSED_RG_RGTC2"; break; - case 0x8DBE: return "GL_COMPRESSED_SIGNED_RG_RGTC2"; break; - // GL_ARB_texture_compression_bptc - case 0x8E8C: return "GL_COMPRESSED_RGBA_BPTC_UNORM_ARB"; break; - case 0x8E8D: return "GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM_ARB"; break; - case 0x8E8E: return "GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT_ARB"; break; - case 0x8E8F: return "GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT_ARB"; break; - // GL_ARB_ES3_compatibility - case 0x9274: return "GL_COMPRESSED_RGB8_ETC2"; break; - case 0x9275: return "GL_COMPRESSED_SRGB8_ETC2"; break; - case 0x9276: return "GL_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2"; break; - case 0x9277: return "GL_COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2"; break; - case 0x9278: return "GL_COMPRESSED_RGBA8_ETC2_EAC"; break; - case 0x9279: return "GL_COMPRESSED_SRGB8_ALPHA8_ETC2_EAC"; break; - case 0x9270: return "GL_COMPRESSED_R11_EAC"; break; - case 0x9271: return "GL_COMPRESSED_SIGNED_R11_EAC"; break; - case 0x9272: return "GL_COMPRESSED_RG11_EAC"; break; - case 0x9273: return "GL_COMPRESSED_SIGNED_RG11_EAC"; break; - // GL_KHR_texture_compression_astc_hdr - case 0x93B0: return "GL_COMPRESSED_RGBA_ASTC_4x4_KHR"; break; - case 0x93B1: return "GL_COMPRESSED_RGBA_ASTC_5x4_KHR"; break; - case 0x93B2: return "GL_COMPRESSED_RGBA_ASTC_5x5_KHR"; break; - case 0x93B3: return "GL_COMPRESSED_RGBA_ASTC_6x5_KHR"; break; - case 0x93B4: return "GL_COMPRESSED_RGBA_ASTC_6x6_KHR"; break; - case 0x93B5: return "GL_COMPRESSED_RGBA_ASTC_8x5_KHR"; break; - case 0x93B6: return "GL_COMPRESSED_RGBA_ASTC_8x6_KHR"; break; - case 0x93B7: return "GL_COMPRESSED_RGBA_ASTC_8x8_KHR"; break; - case 0x93B8: return "GL_COMPRESSED_RGBA_ASTC_10x5_KHR"; break; - case 0x93B9: return "GL_COMPRESSED_RGBA_ASTC_10x6_KHR"; break; - case 0x93BA: return "GL_COMPRESSED_RGBA_ASTC_10x8_KHR"; break; - case 0x93BB: return "GL_COMPRESSED_RGBA_ASTC_10x10_KHR"; break; - case 0x93BC: return "GL_COMPRESSED_RGBA_ASTC_12x10_KHR"; break; - case 0x93BD: return "GL_COMPRESSED_RGBA_ASTC_12x12_KHR"; break; - case 0x93D0: return "GL_COMPRESSED_SRGB8_ALPHA8_ASTC_4x4_KHR"; break; - case 0x93D1: return "GL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x4_KHR"; break; - case 0x93D2: return "GL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x5_KHR"; break; - case 0x93D3: return "GL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x5_KHR"; break; - case 0x93D4: return "GL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x6_KHR"; break; - case 0x93D5: return "GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x5_KHR"; break; - case 0x93D6: return "GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x6_KHR"; break; - case 0x93D7: return "GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x8_KHR"; break; - case 0x93D8: return "GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x5_KHR"; break; - case 0x93D9: return "GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x6_KHR"; break; - case 0x93DA: return "GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x8_KHR"; break; - case 0x93DB: return "GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x10_KHR"; break; - case 0x93DC: return "GL_COMPRESSED_SRGB8_ALPHA8_ASTC_12x10_KHR"; break; - case 0x93DD: return "GL_COMPRESSED_SRGB8_ALPHA8_ASTC_12x12_KHR"; break; - default: return "GL_COMPRESSED_UNKNOWN"; break; - } -} -#endif // RLGL_SHOW_GL_DETAILS_INFO - -#endif // GRAPHICS_API_OPENGL_33 || GRAPHICS_API_OPENGL_ES2 - -// Get pixel data size in bytes (image or texture) -// NOTE: Size depends on pixel format -static int rlGetPixelDataSize(int width, int height, int format) -{ - int dataSize = 0; // Size in bytes - int bpp = 0; // Bits per pixel - - switch (format) - { - case RL_PIXELFORMAT_UNCOMPRESSED_GRAYSCALE: bpp = 8; break; - case RL_PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA: - case RL_PIXELFORMAT_UNCOMPRESSED_R5G6B5: - case RL_PIXELFORMAT_UNCOMPRESSED_R5G5B5A1: - case RL_PIXELFORMAT_UNCOMPRESSED_R4G4B4A4: bpp = 16; break; - case RL_PIXELFORMAT_UNCOMPRESSED_R8G8B8A8: bpp = 32; break; - case RL_PIXELFORMAT_UNCOMPRESSED_R8G8B8: bpp = 24; break; - case RL_PIXELFORMAT_UNCOMPRESSED_R32: bpp = 32; break; - case RL_PIXELFORMAT_UNCOMPRESSED_R32G32B32: bpp = 32*3; break; - case RL_PIXELFORMAT_UNCOMPRESSED_R32G32B32A32: bpp = 32*4; break; - case RL_PIXELFORMAT_UNCOMPRESSED_R16: bpp = 16; break; - case RL_PIXELFORMAT_UNCOMPRESSED_R16G16B16: bpp = 16*3; break; - case RL_PIXELFORMAT_UNCOMPRESSED_R16G16B16A16: bpp = 16*4; break; - case RL_PIXELFORMAT_COMPRESSED_DXT1_RGB: - case RL_PIXELFORMAT_COMPRESSED_DXT1_RGBA: - case RL_PIXELFORMAT_COMPRESSED_ETC1_RGB: - case RL_PIXELFORMAT_COMPRESSED_ETC2_RGB: - case RL_PIXELFORMAT_COMPRESSED_PVRT_RGB: - case RL_PIXELFORMAT_COMPRESSED_PVRT_RGBA: bpp = 4; break; - case RL_PIXELFORMAT_COMPRESSED_DXT3_RGBA: - case RL_PIXELFORMAT_COMPRESSED_DXT5_RGBA: - case RL_PIXELFORMAT_COMPRESSED_ETC2_EAC_RGBA: - case RL_PIXELFORMAT_COMPRESSED_ASTC_4x4_RGBA: bpp = 8; break; - case RL_PIXELFORMAT_COMPRESSED_ASTC_8x8_RGBA: bpp = 2; break; - default: break; - } - - double bytesPerPixel = (double)bpp/8.0; - dataSize = (int)(bytesPerPixel*width*height); // Total data size in bytes - - // Most compressed formats works on 4x4 blocks, - // if texture is smaller, minimum dataSize is 8 or 16 - if ((width < 4) && (height < 4)) - { - if ((format >= RL_PIXELFORMAT_COMPRESSED_DXT1_RGB) && (format < RL_PIXELFORMAT_COMPRESSED_DXT3_RGBA)) dataSize = 8; - else if ((format >= RL_PIXELFORMAT_COMPRESSED_DXT3_RGBA) && (format < RL_PIXELFORMAT_COMPRESSED_ASTC_8x8_RGBA)) dataSize = 16; - } - - return dataSize; -} - -// Auxiliar math functions - -// Get float array of matrix data -static rl_float16 rlMatrixToFloatV(Matrix mat) -{ - rl_float16 result = { 0 }; - - result.v[0] = mat.m0; - result.v[1] = mat.m1; - result.v[2] = mat.m2; - result.v[3] = mat.m3; - result.v[4] = mat.m4; - result.v[5] = mat.m5; - result.v[6] = mat.m6; - result.v[7] = mat.m7; - result.v[8] = mat.m8; - result.v[9] = mat.m9; - result.v[10] = mat.m10; - result.v[11] = mat.m11; - result.v[12] = mat.m12; - result.v[13] = mat.m13; - result.v[14] = mat.m14; - result.v[15] = mat.m15; - - return result; -} - -// Get identity matrix -static Matrix rlMatrixIdentity(void) -{ - Matrix result = { - 1.0f, 0.0f, 0.0f, 0.0f, - 0.0f, 1.0f, 0.0f, 0.0f, - 0.0f, 0.0f, 1.0f, 0.0f, - 0.0f, 0.0f, 0.0f, 1.0f - }; - - return result; -} - -// Get two matrix multiplication -// NOTE: When multiplying matrices... the order matters! -static Matrix rlMatrixMultiply(Matrix left, Matrix right) -{ - Matrix result = { 0 }; - - result.m0 = left.m0*right.m0 + left.m1*right.m4 + left.m2*right.m8 + left.m3*right.m12; - result.m1 = left.m0*right.m1 + left.m1*right.m5 + left.m2*right.m9 + left.m3*right.m13; - result.m2 = left.m0*right.m2 + left.m1*right.m6 + left.m2*right.m10 + left.m3*right.m14; - result.m3 = left.m0*right.m3 + left.m1*right.m7 + left.m2*right.m11 + left.m3*right.m15; - result.m4 = left.m4*right.m0 + left.m5*right.m4 + left.m6*right.m8 + left.m7*right.m12; - result.m5 = left.m4*right.m1 + left.m5*right.m5 + left.m6*right.m9 + left.m7*right.m13; - result.m6 = left.m4*right.m2 + left.m5*right.m6 + left.m6*right.m10 + left.m7*right.m14; - result.m7 = left.m4*right.m3 + left.m5*right.m7 + left.m6*right.m11 + left.m7*right.m15; - result.m8 = left.m8*right.m0 + left.m9*right.m4 + left.m10*right.m8 + left.m11*right.m12; - result.m9 = left.m8*right.m1 + left.m9*right.m5 + left.m10*right.m9 + left.m11*right.m13; - result.m10 = left.m8*right.m2 + left.m9*right.m6 + left.m10*right.m10 + left.m11*right.m14; - result.m11 = left.m8*right.m3 + left.m9*right.m7 + left.m10*right.m11 + left.m11*right.m15; - result.m12 = left.m12*right.m0 + left.m13*right.m4 + left.m14*right.m8 + left.m15*right.m12; - result.m13 = left.m12*right.m1 + left.m13*right.m5 + left.m14*right.m9 + left.m15*right.m13; - result.m14 = left.m12*right.m2 + left.m13*right.m6 + left.m14*right.m10 + left.m15*right.m14; - result.m15 = left.m12*right.m3 + left.m13*right.m7 + left.m14*right.m11 + left.m15*right.m15; - - return result; -} - -// Transposes provided matrix -static Matrix rlMatrixTranspose(Matrix mat) -{ - Matrix result = { 0 }; - - result.m0 = mat.m0; - result.m1 = mat.m4; - result.m2 = mat.m8; - result.m3 = mat.m12; - result.m4 = mat.m1; - result.m5 = mat.m5; - result.m6 = mat.m9; - result.m7 = mat.m13; - result.m8 = mat.m2; - result.m9 = mat.m6; - result.m10 = mat.m10; - result.m11 = mat.m14; - result.m12 = mat.m3; - result.m13 = mat.m7; - result.m14 = mat.m11; - result.m15 = mat.m15; - - return result; -} - -// Invert provided matrix -static Matrix rlMatrixInvert(Matrix mat) -{ - Matrix result = { 0 }; - - // Cache the matrix values (speed optimization) - float a00 = mat.m0, a01 = mat.m1, a02 = mat.m2, a03 = mat.m3; - float a10 = mat.m4, a11 = mat.m5, a12 = mat.m6, a13 = mat.m7; - float a20 = mat.m8, a21 = mat.m9, a22 = mat.m10, a23 = mat.m11; - float a30 = mat.m12, a31 = mat.m13, a32 = mat.m14, a33 = mat.m15; - - float b00 = a00*a11 - a01*a10; - float b01 = a00*a12 - a02*a10; - float b02 = a00*a13 - a03*a10; - float b03 = a01*a12 - a02*a11; - float b04 = a01*a13 - a03*a11; - float b05 = a02*a13 - a03*a12; - float b06 = a20*a31 - a21*a30; - float b07 = a20*a32 - a22*a30; - float b08 = a20*a33 - a23*a30; - float b09 = a21*a32 - a22*a31; - float b10 = a21*a33 - a23*a31; - float b11 = a22*a33 - a23*a32; - - // Calculate the invert determinant (inlined to avoid double-caching) - float invDet = 1.0f/(b00*b11 - b01*b10 + b02*b09 + b03*b08 - b04*b07 + b05*b06); - - result.m0 = (a11*b11 - a12*b10 + a13*b09)*invDet; - result.m1 = (-a01*b11 + a02*b10 - a03*b09)*invDet; - result.m2 = (a31*b05 - a32*b04 + a33*b03)*invDet; - result.m3 = (-a21*b05 + a22*b04 - a23*b03)*invDet; - result.m4 = (-a10*b11 + a12*b08 - a13*b07)*invDet; - result.m5 = (a00*b11 - a02*b08 + a03*b07)*invDet; - result.m6 = (-a30*b05 + a32*b02 - a33*b01)*invDet; - result.m7 = (a20*b05 - a22*b02 + a23*b01)*invDet; - result.m8 = (a10*b10 - a11*b08 + a13*b06)*invDet; - result.m9 = (-a00*b10 + a01*b08 - a03*b06)*invDet; - result.m10 = (a30*b04 - a31*b02 + a33*b00)*invDet; - result.m11 = (-a20*b04 + a21*b02 - a23*b00)*invDet; - result.m12 = (-a10*b09 + a11*b07 - a12*b06)*invDet; - result.m13 = (a00*b09 - a01*b07 + a02*b06)*invDet; - result.m14 = (-a30*b03 + a31*b01 - a32*b00)*invDet; - result.m15 = (a20*b03 - a21*b01 + a22*b00)*invDet; - - return result; -} - -#endif // RLGL_IMPLEMENTATION diff --git a/third_party/raylib/larch64/libraylib.a b/third_party/raylib/larch64/libraylib.a deleted file mode 100644 index fa538e5214..0000000000 --- a/third_party/raylib/larch64/libraylib.a +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f760af8b4693cf60e3760341e5275890d78d933da2354c4bad0572ec575b970a -size 2001860 diff --git a/third_party/raylib/x86_64/libraylib.a b/third_party/raylib/x86_64/libraylib.a deleted file mode 100644 index ea124c1bcf..0000000000 --- a/third_party/raylib/x86_64/libraylib.a +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:3c928e849b51b04d8e3603cd649184299efed0e9e0fb02201612b967b31efd73 -size 2771092 diff --git a/uv.lock b/uv.lock index f5c128fcb7..e12842ac5b 100644 --- a/uv.lock +++ b/uv.lock @@ -116,12 +116,12 @@ wheels = [ [[package]] name = "bzip2" version = "1.0.8" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=bzip2&rev=releases#12581f30b45b570dd0bbc36055fe1532f5a8ef60" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=bzip2&rev=releases#de7c914a461f68a5fb80d57d534181b6350afe8f" } [[package]] name = "capnproto" version = "1.0.1" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=capnproto&rev=releases#12581f30b45b570dd0bbc36055fe1532f5a8ef60" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=capnproto&rev=releases#de7c914a461f68a5fb80d57d534181b6350afe8f" } [[package]] name = "casadi" @@ -381,7 +381,7 @@ wheels = [ [[package]] name = "eigen" version = "3.4.0" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=eigen&rev=releases#12581f30b45b570dd0bbc36055fe1532f5a8ef60" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=eigen&rev=releases#de7c914a461f68a5fb80d57d534181b6350afe8f" } [[package]] name = "execnet" @@ -395,7 +395,7 @@ wheels = [ [[package]] name = "ffmpeg" version = "7.1.0" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=ffmpeg&rev=releases#12581f30b45b570dd0bbc36055fe1532f5a8ef60" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=ffmpeg&rev=releases#de7c914a461f68a5fb80d57d534181b6350afe8f" } [[package]] name = "fonttools" @@ -442,7 +442,7 @@ wheels = [ [[package]] name = "gcc-arm-none-eabi" version = "13.2.1" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=gcc-arm-none-eabi&rev=releases#12581f30b45b570dd0bbc36055fe1532f5a8ef60" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=gcc-arm-none-eabi&rev=releases#de7c914a461f68a5fb80d57d534181b6350afe8f" } [[package]] name = "ghp-import" @@ -459,7 +459,7 @@ wheels = [ [[package]] name = "git-lfs" version = "3.6.1" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=git-lfs&rev=releases#12581f30b45b570dd0bbc36055fe1532f5a8ef60" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=git-lfs&rev=releases#de7c914a461f68a5fb80d57d534181b6350afe8f" } [[package]] name = "google-crc32c" @@ -577,7 +577,7 @@ wheels = [ [[package]] name = "libjpeg" version = "3.1.0" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=libjpeg&rev=releases#12581f30b45b570dd0bbc36055fe1532f5a8ef60" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=libjpeg&rev=releases#de7c914a461f68a5fb80d57d534181b6350afe8f" } [[package]] name = "libusb1" @@ -593,7 +593,7 @@ wheels = [ [[package]] name = "libyuv" version = "1922.0" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=libyuv&rev=releases#12581f30b45b570dd0bbc36055fe1532f5a8ef60" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=libyuv&rev=releases#de7c914a461f68a5fb80d57d534181b6350afe8f" } [[package]] name = "markdown" @@ -745,7 +745,7 @@ wheels = [ [[package]] name = "ncurses" version = "6.5" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=ncurses&rev=releases#12581f30b45b570dd0bbc36055fe1532f5a8ef60" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=ncurses&rev=releases#de7c914a461f68a5fb80d57d534181b6350afe8f" } [[package]] name = "numpy" @@ -912,7 +912,7 @@ requires-dist = [ { name = "python3-dev", git = "https://github.com/commaai/dependencies.git?subdirectory=python3-dev&rev=releases" }, { name = "pyzmq" }, { name = "qrcode" }, - { name = "raylib", specifier = ">5.5.0.3" }, + { name = "raylib", git = "https://github.com/commaai/dependencies.git?subdirectory=raylib&rev=releases" }, { name = "requests" }, { name = "ruff", marker = "extra == 'testing'" }, { name = "scons" }, @@ -935,7 +935,7 @@ provides-extras = ["docs", "testing", "dev", "tools"] [[package]] name = "openssl3" version = "3.4.1" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=openssl3&rev=releases#12581f30b45b570dd0bbc36055fe1532f5a8ef60" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=openssl3&rev=releases#de7c914a461f68a5fb80d57d534181b6350afe8f" } [[package]] name = "packaging" @@ -1306,7 +1306,7 @@ wheels = [ [[package]] name = "python3-dev" version = "3.12.8" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=python3-dev&rev=releases#12581f30b45b570dd0bbc36055fe1532f5a8ef60" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=python3-dev&rev=releases#de7c914a461f68a5fb80d57d534181b6350afe8f" } [[package]] name = "pyyaml" @@ -1374,20 +1374,10 @@ wheels = [ [[package]] name = "raylib" version = "5.5.0.4" -source = { registry = "https://pypi.org/simple" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=raylib&rev=releases#de7c914a461f68a5fb80d57d534181b6350afe8f" } dependencies = [ { name = "cffi" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/7c/4b/858958762c075c54058ee3b0771838fd505ca908871e6a0397b01086e526/raylib-5.5.0.4.tar.gz", hash = "sha256:996506e8a533cd7a6a3ef6c44ec11f9d6936698f2c394a991af8022be33079a0", size = 184413, upload-time = "2025-12-11T15:32:12.465Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/95/21/9117d7013997a65f6d51c6f56145b2c583eeba8f7c1af71a60776eaae9b9/raylib-5.5.0.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:31f64f71e42fed10e8f3629028c9f5700906e0e573b915cfc2244d7a3f3b2ed9", size = 1635486, upload-time = "2025-12-11T15:27:31.05Z" }, - { url = "https://files.pythonhosted.org/packages/1c/a3/e55039c8f49856c5a194f2b81f27ca6ba2d5900024f09435587e177bfaf2/raylib-5.5.0.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:80bfa053e765d47a9f58d59e321a999184b5a5190e369dd015c12fcfd08d6217", size = 1554132, upload-time = "2025-12-11T15:27:33.291Z" }, - { url = "https://files.pythonhosted.org/packages/58/1c/86bee75ecaa577214da16b374f8de70b45885452703f622c63e06baa0b8e/raylib-5.5.0.4-cp312-cp312-manylinux2010_i686.manylinux_2_12_i686.whl", hash = "sha256:033240c61c1a1fc06fecff747a183671431a4ce63a0c8aafec59217845f86888", size = 2039888, upload-time = "2025-12-11T15:27:36.059Z" }, - { url = "https://files.pythonhosted.org/packages/fb/f9/00763899bb8a178a927b5dda90aca692c80ff6cec5f51e6fee88db3f45c2/raylib-5.5.0.4-cp312-cp312-manylinux2014_aarch64.whl", hash = "sha256:ba87ca50c5748cab75de37a991b7f3f836ce500efbb2d737a923a5f464169088", size = 2198926, upload-time = "2025-12-11T18:50:08.813Z" }, - { url = "https://files.pythonhosted.org/packages/6b/e9/0123385e369904335985ebd59157f7a10c89c3a706dffcf6dace863a1fa2/raylib-5.5.0.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:788830bc371ce067c4930ff46a1b6eca0c9cf27bac88f81b035e4b73cc6bf197", size = 2205629, upload-time = "2025-12-11T15:27:39.491Z" }, - { url = "https://files.pythonhosted.org/packages/5c/fa/c25087b39d2db2d833a52b4056ae62db74e64b4be677f816e0b368e65453/raylib-5.5.0.4-cp312-cp312-win32.whl", hash = "sha256:e09f395035484337776c90e6c9955c5876b988db7e13168dcadb6ed11974f8ee", size = 1457266, upload-time = "2025-12-11T15:27:43.798Z" }, - { url = "https://files.pythonhosted.org/packages/2c/66/a307e61c953ace906ba68ba1174ed8f1e90e68d5fc3e3af9fb7dc46d68d1/raylib-5.5.0.4-cp312-cp312-win_amd64.whl", hash = "sha256:553043a050a31f2ef072f26d3a70373f838a04733f7c5b26a4e9ee3f8caf06ec", size = 1708354, upload-time = "2025-12-11T15:27:45.979Z" }, -] [[package]] name = "requests" @@ -1674,7 +1664,7 @@ wheels = [ [[package]] name = "zeromq" version = "4.3.5" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=zeromq&rev=releases#12581f30b45b570dd0bbc36055fe1532f5a8ef60" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=zeromq&rev=releases#de7c914a461f68a5fb80d57d534181b6350afe8f" } [[package]] name = "zstandard" @@ -1704,4 +1694,4 @@ wheels = [ [[package]] name = "zstd" version = "1.5.6" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=zstd&rev=releases#12581f30b45b570dd0bbc36055fe1532f5a8ef60" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=zstd&rev=releases#de7c914a461f68a5fb80d57d534181b6350afe8f" } From c3d5c5f016114deac3eb723b53459bd3fc59c49e Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sun, 1 Mar 2026 14:12:27 -0800 Subject: [PATCH 002/141] fix nigthly build (#37516) --- .github/workflows/release.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index db0e12234b..a90f064b82 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -22,7 +22,7 @@ jobs: running-workflow-name: 'build master-ci' repo-token: ${{ secrets.GITHUB_TOKEN }} check-regexp: ^((?!.*(build prebuilt|create badges).*).)*$ - - uses: actions/checkout@v6 + - uses: actions/checkout@v4 with: submodules: true fetch-depth: 0 From 6b52ee7ef267f5e13dd19d72dac1841f49729ed5 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sun, 1 Mar 2026 15:40:10 -0800 Subject: [PATCH 003/141] tools cleanup (#37520) --- .../measure_steering_accuracy.py | 0 tools/jotpluggler/README.md | 67 --- tools/jotpluggler/assets/pause.png | 3 - tools/jotpluggler/assets/play.png | 3 - tools/jotpluggler/assets/plus.png | 3 - tools/jotpluggler/assets/split_h.png | 3 - tools/jotpluggler/assets/split_v.png | 3 - tools/jotpluggler/assets/x.png | 3 - tools/jotpluggler/data.py | 360 ------------- tools/jotpluggler/datatree.py | 315 ------------ tools/jotpluggler/layout.py | 477 ------------------ .../layouts/torque-controller.yaml | 128 ----- tools/jotpluggler/pluggle.py | 368 -------------- tools/jotpluggler/views.py | 294 ----------- 14 files changed, 2027 deletions(-) rename tools/{tuning => car_porting}/measure_steering_accuracy.py (100%) delete mode 100644 tools/jotpluggler/README.md delete mode 100644 tools/jotpluggler/assets/pause.png delete mode 100644 tools/jotpluggler/assets/play.png delete mode 100644 tools/jotpluggler/assets/plus.png delete mode 100644 tools/jotpluggler/assets/split_h.png delete mode 100644 tools/jotpluggler/assets/split_v.png delete mode 100644 tools/jotpluggler/assets/x.png delete mode 100644 tools/jotpluggler/data.py delete mode 100644 tools/jotpluggler/datatree.py delete mode 100644 tools/jotpluggler/layout.py delete mode 100644 tools/jotpluggler/layouts/torque-controller.yaml delete mode 100755 tools/jotpluggler/pluggle.py delete mode 100644 tools/jotpluggler/views.py diff --git a/tools/tuning/measure_steering_accuracy.py b/tools/car_porting/measure_steering_accuracy.py similarity index 100% rename from tools/tuning/measure_steering_accuracy.py rename to tools/car_porting/measure_steering_accuracy.py diff --git a/tools/jotpluggler/README.md b/tools/jotpluggler/README.md deleted file mode 100644 index d5e4b8ab0f..0000000000 --- a/tools/jotpluggler/README.md +++ /dev/null @@ -1,67 +0,0 @@ -# JotPluggler - -JotPluggler is a tool to quickly visualize openpilot logs. - -## Usage - -``` -$ ./jotpluggler/pluggle.py -h -usage: pluggle.py [-h] [--demo] [--layout LAYOUT] [route] - -A tool for visualizing openpilot logs. - -positional arguments: - route Optional route name to load on startup. - -options: - -h, --help show this help message and exit - --demo Use the demo route instead of providing one - --layout LAYOUT Path to YAML layout file to load on startup -``` - -Example using route name: - -`./pluggle.py "5beb9b58bd12b691/0000010a--a51155e496"` - -Examples using segment: - -`./pluggle.py "5beb9b58bd12b691/0000010a--a51155e496/1"` - -`./pluggle.py "5beb9b58bd12b691/0000010a--a51155e496/1/q" # use qlogs` - -Example using segment range: - -`./pluggle.py "5beb9b58bd12b691/0000010a--a51155e496/0:1"` - -## Demo - -For a quick demo, run this command: - -`./pluggle.py --demo --layout=layouts/torque-controller.yaml` - - -## Basic Usage/Features: -- The text box to load a route is a the top left of the page, accepts standard openpilot format routes (e.g. `5beb9b58bd12b691/0000010a--a51155e496/0:1`, `https://connect.comma.ai/5beb9b58bd12b691/0000010a--a51155e496/`) -- The Play/Pause button is at the bottom of the screen, you can drag the bottom slider to seek. The timeline in timeseries plots are synced with the slider. -- The Timeseries List sidebar has several dropdowns, the fields each show the field name and value, synced with the timeline (will show N/A until the time of the first message in that field is reached). -- There is a search bar for the timeseries list, you can search for structs or fields, or both by separating with a "/" -- You can drag and drop any numeric/boolean field from the timeseries list into a timeseries panel. -- You can create more panels with the split buttons (buttons with two rectangles, either horizontal or vertical). You can resize the panels by dragging the grip in between any panel. -- You can load and save layouts with the corresponding buttons. Layouts will save all tabs, panels, titles, timeseries, etc. - -## Layouts - -If you create a layout that's useful for others, consider upstreaming it. - -## Plot Interaction Controls - -- **Left click and drag within the plot area** to pan X - - Left click and drag on an axis to pan an individual axis (disabled for Y-axis) -- **Scroll in the plot area** to zoom in X axes, Y-axis is autofit - - Scroll on an axis to zoom an individual axis -- **Right click and drag** to select data and zoom into the selected data - - Left click while box selecting to cancel the selection -- **Double left click** to fit all visible data - - Double left click on an axis to fit the individual axis (disabled for Y-axis, always autofit) -- **Double right click** to open the plot context menu -- **Click legend label icons** to show/hide plot items diff --git a/tools/jotpluggler/assets/pause.png b/tools/jotpluggler/assets/pause.png deleted file mode 100644 index 8040099831..0000000000 --- a/tools/jotpluggler/assets/pause.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:3ea96d8193eb9067a5efdc5d88a3099730ecafa40efcd09d7402bb3efd723603 -size 2305 diff --git a/tools/jotpluggler/assets/play.png b/tools/jotpluggler/assets/play.png deleted file mode 100644 index b1556cf0ab..0000000000 --- a/tools/jotpluggler/assets/play.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:53097ac5403b725ff1841dfa186ea770b4bb3714205824bde36ec3c2a0fb5dba -size 2758 diff --git a/tools/jotpluggler/assets/plus.png b/tools/jotpluggler/assets/plus.png deleted file mode 100644 index 6f8388b24d..0000000000 --- a/tools/jotpluggler/assets/plus.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:248b71eafd1b42b0861da92114da3d625221cd88121fff01e0514bf3d79ff3b1 -size 1364 diff --git a/tools/jotpluggler/assets/split_h.png b/tools/jotpluggler/assets/split_h.png deleted file mode 100644 index 4fd88806e1..0000000000 --- a/tools/jotpluggler/assets/split_h.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:54dd035ff898d881509fa686c402a61af8ef5fb408b92414722da01f773b0d33 -size 2900 diff --git a/tools/jotpluggler/assets/split_v.png b/tools/jotpluggler/assets/split_v.png deleted file mode 100644 index 752e62a4ae..0000000000 --- a/tools/jotpluggler/assets/split_v.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:adbd4e5df1f58694dca9dde46d1d95b4e7471684e42e6bca9f41ea5d346e67c5 -size 3669 diff --git a/tools/jotpluggler/assets/x.png b/tools/jotpluggler/assets/x.png deleted file mode 100644 index 3b2eabd447..0000000000 --- a/tools/jotpluggler/assets/x.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a6d9c90cb0dd906e0b15e1f7f3fd9f0dfad3c3b0b34eeed7a7882768dc5f3961 -size 2053 diff --git a/tools/jotpluggler/data.py b/tools/jotpluggler/data.py deleted file mode 100644 index 756b87bd20..0000000000 --- a/tools/jotpluggler/data.py +++ /dev/null @@ -1,360 +0,0 @@ -import numpy as np -import threading -import multiprocessing -import bisect -from collections import defaultdict -from tqdm import tqdm -from openpilot.common.swaglog import cloudlog -from openpilot.selfdrive.test.process_replay.migration import migrate_all -from openpilot.tools.lib.logreader import _LogFileReader, LogReader - - -def flatten_dict(d: dict, sep: str = "/", prefix: str | None = None) -> dict: - result = {} - stack: list[tuple] = [(d, prefix)] - - while stack: - obj, current_prefix = stack.pop() - - if isinstance(obj, dict): - for key, val in obj.items(): - new_prefix = key if current_prefix is None else f"{current_prefix}{sep}{key}" - if isinstance(val, (dict, list)): - stack.append((val, new_prefix)) - else: - result[new_prefix] = val - elif isinstance(obj, list): - for i, item in enumerate(obj): - new_prefix = f"{current_prefix}{sep}{i}" - if isinstance(item, (dict, list)): - stack.append((item, new_prefix)) - else: - result[new_prefix] = item - else: - if current_prefix is not None: - result[current_prefix] = obj - return result - - -def extract_field_types(schema, prefix, field_types_dict): - stack = [(schema, prefix)] - - while stack: - current_schema, current_prefix = stack.pop() - - for field in current_schema.fields_list: - field_name = field.proto.name - field_path = f"{current_prefix}/{field_name}" - field_proto = field.proto - field_which = field_proto.which() - - field_type = field_proto.slot.type.which() if field_which == 'slot' else field_which - field_types_dict[field_path] = field_type - - if field_which == 'slot': - slot_type = field_proto.slot.type - type_which = slot_type.which() - - if type_which == 'list': - element_type = slot_type.list.elementType.which() - list_path = f"{field_path}/*" - field_types_dict[list_path] = element_type - - if element_type == 'struct': - stack.append((field.schema.elementType, list_path)) - - elif type_which == 'struct': - stack.append((field.schema, field_path)) - - elif field_which == 'group': - stack.append((field.schema, field_path)) - - -def _convert_to_optimal_dtype(values_list, capnp_type): - dtype_mapping = { - 'bool': np.bool_, 'int8': np.int8, 'int16': np.int16, 'int32': np.int32, 'int64': np.int64, - 'uint8': np.uint8, 'uint16': np.uint16, 'uint32': np.uint32, 'uint64': np.uint64, - 'float32': np.float32, 'float64': np.float64, 'text': object, 'data': object, - 'enum': object, 'anyPointer': object, - } - - target_dtype = dtype_mapping.get(capnp_type, object) - return np.array(values_list, dtype=target_dtype) - - -def _match_field_type(field_path, field_types): - if field_path in field_types: - return field_types[field_path] - - path_parts = field_path.split('/') - template_parts = [p if not p.isdigit() else '*' for p in path_parts] - template_path = '/'.join(template_parts) - return field_types.get(template_path) - - -def _get_field_times_values(segment, field_name): - if field_name not in segment: - return None, None - - field_data = segment[field_name] - segment_times = segment['t'] - - if field_data['sparse']: - if len(field_data['t_index']) == 0: - return None, None - return segment_times[field_data['t_index']], field_data['values'] - else: - return segment_times, field_data['values'] - - -def msgs_to_time_series(msgs): - """Extract scalar fields and return (time_series_data, start_time, end_time).""" - collected_data = defaultdict(lambda: {'timestamps': [], 'columns': defaultdict(list), 'sparse_fields': set()}) - field_types = {} - extracted_schemas = set() - min_time = max_time = None - - for msg in msgs: - typ = msg.which() - timestamp = msg.logMonoTime * 1e-9 - if typ != 'initData': - if min_time is None: - min_time = timestamp - max_time = timestamp - - sub_msg = getattr(msg, typ) - if not hasattr(sub_msg, 'to_dict'): - continue - - if hasattr(sub_msg, 'schema') and typ not in extracted_schemas: - extract_field_types(sub_msg.schema, typ, field_types) - extracted_schemas.add(typ) - - try: - msg_dict = sub_msg.to_dict(verbose=True) - except Exception as e: - cloudlog.warning(f"Failed to convert sub_msg.to_dict() for message of type: {typ}: {e}") - continue - - flat_dict = flatten_dict(msg_dict) - flat_dict['_valid'] = msg.valid - field_types[f"{typ}/_valid"] = 'bool' - - type_data = collected_data[typ] - columns, sparse_fields = type_data['columns'], type_data['sparse_fields'] - known_fields = set(columns.keys()) - missing_fields = known_fields - flat_dict.keys() - - for field, value in flat_dict.items(): - if field not in known_fields and type_data['timestamps']: - sparse_fields.add(field) - columns[field].append(value) - if value is None: - sparse_fields.add(field) - - for field in missing_fields: - columns[field].append(None) - sparse_fields.add(field) - - type_data['timestamps'].append(timestamp) - - final_result = {} - for typ, data in collected_data.items(): - if not data['timestamps']: - continue - - typ_result = {'t': np.array(data['timestamps'], dtype=np.float64)} - sparse_fields = data['sparse_fields'] - - for field_name, values in data['columns'].items(): - if len(values) < len(data['timestamps']): - values = [None] * (len(data['timestamps']) - len(values)) + values - sparse_fields.add(field_name) - - capnp_type = _match_field_type(f"{typ}/{field_name}", field_types) - - if field_name in sparse_fields: # extract non-None values and their indices - non_none_indices = [] - non_none_values = [] - for i, value in enumerate(values): - if value is not None: - non_none_indices.append(i) - non_none_values.append(value) - - if non_none_values: # check if indices > uint16 max, currently would require a 1000+ Hz signal since indices are within segments - assert max(non_none_indices) <= 65535, f"Sparse field {typ}/{field_name} has timestamp indices exceeding uint16 max. Max: {max(non_none_indices)}" - - typ_result[field_name] = { - 'values': _convert_to_optimal_dtype(non_none_values, capnp_type), - 'sparse': True, - 't_index': np.array(non_none_indices, dtype=np.uint16), - } - else: # dense representation - typ_result[field_name] = {'values': _convert_to_optimal_dtype(values, capnp_type), 'sparse': False} - - final_result[typ] = typ_result - - return final_result, min_time or 0.0, max_time or 0.0 - - -def _process_segment(segment_identifier: str): - try: - lr = _LogFileReader(segment_identifier, sort_by_time=True) - migrated_msgs = migrate_all(lr) - return msgs_to_time_series(migrated_msgs) - except Exception as e: - cloudlog.warning(f"Warning: Failed to process segment {segment_identifier}: {e}") - return {}, 0.0, 0.0 - - -class DataManager: - def __init__(self): - self._segments = [] - self._segment_starts = [] - self._start_time = 0.0 - self._duration = 0.0 - self._paths = set() - self._observers = [] - self._loading = False - self._lock = threading.RLock() - - def load_route(self, route: str) -> None: - if self._loading: - return - self._reset() - threading.Thread(target=self._load_async, args=(route,), daemon=True).start() - - def get_timeseries(self, path: str): - with self._lock: - msg_type, field = path.split('/', 1) - times, values = [], [] - - for segment in self._segments: - if msg_type in segment: - field_times, field_values = _get_field_times_values(segment[msg_type], field) - if field_times is not None: - times.append(field_times) - values.append(field_values) - - if not times: - return np.array([]), np.array([]) - - combined_times = np.concatenate(times) - self._start_time - - if len(values) > 1: - first_dtype = values[0].dtype - if all(arr.dtype == first_dtype for arr in values): # check if all arrays have compatible dtypes - combined_values = np.concatenate(values) - else: - combined_values = np.concatenate([arr.astype(object) for arr in values]) - else: - combined_values = values[0] if values else np.array([]) - - return combined_times, combined_values - - def get_value_at(self, path: str, time: float): - with self._lock: - MAX_LOOKBACK = 5.0 # seconds - absolute_time = self._start_time + time - message_type, field = path.split('/', 1) - current_index = bisect.bisect_right(self._segment_starts, absolute_time) - 1 - for index in (current_index, current_index - 1): - if not 0 <= index < len(self._segments): - continue - segment = self._segments[index].get(message_type) - if not segment: - continue - times, values = _get_field_times_values(segment, field) - if times is None or len(times) == 0 or (index != current_index and absolute_time - times[-1] > MAX_LOOKBACK): - continue - position = np.searchsorted(times, absolute_time, 'right') - 1 - if position >= 0 and absolute_time - times[position] <= MAX_LOOKBACK: - return values[position] - return None - - def get_all_paths(self): - with self._lock: - return sorted(self._paths) - - def get_duration(self): - with self._lock: - return self._duration - - def is_plottable(self, path: str): - _, values = self.get_timeseries(path) - if len(values) == 0: - return False - return np.issubdtype(values.dtype, np.number) or np.issubdtype(values.dtype, np.bool_) - - def add_observer(self, callback): - with self._lock: - self._observers.append(callback) - - def remove_observer(self, callback): - with self._lock: - if callback in self._observers: - self._observers.remove(callback) - - def _reset(self): - with self._lock: - self._loading = True - self._segments.clear() - self._segment_starts.clear() - self._paths.clear() - self._start_time = self._duration = 0.0 - observers = self._observers.copy() - - for callback in observers: - callback({'reset': True}) - - def _load_async(self, route: str): - try: - lr = LogReader(route, sort_by_time=True) - if not lr.logreader_identifiers: - cloudlog.warning(f"Warning: No log segments found for route: {route}") - return - - total_segments = len(lr.logreader_identifiers) - with self._lock: - observers = self._observers.copy() - for callback in observers: - callback({'metadata_loaded': True, 'total_segments': total_segments}) - - num_processes = max(1, multiprocessing.cpu_count() // 2) - with multiprocessing.Pool(processes=num_processes) as pool, tqdm(total=len(lr.logreader_identifiers), desc="Processing Segments") as pbar: - for segment_result, start_time, end_time in pool.imap(_process_segment, lr.logreader_identifiers): - pbar.update(1) - if segment_result: - self._add_segment(segment_result, start_time, end_time) - except Exception: - cloudlog.exception(f"Error loading route {route}:") - finally: - self._finalize_loading() - - def _add_segment(self, segment_data: dict, start_time: float, end_time: float): - with self._lock: - self._segments.append(segment_data) - self._segment_starts.append(start_time) - - if len(self._segments) == 1: - self._start_time = start_time - self._duration = end_time - self._start_time - - for msg_type, data in segment_data.items(): - for field_name in data.keys(): - if field_name != 't': - self._paths.add(f"{msg_type}/{field_name}") - - observers = self._observers.copy() - - for callback in observers: - callback({'segment_added': True, 'duration': self._duration, 'segment_count': len(self._segments)}) - - def _finalize_loading(self): - with self._lock: - self._loading = False - observers = self._observers.copy() - duration = self._duration - - for callback in observers: - callback({'loading_complete': True, 'duration': duration}) diff --git a/tools/jotpluggler/datatree.py b/tools/jotpluggler/datatree.py deleted file mode 100644 index 4f3219dc1b..0000000000 --- a/tools/jotpluggler/datatree.py +++ /dev/null @@ -1,315 +0,0 @@ -import os -import re -import threading -import numpy as np -import dearpygui.dearpygui as dpg - - -class DataTreeNode: - def __init__(self, name: str, full_path: str = "", parent=None): - self.name = name - self.full_path = full_path - self.parent = parent - self.children: dict[str, DataTreeNode] = {} - self.filtered_children: dict[str, DataTreeNode] = {} - self.created_children: dict[str, DataTreeNode] = {} - self.is_leaf = False - self.is_plottable: bool | None = None - self.ui_created = False - self.children_ui_created = False - self.ui_tag: str | None = None - - -class DataTree: - MAX_NODES_PER_FRAME = 50 - - def __init__(self, data_manager, playback_manager): - self.data_manager = data_manager - self.playback_manager = playback_manager - self.current_search = "" - self.data_tree = DataTreeNode(name="root") - self._build_queue: dict[str, tuple[DataTreeNode, DataTreeNode, str | int]] = {} # full_path -> (node, parent, before_tag) - self._current_created_paths: set[str] = set() - self._current_filtered_paths: set[str] = set() - self._path_to_node: dict[str, DataTreeNode] = {} # full_path -> node - self._expanded_tags: set[str] = set() - self._item_handlers: dict[str, str] = {} # ui_tag -> handler_tag - self._char_width = None - self._queued_search = None - self._new_data = False - self._ui_lock = threading.RLock() - self._handlers_to_delete = [] - self.data_manager.add_observer(self._on_data_loaded) - - def create_ui(self, parent_tag: str): - with dpg.child_window(parent=parent_tag, border=False, width=-1, height=-1): - dpg.add_text("Timeseries List") - dpg.add_separator() - dpg.add_input_text(tag="search_input", width=-1, hint="Search fields...", callback=self.search_data) - dpg.add_separator() - with dpg.child_window(border=False, width=-1, height=-1): - with dpg.group(tag="data_tree_container"): - pass - - def _on_data_loaded(self, data: dict): - with self._ui_lock: - if data.get('segment_added') or data.get('reset'): - self._new_data = True - - def update_frame(self, font): - if self._handlers_to_delete: # we need to do everything in main thread, frame callbacks are flaky - dpg.render_dearpygui_frame() # wait a frame to ensure queued callbacks are done - with self._ui_lock: - for handler in self._handlers_to_delete: - dpg.delete_item(handler) - self._handlers_to_delete.clear() - - with self._ui_lock: - if self._char_width is None: - if size := dpg.get_text_size(" ", font=font): - self._char_width = size[0] / 2 # we scale font 2x and downscale to fix hidpi bug - - if self._new_data: - self._process_path_change() - self._new_data = False - return - - if self._queued_search is not None: - self.current_search = self._queued_search - self._process_path_change() - self._queued_search = None - return - - nodes_processed = 0 - while self._build_queue and nodes_processed < self.MAX_NODES_PER_FRAME: - child_node, parent, before_tag = self._build_queue.pop(next(iter(self._build_queue))) - parent_tag = "data_tree_container" if parent.name == "root" else parent.ui_tag - if not child_node.ui_created: - if child_node.is_leaf: - self._create_leaf_ui(child_node, parent_tag, before_tag) - else: - self._create_tree_node_ui(child_node, parent_tag, before_tag) - parent.created_children[child_node.name] = parent.children[child_node.name] - self._current_created_paths.add(child_node.full_path) - nodes_processed += 1 - - def _process_path_change(self): - self._build_queue.clear() - search_term = self.current_search.strip().lower() - all_paths = set(self.data_manager.get_all_paths()) - new_filtered_leafs = {path for path in all_paths if self._should_show_path(path, search_term)} - new_filtered_paths = set(new_filtered_leafs) - for path in new_filtered_leafs: - parts = path.split('/') - for i in range(1, len(parts)): - prefix = '/'.join(parts[:i]) - new_filtered_paths.add(prefix) - created_paths_to_remove = self._current_created_paths - new_filtered_paths - filtered_paths_to_remove = self._current_filtered_paths - new_filtered_leafs - - if created_paths_to_remove or filtered_paths_to_remove: - self._remove_paths_from_tree(created_paths_to_remove, filtered_paths_to_remove) - self._apply_expansion_to_tree(self.data_tree, search_term) - - paths_to_add = new_filtered_leafs - self._current_created_paths - if paths_to_add: - self._add_paths_to_tree(paths_to_add) - self._apply_expansion_to_tree(self.data_tree, search_term) - self._current_filtered_paths = new_filtered_paths - - def _remove_paths_from_tree(self, created_paths_to_remove, filtered_paths_to_remove): - for path in sorted(created_paths_to_remove, reverse=True): - current_node = self._path_to_node[path] - - if len(current_node.created_children) == 0: - self._current_created_paths.remove(current_node.full_path) - if item_handler_tag := self._item_handlers.get(current_node.ui_tag): - dpg.configure_item(item_handler_tag, show=False) - self._handlers_to_delete.append(item_handler_tag) - del self._item_handlers[current_node.ui_tag] - dpg.delete_item(current_node.ui_tag) - current_node.ui_created = False - current_node.ui_tag = None - current_node.children_ui_created = False - del current_node.parent.created_children[current_node.name] - del current_node.parent.filtered_children[current_node.name] - - for path in filtered_paths_to_remove: - parts = path.split('/') - current_node = self._path_to_node[path] - - part_array_index = -1 - while len(current_node.filtered_children) == 0 and part_array_index >= -len(parts): - current_node = current_node.parent - if parts[part_array_index] in current_node.filtered_children: - del current_node.filtered_children[parts[part_array_index]] - part_array_index -= 1 - - def _add_paths_to_tree(self, paths): - parent_nodes_to_recheck = set() - for path in sorted(paths): - parts = path.split('/') - current_node = self.data_tree - current_path_prefix = "" - - for i, part in enumerate(parts): - current_path_prefix = f"{current_path_prefix}/{part}" if current_path_prefix else part - if i < len(parts): - parent_nodes_to_recheck.add(current_node) # for incremental changes from new data - if part not in current_node.children: - current_node.children[part] = DataTreeNode(name=part, full_path=current_path_prefix, parent=current_node) - self._path_to_node[current_path_prefix] = current_node.children[part] - current_node.filtered_children[part] = current_node.children[part] - current_node = current_node.children[part] - - if not current_node.is_leaf: - current_node.is_leaf = True - - for p_node in parent_nodes_to_recheck: - p_node.children_ui_created = False - self._request_children_build(p_node) - - def _get_node_label_and_expand(self, node: DataTreeNode, search_term: str): - label = f"{node.name} ({len(node.filtered_children)} fields)" - expand = len(search_term) > 0 and any(search_term in path for path in self._get_descendant_paths(node)) - if expand and node.parent and len(node.parent.filtered_children) > 100 and len(node.filtered_children) > 2: - label += " (+)" # symbol for large lists which aren't fully expanded for performance (only affects procLog rn) - expand = False - return label, expand - - def _apply_expansion_to_tree(self, node: DataTreeNode, search_term: str): - if node.ui_created and not node.is_leaf and node.ui_tag and dpg.does_item_exist(node.ui_tag): - label, expand = self._get_node_label_and_expand(node, search_term) - if expand: - self._expanded_tags.add(node.ui_tag) - dpg.set_value(node.ui_tag, expand) - elif node.ui_tag in self._expanded_tags: # not expanded and was expanded - self._expanded_tags.remove(node.ui_tag) - dpg.set_value(node.ui_tag, expand) - dpg.delete_item(node.ui_tag, children_only=True) # delete children (not visible since collapsed) - self._reset_ui_state_recursive(node) - node.children_ui_created = False - dpg.set_item_label(node.ui_tag, label) - for child in node.created_children.values(): - self._apply_expansion_to_tree(child, search_term) - - def _reset_ui_state_recursive(self, node: DataTreeNode): - for child in node.created_children.values(): - if child.ui_tag is not None: - if item_handler_tag := self._item_handlers.get(child.ui_tag): - self._handlers_to_delete.append(item_handler_tag) - dpg.configure_item(item_handler_tag, show=False) - del self._item_handlers[child.ui_tag] - self._reset_ui_state_recursive(child) - child.ui_created = False - child.ui_tag = None - child.children_ui_created = False - self._current_created_paths.remove(child.full_path) - node.created_children.clear() - - def search_data(self): - with self._ui_lock: - self._queued_search = dpg.get_value("search_input") - - def _create_tree_node_ui(self, node: DataTreeNode, parent_tag: str, before: str | int): - node.ui_tag = f"tree_{node.full_path}" - search_term = self.current_search.strip().lower() - label, expand = self._get_node_label_and_expand(node, search_term) - if expand: - self._expanded_tags.add(node.ui_tag) - elif node.ui_tag in self._expanded_tags: - self._expanded_tags.remove(node.ui_tag) - - with dpg.tree_node( - label=label, parent=parent_tag, tag=node.ui_tag, default_open=expand, open_on_arrow=True, open_on_double_click=True, before=before, delay_search=True - ): - with dpg.item_handler_registry() as handler_tag: - dpg.add_item_toggled_open_handler(callback=lambda s, a, u: self._request_children_build(node)) - dpg.add_item_visible_handler(callback=lambda s, a, u: self._request_children_build(node)) - dpg.bind_item_handler_registry(node.ui_tag, handler_tag) - self._item_handlers[node.ui_tag] = handler_tag - node.ui_created = True - - def _create_leaf_ui(self, node: DataTreeNode, parent_tag: str, before: str | int): - node.ui_tag = f"leaf_{node.full_path}" - with dpg.group(parent=parent_tag, tag=node.ui_tag, before=before, delay_search=True): - with dpg.table(header_row=False, policy=dpg.mvTable_SizingStretchProp, delay_search=True): - dpg.add_table_column(init_width_or_weight=0.5) - dpg.add_table_column(init_width_or_weight=0.5) - with dpg.table_row(): - dpg.add_text(node.name) - dpg.add_text("N/A", tag=f"value_{node.full_path}") - - if node.is_plottable is None: - node.is_plottable = self.data_manager.is_plottable(node.full_path) - if node.is_plottable: - with dpg.drag_payload(parent=node.ui_tag, drag_data=node.full_path, payload_type="TIMESERIES_PAYLOAD"): - dpg.add_text(f"Plot: {node.full_path}") - - with dpg.item_handler_registry() as handler_tag: - dpg.add_item_visible_handler(callback=self._on_item_visible, user_data=node.full_path) - dpg.bind_item_handler_registry(node.ui_tag, handler_tag) - self._item_handlers[node.ui_tag] = handler_tag - node.ui_created = True - - def _on_item_visible(self, sender, app_data, user_data): - with self._ui_lock: - path = user_data - value_tag = f"value_{path}" - if not dpg.does_item_exist(value_tag): - return - value_column_width = dpg.get_item_rect_size(f"leaf_{path}")[0] // 2 - value = self.data_manager.get_value_at(path, self.playback_manager.current_time_s) - if value is not None: - formatted_value = self.format_and_truncate(value, value_column_width, self._char_width) - dpg.set_value(value_tag, formatted_value) - else: - dpg.set_value(value_tag, "N/A") - - def _request_children_build(self, node: DataTreeNode): - with self._ui_lock: - if not node.children_ui_created and (node.name == "root" or (node.ui_tag is not None and dpg.get_value(node.ui_tag))): # check root or node expanded - sorted_children = sorted(node.filtered_children.values(), key=self._natural_sort_key) - next_existing: list[int | str] = [0] * len(sorted_children) - current_before_tag: int | str = 0 - - for i in range(len(sorted_children) - 1, -1, -1): # calculate "before_tag" for correct ordering when incrementally building tree - child = sorted_children[i] - next_existing[i] = current_before_tag - if child.ui_created: - candidate_tag = f"leaf_{child.full_path}" if child.is_leaf else f"tree_{child.full_path}" - if dpg.does_item_exist(candidate_tag): - current_before_tag = candidate_tag - - for i, child_node in enumerate(sorted_children): - if not child_node.ui_created: - before_tag = next_existing[i] - self._build_queue[child_node.full_path] = (child_node, node, before_tag) - node.children_ui_created = True - - def _should_show_path(self, path: str, search_term: str) -> bool: - if 'DEPRECATED' in path and not os.environ.get('SHOW_DEPRECATED'): - return False - return not search_term or search_term in path.lower() - - def _natural_sort_key(self, node: DataTreeNode): - node_type_key = node.is_leaf - parts = [int(p) if p.isdigit() else p.lower() for p in re.split(r'(\d+)', node.name) if p] - return (node_type_key, parts) - - def _get_descendant_paths(self, node: DataTreeNode): - for child_name, child_node in node.filtered_children.items(): - child_name_lower = child_name.lower() - if child_node.is_leaf: - yield child_name_lower - else: - for path in self._get_descendant_paths(child_node): - yield f"{child_name_lower}/{path}" - - @staticmethod - def format_and_truncate(value, available_width: float, char_width: float) -> str: - s = f"{value:.5f}" if np.issubdtype(type(value), np.floating) else str(value) - max_chars = int(available_width / char_width) - if len(s) > max_chars: - return s[: max(0, max_chars - 3)] + "..." - return s diff --git a/tools/jotpluggler/layout.py b/tools/jotpluggler/layout.py deleted file mode 100644 index 13fbee54e2..0000000000 --- a/tools/jotpluggler/layout.py +++ /dev/null @@ -1,477 +0,0 @@ -import dearpygui.dearpygui as dpg -from openpilot.tools.jotpluggler.data import DataManager -from openpilot.tools.jotpluggler.views import TimeSeriesPanel - -GRIP_SIZE = 4 -MIN_PANE_SIZE = 60 - -class LayoutManager: - def __init__(self, data_manager, playback_manager, worker_manager, scale: float = 1.0): - self.data_manager = data_manager - self.playback_manager = playback_manager - self.worker_manager = worker_manager - self.scale = scale - self.container_tag = "plot_layout_container" - self.tab_bar_tag = "tab_bar_container" - self.tab_content_tag = "tab_content_area" - - self.active_tab = 0 - initial_panel_layout = PanelLayoutManager(data_manager, playback_manager, worker_manager, scale) - self.tabs: dict = {0: {"name": "Tab 1", "panel_layout": initial_panel_layout}} - self._next_tab_id = self.active_tab + 1 - - def to_dict(self) -> dict: - return { - "tabs": { - str(tab_id): { - "name": tab_data["name"], - "panel_layout": tab_data["panel_layout"].to_dict() - } - for tab_id, tab_data in self.tabs.items() - } - } - - def clear_and_load_from_dict(self, data: dict): - tab_ids_to_close = list(self.tabs.keys()) - for tab_id in tab_ids_to_close: - self.close_tab(tab_id, force=True) - - for tab_id_str, tab_data in data["tabs"].items(): - tab_id = int(tab_id_str) - panel_layout = PanelLayoutManager.load_from_dict( - tab_data["panel_layout"], self.data_manager, self.playback_manager, - self.worker_manager, self.scale - ) - self.tabs[tab_id] = { - "name": tab_data["name"], - "panel_layout": panel_layout - } - - self.active_tab = min(self.tabs.keys()) if self.tabs else 0 - self._next_tab_id = max(self.tabs.keys()) + 1 if self.tabs else 1 - - def create_ui(self, parent_tag: str): - if dpg.does_item_exist(self.container_tag): - dpg.delete_item(self.container_tag) - - with dpg.child_window(tag=self.container_tag, parent=parent_tag, border=False, width=-1, height=-1, no_scrollbar=True, no_scroll_with_mouse=True): - self._create_tab_bar() - self._create_tab_content() - dpg.bind_item_theme(self.tab_bar_tag, "tab_bar_theme") - - def _create_tab_bar(self): - text_size = int(13 * self.scale) - with dpg.child_window(tag=self.tab_bar_tag, parent=self.container_tag, height=(text_size + 8), border=False, horizontal_scrollbar=True): - with dpg.group(horizontal=True, tag="tab_bar_group"): - for tab_id, tab_data in self.tabs.items(): - self._create_tab_ui(tab_id, tab_data["name"]) - dpg.add_image_button(texture_tag="plus_texture", callback=self.add_tab, width=text_size, height=text_size, tag="add_tab_button") - dpg.bind_item_theme("add_tab_button", "inactive_tab_theme") - - def _create_tab_ui(self, tab_id: int, tab_name: str): - text_size = int(13 * self.scale) - tab_width = int(140 * self.scale) - with dpg.child_window(width=tab_width, height=-1, border=False, no_scrollbar=True, tag=f"tab_window_{tab_id}", parent="tab_bar_group"): - with dpg.group(horizontal=True, tag=f"tab_group_{tab_id}"): - dpg.add_input_text( - default_value=tab_name, width=tab_width - text_size - 16, callback=lambda s, v, u: self.rename_tab(u, v), user_data=tab_id, tag=f"tab_input_{tab_id}" - ) - dpg.add_image_button( - texture_tag="x_texture", callback=lambda s, a, u: self.close_tab(u), user_data=tab_id, width=text_size, height=text_size, tag=f"tab_close_{tab_id}" - ) - with dpg.item_handler_registry(tag=f"tab_handler_{tab_id}"): - dpg.add_item_clicked_handler(callback=lambda s, a, u: self.switch_tab(u), user_data=tab_id) - dpg.bind_item_handler_registry(f"tab_group_{tab_id}", f"tab_handler_{tab_id}") - - theme_tag = "active_tab_theme" if tab_id == self.active_tab else "inactive_tab_theme" - dpg.bind_item_theme(f"tab_window_{tab_id}", theme_tag) - - def _create_tab_content(self): - with dpg.child_window(tag=self.tab_content_tag, parent=self.container_tag, border=False, width=-1, height=-1, no_scrollbar=True, no_scroll_with_mouse=True): - if self.active_tab in self.tabs: - active_panel_layout = self.tabs[self.active_tab]["panel_layout"] - active_panel_layout.create_ui() - - def add_tab(self): - new_panel_layout = PanelLayoutManager(self.data_manager, self.playback_manager, self.worker_manager, self.scale) - new_tab = {"name": f"Tab {self._next_tab_id + 1}", "panel_layout": new_panel_layout} - self.tabs[self._next_tab_id] = new_tab - self._create_tab_ui(self._next_tab_id, new_tab["name"]) - dpg.move_item("add_tab_button", parent="tab_bar_group") # move plus button to end - self.switch_tab(self._next_tab_id) - self._next_tab_id += 1 - - def close_tab(self, tab_id: int, force = False): - if len(self.tabs) <= 1 and not force: - return # don't allow closing the last tab - - tab_to_close = self.tabs[tab_id] - tab_to_close["panel_layout"].destroy_ui() - for suffix in ["window", "group", "input", "close", "handler"]: - tag = f"tab_{suffix}_{tab_id}" - if dpg.does_item_exist(tag): - dpg.delete_item(tag) - del self.tabs[tab_id] - - if self.active_tab == tab_id and self.tabs: # switch to another tab if we closed the active one - self.active_tab = next(iter(self.tabs.keys())) - self._switch_tab_content() - dpg.bind_item_theme(f"tab_window_{self.active_tab}", "active_tab_theme") - - def switch_tab(self, tab_id: int): - if tab_id == self.active_tab or tab_id not in self.tabs: - return - - current_panel_layout = self.tabs[self.active_tab]["panel_layout"] - current_panel_layout.destroy_ui() - dpg.bind_item_theme(f"tab_window_{self.active_tab}", "inactive_tab_theme") # deactivate old tab - self.active_tab = tab_id - dpg.bind_item_theme(f"tab_window_{tab_id}", "active_tab_theme") # activate new tab - self._switch_tab_content() - - def _switch_tab_content(self): - dpg.delete_item(self.tab_content_tag, children_only=True) - active_panel_layout = self.tabs[self.active_tab]["panel_layout"] - active_panel_layout.create_ui() - active_panel_layout.update_all_panels() - - def rename_tab(self, tab_id: int, new_name: str): - if tab_id in self.tabs: - self.tabs[tab_id]["name"] = new_name - - def update_all_panels(self): - self.tabs[self.active_tab]["panel_layout"].update_all_panels() - - def on_viewport_resize(self): - self.tabs[self.active_tab]["panel_layout"].on_viewport_resize() - -class PanelLayoutManager: - def __init__(self, data_manager: DataManager, playback_manager, worker_manager, scale: float = 1.0): - self.data_manager = data_manager - self.playback_manager = playback_manager - self.worker_manager = worker_manager - self.scale = scale - self.active_panels: list = [] - self.parent_tag = "tab_content_area" - self._queue_resize = False - self._created_handler_tags: set[str] = set() - - self.grip_size = int(GRIP_SIZE * self.scale) - self.min_pane_size = int(MIN_PANE_SIZE * self.scale) - - initial_panel = TimeSeriesPanel(data_manager, playback_manager, worker_manager) - self.layout: dict = {"type": "panel", "panel": initial_panel} - - def to_dict(self) -> dict: - return self._layout_to_dict(self.layout) - - def _layout_to_dict(self, layout: dict) -> dict: - if layout["type"] == "panel": - return { - "type": "panel", - "panel": layout["panel"].to_dict() - } - else: # split - return { - "type": "split", - "orientation": layout["orientation"], - "proportions": layout["proportions"], - "children": [self._layout_to_dict(child) for child in layout["children"]] - } - - @classmethod - def load_from_dict(cls, data: dict, data_manager, playback_manager, worker_manager, scale: float = 1.0): - manager = cls(data_manager, playback_manager, worker_manager, scale) - manager.layout = manager._dict_to_layout(data) - return manager - - def _dict_to_layout(self, data: dict) -> dict: - if data["type"] == "panel": - panel_data = data["panel"] - if panel_data["type"] == "timeseries": - panel = TimeSeriesPanel.load_from_dict( - panel_data, self.data_manager, self.playback_manager, self.worker_manager - ) - return {"type": "panel", "panel": panel} - else: - # Handle future panel types here or make a general mapping - raise ValueError(f"Unknown panel type: {panel_data['type']}") - else: # split - return { - "type": "split", - "orientation": data["orientation"], - "proportions": data["proportions"], - "children": [self._dict_to_layout(child) for child in data["children"]] - } - - def create_ui(self): - self.active_panels.clear() - if dpg.does_item_exist(self.parent_tag): - dpg.delete_item(self.parent_tag, children_only=True) - self._cleanup_all_handlers() - - container_width, container_height = dpg.get_item_rect_size(self.parent_tag) - if container_width == 0 and container_height == 0: - self._queue_resize = True - self._create_ui_recursive(self.layout, self.parent_tag, [], container_width, container_height) - - def destroy_ui(self): - self._cleanup_ui_recursive(self.layout, []) - self._cleanup_all_handlers() - self.active_panels.clear() - - def _cleanup_all_handlers(self): - for handler_tag in list(self._created_handler_tags): - if dpg.does_item_exist(handler_tag): - dpg.delete_item(handler_tag) - self._created_handler_tags.clear() - - def _create_ui_recursive(self, layout: dict, parent_tag: str, path: list[int], width: int, height: int): - if layout["type"] == "panel": - self._create_panel_ui(layout, parent_tag, path, width, height) - else: - self._create_split_ui(layout, parent_tag, path, width, height) - - def _create_panel_ui(self, layout: dict, parent_tag: str, path: list[int], width: int, height: int): - panel_tag = self._path_to_tag(path, "panel") - panel = layout["panel"] - self.active_panels.append(panel) - text_size = int(13 * self.scale) - bar_height = (text_size + 24) if width < int(329 * self.scale + 64) else (text_size + 8) # adjust height to allow for scrollbar - - with dpg.child_window(parent=parent_tag, border=False, width=-1, height=-1, no_scrollbar=True): - with dpg.group(horizontal=True): - with dpg.child_window(tag=panel_tag, width=-(text_size + 16), height=bar_height, horizontal_scrollbar=True, no_scroll_with_mouse=True, border=False): - with dpg.group(horizontal=True): - # if you change the widths make sure to change the sum of widths (currently 329 * scale) - dpg.add_input_text(default_value=panel.title, width=int(150 * self.scale), callback=lambda s, v: setattr(panel, "title", v)) - dpg.add_combo(items=["Time Series"], default_value="Time Series", width=int(100 * self.scale)) - dpg.add_button(label="Clear", callback=lambda: self.clear_panel(panel), width=int(40 * self.scale)) - dpg.add_image_button(texture_tag="split_h_texture", callback=lambda: self.split_panel(path, 0), width=text_size, height=text_size) - dpg.add_image_button(texture_tag="split_v_texture", callback=lambda: self.split_panel(path, 1), width=text_size, height=text_size) - dpg.add_image_button(texture_tag="x_texture", callback=lambda: self.delete_panel(path), width=text_size, height=text_size) - - dpg.add_separator() - - content_tag = self._path_to_tag(path, "content") - with dpg.child_window(tag=content_tag, border=False, height=-1, width=-1, no_scrollbar=True): - panel.create_ui(content_tag) - - def _create_split_ui(self, layout: dict, parent_tag: str, path: list[int], width: int, height: int): - split_tag = self._path_to_tag(path, "split") - orientation, _, pane_sizes = self._get_split_geometry(layout, (width, height)) - - with dpg.group(tag=split_tag, parent=parent_tag, horizontal=orientation == 0): - for i, child_layout in enumerate(layout["children"]): - child_path = path + [i] - container_tag = self._path_to_tag(child_path, "container") - pane_width, pane_height = [(pane_sizes[i], -1), (-1, pane_sizes[i])][orientation] # fill 2nd dim up to the border - with dpg.child_window(tag=container_tag, width=pane_width, height=pane_height, border=False, no_scrollbar=True): - child_width, child_height = [(pane_sizes[i], height), (width, pane_sizes[i])][orientation] - self._create_ui_recursive(child_layout, container_tag, child_path, child_width, child_height) - if i < len(layout["children"]) - 1: - self._create_grip(split_tag, path, i, orientation) - - def clear_panel(self, panel): - panel.clear() - - def delete_panel(self, panel_path: list[int]): - if not panel_path: # Root deletion - old_panel = self.layout["panel"] - old_panel.destroy_ui() - self.active_panels.remove(old_panel) - new_panel = TimeSeriesPanel(self.data_manager, self.playback_manager, self.worker_manager) - self.layout = {"type": "panel", "panel": new_panel} - self._rebuild_ui_at_path([]) - return - - parent, child_index = self._get_parent_and_index(panel_path) - layout_to_delete = parent["children"][child_index] - self._cleanup_ui_recursive(layout_to_delete, panel_path) - - parent["children"].pop(child_index) - parent["proportions"].pop(child_index) - - if len(parent["children"]) == 1: # remove parent and collapse - remaining_child = parent["children"][0] - if len(panel_path) == 1: # parent is at root level - promote remaining child to root - self.layout = remaining_child - self._rebuild_ui_at_path([]) - else: # replace parent with remaining child in grandparent - grandparent_path = panel_path[:-2] - parent_index = panel_path[-2] - self._replace_layout_at_path(grandparent_path + [parent_index], remaining_child) - self._rebuild_ui_at_path(grandparent_path + [parent_index]) - else: # redistribute proportions - equal_prop = 1.0 / len(parent["children"]) - parent["proportions"] = [equal_prop] * len(parent["children"]) - self._rebuild_ui_at_path(panel_path[:-1]) - - def split_panel(self, panel_path: list[int], orientation: int): - current_layout = self._get_layout_at_path(panel_path) - existing_panel = current_layout["panel"] - new_panel = TimeSeriesPanel(self.data_manager, self.playback_manager, self.worker_manager) - parent, child_index = self._get_parent_and_index(panel_path) - - if parent is None: # Root split - self.layout = { - "type": "split", - "orientation": orientation, - "children": [{"type": "panel", "panel": existing_panel}, {"type": "panel", "panel": new_panel}], - "proportions": [0.5, 0.5], - } - self._rebuild_ui_at_path([]) - elif parent["type"] == "split" and parent["orientation"] == orientation: # Same orientation - insert into existing split - parent["children"].insert(child_index + 1, {"type": "panel", "panel": new_panel}) - parent["proportions"] = [1.0 / len(parent["children"])] * len(parent["children"]) - self._rebuild_ui_at_path(panel_path[:-1]) - else: # Different orientation - create new split level - new_split = {"type": "split", "orientation": orientation, "children": [current_layout, {"type": "panel", "panel": new_panel}], "proportions": [0.5, 0.5]} - self._replace_layout_at_path(panel_path, new_split) - self._rebuild_ui_at_path(panel_path) - - def _rebuild_ui_at_path(self, path: list[int]): - layout = self._get_layout_at_path(path) - if path: - container_tag = self._path_to_tag(path, "container") - else: # Root update - container_tag = self.parent_tag - - self._cleanup_ui_recursive(layout, path) - dpg.delete_item(container_tag, children_only=True) - width, height = dpg.get_item_rect_size(container_tag) - self._create_ui_recursive(layout, container_tag, path, width, height) - - def _cleanup_ui_recursive(self, layout: dict, path: list[int]): - if layout["type"] == "panel": - panel = layout["panel"] - panel.destroy_ui() - if panel in self.active_panels: - self.active_panels.remove(panel) - else: - for i in range(len(layout["children"]) - 1): - handler_tag = f"{self._path_to_tag(path, f'grip_{i}')}_handler" - if dpg.does_item_exist(handler_tag): - dpg.delete_item(handler_tag) - self._created_handler_tags.discard(handler_tag) - - for i, child in enumerate(layout["children"]): - self._cleanup_ui_recursive(child, path + [i]) - - def update_all_panels(self): - if self._queue_resize: - if (size := dpg.get_item_rect_size(self.parent_tag)) != [0, 0]: - self._queue_resize = False - self._resize_splits_recursive(self.layout, [], *size) - for panel in self.active_panels: - panel.update() - - def on_viewport_resize(self): - self._resize_splits_recursive(self.layout, []) - - def _resize_splits_recursive(self, layout: dict, path: list[int], width: int | None = None, height: int | None = None): - if layout["type"] == "split": - split_tag = self._path_to_tag(path, "split") - if dpg.does_item_exist(split_tag): - available_sizes = (width, height) if width and height else dpg.get_item_rect_size(dpg.get_item_parent(split_tag)) - orientation, _, pane_sizes = self._get_split_geometry(layout, available_sizes) - size_properties = ("width", "height") - - for i, child_layout in enumerate(layout["children"]): - child_path = path + [i] - container_tag = self._path_to_tag(child_path, "container") - if dpg.does_item_exist(container_tag): - dpg.configure_item(container_tag, **{size_properties[orientation]: pane_sizes[i]}) - child_width, child_height = [(pane_sizes[i], available_sizes[1]), (available_sizes[0], pane_sizes[i])][orientation] - self._resize_splits_recursive(child_layout, child_path, child_width, child_height) - else: # leaf node/panel - adjust bar height to allow for scrollbar - panel_tag = self._path_to_tag(path, "panel") - if width is not None and width < int(329 * self.scale + 64): # scaled widths of the elements in top bar + fixed 8 padding on left and right of each item - dpg.configure_item(panel_tag, height=(int(13 * self.scale) + 24)) - else: - dpg.configure_item(panel_tag, height=(int(13 * self.scale) + 8)) - - def _get_split_geometry(self, layout: dict, available_size: tuple[int, int]) -> tuple[int, int, list[int]]: - orientation = layout["orientation"] - num_grips = len(layout["children"]) - 1 - usable_size = max(self.min_pane_size, available_size[orientation] - (num_grips * (self.grip_size + 8 * (2 - orientation)))) # approximate, scaling is weird - pane_sizes = [max(self.min_pane_size, int(usable_size * prop)) for prop in layout["proportions"]] - return orientation, usable_size, pane_sizes - - def _get_layout_at_path(self, path: list[int]) -> dict: - current = self.layout - for index in path: - current = current["children"][index] - return current - - def _get_parent_and_index(self, path: list[int]) -> tuple: - return (None, -1) if not path else (self._get_layout_at_path(path[:-1]), path[-1]) - - def _replace_layout_at_path(self, path: list[int], new_layout: dict): - if not path: - self.layout = new_layout - else: - parent, index = self._get_parent_and_index(path) - parent["children"][index] = new_layout - - def _path_to_tag(self, path: list[int], prefix: str = "") -> str: - path_str = "_".join(map(str, path)) if path else "root" - return f"{prefix}_{path_str}" if prefix else path_str - - def _create_grip(self, parent_tag: str, path: list[int], grip_index: int, orientation: int): - grip_tag = self._path_to_tag(path, f"grip_{grip_index}") - handler_tag = f"{grip_tag}_handler" - width, height = [(self.grip_size, -1), (-1, self.grip_size)][orientation] - - with dpg.child_window(tag=grip_tag, parent=parent_tag, width=width, height=height, no_scrollbar=True, border=False): - button_tag = dpg.add_button(label="", width=-1, height=-1) - - with dpg.item_handler_registry(tag=handler_tag): - user_data = (path, grip_index, orientation) - dpg.add_item_active_handler(callback=self._on_grip_drag, user_data=user_data) - dpg.add_item_deactivated_handler(callback=self._on_grip_end, user_data=user_data) - dpg.bind_item_handler_registry(button_tag, handler_tag) - self._created_handler_tags.add(handler_tag) - - def _on_grip_drag(self, sender, app_data, user_data): - path, grip_index, orientation = user_data - layout = self._get_layout_at_path(path) - - if "_drag_data" not in layout: - layout["_drag_data"] = {"initial_proportions": layout["proportions"][:], "start_mouse": dpg.get_mouse_pos(local=False)[orientation]} - return - - drag_data = layout["_drag_data"] - split_tag = self._path_to_tag(path, "split") - if not dpg.does_item_exist(split_tag): - return - - _, usable_size, _ = self._get_split_geometry(layout, dpg.get_item_rect_size(split_tag)) - current_coord = dpg.get_mouse_pos(local=False)[orientation] - delta = current_coord - drag_data["start_mouse"] - delta_prop = delta / usable_size - - left_idx = grip_index - right_idx = left_idx + 1 - initial = drag_data["initial_proportions"] - min_prop = self.min_pane_size / usable_size - - new_left = max(min_prop, initial[left_idx] + delta_prop) - new_right = max(min_prop, initial[right_idx] - delta_prop) - - total_available = initial[left_idx] + initial[right_idx] - if new_left + new_right > total_available: - if new_left > new_right: - new_left = total_available - new_right - else: - new_right = total_available - new_left - - layout["proportions"] = initial[:] - layout["proportions"][left_idx] = new_left - layout["proportions"][right_idx] = new_right - - self._resize_splits_recursive(layout, path) - - def _on_grip_end(self, sender, app_data, user_data): - path, _, _ = user_data - self._get_layout_at_path(path).pop("_drag_data", None) diff --git a/tools/jotpluggler/layouts/torque-controller.yaml b/tools/jotpluggler/layouts/torque-controller.yaml deleted file mode 100644 index 5503be9e64..0000000000 --- a/tools/jotpluggler/layouts/torque-controller.yaml +++ /dev/null @@ -1,128 +0,0 @@ -tabs: - '0': - name: Lateral Plan Conformance - panel_layout: - type: split - orientation: 1 - proportions: - - 0.3333333333333333 - - 0.3333333333333333 - - 0.3333333333333333 - children: - - type: panel - panel: - type: timeseries - title: desired vs actual - series_paths: - - controlsState/lateralControlState/torqueState/desiredLateralAccel - - controlsState/lateralControlState/torqueState/actualLateralAccel - - type: panel - panel: - type: timeseries - title: ff vs output - series_paths: - - controlsState/lateralControlState/torqueState/f - - carState/steeringPressed - - carControl/actuators/torque - - type: panel - panel: - type: timeseries - title: vehicle speed - series_paths: - - carState/vEgo - '1': - name: Actuator Performance - panel_layout: - type: split - orientation: 1 - proportions: - - 0.3333333333333333 - - 0.3333333333333333 - - 0.3333333333333333 - children: - - type: panel - panel: - type: timeseries - title: calc vs learned latAccelFactor - series_paths: - - liveTorqueParameters/latAccelFactorFiltered - - liveTorqueParameters/latAccelFactorRaw - - carParams/lateralTuning/torque/latAccelFactor - - type: panel - panel: - type: timeseries - title: learned latAccelOffset - series_paths: - - liveTorqueParameters/latAccelOffsetRaw - - liveTorqueParameters/latAccelOffsetFiltered - - type: panel - panel: - type: timeseries - title: calc vs learned friction - series_paths: - - liveTorqueParameters/frictionCoefficientFiltered - - liveTorqueParameters/frictionCoefficientRaw - - carParams/lateralTuning/torque/friction - '2': - name: Vehicle Dynamics - panel_layout: - type: split - orientation: 1 - proportions: - - 0.3333333333333333 - - 0.3333333333333333 - - 0.3333333333333333 - children: - - type: panel - panel: - type: timeseries - title: initial vs learned steerRatio - series_paths: - - carParams/steerRatio - - liveParameters/steerRatio - - type: panel - panel: - type: timeseries - title: initial vs learned tireStiffnessFactor - series_paths: - - carParams/tireStiffnessFactor - - liveParameters/stiffnessFactor - - type: panel - panel: - type: timeseries - title: live steering angle offsets - series_paths: - - liveParameters/angleOffsetDeg - - liveParameters/angleOffsetAverageDeg - '3': - name: Controller PIF Terms - panel_layout: - type: split - orientation: 1 - proportions: - - 0.3333333333333333 - - 0.3333333333333333 - - 0.3333333333333333 - children: - - type: panel - panel: - type: timeseries - title: ff vs output - series_paths: - - carControl/actuators/torque - - controlsState/lateralControlState/torqueState/f - - carState/steeringPressed - - type: panel - panel: - type: timeseries - title: PIF terms - series_paths: - - controlsState/lateralControlState/torqueState/f - - controlsState/lateralControlState/torqueState/p - - controlsState/lateralControlState/torqueState/i - - type: panel - panel: - type: timeseries - title: road roll angle - series_paths: - - liveParameters/roll diff --git a/tools/jotpluggler/pluggle.py b/tools/jotpluggler/pluggle.py deleted file mode 100755 index 2fb6e3e2f4..0000000000 --- a/tools/jotpluggler/pluggle.py +++ /dev/null @@ -1,368 +0,0 @@ -#!/usr/bin/env python3 -import argparse -import os -import dearpygui.dearpygui as dpg -import multiprocessing -import uuid -import signal -import yaml -from openpilot.common.swaglog import cloudlog -from openpilot.common.basedir import BASEDIR -from openpilot.tools.jotpluggler.data import DataManager -from openpilot.tools.jotpluggler.datatree import DataTree -from openpilot.tools.jotpluggler.layout import LayoutManager - -DEMO_ROUTE = "5beb9b58bd12b691/0000010a--a51155e496" - - -class WorkerManager: - def __init__(self, max_workers=None): - self.pool = multiprocessing.Pool(max_workers or min(4, multiprocessing.cpu_count()), initializer=WorkerManager.worker_initializer) - self.active_tasks = {} - - def submit_task(self, func, args_list, callback=None, task_id=None): - task_id = task_id or str(uuid.uuid4()) - - if task_id in self.active_tasks: - try: - self.active_tasks[task_id].terminate() - except Exception: - pass - - def handle_success(result): - self.active_tasks.pop(task_id, None) - if callback: - try: - callback(result) - except Exception as e: - print(f"Callback for task {task_id} failed: {e}") - - def handle_error(error): - self.active_tasks.pop(task_id, None) - print(f"Task {task_id} failed: {error}") - - async_result = self.pool.starmap_async(func, args_list, callback=handle_success, error_callback=handle_error) - self.active_tasks[task_id] = async_result - return task_id - - @staticmethod - def worker_initializer(): - signal.signal(signal.SIGINT, signal.SIG_IGN) - - def shutdown(self): - for task in self.active_tasks.values(): - try: - task.terminate() - except Exception: - pass - self.pool.terminate() - self.pool.join() - - -class PlaybackManager: - def __init__(self): - self.is_playing = False - self.current_time_s = 0.0 - self.duration_s = 0.0 - self.num_segments = 0 - - self.x_axis_bounds = (0.0, 0.0) # (min_time, max_time) - self.x_axis_observers = [] # callbacks for x-axis changes - self._updating_x_axis = False - - def set_route_duration(self, duration: float): - self.duration_s = duration - self.seek(min(self.current_time_s, duration)) - - def toggle_play_pause(self): - if not self.is_playing and self.current_time_s >= self.duration_s: - self.seek(0.0) - self.is_playing = not self.is_playing - texture_tag = "pause_texture" if self.is_playing else "play_texture" - dpg.configure_item("play_pause_button", texture_tag=texture_tag) - - def seek(self, time_s: float): - self.current_time_s = max(0.0, min(time_s, self.duration_s)) - - def update_time(self, delta_t: float): - if self.is_playing: - self.current_time_s = min(self.current_time_s + delta_t, self.duration_s) - if self.current_time_s >= self.duration_s: - self.is_playing = False - dpg.configure_item("play_pause_button", texture_tag="play_texture") - return self.current_time_s - - def set_x_axis_bounds(self, min_time: float, max_time: float, source_panel=None): - if self._updating_x_axis: - return - - new_bounds = (min_time, max_time) - if new_bounds == self.x_axis_bounds: - return - - self.x_axis_bounds = new_bounds - self._updating_x_axis = True # prevent recursive updates - - try: - for callback in self.x_axis_observers: - try: - callback(min_time, max_time, source_panel) - except Exception as e: - print(f"Error in x-axis sync callback: {e}") - finally: - self._updating_x_axis = False - - def add_x_axis_observer(self, callback): - if callback not in self.x_axis_observers: - self.x_axis_observers.append(callback) - - def remove_x_axis_observer(self, callback): - if callback in self.x_axis_observers: - self.x_axis_observers.remove(callback) - -class MainController: - def __init__(self, scale: float = 1.0): - self.scale = scale - self.data_manager = DataManager() - self.playback_manager = PlaybackManager() - self.worker_manager = WorkerManager() - self._create_global_themes() - self.data_tree = DataTree(self.data_manager, self.playback_manager) - self.layout_manager = LayoutManager(self.data_manager, self.playback_manager, self.worker_manager, scale=self.scale) - self.data_manager.add_observer(self.on_data_loaded) - self._total_segments = 0 - - def _create_global_themes(self): - with dpg.theme(tag="line_theme"): - with dpg.theme_component(dpg.mvLineSeries): - scaled_thickness = max(1.0, self.scale) - dpg.add_theme_style(dpg.mvPlotStyleVar_LineWeight, scaled_thickness, category=dpg.mvThemeCat_Plots) - - with dpg.theme(tag="timeline_theme"): - with dpg.theme_component(dpg.mvInfLineSeries): - scaled_thickness = max(1.0, self.scale) - dpg.add_theme_style(dpg.mvPlotStyleVar_LineWeight, scaled_thickness, category=dpg.mvThemeCat_Plots) - dpg.add_theme_color(dpg.mvPlotCol_Line, (255, 0, 0, 128), category=dpg.mvThemeCat_Plots) - - for tag, color in (("active_tab_theme", (37, 37, 38, 255)), ("inactive_tab_theme", (70, 70, 75, 255))): - with dpg.theme(tag=tag): - for cmp, target in ((dpg.mvChildWindow, dpg.mvThemeCol_ChildBg), (dpg.mvInputText, dpg.mvThemeCol_FrameBg), (dpg.mvImageButton, dpg.mvThemeCol_Button)): - with dpg.theme_component(cmp): - dpg.add_theme_color(target, color) - - with dpg.theme(tag="tab_bar_theme"): - with dpg.theme_component(dpg.mvChildWindow): - dpg.add_theme_color(dpg.mvThemeCol_ChildBg, (51, 51, 55, 255)) - - def on_data_loaded(self, data: dict): - duration = data.get('duration', 0.0) - self.playback_manager.set_route_duration(duration) - - if data.get('metadata_loaded'): - self.playback_manager.num_segments = data.get('total_segments', 0) - self._total_segments = data.get('total_segments', 0) - dpg.set_value("load_status", f"Loading... 0/{self._total_segments} segments processed") - elif data.get('reset'): - self.playback_manager.current_time_s = 0.0 - self.playback_manager.duration_s = 0.0 - self.playback_manager.is_playing = False - self._total_segments = 0 - dpg.set_value("load_status", "Loading...") - dpg.set_value("timeline_slider", 0.0) - dpg.configure_item("timeline_slider", max_value=0.0) - dpg.configure_item("play_pause_button", texture_tag="play_texture") - dpg.configure_item("load_button", enabled=True) - elif data.get('loading_complete'): - num_paths = len(self.data_manager.get_all_paths()) - dpg.set_value("load_status", f"Loaded {num_paths} data paths") - dpg.configure_item("load_button", enabled=True) - elif data.get('segment_added'): - segment_count = data.get('segment_count', 0) - dpg.set_value("load_status", f"Loading... {segment_count}/{self._total_segments} segments processed") - - dpg.configure_item("timeline_slider", max_value=duration) - - def save_layout_to_yaml(self, filepath: str): - layout_dict = self.layout_manager.to_dict() - with open(filepath, 'w') as f: - yaml.dump(layout_dict, f, default_flow_style=False, sort_keys=False) - - def load_layout_from_yaml(self, filepath: str): - with open(filepath) as f: - layout_dict = yaml.safe_load(f) - self.layout_manager.clear_and_load_from_dict(layout_dict) - self.layout_manager.create_ui("main_plot_area") - - def save_layout_dialog(self): - if dpg.does_item_exist("save_layout_dialog"): - dpg.delete_item("save_layout_dialog") - with dpg.file_dialog( - callback=self._save_layout_callback, tag="save_layout_dialog", width=int(700 * self.scale), height=int(400 * self.scale), - default_filename="layout", default_path=os.path.join(os.path.dirname(os.path.realpath(__file__)), "layouts") - ): - dpg.add_file_extension(".yaml") - - def load_layout_dialog(self): - if dpg.does_item_exist("load_layout_dialog"): - dpg.delete_item("load_layout_dialog") - with dpg.file_dialog( - callback=self._load_layout_callback, tag="load_layout_dialog", width=int(700 * self.scale), height=int(400 * self.scale), - default_path=os.path.join(os.path.dirname(os.path.realpath(__file__)), "layouts") - ): - dpg.add_file_extension(".yaml") - - def _save_layout_callback(self, sender, app_data): - filepath = app_data['file_path_name'] - try: - self.save_layout_to_yaml(filepath) - dpg.set_value("load_status", f"Layout saved to {os.path.basename(filepath)}") - except Exception: - dpg.set_value("load_status", "Error saving layout") - cloudlog.exception(f"Error saving layout to {filepath}") - dpg.delete_item("save_layout_dialog") - - def _load_layout_callback(self, sender, app_data): - filepath = app_data['file_path_name'] - try: - self.load_layout_from_yaml(filepath) - dpg.set_value("load_status", f"Layout loaded from {os.path.basename(filepath)}") - except Exception: - dpg.set_value("load_status", "Error loading layout") - cloudlog.exception(f"Error loading layout from {filepath}:") - dpg.delete_item("load_layout_dialog") - - def setup_ui(self): - with dpg.texture_registry(): - script_dir = os.path.dirname(os.path.realpath(__file__)) - for image in ["play", "pause", "x", "split_h", "split_v", "plus"]: - texture = dpg.load_image(os.path.join(script_dir, "assets", f"{image}.png")) - dpg.add_static_texture(width=texture[0], height=texture[1], default_value=texture[3], tag=f"{image}_texture") - - with dpg.window(tag="Primary Window"): - with dpg.group(horizontal=True): - # Left panel - Data tree - with dpg.child_window(label="Sidebar", width=int(300 * self.scale), tag="sidebar_window", border=True, resizable_x=True): - with dpg.group(horizontal=True): - dpg.add_input_text(tag="route_input", width=int(-75 * self.scale), hint="Enter route name...") - dpg.add_button(label="Load", callback=self.load_route, tag="load_button", width=-1) - dpg.add_text("Ready to load route", tag="load_status") - dpg.add_separator() - - with dpg.table(header_row=False, policy=dpg.mvTable_SizingStretchProp): - dpg.add_table_column(init_width_or_weight=0.5) - dpg.add_table_column(init_width_or_weight=0.5) - with dpg.table_row(): - dpg.add_button(label="Save Layout", callback=self.save_layout_dialog, width=-1) - dpg.add_button(label="Load Layout", callback=self.load_layout_dialog, width=-1) - dpg.add_separator() - - self.data_tree.create_ui("sidebar_window") - - # Right panel - Plots and timeline - with dpg.group(tag="right_panel"): - with dpg.child_window(label="Plot Window", border=True, height=int(-(32 + 13 * self.scale)), tag="main_plot_area"): - self.layout_manager.create_ui("main_plot_area") - - with dpg.child_window(label="Timeline", border=True): - with dpg.table(header_row=False): - btn_size = int(13 * self.scale) - dpg.add_table_column(width_fixed=True, init_width_or_weight=(btn_size + 8)) # Play button - dpg.add_table_column(width_stretch=True) # Timeline slider - dpg.add_table_column(width_fixed=True, init_width_or_weight=int(50 * self.scale)) # FPS counter - with dpg.table_row(): - dpg.add_image_button(texture_tag="play_texture", tag="play_pause_button", callback=self.toggle_play_pause, width=btn_size, height=btn_size) - dpg.add_slider_float(tag="timeline_slider", default_value=0.0, label="", width=-1, callback=self.timeline_drag) - dpg.add_text("", tag="fps_counter") - with dpg.item_handler_registry(tag="plot_resize_handler"): - dpg.add_item_resize_handler(callback=self.on_plot_resize) - dpg.bind_item_handler_registry("right_panel", "plot_resize_handler") - - dpg.set_primary_window("Primary Window", True) - - def on_plot_resize(self, sender, app_data, user_data): - self.layout_manager.on_viewport_resize() - - def load_route(self): - route_name = dpg.get_value("route_input").strip() - if route_name: - dpg.set_value("load_status", "Loading route...") - dpg.configure_item("load_button", enabled=False) - self.data_manager.load_route(route_name) - - def toggle_play_pause(self, sender): - self.playback_manager.toggle_play_pause() - - def timeline_drag(self, sender, app_data): - self.playback_manager.seek(app_data) - - def update_frame(self, font): - self.data_tree.update_frame(font) - - new_time = self.playback_manager.update_time(dpg.get_delta_time()) - if not dpg.is_item_active("timeline_slider"): - dpg.set_value("timeline_slider", new_time) - - self.layout_manager.update_all_panels() - - dpg.set_value("fps_counter", f"{dpg.get_frame_rate():.1f} FPS") - - def shutdown(self): - self.worker_manager.shutdown() - - -def main(route_to_load=None, layout_to_load=None): - dpg.create_context() - - # TODO: find better way of calculating display scaling - #try: - # w, h = next(tuple(map(int, l.split()[0].split('x'))) for l in subprocess.check_output(['xrandr']).decode().split('\n') if '*' in l) # actual resolution - # scale = pyautogui.size()[0] / w # scaled resolution - #except Exception: - # scale = 1 - scale = 1 - - with dpg.font_registry(): - default_font = dpg.add_font(os.path.join(BASEDIR, "selfdrive/assets/fonts/JetBrainsMono-Medium.ttf"), int(13 * scale * 2)) # 2x then scale for hidpi - dpg.bind_font(default_font) - dpg.set_global_font_scale(0.5) - - viewport_width, viewport_height = int(1200 * scale), int(800 * scale) - dpg.create_viewport( - title='JotPluggler', width=viewport_width, height=viewport_height, - ) - dpg.setup_dearpygui() - - controller = MainController(scale=scale) - controller.setup_ui() - - if layout_to_load: - try: - controller.load_layout_from_yaml(layout_to_load) - print(f"Loaded layout from {layout_to_load}") - except Exception as e: - print(f"Failed to load layout from {layout_to_load}: {e}") - cloudlog.exception(f"Error loading layout from {layout_to_load}") - - if route_to_load: - dpg.set_value("route_input", route_to_load) - controller.load_route() - - dpg.show_viewport() - - # Main loop - try: - while dpg.is_dearpygui_running(): - controller.update_frame(default_font) - dpg.render_dearpygui_frame() - finally: - controller.shutdown() - dpg.destroy_context() - -if __name__ == "__main__": - parser = argparse.ArgumentParser(description="A tool for visualizing openpilot logs.") - parser.add_argument("--demo", action="store_true", help="Use the demo route instead of providing one") - parser.add_argument("--layout", type=str, help="Path to YAML layout file to load on startup") - parser.add_argument("route", nargs='?', default=None, help="Optional route name to load on startup.") - args = parser.parse_args() - route = DEMO_ROUTE if args.demo else args.route - main(route_to_load=route, layout_to_load=args.layout) diff --git a/tools/jotpluggler/views.py b/tools/jotpluggler/views.py deleted file mode 100644 index cd723d161e..0000000000 --- a/tools/jotpluggler/views.py +++ /dev/null @@ -1,294 +0,0 @@ -import uuid -import threading -import numpy as np -from collections import deque -import dearpygui.dearpygui as dpg -from abc import ABC, abstractmethod - - -class ViewPanel(ABC): - """Abstract base class for all view panels that can be displayed in a plot container""" - - def __init__(self, panel_id: str | None = None): - self.panel_id = panel_id or str(uuid.uuid4()) - self.title = "Untitled Panel" - - @abstractmethod - def clear(self): - pass - - @abstractmethod - def create_ui(self, parent_tag: str): - pass - - @abstractmethod - def destroy_ui(self): - pass - - @abstractmethod - def get_panel_type(self) -> str: - pass - - @abstractmethod - def update(self): - pass - - @abstractmethod - def to_dict(self) -> dict: - pass - - @classmethod - @abstractmethod - def load_from_dict(cls, data: dict, data_manager, playback_manager, worker_manager): - pass - - -class TimeSeriesPanel(ViewPanel): - def __init__(self, data_manager, playback_manager, worker_manager, panel_id: str | None = None): - super().__init__(panel_id) - self.data_manager = data_manager - self.playback_manager = playback_manager - self.worker_manager = worker_manager - self.title = "Time Series Plot" - self.plot_tag = f"plot_{self.panel_id}" - self.x_axis_tag = f"{self.plot_tag}_x_axis" - self.y_axis_tag = f"{self.plot_tag}_y_axis" - self.timeline_indicator_tag = f"{self.plot_tag}_timeline" - self._ui_created = False - self._series_data: dict[str, tuple[np.ndarray, np.ndarray]] = {} - self._last_plot_duration = 0 - self._update_lock = threading.RLock() - self._results_deque: deque[tuple[str, list, list]] = deque() - self._new_data = False - self._last_x_limits = (0.0, 0.0) - self._queued_x_sync: tuple | None = None - self._queued_reallow_x_zoom = False - self._total_segments = self.playback_manager.num_segments - - def to_dict(self) -> dict: - return { - "type": "timeseries", - "title": self.title, - "series_paths": list(self._series_data.keys()) - } - - @classmethod - def load_from_dict(cls, data: dict, data_manager, playback_manager, worker_manager): - panel = cls(data_manager, playback_manager, worker_manager) - panel.title = data.get("title", "Time Series Plot") - panel._series_data = {path: (np.array([]), np.array([])) for path in data.get("series_paths", [])} - return panel - - def create_ui(self, parent_tag: str): - self.data_manager.add_observer(self.on_data_loaded) - self.playback_manager.add_x_axis_observer(self._on_x_axis_sync) - with dpg.plot(height=-1, width=-1, tag=self.plot_tag, parent=parent_tag, drop_callback=self._on_series_drop, payload_type="TIMESERIES_PAYLOAD"): - dpg.add_plot_legend() - dpg.add_plot_axis(dpg.mvXAxis, no_label=True, tag=self.x_axis_tag) - dpg.add_plot_axis(dpg.mvYAxis, no_label=True, tag=self.y_axis_tag) - timeline_series_tag = dpg.add_inf_line_series(x=[0], label="Timeline", parent=self.y_axis_tag, tag=self.timeline_indicator_tag) - dpg.bind_item_theme(timeline_series_tag, "timeline_theme") - - self._new_data = True - self._queued_x_sync = self.playback_manager.x_axis_bounds - self._ui_created = True - - def update(self): - with self._update_lock: - if not self._ui_created: - return - - if self._queued_x_sync: - min_time, max_time = self._queued_x_sync - self._queued_x_sync = None - dpg.set_axis_limits(self.x_axis_tag, min_time, max_time) - self._last_x_limits = (min_time, max_time) - self._fit_y_axis(min_time, max_time) - self._queued_reallow_x_zoom = True # must wait a frame before allowing user changes so that axis limits take effect - return - - if self._queued_reallow_x_zoom: - self._queued_reallow_x_zoom = False - if tuple(dpg.get_axis_limits(self.x_axis_tag)) == self._last_x_limits: - dpg.set_axis_limits_auto(self.x_axis_tag) - else: - self._queued_x_sync = self._last_x_limits # retry, likely too early - return - - if self._new_data: # handle new data in main thread - self._new_data = False - if self._total_segments > 0: - dpg.set_axis_limits_constraints(self.x_axis_tag, -10, self._total_segments * 60 + 10) - self._fit_y_axis(*dpg.get_axis_limits(self.x_axis_tag)) - for series_path in list(self._series_data.keys()): - self.add_series(series_path, update=True) - - current_limits = dpg.get_axis_limits(self.x_axis_tag) - # downsample if plot zoom changed significantly - plot_duration = current_limits[1] - current_limits[0] - if plot_duration > self._last_plot_duration * 2 or plot_duration < self._last_plot_duration * 0.5: - self._downsample_all_series(plot_duration) - # sync x-axis if changed by user - if self._last_x_limits != current_limits: - self.playback_manager.set_x_axis_bounds(current_limits[0], current_limits[1], source_panel=self) - self._last_x_limits = current_limits - self._fit_y_axis(current_limits[0], current_limits[1]) - - while self._results_deque: # handle downsampled results in main thread - results = self._results_deque.popleft() - for series_path, downsampled_time, downsampled_values in results: - series_tag = f"series_{self.panel_id}_{series_path}" - if dpg.does_item_exist(series_tag): - dpg.set_value(series_tag, (downsampled_time, downsampled_values.astype(float))) - - # update timeline - current_time_s = self.playback_manager.current_time_s - dpg.set_value(self.timeline_indicator_tag, [[current_time_s], [0]]) - - # update timeseries legend label - for series_path, (time_array, value_array) in self._series_data.items(): - position = np.searchsorted(time_array, current_time_s, side='right') - 1 - if position >= 0 and (current_time_s - time_array[position]) <= 1.0: - value = value_array[position] - formatted_value = f"{value:.5f}" if np.issubdtype(type(value), np.floating) else str(value) - series_tag = f"series_{self.panel_id}_{series_path}" - if dpg.does_item_exist(series_tag): - dpg.configure_item(series_tag, label=f"{series_path}: {formatted_value}") - - def _on_x_axis_sync(self, min_time: float, max_time: float, source_panel): - with self._update_lock: - if source_panel != self: - self._queued_x_sync = (min_time, max_time) - - def _fit_y_axis(self, x_min: float, x_max: float): - if not self._series_data: - dpg.set_axis_limits(self.y_axis_tag, -1, 1) - return - - global_min = float('inf') - global_max = float('-inf') - found_data = False - - for time_array, value_array in self._series_data.values(): - if len(time_array) == 0: - continue - start_idx, end_idx = np.searchsorted(time_array, [x_min, x_max]) - end_idx = min(end_idx, len(time_array) - 1) - if start_idx <= end_idx: - y_slice = value_array[start_idx:end_idx + 1] - series_min, series_max = np.min(y_slice), np.max(y_slice) - global_min = min(global_min, series_min) - global_max = max(global_max, series_max) - found_data = True - - if not found_data: - dpg.set_axis_limits(self.y_axis_tag, -1, 1) - return - - if global_min == global_max: - padding = max(abs(global_min) * 0.1, 1.0) - y_min, y_max = global_min - padding, global_max + padding - else: - range_size = global_max - global_min - padding = range_size * 0.1 - y_min, y_max = global_min - padding, global_max + padding - - dpg.set_axis_limits(self.y_axis_tag, y_min, y_max) - - def _downsample_all_series(self, plot_duration): - plot_width = dpg.get_item_rect_size(self.plot_tag)[0] - if plot_width <= 0 or plot_duration <= 0: - return - - self._last_plot_duration = plot_duration - target_points_per_second = plot_width / plot_duration - work_items = [] - for series_path, (time_array, value_array) in self._series_data.items(): - if len(time_array) == 0: - continue - series_duration = time_array[-1] - time_array[0] if len(time_array) > 1 else 1 - points_per_second = len(time_array) / series_duration - if points_per_second > target_points_per_second * 2: - target_points = max(int(target_points_per_second * series_duration), plot_width) - work_items.append((series_path, time_array, value_array, target_points)) - elif dpg.does_item_exist(f"series_{self.panel_id}_{series_path}"): - dpg.set_value(f"series_{self.panel_id}_{series_path}", (time_array, value_array.astype(float))) - - if work_items: - self.worker_manager.submit_task( - TimeSeriesPanel._downsample_worker, work_items, callback=lambda results: self._results_deque.append(results), task_id=f"downsample_{self.panel_id}" - ) - - def add_series(self, series_path: str, update: bool = False): - with self._update_lock: - if update or series_path not in self._series_data: - self._series_data[series_path] = self.data_manager.get_timeseries(series_path) - - time_array, value_array = self._series_data[series_path] - series_tag = f"series_{self.panel_id}_{series_path}" - if dpg.does_item_exist(series_tag): - dpg.set_value(series_tag, (time_array, value_array.astype(float))) - else: - line_series_tag = dpg.add_line_series(x=time_array, y=value_array.astype(float), label=series_path, parent=self.y_axis_tag, tag=series_tag) - dpg.bind_item_theme(line_series_tag, "line_theme") - self._fit_y_axis(*dpg.get_axis_limits(self.x_axis_tag)) - plot_duration = dpg.get_axis_limits(self.x_axis_tag)[1] - dpg.get_axis_limits(self.x_axis_tag)[0] - self._downsample_all_series(plot_duration) - - def destroy_ui(self): - with self._update_lock: - self.data_manager.remove_observer(self.on_data_loaded) - self.playback_manager.remove_x_axis_observer(self._on_x_axis_sync) - if dpg.does_item_exist(self.plot_tag): - dpg.delete_item(self.plot_tag) - self._ui_created = False - - def get_panel_type(self) -> str: - return "timeseries" - - def clear(self): - with self._update_lock: - for series_path in list(self._series_data.keys()): - self.remove_series(series_path) - - def remove_series(self, series_path: str): - with self._update_lock: - if series_path in self._series_data: - if dpg.does_item_exist(f"series_{self.panel_id}_{series_path}"): - dpg.delete_item(f"series_{self.panel_id}_{series_path}") - del self._series_data[series_path] - - def on_data_loaded(self, data: dict): - with self._update_lock: - self._new_data = True - if data.get('metadata_loaded'): - self._total_segments = data.get('total_segments', 0) - limits = (-10, self._total_segments * 60 + 10) - self._queued_x_sync = limits - - def _on_series_drop(self, sender, app_data, user_data): - self.add_series(app_data) - - @staticmethod - def _downsample_worker(series_path, time_array, value_array, target_points): - if len(time_array) <= target_points: - return series_path, time_array, value_array - - step = len(time_array) / target_points - indices = [] - - for i in range(target_points): - start_idx = int(i * step) - end_idx = int((i + 1) * step) - if start_idx == end_idx: - indices.append(start_idx) - else: - bucket_values = value_array[start_idx:end_idx] - min_idx = start_idx + np.argmin(bucket_values) - max_idx = start_idx + np.argmax(bucket_values) - if min_idx != max_idx: - indices.extend([min(min_idx, max_idx), max(min_idx, max_idx)]) - else: - indices.append(min_idx) - indices = sorted(set(indices)) - return series_path, time_array[indices], value_array[indices] From 0b9ab8bb91f0e8c7f0ca274c67310caaa4a776ed Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sun, 1 Mar 2026 15:51:16 -0800 Subject: [PATCH 004/141] cabana: replace Qt types with stdlib (#37519) * cabana: replace Qt types with stdlib * lil more * cleanup sconscript --- tools/cabana/SConscript | 5 +- tools/cabana/binaryview.cc | 2 +- tools/cabana/cabana.cc | 6 +- tools/cabana/chart/chart.cc | 14 +-- tools/cabana/chart/chartswidget.cc | 6 +- tools/cabana/chart/signalselector.cc | 6 +- tools/cabana/commands.cc | 22 ++--- tools/cabana/commands.h | 12 +-- tools/cabana/dbc/dbc.cc | 30 +++---- tools/cabana/dbc/dbc.h | 62 +++++++------ tools/cabana/dbc/dbcfile.cc | 111 +++++++++++++----------- tools/cabana/dbc/dbcfile.h | 27 +++--- tools/cabana/dbc/dbcmanager.cc | 43 ++++----- tools/cabana/dbc/dbcmanager.h | 24 ++--- tools/cabana/detailwidget.cc | 32 +++---- tools/cabana/historylog.cc | 12 +-- tools/cabana/mainwin.cc | 34 ++++---- tools/cabana/messageswidget.cc | 8 +- tools/cabana/signalview.cc | 58 +++++++------ tools/cabana/signalview.h | 9 +- tools/cabana/streams/abstractstream.h | 6 +- tools/cabana/streams/devicestream.h | 4 +- tools/cabana/streams/pandastream.cc | 8 +- tools/cabana/streams/pandastream.h | 6 +- tools/cabana/streams/replaystream.cc | 16 ++-- tools/cabana/streams/replaystream.h | 6 +- tools/cabana/streams/socketcanstream.cc | 6 +- tools/cabana/streams/socketcanstream.h | 6 +- tools/cabana/tests/test_cabana.cc | 18 ++-- tools/cabana/tools/findsignal.cc | 2 +- tools/cabana/tools/findsimilarbits.cc | 2 +- tools/cabana/utils/export.cc | 2 +- tools/cabana/utils/util.cc | 2 +- tools/cabana/videowidget.cc | 8 +- tools/cabana/videowidget.h | 5 +- 35 files changed, 327 insertions(+), 293 deletions(-) diff --git a/tools/cabana/SConscript b/tools/cabana/SConscript index cecb5ed8d9..a56ddff7a6 100644 --- a/tools/cabana/SConscript +++ b/tools/cabana/SConscript @@ -13,7 +13,6 @@ if arch == "Darwin": has_qt = False else: has_qt = shutil.which('qmake') is not None - if not has_qt: Return() @@ -21,7 +20,7 @@ SConscript(['#tools/replay/SConscript']) Import('replay_lib') qt_env = env.Clone() -qt_modules = ["Widgets", "Gui", "Core", "Network", "Concurrent", "DBus", "Xml"] +qt_modules = ["Widgets", "Gui", "Core", "Concurrent", "Xml"] qt_libs = [] if arch == "Darwin": @@ -51,7 +50,7 @@ else: qt_env['QT3DIR'] = qt_env['QTDIR'] qt_env.Tool('qt3') -qt_env['CPPPATH'] += qt_dirs + ["#third_party/qrcode"] +qt_env['CPPPATH'] += qt_dirs qt_flags = [ "-D_REENTRANT", "-DQT_NO_DEBUG", diff --git a/tools/cabana/binaryview.cc b/tools/cabana/binaryview.cc index b5a68c6b26..90c7c1c341 100644 --- a/tools/cabana/binaryview.cc +++ b/tools/cabana/binaryview.cc @@ -258,7 +258,7 @@ void BinaryViewModel::refresh() { int pos = sig->is_little_endian ? flipBitPos(sig->start_bit + j) : flipBitPos(sig->start_bit) + j; int idx = column_count * (pos / 8) + pos % 8; if (idx >= items.size()) { - qWarning() << "signal " << sig->name << "out of bounds.start_bit:" << sig->start_bit << "size:" << sig->size; + qWarning() << "signal " << sig->name.c_str() << "out of bounds.start_bit:" << sig->start_bit << "size:" << sig->size; break; } if (j == 0) sig->is_little_endian ? items[idx].is_lsb = true : items[idx].is_msb = true; diff --git a/tools/cabana/cabana.cc b/tools/cabana/cabana.cc index bc50afc03a..9e4cfc4a51 100644 --- a/tools/cabana/cabana.cc +++ b/tools/cabana/cabana.cc @@ -46,13 +46,13 @@ int main(int argc, char *argv[]) { stream = new DeviceStream(&app, cmd_parser.value("zmq")); } else if (cmd_parser.isSet("panda") || cmd_parser.isSet("panda-serial")) { try { - stream = new PandaStream(&app, {.serial = cmd_parser.value("panda-serial")}); + stream = new PandaStream(&app, {.serial = cmd_parser.value("panda-serial").toStdString()}); } catch (std::exception &e) { qWarning() << e.what(); return 0; } } else if (SocketCanStream::available() && cmd_parser.isSet("socketcan")) { - stream = new SocketCanStream(&app, {.device = cmd_parser.value("socketcan")}); + stream = new SocketCanStream(&app, {.device = cmd_parser.value("socketcan").toStdString()}); } else { uint32_t replay_flags = REPLAY_FLAG_NONE; if (cmd_parser.isSet("ecam")) replay_flags |= REPLAY_FLAG_ECAM; @@ -70,7 +70,7 @@ int main(int argc, char *argv[]) { if (!route.isEmpty()) { auto replay_stream = std::make_unique(&app); bool auto_source = cmd_parser.isSet("auto"); - if (!replay_stream->loadRoute(route, cmd_parser.value("data_dir"), replay_flags, auto_source)) { + if (!replay_stream->loadRoute(route.toStdString(), cmd_parser.value("data_dir").toStdString(), replay_flags, auto_source)) { return 0; } stream = replay_stream.release(); diff --git a/tools/cabana/chart/chart.cc b/tools/cabana/chart/chart.cc index 14491b1eff..9dfdc595f0 100644 --- a/tools/cabana/chart/chart.cc +++ b/tools/cabana/chart/chart.cc @@ -237,8 +237,8 @@ void ChartView::updateTitle() { for (auto &s : sigs) { auto decoration = s.series->isVisible() ? "none" : "line-through"; s.series->setName(QString("%3 %5 %6") - .arg(decoration, titleColorCss, s.sig->name, - msgColorCss, msgName(s.msg_id), s.msg_id.toString())); + .arg(decoration, titleColorCss, QString::fromStdString(s.sig->name), + msgColorCss, QString::fromStdString(msgName(s.msg_id)), QString::fromStdString(s.msg_id.toString()))); } split_chart_act->setEnabled(sigs.size() > 1); resetChartCache(); @@ -339,13 +339,13 @@ void ChartView::updateAxisY() { double min = std::numeric_limits::max(); double max = std::numeric_limits::lowest(); - QString unit = sigs[0].sig->unit; + QString unit = QString::fromStdString(sigs[0].sig->unit); for (auto &s : sigs) { if (!s.series->isVisible()) continue; // Only show unit when all signals have the same unit - if (unit != s.sig->unit) { + if (unit != QString::fromStdString(s.sig->unit)) { unit.clear(); } @@ -573,11 +573,11 @@ void ChartView::showTip(double sec) { // use reverse iterator to find last item <= sec. auto it = std::lower_bound(s.vals.crbegin(), s.vals.crend(), sec, [](auto &p, double v) { return p.x() > v; }); if (it != s.vals.crend() && it->x() >= axis_x->min()) { - value = s.sig->formatValue(it->y(), false); + value = QString::fromStdString(s.sig->formatValue(it->y(), false)); s.track_pt = *it; x = std::max(x, chart()->mapToPosition(*it).x()); } - QString name = sigs.size() > 1 ? s.sig->name + ": " : ""; + QString name = sigs.size() > 1 ? QString::fromStdString(s.sig->name) + ": " : ""; QString min = s.min == std::numeric_limits::max() ? "--" : QString::number(s.min); QString max = s.max == std::numeric_limits::lowest() ? "--" : QString::number(s.max); text_list << QString("%2%3 (%4, %5)") @@ -766,7 +766,7 @@ void ChartView::drawSignalValue(QPainter *painter) { for (auto &s : sigs) { auto it = std::lower_bound(s.vals.crbegin(), s.vals.crend(), cur_sec, [](auto &p, double x) { return p.x() > x + EPSILON; }); - QString value = (it != s.vals.crend() && it->x() >= axis_x->min()) ? s.sig->formatValue(it->y()) : "--"; + QString value = (it != s.vals.crend() && it->x() >= axis_x->min()) ? QString::fromStdString(s.sig->formatValue(it->y())) : "--"; QRectF marker_rect = legend_markers[i++]->sceneBoundingRect(); QRectF value_rect(marker_rect.bottomLeft() - QPoint(0, 1), marker_rect.size()); QString elided_val = painter->fontMetrics().elidedText(value, Qt::ElideRight, value_rect.width()); diff --git a/tools/cabana/chart/chartswidget.cc b/tools/cabana/chart/chartswidget.cc index aba25dcf83..4e44a560b3 100644 --- a/tools/cabana/chart/chartswidget.cc +++ b/tools/cabana/chart/chartswidget.cc @@ -327,7 +327,7 @@ QStringList ChartsWidget::serializeChartIds() const { for (auto c : charts) { QStringList ids; for (const auto& s : c->sigs) - ids += QString("%1|%2").arg(s.msg_id.toString(), s.sig->name); + ids += QString("%1|%2").arg(QString::fromStdString(s.msg_id.toString()), QString::fromStdString(s.sig->name)); chart_ids += ids.join(','); } std::reverse(chart_ids.begin(), chart_ids.end()); @@ -340,9 +340,9 @@ void ChartsWidget::restoreChartsFromIds(const QStringList& chart_ids) { for (const auto& part : chart_id.split(',')) { const auto sig_parts = part.split('|'); if (sig_parts.size() != 2) continue; - MessageId msg_id = MessageId::fromString(sig_parts[0]); + MessageId msg_id = MessageId::fromString(sig_parts[0].toStdString()); if (auto* msg = dbc()->msg(msg_id)) - if (auto* sig = msg->sig(sig_parts[1])) + if (auto* sig = msg->sig(sig_parts[1].toStdString())) showChart(msg_id, sig, true, index++ > 0); } } diff --git a/tools/cabana/chart/signalselector.cc b/tools/cabana/chart/signalselector.cc index 63f3a7d575..168bf824df 100644 --- a/tools/cabana/chart/signalselector.cc +++ b/tools/cabana/chart/signalselector.cc @@ -46,7 +46,7 @@ SignalSelector::SignalSelector(QString title, QWidget *parent) : QDialog(parent) for (const auto &[id, _] : can->lastMessages()) { if (auto m = dbc()->msg(id)) { - msgs_combo->addItem(QString("%1 (%2)").arg(m->name).arg(id.toString()), QVariant::fromValue(id)); + msgs_combo->addItem(QString("%1 (%2)").arg(QString::fromStdString(m->name)).arg(QString::fromStdString(id.toString())), QVariant::fromValue(id)); } } msgs_combo->model()->sort(0); @@ -92,8 +92,8 @@ void SignalSelector::updateAvailableList(int index) { } void SignalSelector::addItemToList(QListWidget *parent, const MessageId id, const cabana::Signal *sig, bool show_msg_name) { - QString text = QString(" %1").arg(sig->color.name(), sig->name); - if (show_msg_name) text += QString(" %0 %1").arg(msgName(id), id.toString()); + QString text = QString(" %1").arg(sig->color.name(), QString::fromStdString(sig->name)); + if (show_msg_name) text += QString(" %0 %1").arg(QString::fromStdString(msgName(id)), QString::fromStdString(id.toString())); QLabel *label = new QLabel(text); label->setContentsMargins(5, 0, 5, 0); diff --git a/tools/cabana/commands.cc b/tools/cabana/commands.cc index 52861723f4..f158528b51 100644 --- a/tools/cabana/commands.cc +++ b/tools/cabana/commands.cc @@ -4,22 +4,22 @@ // EditMsgCommand -EditMsgCommand::EditMsgCommand(const MessageId &id, const QString &name, int size, - const QString &node, const QString &comment, QUndoCommand *parent) +EditMsgCommand::EditMsgCommand(const MessageId &id, const std::string &name, int size, + const std::string &node, const std::string &comment, QUndoCommand *parent) : id(id), new_name(name), new_size(size), new_node(node), new_comment(comment), QUndoCommand(parent) { if (auto msg = dbc()->msg(id)) { old_name = msg->name; old_size = msg->size; old_node = msg->transmitter; old_comment = msg->comment; - setText(QObject::tr("edit message %1:%2").arg(name).arg(id.address)); + setText(QObject::tr("edit message %1:%2").arg(QString::fromStdString(name)).arg(id.address)); } else { - setText(QObject::tr("new message %1:%2").arg(name).arg(id.address)); + setText(QObject::tr("new message %1:%2").arg(QString::fromStdString(name)).arg(id.address)); } } void EditMsgCommand::undo() { - if (old_name.isEmpty()) + if (old_name.empty()) dbc()->removeMsg(id); else dbc()->updateMsg(id, old_name, old_size, old_node, old_comment); @@ -34,12 +34,12 @@ void EditMsgCommand::redo() { RemoveMsgCommand::RemoveMsgCommand(const MessageId &id, QUndoCommand *parent) : id(id), QUndoCommand(parent) { if (auto msg = dbc()->msg(id)) { message = *msg; - setText(QObject::tr("remove message %1:%2").arg(message.name).arg(id.address)); + setText(QObject::tr("remove message %1:%2").arg(QString::fromStdString(message.name)).arg(id.address)); } } void RemoveMsgCommand::undo() { - if (!message.name.isEmpty()) { + if (!message.name.empty()) { dbc()->updateMsg(id, message.name, message.size, message.transmitter, message.comment); for (auto s : message.getSignals()) dbc()->addSignal(id, *s); @@ -47,7 +47,7 @@ void RemoveMsgCommand::undo() { } void RemoveMsgCommand::redo() { - if (!message.name.isEmpty()) + if (!message.name.empty()) dbc()->removeMsg(id); } @@ -55,7 +55,7 @@ void RemoveMsgCommand::redo() { AddSigCommand::AddSigCommand(const MessageId &id, const cabana::Signal &sig, QUndoCommand *parent) : id(id), signal(sig), QUndoCommand(parent) { - setText(QObject::tr("add signal %1 to %2:%3").arg(sig.name).arg(msgName(id)).arg(id.address)); + setText(QObject::tr("add signal %1 to %2:%3").arg(QString::fromStdString(sig.name)).arg(QString::fromStdString(msgName(id))).arg(id.address)); } void AddSigCommand::undo() { @@ -85,7 +85,7 @@ RemoveSigCommand::RemoveSigCommand(const MessageId &id, const cabana::Signal *si } } } - setText(QObject::tr("remove signal %1 from %2:%3").arg(sig->name).arg(msgName(id)).arg(id.address)); + setText(QObject::tr("remove signal %1 from %2:%3").arg(QString::fromStdString(sig->name)).arg(QString::fromStdString(msgName(id))).arg(id.address)); } void RemoveSigCommand::undo() { for (const auto &s : sigs) dbc()->addSignal(id, s); } @@ -108,7 +108,7 @@ EditSignalCommand::EditSignalCommand(const MessageId &id, const cabana::Signal * } } } - setText(QObject::tr("edit signal %1 in %2:%3").arg(sig->name).arg(msgName(id)).arg(id.address)); + setText(QObject::tr("edit signal %1 in %2:%3").arg(QString::fromStdString(sig->name)).arg(QString::fromStdString(msgName(id))).arg(id.address)); } void EditSignalCommand::undo() { for (const auto &s : sigs) dbc()->updateSignal(id, s.second.name, s.first); } diff --git a/tools/cabana/commands.h b/tools/cabana/commands.h index 0736d9b83f..4081f86985 100644 --- a/tools/cabana/commands.h +++ b/tools/cabana/commands.h @@ -1,6 +1,8 @@ #pragma once +#include #include +#include #include #include @@ -10,14 +12,14 @@ class EditMsgCommand : public QUndoCommand { public: - EditMsgCommand(const MessageId &id, const QString &name, int size, const QString &node, - const QString &comment, QUndoCommand *parent = nullptr); + EditMsgCommand(const MessageId &id, const std::string &name, int size, const std::string &node, + const std::string &comment, QUndoCommand *parent = nullptr); void undo() override; void redo() override; private: const MessageId id; - QString old_name, new_name, old_comment, new_comment, old_node, new_node; + std::string old_name, new_name, old_comment, new_comment, old_node, new_node; int old_size = 0, new_size = 0; }; @@ -52,7 +54,7 @@ public: private: const MessageId id; - QList sigs; + std::vector sigs; }; class EditSignalCommand : public QUndoCommand { @@ -63,7 +65,7 @@ public: private: const MessageId id; - QList> sigs; // QList<{old_sig, new_sig}> + std::vector> sigs; // {old_sig, new_sig} }; namespace UndoStack { diff --git a/tools/cabana/dbc/dbc.cc b/tools/cabana/dbc/dbc.cc index 9b0de92218..8e41cf54e3 100644 --- a/tools/cabana/dbc/dbc.cc +++ b/tools/cabana/dbc/dbc.cc @@ -4,10 +4,6 @@ #include "tools/cabana/utils/util.h" -uint qHash(const MessageId &item) { - return qHash(item.source) ^ qHash(item.address); -} - // cabana::Msg cabana::Msg::~Msg() { @@ -22,7 +18,7 @@ cabana::Signal *cabana::Msg::addSignal(const cabana::Signal &sig) { return s; } -cabana::Signal *cabana::Msg::updateSignal(const QString &sig_name, const cabana::Signal &new_sig) { +cabana::Signal *cabana::Msg::updateSignal(const std::string &sig_name, const cabana::Signal &new_sig) { auto s = sig(sig_name); if (s) { *s = new_sig; @@ -31,7 +27,7 @@ cabana::Signal *cabana::Msg::updateSignal(const QString &sig_name, const cabana: return s; } -void cabana::Msg::removeSignal(const QString &sig_name) { +void cabana::Msg::removeSignal(const std::string &sig_name) { auto it = std::find_if(sigs.begin(), sigs.end(), [&](auto &s) { return s->name == sig_name; }); if (it != sigs.end()) { delete *it; @@ -57,7 +53,7 @@ cabana::Msg &cabana::Msg::operator=(const cabana::Msg &other) { return *this; } -cabana::Signal *cabana::Msg::sig(const QString &sig_name) const { +cabana::Signal *cabana::Msg::sig(const std::string &sig_name) const { auto it = std::find_if(sigs.begin(), sigs.end(), [&](auto &s) { return s->name == sig_name; }); return it != sigs.end() ? *it : nullptr; } @@ -69,17 +65,17 @@ int cabana::Msg::indexOf(const cabana::Signal *sig) const { return -1; } -QString cabana::Msg::newSignalName() { - QString new_name; +std::string cabana::Msg::newSignalName() { + std::string new_name; for (int i = 1; /**/; ++i) { - new_name = QString("NEW_SIGNAL_%1").arg(i); + new_name = "NEW_SIGNAL_" + std::to_string(i); if (sig(new_name) == nullptr) break; } return new_name; } void cabana::Msg::update() { - if (transmitter.isEmpty()) { + if (transmitter.empty()) { transmitter = DEFAULT_NODE_NAME; } mask.assign(size, 0x00); @@ -129,13 +125,13 @@ void cabana::Msg::update() { void cabana::Signal::update() { updateMsbLsb(*this); - if (receiver_name.isEmpty()) { + if (receiver_name.empty()) { receiver_name = DEFAULT_NODE_NAME; } float h = 19 * (float)lsb / 64.0; h = fmod(h, 1.0); - size_t hash = qHash(name); + size_t hash = std::hash{}(name); float s = 0.25 + 0.25 * (float)(hash & 0xff) / 255.0; float v = 0.75 + 0.25 * (float)((hash >> 8) & 0xff) / 255.0; @@ -143,7 +139,7 @@ void cabana::Signal::update() { precision = std::max(num_decimals(factor), num_decimals(offset)); } -QString cabana::Signal::formatValue(double value, bool with_unit) const { +std::string cabana::Signal::formatValue(double value, bool with_unit) const { // Show enum string int64_t raw_value = round((value - offset) / factor); for (const auto &[val, desc] : val_desc) { @@ -152,8 +148,10 @@ QString cabana::Signal::formatValue(double value, bool with_unit) const { } } - QString val_str = QString::number(value, 'f', precision); - if (with_unit && !unit.isEmpty()) { + char buf[64]; + snprintf(buf, sizeof(buf), "%.*f", precision, value); + std::string val_str(buf); + if (with_unit && !unit.empty()) { val_str += " " + unit; } return val_str; diff --git a/tools/cabana/dbc/dbc.h b/tools/cabana/dbc/dbc.h index 134d88a919..a10e7871fe 100644 --- a/tools/cabana/dbc/dbc.h +++ b/tools/cabana/dbc/dbc.h @@ -1,29 +1,35 @@ #pragma once +#include +#include +#include #include +#include #include #include #include #include -#include -const QString UNTITLED = "untitled"; -const QString DEFAULT_NODE_NAME = "XXX"; +const std::string UNTITLED = "untitled"; +const std::string DEFAULT_NODE_NAME = "XXX"; constexpr int CAN_MAX_DATA_BYTES = 64; struct MessageId { uint8_t source = 0; uint32_t address = 0; - QString toString() const { - return QString("%1:%2").arg(source).arg(QString::number(address, 16).toUpper()); + std::string toString() const { + char buf[64]; + snprintf(buf, sizeof(buf), "%u:%X", source, address); + return buf; } - inline static MessageId fromString(const QString &str) { - auto parts = str.split(':'); - if (parts.size() != 2) return {}; - return MessageId{.source = uint8_t(parts[0].toUInt()), .address = parts[1].toUInt(nullptr, 16)}; + inline static MessageId fromString(const std::string &str) { + auto pos = str.find(':'); + if (pos == std::string::npos) return {}; + return MessageId{.source = uint8_t(std::stoul(str.substr(0, pos))), + .address = uint32_t(std::stoul(str.substr(pos + 1), nullptr, 16))}; } bool operator==(const MessageId &other) const { @@ -43,15 +49,17 @@ struct MessageId { } }; -uint qHash(const MessageId &item); Q_DECLARE_METATYPE(MessageId); template <> struct std::hash { - std::size_t operator()(const MessageId &k) const noexcept { return qHash(k); } + std::size_t operator()(const MessageId &k) const noexcept { + return std::hash{}(k.source) ^ (std::hash{}(k.address) << 1); + } }; -typedef std::vector> ValueDescription; +typedef std::vector> ValueDescription; +Q_DECLARE_METATYPE(ValueDescription); namespace cabana { @@ -61,7 +69,7 @@ public: Signal(const Signal &other) = default; void update(); bool getValue(const uint8_t *data, size_t data_size, double *val) const; - QString formatValue(double value, bool with_unit = true) const; + std::string formatValue(double value, bool with_unit = true) const; bool operator==(const cabana::Signal &other) const; inline bool operator!=(const cabana::Signal &other) const { return !(*this == other); } @@ -72,16 +80,16 @@ public: }; Type type = Type::Normal; - QString name; + std::string name; int start_bit, msb, lsb, size; double factor = 1.0; double offset = 0; bool is_signed; bool is_little_endian; double min, max; - QString unit; - QString comment; - QString receiver_name; + std::string unit; + std::string comment; + std::string receiver_name; ValueDescription val_desc; int precision = 0; QColor color; @@ -97,20 +105,20 @@ public: Msg(const Msg &other) { *this = other; } ~Msg(); cabana::Signal *addSignal(const cabana::Signal &sig); - cabana::Signal *updateSignal(const QString &sig_name, const cabana::Signal &sig); - void removeSignal(const QString &sig_name); + cabana::Signal *updateSignal(const std::string &sig_name, const cabana::Signal &sig); + void removeSignal(const std::string &sig_name); Msg &operator=(const Msg &other); int indexOf(const cabana::Signal *sig) const; - cabana::Signal *sig(const QString &sig_name) const; - QString newSignalName(); + cabana::Signal *sig(const std::string &sig_name) const; + std::string newSignalName(); void update(); inline const std::vector &getSignals() const { return sigs; } uint32_t address; - QString name; + std::string name; uint32_t size; - QString comment; - QString transmitter; + std::string comment; + std::string transmitter; std::vector sigs; std::vector mask; @@ -123,4 +131,8 @@ public: double get_raw_value(const uint8_t *data, size_t data_size, const cabana::Signal &sig); void updateMsbLsb(cabana::Signal &s); inline int flipBitPos(int start_bit) { return 8 * (start_bit / 8) + 7 - start_bit % 8; } -inline QString doubleToString(double value) { return QString::number(value, 'g', std::numeric_limits::digits10); } +inline std::string doubleToString(double value) { + char buf[64]; + snprintf(buf, sizeof(buf), "%.*g", std::numeric_limits::digits10, value); + return buf; +} diff --git a/tools/cabana/dbc/dbcfile.cc b/tools/cabana/dbc/dbcfile.cc index 1c03c8a0aa..d9c129ee81 100644 --- a/tools/cabana/dbc/dbcfile.cc +++ b/tools/cabana/dbc/dbcfile.cc @@ -3,11 +3,12 @@ #include #include #include +#include -DBCFile::DBCFile(const QString &dbc_file_name) { - QFile file(dbc_file_name); +DBCFile::DBCFile(const std::string &dbc_file_name) { + QFile file(QString::fromStdString(dbc_file_name)); if (file.open(QIODevice::ReadOnly)) { - name_ = QFileInfo(dbc_file_name).baseName(); + name_ = QFileInfo(QString::fromStdString(dbc_file_name)).baseName().toStdString(); filename = dbc_file_name; parse(file.readAll()); } else { @@ -15,34 +16,35 @@ DBCFile::DBCFile(const QString &dbc_file_name) { } } -DBCFile::DBCFile(const QString &name, const QString &content) : name_(name), filename("") { - parse(content); +DBCFile::DBCFile(const std::string &name, const std::string &content) : name_(name), filename("") { + parse(QString::fromStdString(content)); } bool DBCFile::save() { - assert(!filename.isEmpty()); + assert(!filename.empty()); return writeContents(filename); } -bool DBCFile::saveAs(const QString &new_filename) { +bool DBCFile::saveAs(const std::string &new_filename) { filename = new_filename; return save(); } -bool DBCFile::writeContents(const QString &fn) { - QFile file(fn); +bool DBCFile::writeContents(const std::string &fn) { + QFile file(QString::fromStdString(fn)); if (file.open(QIODevice::WriteOnly)) { - return file.write(generateDBC().toUtf8()) >= 0; + std::string content = generateDBC(); + return file.write(content.c_str(), content.size()) >= 0; } return false; } -void DBCFile::updateMsg(const MessageId &id, const QString &name, uint32_t size, const QString &node, const QString &comment) { +void DBCFile::updateMsg(const MessageId &id, const std::string &name, uint32_t size, const std::string &node, const std::string &comment) { auto &m = msgs[id.address]; m.address = id.address; m.name = name; m.size = size; - m.transmitter = node.isEmpty() ? DEFAULT_NODE_NAME : node; + m.transmitter = node.empty() ? DEFAULT_NODE_NAME : node; m.comment = comment; } @@ -51,12 +53,12 @@ cabana::Msg *DBCFile::msg(uint32_t address) { return it != msgs.end() ? &it->second : nullptr; } -cabana::Msg *DBCFile::msg(const QString &name) { +cabana::Msg *DBCFile::msg(const std::string &name) { auto it = std::find_if(msgs.begin(), msgs.end(), [&name](auto &m) { return m.second.name == name; }); return it != msgs.end() ? &(it->second) : nullptr; } -cabana::Signal *DBCFile::signal(uint32_t address, const QString &name) { +cabana::Signal *DBCFile::signal(uint32_t address, const std::string &name) { auto m = msg(address); return m ? (cabana::Signal *)m->sig(name) : nullptr; } @@ -93,13 +95,13 @@ void DBCFile::parse(const QString &content) { seen = false; } } catch (std::exception &e) { - throw std::runtime_error(QString("[%1:%2]%3: %4").arg(filename).arg(line_num).arg(e.what()).arg(line).toStdString()); + throw std::runtime_error(QString("[%1:%2]%3: %4").arg(QString::fromStdString(filename)).arg(line_num).arg(e.what()).arg(line).toStdString()); } if (seen) { seen_first = true; } else if (!seen_first) { - header += raw_line + "\n"; + header += raw_line.toStdString() + "\n"; } } @@ -122,9 +124,9 @@ cabana::Msg *DBCFile::parseBO(const QString &line) { // Create a new message object cabana::Msg *msg = &msgs[address]; msg->address = address; - msg->name = match.captured("name"); + msg->name = match.captured("name").toStdString(); msg->size = match.captured("size").toULong(); - msg->transmitter = match.captured("transmitter").trimmed(); + msg->transmitter = match.captured("transmitter").trimmed().toStdString(); return msg; } @@ -141,7 +143,7 @@ void DBCFile::parseCM_BO(const QString &line, const QString &content, const QStr throw std::runtime_error("Invalid message comment format"); if (auto m = (cabana::Msg *)msg(match.captured("address").toUInt())) - m->comment = match.captured("comment").trimmed().replace("\\\"", "\""); + m->comment = match.captured("comment").trimmed().replace("\\\"", "\"").toStdString(); } void DBCFile::parseSG(const QString &line, cabana::Msg *current_msg, int &multiplexor_cnt) { @@ -160,7 +162,7 @@ void DBCFile::parseSG(const QString &line, cabana::Msg *current_msg, int &multip if (!match.hasMatch()) throw std::runtime_error("Invalid SG_ line format"); - QString name = match.captured(1); + std::string name = match.captured(1).toStdString(); if (current_msg->sig(name) != nullptr) throw std::runtime_error("Duplicate signal name"); @@ -188,8 +190,8 @@ void DBCFile::parseSG(const QString &line, cabana::Msg *current_msg, int &multip s.offset = match.captured(offset + 7).toDouble(); s.min = match.captured(8 + offset).toDouble(); s.max = match.captured(9 + offset).toDouble(); - s.unit = match.captured(10 + offset); - s.receiver_name = match.captured(11 + offset).trimmed(); + s.unit = match.captured(10 + offset).toStdString(); + s.receiver_name = match.captured(11 + offset).trimmed().toStdString(); current_msg->sigs.push_back(new cabana::Signal(s)); } @@ -205,8 +207,8 @@ void DBCFile::parseCM_SG(const QString &line, const QString &content, const QStr if (!match.hasMatch()) throw std::runtime_error("Invalid CM_ SG_ line format"); - if (auto s = signal(match.captured(1).toUInt(), match.captured(2))) { - s->comment = match.captured(3).trimmed().replace("\\\"", "\""); + if (auto s = signal(match.captured(1).toUInt(), match.captured(2).toStdString())) { + s->comment = match.captured(3).trimmed().replace("\\\"", "\"").toStdString(); } } @@ -217,55 +219,60 @@ void DBCFile::parseVAL(const QString &line) { if (!match.hasMatch()) throw std::runtime_error("invalid VAL_ line format"); - if (auto s = signal(match.captured(1).toUInt(), match.captured(2))) { + if (auto s = signal(match.captured(1).toUInt(), match.captured(2).toStdString())) { QStringList desc_list = match.captured(3).trimmed().split('"'); for (int i = 0; i < desc_list.size(); i += 2) { auto val = desc_list[i].trimmed(); if (!val.isEmpty() && (i + 1) < desc_list.size()) { auto desc = desc_list[i + 1].trimmed(); - s->val_desc.push_back({val.toDouble(), desc}); + s->val_desc.push_back({val.toDouble(), desc.toStdString()}); } } } } -QString DBCFile::generateDBC() { - QString dbc_string, comment, val_desc; +std::string DBCFile::generateDBC() { + std::string dbc_string, comment, val_desc; for (const auto &[address, m] : msgs) { - const QString transmitter = m.transmitter.isEmpty() ? DEFAULT_NODE_NAME : m.transmitter; - dbc_string += QString("BO_ %1 %2: %3 %4\n").arg(address).arg(m.name).arg(m.size).arg(transmitter); - if (!m.comment.isEmpty()) { - comment += QString("CM_ BO_ %1 \"%2\";\n").arg(address).arg(QString(m.comment).replace("\"", "\\\"")); + const std::string &transmitter = m.transmitter.empty() ? DEFAULT_NODE_NAME : m.transmitter; + dbc_string += "BO_ " + std::to_string(address) + " " + m.name + ": " + std::to_string(m.size) + " " + transmitter + "\n"; + if (!m.comment.empty()) { + std::string escaped_comment = m.comment; + // Replace " with \" + for (size_t pos = 0; (pos = escaped_comment.find('"', pos)) != std::string::npos; pos += 2) + escaped_comment.replace(pos, 1, "\\\""); + comment += "CM_ BO_ " + std::to_string(address) + " \"" + escaped_comment + "\";\n"; } for (auto sig : m.getSignals()) { - QString multiplexer_indicator; + std::string multiplexer_indicator; if (sig->type == cabana::Signal::Type::Multiplexor) { multiplexer_indicator = "M "; } else if (sig->type == cabana::Signal::Type::Multiplexed) { - multiplexer_indicator = QString("m%1 ").arg(sig->multiplex_value); + multiplexer_indicator = "m" + std::to_string(sig->multiplex_value) + " "; } - dbc_string += QString(" SG_ %1 %2: %3|%4@%5%6 (%7,%8) [%9|%10] \"%11\" %12\n") - .arg(sig->name) - .arg(multiplexer_indicator) - .arg(sig->start_bit) - .arg(sig->size) - .arg(sig->is_little_endian ? '1' : '0') - .arg(sig->is_signed ? '-' : '+') - .arg(doubleToString(sig->factor)) - .arg(doubleToString(sig->offset)) - .arg(doubleToString(sig->min)) - .arg(doubleToString(sig->max)) - .arg(sig->unit) - .arg(sig->receiver_name.isEmpty() ? DEFAULT_NODE_NAME : sig->receiver_name); - if (!sig->comment.isEmpty()) { - comment += QString("CM_ SG_ %1 %2 \"%3\";\n").arg(address).arg(sig->name).arg(QString(sig->comment).replace("\"", "\\\"")); + const std::string &recv = sig->receiver_name.empty() ? DEFAULT_NODE_NAME : sig->receiver_name; + dbc_string += " SG_ " + sig->name + " " + multiplexer_indicator + ": " + + std::to_string(sig->start_bit) + "|" + std::to_string(sig->size) + "@" + + std::string(1, sig->is_little_endian ? '1' : '0') + + std::string(1, sig->is_signed ? '-' : '+') + + " (" + doubleToString(sig->factor) + "," + doubleToString(sig->offset) + ")" + + " [" + doubleToString(sig->min) + "|" + doubleToString(sig->max) + "]" + + " \"" + sig->unit + "\" " + recv + "\n"; + if (!sig->comment.empty()) { + std::string escaped_comment = sig->comment; + for (size_t pos = 0; (pos = escaped_comment.find('"', pos)) != std::string::npos; pos += 2) + escaped_comment.replace(pos, 1, "\\\""); + comment += "CM_ SG_ " + std::to_string(address) + " " + sig->name + " \"" + escaped_comment + "\";\n"; } if (!sig->val_desc.empty()) { - QStringList text; + std::string text; for (auto &[val, desc] : sig->val_desc) { - text << QString("%1 \"%2\"").arg(val).arg(desc); + if (!text.empty()) text += " "; + char val_buf[64]; + snprintf(val_buf, sizeof(val_buf), "%g", val); + text += std::string(val_buf) + " \"" + desc + "\""; } - val_desc += QString("VAL_ %1 %2 %3;\n").arg(address).arg(sig->name).arg(text.join(" ")); + val_desc += "VAL_ " + std::to_string(address) + " " + sig->name + " " + text + ";\n"; } } dbc_string += "\n"; diff --git a/tools/cabana/dbc/dbcfile.h b/tools/cabana/dbc/dbcfile.h index bd267898f9..decb566abd 100644 --- a/tools/cabana/dbc/dbcfile.h +++ b/tools/cabana/dbc/dbcfile.h @@ -1,34 +1,35 @@ #pragma once #include +#include #include #include "tools/cabana/dbc/dbc.h" class DBCFile { public: - DBCFile(const QString &dbc_file_name); - DBCFile(const QString &name, const QString &content); + DBCFile(const std::string &dbc_file_name); + DBCFile(const std::string &name, const std::string &content); ~DBCFile() {} bool save(); - bool saveAs(const QString &new_filename); - bool writeContents(const QString &fn); - QString generateDBC(); + bool saveAs(const std::string &new_filename); + bool writeContents(const std::string &fn); + std::string generateDBC(); - void updateMsg(const MessageId &id, const QString &name, uint32_t size, const QString &node, const QString &comment); + void updateMsg(const MessageId &id, const std::string &name, uint32_t size, const std::string &node, const std::string &comment); inline void removeMsg(const MessageId &id) { msgs.erase(id.address); } inline const std::map &getMessages() const { return msgs; } cabana::Msg *msg(uint32_t address); - cabana::Msg *msg(const QString &name); + cabana::Msg *msg(const std::string &name); inline cabana::Msg *msg(const MessageId &id) { return msg(id.address); } - cabana::Signal *signal(uint32_t address, const QString &name); + cabana::Signal *signal(uint32_t address, const std::string &name); - inline QString name() const { return name_.isEmpty() ? "untitled" : name_; } - inline bool isEmpty() const { return msgs.empty() && name_.isEmpty(); } + inline std::string name() const { return name_.empty() ? "untitled" : name_; } + inline bool isEmpty() const { return msgs.empty() && name_.empty(); } - QString filename; + std::string filename; private: void parse(const QString &content); @@ -38,7 +39,7 @@ private: void parseCM_SG(const QString &line, const QString &content, const QString &raw_line, const QTextStream &stream); void parseVAL(const QString &line); - QString header; + std::string header; std::map msgs; - QString name_; + std::string name_; }; diff --git a/tools/cabana/dbc/dbcmanager.cc b/tools/cabana/dbc/dbcmanager.cc index 8e98d95322..2236a93da1 100644 --- a/tools/cabana/dbc/dbcmanager.cc +++ b/tools/cabana/dbc/dbcmanager.cc @@ -1,10 +1,9 @@ #include "tools/cabana/dbc/dbcmanager.h" -#include #include -#include +#include -bool DBCManager::open(const SourceSet &sources, const QString &dbc_file_name, QString *error) { +bool DBCManager::open(const SourceSet &sources, const std::string &dbc_file_name, QString *error) { try { auto it = std::find_if(dbc_files.begin(), dbc_files.end(), [&](auto &f) { return f.second && f.second->filename == dbc_file_name; }); @@ -21,7 +20,7 @@ bool DBCManager::open(const SourceSet &sources, const QString &dbc_file_name, QS return true; } -bool DBCManager::open(const SourceSet &sources, const QString &name, const QString &content, QString *error) { +bool DBCManager::open(const SourceSet &sources, const std::string &name, const std::string &content, QString *error) { try { auto file = std::make_shared(name, content); for (auto s : sources) { @@ -64,7 +63,7 @@ void DBCManager::addSignal(const MessageId &id, const cabana::Signal &sig) { } } -void DBCManager::updateSignal(const MessageId &id, const QString &sig_name, const cabana::Signal &sig) { +void DBCManager::updateSignal(const MessageId &id, const std::string &sig_name, const cabana::Signal &sig) { if (auto m = msg(id)) { if (auto s = m->updateSignal(sig_name, sig)) { emit signalUpdated(s); @@ -73,7 +72,7 @@ void DBCManager::updateSignal(const MessageId &id, const QString &sig_name, cons } } -void DBCManager::removeSignal(const MessageId &id, const QString &sig_name) { +void DBCManager::removeSignal(const MessageId &id, const std::string &sig_name) { if (auto m = msg(id)) { if (auto s = m->sig(sig_name)) { emit signalRemoved(s); @@ -83,7 +82,7 @@ void DBCManager::removeSignal(const MessageId &id, const QString &sig_name) { } } -void DBCManager::updateMsg(const MessageId &id, const QString &name, uint32_t size, const QString &node, const QString &comment) { +void DBCManager::updateMsg(const MessageId &id, const std::string &name, uint32_t size, const std::string &node, const std::string &comment) { auto dbc_file = findDBCFile(id); assert(dbc_file); // This should be impossible dbc_file->updateMsg(id, name, size, node, comment); @@ -98,11 +97,13 @@ void DBCManager::removeMsg(const MessageId &id) { emit maskUpdated(); } -QString DBCManager::newMsgName(const MessageId &id) { - return QString("NEW_MSG_") + QString::number(id.address, 16).toUpper(); +std::string DBCManager::newMsgName(const MessageId &id) { + char buf[64]; + snprintf(buf, sizeof(buf), "NEW_MSG_%X", id.address); + return buf; } -QString DBCManager::newSignalName(const MessageId &id) { +std::string DBCManager::newSignalName(const MessageId &id) { auto m = msg(id); return m ? m->newSignalName() : ""; } @@ -118,14 +119,14 @@ cabana::Msg *DBCManager::msg(const MessageId &id) { return dbc_file ? dbc_file->msg(id) : nullptr; } -cabana::Msg *DBCManager::msg(uint8_t source, const QString &name) { +cabana::Msg *DBCManager::msg(uint8_t source, const std::string &name) { auto dbc_file = findDBCFile(source); return dbc_file ? dbc_file->msg(name) : nullptr; } -QStringList DBCManager::signalNames() { +std::vector DBCManager::signalNames() { // Used for autocompletion - QSet names; + std::set names; for (auto &f : allDBCFiles()) { for (auto &[_, m] : f->getMessages()) { for (auto sig : m.getSignals()) { @@ -133,8 +134,8 @@ QStringList DBCManager::signalNames() { } } } - QStringList ret = names.values(); - ret.sort(); + std::vector ret(names.begin(), names.end()); + std::sort(ret.begin(), ret.end()); return ret; } @@ -165,11 +166,13 @@ const SourceSet DBCManager::sources(const DBCFile *dbc_file) const { return sources; } -QString toString(const SourceSet &ss) { - return std::accumulate(ss.cbegin(), ss.cend(), QString(), [](QString str, int source) { - if (!str.isEmpty()) str += ", "; - return str + (source == -1 ? QStringLiteral("all") : QString::number(source)); - }); +std::string toString(const SourceSet &ss) { + std::string result; + for (int source : ss) { + if (!result.empty()) result += ", "; + result += (source == -1) ? "all" : std::to_string(source); + } + return result; } DBCManager *dbc() { diff --git a/tools/cabana/dbc/dbcmanager.h b/tools/cabana/dbc/dbcmanager.h index 5f183752d2..4a122073ea 100644 --- a/tools/cabana/dbc/dbcmanager.h +++ b/tools/cabana/dbc/dbcmanager.h @@ -4,6 +4,8 @@ #include #include #include +#include +#include #include "tools/cabana/dbc/dbcfile.h" @@ -18,27 +20,27 @@ class DBCManager : public QObject { public: DBCManager(QObject *parent) : QObject(parent) {} ~DBCManager() {} - bool open(const SourceSet &sources, const QString &dbc_file_name, QString *error = nullptr); - bool open(const SourceSet &sources, const QString &name, const QString &content, QString *error = nullptr); + bool open(const SourceSet &sources, const std::string &dbc_file_name, QString *error = nullptr); + bool open(const SourceSet &sources, const std::string &name, const std::string &content, QString *error = nullptr); void close(const SourceSet &sources); void close(DBCFile *dbc_file); void closeAll(); void addSignal(const MessageId &id, const cabana::Signal &sig); - void updateSignal(const MessageId &id, const QString &sig_name, const cabana::Signal &sig); - void removeSignal(const MessageId &id, const QString &sig_name); + void updateSignal(const MessageId &id, const std::string &sig_name, const cabana::Signal &sig); + void removeSignal(const MessageId &id, const std::string &sig_name); - void updateMsg(const MessageId &id, const QString &name, uint32_t size, const QString &node, const QString &comment); + void updateMsg(const MessageId &id, const std::string &name, uint32_t size, const std::string &node, const std::string &comment); void removeMsg(const MessageId &id); - QString newMsgName(const MessageId &id); - QString newSignalName(const MessageId &id); + std::string newMsgName(const MessageId &id); + std::string newSignalName(const MessageId &id); const std::map &getMessages(uint8_t source); cabana::Msg *msg(const MessageId &id); - cabana::Msg* msg(uint8_t source, const QString &name); + cabana::Msg* msg(uint8_t source, const std::string &name); - QStringList signalNames(); + std::vector signalNames(); inline int dbcCount() { return allDBCFiles().size(); } int nonEmptyDBCCount(); @@ -62,8 +64,8 @@ private: DBCManager *dbc(); -QString toString(const SourceSet &ss); -inline QString msgName(const MessageId &id) { +std::string toString(const SourceSet &ss); +inline std::string msgName(const MessageId &id) { auto msg = dbc()->msg(id); return msg ? msg->name : UNTITLED; } diff --git a/tools/cabana/detailwidget.cc b/tools/cabana/detailwidget.cc index 35492c8efa..148b059e5b 100644 --- a/tools/cabana/detailwidget.cc +++ b/tools/cabana/detailwidget.cc @@ -124,9 +124,9 @@ int DetailWidget::findOrAddTab(const MessageId& message_id) { if (tabbar->tabData(index).value() == message_id) break; } if (index == -1) { - index = tabbar->addTab(message_id.toString()); + index = tabbar->addTab(QString::fromStdString(message_id.toString())); tabbar->setTabData(index, QVariant::fromValue(message_id)); - tabbar->setTabToolTip(index, msgName(message_id)); + tabbar->setTabToolTip(index, QString::fromStdString(msgName(message_id))); } return index; } @@ -151,21 +151,21 @@ std::pair DetailWidget::serializeMessageIds() const { QStringList msgs; for (int i = 0; i < tabbar->count(); ++i) { MessageId id = tabbar->tabData(i).value(); - msgs.append(id.toString()); + msgs.append(QString::fromStdString(id.toString())); } - return std::make_pair(msg_id.toString(), msgs); + return std::make_pair(QString::fromStdString(msg_id.toString()), msgs); } void DetailWidget::restoreTabs(const QString active_msg_id, const QStringList& msg_ids) { tabbar->blockSignals(true); for (const auto& str_id : msg_ids) { - MessageId id = MessageId::fromString(str_id); + MessageId id = MessageId::fromString(str_id.toStdString()); if (dbc()->msg(id) != nullptr) findOrAddTab(id); } tabbar->blockSignals(false); - auto active_id = MessageId::fromString(active_msg_id); + auto active_id = MessageId::fromString(active_msg_id.toStdString()); if (dbc()->msg(active_id) != nullptr) setMessage(active_id); } @@ -180,10 +180,10 @@ void DetailWidget::refresh() { warnings.push_back(tr("Message size (%1) is incorrect.").arg(msg->size)); } for (auto s : binary_view->getOverlappingSignals()) { - warnings.push_back(tr("%1 has overlapping bits.").arg(s->name)); + warnings.push_back(tr("%1 has overlapping bits.").arg(QString::fromStdString(s->name))); } } - QString msg_name = msg ? QString("%1 (%2)").arg(msg->name, msg->transmitter) : msgName(msg_id); + QString msg_name = msg ? QString("%1 (%2)").arg(QString::fromStdString(msg->name), QString::fromStdString(msg->transmitter)) : QString::fromStdString(msgName(msg_id)); name_label->setText(msg_name); name_label->setToolTip(msg_name); action_remove_msg->setEnabled(msg != nullptr); @@ -208,10 +208,10 @@ void DetailWidget::updateState(const std::set *msgs) { void DetailWidget::editMsg() { auto msg = dbc()->msg(msg_id); int size = msg ? msg->size : can->lastMessage(msg_id).dat.size(); - EditMessageDialog dlg(msg_id, msgName(msg_id), size, this); + EditMessageDialog dlg(msg_id, QString::fromStdString(msgName(msg_id)), size, this); if (dlg.exec()) { - UndoStack::push(new EditMsgCommand(msg_id, dlg.name_edit->text().trimmed(), dlg.size_spin->value(), - dlg.node->text().trimmed(), dlg.comment_edit->toPlainText().trimmed())); + UndoStack::push(new EditMsgCommand(msg_id, dlg.name_edit->text().trimmed().toStdString(), dlg.size_spin->value(), + dlg.node->text().trimmed().toStdString(), dlg.comment_edit->toPlainText().trimmed().toStdString())); } } @@ -223,7 +223,7 @@ void DetailWidget::removeMsg() { EditMessageDialog::EditMessageDialog(const MessageId &msg_id, const QString &title, int size, QWidget *parent) : original_name(title), msg_id(msg_id), QDialog(parent) { - setWindowTitle(tr("Edit message: %1").arg(msg_id.toString())); + setWindowTitle(tr("Edit message: %1").arg(QString::fromStdString(msg_id.toString()))); QFormLayout *form_layout = new QFormLayout(this); form_layout->addRow("", error_label = new QLabel); @@ -241,8 +241,8 @@ EditMessageDialog::EditMessageDialog(const MessageId &msg_id, const QString &tit form_layout->addRow(btn_box = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel)); if (auto msg = dbc()->msg(msg_id)) { - node->setText(msg->transmitter); - comment_edit->setText(msg->comment); + node->setText(QString::fromStdString(msg->transmitter)); + comment_edit->setText(QString::fromStdString(msg->comment)); } validateName(name_edit->text()); setFixedWidth(parent->width() * 0.9); @@ -252,10 +252,10 @@ EditMessageDialog::EditMessageDialog(const MessageId &msg_id, const QString &tit } void EditMessageDialog::validateName(const QString &text) { - bool valid = text.compare(UNTITLED, Qt::CaseInsensitive) != 0; + bool valid = text.compare(QString::fromStdString(UNTITLED), Qt::CaseInsensitive) != 0; error_label->setVisible(false); if (!text.isEmpty() && valid && text != original_name) { - valid = dbc()->msg(msg_id.source, text) == nullptr; + valid = dbc()->msg(msg_id.source, text.toStdString()) == nullptr; if (!valid) { error_label->setText(tr("Name already exists")); error_label->setVisible(true); diff --git a/tools/cabana/historylog.cc b/tools/cabana/historylog.cc index 3dbdf5a7cd..fb79ff9cea 100644 --- a/tools/cabana/historylog.cc +++ b/tools/cabana/historylog.cc @@ -14,7 +14,7 @@ QVariant HistoryLogModel::data(const QModelIndex &index, int role) const { const int col = index.column(); if (role == Qt::DisplayRole) { if (col == 0) return QString::number(can->toSeconds(m.mono_time), 'f', 3); - if (!isHexMode()) return sigs[col - 1]->formatValue(m.sig_values[col - 1], false); + if (!isHexMode()) return QString::fromStdString(sigs[col - 1]->formatValue(m.sig_values[col - 1], false)); } else if (role == Qt::TextAlignmentRole) { return (uint32_t)(Qt::AlignRight | Qt::AlignVCenter); } @@ -49,8 +49,8 @@ QVariant HistoryLogModel::headerData(int section, Qt::Orientation orientation, i if (section == 0) return "Time"; if (isHexMode()) return "Data"; - QString name = sigs[section - 1]->name; - QString unit = sigs[section - 1]->unit; + QString name = QString::fromStdString(sigs[section - 1]->name); + QString unit = QString::fromStdString(sigs[section - 1]->unit); return unit.isEmpty() ? name : QString("%1 (%2)").arg(name, unit); } else if (role == Qt::BackgroundRole && section > 0 && !isHexMode()) { // Alpha-blend the signal color with the background to ensure contrast @@ -216,7 +216,7 @@ LogsWidget::LogsWidget(QWidget *parent) : QFrame(parent) { void LogsWidget::modelReset() { signals_cb->clear(); for (auto s : model->sigs) { - signals_cb->addItem(s->name); + signals_cb->addItem(QString::fromStdString(s->name)); } export_btn->setEnabled(false); value_edit->clear(); @@ -238,8 +238,8 @@ void LogsWidget::filterChanged() { } void LogsWidget::exportToCSV() { - QString dir = QString("%1/%2_%3.csv").arg(settings.last_dir).arg(can->routeName()).arg(msgName(model->msg_id)); - QString fn = QFileDialog::getSaveFileName(this, QString("Export %1 to CSV file").arg(msgName(model->msg_id)), + QString dir = QString("%1/%2_%3.csv").arg(settings.last_dir).arg(QString::fromStdString(can->routeName())).arg(QString::fromStdString(msgName(model->msg_id))); + QString fn = QFileDialog::getSaveFileName(this, QString("Export %1 to CSV file").arg(QString::fromStdString(msgName(model->msg_id))), dir, tr("csv (*.csv)")); if (!fn.isEmpty()) { model->isHexMode() ? utils::exportToCSV(fn, model->msg_id) diff --git a/tools/cabana/mainwin.cc b/tools/cabana/mainwin.cc index a7040f891e..39fb979c79 100644 --- a/tools/cabana/mainwin.cc +++ b/tools/cabana/mainwin.cc @@ -234,7 +234,7 @@ void MainWindow::DBCFileChanged() { QStringList title; for (auto f : dbc()->allDBCFiles()) { - title.push_back(tr("(%1) %2").arg(toString(dbc()->sources(f)), f->name())); + title.push_back(tr("(%1) %2").arg(QString::fromStdString(toString(dbc()->sources(f))), QString::fromStdString(f->name()))); } setWindowFilePath(title.join(" | ")); @@ -259,7 +259,7 @@ void MainWindow::closeStream() { } void MainWindow::exportToCSV() { - QString dir = QString("%1/%2.csv").arg(settings.last_dir).arg(can->routeName()); + QString dir = QString("%1/%2.csv").arg(settings.last_dir).arg(QString::fromStdString(can->routeName())); QString fn = QFileDialog::getSaveFileName(this, "Export stream to CSV file", dir, tr("csv (*.csv)")); if (!fn.isEmpty()) { utils::exportToCSV(fn); @@ -268,7 +268,7 @@ void MainWindow::exportToCSV() { void MainWindow::newFile(SourceSet s) { closeFile(s); - dbc()->open(s, "", ""); + dbc()->open(s, std::string(""), std::string("")); } void MainWindow::openFile(SourceSet s) { @@ -284,7 +284,7 @@ void MainWindow::loadFile(const QString &fn, SourceSet s) { closeFile(s); QString error; - if (dbc()->open(s, fn, &error)) { + if (dbc()->open(s, fn.toStdString(), &error)) { updateRecentFiles(fn); statusBar()->showMessage(tr("DBC File %1 loaded").arg(fn), 2000); } else { @@ -304,7 +304,7 @@ void MainWindow::loadFromClipboard(SourceSet s, bool close_all) { QString dbc_str = QGuiApplication::clipboard()->text(); QString error; - bool ret = dbc()->open(s, "", dbc_str, &error); + bool ret = dbc()->open(s, std::string(""), dbc_str.toStdString(), &error); if (ret && dbc()->nonEmptyDBCCount() > 0) { QMessageBox::information(this, tr("Load From Clipboard"), tr("DBC Successfully Loaded!")); } else { @@ -333,7 +333,7 @@ void MainWindow::startStream(AbstractStream *stream, QString dbc_file) { can->start(); loadFile(dbc_file); - statusBar()->showMessage(tr("Stream [%1] started").arg(can->routeName()), 2000); + statusBar()->showMessage(tr("Stream [%1] started").arg(QString::fromStdString(can->routeName())), 2000); bool has_stream = dynamic_cast(can) == nullptr; close_stream_act->setEnabled(has_stream); @@ -341,7 +341,7 @@ void MainWindow::startStream(AbstractStream *stream, QString dbc_file) { tools_menu->setEnabled(has_stream); createDockWidgets(); - video_dock->setWindowTitle(can->routeName()); + video_dock->setWindowTitle(QString::fromStdString(can->routeName())); if (can->liveStreaming() || video_splitter->sizes()[0] == 0) { // display video at minimum size. video_splitter->setSizes({1, 1}); @@ -368,9 +368,9 @@ void MainWindow::startStream(AbstractStream *stream, QString dbc_file) { } void MainWindow::eventsMerged() { - if (!can->liveStreaming() && std::exchange(car_fingerprint, can->carFingerprint()) != car_fingerprint) { + if (!can->liveStreaming() && std::exchange(car_fingerprint, QString::fromStdString(can->carFingerprint())) != car_fingerprint) { video_dock->setWindowTitle(tr("ROUTE: %1 FINGERPRINT: %2") - .arg(can->routeName()) + .arg(QString::fromStdString(can->routeName())) .arg(car_fingerprint.isEmpty() ? tr("Unknown Car") : car_fingerprint)); // Don't overwrite already loaded DBC if (!dbc()->nonEmptyDBCCount() && fingerprint_to_dbc.object().contains(car_fingerprint)) { @@ -416,7 +416,7 @@ void MainWindow::closeFile(DBCFile *dbc_file) { void MainWindow::saveFile(DBCFile *dbc_file) { assert(dbc_file != nullptr); - if (!dbc_file->filename.isEmpty()) { + if (!dbc_file->filename.empty()) { dbc_file->save(); UndoStack::instance()->setClean(); statusBar()->showMessage(tr("File saved"), 2000); @@ -426,10 +426,10 @@ void MainWindow::saveFile(DBCFile *dbc_file) { } void MainWindow::saveFileAs(DBCFile *dbc_file) { - QString title = tr("Save File (bus: %1)").arg(toString(dbc()->sources(dbc_file))); + QString title = tr("Save File (bus: %1)").arg(QString::fromStdString(toString(dbc()->sources(dbc_file)))); QString fn = QFileDialog::getSaveFileName(this, title, QDir::cleanPath(settings.last_dir + "/untitled.dbc"), tr("DBC (*.dbc)")); if (!fn.isEmpty()) { - dbc_file->saveAs(fn); + dbc_file->saveAs(fn.toStdString()); UndoStack::instance()->setClean(); statusBar()->showMessage(tr("File saved as %1").arg(fn), 2000); updateRecentFiles(fn); @@ -446,7 +446,7 @@ void MainWindow::saveToClipboard() { void MainWindow::saveFileToClipboard(DBCFile *dbc_file) { assert(dbc_file != nullptr); - QGuiApplication::clipboard()->setText(dbc_file->generateDBC()); + QGuiApplication::clipboard()->setText(QString::fromStdString(dbc_file->generateDBC())); QMessageBox::information(this, tr("Copy To Clipboard"), tr("DBC Successfully copied!")); } @@ -467,14 +467,14 @@ void MainWindow::updateLoadSaveMenus() { auto dbc_file = dbc()->findDBCFile(source); if (dbc_file) { bus_menu->addSeparator(); - bus_menu->addAction(dbc_file->name() + " (" + toString(dbc()->sources(dbc_file)) + ")")->setEnabled(false); + bus_menu->addAction(QString::fromStdString(dbc_file->name()) + " (" + QString::fromStdString(toString(dbc()->sources(dbc_file))) + ")")->setEnabled(false); bus_menu->addAction(tr("Save..."), [=]() { saveFile(dbc_file); }); bus_menu->addAction(tr("Save As..."), [=]() { saveFileAs(dbc_file); }); bus_menu->addAction(tr("Copy to Clipboard..."), [=]() { saveFileToClipboard(dbc_file); }); bus_menu->addAction(tr("Remove from this bus..."), [=]() { closeFile(ss); }); bus_menu->addAction(tr("Remove from all buses..."), [=]() { closeFile(dbc_file); }); } - bus_menu->setTitle(tr("Bus %1 (%2)").arg(source).arg(dbc_file ? dbc_file->name() : "No DBCs loaded")); + bus_menu->setTitle(tr("Bus %1 (%2)").arg(source).arg(dbc_file ? QString::fromStdString(dbc_file->name()) : "No DBCs loaded")); manage_dbcs_menu->addMenu(bus_menu); } @@ -627,7 +627,7 @@ void MainWindow::saveSessionState() { settings.active_charts.clear(); for (auto &f : dbc()->allDBCFiles()) - if (!f->isEmpty()) { settings.recent_dbc_file = f->filename; break; } + if (!f->isEmpty()) { settings.recent_dbc_file = QString::fromStdString(f->filename); break; } if (auto *detail = center_widget->getDetailWidget()) { auto [active_id, ids] = detail->serializeMessageIds(); @@ -643,7 +643,7 @@ void MainWindow::restoreSessionState() { QString dbc_file; for (auto& f : dbc()->allDBCFiles()) - if (!f->isEmpty()) { dbc_file = f->filename; break; } + if (!f->isEmpty()) { dbc_file = QString::fromStdString(f->filename); break; } if (dbc_file != settings.recent_dbc_file) return; if (!settings.selected_msg_ids.isEmpty()) diff --git a/tools/cabana/messageswidget.cc b/tools/cabana/messageswidget.cc index ed9aeaf311..ec8c82dd98 100644 --- a/tools/cabana/messageswidget.cc +++ b/tools/cabana/messageswidget.cc @@ -205,7 +205,7 @@ QVariant MessageListModel::data(const QModelIndex &index, int role) const { } else if (role == Qt::ToolTipRole && index.column() == Column::NAME) { auto msg = dbc()->msg(item.id); auto tooltip = item.name; - if (msg && !msg->comment.isEmpty()) tooltip += "
" + msg->comment + ""; + if (msg && !msg->comment.empty()) tooltip += "
" + QString::fromStdString(msg->comment) + ""; return tooltip; } return {}; @@ -277,7 +277,7 @@ bool MessageListModel::match(const MessageListModel::Item &item) { if (!match) { const auto m = dbc()->msg(item.id); match = m && std::any_of(m->sigs.cbegin(), m->sigs.cend(), - [&txt](const auto &s) { return s->name.contains(txt, Qt::CaseInsensitive); }); + [&txt](const auto &s) { return QString::fromStdString(s->name).contains(txt, Qt::CaseInsensitive); }); } break; } @@ -323,8 +323,8 @@ bool MessageListModel::filterAndSort() { if (show_inactive_messages || can->isMessageActive(id)) { auto msg = dbc()->msg(id); Item item = {.id = id, - .name = msg ? msg->name : UNTITLED, - .node = msg ? msg->transmitter : QString()}; + .name = msg ? QString::fromStdString(msg->name) : QString::fromStdString(UNTITLED), + .node = msg ? QString::fromStdString(msg->transmitter) : QString()}; if (match(item)) items.emplace_back(item); } diff --git a/tools/cabana/signalview.cc b/tools/cabana/signalview.cc index a9ceacd806..df05d287f4 100644 --- a/tools/cabana/signalview.cc +++ b/tools/cabana/signalview.cc @@ -34,8 +34,8 @@ SignalModel::SignalModel(QObject *parent) : root(new Item), QAbstractItemModel(p } void SignalModel::insertItem(SignalModel::Item *root_item, int pos, const cabana::Signal *sig) { - Item *parent_item = new Item{.type = Item::Sig, .parent = root_item, .sig = sig, .title = sig->name}; - root_item->children.insert(pos, parent_item); + Item *parent_item = new Item{.type = Item::Sig, .parent = root_item, .sig = sig, .title = QString::fromStdString(sig->name)}; + root_item->children.insert(root_item->children.begin() + pos, parent_item); QString titles[]{"Name", "Size", "Receiver Nodes", "Little Endian", "Signed", "Offset", "Factor", "Type", "Multiplex Value", "Extra Info", "Unit", "Comment", "Minimum Value", "Maximum Value", "Value Table"}; for (int i = 0; i < std::size(titles); ++i) { @@ -63,7 +63,7 @@ void SignalModel::refresh() { root.reset(new SignalModel::Item); if (auto msg = dbc()->msg(msg_id)) { for (auto s : msg->getSignals()) { - if (filter_str.isEmpty() || s->name.contains(filter_str, Qt::CaseInsensitive)) { + if (filter_str.isEmpty() || QString::fromStdString(s->name).contains(filter_str, Qt::CaseInsensitive)) { insertItem(root.get(), root->children.size(), s); } } @@ -124,25 +124,25 @@ QVariant SignalModel::data(const QModelIndex &index, int role) const { const Item *item = getItem(index); if (role == Qt::DisplayRole || role == Qt::EditRole) { if (index.column() == 0) { - return item->type == Item::Sig ? item->sig->name : item->title; + return item->type == Item::Sig ? QString::fromStdString(item->sig->name) : item->title; } else { switch (item->type) { case Item::Sig: return item->sig_val; - case Item::Name: return item->sig->name; + case Item::Name: return QString::fromStdString(item->sig->name); case Item::Size: return item->sig->size; - case Item::Node: return item->sig->receiver_name; + case Item::Node: return QString::fromStdString(item->sig->receiver_name); case Item::SignalType: return signalTypeToString(item->sig->type); case Item::MultiplexValue: return item->sig->multiplex_value; - case Item::Offset: return doubleToString(item->sig->offset); - case Item::Factor: return doubleToString(item->sig->factor); - case Item::Unit: return item->sig->unit; - case Item::Comment: return item->sig->comment; - case Item::Min: return doubleToString(item->sig->min); - case Item::Max: return doubleToString(item->sig->max); + case Item::Offset: return QString::fromStdString(doubleToString(item->sig->offset)); + case Item::Factor: return QString::fromStdString(doubleToString(item->sig->factor)); + case Item::Unit: return QString::fromStdString(item->sig->unit); + case Item::Comment: return QString::fromStdString(item->sig->comment); + case Item::Min: return QString::fromStdString(doubleToString(item->sig->min)); + case Item::Max: return QString::fromStdString(doubleToString(item->sig->max)); case Item::Desc: { QStringList val_desc; for (auto &[val, desc] : item->sig->val_desc) { - val_desc << QString("%1 \"%2\"").arg(val).arg(desc); + val_desc << QString("%1 \"%2\"").arg(val).arg(QString::fromStdString(desc)); } return val_desc.join(" "); } @@ -165,17 +165,17 @@ bool SignalModel::setData(const QModelIndex &index, const QVariant &value, int r Item *item = getItem(index); cabana::Signal s = *item->sig; switch (item->type) { - case Item::Name: s.name = value.toString(); break; + case Item::Name: s.name = value.toString().toStdString(); break; case Item::Size: s.size = value.toInt(); break; - case Item::Node: s.receiver_name = value.toString().trimmed(); break; + case Item::Node: s.receiver_name = value.toString().trimmed().toStdString(); break; case Item::SignalType: s.type = (cabana::Signal::Type)value.toInt(); break; case Item::MultiplexValue: s.multiplex_value = value.toInt(); break; case Item::Endian: s.is_little_endian = value.toBool(); break; case Item::Signed: s.is_signed = value.toBool(); break; case Item::Offset: s.offset = value.toDouble(); break; case Item::Factor: s.factor = value.toDouble(); break; - case Item::Unit: s.unit = value.toString(); break; - case Item::Comment: s.comment = value.toString(); break; + case Item::Unit: s.unit = value.toString().toStdString(); break; + case Item::Comment: s.comment = value.toString().toStdString(); break; case Item::Min: s.min = value.toDouble(); break; case Item::Max: s.max = value.toDouble(); break; case Item::Desc: s.val_desc = value.value(); break; @@ -189,7 +189,7 @@ bool SignalModel::setData(const QModelIndex &index, const QVariant &value, int r bool SignalModel::saveSignal(const cabana::Signal *origin_s, cabana::Signal &s) { auto msg = dbc()->msg(msg_id); if (s.name != origin_s->name && msg->sig(s.name) != nullptr) { - QString text = tr("There is already a signal with the same name '%1'").arg(s.name); + QString text = tr("There is already a signal with the same name '%1'").arg(QString::fromStdString(s.name)); QMessageBox::warning(nullptr, tr("Failed to save signal"), text); return false; } @@ -214,7 +214,7 @@ void SignalModel::handleSignalAdded(MessageId id, const cabana::Signal *sig) { beginInsertRows({}, i, i); insertItem(root.get(), i, sig); endInsertRows(); - } else if (sig->name.contains(filter_str, Qt::CaseInsensitive)) { + } else if (QString::fromStdString(sig->name).contains(filter_str, Qt::CaseInsensitive)) { refresh(); } } @@ -229,7 +229,9 @@ void SignalModel::handleSignalUpdated(const cabana::Signal *sig) { int to = dbc()->msg(msg_id)->indexOf(sig); if (to != row) { beginMoveRows({}, row, row, {}, to > row ? to + 1 : to); - root->children.move(row, to); + auto item = root->children[row]; + root->children.erase(root->children.begin() + row); + root->children.insert(root->children.begin() + to, item); endMoveRows(); } } @@ -239,7 +241,8 @@ void SignalModel::handleSignalUpdated(const cabana::Signal *sig) { void SignalModel::handleSignalRemoved(const cabana::Signal *sig) { if (int row = signalRow(sig); row != -1) { beginRemoveRows({}, row, row); - delete root->children.takeAt(row); + delete root->children[row]; + root->children.erase(root->children.begin() + row); endRemoveRows(); } } @@ -373,7 +376,10 @@ QWidget *SignalItemDelegate::createEditor(QWidget *parent, const QStyleOptionVie else e->setValidator(double_validator); if (item->type == SignalModel::Item::Name) { - QCompleter *completer = new QCompleter(dbc()->signalNames(), e); + auto names = dbc()->signalNames(); + QStringList qnames; + for (const auto &n : names) qnames.push_back(QString::fromStdString(n)); + QCompleter *completer = new QCompleter(qnames, e); completer->setCaseSensitivity(Qt::CaseInsensitive); completer->setFilterMode(Qt::MatchContains); e->setCompleter(completer); @@ -395,7 +401,7 @@ QWidget *SignalItemDelegate::createEditor(QWidget *parent, const QStyleOptionVie return c; } else if (item->type == SignalModel::Item::Desc) { ValueDescriptionDlg dlg(item->sig->val_desc, parent); - dlg.setWindowTitle(item->sig->name); + dlg.setWindowTitle(QString::fromStdString(item->sig->name)); if (dlg.exec()) { ((QAbstractItemModel *)index.model())->setData(index, QVariant::fromValue(dlg.val_desc)); } @@ -621,7 +627,7 @@ void SignalView::updateState(const std::set *msgs) { for (auto item : model->root->children) { double value = 0; if (item->sig->getValue(last_msg.dat.data(), last_msg.dat.size(), &value)) { - item->sig_val = item->sig->formatValue(value); + item->sig_val = QString::fromStdString(item->sig->formatValue(value)); max_value_width = std::max(max_value_width, fontMetrics().horizontalAdvance(item->sig_val)); } } @@ -677,7 +683,7 @@ ValueDescriptionDlg::ValueDescriptionDlg(const ValueDescription &descriptions, Q int row = 0; for (auto &[val, desc] : descriptions) { table->setItem(row, 0, new QTableWidgetItem(QString::number(val))); - table->setItem(row, 1, new QTableWidgetItem(desc)); + table->setItem(row, 1, new QTableWidgetItem(QString::fromStdString(desc))); ++row; } @@ -706,7 +712,7 @@ void ValueDescriptionDlg::save() { QString val = table->item(i, 0)->text().trimmed(); QString desc = table->item(i, 1)->text().trimmed(); if (!val.isEmpty() && !desc.isEmpty()) { - val_desc.push_back({val.toDouble(), desc}); + val_desc.push_back({val.toDouble(), desc.toStdString()}); } } QDialog::accept(); diff --git a/tools/cabana/signalview.h b/tools/cabana/signalview.h index 4e746ea105..42db830df7 100644 --- a/tools/cabana/signalview.h +++ b/tools/cabana/signalview.h @@ -20,12 +20,15 @@ class SignalModel : public QAbstractItemModel { public: struct Item { enum Type {Root, Sig, Name, Size, Node, Endian, Signed, Offset, Factor, SignalType, MultiplexValue, ExtraInfo, Unit, Comment, Min, Max, Desc }; - ~Item() { qDeleteAll(children); } - inline int row() { return parent->children.indexOf(this); } + ~Item() { for (auto c : children) delete c; } + inline int row() { + auto it = std::find(parent->children.begin(), parent->children.end(), this); + return it != parent->children.end() ? std::distance(parent->children.begin(), it) : -1; + } Type type = Type::Root; Item *parent = nullptr; - QList children; + std::vector children; const cabana::Signal *sig = nullptr; QString title; diff --git a/tools/cabana/streams/abstractstream.h b/tools/cabana/streams/abstractstream.h index f35b19d34f..7d66a420dc 100644 --- a/tools/cabana/streams/abstractstream.h +++ b/tools/cabana/streams/abstractstream.h @@ -65,8 +65,8 @@ public: virtual void start() = 0; virtual bool liveStreaming() const { return true; } virtual void seekTo(double ts) {} - virtual QString routeName() const = 0; - virtual QString carFingerprint() const { return ""; } + virtual std::string routeName() const = 0; + virtual std::string carFingerprint() const { return ""; } virtual QDateTime beginDateTime() const { return {}; } virtual uint64_t beginMonoTime() const { return 0; } virtual double minSeconds() const { return 0; } @@ -149,7 +149,7 @@ class DummyStream : public AbstractStream { Q_OBJECT public: DummyStream(QObject *parent) : AbstractStream(parent) {} - QString routeName() const override { return tr("No Stream"); } + std::string routeName() const override { return "No Stream"; } void start() override {} }; diff --git a/tools/cabana/streams/devicestream.h b/tools/cabana/streams/devicestream.h index 6beb300d7a..4bcdb5351d 100644 --- a/tools/cabana/streams/devicestream.h +++ b/tools/cabana/streams/devicestream.h @@ -9,8 +9,8 @@ class DeviceStream : public LiveStream { public: DeviceStream(QObject *parent, QString address = {}); ~DeviceStream(); - inline QString routeName() const override { - return QString("Live Streaming From %1").arg(zmq_address.isEmpty() ? "127.0.0.1" : zmq_address); + inline std::string routeName() const override { + return "Live Streaming From " + (zmq_address.isEmpty() ? std::string("127.0.0.1") : zmq_address.toStdString()); } protected: diff --git a/tools/cabana/streams/pandastream.cc b/tools/cabana/streams/pandastream.cc index a2430c665f..3692f71a11 100644 --- a/tools/cabana/streams/pandastream.cc +++ b/tools/cabana/streams/pandastream.cc @@ -16,8 +16,8 @@ PandaStream::PandaStream(QObject *parent, PandaStreamConfig config_) : config(co bool PandaStream::connect() { try { - qDebug() << "Connecting to panda " << config.serial; - panda.reset(new Panda(config.serial.toStdString())); + qDebug() << "Connecting to panda " << config.serial.c_str(); + panda.reset(new Panda(config.serial)); config.bus_config.resize(3); qDebug() << "Connected"; } catch (const std::exception& e) { @@ -81,7 +81,7 @@ void PandaStream::streamThread() { OpenPandaWidget::OpenPandaWidget(QWidget *parent) : AbstractOpenStreamWidget(parent) { form_layout = new QFormLayout(this); if (can && dynamic_cast(can) != nullptr) { - form_layout->addWidget(new QLabel(tr("Already connected to %1.").arg(can->routeName()))); + form_layout->addWidget(new QLabel(tr("Already connected to %1.").arg(QString::fromStdString(can->routeName())))); form_layout->addWidget(new QLabel("Close the current connection via [File menu -> Close Stream] before connecting to another Panda.")); QTimer::singleShot(0, [this]() { emit enableOpenButton(false); }); return; @@ -129,7 +129,7 @@ void OpenPandaWidget::buildConfigForm() { } if (has_panda) { - config.serial = serial; + config.serial = serial.toStdString(); config.bus_config.resize(3); for (int i = 0; i < config.bus_config.size(); i++) { QHBoxLayout *bus_layout = new QHBoxLayout; diff --git a/tools/cabana/streams/pandastream.h b/tools/cabana/streams/pandastream.h index e17ad887fc..f8847f65e5 100644 --- a/tools/cabana/streams/pandastream.h +++ b/tools/cabana/streams/pandastream.h @@ -19,7 +19,7 @@ struct BusConfig { }; struct PandaStreamConfig { - QString serial = ""; + std::string serial = ""; std::vector bus_config; }; @@ -28,8 +28,8 @@ class PandaStream : public LiveStream { public: PandaStream(QObject *parent, PandaStreamConfig config_ = {}); ~PandaStream() { stop(); } - inline QString routeName() const override { - return QString("Panda: %1").arg(config.serial); + inline std::string routeName() const override { + return "Panda: " + config.serial; } protected: diff --git a/tools/cabana/streams/replaystream.cc b/tools/cabana/streams/replaystream.cc index b8cf1be299..f42bf2601c 100644 --- a/tools/cabana/streams/replaystream.cc +++ b/tools/cabana/streams/replaystream.cc @@ -46,9 +46,9 @@ void ReplayStream::mergeSegments() { } } -bool ReplayStream::loadRoute(const QString &route, const QString &data_dir, uint32_t replay_flags, bool auto_source) { - replay.reset(new Replay(route.toStdString(), {"can", "roadEncodeIdx", "driverEncodeIdx", "wideRoadEncodeIdx", "carParams"}, - {}, nullptr, replay_flags, data_dir.toStdString(), auto_source)); +bool ReplayStream::loadRoute(const std::string &route, const std::string &data_dir, uint32_t replay_flags, bool auto_source) { + replay.reset(new Replay(route, {"can", "roadEncodeIdx", "driverEncodeIdx", "wideRoadEncodeIdx", "carParams"}, + {}, nullptr, replay_flags, data_dir, auto_source)); replay->setSegmentCacheLimit(settings.max_cached_minutes); replay->installEventFilter([this](const Event *event) { return eventFilter(event); }); @@ -72,17 +72,17 @@ bool ReplayStream::loadRoute(const QString &route, const QString &data_dir, uint "This will grant access to routes from your comma account."; } else { message = tr("Access Denied. You do not have permission to access route:\n\n%1\n\n" - "This is likely a private route.").arg(route); + "This is likely a private route.").arg(QString::fromStdString(route)); } QMessageBox::warning(nullptr, tr("Access Denied"), message); } else if (replay->lastRouteError() == RouteLoadError::NetworkError) { QMessageBox::warning(nullptr, tr("Network Error"), - tr("Unable to load the route:\n\n %1.\n\nPlease check your network connection and try again.").arg(route)); + tr("Unable to load the route:\n\n %1.\n\nPlease check your network connection and try again.").arg(QString::fromStdString(route))); } else if (replay->lastRouteError() == RouteLoadError::FileNotFound) { QMessageBox::warning(nullptr, tr("Route Not Found"), - tr("The specified route could not be found:\n\n %1.\n\nPlease check the route name and try again.").arg(route)); + tr("The specified route could not be found:\n\n %1.\n\nPlease check the route name and try again.").arg(QString::fromStdString(route))); } else { - QMessageBox::warning(nullptr, tr("Route Load Failed"), tr("Failed to load route: '%1'").arg(route)); + QMessageBox::warning(nullptr, tr("Route Load Failed"), tr("Failed to load route: '%1'").arg(QString::fromStdString(route))); } } return success; @@ -168,7 +168,7 @@ AbstractStream *OpenReplayWidget::open() { if (cameras[2]->isChecked()) flags |= REPLAY_FLAG_ECAM; if (flags == REPLAY_FLAG_NONE && !cameras[0]->isChecked()) flags = REPLAY_FLAG_NO_VIPC; - if (replay_stream->loadRoute(route, data_dir, flags)) { + if (replay_stream->loadRoute(route.toStdString(), data_dir.toStdString(), flags)) { return replay_stream.release(); } } diff --git a/tools/cabana/streams/replaystream.h b/tools/cabana/streams/replaystream.h index d429ed1f95..40f8ec8cfb 100644 --- a/tools/cabana/streams/replaystream.h +++ b/tools/cabana/streams/replaystream.h @@ -18,12 +18,12 @@ class ReplayStream : public AbstractStream { public: ReplayStream(QObject *parent); void start() override { replay->start(); } - bool loadRoute(const QString &route, const QString &data_dir, uint32_t replay_flags = REPLAY_FLAG_NONE, bool auto_source = false); + bool loadRoute(const std::string &route, const std::string &data_dir, uint32_t replay_flags = REPLAY_FLAG_NONE, bool auto_source = false); bool eventFilter(const Event *event); void seekTo(double ts) override { replay->seekTo(std::max(double(0), ts), false); } bool liveStreaming() const override { return false; } - inline QString routeName() const override { return QString::fromStdString(replay->route().name()); } - inline QString carFingerprint() const override { return replay->carFingerprint().c_str(); } + inline std::string routeName() const override { return replay->route().name(); } + inline std::string carFingerprint() const override { return replay->carFingerprint(); } double minSeconds() const override { return replay->minSeconds(); } double maxSeconds() const { return replay->maxSeconds(); } inline QDateTime beginDateTime() const { return QDateTime::fromSecsSinceEpoch(replay->routeDateTime()); } diff --git a/tools/cabana/streams/socketcanstream.cc b/tools/cabana/streams/socketcanstream.cc index e4801df9c0..4f558ba049 100644 --- a/tools/cabana/streams/socketcanstream.cc +++ b/tools/cabana/streams/socketcanstream.cc @@ -12,7 +12,7 @@ SocketCanStream::SocketCanStream(QObject *parent, SocketCanStreamConfig config_) throw std::runtime_error("SocketCAN plugin not available"); } - qDebug() << "Connecting to SocketCAN device" << config.device; + qDebug() << "Connecting to SocketCAN device" << config.device.c_str(); if (!connect()) { throw std::runtime_error("Failed to connect to SocketCAN device"); } @@ -26,7 +26,7 @@ bool SocketCanStream::connect() { // Connecting might generate some warnings about missing socketcan/libsocketcan libraries // These are expected and can be ignored, we don't need the advanced features of libsocketcan QString errorString; - device.reset(QCanBus::instance()->createDevice("socketcan", config.device, &errorString)); + device.reset(QCanBus::instance()->createDevice("socketcan", QString::fromStdString(config.device), &errorString)); device->setConfigurationParameter(QCanBusDevice::CanFdKey, true); if (!device) { @@ -87,7 +87,7 @@ OpenSocketCanWidget::OpenSocketCanWidget(QWidget *parent) : AbstractOpenStreamWi main_layout->addStretch(1); QObject::connect(refresh, &QPushButton::clicked, this, &OpenSocketCanWidget::refreshDevices); - QObject::connect(device_edit, &QComboBox::currentTextChanged, this, [=]{ config.device = device_edit->currentText(); }); + QObject::connect(device_edit, &QComboBox::currentTextChanged, this, [=]{ config.device = device_edit->currentText().toStdString(); }); // Populate devices refreshDevices(); diff --git a/tools/cabana/streams/socketcanstream.h b/tools/cabana/streams/socketcanstream.h index 8083b687e9..dc3cf7928a 100644 --- a/tools/cabana/streams/socketcanstream.h +++ b/tools/cabana/streams/socketcanstream.h @@ -10,7 +10,7 @@ #include "tools/cabana/streams/livestream.h" struct SocketCanStreamConfig { - QString device = ""; // TODO: support multiple devices/buses at once + std::string device = ""; // TODO: support multiple devices/buses at once }; class SocketCanStream : public LiveStream { @@ -20,8 +20,8 @@ public: ~SocketCanStream() { stop(); } static bool available(); - inline QString routeName() const override { - return QString("Live Streaming From Socket CAN %1").arg(config.device); + inline std::string routeName() const override { + return "Live Streaming From Socket CAN " + config.device; } protected: diff --git a/tools/cabana/tests/test_cabana.cc b/tools/cabana/tests/test_cabana.cc index d9fcae6f21..833cfbe4b5 100644 --- a/tools/cabana/tests/test_cabana.cc +++ b/tools/cabana/tests/test_cabana.cc @@ -8,7 +8,7 @@ const std::string TEST_RLOG_URL = "https://commadataci.blob.core.windows.net/openpilotci/0c94aa1e1296d7c6/2021-05-05--19-48-37/0/rlog.bz2"; TEST_CASE("DBCFile::generateDBC") { - QString fn = QString("%1/%2.dbc").arg(OPENDBC_FILE_PATH, "tesla_can"); + std::string fn = std::string(OPENDBC_FILE_PATH) + "/tesla_can.dbc"; DBCFile dbc_origin(fn); DBCFile dbc_from_generated("", dbc_origin.generateDBC()); @@ -30,7 +30,7 @@ TEST_CASE("DBCFile::generateDBC") { TEST_CASE("DBCFile::generateDBC - comment order") { // Ensure that message comments are followed by signal comments and in the correct order - auto content = R"(BO_ 160 message_1: 8 EON + std::string content = R"(BO_ 160 message_1: 8 EON SG_ signal_1 : 0|12@1+ (1,0) [0|4095] "unit" XXX BO_ 162 message_2: 8 EON @@ -46,7 +46,7 @@ CM_ SG_ 162 signal_2 "signal comment"; } TEST_CASE("DBCFile::generateDBC -- preserve original header") { - QString content = R"(VERSION "1.0" + std::string content = R"(VERSION "1.0" NS_ : CM_ @@ -66,7 +66,7 @@ CM_ SG_ 160 signal_1 "signal comment"; } TEST_CASE("DBCFile::generateDBC - escaped quotes") { - QString content = R"(BO_ 160 message_1: 8 EON + std::string content = R"(BO_ 160 message_1: 8 EON SG_ signal_1 : 0|12@1+ (1,0) [0|4095] "unit" XXX CM_ BO_ 160 "message comment with \"escaped quotes\""; @@ -77,7 +77,7 @@ CM_ SG_ 160 signal_1 "signal comment with \"escaped quotes\""; } TEST_CASE("parse_dbc") { - QString content = R"( + std::string content = R"( BO_ 160 message_1: 8 EON SG_ signal_1 : 0|12@1+ (1,0) [0|4095] "unit" XXX SG_ signal_2 : 12|1@1+ (1.0,0.0) [0.0|1] "" XXX @@ -119,9 +119,9 @@ CM_ SG_ 162 signal_1 "signal comment with \"escaped quotes\""; REQUIRE(sig_1->comment == "signal comment"); REQUIRE(sig_1->receiver_name == "XXX"); REQUIRE(sig_1->val_desc.size() == 3); - REQUIRE(sig_1->val_desc[0] == std::pair{0, "disabled"}); - REQUIRE(sig_1->val_desc[1] == std::pair{1.2, "initializing"}); - REQUIRE(sig_1->val_desc[2] == std::pair{2, "fault"}); + REQUIRE(sig_1->val_desc[0] == std::pair{0, "disabled"}); + REQUIRE(sig_1->val_desc[1] == std::pair{1.2, "initializing"}); + REQUIRE(sig_1->val_desc[2] == std::pair{2, "fault"}); auto &sig_2 = msg->sigs[1]; REQUIRE(sig_2->comment == "multiple line comment \n1\n2"); @@ -147,7 +147,7 @@ TEST_CASE("parse_opendbc") { QStringList errors; for (auto fn : dir.entryList({"*.dbc"}, QDir::Files, QDir::Name)) { try { - auto dbc = DBCFile(dir.filePath(fn)); + auto dbc = DBCFile(dir.filePath(fn).toStdString()); } catch (std::exception &e) { errors.push_back(e.what()); } diff --git a/tools/cabana/tools/findsignal.cc b/tools/cabana/tools/findsignal.cc index ec56fcaac0..41da8fc4bc 100644 --- a/tools/cabana/tools/findsignal.cc +++ b/tools/cabana/tools/findsignal.cc @@ -20,7 +20,7 @@ QVariant FindSignalModel::data(const QModelIndex &index, int role) const { if (role == Qt::DisplayRole) { const auto &s = filtered_signals[index.row()]; switch (index.column()) { - case 0: return s.id.toString(); + case 0: return QString::fromStdString(s.id.toString()); case 1: return QString("%1, %2").arg(s.sig.start_bit).arg(s.sig.size); case 2: return s.values.join(" "); } diff --git a/tools/cabana/tools/findsimilarbits.cc b/tools/cabana/tools/findsimilarbits.cc index c3c659791a..7cc7b5004f 100644 --- a/tools/cabana/tools/findsimilarbits.cc +++ b/tools/cabana/tools/findsimilarbits.cc @@ -31,7 +31,7 @@ FindSimilarBitsDlg::FindSimilarBitsDlg(QWidget *parent) : QDialog(parent, Qt::Wi msg_cb = new QComboBox(this); // TODO: update when src_bus_combo changes for (auto &[address, msg] : dbc()->getMessages(-1)) { - msg_cb->addItem(msg.name, address); + msg_cb->addItem(QString::fromStdString(msg.name), address); } msg_cb->model()->sort(0); msg_cb->setCurrentIndex(0); diff --git a/tools/cabana/utils/export.cc b/tools/cabana/utils/export.cc index 79ca97ba8f..a7f910193f 100644 --- a/tools/cabana/utils/export.cc +++ b/tools/cabana/utils/export.cc @@ -26,7 +26,7 @@ void exportSignalsToCSV(const QString &file_name, const MessageId &msg_id) { QTextStream stream(&file); stream << "time,addr,bus"; for (auto s : msg->sigs) - stream << "," << s->name; + stream << "," << s->name.c_str(); stream << "\n"; for (auto e : can->events(msg_id)) { diff --git a/tools/cabana/utils/util.cc b/tools/cabana/utils/util.cc index 61e4ee0514..0aee2aeabe 100644 --- a/tools/cabana/utils/util.cc +++ b/tools/cabana/utils/util.cc @@ -278,7 +278,7 @@ QString signalToolTip(const cabana::Signal *sig) { Start Bit: %2 Size: %3
MSB: %4 LSB: %5
Little Endian: %6 Signed: %7 - )").arg(sig->name).arg(sig->start_bit).arg(sig->size).arg(sig->msb).arg(sig->lsb) + )").arg(QString::fromStdString(sig->name)).arg(sig->start_bit).arg(sig->size).arg(sig->msb).arg(sig->lsb) .arg(sig->is_little_endian ? "Y" : "N").arg(sig->is_signed ? "Y" : "N"); } diff --git a/tools/cabana/videowidget.cc b/tools/cabana/videowidget.cc index a05bf24b4a..850999640b 100644 --- a/tools/cabana/videowidget.cc +++ b/tools/cabana/videowidget.cc @@ -384,9 +384,9 @@ QPixmap StreamCameraView::generateThumbnail(QPixmap thumb, double seconds) { void StreamCameraView::drawScrubThumbnail(QPainter &p) { p.fillRect(rect(), Qt::black); - auto it = big_thumbnails.lowerBound(can->toMonoTime(thumbnail_dispaly_time)); + auto it = big_thumbnails.lower_bound(can->toMonoTime(thumbnail_dispaly_time)); if (it != big_thumbnails.end()) { - QPixmap scaled_thumb = it.value().scaled(rect().size(), Qt::KeepAspectRatio, Qt::SmoothTransformation); + QPixmap scaled_thumb = it->second.scaled(rect().size(), Qt::KeepAspectRatio, Qt::SmoothTransformation); QRect thumb_rect(rect().center() - scaled_thumb.rect().center(), scaled_thumb.size()); p.drawPixmap(thumb_rect.topLeft(), scaled_thumb); drawTime(p, thumb_rect, thumbnail_dispaly_time); @@ -394,9 +394,9 @@ void StreamCameraView::drawScrubThumbnail(QPainter &p) { } void StreamCameraView::drawThumbnail(QPainter &p) { - auto it = thumbnails.lowerBound(can->toMonoTime(thumbnail_dispaly_time)); + auto it = thumbnails.lower_bound(can->toMonoTime(thumbnail_dispaly_time)); if (it != thumbnails.end()) { - const QPixmap &thumb = it.value(); + const QPixmap &thumb = it->second; auto [min_sec, max_sec] = can->timeRange().value_or(std::make_pair(can->minSeconds(), can->maxSeconds())); int pos = (thumbnail_dispaly_time - min_sec) * width() / (max_sec - min_sec); int x = std::clamp(pos - thumb.width() / 2, THUMBNAIL_MARGIN, width() - thumb.width() - THUMBNAIL_MARGIN + 1); diff --git a/tools/cabana/videowidget.h b/tools/cabana/videowidget.h index 6da0023123..e52e92ebd1 100644 --- a/tools/cabana/videowidget.h +++ b/tools/cabana/videowidget.h @@ -1,5 +1,6 @@ #pragma once +#include #include #include #include @@ -47,8 +48,8 @@ private: void drawTime(QPainter &p, const QRect &rect, double seconds); QPropertyAnimation *fade_animation; - QMap big_thumbnails; - QMap thumbnails; + std::map big_thumbnails; + std::map thumbnails; double thumbnail_dispaly_time = -1; friend class VideoWidget; }; From 0c7abf38557990706a8a0dc57a95da73338bda79 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sun, 1 Mar 2026 15:55:57 -0800 Subject: [PATCH 005/141] cabana: remove QtXml (#37521) --- tools/cabana/SConscript | 2 +- tools/cabana/utils/util.cc | 52 +++++++++++++++++++++++--------------- 2 files changed, 33 insertions(+), 21 deletions(-) diff --git a/tools/cabana/SConscript b/tools/cabana/SConscript index a56ddff7a6..d3de2dcb72 100644 --- a/tools/cabana/SConscript +++ b/tools/cabana/SConscript @@ -20,7 +20,7 @@ SConscript(['#tools/replay/SConscript']) Import('replay_lib') qt_env = env.Clone() -qt_modules = ["Widgets", "Gui", "Core", "Concurrent", "Xml"] +qt_modules = ["Widgets", "Gui", "Core", "Concurrent"] qt_libs = [] if arch == "Darwin": diff --git a/tools/cabana/utils/util.cc b/tools/cabana/utils/util.cc index 0aee2aeabe..50ab764423 100644 --- a/tools/cabana/utils/util.cc +++ b/tools/cabana/utils/util.cc @@ -17,8 +17,7 @@ #include #include #include -#include -#include +#include #include "common/util.h" // SegmentTree @@ -325,36 +324,49 @@ void initApp(int argc, char *argv[], bool disable_hidpi) { setSurfaceFormat(); } -static QHash load_bootstrap_icons() { - QHash icons; +static std::unordered_map load_bootstrap_icons() { + std::unordered_map icons; QFile f(":/bootstrap-icons.svg"); if (f.open(QIODevice::ReadOnly | QIODevice::Text)) { - QDomDocument xml; - xml.setContent(&f); - QDomNode n = xml.documentElement().firstChild(); - while (!n.isNull()) { - QDomElement e = n.toElement(); - if (!e.isNull() && e.hasAttribute("id")) { - QString svg_str; - QTextStream stream(&svg_str); - n.save(stream, 0); - svg_str.replace("", ""); - icons[e.attribute("id")] = svg_str.toUtf8(); + std::string content = f.readAll().toStdString(); + const std::string sym_open = " with + svg_str.replace(0, 7, " ""); // "" (9) -> "" (6) + icons[id] = std::move(svg_str); + } } - n = n.nextSibling(); + pos = end; } } return icons; } QPixmap bootstrapPixmap(const QString &id) { - static QHash icons = load_bootstrap_icons(); + static auto icons = load_bootstrap_icons(); QPixmap pixmap; - if (auto it = icons.find(id); it != icons.end()) { - pixmap.loadFromData(it.value(), "svg"); + auto it = icons.find(id.toStdString()); + if (it != icons.end()) { + pixmap.loadFromData((const uchar *)it->second.data(), it->second.size(), "svg"); } return pixmap; } From ce04d25f7ddbaa8a4319e33bed30b298a9fc5910 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sun, 1 Mar 2026 16:00:29 -0800 Subject: [PATCH 006/141] cabana: remove QtConcurrent (#37522) --- tools/cabana/SConscript | 2 +- tools/cabana/chart/chartswidget.cc | 9 +++--- tools/cabana/signalview.cc | 10 +++---- tools/cabana/streams/routes.cc | 10 +++---- tools/cabana/tools/findsignal.cc | 46 +++++++++++++++++++----------- tools/cabana/videowidget.cc | 38 +++++++++++++++--------- 6 files changed, 71 insertions(+), 44 deletions(-) diff --git a/tools/cabana/SConscript b/tools/cabana/SConscript index d3de2dcb72..5747d6e621 100644 --- a/tools/cabana/SConscript +++ b/tools/cabana/SConscript @@ -20,7 +20,7 @@ SConscript(['#tools/replay/SConscript']) Import('replay_lib') qt_env = env.Clone() -qt_modules = ["Widgets", "Gui", "Core", "Concurrent"] +qt_modules = ["Widgets", "Gui", "Core"] qt_libs = [] if arch == "Darwin": diff --git a/tools/cabana/chart/chartswidget.cc b/tools/cabana/chart/chartswidget.cc index 4e44a560b3..efae1dded0 100644 --- a/tools/cabana/chart/chartswidget.cc +++ b/tools/cabana/chart/chartswidget.cc @@ -1,13 +1,13 @@ #include "tools/cabana/chart/chartswidget.h" #include +#include #include -#include #include +#include #include #include -#include #include "tools/cabana/chart/chart.h" @@ -171,10 +171,11 @@ void ChartsWidget::updateTabBar() { } void ChartsWidget::eventsMerged(const MessageEventsMap &new_events) { - QFutureSynchronizer future_synchronizer; + std::vector> futures; for (auto c : charts) { - future_synchronizer.addFuture(QtConcurrent::run(c, &ChartView::updateSeries, nullptr, &new_events)); + futures.push_back(std::async(std::launch::async, &ChartView::updateSeries, c, nullptr, &new_events)); } + for (auto &f : futures) f.get(); } void ChartsWidget::timeRangeChanged(const std::optional> &time_range) { diff --git a/tools/cabana/signalview.cc b/tools/cabana/signalview.cc index df05d287f4..15baabe512 100644 --- a/tools/cabana/signalview.cc +++ b/tools/cabana/signalview.cc @@ -1,6 +1,7 @@ #include "tools/cabana/signalview.h" #include +#include #include #include @@ -11,7 +12,6 @@ #include #include #include -#include #include #include "tools/cabana/commands.h" @@ -641,13 +641,13 @@ void SignalView::updateState(const std::set *msgs) { delegate->button_size.height() - style()->pixelMetric(QStyle::PM_FocusFrameVMargin) * 2); auto [first, last] = can->eventsInRange(model->msg_id, std::make_pair(last_msg.ts -settings.sparkline_range, last_msg.ts)); - QFutureSynchronizer synchronizer; + std::vector> futures; for (int i = first_visible.row(); i <= last_visible.row(); ++i) { auto item = model->getItem(model->index(i, 1)); - synchronizer.addFuture(QtConcurrent::run( - &item->sparkline, &Sparkline::update, item->sig, first, last, settings.sparkline_range, size)); + futures.push_back(std::async(std::launch::async, + &Sparkline::update, &item->sparkline, item->sig, first, last, settings.sparkline_range, size)); } - synchronizer.waitForFinished(); + for (auto &f : futures) f.get(); } for (int i = 0; i < model->rowCount(); ++i) { diff --git a/tools/cabana/streams/routes.cc b/tools/cabana/streams/routes.cc index 8539a00b5b..07d8d1a0b1 100644 --- a/tools/cabana/streams/routes.cc +++ b/tools/cabana/streams/routes.cc @@ -11,7 +11,7 @@ #include #include #include -#include +#include #include "tools/replay/py_downloader.h" @@ -72,13 +72,13 @@ RoutesDialog::RoutesDialog(QWidget *parent) : QDialog(parent) { // Fetch devices QPointer self = this; - QtConcurrent::run([self]() { + std::thread([self]() { std::string result = PyDownloader::getDevices(); auto [success, error_code] = checkApiResponse(result); QMetaObject::invokeMethod(qApp, [self, r = QString::fromStdString(result), success, error_code]() { if (self) self->parseDeviceList(r, success, error_code); }, Qt::QueuedConnection); - }); + }).detach(); } void RoutesDialog::parseDeviceList(const QString &json, bool success, int error_code) { @@ -114,14 +114,14 @@ void RoutesDialog::fetchRoutes() { int request_id = ++fetch_id_; QPointer self = this; - QtConcurrent::run([self, did, start_ms, end_ms, preserved, request_id]() { + std::thread([self, did, start_ms, end_ms, preserved, request_id]() { std::string result = PyDownloader::getDeviceRoutes(did, start_ms, end_ms, preserved); if (!self || self->fetch_id_ != request_id) return; auto [success, error_code] = checkApiResponse(result); QMetaObject::invokeMethod(qApp, [self, r = QString::fromStdString(result), success, error_code, request_id]() { if (self && self->fetch_id_ == request_id) self->parseRouteList(r, success, error_code); }, Qt::QueuedConnection); - }); + }).detach(); } void RoutesDialog::parseRouteList(const QString &json, bool success, int error_code) { diff --git a/tools/cabana/tools/findsignal.cc b/tools/cabana/tools/findsignal.cc index 41da8fc4bc..3dc28bb159 100644 --- a/tools/cabana/tools/findsignal.cc +++ b/tools/cabana/tools/findsignal.cc @@ -1,10 +1,11 @@ #include "tools/cabana/tools/findsignal.h" +#include + #include #include #include #include -#include #include #include @@ -35,22 +36,35 @@ void FindSignalModel::search(std::function cmp) { const auto prev_sigs = !histories.isEmpty() ? histories.back() : initial_signals; filtered_signals.clear(); filtered_signals.reserve(prev_sigs.size()); - QtConcurrent::blockingMap(prev_sigs, [&](auto &s) { - const auto &events = can->events(s.id); - auto first = std::upper_bound(events.cbegin(), events.cend(), s.mono_time, CompareCanEvent()); - auto last = events.cend(); - if (last_time < std::numeric_limits::max()) { - last = std::upper_bound(events.cbegin(), events.cend(), last_time, CompareCanEvent()); - } - auto it = std::find_if(first, last, [&](const CanEvent *e) { return cmp(get_raw_value(e->dat, e->size, s.sig)); }); - if (it != last) { - auto values = s.values; - values += QString("(%1, %2)").arg(can->toSeconds((*it)->mono_time), 0, 'f', 3).arg(get_raw_value((*it)->dat, (*it)->size, s.sig)); - std::lock_guard lk(lock); - filtered_signals.push_back({.id = s.id, .mono_time = (*it)->mono_time, .sig = s.sig, .values = values}); - } - }); + unsigned int num_threads = std::max(1u, std::thread::hardware_concurrency()); + size_t chunk = (prev_sigs.size() + num_threads - 1) / num_threads; + std::vector threads; + for (unsigned int t = 0; t < num_threads && t * chunk < (size_t)prev_sigs.size(); ++t) { + size_t start = t * chunk; + size_t end = std::min(start + chunk, (size_t)prev_sigs.size()); + threads.emplace_back([&, start, end]() { + for (size_t i = start; i < end; ++i) { + const auto &s = prev_sigs[i]; + const auto &events = can->events(s.id); + auto first = std::upper_bound(events.cbegin(), events.cend(), s.mono_time, CompareCanEvent()); + auto last = events.cend(); + if (last_time < std::numeric_limits::max()) { + last = std::upper_bound(events.cbegin(), events.cend(), last_time, CompareCanEvent()); + } + + auto it = std::find_if(first, last, [&](const CanEvent *e) { return cmp(get_raw_value(e->dat, e->size, s.sig)); }); + if (it != last) { + auto values = s.values; + values += QString("(%1, %2)").arg(can->toSeconds((*it)->mono_time), 0, 'f', 3).arg(get_raw_value((*it)->dat, (*it)->size, s.sig)); + std::lock_guard lk(lock); + filtered_signals.push_back({.id = s.id, .mono_time = (*it)->mono_time, .sig = s.sig, .values = values}); + } + } + }); + } + for (auto &th : threads) th.join(); + histories.push_back(filtered_signals); endResetModel(); diff --git a/tools/cabana/videowidget.cc b/tools/cabana/videowidget.cc index 850999640b..f203ec663e 100644 --- a/tools/cabana/videowidget.cc +++ b/tools/cabana/videowidget.cc @@ -1,6 +1,7 @@ #include "tools/cabana/videowidget.h" #include +#include #include #include @@ -9,7 +10,6 @@ #include #include #include -#include #include "tools/cabana/tools/routeinfo.h" @@ -334,19 +334,31 @@ StreamCameraView::StreamCameraView(std::string stream_name, VisionStreamType str void StreamCameraView::parseQLog(std::shared_ptr qlog) { std::mutex mutex; - QtConcurrent::blockingMap(qlog->events.cbegin(), qlog->events.cend(), [this, &mutex](const Event &e) { - if (e.which == cereal::Event::Which::THUMBNAIL) { - capnp::FlatArrayMessageReader reader(e.data); - auto thumb_data = reader.getRoot().getThumbnail(); - auto image_data = thumb_data.getThumbnail(); - if (QPixmap thumb; thumb.loadFromData(image_data.begin(), image_data.size(), "jpeg")) { - QPixmap generated_thumb = generateThumbnail(thumb, can->toSeconds(thumb_data.getTimestampEof())); - std::lock_guard lock(mutex); - thumbnails[thumb_data.getTimestampEof()] = generated_thumb; - big_thumbnails[thumb_data.getTimestampEof()] = thumb; + const auto &events = qlog->events; + unsigned int num_threads = std::max(1u, std::thread::hardware_concurrency()); + size_t chunk = (events.size() + num_threads - 1) / num_threads; + std::vector threads; + for (unsigned int t = 0; t < num_threads && t * chunk < events.size(); ++t) { + size_t start = t * chunk; + size_t end = std::min(start + chunk, events.size()); + threads.emplace_back([this, &mutex, &events, start, end]() { + for (size_t i = start; i < end; ++i) { + const Event &e = events[i]; + if (e.which == cereal::Event::Which::THUMBNAIL) { + capnp::FlatArrayMessageReader reader(e.data); + auto thumb_data = reader.getRoot().getThumbnail(); + auto image_data = thumb_data.getThumbnail(); + if (QPixmap thumb; thumb.loadFromData(image_data.begin(), image_data.size(), "jpeg")) { + QPixmap generated_thumb = generateThumbnail(thumb, can->toSeconds(thumb_data.getTimestampEof())); + std::lock_guard lock(mutex); + thumbnails[thumb_data.getTimestampEof()] = generated_thumb; + big_thumbnails[thumb_data.getTimestampEof()] = thumb; + } + } } - } - }); + }); + } + for (auto &th : threads) th.join(); update(); } From 3478ac13387ccfcc3c04c197f8ac4d43c4325c24 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sun, 1 Mar 2026 16:12:04 -0800 Subject: [PATCH 007/141] cabana: remove QtSerialBus (#37523) --- tools/cabana/SConscript | 2 - tools/cabana/binaryview.cc | 19 +++-- tools/cabana/binaryview.h | 7 +- tools/cabana/chart/chartswidget.cc | 29 ++++---- tools/cabana/chart/chartswidget.h | 6 +- tools/cabana/chart/signalselector.cc | 4 +- tools/cabana/chart/signalselector.h | 2 +- tools/cabana/streams/socketcanstream.cc | 98 +++++++++++++++++-------- tools/cabana/streams/socketcanstream.h | 9 +-- tools/cabana/tools/findsignal.cc | 18 ++--- tools/cabana/tools/findsignal.h | 10 ++- tools/cabana/tools/findsimilarbits.cc | 19 ++--- tools/cabana/tools/findsimilarbits.h | 4 +- 13 files changed, 133 insertions(+), 94 deletions(-) diff --git a/tools/cabana/SConscript b/tools/cabana/SConscript index 5747d6e621..098a6351b7 100644 --- a/tools/cabana/SConscript +++ b/tools/cabana/SConscript @@ -68,10 +68,8 @@ base_libs = [common, messaging, cereal, visionipc, 'm', 'ssl', 'crypto', 'pthrea if arch == "Darwin": base_frameworks.append('QtCharts') - base_frameworks.append('QtSerialBus') else: base_libs.append('Qt5Charts') - base_libs.append('Qt5SerialBus') qt_libs = base_libs diff --git a/tools/cabana/binaryview.cc b/tools/cabana/binaryview.cc index 90c7c1c341..0be28f06b7 100644 --- a/tools/cabana/binaryview.cc +++ b/tools/cabana/binaryview.cc @@ -111,7 +111,8 @@ void BinaryView::highlight(const cabana::Signal *sig) { if (sig != hovered_sig) { for (int i = 0; i < model->items.size(); ++i) { auto &item_sigs = model->items[i].sigs; - if ((sig && item_sigs.contains(sig)) || (hovered_sig && item_sigs.contains(hovered_sig))) { + auto has = [](const auto &v, auto p) { return std::find(v.begin(), v.end(), p) != v.end(); }; + if ((sig && has(item_sigs, sig)) || (hovered_sig && has(item_sigs, hovered_sig))) { auto index = model->index(i / model->columnCount(), i % model->columnCount()); emit model->dataChanged(index, index, {Qt::DisplayRole}); } @@ -157,7 +158,7 @@ void BinaryView::mousePressEvent(QMouseEvent *event) { void BinaryView::highlightPosition(const QPoint &pos) { if (auto index = indexAt(viewport()->mapFromGlobal(pos)); index.isValid()) { auto item = (BinaryViewModel::Item *)index.internalPointer(); - const cabana::Signal *sig = item->sigs.isEmpty() ? nullptr : item->sigs.back(); + const cabana::Signal *sig = item->sigs.empty() ? nullptr : item->sigs.back(); highlight(sig); } } @@ -208,12 +209,12 @@ void BinaryView::refresh() { highlightPosition(QCursor::pos()); } -QSet BinaryView::getOverlappingSignals() const { - QSet overlapping; +std::set BinaryView::getOverlappingSignals() const { + std::set overlapping; for (const auto &item : model->items) { if (item.sigs.size() > 1) { for (auto s : item.sigs) { - if (s->type == cabana::Signal::Type::Normal) overlapping += s; + if (s->type == cabana::Signal::Type::Normal) overlapping.insert(s); } } } @@ -404,7 +405,9 @@ bool BinaryItemDelegate::hasSignal(const QModelIndex &index, int dx, int dy, con if (!index.isValid()) return false; auto model = (const BinaryViewModel*)(index.model()); int idx = (index.row() + dy) * model->columnCount() + index.column() + dx; - return (idx >=0 && idx < model->items.size()) ? model->items[idx].sigs.contains(sig) : false; + if (idx < 0 || idx >= (int)model->items.size()) return false; + auto &s = model->items[idx].sigs; + return std::find(s.begin(), s.end(), sig) != s.end(); } void BinaryItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { @@ -421,7 +424,7 @@ void BinaryItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &op auto color = bin_view->resize_sig ? bin_view->resize_sig->color : option.palette.color(QPalette::Active, QPalette::Highlight); painter->fillRect(option.rect, color); painter->setPen(option.palette.color(QPalette::BrightText)); - } else if (!bin_view->selectionModel()->hasSelection() || !item->sigs.contains(bin_view->resize_sig)) { // not resizing + } else if (!bin_view->selectionModel()->hasSelection() || std::find(item->sigs.begin(), item->sigs.end(), bin_view->resize_sig) == item->sigs.end()) { // not resizing if (item->sigs.size() > 0) { for (auto &s : item->sigs) { if (s == bin_view->hovered_sig) { @@ -433,7 +436,7 @@ void BinaryItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &op } else if (item->valid && item->bg_color.alpha() > 0) { painter->fillRect(option.rect, item->bg_color); } - auto color_role = item->sigs.contains(bin_view->hovered_sig) ? QPalette::BrightText : QPalette::Text; + auto color_role = (std::find(item->sigs.begin(), item->sigs.end(), bin_view->hovered_sig) != item->sigs.end()) ? QPalette::BrightText : QPalette::Text; painter->setPen(option.palette.color(bin_view->is_message_active ? QPalette::Normal : QPalette::Disabled, color_role)); } diff --git a/tools/cabana/binaryview.h b/tools/cabana/binaryview.h index 920deb0018..e568228b37 100644 --- a/tools/cabana/binaryview.h +++ b/tools/cabana/binaryview.h @@ -1,10 +1,9 @@ #pragma once +#include #include #include -#include -#include #include #include @@ -51,7 +50,7 @@ public: bool is_msb = false; bool is_lsb = false; uint8_t val; - QList sigs; + std::vector sigs; bool valid = false; }; std::vector items; @@ -68,7 +67,7 @@ public: BinaryView(QWidget *parent = nullptr); void setMessage(const MessageId &message_id); void highlight(const cabana::Signal *sig); - QSet getOverlappingSignals() const; + std::set getOverlappingSignals() const; void updateState() { model->updateState(); } void paintEvent(QPaintEvent *event) override { is_message_active = can->isMessageActive(model->msg_id); diff --git a/tools/cabana/chart/chartswidget.cc b/tools/cabana/chart/chartswidget.cc index efae1dded0..acdb6d064d 100644 --- a/tools/cabana/chart/chartswidget.cc +++ b/tools/cabana/chart/chartswidget.cc @@ -166,7 +166,7 @@ void ChartsWidget::removeTab(int index) { void ChartsWidget::updateTabBar() { for (int i = 0; i < tabbar->count(); ++i) { const auto &charts_in_tab = tab_charts[tabbar->tabData(i).toInt()]; - tabbar->setTabText(i, QString("Tab %1 (%2)").arg(i + 1).arg(charts_in_tab.count())); + tabbar->setTabText(i, QString("Tab %1 (%2)").arg(i + 1).arg((int)charts_in_tab.size())); } } @@ -204,7 +204,7 @@ void ChartsWidget::showValueTip(double sec) { } void ChartsWidget::updateState() { - if (charts.isEmpty()) return; + if (charts.empty()) return; const auto &time_range = can->timeRange(); const double cur_sec = can->currentSec(); @@ -248,7 +248,7 @@ void ChartsWidget::updateToolBar() { redo_zoom_action->setVisible(is_zoomed); reset_zoom_action->setVisible(is_zoomed); reset_zoom_btn->setText(is_zoomed ? tr("%1-%2").arg(can->timeRange()->first, 0, 'f', 2).arg(can->timeRange()->second, 0, 'f', 2) : ""); - remove_all_btn->setEnabled(!charts.isEmpty()); + remove_all_btn->setEnabled(!charts.empty()); } void ChartsWidget::settingChanged() { @@ -282,9 +282,9 @@ ChartView *ChartsWidget::createChart(int pos) { chart->setMinimumWidth(CHART_MIN_WIDTH); chart->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed); QObject::connect(chart, &ChartView::axisYLabelWidthChanged, align_timer, qOverload<>(&QTimer::start)); - pos = std::clamp(pos, 0, charts.size()); - charts.insert(pos, chart); - currentCharts().insert(pos, chart); + pos = std::clamp(pos, 0, (int)charts.size()); + charts.insert(charts.begin() + pos, chart); + currentCharts().insert(currentCharts().begin() + pos, chart); updateLayout(true); updateToolBar(); return chart; @@ -303,7 +303,7 @@ void ChartsWidget::showChart(const MessageId &id, const cabana::Signal *sig, boo void ChartsWidget::splitChart(ChartView *src_chart) { if (src_chart->sigs.size() > 1) { - int pos = charts.indexOf(src_chart) + 1; + int pos = std::find(charts.begin(), charts.end(), src_chart) - charts.begin() + 1; for (auto it = src_chart->sigs.begin() + 1; it != src_chart->sigs.end(); /**/) { auto c = createChart(pos); src_chart->chart()->removeSeries(it->series); @@ -434,7 +434,7 @@ void ChartsWidget::newChart() { SignalSelector dlg(tr("New Chart"), this); if (dlg.exec() == QDialog::Accepted) { auto items = dlg.seletedItems(); - if (!items.isEmpty()) { + if (!items.empty()) { auto c = createChart(); for (auto it : items) { c->addSignal(it->msg_id, it->sig); @@ -444,10 +444,10 @@ void ChartsWidget::newChart() { } void ChartsWidget::removeChart(ChartView *chart) { - charts.removeOne(chart); + charts.erase(std::remove(charts.begin(), charts.end(), chart), charts.end()); chart->deleteLater(); for (auto &[_, list] : tab_charts) { - list.removeOne(chart); + list.erase(std::remove(list.begin(), list.end(), chart), list.end()); } updateToolBar(); updateLayout(true); @@ -461,7 +461,7 @@ void ChartsWidget::removeAll() { } tab_charts.clear(); - if (!charts.isEmpty()) { + if (!charts.empty()) { for (auto c : charts) { delete c; } @@ -561,10 +561,11 @@ void ChartsContainer::dropEvent(QDropEvent *event) { auto chart = qobject_cast(event->source()); if (w != chart) { for (auto &[_, list] : charts_widget->tab_charts) { - list.removeOne(chart); + list.erase(std::remove(list.begin(), list.end(), chart), list.end()); } - int to = w ? charts_widget->currentCharts().indexOf(w) + 1 : 0; - charts_widget->currentCharts().insert(to, chart); + auto &cur = charts_widget->currentCharts(); + int to = w ? std::find(cur.begin(), cur.end(), w) - cur.begin() + 1 : 0; + cur.insert(cur.begin() + to, chart); charts_widget->updateLayout(true); charts_widget->updateTabBar(); event->acceptProposedAction(); diff --git a/tools/cabana/chart/chartswidget.h b/tools/cabana/chart/chartswidget.h index f87b1276c5..ef3fbc471a 100644 --- a/tools/cabana/chart/chartswidget.h +++ b/tools/cabana/chart/chartswidget.h @@ -81,7 +81,7 @@ private: bool eventFilter(QObject *obj, QEvent *event) override; void newTab(); void removeTab(int index); - inline QList ¤tCharts() { return tab_charts[tabbar->tabData(tabbar->currentIndex()).toInt()]; } + inline std::vector ¤tCharts() { return tab_charts[tabbar->tabData(tabbar->currentIndex()).toInt()]; } ChartView *findChart(const MessageId &id, const cabana::Signal *sig); QLabel *title_label; @@ -100,8 +100,8 @@ private: QUndoStack *zoom_undo_stack; ToolButton *remove_all_btn; - QList charts; - std::unordered_map> tab_charts; + std::vector charts; + std::unordered_map> tab_charts; TabBar *tabbar; ChartsContainer *charts_container; QScrollArea *charts_scroll; diff --git a/tools/cabana/chart/signalselector.cc b/tools/cabana/chart/signalselector.cc index 168bf824df..6f2fd8de46 100644 --- a/tools/cabana/chart/signalselector.cc +++ b/tools/cabana/chart/signalselector.cc @@ -102,8 +102,8 @@ void SignalSelector::addItemToList(QListWidget *parent, const MessageId id, cons parent->setItemWidget(new_item, label); } -QList SignalSelector::seletedItems() { - QList ret; +std::vector SignalSelector::seletedItems() { + std::vector ret; for (int i = 0; i < selected_list->count(); ++i) ret.push_back((ListItem *)selected_list->item(i)); return ret; } diff --git a/tools/cabana/chart/signalselector.h b/tools/cabana/chart/signalselector.h index f46779f044..5b6e37e56a 100644 --- a/tools/cabana/chart/signalselector.h +++ b/tools/cabana/chart/signalselector.h @@ -15,7 +15,7 @@ public: }; SignalSelector(QString title, QWidget *parent); - QList seletedItems(); + std::vector seletedItems(); inline void addSelected(const MessageId &id, const cabana::Signal *sig) { addItemToList(selected_list, id, sig, true); } private: diff --git a/tools/cabana/streams/socketcanstream.cc b/tools/cabana/streams/socketcanstream.cc index 4f558ba049..768465d5a3 100644 --- a/tools/cabana/streams/socketcanstream.cc +++ b/tools/cabana/streams/socketcanstream.cc @@ -1,6 +1,14 @@ #include "tools/cabana/streams/socketcanstream.h" +#include +#include +#include +#include +#include +#include + #include +#include #include #include #include @@ -9,7 +17,7 @@ SocketCanStream::SocketCanStream(QObject *parent, SocketCanStreamConfig config_) : config(config_), LiveStream(parent) { if (!available()) { - throw std::runtime_error("SocketCAN plugin not available"); + throw std::runtime_error("SocketCAN not available"); } qDebug() << "Connecting to SocketCAN device" << config.device.c_str(); @@ -18,50 +26,73 @@ SocketCanStream::SocketCanStream(QObject *parent, SocketCanStreamConfig config_) } } +SocketCanStream::~SocketCanStream() { + stop(); + if (sock_fd >= 0) { + ::close(sock_fd); + sock_fd = -1; + } +} + bool SocketCanStream::available() { - return QCanBus::instance()->plugins().contains("socketcan"); + int fd = socket(PF_CAN, SOCK_RAW, CAN_RAW); + if (fd < 0) return false; + ::close(fd); + return true; } bool SocketCanStream::connect() { - // Connecting might generate some warnings about missing socketcan/libsocketcan libraries - // These are expected and can be ignored, we don't need the advanced features of libsocketcan - QString errorString; - device.reset(QCanBus::instance()->createDevice("socketcan", QString::fromStdString(config.device), &errorString)); - device->setConfigurationParameter(QCanBusDevice::CanFdKey, true); - - if (!device) { - qDebug() << "Failed to create SocketCAN device" << errorString; + sock_fd = socket(PF_CAN, SOCK_RAW, CAN_RAW); + if (sock_fd < 0) { + qDebug() << "Failed to create CAN socket"; return false; } - if (!device->connectDevice()) { - qDebug() << "Failed to connect to device"; + // Enable CAN-FD + int fd_enable = 1; + setsockopt(sock_fd, SOL_CAN_RAW, CAN_RAW_FD_FRAMES, &fd_enable, sizeof(fd_enable)); + + struct ifreq ifr = {}; + strncpy(ifr.ifr_name, config.device.c_str(), IFNAMSIZ - 1); + if (ioctl(sock_fd, SIOCGIFINDEX, &ifr) < 0) { + qDebug() << "Failed to get interface index for" << config.device.c_str(); + ::close(sock_fd); + sock_fd = -1; return false; } + struct sockaddr_can addr = {}; + addr.can_family = AF_CAN; + addr.can_ifindex = ifr.ifr_ifindex; + if (bind(sock_fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) { + qDebug() << "Failed to bind CAN socket"; + ::close(sock_fd); + sock_fd = -1; + return false; + } + + // Set read timeout so the thread can check for interruption + struct timeval tv = {.tv_sec = 0, .tv_usec = 100000}; // 100ms + setsockopt(sock_fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)); + return true; } void SocketCanStream::streamThread() { - while (!QThread::currentThread()->isInterruptionRequested()) { - QThread::msleep(1); + struct canfd_frame frame; - auto frames = device->readAllFrames(); - if (frames.size() == 0) continue; + while (!QThread::currentThread()->isInterruptionRequested()) { + ssize_t nbytes = read(sock_fd, &frame, sizeof(frame)); + if (nbytes <= 0) continue; + + uint8_t len = (nbytes == CAN_MTU) ? frame.len : frame.len; // works for both CAN and CAN-FD MessageBuilder msg; auto evt = msg.initEvent(); - auto canData = evt.initCan(frames.size()); - - for (uint i = 0; i < frames.size(); i++) { - if (!frames[i].isValid()) continue; - - canData[i].setAddress(frames[i].frameId()); - canData[i].setSrc(0); - - auto payload = frames[i].payload(); - canData[i].setDat(kj::arrayPtr((uint8_t*)payload.data(), payload.size())); - } + auto canData = evt.initCan(1); + canData[0].setAddress(frame.can_id & CAN_EFF_MASK); + canData[0].setSrc(0); + canData[0].setDat(kj::arrayPtr(frame.data, len)); handleEvent(capnp::messageToFlatArray(msg)); } @@ -95,12 +126,19 @@ OpenSocketCanWidget::OpenSocketCanWidget(QWidget *parent) : AbstractOpenStreamWi void OpenSocketCanWidget::refreshDevices() { device_edit->clear(); - for (auto device : QCanBus::instance()->availableDevices(QStringLiteral("socketcan"))) { - device_edit->addItem(device.name()); + // Scan /sys/class/net/ for CAN interfaces (type 280 = ARPHRD_CAN) + QDir net_dir("/sys/class/net"); + for (const auto &iface : net_dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot)) { + QFile type_file(net_dir.filePath(iface) + "/type"); + if (type_file.open(QIODevice::ReadOnly)) { + int type = type_file.readAll().trimmed().toInt(); + if (type == 280) { + device_edit->addItem(iface); + } + } } } - AbstractStream *OpenSocketCanWidget::open() { try { return new SocketCanStream(qApp, config); diff --git a/tools/cabana/streams/socketcanstream.h b/tools/cabana/streams/socketcanstream.h index dc3cf7928a..3c5cd184f7 100644 --- a/tools/cabana/streams/socketcanstream.h +++ b/tools/cabana/streams/socketcanstream.h @@ -1,10 +1,5 @@ #pragma once -#include - -#include -#include -#include #include #include "tools/cabana/streams/livestream.h" @@ -17,7 +12,7 @@ class SocketCanStream : public LiveStream { Q_OBJECT public: SocketCanStream(QObject *parent, SocketCanStreamConfig config_ = {}); - ~SocketCanStream() { stop(); } + ~SocketCanStream(); static bool available(); inline std::string routeName() const override { @@ -29,7 +24,7 @@ protected: bool connect(); SocketCanStreamConfig config = {}; - std::unique_ptr device; + int sock_fd = -1; }; class OpenSocketCanWidget : public AbstractOpenStreamWidget { diff --git a/tools/cabana/tools/findsignal.cc b/tools/cabana/tools/findsignal.cc index 3dc28bb159..b131893942 100644 --- a/tools/cabana/tools/findsignal.cc +++ b/tools/cabana/tools/findsignal.cc @@ -33,7 +33,7 @@ void FindSignalModel::search(std::function cmp) { beginResetModel(); std::mutex lock; - const auto prev_sigs = !histories.isEmpty() ? histories.back() : initial_signals; + const auto prev_sigs = !histories.empty() ? histories.back() : initial_signals; filtered_signals.clear(); filtered_signals.reserve(prev_sigs.size()); @@ -71,11 +71,11 @@ void FindSignalModel::search(std::function cmp) { } void FindSignalModel::undo() { - if (!histories.isEmpty()) { + if (!histories.empty()) { beginResetModel(); histories.pop_back(); filtered_signals.clear(); - if (!histories.isEmpty()) filtered_signals = histories.back(); + if (!histories.empty()) filtered_signals = histories.back(); endResetModel(); } } @@ -186,7 +186,7 @@ FindSignalDlg::FindSignalDlg(QWidget *parent) : QDialog(parent, Qt::WindowFlags( } void FindSignalDlg::search() { - if (model->histories.isEmpty()) { + if (model->histories.empty()) { setInitialSignals(); } auto v1 = value1->text().toDouble(); @@ -260,12 +260,12 @@ void FindSignalDlg::setInitialSignals() { } void FindSignalDlg::modelReset() { - properties_group->setEnabled(model->histories.isEmpty()); - message_group->setEnabled(model->histories.isEmpty()); - search_btn->setText(model->histories.isEmpty() ? tr("Find") : tr("Find Next")); - reset_btn->setEnabled(!model->histories.isEmpty()); + properties_group->setEnabled(model->histories.empty()); + message_group->setEnabled(model->histories.empty()); + search_btn->setText(model->histories.empty() ? tr("Find") : tr("Find Next")); + reset_btn->setEnabled(!model->histories.empty()); undo_btn->setEnabled(model->histories.size() > 1); - search_btn->setEnabled(model->rowCount() > 0 || model->histories.isEmpty()); + search_btn->setEnabled(model->rowCount() > 0 || model->histories.empty()); stats_label->setVisible(true); stats_label->setText(tr("%1 matches. right click on an item to create signal. double click to open message").arg(model->filtered_signals.size())); } diff --git a/tools/cabana/tools/findsignal.h b/tools/cabana/tools/findsignal.h index 5ef7461fee..239a08c9c4 100644 --- a/tools/cabana/tools/findsignal.h +++ b/tools/cabana/tools/findsignal.h @@ -2,6 +2,8 @@ #include #include +#include +#include #include #include @@ -26,14 +28,14 @@ public: QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; int columnCount(const QModelIndex &parent = QModelIndex()) const override { return 3; } - int rowCount(const QModelIndex &parent = QModelIndex()) const override { return std::min(filtered_signals.size(), 300); } + int rowCount(const QModelIndex &parent = QModelIndex()) const override { return std::min((int)filtered_signals.size(), 300); } void search(std::function cmp); void reset(); void undo(); - QList filtered_signals; - QList initial_signals; - QList> histories; + std::vector filtered_signals; + std::vector initial_signals; + std::vector> histories; uint64_t last_time = std::numeric_limits::max(); }; diff --git a/tools/cabana/tools/findsimilarbits.cc b/tools/cabana/tools/findsimilarbits.cc index 7cc7b5004f..8062b61199 100644 --- a/tools/cabana/tools/findsimilarbits.cc +++ b/tools/cabana/tools/findsimilarbits.cc @@ -1,6 +1,7 @@ #include "tools/cabana/tools/findsimilarbits.h" #include +#include #include #include @@ -114,10 +115,10 @@ void FindSimilarBitsDlg::find() { search_btn->setEnabled(true); } -QList FindSimilarBitsDlg::calcBits(uint8_t bus, uint32_t selected_address, int byte_idx, - int bit_idx, uint8_t find_bus, bool equal, int min_msgs_cnt) { - QHash> mismatches; - QHash msg_count; +std::vector FindSimilarBitsDlg::calcBits(uint8_t bus, uint32_t selected_address, int byte_idx, + int bit_idx, uint8_t find_bus, bool equal, int min_msgs_cnt) { + std::unordered_map> mismatches; + std::unordered_map msg_count; const auto &events = can->allEvents(); int bit_to_find = -1; for (const CanEvent *e : events) { @@ -143,14 +144,14 @@ QList FindSimilarBitsDlg::calcBits(uint8_ } } - QList result; + std::vector result; result.reserve(mismatches.size()); for (auto it = mismatches.begin(); it != mismatches.end(); ++it) { - if (auto cnt = msg_count[it.key()]; cnt > min_msgs_cnt) { - auto &mismatched = it.value(); - for (int i = 0; i < mismatched.size(); ++i) { + if (auto cnt = msg_count[it->first]; cnt > (uint32_t)min_msgs_cnt) { + auto &mismatched = it->second; + for (int i = 0; i < (int)mismatched.size(); ++i) { if (float perc = (mismatched[i] / (double)cnt) * 100; perc < 50) { - result.push_back({it.key(), (uint32_t)i / 8, (uint32_t)i % 8, mismatched[i], cnt, perc}); + result.push_back({it->first, (uint32_t)i / 8, (uint32_t)i % 8, mismatched[i], cnt, perc}); } } } diff --git a/tools/cabana/tools/findsimilarbits.h b/tools/cabana/tools/findsimilarbits.h index 77bfac19ca..3451360654 100644 --- a/tools/cabana/tools/findsimilarbits.h +++ b/tools/cabana/tools/findsimilarbits.h @@ -1,5 +1,7 @@ #pragma once +#include + #include #include #include @@ -22,7 +24,7 @@ private: uint32_t address, byte_idx, bit_idx, mismatches, total; float perc; }; - QList calcBits(uint8_t bus, uint32_t selected_address, int byte_idx, int bit_idx, uint8_t find_bus, + std::vector calcBits(uint8_t bus, uint32_t selected_address, int byte_idx, int bit_idx, uint8_t find_bus, bool equal, int min_msgs_cnt); void find(); From 06b2c68e03240d33174621e664d3ac04fdd954f8 Mon Sep 17 00:00:00 2001 From: royjr Date: Sun, 1 Mar 2026 21:14:41 -0500 Subject: [PATCH 008/141] macOS: fix cabana builds (#37518) --- tools/cabana/streams/routes.cc | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/tools/cabana/streams/routes.cc b/tools/cabana/streams/routes.cc index 07d8d1a0b1..1e69a45cea 100644 --- a/tools/cabana/streams/routes.cc +++ b/tools/cabana/streams/routes.cc @@ -74,9 +74,8 @@ RoutesDialog::RoutesDialog(QWidget *parent) : QDialog(parent) { QPointer self = this; std::thread([self]() { std::string result = PyDownloader::getDevices(); - auto [success, error_code] = checkApiResponse(result); - QMetaObject::invokeMethod(qApp, [self, r = QString::fromStdString(result), success, error_code]() { - if (self) self->parseDeviceList(r, success, error_code); + QMetaObject::invokeMethod(qApp, [self, r = QString::fromStdString(result), response = checkApiResponse(result)]() { + if (self) self->parseDeviceList(r, response.first, response.second); }, Qt::QueuedConnection); }).detach(); } @@ -117,9 +116,8 @@ void RoutesDialog::fetchRoutes() { std::thread([self, did, start_ms, end_ms, preserved, request_id]() { std::string result = PyDownloader::getDeviceRoutes(did, start_ms, end_ms, preserved); if (!self || self->fetch_id_ != request_id) return; - auto [success, error_code] = checkApiResponse(result); - QMetaObject::invokeMethod(qApp, [self, r = QString::fromStdString(result), success, error_code, request_id]() { - if (self && self->fetch_id_ == request_id) self->parseRouteList(r, success, error_code); + QMetaObject::invokeMethod(qApp, [self, r = QString::fromStdString(result), response = checkApiResponse(result), request_id]() { + if (self && self->fetch_id_ == request_id) self->parseRouteList(r, response.first, response.second); }, Qt::QueuedConnection); }).detach(); } From 443cd795a3024925e49da650befd619e2270ccf2 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Mon, 2 Mar 2026 15:37:18 -0800 Subject: [PATCH 009/141] Onboarding: set real width --- selfdrive/ui/mici/layouts/onboarding.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/ui/mici/layouts/onboarding.py b/selfdrive/ui/mici/layouts/onboarding.py index 7b619f31ec..09769a11e6 100644 --- a/selfdrive/ui/mici/layouts/onboarding.py +++ b/selfdrive/ui/mici/layouts/onboarding.py @@ -445,7 +445,7 @@ class OnboardingWindow(Widget): self._state = OnboardingState.TERMS if not self._accepted_terms else OnboardingState.ONBOARDING - self.set_rect(rl.Rectangle(0, 0, 458, gui_app.height)) + self.set_rect(rl.Rectangle(0, 0, gui_app.width, gui_app.height)) # Windows self._terms = TermsPage(on_accept=self._on_terms_accepted, on_decline=self._on_terms_declined) From 3504ccb639659686dbc1b42f758aaf7491729097 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Mon, 2 Mar 2026 19:49:50 -0800 Subject: [PATCH 010/141] ui: keyboard goes back on . or / (#37534) switch back to letters on . or / --- selfdrive/ui/mici/widgets/dialog.py | 5 +++-- system/ui/mici_setup.py | 2 +- system/ui/widgets/mici_keyboard.py | 7 ++++++- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/selfdrive/ui/mici/widgets/dialog.py b/selfdrive/ui/mici/widgets/dialog.py index 619c1ca28f..b2662d8a3b 100644 --- a/selfdrive/ui/mici/widgets/dialog.py +++ b/selfdrive/ui/mici/widgets/dialog.py @@ -103,11 +103,12 @@ class BigInputDialog(BigDialogBase): hint: str, default_text: str = "", minimum_length: int = 1, - confirm_callback: Callable[[str], None] | None = None): + confirm_callback: Callable[[str], None] | None = None, + auto_return_to_letters: str = ""): super().__init__() self._hint_label = UnifiedLabel(hint, font_size=35, text_color=rl.Color(255, 255, 255, int(255 * 0.35)), font_weight=FontWeight.MEDIUM) - self._keyboard = MiciKeyboard() + self._keyboard = MiciKeyboard(auto_return_to_letters=auto_return_to_letters) self._keyboard.set_text(default_text) self._keyboard.set_enabled(lambda: self.enabled and not self.is_dismissing) # for nav stack + NavWidget self._minimum_length = minimum_length diff --git a/system/ui/mici_setup.py b/system/ui/mici_setup.py index 75ea316ece..5761fd1212 100755 --- a/system/ui/mici_setup.py +++ b/system/ui/mici_setup.py @@ -623,7 +623,7 @@ class Setup(Widget): gui_app.pop_widgets_to(self._software_selection_page, instant=True) # don't reset sliders self._download(url) - keyboard = BigInputDialog("custom software URL", confirm_callback=handle_keyboard_result) + keyboard = BigInputDialog("custom software URL...", confirm_callback=handle_keyboard_result, auto_return_to_letters="./") gui_app.push_widget(keyboard) def _download(self, url: str): diff --git a/system/ui/widgets/mici_keyboard.py b/system/ui/widgets/mici_keyboard.py index 18384fd905..74f1f56346 100644 --- a/system/ui/widgets/mici_keyboard.py +++ b/system/ui/widgets/mici_keyboard.py @@ -146,8 +146,9 @@ class CapsState(IntEnum): class MiciKeyboard(Widget): - def __init__(self): + def __init__(self, auto_return_to_letters: str = ""): super().__init__() + self._auto_return_to_letters = auto_return_to_letters lower_chars = [ "qwertyuiop", @@ -305,6 +306,10 @@ class MiciKeyboard(Widget): if self._caps_state == CapsState.UPPER: self._set_uppercase(False) + # Switch back to letters after common URL delimiters + if self._closest_key[0].char in self._auto_return_to_letters and self._current_keys in (self._special_keys, self._super_special_keys): + self._set_uppercase(False) + # ensure minimum selected animation time key_selected_dt = rl.get_time() - (self._selected_key_t or 0) cur_t = rl.get_time() From 90af6be9b8d0f25f527371b75477a66341d95ac5 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Mon, 2 Mar 2026 23:24:43 -0800 Subject: [PATCH 011/141] Render offroad text centered --- selfdrive/ui/mici/onroad/augmented_road_view.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/ui/mici/onroad/augmented_road_view.py b/selfdrive/ui/mici/onroad/augmented_road_view.py index 9287406821..2c083e84b9 100644 --- a/selfdrive/ui/mici/onroad/augmented_road_view.py +++ b/selfdrive/ui/mici/onroad/augmented_road_view.py @@ -247,7 +247,7 @@ class AugmentedRoadView(CameraView): # Draw darkened background and text if not onroad if not ui_state.started: rl.draw_rectangle(int(self.rect.x), int(self.rect.y), int(self.rect.width), int(self.rect.height), rl.Color(0, 0, 0, 175)) - self._offroad_label.render(self._content_rect) + self._offroad_label.render(self._rect) # publish uiDebug msg = messaging.new_message('uiDebug') From 2ebf09eb0705b097167900c5575ab0376525449f Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Mon, 2 Mar 2026 23:25:23 -0800 Subject: [PATCH 012/141] Clear frame on offroad transition --- selfdrive/ui/mici/onroad/cameraview.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/ui/mici/onroad/cameraview.py b/selfdrive/ui/mici/onroad/cameraview.py index 89a4926ce9..62fcfd0654 100644 --- a/selfdrive/ui/mici/onroad/cameraview.py +++ b/selfdrive/ui/mici/onroad/cameraview.py @@ -155,11 +155,11 @@ class CameraView(Widget): # Prevent old frames from showing when going onroad. Qt has a separate thread # which drains the VisionIpcClient SubSocket for us. Re-connecting is not enough # and only clears internal buffers, not the message queue. - self.frame = None self.available_streams.clear() if self.client: del self.client self.client = VisionIpcClient(self._name, self._stream_type, conflate=True) + self.frame = None def _set_placeholder_color(self, color: rl.Color): """Set a placeholder color to be drawn when no frame is available.""" From 91b77522680dfa2987b2eb9f510862b2a1e2315e Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 3 Mar 2026 01:06:51 -0800 Subject: [PATCH 013/141] Setup: improvements (#37264) * pressed state for larger sliders * wifibutton * fix * clean up * some work * don't nee this now * stash * more * new pressed bigcircle * black * interp * just check position * clean up and fix slider reset * fix custom * no speed * stash * even chatter couldn't figure this one out * makes sense to combine together, less split mentality * clean that up * fix lag * match ui.py prio to eliminate lag on wifiui show event. separately, why is this slow? * night mode * delay scroll over * fix auto scrolling * stash * waiting looks disabled * clean up and don't reset sliders until user goes back * rm * fix * add termsheader back * fix callbacks * ctrl alt l * fix text spacing * clean up * stash * fix style * i want to go back * guard on exit * kinda useless stuff * Revert "kinda useless stuff" This reverts commit a4acbac31523408f358c5f68262cb630aa13ad8e. * Revert "guard on exit" This reverts commit 63ccfbf64edfbe1a144a441681f5ec78d8021ff7. * wide * setup pressed! * grow animation * 10s after initial * slow fast * start onboarding (terms) * rm duplicate page * add qr code * final grey * fix visual lag on first start * clean up dead code * dont exit from cancel * revert grey * clean up, REVIEW ME * Revert "clean up, REVIEW ME" This reverts commit c66fa60947c5f922520e7cf58c630b4bbe2d0177. * reboot slider * kb fix * Revert "kb fix" This reverts commit 883039448e6c37ae1d25d4f75ada6e96b6736358. * ./ goes to letters * Revert "./ goes to letters" This reverts commit 0d97442427edb1a000638863a3f2181204ddc160. * clean up * some more clean up * more * clean up * rename block * reset pending scroll so it can't use stale data in rare sequence * remove unused assets * clean up imports * fix updater * clean up * fix double reboot * demo time - reset to setup on reboot * let manager restart * Revert "demo time - reset to setup on reboot" This reverts commit 9468657e8438a1ce8fcb5266403b7bb3539f131f. * url... and no grow animation on start button * one next button * grow instead of shake wifi button * 36 pt font size in setup * touch up onboarding a lil * Revert "rm cpp bz2 (#37332)" This reverts commit f4a36f7f743f2fb3499af4ab6769a8819150917f. * more onboarding and clean up * clean up * wow what an amazing future clean up * back to software select * fix * copy * fix dm confirmation dialog not disabling widget underneath, all fixed with real nav stack in here * uploading * lint * add review terms to device w/ close button * todo * remove old Terms vertical scrolling classes * use new Scroller! * installer * tweak to match figma exactly * revert * fixup updater * demo day * demo day v2 * ... for percent while finishing setup * demo day v3 * demo day v4 * remove ... * demo day v6 -- "why does it do that!!" * demo day v7 -- no flash * hmm * demo day v7 * prebuilt * revert demo day * scroll after pop animation * back -> retry * stash fixes * damn, need back_callback * scroll over immediately if already in network setup * tweaks * going down is confusing * more * Revert "more" This reverts commit 29ce75b1f81eb40e7527a71d27842d9a66802206. * Revert "going down is confusing" This reverts commit 0cd2ae30d4135db1ccba6478429b45e886714e9c. * dupl * nl * sort functions * more clean up from merge * move * more * dismiss to download (hack) * Revert "dismiss to download (hack)" This reverts commit 53c45ed1f63db1f0cebbce0dfab1777c8658f505. * onboarding work * set brightness and timeout in root onboarding only * clean up * type * keep 5m for settings preview * switch back to letters on . or / * reset first step scroller * custom software warning goes down network comes up and back cb fix * clean up * smaller qr * ReviewTermsPage just for device as NavWidget * clean up * installer: stay on 100% * reset has internet while in wifiui * try this * try this * see what error we get exactly see what error we get exactly * not final solution but see how good * rm * copy changes * reset on disconnect * for separate pr * Revert "reset on disconnect" This reverts commit 552372fa4d497ba7d9de7f2edb730ee63798ffa4. * revert this, too buggy * fix for updater * sort * fix test * minor cleanup * more leaks than this rn * onboarding clean up * clean up application * click delay to small button * clean up * reset more state * fix training guide not cleaning up driverview * Revert "fix training guide not cleaning up driverview" This reverts commit cac7c5f436056cc9e747f80905d390790fb83c22. * simpler fix :( * nice catch, if you go back to terms it will reset 300s timeout and brightness * duplicate show * unused --- selfdrive/assets/icons_mici/setup/cancel.png | 3 + .../assets/icons_mici/setup/continue.png | 3 + .../icons_mici/setup/continue_disabled.png | 3 + .../icons_mici/setup/continue_pressed.png | 3 + selfdrive/assets/icons_mici/setup/restore.png | 4 +- selfdrive/ui/installer/installer.cc | 12 +- selfdrive/ui/mici/layouts/main.py | 6 +- selfdrive/ui/mici/layouts/onboarding.py | 467 +++++++----------- selfdrive/ui/mici/layouts/settings/device.py | 30 +- selfdrive/ui/mici/tests/test_widget_leaks.py | 4 +- selfdrive/ui/mici/widgets/button.py | 13 +- system/ui/lib/application.py | 3 + system/ui/mici_setup.py | 455 +++++++---------- system/ui/mici_updater.py | 28 +- system/ui/widgets/button.py | 1 + system/ui/widgets/nav_widget.py | 8 +- 16 files changed, 456 insertions(+), 587 deletions(-) create mode 100644 selfdrive/assets/icons_mici/setup/cancel.png create mode 100644 selfdrive/assets/icons_mici/setup/continue.png create mode 100644 selfdrive/assets/icons_mici/setup/continue_disabled.png create mode 100644 selfdrive/assets/icons_mici/setup/continue_pressed.png diff --git a/selfdrive/assets/icons_mici/setup/cancel.png b/selfdrive/assets/icons_mici/setup/cancel.png new file mode 100644 index 0000000000..f50cc9ef3f --- /dev/null +++ b/selfdrive/assets/icons_mici/setup/cancel.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a6892bd4d9b14b587fa491a6d608562e38819b4c618b1d7a3e8c384f05d52a2b +size 1245 diff --git a/selfdrive/assets/icons_mici/setup/continue.png b/selfdrive/assets/icons_mici/setup/continue.png new file mode 100644 index 0000000000..7a67bb0c96 --- /dev/null +++ b/selfdrive/assets/icons_mici/setup/continue.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3428d8fcf2ecf9542c524706124f82b7fc809453c63418c9234ac9df5d85bd24 +size 10074 diff --git a/selfdrive/assets/icons_mici/setup/continue_disabled.png b/selfdrive/assets/icons_mici/setup/continue_disabled.png new file mode 100644 index 0000000000..8a2bcc2ffe --- /dev/null +++ b/selfdrive/assets/icons_mici/setup/continue_disabled.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b2810add4943dd4f20a984ed6011b520925919a58d5c0dd0d846fc4d7f8a1d02 +size 7109 diff --git a/selfdrive/assets/icons_mici/setup/continue_pressed.png b/selfdrive/assets/icons_mici/setup/continue_pressed.png new file mode 100644 index 0000000000..3eaee7bf1c --- /dev/null +++ b/selfdrive/assets/icons_mici/setup/continue_pressed.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3a3a87454a3d2f1ebb327211062c52480de945673dcfd137c5da3df8fa98d731 +size 22400 diff --git a/selfdrive/assets/icons_mici/setup/restore.png b/selfdrive/assets/icons_mici/setup/restore.png index 5eff924040..5c62086f64 100644 --- a/selfdrive/assets/icons_mici/setup/restore.png +++ b/selfdrive/assets/icons_mici/setup/restore.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1f5ee67cd334d259ac33f932281db36533877009b5769c92d9cff3054fd5627c -size 2942 +oid sha256:63c1499106621a4d927c21b2b04c87235a927216d9f513a0205f0fe03b8c799b +size 12320 diff --git a/selfdrive/ui/installer/installer.cc b/selfdrive/ui/installer/installer.cc index 338bcad34e..ec7a4adc92 100644 --- a/selfdrive/ui/installer/installer.cc +++ b/selfdrive/ui/installer/installer.cc @@ -81,16 +81,14 @@ void run(const char* cmd) { } void finishInstall() { - BeginDrawing(); - ClearBackground(BLACK); - if (tici_device) { + if (tici_device) { + BeginDrawing(); + ClearBackground(BLACK); const char *m = "Finishing install..."; int text_width = MeasureText(m, FONT_SIZE); DrawTextEx(font_display, m, (Vector2){(float)(GetScreenWidth() - text_width)/2 + FONT_SIZE, (float)(GetScreenHeight() - FONT_SIZE)/2}, FONT_SIZE, 0, WHITE); - } else { - DrawTextEx(font_display, "finishing setup", (Vector2){12, 0}, 77, 0, (Color){255, 255, 255, (unsigned char)(255 * 0.9)}); - } - EndDrawing(); + EndDrawing(); + } util::sleep_for(60 * 1000); } diff --git a/selfdrive/ui/mici/layouts/main.py b/selfdrive/ui/mici/layouts/main.py index 2c3fea0d32..95258e2795 100644 --- a/selfdrive/ui/mici/layouts/main.py +++ b/selfdrive/ui/mici/layouts/main.py @@ -53,7 +53,7 @@ class MiciMainLayout(Scroller): gui_app.push_widget(self) # Start onboarding if terms or training not completed, make sure to push after self - self._onboarding_window = OnboardingWindow() + self._onboarding_window = OnboardingWindow(lambda: gui_app.pop_widgets_to(self)) if not self._onboarding_window.completed: gui_app.push_widget(self._onboarding_window) @@ -79,7 +79,7 @@ class MiciMainLayout(Scroller): def _handle_transitions(self): # Don't pop if onboarding - if gui_app.get_active_widget() == self._onboarding_window: + if gui_app.widget_in_stack(self._onboarding_window): return if ui_state.started != self._prev_onroad: @@ -105,7 +105,7 @@ class MiciMainLayout(Scroller): def _on_interactive_timeout(self): # Don't pop if onboarding - if gui_app.get_active_widget() == self._onboarding_window: + if gui_app.widget_in_stack(self._onboarding_window): return if ui_state.started: diff --git a/selfdrive/ui/mici/layouts/onboarding.py b/selfdrive/ui/mici/layouts/onboarding.py index 09769a11e6..cf633192ea 100644 --- a/selfdrive/ui/mici/layouts/onboarding.py +++ b/selfdrive/ui/mici/layouts/onboarding.py @@ -1,29 +1,23 @@ -from enum import IntEnum - -import weakref import math import numpy as np +import qrcode import pyray as rl +from collections.abc import Callable from openpilot.common.filter_simple import FirstOrderFilter -from openpilot.system.hardware import HARDWARE from openpilot.system.ui.lib.application import FontWeight, gui_app from openpilot.system.ui.widgets import Widget -from openpilot.system.ui.widgets.button import SmallButton, SmallCircleIconButton -from openpilot.system.ui.widgets.label import UnifiedLabel -from openpilot.system.ui.widgets.slider import SmallSlider -from openpilot.system.ui.mici_setup import TermsHeader, TermsPage as SetupTermsPage -from openpilot.selfdrive.ui.ui_state import ui_state, device -from openpilot.selfdrive.ui.mici.onroad.driver_state import DriverStateRenderer -from openpilot.selfdrive.ui.mici.onroad.driver_camera_dialog import BaseDriverCameraDialog +from openpilot.system.ui.widgets.button import SmallCircleIconButton +from openpilot.system.ui.widgets.scroller import NavScroller, Scroller +from openpilot.system.ui.widgets.nav_widget import NavWidget +from openpilot.system.ui.mici_setup import GreyBigButton, BigPillButton from openpilot.system.ui.widgets.label import gui_label from openpilot.system.ui.lib.multilang import tr from openpilot.system.version import terms_version, training_version - - -class OnboardingState(IntEnum): - TERMS = 0 - ONBOARDING = 1 - DECLINE = 2 +from openpilot.selfdrive.ui.ui_state import ui_state, device +from openpilot.selfdrive.ui.mici.widgets.button import BigCircleButton +from openpilot.selfdrive.ui.mici.widgets.dialog import BigConfirmationDialogV2 +from openpilot.selfdrive.ui.mici.onroad.driver_state import DriverStateRenderer +from openpilot.selfdrive.ui.mici.onroad.driver_camera_dialog import BaseDriverCameraDialog class DriverCameraSetupDialog(BaseDriverCameraDialog): @@ -57,91 +51,62 @@ class DriverCameraSetupDialog(BaseDriverCameraDialog): rl.end_scissor_mode() -class TrainingGuidePreDMTutorial(SetupTermsPage): - def __init__(self, continue_callback): - super().__init__(continue_callback, continue_text="continue") - self._title_header = TermsHeader("driver monitoring setup", gui_app.texture("icons_mici/setup/green_dm.png", 60, 60)) +class TrainingGuidePreDMTutorial(NavScroller): + def __init__(self, continue_callback: Callable[[], None]): + super().__init__() - self._dm_label = UnifiedLabel("Next, we'll ensure comma four is mounted properly.\n\nIf it does not have a clear view of the driver, " + - "unplug and remount before continuing.", 42, - FontWeight.ROMAN) + continue_button = BigPillButton("next") + continue_button.set_click_callback(continue_callback) + + self._scroller.add_widgets([ + GreyBigButton("driver monitoring\ncheck", "scroll to continue", + gui_app.texture("icons_mici/setup/green_dm.png", 64, 64)), + GreyBigButton("", "Next, we'll check if comma four can detect the driver properly."), + GreyBigButton("", "openpilot uses the cabin camera to check if the driver is distracted."), + GreyBigButton("", "If it does not have a clear view of the driver, unplug and remount before continuing."), + continue_button, + ]) def show_event(self): super().show_event() # Get driver monitoring model ready for next step - ui_state.params.put_bool("IsDriverViewEnabled", True) - - @property - def _content_height(self): - return self._dm_label.rect.y + self._dm_label.rect.height - self._scroll_panel.get_offset() - - def _render_content(self, scroll_offset): - self._title_header.render(rl.Rectangle( - self._rect.x + 16, - self._rect.y + 16 + scroll_offset, - self._title_header.rect.width, - self._title_header.rect.height, - )) - - self._dm_label.render(rl.Rectangle( - self._rect.x + 16, - self._title_header.rect.y + self._title_header.rect.height + 16, - self._rect.width - 32, - self._dm_label.get_content_height(int(self._rect.width - 32)), - )) + ui_state.params.put_bool_nonblocking("IsDriverViewEnabled", True) -class DMBadFaceDetected(SetupTermsPage): - def __init__(self, continue_callback, back_callback): - super().__init__(continue_callback, back_callback, continue_text="power off") - self._title_header = TermsHeader("make sure comma four can see your face", gui_app.texture("icons_mici/setup/orange_dm.png", 60, 60)) - self._dm_label = UnifiedLabel("Re-mount if your face is occluded or driver monitoring has difficulty tracking your face.", 42, FontWeight.ROMAN) +class DMBadFaceDetected(NavScroller): + def __init__(self): + super().__init__() - @property - def _content_height(self): - return self._dm_label.rect.y + self._dm_label.rect.height - self._scroll_panel.get_offset() + back_button = BigPillButton("back") + back_button.set_click_callback(self.dismiss) - def _render_content(self, scroll_offset): - self._title_header.render(rl.Rectangle( - self._rect.x + 16, - self._rect.y + 16 + scroll_offset, - self._title_header.rect.width, - self._title_header.rect.height, - )) - - self._dm_label.render(rl.Rectangle( - self._rect.x + 16, - self._title_header.rect.y + self._title_header.rect.height + 16, - self._rect.width - 32, - self._dm_label.get_content_height(int(self._rect.width - 32)), - )) + self._scroller.add_widgets([ + GreyBigButton("looking for driver", "make sure comma\nfour can see your face", + gui_app.texture("icons_mici/setup/orange_dm.png", 64, 64)), + GreyBigButton("", "Remount if your face is blocked, or driver monitoring has difficulty tracking your face."), + back_button, + ]) -class TrainingGuideDMTutorial(Widget): +class TrainingGuideDMTutorial(NavWidget): PROGRESS_DURATION = 4 LOOKING_THRESHOLD_DEG = 30.0 - def __init__(self, continue_callback): + def __init__(self, continue_callback: Callable[[], None]): super().__init__() - self_ref = weakref.ref(self) - self._back_button = SmallCircleIconButton(gui_app.texture("icons_mici/setup/driver_monitoring/dm_question.png", 28, 48)) - self._back_button.set_click_callback(lambda: self_ref() and self_ref()._show_bad_face_page()) + self._back_button.set_click_callback(lambda: gui_app.push_widget(self._bad_face_page)) + self._back_button.set_touch_valid_callback(lambda: self.enabled and not self.is_dismissing) # for nav stack self._good_button = SmallCircleIconButton(gui_app.texture("icons_mici/setup/driver_monitoring/dm_check.png", 42, 42)) + self._good_button.set_touch_valid_callback(lambda: self.enabled and not self.is_dismissing) # for nav stack - # Wrap the continue callback to restore settings - def wrapped_continue_callback(): - device.set_offroad_brightness(None) - continue_callback() - - self._good_button.set_click_callback(wrapped_continue_callback) + self._good_button.set_click_callback(continue_callback) self._good_button.set_enabled(False) self._progress = FirstOrderFilter(0.0, 0.5, 1 / gui_app.target_fps) self._dialog = DriverCameraSetupDialog() - self._bad_face_page = DMBadFaceDetected(HARDWARE.shutdown, lambda: self_ref() and self_ref()._hide_bad_face_page()) - self._should_show_bad_face_page = False + self._bad_face_page = DMBadFaceDetected() # Disable driver monitoring model when device times out for inactivity def inactivity_callback(): @@ -149,23 +114,11 @@ class TrainingGuideDMTutorial(Widget): device.add_interactive_timeout_callback(inactivity_callback) - def _show_bad_face_page(self): - self._bad_face_page.show_event() - self.hide_event() - self._should_show_bad_face_page = True - - def _hide_bad_face_page(self): - self._bad_face_page.hide_event() - self.show_event() - self._should_show_bad_face_page = False - def show_event(self): super().show_event() self._dialog.show_event() self._progress.x = 0.0 - device.set_offroad_brightness(100) - def _update_state(self): super()._update_state() if device.awake and not ui_state.params.get_bool("IsDriverViewEnabled"): @@ -185,7 +138,8 @@ class TrainingGuideDMTutorial(Widget): looking_center = False # stay at 100% once reached - if (dm_state.faceDetected and looking_center) or self._progress.x > 0.99: + in_bad_face = gui_app.get_active_widget() == self._bad_face_page + if ((dm_state.faceDetected and looking_center) or self._progress.x > 0.99) and not in_bad_face: slow = self._progress.x < 0.25 duration = self.PROGRESS_DURATION * 2 if slow else self.PROGRESS_DURATION self._progress.x += 1.0 / (duration * gui_app.target_fps) @@ -196,9 +150,6 @@ class TrainingGuideDMTutorial(Widget): self._good_button.set_enabled(self._progress.x >= 0.999) def _render(self, _): - if self._should_show_bad_face_page: - return self._bad_face_page.render(self._rect) - self._dialog.render(self._rect) rl.draw_rectangle_gradient_v(int(self._rect.x), int(self._rect.y + self._rect.height - 80), @@ -255,228 +206,193 @@ class TrainingGuideDMTutorial(Widget): )) # rounded border + rl.begin_scissor_mode(int(self._rect.x), int(self._rect.y), int(self._rect.width), int(self._rect.height)) rl.draw_rectangle_rounded_lines_ex(self._rect, 0.2 * 1.02, 10, 50, rl.BLACK) + rl.end_scissor_mode() -class TrainingGuideRecordFront(SetupTermsPage): - def __init__(self, continue_callback): - def on_back(): - ui_state.params.put_bool("RecordFront", False) - continue_callback() - - def on_continue(): - ui_state.params.put_bool("RecordFront", True) - continue_callback() - - super().__init__(on_continue, back_callback=on_back, back_text="no", continue_text="yes") - self._title_header = TermsHeader("improve driver monitoring", gui_app.texture("icons_mici/setup/green_dm.png", 60, 60)) - - self._dm_label = UnifiedLabel("Do you want to upload driver camera data?", 42, - FontWeight.ROMAN) - - def show_event(self): - super().show_event() - # Disable driver monitoring model after last step - ui_state.params.put_bool("IsDriverViewEnabled", False) - - @property - def _content_height(self): - return self._dm_label.rect.y + self._dm_label.rect.height - self._scroll_panel.get_offset() - - def _render_content(self, scroll_offset): - self._title_header.render(rl.Rectangle( - self._rect.x + 16, - self._rect.y + 16 + scroll_offset, - self._title_header.rect.width, - self._title_header.rect.height, - )) - - self._dm_label.render(rl.Rectangle( - self._rect.x + 16, - self._title_header.rect.y + self._title_header.rect.height + 16, - self._rect.width - 32, - self._dm_label.get_content_height(int(self._rect.width - 32)), - )) - - -class TrainingGuideAttentionNotice(SetupTermsPage): - def __init__(self, continue_callback): - super().__init__(continue_callback, continue_text="continue") - self._title_header = TermsHeader("driver assistance", gui_app.texture("icons_mici/setup/warning.png", 60, 60)) - self._warning_label = UnifiedLabel("1. openpilot is a driver assistance system.\n\n" + - "2. You must pay attention at all times.\n\n" + - "3. You must be ready to take over at any time.\n\n" + - "4. You are fully responsible for driving the car.", 42, - FontWeight.ROMAN) - - @property - def _content_height(self): - return self._warning_label.rect.y + self._warning_label.rect.height - self._scroll_panel.get_offset() - - def _render_content(self, scroll_offset): - self._title_header.render(rl.Rectangle( - self._rect.x + 16, - self._rect.y + 16 + scroll_offset, - self._title_header.rect.width, - self._title_header.rect.height, - )) - - self._warning_label.render(rl.Rectangle( - self._rect.x + 16, - self._title_header.rect.y + self._title_header.rect.height + 16, - self._rect.width - 32, - self._warning_label.get_content_height(int(self._rect.width - 32)), - )) - - -class TrainingGuide(Widget): - def __init__(self, completed_callback=None): +class TrainingGuideRecordFront(NavScroller): + def __init__(self, continue_callback: Callable[[], None]): super().__init__() - self._completed_callback = completed_callback - self._step = 0 - self_ref = weakref.ref(self) + def show_accept_dialog(): + def on_accept(): + ui_state.params.put_bool_nonblocking("RecordFront", True) + continue_callback() - def on_continue(): - if obj := self_ref(): - obj._advance_step() + gui_app.push_widget(BigConfirmationDialogV2("allow data uploading", "icons_mici/setup/driver_monitoring/dm_check.png", exit_on_confirm=False, + confirm_callback=on_accept)) + + def show_decline_dialog(): + def on_decline(): + ui_state.params.put_bool_nonblocking("RecordFront", False) + continue_callback() + + gui_app.push_widget(BigConfirmationDialogV2("no, don't upload", "icons_mici/setup/cancel.png", exit_on_confirm=False, confirm_callback=on_decline)) + + self._accept_button = BigCircleButton("icons_mici/setup/driver_monitoring/dm_check.png") + self._accept_button.set_click_callback(show_accept_dialog) + + self._decline_button = BigCircleButton("icons_mici/setup/cancel.png") + self._decline_button.set_click_callback(show_decline_dialog) + + self._scroller.add_widgets([ + GreyBigButton("driver camera data", "do you want to share video data for training?", + gui_app.texture("icons_mici/setup/green_dm.png", 64, 64)), + GreyBigButton("", "Sharing your data with comma helps improve openpilot for everyone."), + self._accept_button, + self._decline_button, + ]) + + +class TrainingGuideAttentionNotice(Scroller): + def __init__(self, continue_callback: Callable[[], None]): + super().__init__() + + continue_button = BigPillButton("next") + continue_button.set_click_callback(continue_callback) + + self._scroller.add_widgets([ + GreyBigButton("what is openpilot?", "scroll to continue", + gui_app.texture("icons_mici/setup/green_info.png", 64, 64)), + GreyBigButton("", "1. openpilot is a driver assistance system."), + GreyBigButton("", "2. You must pay attention at all times."), + GreyBigButton("", "3. You must be ready to take over at any time."), + GreyBigButton("", "4. You are fully responsible for driving the car."), + continue_button, + ]) + + +class TrainingGuide(NavWidget): + def __init__(self, completed_callback: Callable[[], None]): + super().__init__() self._steps = [ - TrainingGuideAttentionNotice(continue_callback=on_continue), - TrainingGuidePreDMTutorial(continue_callback=on_continue), - TrainingGuideDMTutorial(continue_callback=on_continue), - TrainingGuideRecordFront(continue_callback=on_continue), + TrainingGuideAttentionNotice(continue_callback=lambda: gui_app.push_widget(self._steps[1])), + TrainingGuidePreDMTutorial(continue_callback=lambda: gui_app.push_widget(self._steps[2])), + TrainingGuideDMTutorial(continue_callback=lambda: gui_app.push_widget(self._steps[3])), + TrainingGuideRecordFront(continue_callback=completed_callback), ] + self._steps[0].set_enabled(lambda: self.enabled and not self.is_dismissing) # for nav stack + def show_event(self): super().show_event() - device.set_override_interactive_timeout(300) + self._steps[0].show_event() - def hide_event(self): - super().hide_event() - device.set_override_interactive_timeout(None) + def _render(self, _): + self._steps[0].render(self._rect) - def _advance_step(self): - if self._step < len(self._steps) - 1: - self._step += 1 - self._steps[self._step].show_event() - else: - self._step = 0 - if self._completed_callback: - self._completed_callback() + +class QRCodeWidget(Widget): + def __init__(self, url: str, size: int = 170): + super().__init__() + self.set_rect(rl.Rectangle(0, 0, size, size)) + self._size = size + self._qr_texture: rl.Texture | None = None + self._generate_qr(url) + + def _generate_qr(self, url: str): + qr = qrcode.QRCode(version=1, error_correction=qrcode.constants.ERROR_CORRECT_L, box_size=10, border=0) + qr.add_data(url) + qr.make(fit=True) + + pil_img = qr.make_image(fill_color="white", back_color="black").convert('RGBA') + img_array = np.array(pil_img, dtype=np.uint8) + + rl_image = rl.Image() + rl_image.data = rl.ffi.cast("void *", img_array.ctypes.data) + rl_image.width = pil_img.width + rl_image.height = pil_img.height + rl_image.mipmaps = 1 + rl_image.format = rl.PixelFormat.PIXELFORMAT_UNCOMPRESSED_R8G8B8A8 + + self._qr_texture = rl.load_texture_from_image(rl_image) + + def _render(self, _): + if self._qr_texture: + scale = self._size / self._qr_texture.height + rl.draw_texture_ex(self._qr_texture, rl.Vector2(self._rect.x, self._rect.y), 0.0, scale, rl.WHITE) + + def __del__(self): + if self._qr_texture and self._qr_texture.id != 0: + rl.unload_texture(self._qr_texture) + + +class TermsPage(Scroller): + def __init__(self, on_accept, on_decline): + super().__init__() + + def show_accept_dialog(): + gui_app.push_widget(BigConfirmationDialogV2("accept\nterms", "icons_mici/setup/driver_monitoring/dm_check.png", + confirm_callback=on_accept)) + + def show_decline_dialog(): + gui_app.push_widget(BigConfirmationDialogV2("decline &\nuninstall", "icons_mici/setup/cancel.png", + red=True, exit_on_confirm=False, confirm_callback=on_decline)) + + self._accept_button = BigCircleButton("icons_mici/setup/driver_monitoring/dm_check.png") + self._accept_button.set_click_callback(show_accept_dialog) + + self._decline_button = BigCircleButton("icons_mici/setup/cancel.png", red=True) + self._decline_button.set_click_callback(show_decline_dialog) + + self._scroller.add_widgets([ + GreyBigButton("terms and\nconditions", "scroll to continue", + gui_app.texture("icons_mici/setup/green_info.png", 64, 64)), + GreyBigButton("swipe for QR code", "or go to https://comma.ai/terms", + gui_app.texture("icons_mici/setup/small_slider/slider_arrow.png", 64, 56, flip_x=True)), + QRCodeWidget("https://comma.ai/terms"), + GreyBigButton("", "You must accept the Terms & Conditions to use openpilot."), + self._accept_button, + self._decline_button, + ]) def _render(self, _): rl.draw_rectangle_rec(self._rect, rl.BLACK) - if self._step < len(self._steps): - self._steps[self._step].render(self._rect) - - -class DeclinePage(Widget): - def __init__(self, back_callback=None): - super().__init__() - self._uninstall_slider = SmallSlider("uninstall openpilot", self._on_uninstall) - - self._back_button = SmallButton("back") - self._back_button.set_click_callback(back_callback) - - self._warning_header = TermsHeader("you must accept the\nterms to use openpilot", - gui_app.texture("icons_mici/setup/red_warning.png", 66, 60)) - - def _on_uninstall(self): - ui_state.params.put_bool("DoUninstall", True) - gui_app.request_close() - - def _render(self, _): - self._warning_header.render(rl.Rectangle( - self._rect.x + 16, - self._rect.y + 16, - self._warning_header.rect.width, - self._warning_header.rect.height, - )) - - self._back_button.set_opacity(1 - self._uninstall_slider.slider_percentage) - self._back_button.render(rl.Rectangle( - self._rect.x + 8, - self._rect.y + self._rect.height - self._back_button.rect.height, - self._back_button.rect.width, - self._back_button.rect.height, - )) - - self._uninstall_slider.render(rl.Rectangle( - self._rect.x + self._rect.width - self._uninstall_slider.rect.width, - self._rect.y + self._rect.height - self._uninstall_slider.rect.height, - self._uninstall_slider.rect.width, - self._uninstall_slider.rect.height, - )) - - -class TermsPage(SetupTermsPage): - def __init__(self, on_accept=None, on_decline=None): - super().__init__(on_accept, on_decline, "decline") - - info_txt = gui_app.texture("icons_mici/setup/green_info.png", 60, 60) - self._title_header = TermsHeader("terms & conditions", info_txt) - - self._terms_label = UnifiedLabel("You must accept the Terms and Conditions to use openpilot. " + - "Read the latest terms at https://comma.ai/terms before continuing.", 36, - FontWeight.ROMAN) - - @property - def _content_height(self): - return self._terms_label.rect.y + self._terms_label.rect.height - self._scroll_panel.get_offset() - - def _render_content(self, scroll_offset): - self._title_header.set_position(self._rect.x + 16, self._rect.y + 12 + scroll_offset) - self._title_header.render() - - self._terms_label.render(rl.Rectangle( - self._rect.x + 16, - self._title_header.rect.y + self._title_header.rect.height + self.ITEM_SPACING, - self._rect.width - 100, - self._terms_label.get_content_height(int(self._rect.width - 100)), - )) + super()._render(_) class OnboardingWindow(Widget): - def __init__(self): + def __init__(self, completed_callback: Callable[[], None]): super().__init__() + self._completed_callback = completed_callback self._accepted_terms: bool = ui_state.params.get("HasAcceptedTerms") == terms_version self._training_done: bool = ui_state.params.get("CompletedTrainingVersion") == training_version - self._state = OnboardingState.TERMS if not self._accepted_terms else OnboardingState.ONBOARDING - self.set_rect(rl.Rectangle(0, 0, gui_app.width, gui_app.height)) # Windows - self._terms = TermsPage(on_accept=self._on_terms_accepted, on_decline=self._on_terms_declined) + self._terms = TermsPage(on_accept=self._on_terms_accepted, on_decline=self._on_uninstall) + self._terms.set_enabled(lambda: self.enabled) # for nav stack self._training_guide = TrainingGuide(completed_callback=self._on_completed_training) - self._decline_page = DeclinePage(back_callback=self._on_decline_back) + self._training_guide.set_enabled(lambda: self.enabled) # for nav stack + + def _on_uninstall(self): + ui_state.params.put_bool("DoUninstall", True) def show_event(self): super().show_event() device.set_override_interactive_timeout(300) + device.set_offroad_brightness(100) def hide_event(self): super().hide_event() + # FIXME: when nav stack sends hide event to widget 2 below on push, this needs to be moved device.set_override_interactive_timeout(None) + device.set_offroad_brightness(None) @property def completed(self) -> bool: return self._accepted_terms and self._training_done - def _on_terms_declined(self): - self._state = OnboardingState.DECLINE - - def _on_decline_back(self): - self._state = OnboardingState.TERMS - def close(self): - ui_state.params.put_bool("IsDriverViewEnabled", False) - gui_app.pop_widget() + ui_state.params.put_bool_nonblocking("IsDriverViewEnabled", False) + self._completed_callback() def _on_terms_accepted(self): ui_state.params.put("HasAcceptedTerms", terms_version) - self._state = OnboardingState.ONBOARDING + gui_app.push_widget(self._training_guide) def _on_completed_training(self): ui_state.params.put("CompletedTrainingVersion", training_version) @@ -484,9 +400,4 @@ class OnboardingWindow(Widget): def _render(self, _): rl.draw_rectangle_rec(self._rect, rl.BLACK) - if self._state == OnboardingState.TERMS: - self._terms.render(self._rect) - elif self._state == OnboardingState.ONBOARDING: - self._training_guide.render(self._rect) - elif self._state == OnboardingState.DECLINE: - self._decline_page.render(self._rect) + self._terms.render(self._rect) diff --git a/selfdrive/ui/mici/layouts/settings/device.py b/selfdrive/ui/mici/layouts/settings/device.py index b7ee5b6f45..ed29a6a84a 100644 --- a/selfdrive/ui/mici/layouts/settings/device.py +++ b/selfdrive/ui/mici/layouts/settings/device.py @@ -13,15 +13,39 @@ from openpilot.selfdrive.ui.mici.widgets.dialog import BigDialog, BigConfirmatio from openpilot.selfdrive.ui.mici.widgets.pairing_dialog import PairingDialog from openpilot.selfdrive.ui.mici.onroad.driver_camera_dialog import DriverCameraDialog from openpilot.selfdrive.ui.mici.layouts.onboarding import TrainingGuide, TermsPage +from openpilot.system.ui.mici_setup import BigPillButton from openpilot.system.ui.lib.application import gui_app, FontWeight, MousePos from openpilot.system.ui.lib.multilang import tr from openpilot.system.ui.widgets import Widget -from openpilot.selfdrive.ui.ui_state import ui_state +from openpilot.selfdrive.ui.ui_state import device, ui_state from openpilot.system.ui.widgets.label import MiciLabel from openpilot.system.ui.widgets.html_render import HtmlModal, HtmlRenderer from openpilot.system.athena.registration import UNREGISTERED_DONGLE_ID +class ReviewTermsPage(TermsPage, NavScroller): + """TermsPage with NavWidget swipe-to-dismiss for reviewing in device settings.""" + def __init__(self): + super().__init__(on_accept=self.dismiss, on_decline=self.dismiss) + self._accept_button.set_visible(False) + self._decline_button.set_visible(False) + + close_button = BigPillButton("close") + close_button.set_click_callback(self.dismiss) + self._scroller.add_widget(close_button) + + +class ReviewTrainingGuide(TrainingGuide): + def show_event(self): + super().show_event() + device.set_override_interactive_timeout(300) + + def hide_event(self): + super().hide_event() + device.set_override_interactive_timeout(None) + ui_state.params.put_bool_nonblocking("IsDriverViewEnabled", False) + + class MiciFccModal(NavRawScrollPanel): def __init__(self, file_path: str | None = None, text: str | None = None): super().__init__() @@ -311,11 +335,11 @@ class DeviceLayoutMici(NavScroller): driver_cam_btn.set_enabled(lambda: ui_state.is_offroad()) review_training_guide_btn = BigButton("review\ntraining guide", "", "icons_mici/settings/device/info.png") - review_training_guide_btn.set_click_callback(lambda: gui_app.push_widget(TrainingGuide(completed_callback=gui_app.pop_widget))) + review_training_guide_btn.set_click_callback(lambda: gui_app.push_widget(ReviewTrainingGuide(completed_callback=lambda: gui_app.pop_widgets_to(self)))) review_training_guide_btn.set_enabled(lambda: ui_state.is_offroad()) terms_btn = BigButton("terms &\nconditions", "", "icons_mici/settings/device/info.png") - terms_btn.set_click_callback(lambda: gui_app.push_widget(TermsPage(on_accept=gui_app.pop_widget))) + terms_btn.set_click_callback(lambda: gui_app.push_widget(ReviewTermsPage())) terms_btn.set_enabled(lambda: ui_state.is_offroad()) self._scroller.add_widgets([ diff --git a/selfdrive/ui/mici/tests/test_widget_leaks.py b/selfdrive/ui/mici/tests/test_widget_leaks.py index be12839cd7..ea7af84293 100755 --- a/selfdrive/ui/mici/tests/test_widget_leaks.py +++ b/selfdrive/ui/mici/tests/test_widget_leaks.py @@ -68,7 +68,9 @@ def test_dialogs_do_not_leak(): for ctor in ( # mici - MiciDriverCameraDialog, MiciTrainingGuide, MiciOnboardingWindow, MiciPairingDialog, + MiciDriverCameraDialog, MiciPairingDialog, + lambda: MiciTrainingGuide(lambda: None), + lambda: MiciOnboardingWindow(lambda: None), lambda: BigDialog("test", "test"), lambda: BigConfirmationDialogV2("test", "icons_mici/settings/network/new/trash.png"), lambda: BigInputDialog("test"), diff --git a/selfdrive/ui/mici/widgets/button.py b/selfdrive/ui/mici/widgets/button.py index b5bd65e2de..5559a1181c 100644 --- a/selfdrive/ui/mici/widgets/button.py +++ b/selfdrive/ui/mici/widgets/button.py @@ -120,6 +120,7 @@ class BigButton(Widget): self._scale_filter = BounceFilter(1.0, 0.1, 1 / gui_app.target_fps) self._click_delay = 0.075 self._shake_start: float | None = None + self._grow_animation_until: float | None = None self._rotate_icon_t: float | None = None @@ -145,6 +146,9 @@ class BigButton(Widget): self._txt_pressed_bg = gui_app.texture("icons_mici/buttons/button_rectangle_pressed.png", 402, 180) self._txt_disabled_bg = gui_app.texture("icons_mici/buttons/button_rectangle_disabled.png", 402, 180) + def set_touch_valid_callback(self, touch_callback: Callable[[], bool]) -> None: + super().set_touch_valid_callback(lambda: touch_callback() and self._grow_animation_until is None) + def _width_hint(self) -> int: # Single line if scrolling, so hide behind icon if exists icon_size = self._icon_size[0] if self._txt_icon and self._scroll and self.value else 0 @@ -182,6 +186,9 @@ class BigButton(Widget): def trigger_shake(self): self._shake_start = rl.get_time() + def trigger_grow_animation(self, duration: float = 0.65): + self._grow_animation_until = rl.get_time() + duration + @property def _shake_offset(self) -> float: SHAKE_DURATION = 0.5 @@ -197,6 +204,10 @@ class BigButton(Widget): super().set_position(x + self._shake_offset, y) def _handle_background(self) -> tuple[rl.Texture, float, float, float]: + if self._grow_animation_until is not None: + if rl.get_time() >= self._grow_animation_until: + self._grow_animation_until = None + # draw _txt_default_bg txt_bg = self._txt_default_bg if not self.enabled: @@ -204,7 +215,7 @@ class BigButton(Widget): elif self.is_pressed: txt_bg = self._txt_pressed_bg - scale = self._scale_filter.update(PRESSED_SCALE if self.is_pressed else 1.0) + scale = self._scale_filter.update(PRESSED_SCALE if self.is_pressed or self._grow_animation_until is not None else 1.0) btn_x = self._rect.x + (self._rect.width * (1 - scale)) / 2 btn_y = self._rect.y + (self._rect.height * (1 - scale)) / 2 return txt_bg, btn_x, btn_y, scale diff --git a/system/ui/lib/application.py b/system/ui/lib/application.py index 98e05e1127..8bb919cfe1 100644 --- a/system/ui/lib/application.py +++ b/system/ui/lib/application.py @@ -431,6 +431,9 @@ class GuiApplication: return self._nav_stack[-1] return None + def widget_in_stack(self, widget: object) -> bool: + return widget in self._nav_stack + def add_nav_stack_tick(self, tick_function: Callable[[], None]): if tick_function not in self._nav_stack_ticks: self._nav_stack_ticks.append(tick_function) diff --git a/system/ui/mici_setup.py b/system/ui/mici_setup.py index 5761fd1212..22ba8c4032 100755 --- a/system/ui/mici_setup.py +++ b/system/ui/mici_setup.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -from abc import abstractmethod import os import re import threading @@ -14,21 +13,21 @@ import pyray as rl from cereal import log from openpilot.common.filter_simple import FirstOrderFilter +from openpilot.system.hardware import HARDWARE, TICI from openpilot.common.realtime import config_realtime_process, set_core_affinity from openpilot.common.swaglog import cloudlog from openpilot.common.utils import run_cmd -from openpilot.system.hardware import HARDWARE, TICI from openpilot.system.ui.lib.application import gui_app, FontWeight from openpilot.system.ui.lib.wifi_manager import WifiManager -from openpilot.system.ui.lib.scroll_panel2 import GuiScrollPanel2 from openpilot.system.ui.widgets import Widget from openpilot.system.ui.widgets.nav_widget import NavWidget -from openpilot.system.ui.widgets.button import (IconButton, SmallButton, WideRoundedButton, SmallerRoundedButton, - SmallCircleIconButton, WidishRoundedButton, FullRoundedButton) +from openpilot.system.ui.widgets.button import SmallButton from openpilot.system.ui.widgets.label import UnifiedLabel +from openpilot.system.ui.widgets.scroller import Scroller, NavScroller, ITEM_SPACING from openpilot.system.ui.widgets.slider import LargerSlider, SmallSlider -from openpilot.selfdrive.ui.mici.layouts.settings.network import WifiUIMici +from openpilot.selfdrive.ui.mici.layouts.settings.network import WifiNetworkButton, WifiUIMici from openpilot.selfdrive.ui.mici.widgets.dialog import BigInputDialog +from openpilot.selfdrive.ui.mici.widgets.button import BigButton NetworkType = log.DeviceState.NetworkType @@ -122,9 +121,9 @@ class SoftwareSelectionPage(NavWidget): use_custom_software_callback: Callable): super().__init__() - self._openpilot_slider = LargerSlider("slide to use\nopenpilot", use_openpilot_callback) + self._openpilot_slider = LargerSlider("slide to install\nopenpilot", use_openpilot_callback) self._openpilot_slider.set_enabled(lambda: self.enabled and not self.is_dismissing) - self._custom_software_slider = LargerSlider("slide to use\ncustom software", use_custom_software_callback, green=False) + self._custom_software_slider = LargerSlider("slide to install\nother software", use_custom_software_callback, green=False) self._custom_software_slider.set_enabled(lambda: self.enabled and not self.is_dismissing) def show_event(self): @@ -161,199 +160,24 @@ class SoftwareSelectionPage(NavWidget): self._custom_software_slider.render(custom_software_rect) -class TermsHeader(Widget): - def __init__(self, text: str, icon_texture: rl.Texture): - super().__init__() - - self._title = UnifiedLabel(text, 36, text_color=rl.Color(255, 255, 255, int(255 * 0.9)), - font_weight=FontWeight.BOLD, alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_MIDDLE, - line_height=0.8) - self._icon_texture = icon_texture - - self.set_rect(rl.Rectangle(0, 0, gui_app.width - 16 * 2, self._icon_texture.height)) - - def set_title(self, text: str): - self._title.set_text(text) - - def set_icon(self, icon_texture: rl.Texture): - self._icon_texture = icon_texture - - def _render(self, _): - rl.draw_texture_ex(self._icon_texture, rl.Vector2(self._rect.x, self._rect.y), - 0.0, 1.0, rl.WHITE) - - # May expand outside parent rect - title_content_height = self._title.get_content_height(int(self._rect.width - self._icon_texture.width - 16)) - title_rect = rl.Rectangle( - self._rect.x + self._icon_texture.width + 16, - self._rect.y + (self._rect.height - title_content_height) / 2, - self._rect.width - self._icon_texture.width - 16, - title_content_height, - ) - self._title.render(title_rect) - - -class TermsPage(Widget): - ITEM_SPACING = 20 - - def __init__(self, continue_callback: Callable, back_callback: Callable | None = None, - back_text: str = "back", continue_text: str = "accept"): - super().__init__() - - # TODO: use Scroller - self._scroll_panel = GuiScrollPanel2(horizontal=False) - - self._continue_text = continue_text - self._continue_slider: bool = continue_text in ("reboot", "power off") - self._continue_button: WideRoundedButton | FullRoundedButton | SmallSlider - if self._continue_slider: - self._continue_button = SmallSlider(continue_text, confirm_callback=continue_callback) - self._scroll_panel.set_enabled(lambda: not self._continue_button.is_pressed) - elif back_callback is not None: - self._continue_button = WideRoundedButton(continue_text) - else: - self._continue_button = FullRoundedButton(continue_text) - self._continue_button.set_enabled(False) - self._continue_button.set_opacity(0.0) - self._continue_button.set_touch_valid_callback(self._scroll_panel.is_touch_valid) - if not self._continue_slider: - self._continue_button.set_click_callback(continue_callback) - - self._enable_back = back_callback is not None - self._back_button = SmallButton(back_text) - self._back_button.set_opacity(0.0) - self._back_button.set_touch_valid_callback(self._scroll_panel.is_touch_valid) - self._back_button.set_click_callback(back_callback) - - self._scroll_down_indicator = IconButton(gui_app.texture("icons_mici/setup/scroll_down_indicator.png", 64, 78)) - self._scroll_down_indicator.set_enabled(False) - - def reset(self): - self._scroll_panel.set_offset(0) - self._continue_button.set_enabled(False) - self._continue_button.set_opacity(0.0) - self._back_button.set_enabled(False) - self._back_button.set_opacity(0.0) - self._scroll_down_indicator.set_opacity(1.0) - - def show_event(self): - super().show_event() - self.reset() - - @property - @abstractmethod - def _content_height(self): - pass - - @property - def _scrolled_down_offset(self): - return -self._content_height + (self._continue_button.rect.height + 16 + 30) - - @abstractmethod - def _render_content(self, scroll_offset): - pass - - def _render(self, _): - rl.draw_rectangle_rec(self._rect, rl.BLACK) - scroll_offset = round(self._scroll_panel.update(self._rect, self._content_height + self._continue_button.rect.height + 16)) - - if scroll_offset <= self._scrolled_down_offset: - # don't show back if not enabled - if self._enable_back: - self._back_button.set_enabled(True) - self._back_button.set_opacity(1.0, smooth=True) - self._continue_button.set_enabled(True) - self._continue_button.set_opacity(1.0, smooth=True) - self._scroll_down_indicator.set_opacity(0.0, smooth=True) - else: - self._back_button.set_enabled(False) - self._back_button.set_opacity(0.0, smooth=True) - self._continue_button.set_enabled(False) - self._continue_button.set_opacity(0.0, smooth=True) - self._scroll_down_indicator.set_opacity(1.0, smooth=True) - - # Render content - self._render_content(scroll_offset) - - # black gradient at top and bottom for scrolling content - rl.draw_rectangle_gradient_v(int(self._rect.x), int(self._rect.y), - int(self._rect.width), 20, rl.BLACK, rl.BLANK) - rl.draw_rectangle_gradient_v(int(self._rect.x), int(self._rect.y + self._rect.height - 20), - int(self._rect.width), 20, rl.BLANK, rl.BLACK) - - # fade out back button as slider is moved - if self._continue_slider and scroll_offset <= self._scrolled_down_offset: - self._back_button.set_opacity(1.0 - self._continue_button.slider_percentage) - self._back_button.set_visible(self._continue_button.slider_percentage < 0.99) - - self._back_button.render(rl.Rectangle( - self._rect.x + 8, - self._rect.y + self._rect.height - self._back_button.rect.height, - self._back_button.rect.width, - self._back_button.rect.height, - )) - - continue_x = self._rect.x + 8 - if self._enable_back: - continue_x = self._rect.x + self._rect.width - self._continue_button.rect.width - 8 - if self._continue_slider: - continue_x += 8 - self._continue_button.render(rl.Rectangle( - continue_x, - self._rect.y + self._rect.height - self._continue_button.rect.height, - self._continue_button.rect.width, - self._continue_button.rect.height, - )) - - self._scroll_down_indicator.render(rl.Rectangle( - self._rect.x + self._rect.width - self._scroll_down_indicator.rect.width - 8, - self._rect.y + self._rect.height - self._scroll_down_indicator.rect.height - 8, - self._scroll_down_indicator.rect.width, - self._scroll_down_indicator.rect.height, - )) - - -class CustomSoftwareWarningPage(TermsPage): +class CustomSoftwareWarningPage(NavScroller): def __init__(self, continue_callback: Callable, back_callback: Callable): - super().__init__(continue_callback, back_callback) + super().__init__() + self.set_back_callback(back_callback) - self._title_header = TermsHeader("use caution installing\n3rd party software", - gui_app.texture("icons_mici/setup/warning.png", 66, 60)) - self._body = UnifiedLabel("• It has not been tested by comma.\n" + - "• It may not comply with relevant safety standards.\n" + - "• It may cause damage to your device and/or vehicle.\n", 36, text_color=rl.Color(255, 255, 255, int(255 * 0.9)), - font_weight=FontWeight.ROMAN) + self._continue_button = BigPillButton("next") + self._continue_button.set_click_callback(continue_callback) - self._restore_header = TermsHeader("how to backup &\nrestore", gui_app.texture("icons_mici/setup/restore.png", 60, 60)) - self._restore_body = UnifiedLabel("To restore your device to a factory state later, use https://flash.comma.ai", - 36, text_color=rl.Color(255, 255, 255, int(255 * 0.9)), - font_weight=FontWeight.ROMAN) - - @property - def _content_height(self): - return self._restore_body.rect.y + self._restore_body.rect.height - self._scroll_panel.get_offset() - - def _render_content(self, scroll_offset): - self._title_header.set_position(self._rect.x + 16, self._rect.y + 8 + scroll_offset) - self._title_header.render() - - body_rect = rl.Rectangle( - self._rect.x + 8, - self._title_header.rect.y + self._title_header.rect.height + self.ITEM_SPACING, - self._rect.width - 50, - self._body.get_content_height(int(self._rect.width - 50)), - ) - self._body.render(body_rect) - - self._restore_header.set_position(self._rect.x + 16, self._body.rect.y + self._body.rect.height + self.ITEM_SPACING) - self._restore_header.render() - - self._restore_body.render(rl.Rectangle( - self._rect.x + 8, - self._restore_header.rect.y + self._restore_header.rect.height + self.ITEM_SPACING, - self._rect.width - 50, - self._restore_body.get_content_height(int(self._rect.width - 50)), - )) + self._scroller.add_widgets([ + GreyBigButton("use caution", "when installing\n3rd party software", + gui_app.texture("icons_mici/setup/warning.png", 64, 58)), + GreyBigButton("", "• It has not been tested by comma"), + GreyBigButton("", "• It may not comply with relevant safety standards."), + GreyBigButton("", "• It may cause damage to your device and/or vehicle."), + GreyBigButton("how to restore to a\nfactory state later", "https://flash.comma.ai", + gui_app.texture("icons_mici/setup/restore.png", 64, 64)), + self._continue_button, + ]) class DownloadingPage(Widget): @@ -391,11 +215,9 @@ class DownloadingPage(Widget): )) -class FailedPage(NavWidget): +class FailedPageBase(Widget): def __init__(self, reboot_callback: Callable, retry_callback: Callable, title: str = "download failed"): super().__init__() - self.set_back_callback(retry_callback) - self._title_label = UnifiedLabel(title, 64, text_color=rl.Color(255, 255, 255, int(255 * 0.9)), font_weight=FontWeight.DISPLAY) self._reason_label = UnifiedLabel("", 36, text_color=rl.Color(255, 255, 255, int(255 * 0.9 * 0.65)), @@ -446,11 +268,86 @@ class FailedPage(NavWidget): )) -class NetworkSetupPage(NavWidget): +class FailedPage(FailedPageBase, NavWidget): + def __init__(self, reboot_callback: Callable, retry_callback: Callable, title: str = "download failed"): + super().__init__(reboot_callback, retry_callback, title) + self.set_back_callback(retry_callback) + + +class GreyBigButton(BigButton): + """Users should manage newlines with this class themselves""" + + LABEL_HORIZONTAL_PADDING = 30 + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.set_touch_valid_callback(lambda: False) + + self._rect.width = 476 + + self._label.set_font_size(36) + self._label.set_font_weight(FontWeight.BOLD) + self._label.set_line_height(1.0) + + self._sub_label.set_font_size(36) + self._sub_label.set_text_color(rl.Color(255, 255, 255, int(255 * 0.9))) + self._sub_label.set_font_weight(FontWeight.DISPLAY_REGULAR) + self._sub_label.set_alignment_vertical(rl.GuiTextAlignmentVertical.TEXT_ALIGN_MIDDLE if not self._label.text else + rl.GuiTextAlignmentVertical.TEXT_ALIGN_BOTTOM) + self._sub_label.set_line_height(0.95) + + @property + def LABEL_VERTICAL_PADDING(self): + return BigButton.LABEL_VERTICAL_PADDING if self._label.text else 18 + + def _width_hint(self) -> int: + return int(self._rect.width - self.LABEL_HORIZONTAL_PADDING * 2) + + def _render(self, _): + rl.draw_rectangle_rounded(self._rect, 0.4, 10, rl.Color(255, 255, 255, int(255 * 0.15))) + self._draw_content(self._rect.y) + + +class BigPillButton(BigButton): + def __init__(self, *args, green: bool = False, disabled_background: bool = False, **kwargs): + self._green = green + self._disabled_background = disabled_background + super().__init__(*args, **kwargs) + + self._label.set_font_size(48) + self._label.set_alignment(rl.GuiTextAlignment.TEXT_ALIGN_CENTER) + self._label.set_alignment_vertical(rl.GuiTextAlignmentVertical.TEXT_ALIGN_MIDDLE) + + def _load_images(self): + if self._green: + self._txt_default_bg = gui_app.texture("icons_mici/setup/start_button.png", 402, 180) + self._txt_pressed_bg = gui_app.texture("icons_mici/setup/start_button_pressed.png", 402, 180) + else: + self._txt_default_bg = gui_app.texture("icons_mici/setup/continue.png", 402, 180) + self._txt_pressed_bg = gui_app.texture("icons_mici/setup/continue_pressed.png", 402, 180) + self._txt_disabled_bg = gui_app.texture("icons_mici/setup/continue_disabled.png", 402, 180) + + def set_green(self, green: bool): + if self._green != green: + self._green = green + self._load_images() + + def _update_label_layout(self): + # Don't change label text size + pass + + def _handle_background(self) -> tuple[rl.Texture, float, float, float]: + txt_bg, btn_x, btn_y, scale = super()._handle_background() + + if self._disabled_background: + txt_bg = self._txt_disabled_bg + return txt_bg, btn_x, btn_y, scale + + +class NetworkSetupPageBase(Scroller): def __init__(self, network_monitor: NetworkConnectivityMonitor, continue_callback: Callable[[bool], None], - back_callback: Callable[[], None] | None): + disable_connect_hint: bool = False): super().__init__() - self.set_back_callback(back_callback) self._wifi_manager = WifiManager() self._wifi_manager.set_active(True) @@ -459,83 +356,106 @@ class NetworkSetupPage(NavWidget): self._prev_has_internet = False self._wifi_ui = WifiUIMici(self._wifi_manager) - self._no_wifi_txt = gui_app.texture("icons_mici/settings/network/wifi_strength_slash.png", 58, 50) - self._wifi_full_txt = gui_app.texture("icons_mici/settings/network/wifi_strength_full.png", 58, 50) - self._waiting_text = "waiting for internet..." - self._network_header = TermsHeader(self._waiting_text, self._no_wifi_txt) + self._connect_button = GreyBigButton("connect to\ninternet", "swipe down to go back", + gui_app.texture("icons_mici/setup/small_slider/slider_arrow.png", 64, 56, flip_x=True)) + self._connect_button.set_visible(not disable_connect_hint) - back_txt = gui_app.texture("icons_mici/setup/back_new.png", 37, 32) - self._back_button = SmallCircleIconButton(back_txt) - self._back_button.set_click_callback(back_callback) - self._back_button.set_enabled(lambda: self.enabled) # for nav stack - - self._wifi_button = SmallerRoundedButton("wifi") + self._wifi_button = WifiNetworkButton(self._wifi_manager) self._wifi_button.set_click_callback(lambda: gui_app.push_widget(self._wifi_ui)) - self._wifi_button.set_enabled(lambda: self.enabled) - self._continue_button = WidishRoundedButton("continue") - self._continue_button.set_enabled(False) + self._show_time = 0.0 + self._pending_has_internet_scroll = False + self._pending_continue_grow_animation = False + self._pending_wifi_grow_animation = False + + def on_waiting_click(): + offset = (self._wifi_button.rect.x + self._wifi_button.rect.width / 2) - (self._rect.x + self._rect.width / 2) + self._scroller.scroll_to(offset, smooth=True, block_interaction=True) + # trigger grow when wifi button in view + self._pending_wifi_grow_animation = True + + self._waiting_button = BigPillButton("waiting for\ninternet...", disabled_background=True) + self._waiting_button.set_click_callback(on_waiting_click) + self._continue_button = BigPillButton("install openpilot", green=True) self._continue_button.set_click_callback(lambda: continue_callback(self._custom_software)) + self._scroller.add_widgets([ + self._connect_button, + self._wifi_button, + self._continue_button, + self._waiting_button, + ]) + gui_app.add_nav_stack_tick(self._nav_stack_tick) def show_event(self): super().show_event() + self._show_time = rl.get_time() self._prev_has_internet = False - self._network_monitor.reset() - self._set_has_internet(False) + self._pending_has_internet_scroll = False + self._pending_continue_grow_animation = False + self._pending_wifi_grow_animation = False def _nav_stack_tick(self): self._wifi_manager.process_callbacks() has_internet = self._network_monitor.network_connected.is_set() - if has_internet != self._prev_has_internet: - self._set_has_internet(has_internet) - if has_internet: - gui_app.pop_widgets_to(self) - self._prev_has_internet = has_internet + if has_internet and not self._prev_has_internet: + self._pending_has_internet_scroll = True + self._prev_has_internet = has_internet - def _set_has_internet(self, has_internet: bool): - if has_internet: - self._network_header.set_title("connected to internet") - self._network_header.set_icon(self._wifi_full_txt) - self._continue_button.set_enabled(lambda: self.enabled) - else: - self._network_header.set_title(self._waiting_text) - self._network_header.set_icon(self._no_wifi_txt) - self._continue_button.set_enabled(False) + if self._pending_has_internet_scroll: + # Scrolls over to continue button, then grows once in view + elapsed = rl.get_time() - self._show_time + if elapsed > 0.5: + self._pending_has_internet_scroll = False + + def scroll_to_download(): + self._scroller._layout() + end_offset = -(self._scroller.content_size - self._rect.width) + remaining = self._scroller.scroll_panel.get_offset() - end_offset + self._scroller.scroll_to(remaining, smooth=True, block_interaction=True) + self._pending_continue_grow_animation = True + + # Animate WifiUi down first before scroll + gui_app.pop_widgets_to(self, scroll_to_download) def set_custom_software(self, custom_software: bool): self._custom_software = custom_software + self._continue_button.set_text("install openpilot" if not custom_software else "choose software") + self._continue_button.set_green(not custom_software) - def _render(self, _): - self._network_header.render(rl.Rectangle( - self._rect.x + 16, - self._rect.y + 16, - self._rect.width - 32, - self._network_header.rect.height, - )) + def set_is_updater(self): + self._continue_button.set_text("download\n& install") + self._continue_button.set_green(False) - self._back_button.render(rl.Rectangle( - self._rect.x + 8, - self._rect.y + self._rect.height - self._back_button.rect.height, - self._back_button.rect.width, - self._back_button.rect.height, - )) + def _update_state(self): + super()._update_state() - self._wifi_button.render(rl.Rectangle( - self._rect.x + 8 + self._back_button.rect.width + 10, - self._rect.y + self._rect.height - self._wifi_button.rect.height, - self._wifi_button.rect.width, - self._wifi_button.rect.height, - )) + if self._pending_continue_grow_animation: + btn_right = self._continue_button.rect.x + self._continue_button.rect.width + visible_right = self._rect.x + self._rect.width + if btn_right < visible_right + 50: + self._pending_continue_grow_animation = False + self._continue_button.trigger_grow_animation() - self._continue_button.render(rl.Rectangle( - self._rect.x + self._rect.width - self._continue_button.rect.width - 8, - self._rect.y + self._rect.height - self._continue_button.rect.height, - self._continue_button.rect.width, - self._continue_button.rect.height, - )) + if self._pending_wifi_grow_animation and abs(self._wifi_button.rect.x - ITEM_SPACING) < 50: + self._pending_wifi_grow_animation = False + self._wifi_button.trigger_grow_animation() + + if self._network_monitor.network_connected.is_set(): + self._continue_button.set_visible(True) + self._waiting_button.set_visible(False) + else: + self._continue_button.set_visible(False) + self._waiting_button.set_visible(True) + + +class NetworkSetupPage(NetworkSetupPageBase, NavScroller): + def __init__(self, network_monitor: NetworkConnectivityMonitor, continue_callback: Callable[[bool], None], + back_callback: Callable[[], None] | None): + super().__init__(network_monitor, continue_callback) + self.set_back_callback(back_callback) class Setup(Widget): @@ -557,13 +477,13 @@ class Setup(Widget): self._start_page.set_click_callback(getting_started_button_callback) self._start_page.set_enabled(lambda: self.enabled) # for nav stack - self._network_setup_page = NetworkSetupPage(self._network_monitor, self._network_setup_continue_button_callback, - self._pop_to_software_selection) + self._network_setup_page = NetworkSetupPage(self._network_monitor, self._network_setup_continue_callback, self._pop_to_software_selection) + self._software_selection_page = SoftwareSelectionPage(self._use_openpilot, lambda: gui_app.push_widget(self._custom_software_warning_page)) self._download_failed_page = FailedPage(HARDWARE.reboot, self._pop_to_software_selection) - self._custom_software_warning_page = CustomSoftwareWarningPage(self._software_selection_custom_software_continue, self._pop_to_software_selection) + self._custom_software_warning_page = CustomSoftwareWarningPage(lambda: self._push_network_setup(True), self._pop_to_software_selection) self._downloading_page = DownloadingPage() @@ -602,17 +522,14 @@ class Setup(Widget): time.sleep(0.1) gui_app.request_close() else: - self._push_network_setup(custom_software=False) + self._push_network_setup() - def _push_network_setup(self, custom_software: bool): + def _push_network_setup(self, custom_software: bool = False): + # to fire the correct continue callback later self._network_setup_page.set_custom_software(custom_software) - gui_app.push_widget(self._network_setup_page) + gui_app.pop_widgets_to(self._software_selection_page, lambda: gui_app.push_widget(self._network_setup_page)) - def _software_selection_custom_software_continue(self): - gui_app.pop_widgets_to(self._software_selection_page, instant=True) # don't reset sliders - self._push_network_setup(custom_software=True) - - def _network_setup_continue_button_callback(self, custom_software): + def _network_setup_continue_callback(self, custom_software: bool): if not custom_software: gui_app.pop_widgets_to(self._software_selection_page, instant=True) # don't reset sliders self._download(OPENPILOT_URL) diff --git a/system/ui/mici_updater.py b/system/ui/mici_updater.py index c98b310709..50ea412925 100755 --- a/system/ui/mici_updater.py +++ b/system/ui/mici_updater.py @@ -7,11 +7,10 @@ from enum import IntEnum from openpilot.system.hardware import HARDWARE from openpilot.system.ui.lib.application import gui_app, FontWeight -from openpilot.system.ui.lib.wifi_manager import WifiManager from openpilot.system.ui.widgets import Widget from openpilot.system.ui.widgets.label import UnifiedLabel from openpilot.system.ui.widgets.button import FullRoundedButton -from openpilot.system.ui.mici_setup import NetworkSetupPage, FailedPage, NetworkConnectivityMonitor +from openpilot.system.ui.mici_setup import NetworkSetupPageBase, FailedPageBase, NetworkConnectivityMonitor class Screen(IntEnum): @@ -32,16 +31,14 @@ class Updater(Widget): self.progress_text = "loading" self.process = None self.update_thread = None - self._wifi_manager = WifiManager() - self._wifi_manager.set_active(True) - - self._network_setup_page = NetworkSetupPage(self._wifi_manager, self._network_setup_continue_callback, - self._network_setup_back_callback) - self._network_setup_page.set_enabled(lambda: self.enabled) # for nav stack - self._network_monitor = NetworkConnectivityMonitor() self._network_monitor.start() + self._network_setup_page = NetworkSetupPageBase(self._network_monitor, self._network_setup_continue_callback, + disable_connect_hint=True) + self._network_setup_page.set_is_updater() + self._network_setup_page.set_enabled(lambda: self.enabled) # for nav stack + # Buttons self._continue_button = FullRoundedButton("continue") self._continue_button.set_click_callback(lambda: self.set_current_screen(Screen.WIFI)) @@ -52,8 +49,8 @@ class Updater(Widget): text_color=rl.Color(255, 255, 255, int(255 * 0.9)), font_weight=FontWeight.ROMAN) - self._update_failed_page = FailedPage(HARDWARE.reboot, self._update_failed_retry_callback, - title="update failed") + self._update_failed_page = FailedPageBase(HARDWARE.reboot, self._update_failed_retry_callback, + title="update failed") self._progress_title_label = UnifiedLabel("", 64, text_color=rl.Color(255, 255, 255, int(255 * 0.9)), font_weight=FontWeight.DISPLAY, line_height=0.8) @@ -61,10 +58,7 @@ class Updater(Widget): font_weight=FontWeight.ROMAN, alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_BOTTOM) - def _network_setup_back_callback(self): - self.set_current_screen(Screen.PROMPT) - - def _network_setup_continue_callback(self): + def _network_setup_continue_callback(self, _): self.install_update() def _update_failed_retry_callback(self): @@ -160,14 +154,10 @@ class Updater(Widget): rect.height, )) - def _update_state(self): - self._wifi_manager.process_callbacks() - def _render(self, rect: rl.Rectangle): if self.current_screen == Screen.PROMPT: self.render_prompt_screen(rect) elif self.current_screen == Screen.WIFI: - self._network_setup_page.set_has_internet(self._network_monitor.network_connected.is_set()) self._network_setup_page.render(rect) elif self.current_screen == Screen.PROGRESS: self.render_progress_screen(rect) diff --git a/system/ui/widgets/button.py b/system/ui/widgets/button.py index 67125d7091..a608344710 100644 --- a/system/ui/widgets/button.py +++ b/system/ui/widgets/button.py @@ -228,6 +228,7 @@ class SmallCircleIconButton(Widget): class SmallButton(Widget): def __init__(self, text: str): super().__init__() + self._click_delay = 0.075 self._opacity_filter = FirstOrderFilter(1.0, 0.1, 1 / gui_app.target_fps) self._load_assets() diff --git a/system/ui/widgets/nav_widget.py b/system/ui/widgets/nav_widget.py index 67203d53f4..3292c53ce8 100644 --- a/system/ui/widgets/nav_widget.py +++ b/system/ui/widgets/nav_widget.py @@ -63,7 +63,7 @@ class NavWidget(Widget, abc.ABC): self._playing_dismiss_animation = False # released and animating away self._y_pos_filter = BounceFilter(0.0, 0.1, 1 / gui_app.target_fps, bounce=1) - self._back_callback: Callable[[], None] | None = None # persistent callback for any back navigation + self._back_callback: Callable[[], None] | None = None # persistent callback for user-initiated back navigation self._dismiss_callback: Callable[[], None] | None = None # transient callback for programmatic dismiss # TODO: move this state into NavBar @@ -150,12 +150,12 @@ class NavWidget(Widget, abc.ABC): if new_y > self._rect.height + DISMISS_PUSH_OFFSET - 10: gui_app.pop_widget() - if self._back_callback is not None: - self._back_callback() - + # Only one callback should ever be fired if self._dismiss_callback is not None: self._dismiss_callback() self._dismiss_callback = None + elif self._back_callback is not None: + self._back_callback() self._playing_dismiss_animation = False self._drag_start_pos = None From 92f9684fdb79a14a8a95d3755390e43abc4131a1 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 3 Mar 2026 01:13:11 -0800 Subject: [PATCH 014/141] Revert "use vendored raylib from dependencies repo" (#37537) Revert "use vendored raylib from dependencies repo (#37489)" This reverts commit 037497939778c3fff8d721b60b7c85ac7f995683. --- SConstruct | 3 +- pyproject.toml | 2 +- scripts/lint/check_raylib_includes.sh | 10 + selfdrive/ui/SConscript | 9 +- selfdrive/ui/installer/installer.cc | 2 +- third_party/raylib/.gitignore | 4 + third_party/raylib/Darwin/libraylib.a | 3 + third_party/raylib/build.sh | 93 + third_party/raylib/include/raygui.h | 5759 ++++++++++++++++++++++++ third_party/raylib/include/raylib.h | 1766 ++++++++ third_party/raylib/include/raymath.h | 2949 ++++++++++++ third_party/raylib/include/rlgl.h | 5262 ++++++++++++++++++++++ third_party/raylib/larch64/libraylib.a | 3 + third_party/raylib/x86_64/libraylib.a | 3 + uv.lock | 40 +- 15 files changed, 15882 insertions(+), 26 deletions(-) create mode 100755 scripts/lint/check_raylib_includes.sh create mode 100644 third_party/raylib/.gitignore create mode 100644 third_party/raylib/Darwin/libraylib.a create mode 100755 third_party/raylib/build.sh create mode 100644 third_party/raylib/include/raygui.h create mode 100644 third_party/raylib/include/raylib.h create mode 100644 third_party/raylib/include/raymath.h create mode 100644 third_party/raylib/include/rlgl.h create mode 100644 third_party/raylib/larch64/libraylib.a create mode 100644 third_party/raylib/x86_64/libraylib.a diff --git a/SConstruct b/SConstruct index 7003abbf05..da70bc3924 100644 --- a/SConstruct +++ b/SConstruct @@ -48,10 +48,9 @@ if arch != "larch64": import ncurses import openssl3 import python3_dev - import raylib import zeromq import zstd - pkgs = [bzip2, capnproto, eigen, ffmpeg_pkg, libjpeg, libyuv, ncurses, openssl3, raylib, zeromq, zstd] + pkgs = [bzip2, capnproto, eigen, ffmpeg_pkg, libjpeg, libyuv, ncurses, openssl3, zeromq, zstd] py_include = python3_dev.INCLUDE_DIR else: # TODO: remove when AGNOS has our new vendor pkgs diff --git a/pyproject.toml b/pyproject.toml index d0020b21e7..6516c8cd5b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -73,7 +73,7 @@ dependencies = [ "zstandard", # ui - "raylib @ git+https://github.com/commaai/dependencies.git@releases#subdirectory=raylib", + "raylib > 5.5.0.3", "qrcode", "jeepney", ] diff --git a/scripts/lint/check_raylib_includes.sh b/scripts/lint/check_raylib_includes.sh new file mode 100755 index 0000000000..e3be73a489 --- /dev/null +++ b/scripts/lint/check_raylib_includes.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env bash + +FAIL=0 + +if grep -n '#include "third_party/raylib/include/raylib\.h"' $@ | grep -v '^system/ui/raylib/raylib\.h'; then + echo -e "Bad raylib include found! Use '#include \"system/ui/raylib/raylib.h\"' instead\n" + FAIL=1 +fi + +exit $FAIL diff --git a/selfdrive/ui/SConscript b/selfdrive/ui/SConscript index bebfd0011f..4d7448c62f 100644 --- a/selfdrive/ui/SConscript +++ b/selfdrive/ui/SConscript @@ -16,17 +16,12 @@ env.Command( action=f"python3 {generator}", ) -try: - import raylib -except ImportError: - raylib = None -if GetOption('extras') and arch == "larch64" and raylib is not None: +if GetOption('extras') and arch == "larch64": # build installers if arch != "Darwin": raylib_env = env.Clone() - raylib_env['CPPPATH'] += [raylib.INCLUDE_DIR] - raylib_env['LIBPATH'] += [raylib.LIB_DIR] + raylib_env['LIBPATH'] += [f'#third_party/raylib/{arch}/'] raylib_env['LINKFLAGS'].append('-Wl,-strip-debug') raylib_libs = common + ["raylib"] diff --git a/selfdrive/ui/installer/installer.cc b/selfdrive/ui/installer/installer.cc index ec7a4adc92..9c35b0465e 100644 --- a/selfdrive/ui/installer/installer.cc +++ b/selfdrive/ui/installer/installer.cc @@ -6,7 +6,7 @@ #include "common/swaglog.h" #include "common/util.h" #include "system/hardware/hw.h" -#include "raylib.h" +#include "third_party/raylib/include/raylib.h" int freshClone(); int cachedFetch(const std::string &cache); diff --git a/third_party/raylib/.gitignore b/third_party/raylib/.gitignore new file mode 100644 index 0000000000..6b1d3ad748 --- /dev/null +++ b/third_party/raylib/.gitignore @@ -0,0 +1,4 @@ +/raylib_repo/ +/raylib_python_repo/ +/wheel/ +!*.a diff --git a/third_party/raylib/Darwin/libraylib.a b/third_party/raylib/Darwin/libraylib.a new file mode 100644 index 0000000000..dd2e9b33f1 --- /dev/null +++ b/third_party/raylib/Darwin/libraylib.a @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fd045c1d4bca5c9b2ad044ea730826ff6cedeef0b64451b123717b136f1cd702 +size 6392532 diff --git a/third_party/raylib/build.sh b/third_party/raylib/build.sh new file mode 100755 index 0000000000..d20f9d33af --- /dev/null +++ b/third_party/raylib/build.sh @@ -0,0 +1,93 @@ +#!/usr/bin/env bash +set -e + +export SOURCE_DATE_EPOCH=0 +export ZERO_AR_DATE=1 + +SUDO="" + +# Use sudo if not root +if [[ ! $(id -u) -eq 0 ]]; then + if [[ -z $(which sudo) ]]; then + echo "Please install sudo or run as root" + exit 1 + fi + SUDO="sudo" +fi + +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )" +cd $DIR + +RAYLIB_PLATFORM="PLATFORM_DESKTOP" + +ARCHNAME=$(uname -m) +if [ -f /TICI ]; then + ARCHNAME="larch64" + RAYLIB_PLATFORM="PLATFORM_COMMA" +elif [[ "$OSTYPE" == "linux"* ]]; then + # required dependencies on Linux PC + $SUDO apt install \ + libxcursor-dev \ + libxi-dev \ + libxinerama-dev \ + libxrandr-dev +fi + +if [[ "$OSTYPE" == "darwin"* ]]; then + ARCHNAME="Darwin" +fi + +INSTALL_DIR="$DIR/$ARCHNAME" +rm -rf $INSTALL_DIR +mkdir -p $INSTALL_DIR + +INSTALL_H_DIR="$DIR/include" +rm -rf $INSTALL_H_DIR +mkdir -p $INSTALL_H_DIR + +if [ ! -d raylib_repo ]; then + git clone -b master --no-tags https://github.com/commaai/raylib.git raylib_repo +fi + +cd raylib_repo + +COMMIT=${1:-3425bd9d1fb292ede4d80f97a1f4f258f614cffc} +git fetch origin $COMMIT +git reset --hard $COMMIT +git clean -xdff . + +cd src + +make -j$(nproc) PLATFORM=$RAYLIB_PLATFORM RAYLIB_RELEASE_PATH=$INSTALL_DIR +cp raylib.h raymath.h rlgl.h $INSTALL_H_DIR/ +echo "raylib development files installed/updated in $INSTALL_H_DIR" + +# this commit needs to be in line with raylib +set -x +RAYGUI_COMMIT="76b36b597edb70ffaf96f046076adc20d67e7827" +curl -fsSLo $INSTALL_H_DIR/raygui.h https://raw.githubusercontent.com/raysan5/raygui/$RAYGUI_COMMIT/src/raygui.h + +if [ -f /TICI ]; then + + # Building the python bindings + cd $DIR + + if [ ! -d raylib_python_repo ]; then + git clone -b master --no-tags https://github.com/commaai/raylib-python-cffi.git raylib_python_repo + fi + + cd raylib_python_repo + + BINDINGS_COMMIT="a0710d95af3c12fd7f4b639589be9a13dad93cb6" + git fetch origin $BINDINGS_COMMIT + git reset --hard $BINDINGS_COMMIT + git clean -xdff . + + RAYLIB_PLATFORM=$RAYLIB_PLATFORM RAYLIB_INCLUDE_PATH=$INSTALL_H_DIR RAYLIB_LIB_PATH=$INSTALL_DIR python setup.py bdist_wheel + cd $DIR + + rm -rf wheel + mkdir wheel + cp raylib_python_repo/dist/*.whl wheel/ + +fi diff --git a/third_party/raylib/include/raygui.h b/third_party/raylib/include/raygui.h new file mode 100644 index 0000000000..fe233a16cf --- /dev/null +++ b/third_party/raylib/include/raygui.h @@ -0,0 +1,5759 @@ +/******************************************************************************************* +* +* raygui v4.5-dev - A simple and easy-to-use immediate-mode gui library +* +* DESCRIPTION: +* raygui is a tools-dev-focused immediate-mode-gui library based on raylib but also +* available as a standalone library, as long as input and drawing functions are provided. +* +* FEATURES: +* - Immediate-mode gui, minimal retained data +* - +25 controls provided (basic and advanced) +* - Styling system for colors, font and metrics +* - Icons supported, embedded as a 1-bit icons pack +* - Standalone mode option (custom input/graphics backend) +* - Multiple support tools provided for raygui development +* +* POSSIBLE IMPROVEMENTS: +* - Better standalone mode API for easy plug of custom backends +* - Externalize required inputs, allow user easier customization +* +* LIMITATIONS: +* - No editable multi-line word-wraped text box supported +* - No auto-layout mechanism, up to the user to define controls position and size +* - Standalone mode requires library modification and some user work to plug another backend +* +* NOTES: +* - WARNING: GuiLoadStyle() and GuiLoadStyle{Custom}() functions, allocate memory for +* font atlas recs and glyphs, freeing that memory is (usually) up to the user, +* no unload function is explicitly provided... but note that GuiLoadStyleDefault() unloads +* by default any previously loaded font (texture, recs, glyphs). +* - Global UI alpha (guiAlpha) is applied inside GuiDrawRectangle() and GuiDrawText() functions +* +* CONTROLS PROVIDED: +* # Container/separators Controls +* - WindowBox --> StatusBar, Panel +* - GroupBox --> Line +* - Line +* - Panel --> StatusBar +* - ScrollPanel --> StatusBar +* - TabBar --> Button +* +* # Basic Controls +* - Label +* - LabelButton --> Label +* - Button +* - Toggle +* - ToggleGroup --> Toggle +* - ToggleSlider +* - CheckBox +* - ComboBox +* - DropdownBox +* - TextBox +* - ValueBox --> TextBox +* - Spinner --> Button, ValueBox +* - Slider +* - SliderBar --> Slider +* - ProgressBar +* - StatusBar +* - DummyRec +* - Grid +* +* # Advance Controls +* - ListView +* - ColorPicker --> ColorPanel, ColorBarHue +* - MessageBox --> Window, Label, Button +* - TextInputBox --> Window, Label, TextBox, Button +* +* It also provides a set of functions for styling the controls based on its properties (size, color). +* +* +* RAYGUI STYLE (guiStyle): +* raygui uses a global data array for all gui style properties (allocated on data segment by default), +* when a new style is loaded, it is loaded over the global style... but a default gui style could always be +* recovered with GuiLoadStyleDefault() function, that overwrites the current style to the default one +* +* The global style array size is fixed and depends on the number of controls and properties: +* +* static unsigned int guiStyle[RAYGUI_MAX_CONTROLS*(RAYGUI_MAX_PROPS_BASE + RAYGUI_MAX_PROPS_EXTENDED)]; +* +* guiStyle size is by default: 16*(16 + 8) = 384*4 = 1536 bytes = 1.5 KB +* +* Note that the first set of BASE properties (by default guiStyle[0..15]) belong to the generic style +* used for all controls, when any of those base values is set, it is automatically populated to all +* controls, so, specific control values overwriting generic style should be set after base values. +* +* After the first BASE set we have the EXTENDED properties (by default guiStyle[16..23]), those +* properties are actually common to all controls and can not be overwritten individually (like BASE ones) +* Some of those properties are: TEXT_SIZE, TEXT_SPACING, LINE_COLOR, BACKGROUND_COLOR +* +* Custom control properties can be defined using the EXTENDED properties for each independent control. +* +* TOOL: rGuiStyler is a visual tool to customize raygui style: github.com/raysan5/rguistyler +* +* +* RAYGUI ICONS (guiIcons): +* raygui could use a global array containing icons data (allocated on data segment by default), +* a custom icons set could be loaded over this array using GuiLoadIcons(), but loaded icons set +* must be same RAYGUI_ICON_SIZE and no more than RAYGUI_ICON_MAX_ICONS will be loaded +* +* Every icon is codified in binary form, using 1 bit per pixel, so, every 16x16 icon +* requires 8 integers (16*16/32) to be stored in memory. +* +* When the icon is draw, actually one quad per pixel is drawn if the bit for that pixel is set. +* +* The global icons array size is fixed and depends on the number of icons and size: +* +* static unsigned int guiIcons[RAYGUI_ICON_MAX_ICONS*RAYGUI_ICON_DATA_ELEMENTS]; +* +* guiIcons size is by default: 256*(16*16/32) = 2048*4 = 8192 bytes = 8 KB +* +* TOOL: rGuiIcons is a visual tool to customize/create raygui icons: github.com/raysan5/rguiicons +* +* RAYGUI LAYOUT: +* raygui currently does not provide an auto-layout mechanism like other libraries, +* layouts must be defined manually on controls drawing, providing the right bounds Rectangle for it. +* +* TOOL: rGuiLayout is a visual tool to create raygui layouts: github.com/raysan5/rguilayout +* +* CONFIGURATION: +* #define RAYGUI_IMPLEMENTATION +* Generates the implementation of the library into the included file. +* If not defined, the library is in header only mode and can be included in other headers +* or source files without problems. But only ONE file should hold the implementation. +* +* #define RAYGUI_STANDALONE +* Avoid raylib.h header inclusion in this file. Data types defined on raylib are defined +* internally in the library and input management and drawing functions must be provided by +* the user (check library implementation for further details). +* +* #define RAYGUI_NO_ICONS +* Avoid including embedded ricons data (256 icons, 16x16 pixels, 1-bit per pixel, 2KB) +* +* #define RAYGUI_CUSTOM_ICONS +* Includes custom ricons.h header defining a set of custom icons, +* this file can be generated using rGuiIcons tool +* +* #define RAYGUI_DEBUG_RECS_BOUNDS +* Draw control bounds rectangles for debug +* +* #define RAYGUI_DEBUG_TEXT_BOUNDS +* Draw text bounds rectangles for debug +* +* VERSIONS HISTORY: +* 4.5-dev (Sep-2024) Current dev version... +* ADDED: guiControlExclusiveMode and guiControlExclusiveRec for exclusive modes +* ADDED: GuiValueBoxFloat() +* ADDED: GuiDropdonwBox() properties: DROPDOWN_ARROW_HIDDEN, DROPDOWN_ROLL_UP +* ADDED: GuiListView() property: LIST_ITEMS_BORDER_WIDTH +* ADDED: Multiple new icons +* REVIEWED: GuiTabBar(), close tab with mouse middle button +* REVIEWED: GuiScrollPanel(), scroll speed proportional to content +* REVIEWED: GuiDropdownBox(), support roll up and hidden arrow +* REVIEWED: GuiTextBox(), cursor position initialization +* REVIEWED: GuiSliderPro(), control value change check +* REVIEWED: GuiGrid(), simplified implementation +* REVIEWED: GuiIconText(), increase buffer size and reviewed padding +* REVIEWED: GuiDrawText(), improved wrap mode drawing +* REVIEWED: GuiScrollBar(), minor tweaks +* REVIEWED: Functions descriptions, removed wrong return value reference +* REDESIGNED: GuiColorPanel(), improved HSV <-> RGBA convertion +* +* 4.0 (12-Sep-2023) ADDED: GuiToggleSlider() +* ADDED: GuiColorPickerHSV() and GuiColorPanelHSV() +* ADDED: Multiple new icons, mostly compiler related +* ADDED: New DEFAULT properties: TEXT_LINE_SPACING, TEXT_ALIGNMENT_VERTICAL, TEXT_WRAP_MODE +* ADDED: New enum values: GuiTextAlignment, GuiTextAlignmentVertical, GuiTextWrapMode +* ADDED: Support loading styles with custom font charset from external file +* REDESIGNED: GuiTextBox(), support mouse cursor positioning +* REDESIGNED: GuiDrawText(), support multiline and word-wrap modes (read only) +* REDESIGNED: GuiProgressBar() to be more visual, progress affects border color +* REDESIGNED: Global alpha consideration moved to GuiDrawRectangle() and GuiDrawText() +* REDESIGNED: GuiScrollPanel(), get parameters by reference and return result value +* REDESIGNED: GuiToggleGroup(), get parameters by reference and return result value +* REDESIGNED: GuiComboBox(), get parameters by reference and return result value +* REDESIGNED: GuiCheckBox(), get parameters by reference and return result value +* REDESIGNED: GuiSlider(), get parameters by reference and return result value +* REDESIGNED: GuiSliderBar(), get parameters by reference and return result value +* REDESIGNED: GuiProgressBar(), get parameters by reference and return result value +* REDESIGNED: GuiListView(), get parameters by reference and return result value +* REDESIGNED: GuiColorPicker(), get parameters by reference and return result value +* REDESIGNED: GuiColorPanel(), get parameters by reference and return result value +* REDESIGNED: GuiColorBarAlpha(), get parameters by reference and return result value +* REDESIGNED: GuiColorBarHue(), get parameters by reference and return result value +* REDESIGNED: GuiGrid(), get parameters by reference and return result value +* REDESIGNED: GuiGrid(), added extra parameter +* REDESIGNED: GuiListViewEx(), change parameters order +* REDESIGNED: All controls return result as int value +* REVIEWED: GuiScrollPanel() to avoid smallish scroll-bars +* REVIEWED: All examples and specially controls_test_suite +* RENAMED: gui_file_dialog module to gui_window_file_dialog +* UPDATED: All styles to include ISO-8859-15 charset (as much as possible) +* +* 3.6 (10-May-2023) ADDED: New icon: SAND_TIMER +* ADDED: GuiLoadStyleFromMemory() (binary only) +* REVIEWED: GuiScrollBar() horizontal movement key +* REVIEWED: GuiTextBox() crash on cursor movement +* REVIEWED: GuiTextBox(), additional inputs support +* REVIEWED: GuiLabelButton(), avoid text cut +* REVIEWED: GuiTextInputBox(), password input +* REVIEWED: Local GetCodepointNext(), aligned with raylib +* REDESIGNED: GuiSlider*()/GuiScrollBar() to support out-of-bounds +* +* 3.5 (20-Apr-2023) ADDED: GuiTabBar(), based on GuiToggle() +* ADDED: Helper functions to split text in separate lines +* ADDED: Multiple new icons, useful for code editing tools +* REMOVED: Unneeded icon editing functions +* REMOVED: GuiTextBoxMulti(), very limited and broken +* REMOVED: MeasureTextEx() dependency, logic directly implemented +* REMOVED: DrawTextEx() dependency, logic directly implemented +* REVIEWED: GuiScrollBar(), improve mouse-click behaviour +* REVIEWED: Library header info, more info, better organized +* REDESIGNED: GuiTextBox() to support cursor movement +* REDESIGNED: GuiDrawText() to divide drawing by lines +* +* 3.2 (22-May-2022) RENAMED: Some enum values, for unification, avoiding prefixes +* REMOVED: GuiScrollBar(), only internal +* REDESIGNED: GuiPanel() to support text parameter +* REDESIGNED: GuiScrollPanel() to support text parameter +* REDESIGNED: GuiColorPicker() to support text parameter +* REDESIGNED: GuiColorPanel() to support text parameter +* REDESIGNED: GuiColorBarAlpha() to support text parameter +* REDESIGNED: GuiColorBarHue() to support text parameter +* REDESIGNED: GuiTextInputBox() to support password +* +* 3.1 (12-Jan-2022) REVIEWED: Default style for consistency (aligned with rGuiLayout v2.5 tool) +* REVIEWED: GuiLoadStyle() to support compressed font atlas image data and unload previous textures +* REVIEWED: External icons usage logic +* REVIEWED: GuiLine() for centered alignment when including text +* RENAMED: Multiple controls properties definitions to prepend RAYGUI_ +* RENAMED: RICON_ references to RAYGUI_ICON_ for library consistency +* Projects updated and multiple tweaks +* +* 3.0 (04-Nov-2021) Integrated ricons data to avoid external file +* REDESIGNED: GuiTextBoxMulti() +* REMOVED: GuiImageButton*() +* Multiple minor tweaks and bugs corrected +* +* 2.9 (17-Mar-2021) REMOVED: Tooltip API +* 2.8 (03-May-2020) Centralized rectangles drawing to GuiDrawRectangle() +* 2.7 (20-Feb-2020) ADDED: Possible tooltips API +* 2.6 (09-Sep-2019) ADDED: GuiTextInputBox() +* REDESIGNED: GuiListView*(), GuiDropdownBox(), GuiSlider*(), GuiProgressBar(), GuiMessageBox() +* REVIEWED: GuiTextBox(), GuiSpinner(), GuiValueBox(), GuiLoadStyle() +* Replaced property INNER_PADDING by TEXT_PADDING, renamed some properties +* ADDED: 8 new custom styles ready to use +* Multiple minor tweaks and bugs corrected +* +* 2.5 (28-May-2019) Implemented extended GuiTextBox(), GuiValueBox(), GuiSpinner() +* 2.3 (29-Apr-2019) ADDED: rIcons auxiliar library and support for it, multiple controls reviewed +* Refactor all controls drawing mechanism to use control state +* 2.2 (05-Feb-2019) ADDED: GuiScrollBar(), GuiScrollPanel(), reviewed GuiListView(), removed Gui*Ex() controls +* 2.1 (26-Dec-2018) REDESIGNED: GuiCheckBox(), GuiComboBox(), GuiDropdownBox(), GuiToggleGroup() > Use combined text string +* REDESIGNED: Style system (breaking change) +* 2.0 (08-Nov-2018) ADDED: Support controls guiLock and custom fonts +* REVIEWED: GuiComboBox(), GuiListView()... +* 1.9 (09-Oct-2018) REVIEWED: GuiGrid(), GuiTextBox(), GuiTextBoxMulti(), GuiValueBox()... +* 1.8 (01-May-2018) Lot of rework and redesign to align with rGuiStyler and rGuiLayout +* 1.5 (21-Jun-2017) Working in an improved styles system +* 1.4 (15-Jun-2017) Rewritten all GUI functions (removed useless ones) +* 1.3 (12-Jun-2017) Complete redesign of style system +* 1.1 (01-Jun-2017) Complete review of the library +* 1.0 (07-Jun-2016) Converted to header-only by Ramon Santamaria. +* 0.9 (07-Mar-2016) Reviewed and tested by Albert Martos, Ian Eito, Sergio Martinez and Ramon Santamaria. +* 0.8 (27-Aug-2015) Initial release. Implemented by Kevin Gato, Daniel Nicolás and Ramon Santamaria. +* +* DEPENDENCIES: +* raylib 5.0 - Inputs reading (keyboard/mouse), shapes drawing, font loading and text drawing +* +* STANDALONE MODE: +* By default raygui depends on raylib mostly for the inputs and the drawing functionality but that dependency can be disabled +* with the config flag RAYGUI_STANDALONE. In that case is up to the user to provide another backend to cover library needs. +* +* The following functions should be redefined for a custom backend: +* +* - Vector2 GetMousePosition(void); +* - float GetMouseWheelMove(void); +* - bool IsMouseButtonDown(int button); +* - bool IsMouseButtonPressed(int button); +* - bool IsMouseButtonReleased(int button); +* - bool IsKeyDown(int key); +* - bool IsKeyPressed(int key); +* - int GetCharPressed(void); // -- GuiTextBox(), GuiValueBox() +* +* - void DrawRectangle(int x, int y, int width, int height, Color color); // -- GuiDrawRectangle() +* - void DrawRectangleGradientEx(Rectangle rec, Color col1, Color col2, Color col3, Color col4); // -- GuiColorPicker() +* +* - Font GetFontDefault(void); // -- GuiLoadStyleDefault() +* - Font LoadFontEx(const char *fileName, int fontSize, int *codepoints, int codepointCount); // -- GuiLoadStyle() +* - Texture2D LoadTextureFromImage(Image image); // -- GuiLoadStyle(), required to load texture from embedded font atlas image +* - void SetShapesTexture(Texture2D tex, Rectangle rec); // -- GuiLoadStyle(), required to set shapes rec to font white rec (optimization) +* - char *LoadFileText(const char *fileName); // -- GuiLoadStyle(), required to load charset data +* - void UnloadFileText(char *text); // -- GuiLoadStyle(), required to unload charset data +* - const char *GetDirectoryPath(const char *filePath); // -- GuiLoadStyle(), required to find charset/font file from text .rgs +* - int *LoadCodepoints(const char *text, int *count); // -- GuiLoadStyle(), required to load required font codepoints list +* - void UnloadCodepoints(int *codepoints); // -- GuiLoadStyle(), required to unload codepoints list +* - unsigned char *DecompressData(const unsigned char *compData, int compDataSize, int *dataSize); // -- GuiLoadStyle() +* +* CONTRIBUTORS: +* Ramon Santamaria: Supervision, review, redesign, update and maintenance +* Vlad Adrian: Complete rewrite of GuiTextBox() to support extended features (2019) +* Sergio Martinez: Review, testing (2015) and redesign of multiple controls (2018) +* Adria Arranz: Testing and implementation of additional controls (2018) +* Jordi Jorba: Testing and implementation of additional controls (2018) +* Albert Martos: Review and testing of the library (2015) +* Ian Eito: Review and testing of the library (2015) +* Kevin Gato: Initial implementation of basic components (2014) +* Daniel Nicolas: Initial implementation of basic components (2014) +* +* +* LICENSE: zlib/libpng +* +* Copyright (c) 2014-2025 Ramon Santamaria (@raysan5) +* +* This software is provided "as-is", without any express or implied warranty. In no event +* will the authors be held liable for any damages arising from the use of this software. +* +* Permission is granted to anyone to use this software for any purpose, including commercial +* applications, and to alter it and redistribute it freely, subject to the following restrictions: +* +* 1. The origin of this software must not be misrepresented; you must not claim that you +* wrote the original software. If you use this software in a product, an acknowledgment +* in the product documentation would be appreciated but is not required. +* +* 2. Altered source versions must be plainly marked as such, and must not be misrepresented +* as being the original software. +* +* 3. This notice may not be removed or altered from any source distribution. +* +**********************************************************************************************/ + +#ifndef RAYGUI_H +#define RAYGUI_H + +#define RAYGUI_VERSION_MAJOR 4 +#define RAYGUI_VERSION_MINOR 5 +#define RAYGUI_VERSION_PATCH 0 +#define RAYGUI_VERSION "4.5-dev" + +#if !defined(RAYGUI_STANDALONE) + #include "raylib.h" +#endif + +// Function specifiers in case library is build/used as a shared library (Windows) +// NOTE: Microsoft specifiers to tell compiler that symbols are imported/exported from a .dll +#if defined(_WIN32) + #if defined(BUILD_LIBTYPE_SHARED) + #define RAYGUIAPI __declspec(dllexport) // We are building the library as a Win32 shared library (.dll) + #elif defined(USE_LIBTYPE_SHARED) + #define RAYGUIAPI __declspec(dllimport) // We are using the library as a Win32 shared library (.dll) + #endif +#endif + +// Function specifiers definition +#ifndef RAYGUIAPI + #define RAYGUIAPI // Functions defined as 'extern' by default (implicit specifiers) +#endif + +//---------------------------------------------------------------------------------- +// Defines and Macros +//---------------------------------------------------------------------------------- +// Allow custom memory allocators +#ifndef RAYGUI_MALLOC + #define RAYGUI_MALLOC(sz) malloc(sz) +#endif +#ifndef RAYGUI_CALLOC + #define RAYGUI_CALLOC(n,sz) calloc(n,sz) +#endif +#ifndef RAYGUI_FREE + #define RAYGUI_FREE(p) free(p) +#endif + +// Simple log system to avoid printf() calls if required +// NOTE: Avoiding those calls, also avoids const strings memory usage +#define RAYGUI_SUPPORT_LOG_INFO +#if defined(RAYGUI_SUPPORT_LOG_INFO) + #define RAYGUI_LOG(...) printf(__VA_ARGS__) +#else + #define RAYGUI_LOG(...) +#endif + +//---------------------------------------------------------------------------------- +// Types and Structures Definition +// NOTE: Some types are required for RAYGUI_STANDALONE usage +//---------------------------------------------------------------------------------- +#if defined(RAYGUI_STANDALONE) + #ifndef __cplusplus + // Boolean type + #ifndef true + typedef enum { false, true } bool; + #endif + #endif + + // Vector2 type + typedef struct Vector2 { + float x; + float y; + } Vector2; + + // Vector3 type // -- ConvertHSVtoRGB(), ConvertRGBtoHSV() + typedef struct Vector3 { + float x; + float y; + float z; + } Vector3; + + // Color type, RGBA (32bit) + typedef struct Color { + unsigned char r; + unsigned char g; + unsigned char b; + unsigned char a; + } Color; + + // Rectangle type + typedef struct Rectangle { + float x; + float y; + float width; + float height; + } Rectangle; + + // TODO: Texture2D type is very coupled to raylib, required by Font type + // It should be redesigned to be provided by user + typedef struct Texture2D { + unsigned int id; // OpenGL texture id + int width; // Texture base width + int height; // Texture base height + int mipmaps; // Mipmap levels, 1 by default + int format; // Data format (PixelFormat type) + } Texture2D; + + // Image, pixel data stored in CPU memory (RAM) + typedef struct Image { + void *data; // Image raw data + int width; // Image base width + int height; // Image base height + int mipmaps; // Mipmap levels, 1 by default + int format; // Data format (PixelFormat type) + } Image; + + // GlyphInfo, font characters glyphs info + typedef struct GlyphInfo { + int value; // Character value (Unicode) + int offsetX; // Character offset X when drawing + int offsetY; // Character offset Y when drawing + int advanceX; // Character advance position X + Image image; // Character image data + } GlyphInfo; + + // TODO: Font type is very coupled to raylib, mostly required by GuiLoadStyle() + // It should be redesigned to be provided by user + typedef struct Font { + int baseSize; // Base size (default chars height) + int glyphCount; // Number of glyph characters + int glyphPadding; // Padding around the glyph characters + Texture2D texture; // Texture atlas containing the glyphs + Rectangle *recs; // Rectangles in texture for the glyphs + GlyphInfo *glyphs; // Glyphs info data + } Font; +#endif + +// Style property +// NOTE: Used when exporting style as code for convenience +typedef struct GuiStyleProp { + unsigned short controlId; // Control identifier + unsigned short propertyId; // Property identifier + int propertyValue; // Property value +} GuiStyleProp; + +/* +// Controls text style -NOT USED- +// NOTE: Text style is defined by control +typedef struct GuiTextStyle { + unsigned int size; + int charSpacing; + int lineSpacing; + int alignmentH; + int alignmentV; + int padding; +} GuiTextStyle; +*/ + +// Gui control state +typedef enum { + STATE_NORMAL = 0, + STATE_FOCUSED, + STATE_PRESSED, + STATE_DISABLED +} GuiState; + +// Gui control text alignment +typedef enum { + TEXT_ALIGN_LEFT = 0, + TEXT_ALIGN_CENTER, + TEXT_ALIGN_RIGHT +} GuiTextAlignment; + +// Gui control text alignment vertical +// NOTE: Text vertical position inside the text bounds +typedef enum { + TEXT_ALIGN_TOP = 0, + TEXT_ALIGN_MIDDLE, + TEXT_ALIGN_BOTTOM +} GuiTextAlignmentVertical; + +// Gui control text wrap mode +// NOTE: Useful for multiline text +typedef enum { + TEXT_WRAP_NONE = 0, + TEXT_WRAP_CHAR, + TEXT_WRAP_WORD +} GuiTextWrapMode; + +// Gui controls +typedef enum { + // Default -> populates to all controls when set + DEFAULT = 0, + + // Basic controls + LABEL, // Used also for: LABELBUTTON + BUTTON, + TOGGLE, // Used also for: TOGGLEGROUP + SLIDER, // Used also for: SLIDERBAR, TOGGLESLIDER + PROGRESSBAR, + CHECKBOX, + COMBOBOX, + DROPDOWNBOX, + TEXTBOX, // Used also for: TEXTBOXMULTI + VALUEBOX, + SPINNER, // Uses: BUTTON, VALUEBOX + LISTVIEW, + COLORPICKER, + SCROLLBAR, + STATUSBAR +} GuiControl; + +// Gui base properties for every control +// NOTE: RAYGUI_MAX_PROPS_BASE properties (by default 16 properties) +typedef enum { + BORDER_COLOR_NORMAL = 0, // Control border color in STATE_NORMAL + BASE_COLOR_NORMAL, // Control base color in STATE_NORMAL + TEXT_COLOR_NORMAL, // Control text color in STATE_NORMAL + BORDER_COLOR_FOCUSED, // Control border color in STATE_FOCUSED + BASE_COLOR_FOCUSED, // Control base color in STATE_FOCUSED + TEXT_COLOR_FOCUSED, // Control text color in STATE_FOCUSED + BORDER_COLOR_PRESSED, // Control border color in STATE_PRESSED + BASE_COLOR_PRESSED, // Control base color in STATE_PRESSED + TEXT_COLOR_PRESSED, // Control text color in STATE_PRESSED + BORDER_COLOR_DISABLED, // Control border color in STATE_DISABLED + BASE_COLOR_DISABLED, // Control base color in STATE_DISABLED + TEXT_COLOR_DISABLED, // Control text color in STATE_DISABLED + BORDER_WIDTH, // Control border size, 0 for no border + //TEXT_SIZE, // Control text size (glyphs max height) -> GLOBAL for all controls + //TEXT_SPACING, // Control text spacing between glyphs -> GLOBAL for all controls + //TEXT_LINE_SPACING // Control text spacing between lines -> GLOBAL for all controls + TEXT_PADDING, // Control text padding, not considering border + TEXT_ALIGNMENT, // Control text horizontal alignment inside control text bound (after border and padding) + //TEXT_WRAP_MODE // Control text wrap-mode inside text bounds -> GLOBAL for all controls +} GuiControlProperty; + +// TODO: Which text styling properties should be global or per-control? +// At this moment TEXT_PADDING and TEXT_ALIGNMENT is configured and saved per control while +// TEXT_SIZE, TEXT_SPACING, TEXT_LINE_SPACING, TEXT_ALIGNMENT_VERTICAL, TEXT_WRAP_MODE are global and +// should be configured by user as needed while defining the UI layout + +// Gui extended properties depend on control +// NOTE: RAYGUI_MAX_PROPS_EXTENDED properties (by default, max 8 properties) +//---------------------------------------------------------------------------------- +// DEFAULT extended properties +// NOTE: Those properties are common to all controls or global +// WARNING: We only have 8 slots for those properties by default!!! -> New global control: TEXT? +typedef enum { + TEXT_SIZE = 16, // Text size (glyphs max height) + TEXT_SPACING, // Text spacing between glyphs + LINE_COLOR, // Line control color + BACKGROUND_COLOR, // Background color + TEXT_LINE_SPACING, // Text spacing between lines + TEXT_ALIGNMENT_VERTICAL, // Text vertical alignment inside text bounds (after border and padding) + TEXT_WRAP_MODE // Text wrap-mode inside text bounds + //TEXT_DECORATION // Text decoration: 0-None, 1-Underline, 2-Line-through, 3-Overline + //TEXT_DECORATION_THICK // Text decoration line thickness +} GuiDefaultProperty; + +// Other possible text properties: +// TEXT_WEIGHT // Normal, Italic, Bold -> Requires specific font change +// TEXT_INDENT // Text indentation -> Now using TEXT_PADDING... + +// Label +//typedef enum { } GuiLabelProperty; + +// Button/Spinner +//typedef enum { } GuiButtonProperty; + +// Toggle/ToggleGroup +typedef enum { + GROUP_PADDING = 16, // ToggleGroup separation between toggles +} GuiToggleProperty; + +// Slider/SliderBar +typedef enum { + SLIDER_WIDTH = 16, // Slider size of internal bar + SLIDER_PADDING // Slider/SliderBar internal bar padding +} GuiSliderProperty; + +// ProgressBar +typedef enum { + PROGRESS_PADDING = 16, // ProgressBar internal padding +} GuiProgressBarProperty; + +// ScrollBar +typedef enum { + ARROWS_SIZE = 16, // ScrollBar arrows size + ARROWS_VISIBLE, // ScrollBar arrows visible + SCROLL_SLIDER_PADDING, // ScrollBar slider internal padding + SCROLL_SLIDER_SIZE, // ScrollBar slider size + SCROLL_PADDING, // ScrollBar scroll padding from arrows + SCROLL_SPEED, // ScrollBar scrolling speed +} GuiScrollBarProperty; + +// CheckBox +typedef enum { + CHECK_PADDING = 16 // CheckBox internal check padding +} GuiCheckBoxProperty; + +// ComboBox +typedef enum { + COMBO_BUTTON_WIDTH = 16, // ComboBox right button width + COMBO_BUTTON_SPACING // ComboBox button separation +} GuiComboBoxProperty; + +// DropdownBox +typedef enum { + ARROW_PADDING = 16, // DropdownBox arrow separation from border and items + DROPDOWN_ITEMS_SPACING, // DropdownBox items separation + DROPDOWN_ARROW_HIDDEN, // DropdownBox arrow hidden + DROPDOWN_ROLL_UP // DropdownBox roll up flag (default rolls down) +} GuiDropdownBoxProperty; + +// TextBox/TextBoxMulti/ValueBox/Spinner +typedef enum { + TEXT_READONLY = 16, // TextBox in read-only mode: 0-text editable, 1-text no-editable +} GuiTextBoxProperty; + +// Spinner +typedef enum { + SPIN_BUTTON_WIDTH = 16, // Spinner left/right buttons width + SPIN_BUTTON_SPACING, // Spinner buttons separation +} GuiSpinnerProperty; + +// ListView +typedef enum { + LIST_ITEMS_HEIGHT = 16, // ListView items height + LIST_ITEMS_SPACING, // ListView items separation + SCROLLBAR_WIDTH, // ListView scrollbar size (usually width) + SCROLLBAR_SIDE, // ListView scrollbar side (0-SCROLLBAR_LEFT_SIDE, 1-SCROLLBAR_RIGHT_SIDE) + LIST_ITEMS_BORDER_WIDTH // ListView items border width +} GuiListViewProperty; + +// ColorPicker +typedef enum { + COLOR_SELECTOR_SIZE = 16, + HUEBAR_WIDTH, // ColorPicker right hue bar width + HUEBAR_PADDING, // ColorPicker right hue bar separation from panel + HUEBAR_SELECTOR_HEIGHT, // ColorPicker right hue bar selector height + HUEBAR_SELECTOR_OVERFLOW // ColorPicker right hue bar selector overflow +} GuiColorPickerProperty; + +#define SCROLLBAR_LEFT_SIDE 0 +#define SCROLLBAR_RIGHT_SIDE 1 + +//---------------------------------------------------------------------------------- +// Global Variables Definition +//---------------------------------------------------------------------------------- +// ... + +//---------------------------------------------------------------------------------- +// Module Functions Declaration +//---------------------------------------------------------------------------------- + +#if defined(__cplusplus) +extern "C" { // Prevents name mangling of functions +#endif + +// Global gui state control functions +RAYGUIAPI void GuiEnable(void); // Enable gui controls (global state) +RAYGUIAPI void GuiDisable(void); // Disable gui controls (global state) +RAYGUIAPI void GuiLock(void); // Lock gui controls (global state) +RAYGUIAPI void GuiUnlock(void); // Unlock gui controls (global state) +RAYGUIAPI bool GuiIsLocked(void); // Check if gui is locked (global state) +RAYGUIAPI void GuiSetAlpha(float alpha); // Set gui controls alpha (global state), alpha goes from 0.0f to 1.0f +RAYGUIAPI void GuiSetState(int state); // Set gui state (global state) +RAYGUIAPI int GuiGetState(void); // Get gui state (global state) + +// Font set/get functions +RAYGUIAPI void GuiSetFont(Font font); // Set gui custom font (global state) +RAYGUIAPI Font GuiGetFont(void); // Get gui custom font (global state) + +// Style set/get functions +RAYGUIAPI void GuiSetStyle(int control, int property, int value); // Set one style property +RAYGUIAPI int GuiGetStyle(int control, int property); // Get one style property + +// Styles loading functions +RAYGUIAPI void GuiLoadStyle(const char *fileName); // Load style file over global style variable (.rgs) +RAYGUIAPI void GuiLoadStyleDefault(void); // Load style default over global style + +// Tooltips management functions +RAYGUIAPI void GuiEnableTooltip(void); // Enable gui tooltips (global state) +RAYGUIAPI void GuiDisableTooltip(void); // Disable gui tooltips (global state) +RAYGUIAPI void GuiSetTooltip(const char *tooltip); // Set tooltip string + +// Icons functionality +RAYGUIAPI const char *GuiIconText(int iconId, const char *text); // Get text with icon id prepended (if supported) +#if !defined(RAYGUI_NO_ICONS) +RAYGUIAPI void GuiSetIconScale(int scale); // Set default icon drawing size +RAYGUIAPI unsigned int *GuiGetIcons(void); // Get raygui icons data pointer +RAYGUIAPI char **GuiLoadIcons(const char *fileName, bool loadIconsName); // Load raygui icons file (.rgi) into internal icons data +RAYGUIAPI void GuiDrawIcon(int iconId, int posX, int posY, int pixelSize, Color color); // Draw icon using pixel size at specified position +#endif + +// Controls +//---------------------------------------------------------------------------------------------------------- +// Container/separator controls, useful for controls organization +RAYGUIAPI int GuiWindowBox(Rectangle bounds, const char *title); // Window Box control, shows a window that can be closed +RAYGUIAPI int GuiGroupBox(Rectangle bounds, const char *text); // Group Box control with text name +RAYGUIAPI int GuiLine(Rectangle bounds, const char *text); // Line separator control, could contain text +RAYGUIAPI int GuiPanel(Rectangle bounds, const char *text); // Panel control, useful to group controls +RAYGUIAPI int GuiTabBar(Rectangle bounds, const char **text, int count, int *active); // Tab Bar control, returns TAB to be closed or -1 +RAYGUIAPI int GuiScrollPanel(Rectangle bounds, const char *text, Rectangle content, Vector2 *scroll, Rectangle *view); // Scroll Panel control + +// Basic controls set +RAYGUIAPI int GuiLabel(Rectangle bounds, const char *text); // Label control +RAYGUIAPI int GuiButton(Rectangle bounds, const char *text); // Button control, returns true when clicked +RAYGUIAPI int GuiLabelButton(Rectangle bounds, const char *text); // Label button control, returns true when clicked +RAYGUIAPI int GuiToggle(Rectangle bounds, const char *text, bool *active); // Toggle Button control +RAYGUIAPI int GuiToggleGroup(Rectangle bounds, const char *text, int *active); // Toggle Group control +RAYGUIAPI int GuiToggleSlider(Rectangle bounds, const char *text, int *active); // Toggle Slider control +RAYGUIAPI int GuiCheckBox(Rectangle bounds, const char *text, bool *checked); // Check Box control, returns true when active +RAYGUIAPI int GuiComboBox(Rectangle bounds, const char *text, int *active); // Combo Box control + +RAYGUIAPI int GuiDropdownBox(Rectangle bounds, const char *text, int *active, bool editMode); // Dropdown Box control +RAYGUIAPI int GuiSpinner(Rectangle bounds, const char *text, int *value, int minValue, int maxValue, bool editMode); // Spinner control +RAYGUIAPI int GuiValueBox(Rectangle bounds, const char *text, int *value, int minValue, int maxValue, bool editMode); // Value Box control, updates input text with numbers +RAYGUIAPI int GuiValueBoxFloat(Rectangle bounds, const char *text, char *textValue, float *value, bool editMode); // Value box control for float values +RAYGUIAPI int GuiTextBox(Rectangle bounds, char *text, int textSize, bool editMode); // Text Box control, updates input text + +RAYGUIAPI int GuiSlider(Rectangle bounds, const char *textLeft, const char *textRight, float *value, float minValue, float maxValue); // Slider control +RAYGUIAPI int GuiSliderBar(Rectangle bounds, const char *textLeft, const char *textRight, float *value, float minValue, float maxValue); // Slider Bar control +RAYGUIAPI int GuiProgressBar(Rectangle bounds, const char *textLeft, const char *textRight, float *value, float minValue, float maxValue); // Progress Bar control +RAYGUIAPI int GuiStatusBar(Rectangle bounds, const char *text); // Status Bar control, shows info text +RAYGUIAPI int GuiDummyRec(Rectangle bounds, const char *text); // Dummy control for placeholders +RAYGUIAPI int GuiGrid(Rectangle bounds, const char *text, float spacing, int subdivs, Vector2 *mouseCell); // Grid control + +// Advance controls set +RAYGUIAPI int GuiListView(Rectangle bounds, const char *text, int *scrollIndex, int *active); // List View control +RAYGUIAPI int GuiListViewEx(Rectangle bounds, const char **text, int count, int *scrollIndex, int *active, int *focus); // List View with extended parameters +RAYGUIAPI int GuiMessageBox(Rectangle bounds, const char *title, const char *message, const char *buttons); // Message Box control, displays a message +RAYGUIAPI int GuiTextInputBox(Rectangle bounds, const char *title, const char *message, const char *buttons, char *text, int textMaxSize, bool *secretViewActive); // Text Input Box control, ask for text, supports secret +RAYGUIAPI int GuiColorPicker(Rectangle bounds, const char *text, Color *color); // Color Picker control (multiple color controls) +RAYGUIAPI int GuiColorPanel(Rectangle bounds, const char *text, Color *color); // Color Panel control +RAYGUIAPI int GuiColorBarAlpha(Rectangle bounds, const char *text, float *alpha); // Color Bar Alpha control +RAYGUIAPI int GuiColorBarHue(Rectangle bounds, const char *text, float *value); // Color Bar Hue control +RAYGUIAPI int GuiColorPickerHSV(Rectangle bounds, const char *text, Vector3 *colorHsv); // Color Picker control that avoids conversion to RGB on each call (multiple color controls) +RAYGUIAPI int GuiColorPanelHSV(Rectangle bounds, const char *text, Vector3 *colorHsv); // Color Panel control that updates Hue-Saturation-Value color value, used by GuiColorPickerHSV() +//---------------------------------------------------------------------------------------------------------- + +#if !defined(RAYGUI_NO_ICONS) + +#if !defined(RAYGUI_CUSTOM_ICONS) +//---------------------------------------------------------------------------------- +// Icons enumeration +//---------------------------------------------------------------------------------- +typedef enum { + ICON_NONE = 0, + ICON_FOLDER_FILE_OPEN = 1, + ICON_FILE_SAVE_CLASSIC = 2, + ICON_FOLDER_OPEN = 3, + ICON_FOLDER_SAVE = 4, + ICON_FILE_OPEN = 5, + ICON_FILE_SAVE = 6, + ICON_FILE_EXPORT = 7, + ICON_FILE_ADD = 8, + ICON_FILE_DELETE = 9, + ICON_FILETYPE_TEXT = 10, + ICON_FILETYPE_AUDIO = 11, + ICON_FILETYPE_IMAGE = 12, + ICON_FILETYPE_PLAY = 13, + ICON_FILETYPE_VIDEO = 14, + ICON_FILETYPE_INFO = 15, + ICON_FILE_COPY = 16, + ICON_FILE_CUT = 17, + ICON_FILE_PASTE = 18, + ICON_CURSOR_HAND = 19, + ICON_CURSOR_POINTER = 20, + ICON_CURSOR_CLASSIC = 21, + ICON_PENCIL = 22, + ICON_PENCIL_BIG = 23, + ICON_BRUSH_CLASSIC = 24, + ICON_BRUSH_PAINTER = 25, + ICON_WATER_DROP = 26, + ICON_COLOR_PICKER = 27, + ICON_RUBBER = 28, + ICON_COLOR_BUCKET = 29, + ICON_TEXT_T = 30, + ICON_TEXT_A = 31, + ICON_SCALE = 32, + ICON_RESIZE = 33, + ICON_FILTER_POINT = 34, + ICON_FILTER_BILINEAR = 35, + ICON_CROP = 36, + ICON_CROP_ALPHA = 37, + ICON_SQUARE_TOGGLE = 38, + ICON_SYMMETRY = 39, + ICON_SYMMETRY_HORIZONTAL = 40, + ICON_SYMMETRY_VERTICAL = 41, + ICON_LENS = 42, + ICON_LENS_BIG = 43, + ICON_EYE_ON = 44, + ICON_EYE_OFF = 45, + ICON_FILTER_TOP = 46, + ICON_FILTER = 47, + ICON_TARGET_POINT = 48, + ICON_TARGET_SMALL = 49, + ICON_TARGET_BIG = 50, + ICON_TARGET_MOVE = 51, + ICON_CURSOR_MOVE = 52, + ICON_CURSOR_SCALE = 53, + ICON_CURSOR_SCALE_RIGHT = 54, + ICON_CURSOR_SCALE_LEFT = 55, + ICON_UNDO = 56, + ICON_REDO = 57, + ICON_REREDO = 58, + ICON_MUTATE = 59, + ICON_ROTATE = 60, + ICON_REPEAT = 61, + ICON_SHUFFLE = 62, + ICON_EMPTYBOX = 63, + ICON_TARGET = 64, + ICON_TARGET_SMALL_FILL = 65, + ICON_TARGET_BIG_FILL = 66, + ICON_TARGET_MOVE_FILL = 67, + ICON_CURSOR_MOVE_FILL = 68, + ICON_CURSOR_SCALE_FILL = 69, + ICON_CURSOR_SCALE_RIGHT_FILL = 70, + ICON_CURSOR_SCALE_LEFT_FILL = 71, + ICON_UNDO_FILL = 72, + ICON_REDO_FILL = 73, + ICON_REREDO_FILL = 74, + ICON_MUTATE_FILL = 75, + ICON_ROTATE_FILL = 76, + ICON_REPEAT_FILL = 77, + ICON_SHUFFLE_FILL = 78, + ICON_EMPTYBOX_SMALL = 79, + ICON_BOX = 80, + ICON_BOX_TOP = 81, + ICON_BOX_TOP_RIGHT = 82, + ICON_BOX_RIGHT = 83, + ICON_BOX_BOTTOM_RIGHT = 84, + ICON_BOX_BOTTOM = 85, + ICON_BOX_BOTTOM_LEFT = 86, + ICON_BOX_LEFT = 87, + ICON_BOX_TOP_LEFT = 88, + ICON_BOX_CENTER = 89, + ICON_BOX_CIRCLE_MASK = 90, + ICON_POT = 91, + ICON_ALPHA_MULTIPLY = 92, + ICON_ALPHA_CLEAR = 93, + ICON_DITHERING = 94, + ICON_MIPMAPS = 95, + ICON_BOX_GRID = 96, + ICON_GRID = 97, + ICON_BOX_CORNERS_SMALL = 98, + ICON_BOX_CORNERS_BIG = 99, + ICON_FOUR_BOXES = 100, + ICON_GRID_FILL = 101, + ICON_BOX_MULTISIZE = 102, + ICON_ZOOM_SMALL = 103, + ICON_ZOOM_MEDIUM = 104, + ICON_ZOOM_BIG = 105, + ICON_ZOOM_ALL = 106, + ICON_ZOOM_CENTER = 107, + ICON_BOX_DOTS_SMALL = 108, + ICON_BOX_DOTS_BIG = 109, + ICON_BOX_CONCENTRIC = 110, + ICON_BOX_GRID_BIG = 111, + ICON_OK_TICK = 112, + ICON_CROSS = 113, + ICON_ARROW_LEFT = 114, + ICON_ARROW_RIGHT = 115, + ICON_ARROW_DOWN = 116, + ICON_ARROW_UP = 117, + ICON_ARROW_LEFT_FILL = 118, + ICON_ARROW_RIGHT_FILL = 119, + ICON_ARROW_DOWN_FILL = 120, + ICON_ARROW_UP_FILL = 121, + ICON_AUDIO = 122, + ICON_FX = 123, + ICON_WAVE = 124, + ICON_WAVE_SINUS = 125, + ICON_WAVE_SQUARE = 126, + ICON_WAVE_TRIANGULAR = 127, + ICON_CROSS_SMALL = 128, + ICON_PLAYER_PREVIOUS = 129, + ICON_PLAYER_PLAY_BACK = 130, + ICON_PLAYER_PLAY = 131, + ICON_PLAYER_PAUSE = 132, + ICON_PLAYER_STOP = 133, + ICON_PLAYER_NEXT = 134, + ICON_PLAYER_RECORD = 135, + ICON_MAGNET = 136, + ICON_LOCK_CLOSE = 137, + ICON_LOCK_OPEN = 138, + ICON_CLOCK = 139, + ICON_TOOLS = 140, + ICON_GEAR = 141, + ICON_GEAR_BIG = 142, + ICON_BIN = 143, + ICON_HAND_POINTER = 144, + ICON_LASER = 145, + ICON_COIN = 146, + ICON_EXPLOSION = 147, + ICON_1UP = 148, + ICON_PLAYER = 149, + ICON_PLAYER_JUMP = 150, + ICON_KEY = 151, + ICON_DEMON = 152, + ICON_TEXT_POPUP = 153, + ICON_GEAR_EX = 154, + ICON_CRACK = 155, + ICON_CRACK_POINTS = 156, + ICON_STAR = 157, + ICON_DOOR = 158, + ICON_EXIT = 159, + ICON_MODE_2D = 160, + ICON_MODE_3D = 161, + ICON_CUBE = 162, + ICON_CUBE_FACE_TOP = 163, + ICON_CUBE_FACE_LEFT = 164, + ICON_CUBE_FACE_FRONT = 165, + ICON_CUBE_FACE_BOTTOM = 166, + ICON_CUBE_FACE_RIGHT = 167, + ICON_CUBE_FACE_BACK = 168, + ICON_CAMERA = 169, + ICON_SPECIAL = 170, + ICON_LINK_NET = 171, + ICON_LINK_BOXES = 172, + ICON_LINK_MULTI = 173, + ICON_LINK = 174, + ICON_LINK_BROKE = 175, + ICON_TEXT_NOTES = 176, + ICON_NOTEBOOK = 177, + ICON_SUITCASE = 178, + ICON_SUITCASE_ZIP = 179, + ICON_MAILBOX = 180, + ICON_MONITOR = 181, + ICON_PRINTER = 182, + ICON_PHOTO_CAMERA = 183, + ICON_PHOTO_CAMERA_FLASH = 184, + ICON_HOUSE = 185, + ICON_HEART = 186, + ICON_CORNER = 187, + ICON_VERTICAL_BARS = 188, + ICON_VERTICAL_BARS_FILL = 189, + ICON_LIFE_BARS = 190, + ICON_INFO = 191, + ICON_CROSSLINE = 192, + ICON_HELP = 193, + ICON_FILETYPE_ALPHA = 194, + ICON_FILETYPE_HOME = 195, + ICON_LAYERS_VISIBLE = 196, + ICON_LAYERS = 197, + ICON_WINDOW = 198, + ICON_HIDPI = 199, + ICON_FILETYPE_BINARY = 200, + ICON_HEX = 201, + ICON_SHIELD = 202, + ICON_FILE_NEW = 203, + ICON_FOLDER_ADD = 204, + ICON_ALARM = 205, + ICON_CPU = 206, + ICON_ROM = 207, + ICON_STEP_OVER = 208, + ICON_STEP_INTO = 209, + ICON_STEP_OUT = 210, + ICON_RESTART = 211, + ICON_BREAKPOINT_ON = 212, + ICON_BREAKPOINT_OFF = 213, + ICON_BURGER_MENU = 214, + ICON_CASE_SENSITIVE = 215, + ICON_REG_EXP = 216, + ICON_FOLDER = 217, + ICON_FILE = 218, + ICON_SAND_TIMER = 219, + ICON_WARNING = 220, + ICON_HELP_BOX = 221, + ICON_INFO_BOX = 222, + ICON_PRIORITY = 223, + ICON_LAYERS_ISO = 224, + ICON_LAYERS2 = 225, + ICON_MLAYERS = 226, + ICON_MAPS = 227, + ICON_HOT = 228, + ICON_229 = 229, + ICON_230 = 230, + ICON_231 = 231, + ICON_232 = 232, + ICON_233 = 233, + ICON_234 = 234, + ICON_235 = 235, + ICON_236 = 236, + ICON_237 = 237, + ICON_238 = 238, + ICON_239 = 239, + ICON_240 = 240, + ICON_241 = 241, + ICON_242 = 242, + ICON_243 = 243, + ICON_244 = 244, + ICON_245 = 245, + ICON_246 = 246, + ICON_247 = 247, + ICON_248 = 248, + ICON_249 = 249, + ICON_250 = 250, + ICON_251 = 251, + ICON_252 = 252, + ICON_253 = 253, + ICON_254 = 254, + ICON_255 = 255, +} GuiIconName; +#endif + +#endif + +#if defined(__cplusplus) +} // Prevents name mangling of functions +#endif + +#endif // RAYGUI_H + +/*********************************************************************************** +* +* RAYGUI IMPLEMENTATION +* +************************************************************************************/ + +#if defined(RAYGUI_IMPLEMENTATION) + +#include // required for: isspace() [GuiTextBox()] +#include // Required for: FILE, fopen(), fclose(), fprintf(), feof(), fscanf(), vsprintf() [GuiLoadStyle(), GuiLoadIcons()] +#include // Required for: malloc(), calloc(), free() [GuiLoadStyle(), GuiLoadIcons()] +#include // Required for: strlen() [GuiTextBox(), GuiValueBox()], memset(), memcpy() +#include // Required for: va_list, va_start(), vfprintf(), va_end() [TextFormat()] +#include // Required for: roundf() [GuiColorPicker()] + +#ifdef __cplusplus + #define RAYGUI_CLITERAL(name) name +#else + #define RAYGUI_CLITERAL(name) (name) +#endif + +// Check if two rectangles are equal, used to validate a slider bounds as an id +#ifndef CHECK_BOUNDS_ID + #define CHECK_BOUNDS_ID(src, dst) ((src.x == dst.x) && (src.y == dst.y) && (src.width == dst.width) && (src.height == dst.height)) +#endif + +#if !defined(RAYGUI_NO_ICONS) && !defined(RAYGUI_CUSTOM_ICONS) + +// Embedded icons, no external file provided +#define RAYGUI_ICON_SIZE 16 // Size of icons in pixels (squared) +#define RAYGUI_ICON_MAX_ICONS 256 // Maximum number of icons +#define RAYGUI_ICON_MAX_NAME_LENGTH 32 // Maximum length of icon name id + +// Icons data is defined by bit array (every bit represents one pixel) +// Those arrays are stored as unsigned int data arrays, so, +// every array element defines 32 pixels (bits) of information +// One icon is defined by 8 int, (8 int * 32 bit = 256 bit = 16*16 pixels) +// NOTE: Number of elemens depend on RAYGUI_ICON_SIZE (by default 16x16 pixels) +#define RAYGUI_ICON_DATA_ELEMENTS (RAYGUI_ICON_SIZE*RAYGUI_ICON_SIZE/32) + +//---------------------------------------------------------------------------------- +// Icons data for all gui possible icons (allocated on data segment by default) +// +// NOTE 1: Every icon is codified in binary form, using 1 bit per pixel, so, +// every 16x16 icon requires 8 integers (16*16/32) to be stored +// +// NOTE 2: A different icon set could be loaded over this array using GuiLoadIcons(), +// but loaded icons set must be same RAYGUI_ICON_SIZE and no more than RAYGUI_ICON_MAX_ICONS +// +// guiIcons size is by default: 256*(16*16/32) = 2048*4 = 8192 bytes = 8 KB +//---------------------------------------------------------------------------------- +static unsigned int guiIcons[RAYGUI_ICON_MAX_ICONS*RAYGUI_ICON_DATA_ELEMENTS] = { + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_NONE + 0x3ff80000, 0x2f082008, 0x2042207e, 0x40027fc2, 0x40024002, 0x40024002, 0x40024002, 0x00007ffe, // ICON_FOLDER_FILE_OPEN + 0x3ffe0000, 0x44226422, 0x400247e2, 0x5ffa4002, 0x57ea500a, 0x500a500a, 0x40025ffa, 0x00007ffe, // ICON_FILE_SAVE_CLASSIC + 0x00000000, 0x0042007e, 0x40027fc2, 0x40024002, 0x41024002, 0x44424282, 0x793e4102, 0x00000100, // ICON_FOLDER_OPEN + 0x00000000, 0x0042007e, 0x40027fc2, 0x40024002, 0x41024102, 0x44424102, 0x793e4282, 0x00000000, // ICON_FOLDER_SAVE + 0x3ff00000, 0x201c2010, 0x20042004, 0x21042004, 0x24442284, 0x21042104, 0x20042104, 0x00003ffc, // ICON_FILE_OPEN + 0x3ff00000, 0x201c2010, 0x20042004, 0x21042004, 0x21042104, 0x22842444, 0x20042104, 0x00003ffc, // ICON_FILE_SAVE + 0x3ff00000, 0x201c2010, 0x00042004, 0x20041004, 0x20844784, 0x00841384, 0x20042784, 0x00003ffc, // ICON_FILE_EXPORT + 0x3ff00000, 0x201c2010, 0x20042004, 0x20042004, 0x22042204, 0x22042f84, 0x20042204, 0x00003ffc, // ICON_FILE_ADD + 0x3ff00000, 0x201c2010, 0x20042004, 0x20042004, 0x25042884, 0x25042204, 0x20042884, 0x00003ffc, // ICON_FILE_DELETE + 0x3ff00000, 0x201c2010, 0x20042004, 0x20042ff4, 0x20042ff4, 0x20042ff4, 0x20042004, 0x00003ffc, // ICON_FILETYPE_TEXT + 0x3ff00000, 0x201c2010, 0x27042004, 0x244424c4, 0x26442444, 0x20642664, 0x20042004, 0x00003ffc, // ICON_FILETYPE_AUDIO + 0x3ff00000, 0x201c2010, 0x26042604, 0x20042004, 0x35442884, 0x2414222c, 0x20042004, 0x00003ffc, // ICON_FILETYPE_IMAGE + 0x3ff00000, 0x201c2010, 0x20c42004, 0x22442144, 0x22442444, 0x20c42144, 0x20042004, 0x00003ffc, // ICON_FILETYPE_PLAY + 0x3ff00000, 0x3ffc2ff0, 0x3f3c2ff4, 0x3dbc2eb4, 0x3dbc2bb4, 0x3f3c2eb4, 0x3ffc2ff4, 0x00002ff4, // ICON_FILETYPE_VIDEO + 0x3ff00000, 0x201c2010, 0x21842184, 0x21842004, 0x21842184, 0x21842184, 0x20042184, 0x00003ffc, // ICON_FILETYPE_INFO + 0x0ff00000, 0x381c0810, 0x28042804, 0x28042804, 0x28042804, 0x28042804, 0x20102ffc, 0x00003ff0, // ICON_FILE_COPY + 0x00000000, 0x701c0000, 0x079c1e14, 0x55a000f0, 0x079c00f0, 0x701c1e14, 0x00000000, 0x00000000, // ICON_FILE_CUT + 0x01c00000, 0x13e41bec, 0x3f841004, 0x204420c4, 0x20442044, 0x20442044, 0x207c2044, 0x00003fc0, // ICON_FILE_PASTE + 0x00000000, 0x3aa00fe0, 0x2abc2aa0, 0x2aa42aa4, 0x20042aa4, 0x20042004, 0x3ffc2004, 0x00000000, // ICON_CURSOR_HAND + 0x00000000, 0x003c000c, 0x030800c8, 0x30100c10, 0x10202020, 0x04400840, 0x01800280, 0x00000000, // ICON_CURSOR_POINTER + 0x00000000, 0x00180000, 0x01f00078, 0x03e007f0, 0x07c003e0, 0x04000e40, 0x00000000, 0x00000000, // ICON_CURSOR_CLASSIC + 0x00000000, 0x04000000, 0x11000a00, 0x04400a80, 0x01100220, 0x00580088, 0x00000038, 0x00000000, // ICON_PENCIL + 0x04000000, 0x15000a00, 0x50402880, 0x14102820, 0x05040a08, 0x015c028c, 0x007c00bc, 0x00000000, // ICON_PENCIL_BIG + 0x01c00000, 0x01400140, 0x01400140, 0x0ff80140, 0x0ff80808, 0x0aa80808, 0x0aa80aa8, 0x00000ff8, // ICON_BRUSH_CLASSIC + 0x1ffc0000, 0x5ffc7ffe, 0x40004000, 0x00807f80, 0x01c001c0, 0x01c001c0, 0x01c001c0, 0x00000080, // ICON_BRUSH_PAINTER + 0x00000000, 0x00800000, 0x01c00080, 0x03e001c0, 0x07f003e0, 0x036006f0, 0x000001c0, 0x00000000, // ICON_WATER_DROP + 0x00000000, 0x3e003800, 0x1f803f80, 0x0c201e40, 0x02080c10, 0x00840104, 0x00380044, 0x00000000, // ICON_COLOR_PICKER + 0x00000000, 0x07800300, 0x1fe00fc0, 0x3f883fd0, 0x0e021f04, 0x02040402, 0x00f00108, 0x00000000, // ICON_RUBBER + 0x00c00000, 0x02800140, 0x08200440, 0x20081010, 0x2ffe3004, 0x03f807fc, 0x00e001f0, 0x00000040, // ICON_COLOR_BUCKET + 0x00000000, 0x21843ffc, 0x01800180, 0x01800180, 0x01800180, 0x01800180, 0x03c00180, 0x00000000, // ICON_TEXT_T + 0x00800000, 0x01400180, 0x06200340, 0x0c100620, 0x1ff80c10, 0x380c1808, 0x70067004, 0x0000f80f, // ICON_TEXT_A + 0x78000000, 0x50004000, 0x00004800, 0x03c003c0, 0x03c003c0, 0x00100000, 0x0002000a, 0x0000000e, // ICON_SCALE + 0x75560000, 0x5e004002, 0x54001002, 0x41001202, 0x408200fe, 0x40820082, 0x40820082, 0x00006afe, // ICON_RESIZE + 0x00000000, 0x3f003f00, 0x3f003f00, 0x3f003f00, 0x00400080, 0x001c0020, 0x001c001c, 0x00000000, // ICON_FILTER_POINT + 0x6d800000, 0x00004080, 0x40804080, 0x40800000, 0x00406d80, 0x001c0020, 0x001c001c, 0x00000000, // ICON_FILTER_BILINEAR + 0x40080000, 0x1ffe2008, 0x14081008, 0x11081208, 0x10481088, 0x10081028, 0x10047ff8, 0x00001002, // ICON_CROP + 0x00100000, 0x3ffc0010, 0x2ab03550, 0x22b02550, 0x20b02150, 0x20302050, 0x2000fff0, 0x00002000, // ICON_CROP_ALPHA + 0x40000000, 0x1ff82000, 0x04082808, 0x01082208, 0x00482088, 0x00182028, 0x35542008, 0x00000002, // ICON_SQUARE_TOGGLE + 0x00000000, 0x02800280, 0x06c006c0, 0x0ea00ee0, 0x1e901eb0, 0x3e883e98, 0x7efc7e8c, 0x00000000, // ICON_SYMMETRY + 0x01000000, 0x05600100, 0x1d480d50, 0x7d423d44, 0x3d447d42, 0x0d501d48, 0x01000560, 0x00000100, // ICON_SYMMETRY_HORIZONTAL + 0x01800000, 0x04200240, 0x10080810, 0x00001ff8, 0x00007ffe, 0x0ff01ff8, 0x03c007e0, 0x00000180, // ICON_SYMMETRY_VERTICAL + 0x00000000, 0x010800f0, 0x02040204, 0x02040204, 0x07f00308, 0x1c000e00, 0x30003800, 0x00000000, // ICON_LENS + 0x00000000, 0x061803f0, 0x08240c0c, 0x08040814, 0x0c0c0804, 0x23f01618, 0x18002400, 0x00000000, // ICON_LENS_BIG + 0x00000000, 0x00000000, 0x1c7007c0, 0x638e3398, 0x1c703398, 0x000007c0, 0x00000000, 0x00000000, // ICON_EYE_ON + 0x00000000, 0x10002000, 0x04700fc0, 0x610e3218, 0x1c703098, 0x001007a0, 0x00000008, 0x00000000, // ICON_EYE_OFF + 0x00000000, 0x00007ffc, 0x40047ffc, 0x10102008, 0x04400820, 0x02800280, 0x02800280, 0x00000100, // ICON_FILTER_TOP + 0x00000000, 0x40027ffe, 0x10082004, 0x04200810, 0x02400240, 0x02400240, 0x01400240, 0x000000c0, // ICON_FILTER + 0x00800000, 0x00800080, 0x00000080, 0x3c9e0000, 0x00000000, 0x00800080, 0x00800080, 0x00000000, // ICON_TARGET_POINT + 0x00800000, 0x00800080, 0x00800080, 0x3f7e01c0, 0x008001c0, 0x00800080, 0x00800080, 0x00000000, // ICON_TARGET_SMALL + 0x00800000, 0x00800080, 0x03e00080, 0x3e3e0220, 0x03e00220, 0x00800080, 0x00800080, 0x00000000, // ICON_TARGET_BIG + 0x01000000, 0x04400280, 0x01000100, 0x43842008, 0x43849ab2, 0x01002008, 0x04400100, 0x01000280, // ICON_TARGET_MOVE + 0x01000000, 0x04400280, 0x01000100, 0x41042108, 0x41049ff2, 0x01002108, 0x04400100, 0x01000280, // ICON_CURSOR_MOVE + 0x781e0000, 0x500a4002, 0x04204812, 0x00000240, 0x02400000, 0x48120420, 0x4002500a, 0x0000781e, // ICON_CURSOR_SCALE + 0x00000000, 0x20003c00, 0x24002800, 0x01000200, 0x00400080, 0x00140024, 0x003c0004, 0x00000000, // ICON_CURSOR_SCALE_RIGHT + 0x00000000, 0x0004003c, 0x00240014, 0x00800040, 0x02000100, 0x28002400, 0x3c002000, 0x00000000, // ICON_CURSOR_SCALE_LEFT + 0x00000000, 0x00100020, 0x10101fc8, 0x10001020, 0x10001000, 0x10001000, 0x00001fc0, 0x00000000, // ICON_UNDO + 0x00000000, 0x08000400, 0x080813f8, 0x00080408, 0x00080008, 0x00080008, 0x000003f8, 0x00000000, // ICON_REDO + 0x00000000, 0x3ffc0000, 0x20042004, 0x20002000, 0x20402000, 0x3f902020, 0x00400020, 0x00000000, // ICON_REREDO + 0x00000000, 0x3ffc0000, 0x20042004, 0x27fc2004, 0x20202000, 0x3fc82010, 0x00200010, 0x00000000, // ICON_MUTATE + 0x00000000, 0x0ff00000, 0x10081818, 0x11801008, 0x10001180, 0x18101020, 0x00100fc8, 0x00000020, // ICON_ROTATE + 0x00000000, 0x04000200, 0x240429fc, 0x20042204, 0x20442004, 0x3f942024, 0x00400020, 0x00000000, // ICON_REPEAT + 0x00000000, 0x20001000, 0x22104c0e, 0x00801120, 0x11200040, 0x4c0e2210, 0x10002000, 0x00000000, // ICON_SHUFFLE + 0x7ffe0000, 0x50024002, 0x44024802, 0x41024202, 0x40424082, 0x40124022, 0x4002400a, 0x00007ffe, // ICON_EMPTYBOX + 0x00800000, 0x03e00080, 0x08080490, 0x3c9e0808, 0x08080808, 0x03e00490, 0x00800080, 0x00000000, // ICON_TARGET + 0x00800000, 0x00800080, 0x00800080, 0x3ffe01c0, 0x008001c0, 0x00800080, 0x00800080, 0x00000000, // ICON_TARGET_SMALL_FILL + 0x00800000, 0x00800080, 0x03e00080, 0x3ffe03e0, 0x03e003e0, 0x00800080, 0x00800080, 0x00000000, // ICON_TARGET_BIG_FILL + 0x01000000, 0x07c00380, 0x01000100, 0x638c2008, 0x638cfbbe, 0x01002008, 0x07c00100, 0x01000380, // ICON_TARGET_MOVE_FILL + 0x01000000, 0x07c00380, 0x01000100, 0x610c2108, 0x610cfffe, 0x01002108, 0x07c00100, 0x01000380, // ICON_CURSOR_MOVE_FILL + 0x781e0000, 0x6006700e, 0x04204812, 0x00000240, 0x02400000, 0x48120420, 0x700e6006, 0x0000781e, // ICON_CURSOR_SCALE_FILL + 0x00000000, 0x38003c00, 0x24003000, 0x01000200, 0x00400080, 0x000c0024, 0x003c001c, 0x00000000, // ICON_CURSOR_SCALE_RIGHT_FILL + 0x00000000, 0x001c003c, 0x0024000c, 0x00800040, 0x02000100, 0x30002400, 0x3c003800, 0x00000000, // ICON_CURSOR_SCALE_LEFT_FILL + 0x00000000, 0x00300020, 0x10301ff8, 0x10001020, 0x10001000, 0x10001000, 0x00001fc0, 0x00000000, // ICON_UNDO_FILL + 0x00000000, 0x0c000400, 0x0c081ff8, 0x00080408, 0x00080008, 0x00080008, 0x000003f8, 0x00000000, // ICON_REDO_FILL + 0x00000000, 0x3ffc0000, 0x20042004, 0x20002000, 0x20402000, 0x3ff02060, 0x00400060, 0x00000000, // ICON_REREDO_FILL + 0x00000000, 0x3ffc0000, 0x20042004, 0x27fc2004, 0x20202000, 0x3ff82030, 0x00200030, 0x00000000, // ICON_MUTATE_FILL + 0x00000000, 0x0ff00000, 0x10081818, 0x11801008, 0x10001180, 0x18301020, 0x00300ff8, 0x00000020, // ICON_ROTATE_FILL + 0x00000000, 0x06000200, 0x26042ffc, 0x20042204, 0x20442004, 0x3ff42064, 0x00400060, 0x00000000, // ICON_REPEAT_FILL + 0x00000000, 0x30001000, 0x32107c0e, 0x00801120, 0x11200040, 0x7c0e3210, 0x10003000, 0x00000000, // ICON_SHUFFLE_FILL + 0x00000000, 0x30043ffc, 0x24042804, 0x21042204, 0x20442084, 0x20142024, 0x3ffc200c, 0x00000000, // ICON_EMPTYBOX_SMALL + 0x00000000, 0x20043ffc, 0x20042004, 0x20042004, 0x20042004, 0x20042004, 0x3ffc2004, 0x00000000, // ICON_BOX + 0x00000000, 0x23c43ffc, 0x23c423c4, 0x200423c4, 0x20042004, 0x20042004, 0x3ffc2004, 0x00000000, // ICON_BOX_TOP + 0x00000000, 0x3e043ffc, 0x3e043e04, 0x20043e04, 0x20042004, 0x20042004, 0x3ffc2004, 0x00000000, // ICON_BOX_TOP_RIGHT + 0x00000000, 0x20043ffc, 0x20042004, 0x3e043e04, 0x3e043e04, 0x20042004, 0x3ffc2004, 0x00000000, // ICON_BOX_RIGHT + 0x00000000, 0x20043ffc, 0x20042004, 0x20042004, 0x3e042004, 0x3e043e04, 0x3ffc3e04, 0x00000000, // ICON_BOX_BOTTOM_RIGHT + 0x00000000, 0x20043ffc, 0x20042004, 0x20042004, 0x23c42004, 0x23c423c4, 0x3ffc23c4, 0x00000000, // ICON_BOX_BOTTOM + 0x00000000, 0x20043ffc, 0x20042004, 0x20042004, 0x207c2004, 0x207c207c, 0x3ffc207c, 0x00000000, // ICON_BOX_BOTTOM_LEFT + 0x00000000, 0x20043ffc, 0x20042004, 0x207c207c, 0x207c207c, 0x20042004, 0x3ffc2004, 0x00000000, // ICON_BOX_LEFT + 0x00000000, 0x207c3ffc, 0x207c207c, 0x2004207c, 0x20042004, 0x20042004, 0x3ffc2004, 0x00000000, // ICON_BOX_TOP_LEFT + 0x00000000, 0x20043ffc, 0x20042004, 0x23c423c4, 0x23c423c4, 0x20042004, 0x3ffc2004, 0x00000000, // ICON_BOX_CENTER + 0x7ffe0000, 0x40024002, 0x47e24182, 0x4ff247e2, 0x47e24ff2, 0x418247e2, 0x40024002, 0x00007ffe, // ICON_BOX_CIRCLE_MASK + 0x7fff0000, 0x40014001, 0x40014001, 0x49555ddd, 0x4945495d, 0x400149c5, 0x40014001, 0x00007fff, // ICON_POT + 0x7ffe0000, 0x53327332, 0x44ce4cce, 0x41324332, 0x404e40ce, 0x48125432, 0x4006540e, 0x00007ffe, // ICON_ALPHA_MULTIPLY + 0x7ffe0000, 0x53327332, 0x44ce4cce, 0x41324332, 0x5c4e40ce, 0x44124432, 0x40065c0e, 0x00007ffe, // ICON_ALPHA_CLEAR + 0x7ffe0000, 0x42fe417e, 0x42fe417e, 0x42fe417e, 0x42fe417e, 0x42fe417e, 0x42fe417e, 0x00007ffe, // ICON_DITHERING + 0x07fe0000, 0x1ffa0002, 0x7fea000a, 0x402a402a, 0x5b2a512a, 0x5128552a, 0x40205128, 0x00007fe0, // ICON_MIPMAPS + 0x00000000, 0x1ff80000, 0x12481248, 0x12481ff8, 0x1ff81248, 0x12481248, 0x00001ff8, 0x00000000, // ICON_BOX_GRID + 0x12480000, 0x7ffe1248, 0x12481248, 0x12487ffe, 0x7ffe1248, 0x12481248, 0x12487ffe, 0x00001248, // ICON_GRID + 0x00000000, 0x1c380000, 0x1c3817e8, 0x08100810, 0x08100810, 0x17e81c38, 0x00001c38, 0x00000000, // ICON_BOX_CORNERS_SMALL + 0x700e0000, 0x700e5ffa, 0x20042004, 0x20042004, 0x20042004, 0x20042004, 0x5ffa700e, 0x0000700e, // ICON_BOX_CORNERS_BIG + 0x3f7e0000, 0x21422142, 0x21422142, 0x00003f7e, 0x21423f7e, 0x21422142, 0x3f7e2142, 0x00000000, // ICON_FOUR_BOXES + 0x00000000, 0x3bb80000, 0x3bb83bb8, 0x3bb80000, 0x3bb83bb8, 0x3bb80000, 0x3bb83bb8, 0x00000000, // ICON_GRID_FILL + 0x7ffe0000, 0x7ffe7ffe, 0x77fe7000, 0x77fe77fe, 0x777e7700, 0x777e777e, 0x777e777e, 0x0000777e, // ICON_BOX_MULTISIZE + 0x781e0000, 0x40024002, 0x00004002, 0x01800000, 0x00000180, 0x40020000, 0x40024002, 0x0000781e, // ICON_ZOOM_SMALL + 0x781e0000, 0x40024002, 0x00004002, 0x03c003c0, 0x03c003c0, 0x40020000, 0x40024002, 0x0000781e, // ICON_ZOOM_MEDIUM + 0x781e0000, 0x40024002, 0x07e04002, 0x07e007e0, 0x07e007e0, 0x400207e0, 0x40024002, 0x0000781e, // ICON_ZOOM_BIG + 0x781e0000, 0x5ffa4002, 0x1ff85ffa, 0x1ff81ff8, 0x1ff81ff8, 0x5ffa1ff8, 0x40025ffa, 0x0000781e, // ICON_ZOOM_ALL + 0x00000000, 0x2004381c, 0x00002004, 0x00000000, 0x00000000, 0x20040000, 0x381c2004, 0x00000000, // ICON_ZOOM_CENTER + 0x00000000, 0x1db80000, 0x10081008, 0x10080000, 0x00001008, 0x10081008, 0x00001db8, 0x00000000, // ICON_BOX_DOTS_SMALL + 0x35560000, 0x00002002, 0x00002002, 0x00002002, 0x00002002, 0x00002002, 0x35562002, 0x00000000, // ICON_BOX_DOTS_BIG + 0x7ffe0000, 0x40024002, 0x48124ff2, 0x49924812, 0x48124992, 0x4ff24812, 0x40024002, 0x00007ffe, // ICON_BOX_CONCENTRIC + 0x00000000, 0x10841ffc, 0x10841084, 0x1ffc1084, 0x10841084, 0x10841084, 0x00001ffc, 0x00000000, // ICON_BOX_GRID_BIG + 0x00000000, 0x00000000, 0x10000000, 0x04000800, 0x01040200, 0x00500088, 0x00000020, 0x00000000, // ICON_OK_TICK + 0x00000000, 0x10080000, 0x04200810, 0x01800240, 0x02400180, 0x08100420, 0x00001008, 0x00000000, // ICON_CROSS + 0x00000000, 0x02000000, 0x00800100, 0x00200040, 0x00200010, 0x00800040, 0x02000100, 0x00000000, // ICON_ARROW_LEFT + 0x00000000, 0x00400000, 0x01000080, 0x04000200, 0x04000800, 0x01000200, 0x00400080, 0x00000000, // ICON_ARROW_RIGHT + 0x00000000, 0x00000000, 0x00000000, 0x08081004, 0x02200410, 0x00800140, 0x00000000, 0x00000000, // ICON_ARROW_DOWN + 0x00000000, 0x00000000, 0x01400080, 0x04100220, 0x10040808, 0x00000000, 0x00000000, 0x00000000, // ICON_ARROW_UP + 0x00000000, 0x02000000, 0x03800300, 0x03e003c0, 0x03e003f0, 0x038003c0, 0x02000300, 0x00000000, // ICON_ARROW_LEFT_FILL + 0x00000000, 0x00400000, 0x01c000c0, 0x07c003c0, 0x07c00fc0, 0x01c003c0, 0x004000c0, 0x00000000, // ICON_ARROW_RIGHT_FILL + 0x00000000, 0x00000000, 0x00000000, 0x0ff81ffc, 0x03e007f0, 0x008001c0, 0x00000000, 0x00000000, // ICON_ARROW_DOWN_FILL + 0x00000000, 0x00000000, 0x01c00080, 0x07f003e0, 0x1ffc0ff8, 0x00000000, 0x00000000, 0x00000000, // ICON_ARROW_UP_FILL + 0x00000000, 0x18a008c0, 0x32881290, 0x24822686, 0x26862482, 0x12903288, 0x08c018a0, 0x00000000, // ICON_AUDIO + 0x00000000, 0x04800780, 0x004000c0, 0x662000f0, 0x08103c30, 0x130a0e18, 0x0000318e, 0x00000000, // ICON_FX + 0x00000000, 0x00800000, 0x08880888, 0x2aaa0a8a, 0x0a8a2aaa, 0x08880888, 0x00000080, 0x00000000, // ICON_WAVE + 0x00000000, 0x00600000, 0x01080090, 0x02040108, 0x42044204, 0x24022402, 0x00001800, 0x00000000, // ICON_WAVE_SINUS + 0x00000000, 0x07f80000, 0x04080408, 0x04080408, 0x04080408, 0x7c0e0408, 0x00000000, 0x00000000, // ICON_WAVE_SQUARE + 0x00000000, 0x00000000, 0x00a00040, 0x22084110, 0x08021404, 0x00000000, 0x00000000, 0x00000000, // ICON_WAVE_TRIANGULAR + 0x00000000, 0x00000000, 0x04200000, 0x01800240, 0x02400180, 0x00000420, 0x00000000, 0x00000000, // ICON_CROSS_SMALL + 0x00000000, 0x18380000, 0x12281428, 0x10a81128, 0x112810a8, 0x14281228, 0x00001838, 0x00000000, // ICON_PLAYER_PREVIOUS + 0x00000000, 0x18000000, 0x11801600, 0x10181060, 0x10601018, 0x16001180, 0x00001800, 0x00000000, // ICON_PLAYER_PLAY_BACK + 0x00000000, 0x00180000, 0x01880068, 0x18080608, 0x06081808, 0x00680188, 0x00000018, 0x00000000, // ICON_PLAYER_PLAY + 0x00000000, 0x1e780000, 0x12481248, 0x12481248, 0x12481248, 0x12481248, 0x00001e78, 0x00000000, // ICON_PLAYER_PAUSE + 0x00000000, 0x1ff80000, 0x10081008, 0x10081008, 0x10081008, 0x10081008, 0x00001ff8, 0x00000000, // ICON_PLAYER_STOP + 0x00000000, 0x1c180000, 0x14481428, 0x15081488, 0x14881508, 0x14281448, 0x00001c18, 0x00000000, // ICON_PLAYER_NEXT + 0x00000000, 0x03c00000, 0x08100420, 0x10081008, 0x10081008, 0x04200810, 0x000003c0, 0x00000000, // ICON_PLAYER_RECORD + 0x00000000, 0x0c3007e0, 0x13c81818, 0x14281668, 0x14281428, 0x1c381c38, 0x08102244, 0x00000000, // ICON_MAGNET + 0x07c00000, 0x08200820, 0x3ff80820, 0x23882008, 0x21082388, 0x20082108, 0x1ff02008, 0x00000000, // ICON_LOCK_CLOSE + 0x07c00000, 0x08000800, 0x3ff80800, 0x23882008, 0x21082388, 0x20082108, 0x1ff02008, 0x00000000, // ICON_LOCK_OPEN + 0x01c00000, 0x0c180770, 0x3086188c, 0x60832082, 0x60034781, 0x30062002, 0x0c18180c, 0x01c00770, // ICON_CLOCK + 0x0a200000, 0x1b201b20, 0x04200e20, 0x04200420, 0x04700420, 0x0e700e70, 0x0e700e70, 0x04200e70, // ICON_TOOLS + 0x01800000, 0x3bdc318c, 0x0ff01ff8, 0x7c3e1e78, 0x1e787c3e, 0x1ff80ff0, 0x318c3bdc, 0x00000180, // ICON_GEAR + 0x01800000, 0x3ffc318c, 0x1c381ff8, 0x781e1818, 0x1818781e, 0x1ff81c38, 0x318c3ffc, 0x00000180, // ICON_GEAR_BIG + 0x00000000, 0x08080ff8, 0x08081ffc, 0x0aa80aa8, 0x0aa80aa8, 0x0aa80aa8, 0x08080aa8, 0x00000ff8, // ICON_BIN + 0x00000000, 0x00000000, 0x20043ffc, 0x08043f84, 0x04040f84, 0x04040784, 0x000007fc, 0x00000000, // ICON_HAND_POINTER + 0x00000000, 0x24400400, 0x00001480, 0x6efe0e00, 0x00000e00, 0x24401480, 0x00000400, 0x00000000, // ICON_LASER + 0x00000000, 0x03c00000, 0x08300460, 0x11181118, 0x11181118, 0x04600830, 0x000003c0, 0x00000000, // ICON_COIN + 0x00000000, 0x10880080, 0x06c00810, 0x366c07e0, 0x07e00240, 0x00001768, 0x04200240, 0x00000000, // ICON_EXPLOSION + 0x00000000, 0x3d280000, 0x2528252c, 0x3d282528, 0x05280528, 0x05e80528, 0x00000000, 0x00000000, // ICON_1UP + 0x01800000, 0x03c003c0, 0x018003c0, 0x0ff007e0, 0x0bd00bd0, 0x0a500bd0, 0x02400240, 0x02400240, // ICON_PLAYER + 0x01800000, 0x03c003c0, 0x118013c0, 0x03c81ff8, 0x07c003c8, 0x04400440, 0x0c080478, 0x00000000, // ICON_PLAYER_JUMP + 0x3ff80000, 0x30183ff8, 0x30183018, 0x3ff83ff8, 0x03000300, 0x03c003c0, 0x03e00300, 0x000003e0, // ICON_KEY + 0x3ff80000, 0x3ff83ff8, 0x33983ff8, 0x3ff83398, 0x3ff83ff8, 0x00000540, 0x0fe00aa0, 0x00000fe0, // ICON_DEMON + 0x00000000, 0x0ff00000, 0x20041008, 0x25442004, 0x10082004, 0x06000bf0, 0x00000300, 0x00000000, // ICON_TEXT_POPUP + 0x00000000, 0x11440000, 0x07f00be8, 0x1c1c0e38, 0x1c1c0c18, 0x07f00e38, 0x11440be8, 0x00000000, // ICON_GEAR_EX + 0x00000000, 0x20080000, 0x0c601010, 0x07c00fe0, 0x07c007c0, 0x0c600fe0, 0x20081010, 0x00000000, // ICON_CRACK + 0x00000000, 0x20080000, 0x0c601010, 0x04400fe0, 0x04405554, 0x0c600fe0, 0x20081010, 0x00000000, // ICON_CRACK_POINTS + 0x00000000, 0x00800080, 0x01c001c0, 0x1ffc3ffe, 0x03e007f0, 0x07f003e0, 0x0c180770, 0x00000808, // ICON_STAR + 0x0ff00000, 0x08180810, 0x08100818, 0x0a100810, 0x08180810, 0x08100818, 0x08100810, 0x00001ff8, // ICON_DOOR + 0x0ff00000, 0x08100810, 0x08100810, 0x10100010, 0x4f902010, 0x10102010, 0x08100010, 0x00000ff0, // ICON_EXIT + 0x00040000, 0x001f000e, 0x0ef40004, 0x12f41284, 0x0ef41214, 0x10040004, 0x7ffc3004, 0x10003000, // ICON_MODE_2D + 0x78040000, 0x501f600e, 0x0ef44004, 0x12f41284, 0x0ef41284, 0x10140004, 0x7ffc300c, 0x10003000, // ICON_MODE_3D + 0x7fe00000, 0x50286030, 0x47fe4804, 0x44224402, 0x44224422, 0x241275e2, 0x0c06140a, 0x000007fe, // ICON_CUBE + 0x7fe00000, 0x5ff87ff0, 0x47fe4ffc, 0x44224402, 0x44224422, 0x241275e2, 0x0c06140a, 0x000007fe, // ICON_CUBE_FACE_TOP + 0x7fe00000, 0x50386030, 0x47c2483c, 0x443e443e, 0x443e443e, 0x241e75fe, 0x0c06140e, 0x000007fe, // ICON_CUBE_FACE_LEFT + 0x7fe00000, 0x50286030, 0x47fe4804, 0x47fe47fe, 0x47fe47fe, 0x27fe77fe, 0x0ffe17fe, 0x000007fe, // ICON_CUBE_FACE_FRONT + 0x7fe00000, 0x50286030, 0x47fe4804, 0x44224402, 0x44224422, 0x3bf27be2, 0x0bfe1bfa, 0x000007fe, // ICON_CUBE_FACE_BOTTOM + 0x7fe00000, 0x70286030, 0x7ffe7804, 0x7c227c02, 0x7c227c22, 0x3c127de2, 0x0c061c0a, 0x000007fe, // ICON_CUBE_FACE_RIGHT + 0x7fe00000, 0x6fe85ff0, 0x781e77e4, 0x7be27be2, 0x7be27be2, 0x24127be2, 0x0c06140a, 0x000007fe, // ICON_CUBE_FACE_BACK + 0x00000000, 0x2a0233fe, 0x22022602, 0x22022202, 0x2a022602, 0x00a033fe, 0x02080110, 0x00000000, // ICON_CAMERA + 0x00000000, 0x200c3ffc, 0x000c000c, 0x3ffc000c, 0x30003000, 0x30003000, 0x3ffc3004, 0x00000000, // ICON_SPECIAL + 0x00000000, 0x0022003e, 0x012201e2, 0x0100013e, 0x01000100, 0x79000100, 0x4f004900, 0x00007800, // ICON_LINK_NET + 0x00000000, 0x44007c00, 0x45004600, 0x00627cbe, 0x00620022, 0x45007cbe, 0x44004600, 0x00007c00, // ICON_LINK_BOXES + 0x00000000, 0x0044007c, 0x0010007c, 0x3f100010, 0x3f1021f0, 0x3f100010, 0x3f0021f0, 0x00000000, // ICON_LINK_MULTI + 0x00000000, 0x0044007c, 0x00440044, 0x0010007c, 0x00100010, 0x44107c10, 0x440047f0, 0x00007c00, // ICON_LINK + 0x00000000, 0x0044007c, 0x00440044, 0x0000007c, 0x00000010, 0x44007c10, 0x44004550, 0x00007c00, // ICON_LINK_BROKE + 0x02a00000, 0x22a43ffc, 0x20042004, 0x20042ff4, 0x20042ff4, 0x20042ff4, 0x20042004, 0x00003ffc, // ICON_TEXT_NOTES + 0x3ffc0000, 0x20042004, 0x245e27c4, 0x27c42444, 0x2004201e, 0x201e2004, 0x20042004, 0x00003ffc, // ICON_NOTEBOOK + 0x00000000, 0x07e00000, 0x04200420, 0x24243ffc, 0x24242424, 0x24242424, 0x3ffc2424, 0x00000000, // ICON_SUITCASE + 0x00000000, 0x0fe00000, 0x08200820, 0x40047ffc, 0x7ffc5554, 0x40045554, 0x7ffc4004, 0x00000000, // ICON_SUITCASE_ZIP + 0x00000000, 0x20043ffc, 0x3ffc2004, 0x13c81008, 0x100813c8, 0x10081008, 0x1ff81008, 0x00000000, // ICON_MAILBOX + 0x00000000, 0x40027ffe, 0x5ffa5ffa, 0x5ffa5ffa, 0x40025ffa, 0x03c07ffe, 0x1ff81ff8, 0x00000000, // ICON_MONITOR + 0x0ff00000, 0x6bfe7ffe, 0x7ffe7ffe, 0x68167ffe, 0x08106816, 0x08100810, 0x0ff00810, 0x00000000, // ICON_PRINTER + 0x3ff80000, 0xfffe2008, 0x870a8002, 0x904a888a, 0x904a904a, 0x870a888a, 0xfffe8002, 0x00000000, // ICON_PHOTO_CAMERA + 0x0fc00000, 0xfcfe0cd8, 0x8002fffe, 0x84428382, 0x84428442, 0x80028382, 0xfffe8002, 0x00000000, // ICON_PHOTO_CAMERA_FLASH + 0x00000000, 0x02400180, 0x08100420, 0x20041008, 0x23c42004, 0x22442244, 0x3ffc2244, 0x00000000, // ICON_HOUSE + 0x00000000, 0x1c700000, 0x3ff83ef8, 0x3ff83ff8, 0x0fe01ff0, 0x038007c0, 0x00000100, 0x00000000, // ICON_HEART + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x80000000, 0xe000c000, // ICON_CORNER + 0x00000000, 0x14001c00, 0x15c01400, 0x15401540, 0x155c1540, 0x15541554, 0x1ddc1554, 0x00000000, // ICON_VERTICAL_BARS + 0x00000000, 0x03000300, 0x1b001b00, 0x1b601b60, 0x1b6c1b60, 0x1b6c1b6c, 0x1b6c1b6c, 0x00000000, // ICON_VERTICAL_BARS_FILL + 0x00000000, 0x00000000, 0x403e7ffe, 0x7ffe403e, 0x7ffe0000, 0x43fe43fe, 0x00007ffe, 0x00000000, // ICON_LIFE_BARS + 0x7ffc0000, 0x43844004, 0x43844284, 0x43844004, 0x42844284, 0x42844284, 0x40044384, 0x00007ffc, // ICON_INFO + 0x40008000, 0x10002000, 0x04000800, 0x01000200, 0x00400080, 0x00100020, 0x00040008, 0x00010002, // ICON_CROSSLINE + 0x00000000, 0x1ff01ff0, 0x18301830, 0x1f001830, 0x03001f00, 0x00000300, 0x03000300, 0x00000000, // ICON_HELP + 0x3ff00000, 0x2abc3550, 0x2aac3554, 0x2aac3554, 0x2aac3554, 0x2aac3554, 0x2aac3554, 0x00003ffc, // ICON_FILETYPE_ALPHA + 0x3ff00000, 0x201c2010, 0x22442184, 0x28142424, 0x29942814, 0x2ff42994, 0x20042004, 0x00003ffc, // ICON_FILETYPE_HOME + 0x07fe0000, 0x04020402, 0x7fe20402, 0x44224422, 0x44224422, 0x402047fe, 0x40204020, 0x00007fe0, // ICON_LAYERS_VISIBLE + 0x07fe0000, 0x04020402, 0x7c020402, 0x44024402, 0x44024402, 0x402047fe, 0x40204020, 0x00007fe0, // ICON_LAYERS + 0x00000000, 0x40027ffe, 0x7ffe4002, 0x40024002, 0x40024002, 0x40024002, 0x7ffe4002, 0x00000000, // ICON_WINDOW + 0x09100000, 0x09f00910, 0x09100910, 0x00000910, 0x24a2779e, 0x27a224a2, 0x709e20a2, 0x00000000, // ICON_HIDPI + 0x3ff00000, 0x201c2010, 0x2a842e84, 0x2e842a84, 0x2ba42004, 0x2aa42aa4, 0x20042ba4, 0x00003ffc, // ICON_FILETYPE_BINARY + 0x00000000, 0x00000000, 0x00120012, 0x4a5e4bd2, 0x485233d2, 0x00004bd2, 0x00000000, 0x00000000, // ICON_HEX + 0x01800000, 0x381c0660, 0x23c42004, 0x23c42044, 0x13c82204, 0x08101008, 0x02400420, 0x00000180, // ICON_SHIELD + 0x007e0000, 0x20023fc2, 0x40227fe2, 0x400a403a, 0x400a400a, 0x400a400a, 0x4008400e, 0x00007ff8, // ICON_FILE_NEW + 0x00000000, 0x0042007e, 0x40027fc2, 0x44024002, 0x5f024402, 0x44024402, 0x7ffe4002, 0x00000000, // ICON_FOLDER_ADD + 0x44220000, 0x12482244, 0xf3cf0000, 0x14280420, 0x48122424, 0x08100810, 0x1ff81008, 0x03c00420, // ICON_ALARM + 0x0aa00000, 0x1ff80aa0, 0x1068700e, 0x1008706e, 0x1008700e, 0x1008700e, 0x0aa01ff8, 0x00000aa0, // ICON_CPU + 0x07e00000, 0x04201db8, 0x04a01c38, 0x04a01d38, 0x04a01d38, 0x04a01d38, 0x04201d38, 0x000007e0, // ICON_ROM + 0x00000000, 0x03c00000, 0x3c382ff0, 0x3c04380c, 0x01800000, 0x03c003c0, 0x00000180, 0x00000000, // ICON_STEP_OVER + 0x01800000, 0x01800180, 0x01800180, 0x03c007e0, 0x00000180, 0x01800000, 0x03c003c0, 0x00000180, // ICON_STEP_INTO + 0x01800000, 0x07e003c0, 0x01800180, 0x01800180, 0x00000180, 0x01800000, 0x03c003c0, 0x00000180, // ICON_STEP_OUT + 0x00000000, 0x0ff003c0, 0x181c1c34, 0x303c301c, 0x30003000, 0x1c301800, 0x03c00ff0, 0x00000000, // ICON_RESTART + 0x00000000, 0x00000000, 0x07e003c0, 0x0ff00ff0, 0x0ff00ff0, 0x03c007e0, 0x00000000, 0x00000000, // ICON_BREAKPOINT_ON + 0x00000000, 0x00000000, 0x042003c0, 0x08100810, 0x08100810, 0x03c00420, 0x00000000, 0x00000000, // ICON_BREAKPOINT_OFF + 0x00000000, 0x00000000, 0x1ff81ff8, 0x1ff80000, 0x00001ff8, 0x1ff81ff8, 0x00000000, 0x00000000, // ICON_BURGER_MENU + 0x00000000, 0x00000000, 0x00880070, 0x0c880088, 0x1e8810f8, 0x3e881288, 0x00000000, 0x00000000, // ICON_CASE_SENSITIVE + 0x00000000, 0x02000000, 0x07000a80, 0x07001fc0, 0x02000a80, 0x00300030, 0x00000000, 0x00000000, // ICON_REG_EXP + 0x00000000, 0x0042007e, 0x40027fc2, 0x40024002, 0x40024002, 0x40024002, 0x7ffe4002, 0x00000000, // ICON_FOLDER + 0x3ff00000, 0x201c2010, 0x20042004, 0x20042004, 0x20042004, 0x20042004, 0x20042004, 0x00003ffc, // ICON_FILE + 0x1ff00000, 0x20082008, 0x17d02fe8, 0x05400ba0, 0x09200540, 0x23881010, 0x2fe827c8, 0x00001ff0, // ICON_SAND_TIMER + 0x01800000, 0x02400240, 0x05a00420, 0x09900990, 0x11881188, 0x21842004, 0x40024182, 0x00003ffc, // ICON_WARNING + 0x7ffe0000, 0x4ff24002, 0x4c324ff2, 0x4f824c02, 0x41824f82, 0x41824002, 0x40024182, 0x00007ffe, // ICON_HELP_BOX + 0x7ffe0000, 0x41824002, 0x40024182, 0x41824182, 0x41824182, 0x41824182, 0x40024182, 0x00007ffe, // ICON_INFO_BOX + 0x01800000, 0x04200240, 0x10080810, 0x7bde2004, 0x0a500a50, 0x08500bd0, 0x08100850, 0x00000ff0, // ICON_PRIORITY + 0x01800000, 0x18180660, 0x80016006, 0x98196006, 0x99996666, 0x19986666, 0x01800660, 0x00000000, // ICON_LAYERS_ISO + 0x07fe0000, 0x1c020402, 0x74021402, 0x54025402, 0x54025402, 0x500857fe, 0x40205ff8, 0x00007fe0, // ICON_LAYERS2 + 0x0ffe0000, 0x3ffa0802, 0x7fea200a, 0x402a402a, 0x422a422a, 0x422e422a, 0x40384e28, 0x00007fe0, // ICON_MLAYERS + 0x0ffe0000, 0x3ffa0802, 0x7fea200a, 0x402a402a, 0x5b2a512a, 0x512e552a, 0x40385128, 0x00007fe0, // ICON_MAPS + 0x04200000, 0x1cf00c60, 0x11f019f0, 0x0f3807b8, 0x1e3c0f3c, 0x1c1c1e1c, 0x1e3c1c1c, 0x00000f70, // ICON_HOT + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_229 + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_230 + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_231 + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_232 + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_233 + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_234 + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_235 + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_236 + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_237 + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_238 + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_239 + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_240 + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_241 + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_242 + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_243 + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_244 + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_245 + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_246 + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_247 + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_248 + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_249 + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_250 + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_251 + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_252 + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_253 + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_254 + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_255 +}; + +// NOTE: A pointer to current icons array should be defined +static unsigned int *guiIconsPtr = guiIcons; + +#endif // !RAYGUI_NO_ICONS && !RAYGUI_CUSTOM_ICONS + +#ifndef RAYGUI_ICON_SIZE + #define RAYGUI_ICON_SIZE 0 +#endif + +// WARNING: Those values define the total size of the style data array, +// if changed, previous saved styles could become incompatible +#define RAYGUI_MAX_CONTROLS 16 // Maximum number of controls +#define RAYGUI_MAX_PROPS_BASE 16 // Maximum number of base properties +#define RAYGUI_MAX_PROPS_EXTENDED 8 // Maximum number of extended properties + +//---------------------------------------------------------------------------------- +// Types and Structures Definition +//---------------------------------------------------------------------------------- +// Gui control property style color element +typedef enum { BORDER = 0, BASE, TEXT, OTHER } GuiPropertyElement; + +//---------------------------------------------------------------------------------- +// Global Variables Definition +//---------------------------------------------------------------------------------- +static GuiState guiState = STATE_NORMAL; // Gui global state, if !STATE_NORMAL, forces defined state + +static Font guiFont = { 0 }; // Gui current font (WARNING: highly coupled to raylib) +static bool guiLocked = false; // Gui lock state (no inputs processed) +static float guiAlpha = 1.0f; // Gui controls transparency + +static unsigned int guiIconScale = 1; // Gui icon default scale (if icons enabled) + +static bool guiTooltip = false; // Tooltip enabled/disabled +static const char *guiTooltipPtr = NULL; // Tooltip string pointer (string provided by user) + +static bool guiControlExclusiveMode = false; // Gui control exclusive mode (no inputs processed except current control) +static Rectangle guiControlExclusiveRec = { 0 }; // Gui control exclusive bounds rectangle, used as an unique identifier + +static int textBoxCursorIndex = 0; // Cursor index, shared by all GuiTextBox*() +//static int blinkCursorFrameCounter = 0; // Frame counter for cursor blinking +static int autoCursorCooldownCounter = 0; // Cooldown frame counter for automatic cursor movement on key-down +static int autoCursorDelayCounter = 0; // Delay frame counter for automatic cursor movement + +//---------------------------------------------------------------------------------- +// Style data array for all gui style properties (allocated on data segment by default) +// +// NOTE 1: First set of BASE properties are generic to all controls but could be individually +// overwritten per control, first set of EXTENDED properties are generic to all controls and +// can not be overwritten individually but custom EXTENDED properties can be used by control +// +// NOTE 2: A new style set could be loaded over this array using GuiLoadStyle(), +// but default gui style could always be recovered with GuiLoadStyleDefault() +// +// guiStyle size is by default: 16*(16 + 8) = 384*4 = 1536 bytes = 1.5 KB +//---------------------------------------------------------------------------------- +static unsigned int guiStyle[RAYGUI_MAX_CONTROLS*(RAYGUI_MAX_PROPS_BASE + RAYGUI_MAX_PROPS_EXTENDED)] = { 0 }; + +static bool guiStyleLoaded = false; // Style loaded flag for lazy style initialization + +//---------------------------------------------------------------------------------- +// Standalone Mode Functions Declaration +// +// NOTE: raygui depend on some raylib input and drawing functions +// To use raygui as standalone library, below functions must be defined by the user +//---------------------------------------------------------------------------------- +#if defined(RAYGUI_STANDALONE) + +#define KEY_RIGHT 262 +#define KEY_LEFT 263 +#define KEY_DOWN 264 +#define KEY_UP 265 +#define KEY_BACKSPACE 259 +#define KEY_ENTER 257 + +#define MOUSE_LEFT_BUTTON 0 + +// Input required functions +//------------------------------------------------------------------------------- +static Vector2 GetMousePosition(void); +static float GetMouseWheelMove(void); +static bool IsMouseButtonDown(int button); +static bool IsMouseButtonPressed(int button); +static bool IsMouseButtonReleased(int button); + +static bool IsKeyDown(int key); +static bool IsKeyPressed(int key); +static int GetCharPressed(void); // -- GuiTextBox(), GuiValueBox() +//------------------------------------------------------------------------------- + +// Drawing required functions +//------------------------------------------------------------------------------- +static void DrawRectangle(int x, int y, int width, int height, Color color); // -- GuiDrawRectangle() +static void DrawRectangleGradientEx(Rectangle rec, Color col1, Color col2, Color col3, Color col4); // -- GuiColorPicker() +//------------------------------------------------------------------------------- + +// Text required functions +//------------------------------------------------------------------------------- +static Font GetFontDefault(void); // -- GuiLoadStyleDefault() +static Font LoadFontEx(const char *fileName, int fontSize, int *codepoints, int codepointCount); // -- GuiLoadStyle(), load font + +static Texture2D LoadTextureFromImage(Image image); // -- GuiLoadStyle(), required to load texture from embedded font atlas image +static void SetShapesTexture(Texture2D tex, Rectangle rec); // -- GuiLoadStyle(), required to set shapes rec to font white rec (optimization) + +static char *LoadFileText(const char *fileName); // -- GuiLoadStyle(), required to load charset data +static void UnloadFileText(char *text); // -- GuiLoadStyle(), required to unload charset data + +static const char *GetDirectoryPath(const char *filePath); // -- GuiLoadStyle(), required to find charset/font file from text .rgs + +static int *LoadCodepoints(const char *text, int *count); // -- GuiLoadStyle(), required to load required font codepoints list +static void UnloadCodepoints(int *codepoints); // -- GuiLoadStyle(), required to unload codepoints list + +static unsigned char *DecompressData(const unsigned char *compData, int compDataSize, int *dataSize); // -- GuiLoadStyle() +//------------------------------------------------------------------------------- + +// raylib functions already implemented in raygui +//------------------------------------------------------------------------------- +static Color GetColor(int hexValue); // Returns a Color struct from hexadecimal value +static int ColorToInt(Color color); // Returns hexadecimal value for a Color +static bool CheckCollisionPointRec(Vector2 point, Rectangle rec); // Check if point is inside rectangle +static const char *TextFormat(const char *text, ...); // Formatting of text with variables to 'embed' +static const char **TextSplit(const char *text, char delimiter, int *count); // Split text into multiple strings +static int TextToInteger(const char *text); // Get integer value from text +static float TextToFloat(const char *text); // Get float value from text + +static int GetCodepointNext(const char *text, int *codepointSize); // Get next codepoint in a UTF-8 encoded text +static const char *CodepointToUTF8(int codepoint, int *byteSize); // Encode codepoint into UTF-8 text (char array size returned as parameter) + +static void DrawRectangleGradientV(int posX, int posY, int width, int height, Color color1, Color color2); // Draw rectangle vertical gradient +//------------------------------------------------------------------------------- + +#endif // RAYGUI_STANDALONE + +//---------------------------------------------------------------------------------- +// Module specific Functions Declaration +//---------------------------------------------------------------------------------- +static void GuiLoadStyleFromMemory(const unsigned char *fileData, int dataSize); // Load style from memory (binary only) + +static int GetTextWidth(const char *text); // Gui get text width using gui font and style +static Rectangle GetTextBounds(int control, Rectangle bounds); // Get text bounds considering control bounds +static const char *GetTextIcon(const char *text, int *iconId); // Get text icon if provided and move text cursor + +static void GuiDrawText(const char *text, Rectangle textBounds, int alignment, Color tint); // Gui draw text using default font +static void GuiDrawRectangle(Rectangle rec, int borderWidth, Color borderColor, Color color); // Gui draw rectangle using default raygui style + +static const char **GuiTextSplit(const char *text, char delimiter, int *count, int *textRow); // Split controls text into multiple strings +static Vector3 ConvertHSVtoRGB(Vector3 hsv); // Convert color data from HSV to RGB +static Vector3 ConvertRGBtoHSV(Vector3 rgb); // Convert color data from RGB to HSV + +static int GuiScrollBar(Rectangle bounds, int value, int minValue, int maxValue); // Scroll bar control, used by GuiScrollPanel() +static void GuiTooltip(Rectangle controlRec); // Draw tooltip using control rec position + +static Color GuiFade(Color color, float alpha); // Fade color by an alpha factor + +//---------------------------------------------------------------------------------- +// Gui Setup Functions Definition +//---------------------------------------------------------------------------------- +// Enable gui global state +// NOTE: We check for STATE_DISABLED to avoid messing custom global state setups +void GuiEnable(void) { if (guiState == STATE_DISABLED) guiState = STATE_NORMAL; } + +// Disable gui global state +// NOTE: We check for STATE_NORMAL to avoid messing custom global state setups +void GuiDisable(void) { if (guiState == STATE_NORMAL) guiState = STATE_DISABLED; } + +// Lock gui global state +void GuiLock(void) { guiLocked = true; } + +// Unlock gui global state +void GuiUnlock(void) { guiLocked = false; } + +// Check if gui is locked (global state) +bool GuiIsLocked(void) { return guiLocked; } + +// Set gui controls alpha global state +void GuiSetAlpha(float alpha) +{ + if (alpha < 0.0f) alpha = 0.0f; + else if (alpha > 1.0f) alpha = 1.0f; + + guiAlpha = alpha; +} + +// Set gui state (global state) +void GuiSetState(int state) { guiState = (GuiState)state; } + +// Get gui state (global state) +int GuiGetState(void) { return guiState; } + +// Set custom gui font +// NOTE: Font loading/unloading is external to raygui +void GuiSetFont(Font font) +{ + if (font.texture.id > 0) + { + // NOTE: If we try to setup a font but default style has not been + // lazily loaded before, it will be overwritten, so we need to force + // default style loading first + if (!guiStyleLoaded) GuiLoadStyleDefault(); + + guiFont = font; + } +} + +// Get custom gui font +Font GuiGetFont(void) +{ + return guiFont; +} + +// Set control style property value +void GuiSetStyle(int control, int property, int value) +{ + if (!guiStyleLoaded) GuiLoadStyleDefault(); + guiStyle[control*(RAYGUI_MAX_PROPS_BASE + RAYGUI_MAX_PROPS_EXTENDED) + property] = value; + + // Default properties are propagated to all controls + if ((control == 0) && (property < RAYGUI_MAX_PROPS_BASE)) + { + for (int i = 1; i < RAYGUI_MAX_CONTROLS; i++) guiStyle[i*(RAYGUI_MAX_PROPS_BASE + RAYGUI_MAX_PROPS_EXTENDED) + property] = value; + } +} + +// Get control style property value +int GuiGetStyle(int control, int property) +{ + if (!guiStyleLoaded) GuiLoadStyleDefault(); + return guiStyle[control*(RAYGUI_MAX_PROPS_BASE + RAYGUI_MAX_PROPS_EXTENDED) + property]; +} + +//---------------------------------------------------------------------------------- +// Gui Controls Functions Definition +//---------------------------------------------------------------------------------- + +// Window Box control +int GuiWindowBox(Rectangle bounds, const char *title) +{ + // Window title bar height (including borders) + // NOTE: This define is also used by GuiMessageBox() and GuiTextInputBox() + #if !defined(RAYGUI_WINDOWBOX_STATUSBAR_HEIGHT) + #define RAYGUI_WINDOWBOX_STATUSBAR_HEIGHT 24 + #endif + + int result = 0; + //GuiState state = guiState; + + int statusBarHeight = RAYGUI_WINDOWBOX_STATUSBAR_HEIGHT; + + Rectangle statusBar = { bounds.x, bounds.y, bounds.width, (float)statusBarHeight }; + if (bounds.height < statusBarHeight*2.0f) bounds.height = statusBarHeight*2.0f; + + Rectangle windowPanel = { bounds.x, bounds.y + (float)statusBarHeight - 1, bounds.width, bounds.height - (float)statusBarHeight + 1 }; + Rectangle closeButtonRec = { statusBar.x + statusBar.width - GuiGetStyle(STATUSBAR, BORDER_WIDTH) - 20, + statusBar.y + statusBarHeight/2.0f - 18.0f/2.0f, 18, 18 }; + + // Update control + //-------------------------------------------------------------------- + // NOTE: Logic is directly managed by button + //-------------------------------------------------------------------- + + // Draw control + //-------------------------------------------------------------------- + GuiStatusBar(statusBar, title); // Draw window header as status bar + GuiPanel(windowPanel, NULL); // Draw window base + + // Draw window close button + int tempBorderWidth = GuiGetStyle(BUTTON, BORDER_WIDTH); + int tempTextAlignment = GuiGetStyle(BUTTON, TEXT_ALIGNMENT); + GuiSetStyle(BUTTON, BORDER_WIDTH, 1); + GuiSetStyle(BUTTON, TEXT_ALIGNMENT, TEXT_ALIGN_CENTER); +#if defined(RAYGUI_NO_ICONS) + result = GuiButton(closeButtonRec, "x"); +#else + result = GuiButton(closeButtonRec, GuiIconText(ICON_CROSS_SMALL, NULL)); +#endif + GuiSetStyle(BUTTON, BORDER_WIDTH, tempBorderWidth); + GuiSetStyle(BUTTON, TEXT_ALIGNMENT, tempTextAlignment); + //-------------------------------------------------------------------- + + return result; // Window close button clicked: result = 1 +} + +// Group Box control with text name +int GuiGroupBox(Rectangle bounds, const char *text) +{ + #if !defined(RAYGUI_GROUPBOX_LINE_THICK) + #define RAYGUI_GROUPBOX_LINE_THICK 1 + #endif + + int result = 0; + GuiState state = guiState; + + // Draw control + //-------------------------------------------------------------------- + GuiDrawRectangle(RAYGUI_CLITERAL(Rectangle){ bounds.x, bounds.y, RAYGUI_GROUPBOX_LINE_THICK, bounds.height }, 0, BLANK, GetColor(GuiGetStyle(DEFAULT, (state == STATE_DISABLED)? (int)BORDER_COLOR_DISABLED : (int)LINE_COLOR))); + GuiDrawRectangle(RAYGUI_CLITERAL(Rectangle){ bounds.x, bounds.y + bounds.height - 1, bounds.width, RAYGUI_GROUPBOX_LINE_THICK }, 0, BLANK, GetColor(GuiGetStyle(DEFAULT, (state == STATE_DISABLED)? (int)BORDER_COLOR_DISABLED : (int)LINE_COLOR))); + GuiDrawRectangle(RAYGUI_CLITERAL(Rectangle){ bounds.x + bounds.width - 1, bounds.y, RAYGUI_GROUPBOX_LINE_THICK, bounds.height }, 0, BLANK, GetColor(GuiGetStyle(DEFAULT, (state == STATE_DISABLED)? (int)BORDER_COLOR_DISABLED : (int)LINE_COLOR))); + + GuiLine(RAYGUI_CLITERAL(Rectangle){ bounds.x, bounds.y - GuiGetStyle(DEFAULT, TEXT_SIZE)/2, bounds.width, (float)GuiGetStyle(DEFAULT, TEXT_SIZE) }, text); + //-------------------------------------------------------------------- + + return result; +} + +// Line control +int GuiLine(Rectangle bounds, const char *text) +{ + #if !defined(RAYGUI_LINE_ORIGIN_SIZE) + #define RAYGUI_LINE_MARGIN_TEXT 12 + #endif + #if !defined(RAYGUI_LINE_TEXT_PADDING) + #define RAYGUI_LINE_TEXT_PADDING 4 + #endif + + int result = 0; + GuiState state = guiState; + + Color color = GetColor(GuiGetStyle(DEFAULT, (state == STATE_DISABLED)? (int)BORDER_COLOR_DISABLED : (int)LINE_COLOR)); + + // Draw control + //-------------------------------------------------------------------- + if (text == NULL) GuiDrawRectangle(RAYGUI_CLITERAL(Rectangle){ bounds.x, bounds.y + bounds.height/2, bounds.width, 1 }, 0, BLANK, color); + else + { + Rectangle textBounds = { 0 }; + textBounds.width = (float)GetTextWidth(text) + 2; + textBounds.height = bounds.height; + textBounds.x = bounds.x + RAYGUI_LINE_MARGIN_TEXT; + textBounds.y = bounds.y; + + // Draw line with embedded text label: "--- text --------------" + GuiDrawRectangle(RAYGUI_CLITERAL(Rectangle){ bounds.x, bounds.y + bounds.height/2, RAYGUI_LINE_MARGIN_TEXT - RAYGUI_LINE_TEXT_PADDING, 1 }, 0, BLANK, color); + GuiDrawText(text, textBounds, TEXT_ALIGN_LEFT, color); + GuiDrawRectangle(RAYGUI_CLITERAL(Rectangle){ bounds.x + 12 + textBounds.width + 4, bounds.y + bounds.height/2, bounds.width - textBounds.width - RAYGUI_LINE_MARGIN_TEXT - RAYGUI_LINE_TEXT_PADDING, 1 }, 0, BLANK, color); + } + //-------------------------------------------------------------------- + + return result; +} + +// Panel control +int GuiPanel(Rectangle bounds, const char *text) +{ + #if !defined(RAYGUI_PANEL_BORDER_WIDTH) + #define RAYGUI_PANEL_BORDER_WIDTH 1 + #endif + + int result = 0; + GuiState state = guiState; + + // Text will be drawn as a header bar (if provided) + Rectangle statusBar = { bounds.x, bounds.y, bounds.width, (float)RAYGUI_WINDOWBOX_STATUSBAR_HEIGHT }; + if ((text != NULL) && (bounds.height < RAYGUI_WINDOWBOX_STATUSBAR_HEIGHT*2.0f)) bounds.height = RAYGUI_WINDOWBOX_STATUSBAR_HEIGHT*2.0f; + + if (text != NULL) + { + // Move panel bounds after the header bar + bounds.y += (float)RAYGUI_WINDOWBOX_STATUSBAR_HEIGHT - 1; + bounds.height -= (float)RAYGUI_WINDOWBOX_STATUSBAR_HEIGHT - 1; + } + + // Draw control + //-------------------------------------------------------------------- + if (text != NULL) GuiStatusBar(statusBar, text); // Draw panel header as status bar + + GuiDrawRectangle(bounds, RAYGUI_PANEL_BORDER_WIDTH, GetColor(GuiGetStyle(DEFAULT, (state == STATE_DISABLED)? (int)BORDER_COLOR_DISABLED: (int)LINE_COLOR)), + GetColor(GuiGetStyle(DEFAULT, (state == STATE_DISABLED)? BASE_COLOR_DISABLED : BACKGROUND_COLOR))); + //-------------------------------------------------------------------- + + return result; +} + +// Tab Bar control +// NOTE: Using GuiToggle() for the TABS +int GuiTabBar(Rectangle bounds, const char **text, int count, int *active) +{ + #define RAYGUI_TABBAR_ITEM_WIDTH 160 + + int result = -1; + //GuiState state = guiState; + + Rectangle tabBounds = { bounds.x, bounds.y, RAYGUI_TABBAR_ITEM_WIDTH, bounds.height }; + + if (*active < 0) *active = 0; + else if (*active > count - 1) *active = count - 1; + + int offsetX = 0; // Required in case tabs go out of screen + offsetX = (*active + 2)*RAYGUI_TABBAR_ITEM_WIDTH - GetScreenWidth(); + if (offsetX < 0) offsetX = 0; + + bool toggle = false; // Required for individual toggles + + // Draw control + //-------------------------------------------------------------------- + for (int i = 0; i < count; i++) + { + tabBounds.x = bounds.x + (RAYGUI_TABBAR_ITEM_WIDTH + 4)*i - offsetX; + + if (tabBounds.x < GetScreenWidth()) + { + // Draw tabs as toggle controls + int textAlignment = GuiGetStyle(TOGGLE, TEXT_ALIGNMENT); + int textPadding = GuiGetStyle(TOGGLE, TEXT_PADDING); + GuiSetStyle(TOGGLE, TEXT_ALIGNMENT, TEXT_ALIGN_LEFT); + GuiSetStyle(TOGGLE, TEXT_PADDING, 8); + + if (i == (*active)) + { + toggle = true; + GuiToggle(tabBounds, GuiIconText(12, text[i]), &toggle); + } + else + { + toggle = false; + GuiToggle(tabBounds, GuiIconText(12, text[i]), &toggle); + if (toggle) *active = i; + } + + // Close tab with middle mouse button pressed + if (CheckCollisionPointRec(GetMousePosition(), tabBounds) && IsMouseButtonPressed(MOUSE_MIDDLE_BUTTON)) result = i; + + GuiSetStyle(TOGGLE, TEXT_PADDING, textPadding); + GuiSetStyle(TOGGLE, TEXT_ALIGNMENT, textAlignment); + + // Draw tab close button + // NOTE: Only draw close button for current tab: if (CheckCollisionPointRec(mousePosition, tabBounds)) + int tempBorderWidth = GuiGetStyle(BUTTON, BORDER_WIDTH); + int tempTextAlignment = GuiGetStyle(BUTTON, TEXT_ALIGNMENT); + GuiSetStyle(BUTTON, BORDER_WIDTH, 1); + GuiSetStyle(BUTTON, TEXT_ALIGNMENT, TEXT_ALIGN_CENTER); +#if defined(RAYGUI_NO_ICONS) + if (GuiButton(RAYGUI_CLITERAL(Rectangle){ tabBounds.x + tabBounds.width - 14 - 5, tabBounds.y + 5, 14, 14 }, "x")) result = i; +#else + if (GuiButton(RAYGUI_CLITERAL(Rectangle){ tabBounds.x + tabBounds.width - 14 - 5, tabBounds.y + 5, 14, 14 }, GuiIconText(ICON_CROSS_SMALL, NULL))) result = i; +#endif + GuiSetStyle(BUTTON, BORDER_WIDTH, tempBorderWidth); + GuiSetStyle(BUTTON, TEXT_ALIGNMENT, tempTextAlignment); + } + } + + // Draw tab-bar bottom line + GuiDrawRectangle(RAYGUI_CLITERAL(Rectangle){ bounds.x, bounds.y + bounds.height - 1, bounds.width, 1 }, 0, BLANK, GetColor(GuiGetStyle(TOGGLE, BORDER_COLOR_NORMAL))); + //-------------------------------------------------------------------- + + return result; // Return as result the current TAB closing requested +} + +// Scroll Panel control +int GuiScrollPanel(Rectangle bounds, const char *text, Rectangle content, Vector2 *scroll, Rectangle *view) +{ + #define RAYGUI_MIN_SCROLLBAR_WIDTH 40 + #define RAYGUI_MIN_SCROLLBAR_HEIGHT 40 + #define RAYGUI_MIN_MOUSE_WHEEL_SPEED 20 + + int result = 0; + GuiState state = guiState; + + Rectangle temp = { 0 }; + if (view == NULL) view = &temp; + + Vector2 scrollPos = { 0.0f, 0.0f }; + if (scroll != NULL) scrollPos = *scroll; + + // Text will be drawn as a header bar (if provided) + Rectangle statusBar = { bounds.x, bounds.y, bounds.width, (float)RAYGUI_WINDOWBOX_STATUSBAR_HEIGHT }; + if (bounds.height < RAYGUI_WINDOWBOX_STATUSBAR_HEIGHT*2.0f) bounds.height = RAYGUI_WINDOWBOX_STATUSBAR_HEIGHT*2.0f; + + if (text != NULL) + { + // Move panel bounds after the header bar + bounds.y += (float)RAYGUI_WINDOWBOX_STATUSBAR_HEIGHT - 1; + bounds.height -= (float)RAYGUI_WINDOWBOX_STATUSBAR_HEIGHT + 1; + } + + bool hasHorizontalScrollBar = (content.width > bounds.width - 2*GuiGetStyle(DEFAULT, BORDER_WIDTH))? true : false; + bool hasVerticalScrollBar = (content.height > bounds.height - 2*GuiGetStyle(DEFAULT, BORDER_WIDTH))? true : false; + + // Recheck to account for the other scrollbar being visible + if (!hasHorizontalScrollBar) hasHorizontalScrollBar = (hasVerticalScrollBar && (content.width > (bounds.width - 2*GuiGetStyle(DEFAULT, BORDER_WIDTH) - GuiGetStyle(LISTVIEW, SCROLLBAR_WIDTH))))? true : false; + if (!hasVerticalScrollBar) hasVerticalScrollBar = (hasHorizontalScrollBar && (content.height > (bounds.height - 2*GuiGetStyle(DEFAULT, BORDER_WIDTH) - GuiGetStyle(LISTVIEW, SCROLLBAR_WIDTH))))? true : false; + + int horizontalScrollBarWidth = hasHorizontalScrollBar? GuiGetStyle(LISTVIEW, SCROLLBAR_WIDTH) : 0; + int verticalScrollBarWidth = hasVerticalScrollBar? GuiGetStyle(LISTVIEW, SCROLLBAR_WIDTH) : 0; + Rectangle horizontalScrollBar = { + (float)((GuiGetStyle(LISTVIEW, SCROLLBAR_SIDE) == SCROLLBAR_LEFT_SIDE)? (float)bounds.x + verticalScrollBarWidth : (float)bounds.x) + GuiGetStyle(DEFAULT, BORDER_WIDTH), + (float)bounds.y + bounds.height - horizontalScrollBarWidth - GuiGetStyle(DEFAULT, BORDER_WIDTH), + (float)bounds.width - verticalScrollBarWidth - 2*GuiGetStyle(DEFAULT, BORDER_WIDTH), + (float)horizontalScrollBarWidth + }; + Rectangle verticalScrollBar = { + (float)((GuiGetStyle(LISTVIEW, SCROLLBAR_SIDE) == SCROLLBAR_LEFT_SIDE)? (float)bounds.x + GuiGetStyle(DEFAULT, BORDER_WIDTH) : (float)bounds.x + bounds.width - verticalScrollBarWidth - GuiGetStyle(DEFAULT, BORDER_WIDTH)), + (float)bounds.y + GuiGetStyle(DEFAULT, BORDER_WIDTH), + (float)verticalScrollBarWidth, + (float)bounds.height - horizontalScrollBarWidth - 2*GuiGetStyle(DEFAULT, BORDER_WIDTH) + }; + + // Make sure scroll bars have a minimum width/height + if (horizontalScrollBar.width < RAYGUI_MIN_SCROLLBAR_WIDTH) horizontalScrollBar.width = RAYGUI_MIN_SCROLLBAR_WIDTH; + if (verticalScrollBar.height < RAYGUI_MIN_SCROLLBAR_HEIGHT) verticalScrollBar.height = RAYGUI_MIN_SCROLLBAR_HEIGHT; + + // Calculate view area (area without the scrollbars) + *view = (GuiGetStyle(LISTVIEW, SCROLLBAR_SIDE) == SCROLLBAR_LEFT_SIDE)? + RAYGUI_CLITERAL(Rectangle){ bounds.x + verticalScrollBarWidth + GuiGetStyle(DEFAULT, BORDER_WIDTH), bounds.y + GuiGetStyle(DEFAULT, BORDER_WIDTH), bounds.width - 2*GuiGetStyle(DEFAULT, BORDER_WIDTH) - verticalScrollBarWidth, bounds.height - 2*GuiGetStyle(DEFAULT, BORDER_WIDTH) - horizontalScrollBarWidth } : + RAYGUI_CLITERAL(Rectangle){ bounds.x + GuiGetStyle(DEFAULT, BORDER_WIDTH), bounds.y + GuiGetStyle(DEFAULT, BORDER_WIDTH), bounds.width - 2*GuiGetStyle(DEFAULT, BORDER_WIDTH) - verticalScrollBarWidth, bounds.height - 2*GuiGetStyle(DEFAULT, BORDER_WIDTH) - horizontalScrollBarWidth }; + + // Clip view area to the actual content size + if (view->width > content.width) view->width = content.width; + if (view->height > content.height) view->height = content.height; + + float horizontalMin = hasHorizontalScrollBar? ((GuiGetStyle(LISTVIEW, SCROLLBAR_SIDE) == SCROLLBAR_LEFT_SIDE)? (float)-verticalScrollBarWidth : 0) - (float)GuiGetStyle(DEFAULT, BORDER_WIDTH) : (((float)GuiGetStyle(LISTVIEW, SCROLLBAR_SIDE) == SCROLLBAR_LEFT_SIDE)? (float)-verticalScrollBarWidth : 0) - (float)GuiGetStyle(DEFAULT, BORDER_WIDTH); + float horizontalMax = hasHorizontalScrollBar? content.width - bounds.width + (float)verticalScrollBarWidth + GuiGetStyle(DEFAULT, BORDER_WIDTH) - (((float)GuiGetStyle(LISTVIEW, SCROLLBAR_SIDE) == SCROLLBAR_LEFT_SIDE)? (float)verticalScrollBarWidth : 0) : (float)-GuiGetStyle(DEFAULT, BORDER_WIDTH); + float verticalMin = hasVerticalScrollBar? 0.0f : -1.0f; + float verticalMax = hasVerticalScrollBar? content.height - bounds.height + (float)horizontalScrollBarWidth + (float)GuiGetStyle(DEFAULT, BORDER_WIDTH) : (float)-GuiGetStyle(DEFAULT, BORDER_WIDTH); + + // Update control + //-------------------------------------------------------------------- + if ((state != STATE_DISABLED) && !guiLocked) + { + Vector2 mousePoint = GetMousePosition(); + + // Check button state + if (CheckCollisionPointRec(mousePoint, bounds)) + { + if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) state = STATE_PRESSED; + else state = STATE_FOCUSED; + +#if defined(SUPPORT_SCROLLBAR_KEY_INPUT) + if (hasHorizontalScrollBar) + { + if (IsKeyDown(KEY_RIGHT)) scrollPos.x -= GuiGetStyle(SCROLLBAR, SCROLL_SPEED); + if (IsKeyDown(KEY_LEFT)) scrollPos.x += GuiGetStyle(SCROLLBAR, SCROLL_SPEED); + } + + if (hasVerticalScrollBar) + { + if (IsKeyDown(KEY_DOWN)) scrollPos.y -= GuiGetStyle(SCROLLBAR, SCROLL_SPEED); + if (IsKeyDown(KEY_UP)) scrollPos.y += GuiGetStyle(SCROLLBAR, SCROLL_SPEED); + } +#endif + float wheelMove = GetMouseWheelMove(); + + // Set scrolling speed with mouse wheel based on ratio between bounds and content + Vector2 mouseWheelSpeed = { content.width/bounds.width, content.height/bounds.height }; + if (mouseWheelSpeed.x < RAYGUI_MIN_MOUSE_WHEEL_SPEED) mouseWheelSpeed.x = RAYGUI_MIN_MOUSE_WHEEL_SPEED; + if (mouseWheelSpeed.y < RAYGUI_MIN_MOUSE_WHEEL_SPEED) mouseWheelSpeed.y = RAYGUI_MIN_MOUSE_WHEEL_SPEED; + + // Horizontal and vertical scrolling with mouse wheel + if (hasHorizontalScrollBar && (IsKeyDown(KEY_LEFT_CONTROL) || IsKeyDown(KEY_LEFT_SHIFT))) scrollPos.x += wheelMove*mouseWheelSpeed.x; + else scrollPos.y += wheelMove*mouseWheelSpeed.y; // Vertical scroll + } + } + + // Normalize scroll values + if (scrollPos.x > -horizontalMin) scrollPos.x = -horizontalMin; + if (scrollPos.x < -horizontalMax) scrollPos.x = -horizontalMax; + if (scrollPos.y > -verticalMin) scrollPos.y = -verticalMin; + if (scrollPos.y < -verticalMax) scrollPos.y = -verticalMax; + //-------------------------------------------------------------------- + + // Draw control + //-------------------------------------------------------------------- + if (text != NULL) GuiStatusBar(statusBar, text); // Draw panel header as status bar + + GuiDrawRectangle(bounds, 0, BLANK, GetColor(GuiGetStyle(DEFAULT, BACKGROUND_COLOR))); // Draw background + + // Save size of the scrollbar slider + const int slider = GuiGetStyle(SCROLLBAR, SCROLL_SLIDER_SIZE); + + // Draw horizontal scrollbar if visible + if (hasHorizontalScrollBar) + { + // Change scrollbar slider size to show the diff in size between the content width and the widget width + GuiSetStyle(SCROLLBAR, SCROLL_SLIDER_SIZE, (int)(((bounds.width - 2*GuiGetStyle(DEFAULT, BORDER_WIDTH) - verticalScrollBarWidth)/(int)content.width)*((int)bounds.width - 2*GuiGetStyle(DEFAULT, BORDER_WIDTH) - verticalScrollBarWidth))); + scrollPos.x = (float)-GuiScrollBar(horizontalScrollBar, (int)-scrollPos.x, (int)horizontalMin, (int)horizontalMax); + } + else scrollPos.x = 0.0f; + + // Draw vertical scrollbar if visible + if (hasVerticalScrollBar) + { + // Change scrollbar slider size to show the diff in size between the content height and the widget height + GuiSetStyle(SCROLLBAR, SCROLL_SLIDER_SIZE, (int)(((bounds.height - 2*GuiGetStyle(DEFAULT, BORDER_WIDTH) - horizontalScrollBarWidth)/(int)content.height)*((int)bounds.height - 2*GuiGetStyle(DEFAULT, BORDER_WIDTH) - horizontalScrollBarWidth))); + scrollPos.y = (float)-GuiScrollBar(verticalScrollBar, (int)-scrollPos.y, (int)verticalMin, (int)verticalMax); + } + else scrollPos.y = 0.0f; + + // Draw detail corner rectangle if both scroll bars are visible + if (hasHorizontalScrollBar && hasVerticalScrollBar) + { + Rectangle corner = { (GuiGetStyle(LISTVIEW, SCROLLBAR_SIDE) == SCROLLBAR_LEFT_SIDE)? (bounds.x + GuiGetStyle(DEFAULT, BORDER_WIDTH) + 2) : (horizontalScrollBar.x + horizontalScrollBar.width + 2), verticalScrollBar.y + verticalScrollBar.height + 2, (float)horizontalScrollBarWidth - 4, (float)verticalScrollBarWidth - 4 }; + GuiDrawRectangle(corner, 0, BLANK, GetColor(GuiGetStyle(LISTVIEW, TEXT + (state*3)))); + } + + // Draw scrollbar lines depending on current state + GuiDrawRectangle(bounds, GuiGetStyle(LISTVIEW, BORDER_WIDTH), GetColor(GuiGetStyle(LISTVIEW, BORDER + (state*3))), BLANK); + + // Set scrollbar slider size back to the way it was before + GuiSetStyle(SCROLLBAR, SCROLL_SLIDER_SIZE, slider); + //-------------------------------------------------------------------- + + if (scroll != NULL) *scroll = scrollPos; + + return result; +} + +// Label control +int GuiLabel(Rectangle bounds, const char *text) +{ + int result = 0; + GuiState state = guiState; + + // Update control + //-------------------------------------------------------------------- + //... + //-------------------------------------------------------------------- + + // Draw control + //-------------------------------------------------------------------- + GuiDrawText(text, GetTextBounds(LABEL, bounds), GuiGetStyle(LABEL, TEXT_ALIGNMENT), GetColor(GuiGetStyle(LABEL, TEXT + (state*3)))); + //-------------------------------------------------------------------- + + return result; +} + +// Button control, returns true when clicked +int GuiButton(Rectangle bounds, const char *text) +{ + int result = 0; + GuiState state = guiState; + + // Update control + //-------------------------------------------------------------------- + if ((state != STATE_DISABLED) && !guiLocked && !guiControlExclusiveMode) + { + Vector2 mousePoint = GetMousePosition(); + + // Check button state + if (CheckCollisionPointRec(mousePoint, bounds)) + { + if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) state = STATE_PRESSED; + else state = STATE_FOCUSED; + + if (IsMouseButtonReleased(MOUSE_LEFT_BUTTON)) result = 1; + } + } + //-------------------------------------------------------------------- + + // Draw control + //-------------------------------------------------------------------- + GuiDrawRectangle(bounds, GuiGetStyle(BUTTON, BORDER_WIDTH), GetColor(GuiGetStyle(BUTTON, BORDER + (state*3))), GetColor(GuiGetStyle(BUTTON, BASE + (state*3)))); + GuiDrawText(text, GetTextBounds(BUTTON, bounds), GuiGetStyle(BUTTON, TEXT_ALIGNMENT), GetColor(GuiGetStyle(BUTTON, TEXT + (state*3)))); + + if (state == STATE_FOCUSED) GuiTooltip(bounds); + //------------------------------------------------------------------ + + return result; // Button pressed: result = 1 +} + +// Label button control +int GuiLabelButton(Rectangle bounds, const char *text) +{ + GuiState state = guiState; + bool pressed = false; + + // NOTE: We force bounds.width to be all text + float textWidth = (float)GetTextWidth(text); + if ((bounds.width - 2*GuiGetStyle(LABEL, BORDER_WIDTH) - 2*GuiGetStyle(LABEL, TEXT_PADDING)) < textWidth) bounds.width = textWidth + 2*GuiGetStyle(LABEL, BORDER_WIDTH) + 2*GuiGetStyle(LABEL, TEXT_PADDING) + 2; + + // Update control + //-------------------------------------------------------------------- + if ((state != STATE_DISABLED) && !guiLocked && !guiControlExclusiveMode) + { + Vector2 mousePoint = GetMousePosition(); + + // Check checkbox state + if (CheckCollisionPointRec(mousePoint, bounds)) + { + if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) state = STATE_PRESSED; + else state = STATE_FOCUSED; + + if (IsMouseButtonReleased(MOUSE_LEFT_BUTTON)) pressed = true; + } + } + //-------------------------------------------------------------------- + + // Draw control + //-------------------------------------------------------------------- + GuiDrawText(text, GetTextBounds(LABEL, bounds), GuiGetStyle(LABEL, TEXT_ALIGNMENT), GetColor(GuiGetStyle(LABEL, TEXT + (state*3)))); + //-------------------------------------------------------------------- + + return pressed; +} + +// Toggle Button control +int GuiToggle(Rectangle bounds, const char *text, bool *active) +{ + int result = 0; + GuiState state = guiState; + + bool temp = false; + if (active == NULL) active = &temp; + + // Update control + //-------------------------------------------------------------------- + if ((state != STATE_DISABLED) && !guiLocked && !guiControlExclusiveMode) + { + Vector2 mousePoint = GetMousePosition(); + + // Check toggle button state + if (CheckCollisionPointRec(mousePoint, bounds)) + { + if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) state = STATE_PRESSED; + else if (IsMouseButtonReleased(MOUSE_LEFT_BUTTON)) + { + state = STATE_NORMAL; + *active = !(*active); + } + else state = STATE_FOCUSED; + } + } + //-------------------------------------------------------------------- + + // Draw control + //-------------------------------------------------------------------- + if (state == STATE_NORMAL) + { + GuiDrawRectangle(bounds, GuiGetStyle(TOGGLE, BORDER_WIDTH), GetColor(GuiGetStyle(TOGGLE, ((*active)? BORDER_COLOR_PRESSED : (BORDER + state*3)))), GetColor(GuiGetStyle(TOGGLE, ((*active)? BASE_COLOR_PRESSED : (BASE + state*3))))); + GuiDrawText(text, GetTextBounds(TOGGLE, bounds), GuiGetStyle(TOGGLE, TEXT_ALIGNMENT), GetColor(GuiGetStyle(TOGGLE, ((*active)? TEXT_COLOR_PRESSED : (TEXT + state*3))))); + } + else + { + GuiDrawRectangle(bounds, GuiGetStyle(TOGGLE, BORDER_WIDTH), GetColor(GuiGetStyle(TOGGLE, BORDER + state*3)), GetColor(GuiGetStyle(TOGGLE, BASE + state*3))); + GuiDrawText(text, GetTextBounds(TOGGLE, bounds), GuiGetStyle(TOGGLE, TEXT_ALIGNMENT), GetColor(GuiGetStyle(TOGGLE, TEXT + state*3))); + } + + if (state == STATE_FOCUSED) GuiTooltip(bounds); + //-------------------------------------------------------------------- + + return result; +} + +// Toggle Group control +int GuiToggleGroup(Rectangle bounds, const char *text, int *active) +{ + #if !defined(RAYGUI_TOGGLEGROUP_MAX_ITEMS) + #define RAYGUI_TOGGLEGROUP_MAX_ITEMS 32 + #endif + + int result = 0; + float initBoundsX = bounds.x; + + int temp = 0; + if (active == NULL) active = &temp; + + bool toggle = false; // Required for individual toggles + + // Get substrings items from text (items pointers) + int rows[RAYGUI_TOGGLEGROUP_MAX_ITEMS] = { 0 }; + int itemCount = 0; + const char **items = GuiTextSplit(text, ';', &itemCount, rows); + + int prevRow = rows[0]; + + for (int i = 0; i < itemCount; i++) + { + if (prevRow != rows[i]) + { + bounds.x = initBoundsX; + bounds.y += (bounds.height + GuiGetStyle(TOGGLE, GROUP_PADDING)); + prevRow = rows[i]; + } + + if (i == (*active)) + { + toggle = true; + GuiToggle(bounds, items[i], &toggle); + } + else + { + toggle = false; + GuiToggle(bounds, items[i], &toggle); + if (toggle) *active = i; + } + + bounds.x += (bounds.width + GuiGetStyle(TOGGLE, GROUP_PADDING)); + } + + return result; +} + +// Toggle Slider control extended +int GuiToggleSlider(Rectangle bounds, const char *text, int *active) +{ + int result = 0; + GuiState state = guiState; + + int temp = 0; + if (active == NULL) active = &temp; + + //bool toggle = false; // Required for individual toggles + + // Get substrings items from text (items pointers) + int itemCount = 0; + const char **items = NULL; + + if (text != NULL) items = GuiTextSplit(text, ';', &itemCount, NULL); + + Rectangle slider = { + 0, // Calculated later depending on the active toggle + bounds.y + GuiGetStyle(SLIDER, BORDER_WIDTH) + GuiGetStyle(SLIDER, SLIDER_PADDING), + (bounds.width - 2*GuiGetStyle(SLIDER, BORDER_WIDTH) - (itemCount + 1)*GuiGetStyle(SLIDER, SLIDER_PADDING))/itemCount, + bounds.height - 2*GuiGetStyle(SLIDER, BORDER_WIDTH) - 2*GuiGetStyle(SLIDER, SLIDER_PADDING) }; + + // Update control + //-------------------------------------------------------------------- + if ((state != STATE_DISABLED) && !guiLocked) + { + Vector2 mousePoint = GetMousePosition(); + + if (CheckCollisionPointRec(mousePoint, bounds)) + { + if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) state = STATE_PRESSED; + else if (IsMouseButtonReleased(MOUSE_LEFT_BUTTON)) + { + state = STATE_PRESSED; + (*active)++; + result = 1; + } + else state = STATE_FOCUSED; + } + + if ((*active) && (state != STATE_FOCUSED)) state = STATE_PRESSED; + } + + if (*active >= itemCount) *active = 0; + slider.x = bounds.x + GuiGetStyle(SLIDER, BORDER_WIDTH) + (*active + 1)*GuiGetStyle(SLIDER, SLIDER_PADDING) + (*active)*slider.width; + //-------------------------------------------------------------------- + + // Draw control + //-------------------------------------------------------------------- + GuiDrawRectangle(bounds, GuiGetStyle(SLIDER, BORDER_WIDTH), GetColor(GuiGetStyle(TOGGLE, BORDER + (state*3))), + GetColor(GuiGetStyle(TOGGLE, BASE_COLOR_NORMAL))); + + // Draw internal slider + if (state == STATE_NORMAL) GuiDrawRectangle(slider, 0, BLANK, GetColor(GuiGetStyle(SLIDER, BASE_COLOR_PRESSED))); + else if (state == STATE_FOCUSED) GuiDrawRectangle(slider, 0, BLANK, GetColor(GuiGetStyle(SLIDER, BASE_COLOR_FOCUSED))); + else if (state == STATE_PRESSED) GuiDrawRectangle(slider, 0, BLANK, GetColor(GuiGetStyle(SLIDER, BASE_COLOR_PRESSED))); + + // Draw text in slider + if (text != NULL) + { + Rectangle textBounds = { 0 }; + textBounds.width = (float)GetTextWidth(text); + textBounds.height = (float)GuiGetStyle(DEFAULT, TEXT_SIZE); + textBounds.x = slider.x + slider.width/2 - textBounds.width/2; + textBounds.y = bounds.y + bounds.height/2 - GuiGetStyle(DEFAULT, TEXT_SIZE)/2; + + GuiDrawText(items[*active], textBounds, GuiGetStyle(TOGGLE, TEXT_ALIGNMENT), Fade(GetColor(GuiGetStyle(TOGGLE, TEXT + (state*3))), guiAlpha)); + } + //-------------------------------------------------------------------- + + return result; +} + +// Check Box control, returns 1 when state changed +int GuiCheckBox(Rectangle bounds, const char *text, bool *checked) +{ + int result = 0; + GuiState state = guiState; + + bool temp = false; + if (checked == NULL) checked = &temp; + + Rectangle textBounds = { 0 }; + + if (text != NULL) + { + textBounds.width = (float)GetTextWidth(text) + 2; + textBounds.height = (float)GuiGetStyle(DEFAULT, TEXT_SIZE); + textBounds.x = bounds.x + bounds.width + GuiGetStyle(CHECKBOX, TEXT_PADDING); + textBounds.y = bounds.y + bounds.height/2 - GuiGetStyle(DEFAULT, TEXT_SIZE)/2; + if (GuiGetStyle(CHECKBOX, TEXT_ALIGNMENT) == TEXT_ALIGN_LEFT) textBounds.x = bounds.x - textBounds.width - GuiGetStyle(CHECKBOX, TEXT_PADDING); + } + + // Update control + //-------------------------------------------------------------------- + if ((state != STATE_DISABLED) && !guiLocked && !guiControlExclusiveMode) + { + Vector2 mousePoint = GetMousePosition(); + + Rectangle totalBounds = { + (GuiGetStyle(CHECKBOX, TEXT_ALIGNMENT) == TEXT_ALIGN_LEFT)? textBounds.x : bounds.x, + bounds.y, + bounds.width + textBounds.width + GuiGetStyle(CHECKBOX, TEXT_PADDING), + bounds.height, + }; + + // Check checkbox state + if (CheckCollisionPointRec(mousePoint, totalBounds)) + { + if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) state = STATE_PRESSED; + else state = STATE_FOCUSED; + + if (IsMouseButtonReleased(MOUSE_LEFT_BUTTON)) + { + *checked = !(*checked); + result = 1; + } + } + } + //-------------------------------------------------------------------- + + // Draw control + //-------------------------------------------------------------------- + GuiDrawRectangle(bounds, GuiGetStyle(CHECKBOX, BORDER_WIDTH), GetColor(GuiGetStyle(CHECKBOX, BORDER + (state*3))), BLANK); + + if (*checked) + { + Rectangle check = { bounds.x + GuiGetStyle(CHECKBOX, BORDER_WIDTH) + GuiGetStyle(CHECKBOX, CHECK_PADDING), + bounds.y + GuiGetStyle(CHECKBOX, BORDER_WIDTH) + GuiGetStyle(CHECKBOX, CHECK_PADDING), + bounds.width - 2*(GuiGetStyle(CHECKBOX, BORDER_WIDTH) + GuiGetStyle(CHECKBOX, CHECK_PADDING)), + bounds.height - 2*(GuiGetStyle(CHECKBOX, BORDER_WIDTH) + GuiGetStyle(CHECKBOX, CHECK_PADDING)) }; + GuiDrawRectangle(check, 0, BLANK, GetColor(GuiGetStyle(CHECKBOX, TEXT + state*3))); + } + + GuiDrawText(text, textBounds, (GuiGetStyle(CHECKBOX, TEXT_ALIGNMENT) == TEXT_ALIGN_RIGHT)? TEXT_ALIGN_LEFT : TEXT_ALIGN_RIGHT, GetColor(GuiGetStyle(LABEL, TEXT + (state*3)))); + //-------------------------------------------------------------------- + + return result; +} + +// Combo Box control +int GuiComboBox(Rectangle bounds, const char *text, int *active) +{ + int result = 0; + GuiState state = guiState; + + int temp = 0; + if (active == NULL) active = &temp; + + bounds.width -= (GuiGetStyle(COMBOBOX, COMBO_BUTTON_WIDTH) + GuiGetStyle(COMBOBOX, COMBO_BUTTON_SPACING)); + + Rectangle selector = { (float)bounds.x + bounds.width + GuiGetStyle(COMBOBOX, COMBO_BUTTON_SPACING), + (float)bounds.y, (float)GuiGetStyle(COMBOBOX, COMBO_BUTTON_WIDTH), (float)bounds.height }; + + // Get substrings items from text (items pointers, lengths and count) + int itemCount = 0; + const char **items = GuiTextSplit(text, ';', &itemCount, NULL); + + if (*active < 0) *active = 0; + else if (*active > (itemCount - 1)) *active = itemCount - 1; + + // Update control + //-------------------------------------------------------------------- + if ((state != STATE_DISABLED) && !guiLocked && (itemCount > 1) && !guiControlExclusiveMode) + { + Vector2 mousePoint = GetMousePosition(); + + if (CheckCollisionPointRec(mousePoint, bounds) || + CheckCollisionPointRec(mousePoint, selector)) + { + if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) + { + *active += 1; + if (*active >= itemCount) *active = 0; // Cyclic combobox + } + + if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) state = STATE_PRESSED; + else state = STATE_FOCUSED; + } + } + //-------------------------------------------------------------------- + + // Draw control + //-------------------------------------------------------------------- + // Draw combo box main + GuiDrawRectangle(bounds, GuiGetStyle(COMBOBOX, BORDER_WIDTH), GetColor(GuiGetStyle(COMBOBOX, BORDER + (state*3))), GetColor(GuiGetStyle(COMBOBOX, BASE + (state*3)))); + GuiDrawText(items[*active], GetTextBounds(COMBOBOX, bounds), GuiGetStyle(COMBOBOX, TEXT_ALIGNMENT), GetColor(GuiGetStyle(COMBOBOX, TEXT + (state*3)))); + + // Draw selector using a custom button + // NOTE: BORDER_WIDTH and TEXT_ALIGNMENT forced values + int tempBorderWidth = GuiGetStyle(BUTTON, BORDER_WIDTH); + int tempTextAlign = GuiGetStyle(BUTTON, TEXT_ALIGNMENT); + GuiSetStyle(BUTTON, BORDER_WIDTH, 1); + GuiSetStyle(BUTTON, TEXT_ALIGNMENT, TEXT_ALIGN_CENTER); + + GuiButton(selector, TextFormat("%i/%i", *active + 1, itemCount)); + + GuiSetStyle(BUTTON, TEXT_ALIGNMENT, tempTextAlign); + GuiSetStyle(BUTTON, BORDER_WIDTH, tempBorderWidth); + //-------------------------------------------------------------------- + + return result; +} + +// Dropdown Box control +// NOTE: Returns mouse click +int GuiDropdownBox(Rectangle bounds, const char *text, int *active, bool editMode) +{ + int result = 0; + GuiState state = guiState; + + int temp = 0; + if (active == NULL) active = &temp; + + int itemSelected = *active; + int itemFocused = -1; + + int direction = 0; // Dropdown box open direction: down (default) + if (GuiGetStyle(DROPDOWNBOX, DROPDOWN_ROLL_UP) == 1) direction = 1; // Up + + // Get substrings items from text (items pointers, lengths and count) + int itemCount = 0; + const char **items = GuiTextSplit(text, ';', &itemCount, NULL); + + Rectangle boundsOpen = bounds; + boundsOpen.height = (itemCount + 1)*(bounds.height + GuiGetStyle(DROPDOWNBOX, DROPDOWN_ITEMS_SPACING)); + if (direction == 1) boundsOpen.y -= itemCount*(bounds.height + GuiGetStyle(DROPDOWNBOX, DROPDOWN_ITEMS_SPACING)) + GuiGetStyle(DROPDOWNBOX, DROPDOWN_ITEMS_SPACING); + + Rectangle itemBounds = bounds; + + // Update control + //-------------------------------------------------------------------- + if ((state != STATE_DISABLED) && (editMode || !guiLocked) && (itemCount > 1) && !guiControlExclusiveMode) + { + Vector2 mousePoint = GetMousePosition(); + + if (editMode) + { + state = STATE_PRESSED; + + // Check if mouse has been pressed or released outside limits + if (!CheckCollisionPointRec(mousePoint, boundsOpen)) + { + if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON) || IsMouseButtonReleased(MOUSE_LEFT_BUTTON)) result = 1; + } + + // Check if already selected item has been pressed again + if (CheckCollisionPointRec(mousePoint, bounds) && IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) result = 1; + + // Check focused and selected item + for (int i = 0; i < itemCount; i++) + { + // Update item rectangle y position for next item + if (direction == 0) itemBounds.y += (bounds.height + GuiGetStyle(DROPDOWNBOX, DROPDOWN_ITEMS_SPACING)); + else itemBounds.y -= (bounds.height + GuiGetStyle(DROPDOWNBOX, DROPDOWN_ITEMS_SPACING)); + + if (CheckCollisionPointRec(mousePoint, itemBounds)) + { + itemFocused = i; + if (IsMouseButtonReleased(MOUSE_LEFT_BUTTON)) + { + itemSelected = i; + result = 1; // Item selected + } + break; + } + } + + itemBounds = bounds; + } + else + { + if (CheckCollisionPointRec(mousePoint, bounds)) + { + if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) + { + result = 1; + state = STATE_PRESSED; + } + else state = STATE_FOCUSED; + } + } + } + //-------------------------------------------------------------------- + + // Draw control + //-------------------------------------------------------------------- + if (editMode) GuiPanel(boundsOpen, NULL); + + GuiDrawRectangle(bounds, GuiGetStyle(DROPDOWNBOX, BORDER_WIDTH), GetColor(GuiGetStyle(DROPDOWNBOX, BORDER + state*3)), GetColor(GuiGetStyle(DROPDOWNBOX, BASE + state*3))); + GuiDrawText(items[itemSelected], GetTextBounds(DROPDOWNBOX, bounds), GuiGetStyle(DROPDOWNBOX, TEXT_ALIGNMENT), GetColor(GuiGetStyle(DROPDOWNBOX, TEXT + state*3))); + + if (editMode) + { + // Draw visible items + for (int i = 0; i < itemCount; i++) + { + // Update item rectangle y position for next item + if (direction == 0) itemBounds.y += (bounds.height + GuiGetStyle(DROPDOWNBOX, DROPDOWN_ITEMS_SPACING)); + else itemBounds.y -= (bounds.height + GuiGetStyle(DROPDOWNBOX, DROPDOWN_ITEMS_SPACING)); + + if (i == itemSelected) + { + GuiDrawRectangle(itemBounds, GuiGetStyle(DROPDOWNBOX, BORDER_WIDTH), GetColor(GuiGetStyle(DROPDOWNBOX, BORDER_COLOR_PRESSED)), GetColor(GuiGetStyle(DROPDOWNBOX, BASE_COLOR_PRESSED))); + GuiDrawText(items[i], GetTextBounds(DROPDOWNBOX, itemBounds), GuiGetStyle(DROPDOWNBOX, TEXT_ALIGNMENT), GetColor(GuiGetStyle(DROPDOWNBOX, TEXT_COLOR_PRESSED))); + } + else if (i == itemFocused) + { + GuiDrawRectangle(itemBounds, GuiGetStyle(DROPDOWNBOX, BORDER_WIDTH), GetColor(GuiGetStyle(DROPDOWNBOX, BORDER_COLOR_FOCUSED)), GetColor(GuiGetStyle(DROPDOWNBOX, BASE_COLOR_FOCUSED))); + GuiDrawText(items[i], GetTextBounds(DROPDOWNBOX, itemBounds), GuiGetStyle(DROPDOWNBOX, TEXT_ALIGNMENT), GetColor(GuiGetStyle(DROPDOWNBOX, TEXT_COLOR_FOCUSED))); + } + else GuiDrawText(items[i], GetTextBounds(DROPDOWNBOX, itemBounds), GuiGetStyle(DROPDOWNBOX, TEXT_ALIGNMENT), GetColor(GuiGetStyle(DROPDOWNBOX, TEXT_COLOR_NORMAL))); + } + } + + if (!GuiGetStyle(DROPDOWNBOX, DROPDOWN_ARROW_HIDDEN)) + { + // Draw arrows (using icon if available) +#if defined(RAYGUI_NO_ICONS) + GuiDrawText("v", RAYGUI_CLITERAL(Rectangle){ bounds.x + bounds.width - GuiGetStyle(DROPDOWNBOX, ARROW_PADDING), bounds.y + bounds.height/2 - 2, 10, 10 }, + TEXT_ALIGN_CENTER, GetColor(GuiGetStyle(DROPDOWNBOX, TEXT + (state*3)))); +#else + GuiDrawText(direction? "#121#" : "#120#", RAYGUI_CLITERAL(Rectangle){ bounds.x + bounds.width - GuiGetStyle(DROPDOWNBOX, ARROW_PADDING), bounds.y + bounds.height/2 - 6, 10, 10 }, + TEXT_ALIGN_CENTER, GetColor(GuiGetStyle(DROPDOWNBOX, TEXT + (state*3)))); // ICON_ARROW_DOWN_FILL +#endif + } + //-------------------------------------------------------------------- + + *active = itemSelected; + + // TODO: Use result to return more internal states: mouse-press out-of-bounds, mouse-press over selected-item... + return result; // Mouse click: result = 1 +} + +// Text Box control +// NOTE: Returns true on ENTER pressed (useful for data validation) +int GuiTextBox(Rectangle bounds, char *text, int textSize, bool editMode) +{ + #if !defined(RAYGUI_TEXTBOX_AUTO_CURSOR_COOLDOWN) + #define RAYGUI_TEXTBOX_AUTO_CURSOR_COOLDOWN 40 // Frames to wait for autocursor movement + #endif + #if !defined(RAYGUI_TEXTBOX_AUTO_CURSOR_DELAY) + #define RAYGUI_TEXTBOX_AUTO_CURSOR_DELAY 1 // Frames delay for autocursor movement + #endif + + int result = 0; + GuiState state = guiState; + + bool multiline = false; // TODO: Consider multiline text input + int wrapMode = GuiGetStyle(DEFAULT, TEXT_WRAP_MODE); + + Rectangle textBounds = GetTextBounds(TEXTBOX, bounds); + int textLength = (int)strlen(text); // Get current text length + int thisCursorIndex = textBoxCursorIndex; + if (thisCursorIndex > textLength) thisCursorIndex = textLength; + int textWidth = GetTextWidth(text) - GetTextWidth(text + thisCursorIndex); + int textIndexOffset = 0; // Text index offset to start drawing in the box + + // Cursor rectangle + // NOTE: Position X value should be updated + Rectangle cursor = { + textBounds.x + textWidth + GuiGetStyle(DEFAULT, TEXT_SPACING), + textBounds.y + textBounds.height/2 - GuiGetStyle(DEFAULT, TEXT_SIZE), + 2, + (float)GuiGetStyle(DEFAULT, TEXT_SIZE)*2 + }; + + if (cursor.height >= bounds.height) cursor.height = bounds.height - GuiGetStyle(TEXTBOX, BORDER_WIDTH)*2; + if (cursor.y < (bounds.y + GuiGetStyle(TEXTBOX, BORDER_WIDTH))) cursor.y = bounds.y + GuiGetStyle(TEXTBOX, BORDER_WIDTH); + + // Mouse cursor rectangle + // NOTE: Initialized outside of screen + Rectangle mouseCursor = cursor; + mouseCursor.x = -1; + mouseCursor.width = 1; + + // Auto-cursor movement logic + // NOTE: Cursor moves automatically when key down after some time + if (IsKeyDown(KEY_LEFT) || IsKeyDown(KEY_RIGHT) || IsKeyDown(KEY_UP) || IsKeyDown(KEY_DOWN) || IsKeyDown(KEY_BACKSPACE) || IsKeyDown(KEY_DELETE)) autoCursorCooldownCounter++; + else + { + autoCursorCooldownCounter = 0; // GLOBAL: Cursor cooldown counter + autoCursorDelayCounter = 0; // GLOBAL: Cursor delay counter + } + + // Blink-cursor frame counter + //if (!autoCursorMode) blinkCursorFrameCounter++; + //else blinkCursorFrameCounter = 0; + + // Update control + //-------------------------------------------------------------------- + // WARNING: Text editing is only supported under certain conditions: + if ((state != STATE_DISABLED) && // Control not disabled + !GuiGetStyle(TEXTBOX, TEXT_READONLY) && // TextBox not on read-only mode + !guiLocked && // Gui not locked + !guiControlExclusiveMode && // No gui slider on dragging + (wrapMode == TEXT_WRAP_NONE)) // No wrap mode + { + Vector2 mousePosition = GetMousePosition(); + + if (editMode) + { + state = STATE_PRESSED; + + if (textBoxCursorIndex > textLength) textBoxCursorIndex = textLength; + + // If text does not fit in the textbox and current cursor position is out of bounds, + // we add an index offset to text for drawing only what requires depending on cursor + while (textWidth >= textBounds.width) + { + int nextCodepointSize = 0; + GetCodepointNext(text + textIndexOffset, &nextCodepointSize); + + textIndexOffset += nextCodepointSize; + + textWidth = GetTextWidth(text + textIndexOffset) - GetTextWidth(text + textBoxCursorIndex); + } + + int codepoint = GetCharPressed(); // Get Unicode codepoint + if (multiline && IsKeyPressed(KEY_ENTER)) codepoint = (int)'\n'; + + // Encode codepoint as UTF-8 + int codepointSize = 0; + const char *charEncoded = CodepointToUTF8(codepoint, &codepointSize); + + // Add codepoint to text, at current cursor position + // NOTE: Make sure we do not overflow buffer size + if (((multiline && (codepoint == (int)'\n')) || (codepoint >= 32)) && ((textLength + codepointSize) < textSize)) + { + // Move forward data from cursor position + for (int i = (textLength + codepointSize); i > textBoxCursorIndex; i--) text[i] = text[i - codepointSize]; + + // Add new codepoint in current cursor position + for (int i = 0; i < codepointSize; i++) text[textBoxCursorIndex + i] = charEncoded[i]; + + textBoxCursorIndex += codepointSize; + textLength += codepointSize; + + // Make sure text last character is EOL + text[textLength] = '\0'; + } + + // Move cursor to start + if ((textLength > 0) && IsKeyPressed(KEY_HOME)) textBoxCursorIndex = 0; + + // Move cursor to end + if ((textLength > textBoxCursorIndex) && IsKeyPressed(KEY_END)) textBoxCursorIndex = textLength; + + // Delete codepoint from text, after current cursor position + if ((textLength > textBoxCursorIndex) && (IsKeyPressed(KEY_DELETE) || (IsKeyDown(KEY_DELETE) && (autoCursorCooldownCounter >= RAYGUI_TEXTBOX_AUTO_CURSOR_COOLDOWN)))) + { + autoCursorDelayCounter++; + + if (IsKeyPressed(KEY_DELETE) || (autoCursorDelayCounter%RAYGUI_TEXTBOX_AUTO_CURSOR_DELAY) == 0) // Delay every movement some frames + { + int nextCodepointSize = 0; + GetCodepointNext(text + textBoxCursorIndex, &nextCodepointSize); + + // Move backward text from cursor position + for (int i = textBoxCursorIndex; i < textLength; i++) text[i] = text[i + nextCodepointSize]; + + textLength -= codepointSize; + if (textBoxCursorIndex > textLength) textBoxCursorIndex = textLength; + + // Make sure text last character is EOL + text[textLength] = '\0'; + } + } + + // Delete related codepoints from text, before current cursor position + if ((textLength > 0) && IsKeyPressed(KEY_BACKSPACE) && (IsKeyDown(KEY_LEFT_CONTROL) || IsKeyDown(KEY_RIGHT_CONTROL))) + { + int i = textBoxCursorIndex - 1; + int accCodepointSize = 0; + + // Move cursor to the end of word if on space already + while ((i > 0) && isspace(text[i])) + { + int prevCodepointSize = 0; + GetCodepointPrevious(text + i, &prevCodepointSize); + i -= prevCodepointSize; + accCodepointSize += prevCodepointSize; + } + + // Move cursor to the start of the word + while ((i > 0) && !isspace(text[i])) + { + int prevCodepointSize = 0; + GetCodepointPrevious(text + i, &prevCodepointSize); + i -= prevCodepointSize; + accCodepointSize += prevCodepointSize; + } + + // Move forward text from cursor position + for (int j = (textBoxCursorIndex - accCodepointSize); j < textLength; j++) text[j] = text[j + accCodepointSize]; + + // Prevent cursor index from decrementing past 0 + if (textBoxCursorIndex > 0) + { + textBoxCursorIndex -= accCodepointSize; + textLength -= accCodepointSize; + } + + // Make sure text last character is EOL + text[textLength] = '\0'; + } + else if ((textLength > 0) && (IsKeyPressed(KEY_BACKSPACE) || (IsKeyDown(KEY_BACKSPACE) && (autoCursorCooldownCounter >= RAYGUI_TEXTBOX_AUTO_CURSOR_COOLDOWN)))) + { + autoCursorDelayCounter++; + + if (IsKeyPressed(KEY_BACKSPACE) || (autoCursorDelayCounter%RAYGUI_TEXTBOX_AUTO_CURSOR_DELAY) == 0) // Delay every movement some frames + { + int prevCodepointSize = 0; + + // Prevent cursor index from decrementing past 0 + if (textBoxCursorIndex > 0) + { + GetCodepointPrevious(text + textBoxCursorIndex, &prevCodepointSize); + + // Move backward text from cursor position + for (int i = (textBoxCursorIndex - prevCodepointSize); i < textLength; i++) text[i] = text[i + prevCodepointSize]; + + textBoxCursorIndex -= codepointSize; + textLength -= codepointSize; + } + + // Make sure text last character is EOL + text[textLength] = '\0'; + } + } + + // Move cursor position with keys + if (IsKeyPressed(KEY_LEFT) || (IsKeyDown(KEY_LEFT) && (autoCursorCooldownCounter > RAYGUI_TEXTBOX_AUTO_CURSOR_COOLDOWN))) + { + autoCursorDelayCounter++; + + if (IsKeyPressed(KEY_LEFT) || (autoCursorDelayCounter%RAYGUI_TEXTBOX_AUTO_CURSOR_DELAY) == 0) // Delay every movement some frames + { + int prevCodepointSize = 0; + if (textBoxCursorIndex > 0) GetCodepointPrevious(text + textBoxCursorIndex, &prevCodepointSize); + + if (textBoxCursorIndex >= prevCodepointSize) textBoxCursorIndex -= prevCodepointSize; + } + } + else if (IsKeyPressed(KEY_RIGHT) || (IsKeyDown(KEY_RIGHT) && (autoCursorCooldownCounter > RAYGUI_TEXTBOX_AUTO_CURSOR_COOLDOWN))) + { + autoCursorDelayCounter++; + + if (IsKeyPressed(KEY_RIGHT) || (autoCursorDelayCounter%RAYGUI_TEXTBOX_AUTO_CURSOR_DELAY) == 0) // Delay every movement some frames + { + int nextCodepointSize = 0; + GetCodepointNext(text + textBoxCursorIndex, &nextCodepointSize); + + if ((textBoxCursorIndex + nextCodepointSize) <= textLength) textBoxCursorIndex += nextCodepointSize; + } + } + + // Move cursor position with mouse + if (CheckCollisionPointRec(mousePosition, textBounds)) // Mouse hover text + { + float scaleFactor = (float)GuiGetStyle(DEFAULT, TEXT_SIZE)/(float)guiFont.baseSize; + int codepointIndex = 0; + float glyphWidth = 0.0f; + float widthToMouseX = 0; + int mouseCursorIndex = 0; + + for (int i = textIndexOffset; i < textLength; i++) + { + codepoint = GetCodepointNext(&text[i], &codepointSize); + codepointIndex = GetGlyphIndex(guiFont, codepoint); + + if (guiFont.glyphs[codepointIndex].advanceX == 0) glyphWidth = ((float)guiFont.recs[codepointIndex].width*scaleFactor); + else glyphWidth = ((float)guiFont.glyphs[codepointIndex].advanceX*scaleFactor); + + if (mousePosition.x <= (textBounds.x + (widthToMouseX + glyphWidth/2))) + { + mouseCursor.x = textBounds.x + widthToMouseX; + mouseCursorIndex = i; + break; + } + + widthToMouseX += (glyphWidth + (float)GuiGetStyle(DEFAULT, TEXT_SPACING)); + } + + // Check if mouse cursor is at the last position + int textEndWidth = GetTextWidth(text + textIndexOffset); + if (GetMousePosition().x >= (textBounds.x + textEndWidth - glyphWidth/2)) + { + mouseCursor.x = textBounds.x + textEndWidth; + mouseCursorIndex = textLength; + } + + // Place cursor at required index on mouse click + if ((mouseCursor.x >= 0) && IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) + { + cursor.x = mouseCursor.x; + textBoxCursorIndex = mouseCursorIndex; + } + } + else mouseCursor.x = -1; + + // Recalculate cursor position.y depending on textBoxCursorIndex + cursor.x = bounds.x + GuiGetStyle(TEXTBOX, TEXT_PADDING) + GetTextWidth(text + textIndexOffset) - GetTextWidth(text + textBoxCursorIndex) + GuiGetStyle(DEFAULT, TEXT_SPACING); + //if (multiline) cursor.y = GetTextLines() + + // Finish text editing on ENTER or mouse click outside bounds + if ((!multiline && IsKeyPressed(KEY_ENTER)) || + (!CheckCollisionPointRec(mousePosition, bounds) && IsMouseButtonPressed(MOUSE_LEFT_BUTTON))) + { + textBoxCursorIndex = 0; // GLOBAL: Reset the shared cursor index + result = 1; + } + } + else + { + if (CheckCollisionPointRec(mousePosition, bounds)) + { + state = STATE_FOCUSED; + + if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) + { + textBoxCursorIndex = textLength; // GLOBAL: Place cursor index to the end of current text + result = 1; + } + } + } + } + //-------------------------------------------------------------------- + + // Draw control + //-------------------------------------------------------------------- + if (state == STATE_PRESSED) + { + GuiDrawRectangle(bounds, GuiGetStyle(TEXTBOX, BORDER_WIDTH), GetColor(GuiGetStyle(TEXTBOX, BORDER + (state*3))), GetColor(GuiGetStyle(TEXTBOX, BASE_COLOR_PRESSED))); + } + else if (state == STATE_DISABLED) + { + GuiDrawRectangle(bounds, GuiGetStyle(TEXTBOX, BORDER_WIDTH), GetColor(GuiGetStyle(TEXTBOX, BORDER + (state*3))), GetColor(GuiGetStyle(TEXTBOX, BASE_COLOR_DISABLED))); + } + else GuiDrawRectangle(bounds, GuiGetStyle(TEXTBOX, BORDER_WIDTH), GetColor(GuiGetStyle(TEXTBOX, BORDER + (state*3))), BLANK); + + // Draw text considering index offset if required + // NOTE: Text index offset depends on cursor position + GuiDrawText(text + textIndexOffset, textBounds, GuiGetStyle(TEXTBOX, TEXT_ALIGNMENT), GetColor(GuiGetStyle(TEXTBOX, TEXT + (state*3)))); + + // Draw cursor + if (editMode && !GuiGetStyle(TEXTBOX, TEXT_READONLY)) + { + //if (autoCursorMode || ((blinkCursorFrameCounter/40)%2 == 0)) + GuiDrawRectangle(cursor, 0, BLANK, GetColor(GuiGetStyle(TEXTBOX, BORDER_COLOR_PRESSED))); + + // Draw mouse position cursor (if required) + if (mouseCursor.x >= 0) GuiDrawRectangle(mouseCursor, 0, BLANK, GetColor(GuiGetStyle(TEXTBOX, BORDER_COLOR_PRESSED))); + } + else if (state == STATE_FOCUSED) GuiTooltip(bounds); + //-------------------------------------------------------------------- + + return result; // Mouse button pressed: result = 1 +} + +/* +// Text Box control with multiple lines and word-wrap +// NOTE: This text-box is readonly, no editing supported by default +bool GuiTextBoxMulti(Rectangle bounds, char *text, int textSize, bool editMode) +{ + bool pressed = false; + + GuiSetStyle(TEXTBOX, TEXT_READONLY, 1); + GuiSetStyle(DEFAULT, TEXT_WRAP_MODE, TEXT_WRAP_WORD); // WARNING: If wrap mode enabled, text editing is not supported + GuiSetStyle(DEFAULT, TEXT_ALIGNMENT_VERTICAL, TEXT_ALIGN_TOP); + + // TODO: Implement methods to calculate cursor position properly + pressed = GuiTextBox(bounds, text, textSize, editMode); + + GuiSetStyle(DEFAULT, TEXT_ALIGNMENT_VERTICAL, TEXT_ALIGN_MIDDLE); + GuiSetStyle(DEFAULT, TEXT_WRAP_MODE, TEXT_WRAP_NONE); + GuiSetStyle(TEXTBOX, TEXT_READONLY, 0); + + return pressed; +} +*/ + +// Spinner control, returns selected value +int GuiSpinner(Rectangle bounds, const char *text, int *value, int minValue, int maxValue, bool editMode) +{ + int result = 1; + GuiState state = guiState; + + int tempValue = *value; + + Rectangle spinner = { bounds.x + GuiGetStyle(SPINNER, SPIN_BUTTON_WIDTH) + GuiGetStyle(SPINNER, SPIN_BUTTON_SPACING), bounds.y, + bounds.width - 2*(GuiGetStyle(SPINNER, SPIN_BUTTON_WIDTH) + GuiGetStyle(SPINNER, SPIN_BUTTON_SPACING)), bounds.height }; + Rectangle leftButtonBound = { (float)bounds.x, (float)bounds.y, (float)GuiGetStyle(SPINNER, SPIN_BUTTON_WIDTH), (float)bounds.height }; + Rectangle rightButtonBound = { (float)bounds.x + bounds.width - GuiGetStyle(SPINNER, SPIN_BUTTON_WIDTH), (float)bounds.y, (float)GuiGetStyle(SPINNER, SPIN_BUTTON_WIDTH), (float)bounds.height }; + + Rectangle textBounds = { 0 }; + if (text != NULL) + { + textBounds.width = (float)GetTextWidth(text) + 2; + textBounds.height = (float)GuiGetStyle(DEFAULT, TEXT_SIZE); + textBounds.x = bounds.x + bounds.width + GuiGetStyle(SPINNER, TEXT_PADDING); + textBounds.y = bounds.y + bounds.height/2 - GuiGetStyle(DEFAULT, TEXT_SIZE)/2; + if (GuiGetStyle(SPINNER, TEXT_ALIGNMENT) == TEXT_ALIGN_LEFT) textBounds.x = bounds.x - textBounds.width - GuiGetStyle(SPINNER, TEXT_PADDING); + } + + // Update control + //-------------------------------------------------------------------- + if ((state != STATE_DISABLED) && !guiLocked && !guiControlExclusiveMode) + { + Vector2 mousePoint = GetMousePosition(); + + // Check spinner state + if (CheckCollisionPointRec(mousePoint, bounds)) + { + if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) state = STATE_PRESSED; + else state = STATE_FOCUSED; + } + } + +#if defined(RAYGUI_NO_ICONS) + if (GuiButton(leftButtonBound, "<")) tempValue--; + if (GuiButton(rightButtonBound, ">")) tempValue++; +#else + if (GuiButton(leftButtonBound, GuiIconText(ICON_ARROW_LEFT_FILL, NULL))) tempValue--; + if (GuiButton(rightButtonBound, GuiIconText(ICON_ARROW_RIGHT_FILL, NULL))) tempValue++; +#endif + + if (!editMode) + { + if (tempValue < minValue) tempValue = minValue; + if (tempValue > maxValue) tempValue = maxValue; + } + //-------------------------------------------------------------------- + + // Draw control + //-------------------------------------------------------------------- + result = GuiValueBox(spinner, NULL, &tempValue, minValue, maxValue, editMode); + + // Draw value selector custom buttons + // NOTE: BORDER_WIDTH and TEXT_ALIGNMENT forced values + int tempBorderWidth = GuiGetStyle(BUTTON, BORDER_WIDTH); + int tempTextAlign = GuiGetStyle(BUTTON, TEXT_ALIGNMENT); + GuiSetStyle(BUTTON, BORDER_WIDTH, GuiGetStyle(SPINNER, BORDER_WIDTH)); + GuiSetStyle(BUTTON, TEXT_ALIGNMENT, TEXT_ALIGN_CENTER); + + GuiSetStyle(BUTTON, TEXT_ALIGNMENT, tempTextAlign); + GuiSetStyle(BUTTON, BORDER_WIDTH, tempBorderWidth); + + // Draw text label if provided + GuiDrawText(text, textBounds, (GuiGetStyle(SPINNER, TEXT_ALIGNMENT) == TEXT_ALIGN_RIGHT)? TEXT_ALIGN_LEFT : TEXT_ALIGN_RIGHT, GetColor(GuiGetStyle(LABEL, TEXT + (state*3)))); + //-------------------------------------------------------------------- + + *value = tempValue; + return result; +} + +// Value Box control, updates input text with numbers +// NOTE: Requires static variables: frameCounter +int GuiValueBox(Rectangle bounds, const char *text, int *value, int minValue, int maxValue, bool editMode) +{ + #if !defined(RAYGUI_VALUEBOX_MAX_CHARS) + #define RAYGUI_VALUEBOX_MAX_CHARS 32 + #endif + + int result = 0; + GuiState state = guiState; + + char textValue[RAYGUI_VALUEBOX_MAX_CHARS + 1] = "\0"; + sprintf(textValue, "%i", *value); + + Rectangle textBounds = { 0 }; + if (text != NULL) + { + textBounds.width = (float)GetTextWidth(text) + 2; + textBounds.height = (float)GuiGetStyle(DEFAULT, TEXT_SIZE); + textBounds.x = bounds.x + bounds.width + GuiGetStyle(VALUEBOX, TEXT_PADDING); + textBounds.y = bounds.y + bounds.height/2 - GuiGetStyle(DEFAULT, TEXT_SIZE)/2; + if (GuiGetStyle(VALUEBOX, TEXT_ALIGNMENT) == TEXT_ALIGN_LEFT) textBounds.x = bounds.x - textBounds.width - GuiGetStyle(VALUEBOX, TEXT_PADDING); + } + + // Update control + //-------------------------------------------------------------------- + if ((state != STATE_DISABLED) && !guiLocked && !guiControlExclusiveMode) + { + Vector2 mousePoint = GetMousePosition(); + + bool valueHasChanged = false; + + if (editMode) + { + state = STATE_PRESSED; + + int keyCount = (int)strlen(textValue); + + // Only allow keys in range [48..57] + if (keyCount < RAYGUI_VALUEBOX_MAX_CHARS) + { + if (GetTextWidth(textValue) < bounds.width) + { + int key = GetCharPressed(); + if ((key >= 48) && (key <= 57)) + { + textValue[keyCount] = (char)key; + keyCount++; + valueHasChanged = true; + } + } + } + + // Delete text + if (keyCount > 0) + { + if (IsKeyPressed(KEY_BACKSPACE)) + { + keyCount--; + textValue[keyCount] = '\0'; + valueHasChanged = true; + } + } + + if (valueHasChanged) *value = TextToInteger(textValue); + + // NOTE: We are not clamp values until user input finishes + //if (*value > maxValue) *value = maxValue; + //else if (*value < minValue) *value = minValue; + + if ((IsKeyPressed(KEY_ENTER) || IsKeyPressed(KEY_KP_ENTER)) || (!CheckCollisionPointRec(mousePoint, bounds) && IsMouseButtonPressed(MOUSE_LEFT_BUTTON))) + { + if (*value > maxValue) *value = maxValue; + else if (*value < minValue) *value = minValue; + + result = 1; + } + } + else + { + if (*value > maxValue) *value = maxValue; + else if (*value < minValue) *value = minValue; + + if (CheckCollisionPointRec(mousePoint, bounds)) + { + state = STATE_FOCUSED; + if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) result = 1; + } + } + } + //-------------------------------------------------------------------- + + // Draw control + //-------------------------------------------------------------------- + Color baseColor = BLANK; + if (state == STATE_PRESSED) baseColor = GetColor(GuiGetStyle(VALUEBOX, BASE_COLOR_PRESSED)); + else if (state == STATE_DISABLED) baseColor = GetColor(GuiGetStyle(VALUEBOX, BASE_COLOR_DISABLED)); + + GuiDrawRectangle(bounds, GuiGetStyle(VALUEBOX, BORDER_WIDTH), GetColor(GuiGetStyle(VALUEBOX, BORDER + (state*3))), baseColor); + GuiDrawText(textValue, GetTextBounds(VALUEBOX, bounds), TEXT_ALIGN_CENTER, GetColor(GuiGetStyle(VALUEBOX, TEXT + (state*3)))); + + // Draw cursor + if (editMode) + { + // NOTE: ValueBox internal text is always centered + Rectangle cursor = { bounds.x + GetTextWidth(textValue)/2 + bounds.width/2 + 1, bounds.y + 2*GuiGetStyle(VALUEBOX, BORDER_WIDTH), 4, bounds.height - 4*GuiGetStyle(VALUEBOX, BORDER_WIDTH) }; + GuiDrawRectangle(cursor, 0, BLANK, GetColor(GuiGetStyle(VALUEBOX, BORDER_COLOR_PRESSED))); + } + + // Draw text label if provided + GuiDrawText(text, textBounds, (GuiGetStyle(VALUEBOX, TEXT_ALIGNMENT) == TEXT_ALIGN_RIGHT)? TEXT_ALIGN_LEFT : TEXT_ALIGN_RIGHT, GetColor(GuiGetStyle(LABEL, TEXT + (state*3)))); + //-------------------------------------------------------------------- + + return result; +} + +// Floating point Value Box control, updates input val_str with numbers +// NOTE: Requires static variables: frameCounter +int GuiValueBoxFloat(Rectangle bounds, const char *text, char *textValue, float *value, bool editMode) +{ + #if !defined(RAYGUI_VALUEBOX_MAX_CHARS) + #define RAYGUI_VALUEBOX_MAX_CHARS 32 + #endif + + int result = 0; + GuiState state = guiState; + + //char textValue[RAYGUI_VALUEBOX_MAX_CHARS + 1] = "\0"; + //sprintf(textValue, "%2.2f", *value); + + Rectangle textBounds = {0}; + if (text != NULL) + { + textBounds.width = (float)GetTextWidth(text) + 2; + textBounds.height = (float)GuiGetStyle(DEFAULT, TEXT_SIZE); + textBounds.x = bounds.x + bounds.width + GuiGetStyle(VALUEBOX, TEXT_PADDING); + textBounds.y = bounds.y + bounds.height/2 - GuiGetStyle(DEFAULT, TEXT_SIZE)/2; + if (GuiGetStyle(VALUEBOX, TEXT_ALIGNMENT) == TEXT_ALIGN_LEFT) textBounds.x = bounds.x - textBounds.width - GuiGetStyle(VALUEBOX, TEXT_PADDING); + } + + // Update control + //-------------------------------------------------------------------- + if ((state != STATE_DISABLED) && !guiLocked && !guiControlExclusiveMode) + { + Vector2 mousePoint = GetMousePosition(); + + bool valueHasChanged = false; + + if (editMode) + { + state = STATE_PRESSED; + + int keyCount = (int)strlen(textValue); + + // Only allow keys in range [48..57] + if (keyCount < RAYGUI_VALUEBOX_MAX_CHARS) + { + if (GetTextWidth(textValue) < bounds.width) + { + int key = GetCharPressed(); + if (((key >= 48) && (key <= 57)) || + (key == '.') || + ((keyCount == 0) && (key == '+')) || // NOTE: Sign can only be in first position + ((keyCount == 0) && (key == '-'))) + { + textValue[keyCount] = (char)key; + keyCount++; + + valueHasChanged = true; + } + } + } + + // Pressed backspace + if (IsKeyPressed(KEY_BACKSPACE)) + { + if (keyCount > 0) + { + keyCount--; + textValue[keyCount] = '\0'; + valueHasChanged = true; + } + } + + if (valueHasChanged) *value = TextToFloat(textValue); + + if ((IsKeyPressed(KEY_ENTER) || IsKeyPressed(KEY_KP_ENTER)) || (!CheckCollisionPointRec(mousePoint, bounds) && IsMouseButtonPressed(MOUSE_LEFT_BUTTON))) result = 1; + } + else + { + if (CheckCollisionPointRec(mousePoint, bounds)) + { + state = STATE_FOCUSED; + if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) result = 1; + } + } + } + //-------------------------------------------------------------------- + + // Draw control + //-------------------------------------------------------------------- + Color baseColor = BLANK; + if (state == STATE_PRESSED) baseColor = GetColor(GuiGetStyle(VALUEBOX, BASE_COLOR_PRESSED)); + else if (state == STATE_DISABLED) baseColor = GetColor(GuiGetStyle(VALUEBOX, BASE_COLOR_DISABLED)); + + GuiDrawRectangle(bounds, GuiGetStyle(VALUEBOX, BORDER_WIDTH), GetColor(GuiGetStyle(VALUEBOX, BORDER + (state*3))), baseColor); + GuiDrawText(textValue, GetTextBounds(VALUEBOX, bounds), TEXT_ALIGN_CENTER, GetColor(GuiGetStyle(VALUEBOX, TEXT + (state*3)))); + + // Draw cursor + if (editMode) + { + // NOTE: ValueBox internal text is always centered + Rectangle cursor = {bounds.x + GetTextWidth(textValue)/2 + bounds.width/2 + 1, + bounds.y + 2*GuiGetStyle(VALUEBOX, BORDER_WIDTH), 4, + bounds.height - 4*GuiGetStyle(VALUEBOX, BORDER_WIDTH)}; + GuiDrawRectangle(cursor, 0, BLANK, GetColor(GuiGetStyle(VALUEBOX, BORDER_COLOR_PRESSED))); + } + + // Draw text label if provided + GuiDrawText(text, textBounds, + (GuiGetStyle(VALUEBOX, TEXT_ALIGNMENT) == TEXT_ALIGN_RIGHT)? TEXT_ALIGN_LEFT : TEXT_ALIGN_RIGHT, + GetColor(GuiGetStyle(LABEL, TEXT + (state*3)))); + //-------------------------------------------------------------------- + + return result; +} + +// Slider control with pro parameters +// NOTE: Other GuiSlider*() controls use this one +int GuiSliderPro(Rectangle bounds, const char *textLeft, const char *textRight, float *value, float minValue, float maxValue, int sliderWidth) +{ + int result = 0; + GuiState state = guiState; + + float temp = (maxValue - minValue)/2.0f; + if (value == NULL) value = &temp; + float oldValue = *value; + + Rectangle slider = { bounds.x, bounds.y + GuiGetStyle(SLIDER, BORDER_WIDTH) + GuiGetStyle(SLIDER, SLIDER_PADDING), + 0, bounds.height - 2*GuiGetStyle(SLIDER, BORDER_WIDTH) - 2*GuiGetStyle(SLIDER, SLIDER_PADDING) }; + + // Update control + //-------------------------------------------------------------------- + if ((state != STATE_DISABLED) && !guiLocked) + { + Vector2 mousePoint = GetMousePosition(); + + if (guiControlExclusiveMode) // Allows to keep dragging outside of bounds + { + if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) + { + if (CHECK_BOUNDS_ID(bounds, guiControlExclusiveRec)) + { + state = STATE_PRESSED; + // Get equivalent value and slider position from mousePosition.x + *value = (maxValue - minValue)*((mousePoint.x - bounds.x - sliderWidth/2)/(bounds.width-sliderWidth)) + minValue; + } + } + else + { + guiControlExclusiveMode = false; + guiControlExclusiveRec = RAYGUI_CLITERAL(Rectangle){ 0, 0, 0, 0 }; + } + } + else if (CheckCollisionPointRec(mousePoint, bounds)) + { + if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) + { + state = STATE_PRESSED; + guiControlExclusiveMode = true; + guiControlExclusiveRec = bounds; // Store bounds as an identifier when dragging starts + + if (!CheckCollisionPointRec(mousePoint, slider)) + { + // Get equivalent value and slider position from mousePosition.x + *value = (maxValue - minValue)*((mousePoint.x - bounds.x - sliderWidth/2)/(bounds.width-sliderWidth)) + minValue; + } + } + else state = STATE_FOCUSED; + } + + if (*value > maxValue) *value = maxValue; + else if (*value < minValue) *value = minValue; + } + + // Control value change check + if (oldValue == *value) result = 0; + else result = 1; + + // Slider bar limits check + float sliderValue = (((*value - minValue)/(maxValue - minValue))*(bounds.width - sliderWidth - 2*GuiGetStyle(SLIDER, BORDER_WIDTH))); + if (sliderWidth > 0) // Slider + { + slider.x += sliderValue; + slider.width = (float)sliderWidth; + if (slider.x <= (bounds.x + GuiGetStyle(SLIDER, BORDER_WIDTH))) slider.x = bounds.x + GuiGetStyle(SLIDER, BORDER_WIDTH); + else if ((slider.x + slider.width) >= (bounds.x + bounds.width)) slider.x = bounds.x + bounds.width - slider.width - GuiGetStyle(SLIDER, BORDER_WIDTH); + } + else if (sliderWidth == 0) // SliderBar + { + slider.x += GuiGetStyle(SLIDER, BORDER_WIDTH); + slider.width = sliderValue; + if (slider.width > bounds.width) slider.width = bounds.width - 2*GuiGetStyle(SLIDER, BORDER_WIDTH); + } + //-------------------------------------------------------------------- + + // Draw control + //-------------------------------------------------------------------- + GuiDrawRectangle(bounds, GuiGetStyle(SLIDER, BORDER_WIDTH), GetColor(GuiGetStyle(SLIDER, BORDER + (state*3))), GetColor(GuiGetStyle(SLIDER, (state != STATE_DISABLED)? BASE_COLOR_NORMAL : BASE_COLOR_DISABLED))); + + // Draw slider internal bar (depends on state) + if (state == STATE_NORMAL) GuiDrawRectangle(slider, 0, BLANK, GetColor(GuiGetStyle(SLIDER, BASE_COLOR_PRESSED))); + else if (state == STATE_FOCUSED) GuiDrawRectangle(slider, 0, BLANK, GetColor(GuiGetStyle(SLIDER, TEXT_COLOR_FOCUSED))); + else if (state == STATE_PRESSED) GuiDrawRectangle(slider, 0, BLANK, GetColor(GuiGetStyle(SLIDER, TEXT_COLOR_PRESSED))); + + // Draw left/right text if provided + if (textLeft != NULL) + { + Rectangle textBounds = { 0 }; + textBounds.width = (float)GetTextWidth(textLeft); + textBounds.height = (float)GuiGetStyle(DEFAULT, TEXT_SIZE); + textBounds.x = bounds.x - textBounds.width - GuiGetStyle(SLIDER, TEXT_PADDING); + textBounds.y = bounds.y + bounds.height/2 - GuiGetStyle(DEFAULT, TEXT_SIZE)/2; + + GuiDrawText(textLeft, textBounds, TEXT_ALIGN_RIGHT, GetColor(GuiGetStyle(SLIDER, TEXT + (state*3)))); + } + + if (textRight != NULL) + { + Rectangle textBounds = { 0 }; + textBounds.width = (float)GetTextWidth(textRight); + textBounds.height = (float)GuiGetStyle(DEFAULT, TEXT_SIZE); + textBounds.x = bounds.x + bounds.width + GuiGetStyle(SLIDER, TEXT_PADDING); + textBounds.y = bounds.y + bounds.height/2 - GuiGetStyle(DEFAULT, TEXT_SIZE)/2; + + GuiDrawText(textRight, textBounds, TEXT_ALIGN_LEFT, GetColor(GuiGetStyle(SLIDER, TEXT + (state*3)))); + } + //-------------------------------------------------------------------- + + return result; +} + +// Slider control extended, returns selected value and has text +int GuiSlider(Rectangle bounds, const char *textLeft, const char *textRight, float *value, float minValue, float maxValue) +{ + return GuiSliderPro(bounds, textLeft, textRight, value, minValue, maxValue, GuiGetStyle(SLIDER, SLIDER_WIDTH)); +} + +// Slider Bar control extended, returns selected value +int GuiSliderBar(Rectangle bounds, const char *textLeft, const char *textRight, float *value, float minValue, float maxValue) +{ + return GuiSliderPro(bounds, textLeft, textRight, value, minValue, maxValue, 0); +} + +// Progress Bar control extended, shows current progress value +int GuiProgressBar(Rectangle bounds, const char *textLeft, const char *textRight, float *value, float minValue, float maxValue) +{ + int result = 0; + GuiState state = guiState; + + float temp = (maxValue - minValue)/2.0f; + if (value == NULL) value = &temp; + + // Progress bar + Rectangle progress = { bounds.x + GuiGetStyle(PROGRESSBAR, BORDER_WIDTH), + bounds.y + GuiGetStyle(PROGRESSBAR, BORDER_WIDTH) + GuiGetStyle(PROGRESSBAR, PROGRESS_PADDING), 0, + bounds.height - 2*GuiGetStyle(PROGRESSBAR, BORDER_WIDTH) - 2*GuiGetStyle(PROGRESSBAR, PROGRESS_PADDING) }; + + // Update control + //-------------------------------------------------------------------- + if (*value > maxValue) *value = maxValue; + + // WARNING: Working with floats could lead to rounding issues + if ((state != STATE_DISABLED)) progress.width = (float)(*value/(maxValue - minValue))*bounds.width - ((*value >= maxValue)? (float)(2*GuiGetStyle(PROGRESSBAR, BORDER_WIDTH)) : 0.0f); + //-------------------------------------------------------------------- + + // Draw control + //-------------------------------------------------------------------- + if (state == STATE_DISABLED) + { + GuiDrawRectangle(bounds, GuiGetStyle(PROGRESSBAR, BORDER_WIDTH), GetColor(GuiGetStyle(PROGRESSBAR, BORDER + (state*3))), BLANK); + } + else + { + if (*value > minValue) + { + // Draw progress bar with colored border, more visual + GuiDrawRectangle(RAYGUI_CLITERAL(Rectangle){ bounds.x, bounds.y, (int)progress.width + (float)GuiGetStyle(PROGRESSBAR, BORDER_WIDTH), (float)GuiGetStyle(PROGRESSBAR, BORDER_WIDTH) }, 0, BLANK, GetColor(GuiGetStyle(PROGRESSBAR, BORDER_COLOR_FOCUSED))); + GuiDrawRectangle(RAYGUI_CLITERAL(Rectangle){ bounds.x, bounds.y + 1, (float)GuiGetStyle(PROGRESSBAR, BORDER_WIDTH), bounds.height - 2 }, 0, BLANK, GetColor(GuiGetStyle(PROGRESSBAR, BORDER_COLOR_FOCUSED))); + GuiDrawRectangle(RAYGUI_CLITERAL(Rectangle){ bounds.x, bounds.y + bounds.height - 1, (int)progress.width + (float)GuiGetStyle(PROGRESSBAR, BORDER_WIDTH), (float)GuiGetStyle(PROGRESSBAR, BORDER_WIDTH) }, 0, BLANK, GetColor(GuiGetStyle(PROGRESSBAR, BORDER_COLOR_FOCUSED))); + } + else GuiDrawRectangle(RAYGUI_CLITERAL(Rectangle){ bounds.x, bounds.y, (float)GuiGetStyle(PROGRESSBAR, BORDER_WIDTH), bounds.height }, 0, BLANK, GetColor(GuiGetStyle(PROGRESSBAR, BORDER_COLOR_NORMAL))); + + if (*value >= maxValue) GuiDrawRectangle(RAYGUI_CLITERAL(Rectangle){ bounds.x + progress.width + 1, bounds.y, (float)GuiGetStyle(PROGRESSBAR, BORDER_WIDTH), bounds.height }, 0, BLANK, GetColor(GuiGetStyle(PROGRESSBAR, BORDER_COLOR_FOCUSED))); + else + { + // Draw borders not yet reached by value + GuiDrawRectangle(RAYGUI_CLITERAL(Rectangle){ bounds.x + (int)progress.width + 1, bounds.y, bounds.width - (int)progress.width - 1, (float)GuiGetStyle(PROGRESSBAR, BORDER_WIDTH) }, 0, BLANK, GetColor(GuiGetStyle(PROGRESSBAR, BORDER_COLOR_NORMAL))); + GuiDrawRectangle(RAYGUI_CLITERAL(Rectangle){ bounds.x + (int)progress.width + 1, bounds.y + bounds.height - 1, bounds.width - (int)progress.width - 1, (float)GuiGetStyle(PROGRESSBAR, BORDER_WIDTH) }, 0, BLANK, GetColor(GuiGetStyle(PROGRESSBAR, BORDER_COLOR_NORMAL))); + GuiDrawRectangle(RAYGUI_CLITERAL(Rectangle){ bounds.x + bounds.width - 1, bounds.y + 1, (float)GuiGetStyle(PROGRESSBAR, BORDER_WIDTH), bounds.height - 2 }, 0, BLANK, GetColor(GuiGetStyle(PROGRESSBAR, BORDER_COLOR_NORMAL))); + } + + // Draw slider internal progress bar (depends on state) + GuiDrawRectangle(progress, 0, BLANK, GetColor(GuiGetStyle(PROGRESSBAR, BASE_COLOR_PRESSED))); + } + + // Draw left/right text if provided + if (textLeft != NULL) + { + Rectangle textBounds = { 0 }; + textBounds.width = (float)GetTextWidth(textLeft); + textBounds.height = (float)GuiGetStyle(DEFAULT, TEXT_SIZE); + textBounds.x = bounds.x - textBounds.width - GuiGetStyle(PROGRESSBAR, TEXT_PADDING); + textBounds.y = bounds.y + bounds.height/2 - GuiGetStyle(DEFAULT, TEXT_SIZE)/2; + + GuiDrawText(textLeft, textBounds, TEXT_ALIGN_RIGHT, GetColor(GuiGetStyle(PROGRESSBAR, TEXT + (state*3)))); + } + + if (textRight != NULL) + { + Rectangle textBounds = { 0 }; + textBounds.width = (float)GetTextWidth(textRight); + textBounds.height = (float)GuiGetStyle(DEFAULT, TEXT_SIZE); + textBounds.x = bounds.x + bounds.width + GuiGetStyle(PROGRESSBAR, TEXT_PADDING); + textBounds.y = bounds.y + bounds.height/2 - GuiGetStyle(DEFAULT, TEXT_SIZE)/2; + + GuiDrawText(textRight, textBounds, TEXT_ALIGN_LEFT, GetColor(GuiGetStyle(PROGRESSBAR, TEXT + (state*3)))); + } + //-------------------------------------------------------------------- + + return result; +} + +// Status Bar control +int GuiStatusBar(Rectangle bounds, const char *text) +{ + int result = 0; + GuiState state = guiState; + + // Draw control + //-------------------------------------------------------------------- + GuiDrawRectangle(bounds, GuiGetStyle(STATUSBAR, BORDER_WIDTH), GetColor(GuiGetStyle(STATUSBAR, BORDER + (state*3))), GetColor(GuiGetStyle(STATUSBAR, BASE + (state*3)))); + GuiDrawText(text, GetTextBounds(STATUSBAR, bounds), GuiGetStyle(STATUSBAR, TEXT_ALIGNMENT), GetColor(GuiGetStyle(STATUSBAR, TEXT + (state*3)))); + //-------------------------------------------------------------------- + + return result; +} + +// Dummy rectangle control, intended for placeholding +int GuiDummyRec(Rectangle bounds, const char *text) +{ + int result = 0; + GuiState state = guiState; + + // Update control + //-------------------------------------------------------------------- + if ((state != STATE_DISABLED) && !guiLocked && !guiControlExclusiveMode) + { + Vector2 mousePoint = GetMousePosition(); + + // Check button state + if (CheckCollisionPointRec(mousePoint, bounds)) + { + if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) state = STATE_PRESSED; + else state = STATE_FOCUSED; + } + } + //-------------------------------------------------------------------- + + // Draw control + //-------------------------------------------------------------------- + GuiDrawRectangle(bounds, 0, BLANK, GetColor(GuiGetStyle(DEFAULT, (state != STATE_DISABLED)? BASE_COLOR_NORMAL : BASE_COLOR_DISABLED))); + GuiDrawText(text, GetTextBounds(DEFAULT, bounds), TEXT_ALIGN_CENTER, GetColor(GuiGetStyle(BUTTON, (state != STATE_DISABLED)? TEXT_COLOR_NORMAL : TEXT_COLOR_DISABLED))); + //------------------------------------------------------------------ + + return result; +} + +// List View control +int GuiListView(Rectangle bounds, const char *text, int *scrollIndex, int *active) +{ + int result = 0; + int itemCount = 0; + const char **items = NULL; + + if (text != NULL) items = GuiTextSplit(text, ';', &itemCount, NULL); + + result = GuiListViewEx(bounds, items, itemCount, scrollIndex, active, NULL); + + return result; +} + +// List View control with extended parameters +int GuiListViewEx(Rectangle bounds, const char **text, int count, int *scrollIndex, int *active, int *focus) +{ + int result = 0; + GuiState state = guiState; + + int itemFocused = (focus == NULL)? -1 : *focus; + int itemSelected = (active == NULL)? -1 : *active; + + // Check if we need a scroll bar + bool useScrollBar = false; + if ((GuiGetStyle(LISTVIEW, LIST_ITEMS_HEIGHT) + GuiGetStyle(LISTVIEW, LIST_ITEMS_SPACING))*count > bounds.height) useScrollBar = true; + + // Define base item rectangle [0] + Rectangle itemBounds = { 0 }; + itemBounds.x = bounds.x + GuiGetStyle(LISTVIEW, LIST_ITEMS_SPACING); + itemBounds.y = bounds.y + GuiGetStyle(LISTVIEW, LIST_ITEMS_SPACING) + GuiGetStyle(DEFAULT, BORDER_WIDTH); + itemBounds.width = bounds.width - 2*GuiGetStyle(LISTVIEW, LIST_ITEMS_SPACING) - GuiGetStyle(DEFAULT, BORDER_WIDTH); + itemBounds.height = (float)GuiGetStyle(LISTVIEW, LIST_ITEMS_HEIGHT); + if (useScrollBar) itemBounds.width -= GuiGetStyle(LISTVIEW, SCROLLBAR_WIDTH); + + // Get items on the list + int visibleItems = (int)bounds.height/(GuiGetStyle(LISTVIEW, LIST_ITEMS_HEIGHT) + GuiGetStyle(LISTVIEW, LIST_ITEMS_SPACING)); + if (visibleItems > count) visibleItems = count; + + int startIndex = (scrollIndex == NULL)? 0 : *scrollIndex; + if ((startIndex < 0) || (startIndex > (count - visibleItems))) startIndex = 0; + int endIndex = startIndex + visibleItems; + + // Update control + //-------------------------------------------------------------------- + if ((state != STATE_DISABLED) && !guiLocked && !guiControlExclusiveMode) + { + Vector2 mousePoint = GetMousePosition(); + + // Check mouse inside list view + if (CheckCollisionPointRec(mousePoint, bounds)) + { + state = STATE_FOCUSED; + + // Check focused and selected item + for (int i = 0; i < visibleItems; i++) + { + if (CheckCollisionPointRec(mousePoint, itemBounds)) + { + itemFocused = startIndex + i; + if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) + { + if (itemSelected == (startIndex + i)) itemSelected = -1; + else itemSelected = startIndex + i; + } + break; + } + + // Update item rectangle y position for next item + itemBounds.y += (GuiGetStyle(LISTVIEW, LIST_ITEMS_HEIGHT) + GuiGetStyle(LISTVIEW, LIST_ITEMS_SPACING)); + } + + if (useScrollBar) + { + int wheelMove = (int)GetMouseWheelMove(); + startIndex -= wheelMove; + + if (startIndex < 0) startIndex = 0; + else if (startIndex > (count - visibleItems)) startIndex = count - visibleItems; + + endIndex = startIndex + visibleItems; + if (endIndex > count) endIndex = count; + } + } + else itemFocused = -1; + + // Reset item rectangle y to [0] + itemBounds.y = bounds.y + GuiGetStyle(LISTVIEW, LIST_ITEMS_SPACING) + GuiGetStyle(DEFAULT, BORDER_WIDTH); + } + //-------------------------------------------------------------------- + + // Draw control + //-------------------------------------------------------------------- + GuiDrawRectangle(bounds, GuiGetStyle(LISTVIEW, BORDER_WIDTH), GetColor(GuiGetStyle(LISTVIEW, BORDER + state*3)), GetColor(GuiGetStyle(DEFAULT, BACKGROUND_COLOR))); // Draw background + + // Draw visible items + for (int i = 0; ((i < visibleItems) && (text != NULL)); i++) + { + GuiDrawRectangle(itemBounds, GuiGetStyle(LISTVIEW, LIST_ITEMS_BORDER_WIDTH), GetColor(GuiGetStyle(LISTVIEW, BORDER_COLOR_NORMAL)), BLANK); + + if (state == STATE_DISABLED) + { + if ((startIndex + i) == itemSelected) GuiDrawRectangle(itemBounds, GuiGetStyle(LISTVIEW, BORDER_WIDTH), GetColor(GuiGetStyle(LISTVIEW, BORDER_COLOR_DISABLED)), GetColor(GuiGetStyle(LISTVIEW, BASE_COLOR_DISABLED))); + + GuiDrawText(text[startIndex + i], GetTextBounds(DEFAULT, itemBounds), GuiGetStyle(LISTVIEW, TEXT_ALIGNMENT), GetColor(GuiGetStyle(LISTVIEW, TEXT_COLOR_DISABLED))); + } + else + { + if (((startIndex + i) == itemSelected) && (active != NULL)) + { + // Draw item selected + GuiDrawRectangle(itemBounds, GuiGetStyle(LISTVIEW, BORDER_WIDTH), GetColor(GuiGetStyle(LISTVIEW, BORDER_COLOR_PRESSED)), GetColor(GuiGetStyle(LISTVIEW, BASE_COLOR_PRESSED))); + GuiDrawText(text[startIndex + i], GetTextBounds(DEFAULT, itemBounds), GuiGetStyle(LISTVIEW, TEXT_ALIGNMENT), GetColor(GuiGetStyle(LISTVIEW, TEXT_COLOR_PRESSED))); + } + else if (((startIndex + i) == itemFocused)) // && (focus != NULL)) // NOTE: We want items focused, despite not returned! + { + // Draw item focused + GuiDrawRectangle(itemBounds, GuiGetStyle(LISTVIEW, BORDER_WIDTH), GetColor(GuiGetStyle(LISTVIEW, BORDER_COLOR_FOCUSED)), GetColor(GuiGetStyle(LISTVIEW, BASE_COLOR_FOCUSED))); + GuiDrawText(text[startIndex + i], GetTextBounds(DEFAULT, itemBounds), GuiGetStyle(LISTVIEW, TEXT_ALIGNMENT), GetColor(GuiGetStyle(LISTVIEW, TEXT_COLOR_FOCUSED))); + } + else + { + // Draw item normal + GuiDrawText(text[startIndex + i], GetTextBounds(DEFAULT, itemBounds), GuiGetStyle(LISTVIEW, TEXT_ALIGNMENT), GetColor(GuiGetStyle(LISTVIEW, TEXT_COLOR_NORMAL))); + } + } + + // Update item rectangle y position for next item + itemBounds.y += (GuiGetStyle(LISTVIEW, LIST_ITEMS_HEIGHT) + GuiGetStyle(LISTVIEW, LIST_ITEMS_SPACING)); + } + + if (useScrollBar) + { + Rectangle scrollBarBounds = { + bounds.x + bounds.width - GuiGetStyle(LISTVIEW, BORDER_WIDTH) - GuiGetStyle(LISTVIEW, SCROLLBAR_WIDTH), + bounds.y + GuiGetStyle(LISTVIEW, BORDER_WIDTH), (float)GuiGetStyle(LISTVIEW, SCROLLBAR_WIDTH), + bounds.height - 2*GuiGetStyle(DEFAULT, BORDER_WIDTH) + }; + + // Calculate percentage of visible items and apply same percentage to scrollbar + float percentVisible = (float)(endIndex - startIndex)/count; + float sliderSize = bounds.height*percentVisible; + + int prevSliderSize = GuiGetStyle(SCROLLBAR, SCROLL_SLIDER_SIZE); // Save default slider size + int prevScrollSpeed = GuiGetStyle(SCROLLBAR, SCROLL_SPEED); // Save default scroll speed + GuiSetStyle(SCROLLBAR, SCROLL_SLIDER_SIZE, (int)sliderSize); // Change slider size + GuiSetStyle(SCROLLBAR, SCROLL_SPEED, count - visibleItems); // Change scroll speed + + startIndex = GuiScrollBar(scrollBarBounds, startIndex, 0, count - visibleItems); + + GuiSetStyle(SCROLLBAR, SCROLL_SPEED, prevScrollSpeed); // Reset scroll speed to default + GuiSetStyle(SCROLLBAR, SCROLL_SLIDER_SIZE, prevSliderSize); // Reset slider size to default + } + //-------------------------------------------------------------------- + + if (active != NULL) *active = itemSelected; + if (focus != NULL) *focus = itemFocused; + if (scrollIndex != NULL) *scrollIndex = startIndex; + + return result; +} + +// Color Panel control - Color (RGBA) variant. +int GuiColorPanel(Rectangle bounds, const char *text, Color *color) +{ + int result = 0; + + Vector3 vcolor = { (float)color->r/255.0f, (float)color->g/255.0f, (float)color->b/255.0f }; + Vector3 hsv = ConvertRGBtoHSV(vcolor); + Vector3 prevHsv = hsv; // workaround to see if GuiColorPanelHSV modifies the hsv. + + GuiColorPanelHSV(bounds, text, &hsv); + + // Check if the hsv was changed, only then change the color. + // This is required, because the Color->HSV->Color conversion has precision errors. + // Thus the assignment from HSV to Color should only be made, if the HSV has a new user-entered value. + // Otherwise GuiColorPanel would often modify it's color without user input. + // TODO: GuiColorPanelHSV could return 1 if the slider was dragged, to simplify this check. + if (hsv.x != prevHsv.x || hsv.y != prevHsv.y || hsv.z != prevHsv.z) + { + Vector3 rgb = ConvertHSVtoRGB(hsv); + + // NOTE: Vector3ToColor() only available on raylib 1.8.1 + *color = RAYGUI_CLITERAL(Color){ (unsigned char)(255.0f*rgb.x), + (unsigned char)(255.0f*rgb.y), + (unsigned char)(255.0f*rgb.z), + color->a }; + } + return result; +} + +// Color Bar Alpha control +// NOTE: Returns alpha value normalized [0..1] +int GuiColorBarAlpha(Rectangle bounds, const char *text, float *alpha) +{ + #if !defined(RAYGUI_COLORBARALPHA_CHECKED_SIZE) + #define RAYGUI_COLORBARALPHA_CHECKED_SIZE 10 + #endif + + int result = 0; + GuiState state = guiState; + Rectangle selector = { (float)bounds.x + (*alpha)*bounds.width - GuiGetStyle(COLORPICKER, HUEBAR_SELECTOR_HEIGHT)/2, (float)bounds.y - GuiGetStyle(COLORPICKER, HUEBAR_SELECTOR_OVERFLOW), (float)GuiGetStyle(COLORPICKER, HUEBAR_SELECTOR_HEIGHT), (float)bounds.height + GuiGetStyle(COLORPICKER, HUEBAR_SELECTOR_OVERFLOW)*2 }; + + // Update control + //-------------------------------------------------------------------- + if ((state != STATE_DISABLED) && !guiLocked) + { + Vector2 mousePoint = GetMousePosition(); + + if (guiControlExclusiveMode) // Allows to keep dragging outside of bounds + { + if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) + { + if (CHECK_BOUNDS_ID(bounds, guiControlExclusiveRec)) + { + state = STATE_PRESSED; + + *alpha = (mousePoint.x - bounds.x)/bounds.width; + if (*alpha <= 0.0f) *alpha = 0.0f; + if (*alpha >= 1.0f) *alpha = 1.0f; + } + } + else + { + guiControlExclusiveMode = false; + guiControlExclusiveRec = RAYGUI_CLITERAL(Rectangle){ 0, 0, 0, 0 }; + } + } + else if (CheckCollisionPointRec(mousePoint, bounds) || CheckCollisionPointRec(mousePoint, selector)) + { + if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) + { + state = STATE_PRESSED; + guiControlExclusiveMode = true; + guiControlExclusiveRec = bounds; // Store bounds as an identifier when dragging starts + + *alpha = (mousePoint.x - bounds.x)/bounds.width; + if (*alpha <= 0.0f) *alpha = 0.0f; + if (*alpha >= 1.0f) *alpha = 1.0f; + //selector.x = bounds.x + (int)(((alpha - 0)/(100 - 0))*(bounds.width - 2*GuiGetStyle(SLIDER, BORDER_WIDTH))) - selector.width/2; + } + else state = STATE_FOCUSED; + } + } + //-------------------------------------------------------------------- + + // Draw control + //-------------------------------------------------------------------- + + // Draw alpha bar: checked background + if (state != STATE_DISABLED) + { + int checksX = (int)bounds.width/RAYGUI_COLORBARALPHA_CHECKED_SIZE; + int checksY = (int)bounds.height/RAYGUI_COLORBARALPHA_CHECKED_SIZE; + + for (int x = 0; x < checksX; x++) + { + for (int y = 0; y < checksY; y++) + { + Rectangle check = { bounds.x + x*RAYGUI_COLORBARALPHA_CHECKED_SIZE, bounds.y + y*RAYGUI_COLORBARALPHA_CHECKED_SIZE, RAYGUI_COLORBARALPHA_CHECKED_SIZE, RAYGUI_COLORBARALPHA_CHECKED_SIZE }; + GuiDrawRectangle(check, 0, BLANK, ((x + y)%2)? Fade(GetColor(GuiGetStyle(COLORPICKER, BORDER_COLOR_DISABLED)), 0.4f) : Fade(GetColor(GuiGetStyle(COLORPICKER, BASE_COLOR_DISABLED)), 0.4f)); + } + } + + DrawRectangleGradientEx(bounds, RAYGUI_CLITERAL(Color){ 255, 255, 255, 0 }, RAYGUI_CLITERAL(Color){ 255, 255, 255, 0 }, Fade(RAYGUI_CLITERAL(Color){ 0, 0, 0, 255 }, guiAlpha), Fade(RAYGUI_CLITERAL(Color){ 0, 0, 0, 255 }, guiAlpha)); + } + else DrawRectangleGradientEx(bounds, Fade(GetColor(GuiGetStyle(COLORPICKER, BASE_COLOR_DISABLED)), 0.1f), Fade(GetColor(GuiGetStyle(COLORPICKER, BASE_COLOR_DISABLED)), 0.1f), Fade(GetColor(GuiGetStyle(COLORPICKER, BORDER_COLOR_DISABLED)), guiAlpha), Fade(GetColor(GuiGetStyle(COLORPICKER, BORDER_COLOR_DISABLED)), guiAlpha)); + + GuiDrawRectangle(bounds, GuiGetStyle(COLORPICKER, BORDER_WIDTH), GetColor(GuiGetStyle(COLORPICKER, BORDER + state*3)), BLANK); + + // Draw alpha bar: selector + GuiDrawRectangle(selector, 0, BLANK, GetColor(GuiGetStyle(COLORPICKER, BORDER + state*3))); + //-------------------------------------------------------------------- + + return result; +} + +// Color Bar Hue control +// Returns hue value normalized [0..1] +// NOTE: Other similar bars (for reference): +// Color GuiColorBarSat() [WHITE->color] +// Color GuiColorBarValue() [BLACK->color], HSV/HSL +// float GuiColorBarLuminance() [BLACK->WHITE] +int GuiColorBarHue(Rectangle bounds, const char *text, float *hue) +{ + int result = 0; + GuiState state = guiState; + Rectangle selector = { (float)bounds.x - GuiGetStyle(COLORPICKER, HUEBAR_SELECTOR_OVERFLOW), (float)bounds.y + (*hue)/360.0f*bounds.height - GuiGetStyle(COLORPICKER, HUEBAR_SELECTOR_HEIGHT)/2, (float)bounds.width + GuiGetStyle(COLORPICKER, HUEBAR_SELECTOR_OVERFLOW)*2, (float)GuiGetStyle(COLORPICKER, HUEBAR_SELECTOR_HEIGHT) }; + + // Update control + //-------------------------------------------------------------------- + if ((state != STATE_DISABLED) && !guiLocked) + { + Vector2 mousePoint = GetMousePosition(); + + if (guiControlExclusiveMode) // Allows to keep dragging outside of bounds + { + if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) + { + if (CHECK_BOUNDS_ID(bounds, guiControlExclusiveRec)) + { + state = STATE_PRESSED; + + *hue = (mousePoint.y - bounds.y)*360/bounds.height; + if (*hue <= 0.0f) *hue = 0.0f; + if (*hue >= 359.0f) *hue = 359.0f; + } + } + else + { + guiControlExclusiveMode = false; + guiControlExclusiveRec = RAYGUI_CLITERAL(Rectangle){ 0, 0, 0, 0 }; + } + } + else if (CheckCollisionPointRec(mousePoint, bounds) || CheckCollisionPointRec(mousePoint, selector)) + { + if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) + { + state = STATE_PRESSED; + guiControlExclusiveMode = true; + guiControlExclusiveRec = bounds; // Store bounds as an identifier when dragging starts + + *hue = (mousePoint.y - bounds.y)*360/bounds.height; + if (*hue <= 0.0f) *hue = 0.0f; + if (*hue >= 359.0f) *hue = 359.0f; + + } + else state = STATE_FOCUSED; + + /*if (IsKeyDown(KEY_UP)) + { + hue -= 2.0f; + if (hue <= 0.0f) hue = 0.0f; + } + else if (IsKeyDown(KEY_DOWN)) + { + hue += 2.0f; + if (hue >= 360.0f) hue = 360.0f; + }*/ + } + } + //-------------------------------------------------------------------- + + // Draw control + //-------------------------------------------------------------------- + if (state != STATE_DISABLED) + { + // Draw hue bar:color bars + // TODO: Use directly DrawRectangleGradientEx(bounds, color1, color2, color2, color1); + DrawRectangleGradientV((int)bounds.x, (int)(bounds.y), (int)bounds.width, (int)ceilf(bounds.height/6), Fade(RAYGUI_CLITERAL(Color){ 255, 0, 0, 255 }, guiAlpha), Fade(RAYGUI_CLITERAL(Color){ 255, 255, 0, 255 }, guiAlpha)); + DrawRectangleGradientV((int)bounds.x, (int)(bounds.y + bounds.height/6), (int)bounds.width, (int)ceilf(bounds.height/6), Fade(RAYGUI_CLITERAL(Color){ 255, 255, 0, 255 }, guiAlpha), Fade(RAYGUI_CLITERAL(Color){ 0, 255, 0, 255 }, guiAlpha)); + DrawRectangleGradientV((int)bounds.x, (int)(bounds.y + 2*(bounds.height/6)), (int)bounds.width, (int)ceilf(bounds.height/6), Fade(RAYGUI_CLITERAL(Color){ 0, 255, 0, 255 }, guiAlpha), Fade(RAYGUI_CLITERAL(Color){ 0, 255, 255, 255 }, guiAlpha)); + DrawRectangleGradientV((int)bounds.x, (int)(bounds.y + 3*(bounds.height/6)), (int)bounds.width, (int)ceilf(bounds.height/6), Fade(RAYGUI_CLITERAL(Color){ 0, 255, 255, 255 }, guiAlpha), Fade(RAYGUI_CLITERAL(Color){ 0, 0, 255, 255 }, guiAlpha)); + DrawRectangleGradientV((int)bounds.x, (int)(bounds.y + 4*(bounds.height/6)), (int)bounds.width, (int)ceilf(bounds.height/6), Fade(RAYGUI_CLITERAL(Color){ 0, 0, 255, 255 }, guiAlpha), Fade(RAYGUI_CLITERAL(Color){ 255, 0, 255, 255 }, guiAlpha)); + DrawRectangleGradientV((int)bounds.x, (int)(bounds.y + 5*(bounds.height/6)), (int)bounds.width, (int)(bounds.height/6), Fade(RAYGUI_CLITERAL(Color){ 255, 0, 255, 255 }, guiAlpha), Fade(RAYGUI_CLITERAL(Color){ 255, 0, 0, 255 }, guiAlpha)); + } + else DrawRectangleGradientV((int)bounds.x, (int)bounds.y, (int)bounds.width, (int)bounds.height, Fade(Fade(GetColor(GuiGetStyle(COLORPICKER, BASE_COLOR_DISABLED)), 0.1f), guiAlpha), Fade(GetColor(GuiGetStyle(COLORPICKER, BORDER_COLOR_DISABLED)), guiAlpha)); + + GuiDrawRectangle(bounds, GuiGetStyle(COLORPICKER, BORDER_WIDTH), GetColor(GuiGetStyle(COLORPICKER, BORDER + state*3)), BLANK); + + // Draw hue bar: selector + GuiDrawRectangle(selector, 0, BLANK, GetColor(GuiGetStyle(COLORPICKER, BORDER + state*3))); + //-------------------------------------------------------------------- + + return result; +} + +// Color Picker control +// NOTE: It's divided in multiple controls: +// Color GuiColorPanel(Rectangle bounds, Color color) +// float GuiColorBarAlpha(Rectangle bounds, float alpha) +// float GuiColorBarHue(Rectangle bounds, float value) +// NOTE: bounds define GuiColorPanel() size +// NOTE: this picker converts RGB to HSV, which can cause the Hue control to jump. If you have this problem, consider using the HSV variant instead +int GuiColorPicker(Rectangle bounds, const char *text, Color *color) +{ + int result = 0; + + Color temp = { 200, 0, 0, 255 }; + if (color == NULL) color = &temp; + + GuiColorPanel(bounds, NULL, color); + + Rectangle boundsHue = { (float)bounds.x + bounds.width + GuiGetStyle(COLORPICKER, HUEBAR_PADDING), (float)bounds.y, (float)GuiGetStyle(COLORPICKER, HUEBAR_WIDTH), (float)bounds.height }; + //Rectangle boundsAlpha = { bounds.x, bounds.y + bounds.height + GuiGetStyle(COLORPICKER, BARS_PADDING), bounds.width, GuiGetStyle(COLORPICKER, BARS_THICK) }; + + // NOTE: this conversion can cause low hue-resolution, if the r, g and b value are very similar, which causes the hue bar to shift around when only the GuiColorPanel is used. + Vector3 hsv = ConvertRGBtoHSV(RAYGUI_CLITERAL(Vector3){ (*color).r/255.0f, (*color).g/255.0f, (*color).b/255.0f }); + + GuiColorBarHue(boundsHue, NULL, &hsv.x); + + //color.a = (unsigned char)(GuiColorBarAlpha(boundsAlpha, (float)color.a/255.0f)*255.0f); + Vector3 rgb = ConvertHSVtoRGB(hsv); + + *color = RAYGUI_CLITERAL(Color){ (unsigned char)roundf(rgb.x*255.0f), (unsigned char)roundf(rgb.y*255.0f), (unsigned char)roundf(rgb.z*255.0f), (*color).a }; + + return result; +} + +// Color Picker control that avoids conversion to RGB and back to HSV on each call, thus avoiding jittering. +// The user can call ConvertHSVtoRGB() to convert *colorHsv value to RGB. +// NOTE: It's divided in multiple controls: +// int GuiColorPanelHSV(Rectangle bounds, const char *text, Vector3 *colorHsv) +// int GuiColorBarAlpha(Rectangle bounds, const char *text, float *alpha) +// float GuiColorBarHue(Rectangle bounds, float value) +// NOTE: bounds define GuiColorPanelHSV() size +int GuiColorPickerHSV(Rectangle bounds, const char *text, Vector3 *colorHsv) +{ + int result = 0; + + Vector3 tempHsv = { 0 }; + + if (colorHsv == NULL) + { + const Vector3 tempColor = { 200.0f/255.0f, 0.0f, 0.0f }; + tempHsv = ConvertRGBtoHSV(tempColor); + colorHsv = &tempHsv; + } + + GuiColorPanelHSV(bounds, NULL, colorHsv); + + const Rectangle boundsHue = { (float)bounds.x + bounds.width + GuiGetStyle(COLORPICKER, HUEBAR_PADDING), (float)bounds.y, (float)GuiGetStyle(COLORPICKER, HUEBAR_WIDTH), (float)bounds.height }; + + GuiColorBarHue(boundsHue, NULL, &colorHsv->x); + + return result; +} + +// Color Panel control - HSV variant +int GuiColorPanelHSV(Rectangle bounds, const char *text, Vector3 *colorHsv) +{ + int result = 0; + GuiState state = guiState; + Vector2 pickerSelector = { 0 }; + + const Color colWhite = { 255, 255, 255, 255 }; + const Color colBlack = { 0, 0, 0, 255 }; + + pickerSelector.x = bounds.x + (float)colorHsv->y*bounds.width; // HSV: Saturation + pickerSelector.y = bounds.y + (1.0f - (float)colorHsv->z)*bounds.height; // HSV: Value + + Vector3 maxHue = { colorHsv->x, 1.0f, 1.0f }; + Vector3 rgbHue = ConvertHSVtoRGB(maxHue); + Color maxHueCol = { (unsigned char)(255.0f*rgbHue.x), + (unsigned char)(255.0f*rgbHue.y), + (unsigned char)(255.0f*rgbHue.z), 255 }; + + // Update control + //-------------------------------------------------------------------- + if ((state != STATE_DISABLED) && !guiLocked) + { + Vector2 mousePoint = GetMousePosition(); + + if (guiControlExclusiveMode) // Allows to keep dragging outside of bounds + { + if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) + { + if (CHECK_BOUNDS_ID(bounds, guiControlExclusiveRec)) + { + pickerSelector = mousePoint; + + if (pickerSelector.x < bounds.x) pickerSelector.x = bounds.x; + if (pickerSelector.x > bounds.x + bounds.width) pickerSelector.x = bounds.x + bounds.width; + if (pickerSelector.y < bounds.y) pickerSelector.y = bounds.y; + if (pickerSelector.y > bounds.y + bounds.height) pickerSelector.y = bounds.y + bounds.height; + + // Calculate color from picker + Vector2 colorPick = { pickerSelector.x - bounds.x, pickerSelector.y - bounds.y }; + + colorPick.x /= (float)bounds.width; // Get normalized value on x + colorPick.y /= (float)bounds.height; // Get normalized value on y + + colorHsv->y = colorPick.x; + colorHsv->z = 1.0f - colorPick.y; + + } + } + else + { + guiControlExclusiveMode = false; + guiControlExclusiveRec = RAYGUI_CLITERAL(Rectangle){ 0, 0, 0, 0 }; + } + } + else if (CheckCollisionPointRec(mousePoint, bounds)) + { + if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) + { + state = STATE_PRESSED; + guiControlExclusiveMode = true; + guiControlExclusiveRec = bounds; + pickerSelector = mousePoint; + + // Calculate color from picker + Vector2 colorPick = { pickerSelector.x - bounds.x, pickerSelector.y - bounds.y }; + + colorPick.x /= (float)bounds.width; // Get normalized value on x + colorPick.y /= (float)bounds.height; // Get normalized value on y + + colorHsv->y = colorPick.x; + colorHsv->z = 1.0f - colorPick.y; + } + else state = STATE_FOCUSED; + } + } + //-------------------------------------------------------------------- + + // Draw control + //-------------------------------------------------------------------- + if (state != STATE_DISABLED) + { + DrawRectangleGradientEx(bounds, Fade(colWhite, guiAlpha), Fade(colWhite, guiAlpha), Fade(maxHueCol, guiAlpha), Fade(maxHueCol, guiAlpha)); + DrawRectangleGradientEx(bounds, Fade(colBlack, 0), Fade(colBlack, guiAlpha), Fade(colBlack, guiAlpha), Fade(colBlack, 0)); + + // Draw color picker: selector + Rectangle selector = { pickerSelector.x - GuiGetStyle(COLORPICKER, COLOR_SELECTOR_SIZE)/2, pickerSelector.y - GuiGetStyle(COLORPICKER, COLOR_SELECTOR_SIZE)/2, (float)GuiGetStyle(COLORPICKER, COLOR_SELECTOR_SIZE), (float)GuiGetStyle(COLORPICKER, COLOR_SELECTOR_SIZE) }; + GuiDrawRectangle(selector, 0, BLANK, colWhite); + } + else + { + DrawRectangleGradientEx(bounds, Fade(Fade(GetColor(GuiGetStyle(COLORPICKER, BASE_COLOR_DISABLED)), 0.1f), guiAlpha), Fade(Fade(colBlack, 0.6f), guiAlpha), Fade(Fade(colBlack, 0.6f), guiAlpha), Fade(Fade(GetColor(GuiGetStyle(COLORPICKER, BORDER_COLOR_DISABLED)), 0.6f), guiAlpha)); + } + + GuiDrawRectangle(bounds, GuiGetStyle(COLORPICKER, BORDER_WIDTH), GetColor(GuiGetStyle(COLORPICKER, BORDER + state*3)), BLANK); + //-------------------------------------------------------------------- + + return result; +} + +// Message Box control +int GuiMessageBox(Rectangle bounds, const char *title, const char *message, const char *buttons) +{ + #if !defined(RAYGUI_MESSAGEBOX_BUTTON_HEIGHT) + #define RAYGUI_MESSAGEBOX_BUTTON_HEIGHT 24 + #endif + #if !defined(RAYGUI_MESSAGEBOX_BUTTON_PADDING) + #define RAYGUI_MESSAGEBOX_BUTTON_PADDING 12 + #endif + + int result = -1; // Returns clicked button from buttons list, 0 refers to closed window button + + int buttonCount = 0; + const char **buttonsText = GuiTextSplit(buttons, ';', &buttonCount, NULL); + Rectangle buttonBounds = { 0 }; + buttonBounds.x = bounds.x + RAYGUI_MESSAGEBOX_BUTTON_PADDING; + buttonBounds.y = bounds.y + bounds.height - RAYGUI_MESSAGEBOX_BUTTON_HEIGHT - RAYGUI_MESSAGEBOX_BUTTON_PADDING; + buttonBounds.width = (bounds.width - RAYGUI_MESSAGEBOX_BUTTON_PADDING*(buttonCount + 1))/buttonCount; + buttonBounds.height = RAYGUI_MESSAGEBOX_BUTTON_HEIGHT; + + //int textWidth = GetTextWidth(message) + 2; + + Rectangle textBounds = { 0 }; + textBounds.x = bounds.x + RAYGUI_MESSAGEBOX_BUTTON_PADDING; + textBounds.y = bounds.y + RAYGUI_WINDOWBOX_STATUSBAR_HEIGHT + RAYGUI_MESSAGEBOX_BUTTON_PADDING; + textBounds.width = bounds.width - RAYGUI_MESSAGEBOX_BUTTON_PADDING*2; + textBounds.height = bounds.height - RAYGUI_WINDOWBOX_STATUSBAR_HEIGHT - 3*RAYGUI_MESSAGEBOX_BUTTON_PADDING - RAYGUI_MESSAGEBOX_BUTTON_HEIGHT; + + // Draw control + //-------------------------------------------------------------------- + if (GuiWindowBox(bounds, title)) result = 0; + + int prevTextAlignment = GuiGetStyle(LABEL, TEXT_ALIGNMENT); + GuiSetStyle(LABEL, TEXT_ALIGNMENT, TEXT_ALIGN_CENTER); + GuiLabel(textBounds, message); + GuiSetStyle(LABEL, TEXT_ALIGNMENT, prevTextAlignment); + + prevTextAlignment = GuiGetStyle(BUTTON, TEXT_ALIGNMENT); + GuiSetStyle(BUTTON, TEXT_ALIGNMENT, TEXT_ALIGN_CENTER); + + for (int i = 0; i < buttonCount; i++) + { + if (GuiButton(buttonBounds, buttonsText[i])) result = i + 1; + buttonBounds.x += (buttonBounds.width + RAYGUI_MESSAGEBOX_BUTTON_PADDING); + } + + GuiSetStyle(BUTTON, TEXT_ALIGNMENT, prevTextAlignment); + //-------------------------------------------------------------------- + + return result; +} + +// Text Input Box control, ask for text +int GuiTextInputBox(Rectangle bounds, const char *title, const char *message, const char *buttons, char *text, int textMaxSize, bool *secretViewActive) +{ + #if !defined(RAYGUI_TEXTINPUTBOX_BUTTON_HEIGHT) + #define RAYGUI_TEXTINPUTBOX_BUTTON_HEIGHT 24 + #endif + #if !defined(RAYGUI_TEXTINPUTBOX_BUTTON_PADDING) + #define RAYGUI_TEXTINPUTBOX_BUTTON_PADDING 12 + #endif + #if !defined(RAYGUI_TEXTINPUTBOX_HEIGHT) + #define RAYGUI_TEXTINPUTBOX_HEIGHT 26 + #endif + + // Used to enable text edit mode + // WARNING: No more than one GuiTextInputBox() should be open at the same time + static bool textEditMode = false; + + int result = -1; + + int buttonCount = 0; + const char **buttonsText = GuiTextSplit(buttons, ';', &buttonCount, NULL); + Rectangle buttonBounds = { 0 }; + buttonBounds.x = bounds.x + RAYGUI_TEXTINPUTBOX_BUTTON_PADDING; + buttonBounds.y = bounds.y + bounds.height - RAYGUI_TEXTINPUTBOX_BUTTON_HEIGHT - RAYGUI_TEXTINPUTBOX_BUTTON_PADDING; + buttonBounds.width = (bounds.width - RAYGUI_TEXTINPUTBOX_BUTTON_PADDING*(buttonCount + 1))/buttonCount; + buttonBounds.height = RAYGUI_TEXTINPUTBOX_BUTTON_HEIGHT; + + int messageInputHeight = (int)bounds.height - RAYGUI_WINDOWBOX_STATUSBAR_HEIGHT - GuiGetStyle(STATUSBAR, BORDER_WIDTH) - RAYGUI_TEXTINPUTBOX_BUTTON_HEIGHT - 2*RAYGUI_TEXTINPUTBOX_BUTTON_PADDING; + + Rectangle textBounds = { 0 }; + if (message != NULL) + { + int textSize = GetTextWidth(message) + 2; + + textBounds.x = bounds.x + bounds.width/2 - textSize/2; + textBounds.y = bounds.y + RAYGUI_WINDOWBOX_STATUSBAR_HEIGHT + messageInputHeight/4 - (float)GuiGetStyle(DEFAULT, TEXT_SIZE)/2; + textBounds.width = (float)textSize; + textBounds.height = (float)GuiGetStyle(DEFAULT, TEXT_SIZE); + } + + Rectangle textBoxBounds = { 0 }; + textBoxBounds.x = bounds.x + RAYGUI_TEXTINPUTBOX_BUTTON_PADDING; + textBoxBounds.y = bounds.y + RAYGUI_WINDOWBOX_STATUSBAR_HEIGHT - RAYGUI_TEXTINPUTBOX_HEIGHT/2; + if (message == NULL) textBoxBounds.y = bounds.y + 24 + RAYGUI_TEXTINPUTBOX_BUTTON_PADDING; + else textBoxBounds.y += (messageInputHeight/2 + messageInputHeight/4); + textBoxBounds.width = bounds.width - RAYGUI_TEXTINPUTBOX_BUTTON_PADDING*2; + textBoxBounds.height = RAYGUI_TEXTINPUTBOX_HEIGHT; + + // Draw control + //-------------------------------------------------------------------- + if (GuiWindowBox(bounds, title)) result = 0; + + // Draw message if available + if (message != NULL) + { + int prevTextAlignment = GuiGetStyle(LABEL, TEXT_ALIGNMENT); + GuiSetStyle(LABEL, TEXT_ALIGNMENT, TEXT_ALIGN_CENTER); + GuiLabel(textBounds, message); + GuiSetStyle(LABEL, TEXT_ALIGNMENT, prevTextAlignment); + } + + if (secretViewActive != NULL) + { + static char stars[] = "****************"; + if (GuiTextBox(RAYGUI_CLITERAL(Rectangle){ textBoxBounds.x, textBoxBounds.y, textBoxBounds.width - 4 - RAYGUI_TEXTINPUTBOX_HEIGHT, textBoxBounds.height }, + ((*secretViewActive == 1) || textEditMode)? text : stars, textMaxSize, textEditMode)) textEditMode = !textEditMode; + + GuiToggle(RAYGUI_CLITERAL(Rectangle){ textBoxBounds.x + textBoxBounds.width - RAYGUI_TEXTINPUTBOX_HEIGHT, textBoxBounds.y, RAYGUI_TEXTINPUTBOX_HEIGHT, RAYGUI_TEXTINPUTBOX_HEIGHT }, (*secretViewActive == 1)? "#44#" : "#45#", secretViewActive); + } + else + { + if (GuiTextBox(textBoxBounds, text, textMaxSize, textEditMode)) textEditMode = !textEditMode; + } + + int prevBtnTextAlignment = GuiGetStyle(BUTTON, TEXT_ALIGNMENT); + GuiSetStyle(BUTTON, TEXT_ALIGNMENT, TEXT_ALIGN_CENTER); + + for (int i = 0; i < buttonCount; i++) + { + if (GuiButton(buttonBounds, buttonsText[i])) result = i + 1; + buttonBounds.x += (buttonBounds.width + RAYGUI_MESSAGEBOX_BUTTON_PADDING); + } + + if (result >= 0) textEditMode = false; + + GuiSetStyle(BUTTON, TEXT_ALIGNMENT, prevBtnTextAlignment); + //-------------------------------------------------------------------- + + return result; // Result is the pressed button index +} + +// Grid control +// NOTE: Returns grid mouse-hover selected cell +// About drawing lines at subpixel spacing, simple put, not easy solution: +// https://stackoverflow.com/questions/4435450/2d-opengl-drawing-lines-that-dont-exactly-fit-pixel-raster +int GuiGrid(Rectangle bounds, const char *text, float spacing, int subdivs, Vector2 *mouseCell) +{ + // Grid lines alpha amount + #if !defined(RAYGUI_GRID_ALPHA) + #define RAYGUI_GRID_ALPHA 0.15f + #endif + + int result = 0; + GuiState state = guiState; + + Vector2 mousePoint = GetMousePosition(); + Vector2 currentMouseCell = { -1, -1 }; + + float spaceWidth = spacing/(float)subdivs; + int linesV = (int)(bounds.width/spaceWidth) + 1; + int linesH = (int)(bounds.height/spaceWidth) + 1; + + int color = GuiGetStyle(DEFAULT, LINE_COLOR); + + // Update control + //-------------------------------------------------------------------- + if ((state != STATE_DISABLED) && !guiLocked && !guiControlExclusiveMode) + { + if (CheckCollisionPointRec(mousePoint, bounds)) + { + // NOTE: Cell values must be the upper left of the cell the mouse is in + currentMouseCell.x = floorf((mousePoint.x - bounds.x)/spacing); + currentMouseCell.y = floorf((mousePoint.y - bounds.y)/spacing); + } + } + //-------------------------------------------------------------------- + + // Draw control + //-------------------------------------------------------------------- + if (state == STATE_DISABLED) color = GuiGetStyle(DEFAULT, BORDER_COLOR_DISABLED); + + if (subdivs > 0) + { + // Draw vertical grid lines + for (int i = 0; i < linesV; i++) + { + Rectangle lineV = { bounds.x + spacing*i/subdivs, bounds.y, 1, bounds.height + 1 }; + GuiDrawRectangle(lineV, 0, BLANK, ((i%subdivs) == 0)? GuiFade(GetColor(color), RAYGUI_GRID_ALPHA*4) : GuiFade(GetColor(color), RAYGUI_GRID_ALPHA)); + } + + // Draw horizontal grid lines + for (int i = 0; i < linesH; i++) + { + Rectangle lineH = { bounds.x, bounds.y + spacing*i/subdivs, bounds.width + 1, 1 }; + GuiDrawRectangle(lineH, 0, BLANK, ((i%subdivs) == 0)? GuiFade(GetColor(color), RAYGUI_GRID_ALPHA*4) : GuiFade(GetColor(color), RAYGUI_GRID_ALPHA)); + } + } + + if (mouseCell != NULL) *mouseCell = currentMouseCell; + return result; +} + +//---------------------------------------------------------------------------------- +// Tooltip management functions +// NOTE: Tooltips requires some global variables: tooltipPtr +//---------------------------------------------------------------------------------- +// Enable gui tooltips (global state) +void GuiEnableTooltip(void) { guiTooltip = true; } + +// Disable gui tooltips (global state) +void GuiDisableTooltip(void) { guiTooltip = false; } + +// Set tooltip string +void GuiSetTooltip(const char *tooltip) { guiTooltipPtr = tooltip; } + +//---------------------------------------------------------------------------------- +// Styles loading functions +//---------------------------------------------------------------------------------- + +// Load raygui style file (.rgs) +// NOTE: By default a binary file is expected, that file could contain a custom font, +// in that case, custom font image atlas is GRAY+ALPHA and pixel data can be compressed (DEFLATE) +void GuiLoadStyle(const char *fileName) +{ + #define MAX_LINE_BUFFER_SIZE 256 + + bool tryBinary = false; + if (!guiStyleLoaded) GuiLoadStyleDefault(); + + // Try reading the files as text file first + FILE *rgsFile = fopen(fileName, "rt"); + + if (rgsFile != NULL) + { + char buffer[MAX_LINE_BUFFER_SIZE] = { 0 }; + fgets(buffer, MAX_LINE_BUFFER_SIZE, rgsFile); + + if (buffer[0] == '#') + { + int controlId = 0; + int propertyId = 0; + unsigned int propertyValue = 0; + + while (!feof(rgsFile)) + { + switch (buffer[0]) + { + case 'p': + { + // Style property: p + + sscanf(buffer, "p %d %d 0x%x", &controlId, &propertyId, &propertyValue); + GuiSetStyle(controlId, propertyId, (int)propertyValue); + + } break; + case 'f': + { + // Style font: f + + int fontSize = 0; + char charmapFileName[256] = { 0 }; + char fontFileName[256] = { 0 }; + sscanf(buffer, "f %d %s %[^\r\n]s", &fontSize, charmapFileName, fontFileName); + + Font font = { 0 }; + int *codepoints = NULL; + int codepointCount = 0; + + if (charmapFileName[0] != '0') + { + // Load text data from file + // NOTE: Expected an UTF-8 array of codepoints, no separation + char *textData = LoadFileText(TextFormat("%s/%s", GetDirectoryPath(fileName), charmapFileName)); + codepoints = LoadCodepoints(textData, &codepointCount); + UnloadFileText(textData); + } + + if (fontFileName[0] != '\0') + { + // In case a font is already loaded and it is not default internal font, unload it + if (font.texture.id != GetFontDefault().texture.id) UnloadTexture(font.texture); + + if (codepointCount > 0) font = LoadFontEx(TextFormat("%s/%s", GetDirectoryPath(fileName), fontFileName), fontSize, codepoints, codepointCount); + else font = LoadFontEx(TextFormat("%s/%s", GetDirectoryPath(fileName), fontFileName), fontSize, NULL, 0); // Default to 95 standard codepoints + } + + // If font texture not properly loaded, revert to default font and size/spacing + if (font.texture.id == 0) + { + font = GetFontDefault(); + GuiSetStyle(DEFAULT, TEXT_SIZE, 10); + GuiSetStyle(DEFAULT, TEXT_SPACING, 1); + } + + UnloadCodepoints(codepoints); + + if ((font.texture.id > 0) && (font.glyphCount > 0)) GuiSetFont(font); + + } break; + default: break; + } + + fgets(buffer, MAX_LINE_BUFFER_SIZE, rgsFile); + } + } + else tryBinary = true; + + fclose(rgsFile); + } + + if (tryBinary) + { + rgsFile = fopen(fileName, "rb"); + + if (rgsFile != NULL) + { + fseek(rgsFile, 0, SEEK_END); + int fileDataSize = ftell(rgsFile); + fseek(rgsFile, 0, SEEK_SET); + + if (fileDataSize > 0) + { + unsigned char *fileData = (unsigned char *)RAYGUI_MALLOC(fileDataSize*sizeof(unsigned char)); + fread(fileData, sizeof(unsigned char), fileDataSize, rgsFile); + + GuiLoadStyleFromMemory(fileData, fileDataSize); + + RAYGUI_FREE(fileData); + } + + fclose(rgsFile); + } + } +} + +// Load style default over global style +void GuiLoadStyleDefault(void) +{ + // We set this variable first to avoid cyclic function calls + // when calling GuiSetStyle() and GuiGetStyle() + guiStyleLoaded = true; + + // Initialize default LIGHT style property values + // WARNING: Default value are applied to all controls on set but + // they can be overwritten later on for every custom control + GuiSetStyle(DEFAULT, BORDER_COLOR_NORMAL, 0x838383ff); + GuiSetStyle(DEFAULT, BASE_COLOR_NORMAL, 0xc9c9c9ff); + GuiSetStyle(DEFAULT, TEXT_COLOR_NORMAL, 0x686868ff); + GuiSetStyle(DEFAULT, BORDER_COLOR_FOCUSED, 0x5bb2d9ff); + GuiSetStyle(DEFAULT, BASE_COLOR_FOCUSED, 0xc9effeff); + GuiSetStyle(DEFAULT, TEXT_COLOR_FOCUSED, 0x6c9bbcff); + GuiSetStyle(DEFAULT, BORDER_COLOR_PRESSED, 0x0492c7ff); + GuiSetStyle(DEFAULT, BASE_COLOR_PRESSED, 0x97e8ffff); + GuiSetStyle(DEFAULT, TEXT_COLOR_PRESSED, 0x368bafff); + GuiSetStyle(DEFAULT, BORDER_COLOR_DISABLED, 0xb5c1c2ff); + GuiSetStyle(DEFAULT, BASE_COLOR_DISABLED, 0xe6e9e9ff); + GuiSetStyle(DEFAULT, TEXT_COLOR_DISABLED, 0xaeb7b8ff); + GuiSetStyle(DEFAULT, BORDER_WIDTH, 1); + GuiSetStyle(DEFAULT, TEXT_PADDING, 0); + GuiSetStyle(DEFAULT, TEXT_ALIGNMENT, TEXT_ALIGN_CENTER); + + // Initialize default extended property values + // NOTE: By default, extended property values are initialized to 0 + GuiSetStyle(DEFAULT, TEXT_SIZE, 10); // DEFAULT, shared by all controls + GuiSetStyle(DEFAULT, TEXT_SPACING, 1); // DEFAULT, shared by all controls + GuiSetStyle(DEFAULT, LINE_COLOR, 0x90abb5ff); // DEFAULT specific property + GuiSetStyle(DEFAULT, BACKGROUND_COLOR, 0xf5f5f5ff); // DEFAULT specific property + GuiSetStyle(DEFAULT, TEXT_LINE_SPACING, 15); // DEFAULT, 15 pixels between lines + GuiSetStyle(DEFAULT, TEXT_ALIGNMENT_VERTICAL, TEXT_ALIGN_MIDDLE); // DEFAULT, text aligned vertically to middle of text-bounds + + // Initialize control-specific property values + // NOTE: Those properties are in default list but require specific values by control type + GuiSetStyle(LABEL, TEXT_ALIGNMENT, TEXT_ALIGN_LEFT); + GuiSetStyle(BUTTON, BORDER_WIDTH, 2); + GuiSetStyle(SLIDER, TEXT_PADDING, 4); + GuiSetStyle(PROGRESSBAR, TEXT_PADDING, 4); + GuiSetStyle(CHECKBOX, TEXT_PADDING, 4); + GuiSetStyle(CHECKBOX, TEXT_ALIGNMENT, TEXT_ALIGN_RIGHT); + GuiSetStyle(DROPDOWNBOX, TEXT_PADDING, 0); + GuiSetStyle(DROPDOWNBOX, TEXT_ALIGNMENT, TEXT_ALIGN_CENTER); + GuiSetStyle(TEXTBOX, TEXT_PADDING, 4); + GuiSetStyle(TEXTBOX, TEXT_ALIGNMENT, TEXT_ALIGN_LEFT); + GuiSetStyle(VALUEBOX, TEXT_PADDING, 0); + GuiSetStyle(VALUEBOX, TEXT_ALIGNMENT, TEXT_ALIGN_LEFT); + GuiSetStyle(SPINNER, TEXT_PADDING, 0); + GuiSetStyle(SPINNER, TEXT_ALIGNMENT, TEXT_ALIGN_LEFT); + GuiSetStyle(STATUSBAR, TEXT_PADDING, 8); + GuiSetStyle(STATUSBAR, TEXT_ALIGNMENT, TEXT_ALIGN_LEFT); + + // Initialize extended property values + // NOTE: By default, extended property values are initialized to 0 + GuiSetStyle(TOGGLE, GROUP_PADDING, 2); + GuiSetStyle(SLIDER, SLIDER_WIDTH, 16); + GuiSetStyle(SLIDER, SLIDER_PADDING, 1); + GuiSetStyle(PROGRESSBAR, PROGRESS_PADDING, 1); + GuiSetStyle(CHECKBOX, CHECK_PADDING, 1); + GuiSetStyle(COMBOBOX, COMBO_BUTTON_WIDTH, 32); + GuiSetStyle(COMBOBOX, COMBO_BUTTON_SPACING, 2); + GuiSetStyle(DROPDOWNBOX, ARROW_PADDING, 16); + GuiSetStyle(DROPDOWNBOX, DROPDOWN_ITEMS_SPACING, 2); + GuiSetStyle(SPINNER, SPIN_BUTTON_WIDTH, 24); + GuiSetStyle(SPINNER, SPIN_BUTTON_SPACING, 2); + GuiSetStyle(SCROLLBAR, BORDER_WIDTH, 0); + GuiSetStyle(SCROLLBAR, ARROWS_VISIBLE, 0); + GuiSetStyle(SCROLLBAR, ARROWS_SIZE, 6); + GuiSetStyle(SCROLLBAR, SCROLL_SLIDER_PADDING, 0); + GuiSetStyle(SCROLLBAR, SCROLL_SLIDER_SIZE, 16); + GuiSetStyle(SCROLLBAR, SCROLL_PADDING, 0); + GuiSetStyle(SCROLLBAR, SCROLL_SPEED, 12); + GuiSetStyle(LISTVIEW, LIST_ITEMS_HEIGHT, 28); + GuiSetStyle(LISTVIEW, LIST_ITEMS_SPACING, 2); + GuiSetStyle(LISTVIEW, SCROLLBAR_WIDTH, 12); + GuiSetStyle(LISTVIEW, SCROLLBAR_SIDE, SCROLLBAR_RIGHT_SIDE); + GuiSetStyle(COLORPICKER, COLOR_SELECTOR_SIZE, 8); + GuiSetStyle(COLORPICKER, HUEBAR_WIDTH, 16); + GuiSetStyle(COLORPICKER, HUEBAR_PADDING, 8); + GuiSetStyle(COLORPICKER, HUEBAR_SELECTOR_HEIGHT, 8); + GuiSetStyle(COLORPICKER, HUEBAR_SELECTOR_OVERFLOW, 2); + + if (guiFont.texture.id != GetFontDefault().texture.id) + { + // Unload previous font texture + UnloadTexture(guiFont.texture); + RL_FREE(guiFont.recs); + RL_FREE(guiFont.glyphs); + guiFont.recs = NULL; + guiFont.glyphs = NULL; + + // Setup default raylib font + guiFont = GetFontDefault(); + + // NOTE: Default raylib font character 95 is a white square + Rectangle whiteChar = guiFont.recs[95]; + + // NOTE: We set up a 1px padding on char rectangle to avoid pixel bleeding on MSAA filtering + SetShapesTexture(guiFont.texture, RAYGUI_CLITERAL(Rectangle){ whiteChar.x + 1, whiteChar.y + 1, whiteChar.width - 2, whiteChar.height - 2 }); + } +} + +// Get text with icon id prepended +// NOTE: Useful to add icons by name id (enum) instead of +// a number that can change between ricon versions +const char *GuiIconText(int iconId, const char *text) +{ +#if defined(RAYGUI_NO_ICONS) + return NULL; +#else + static char buffer[1024] = { 0 }; + static char iconBuffer[16] = { 0 }; + + if (text != NULL) + { + memset(buffer, 0, 1024); + sprintf(buffer, "#%03i#", iconId); + + for (int i = 5; i < 1024; i++) + { + buffer[i] = text[i - 5]; + if (text[i - 5] == '\0') break; + } + + return buffer; + } + else + { + sprintf(iconBuffer, "#%03i#", iconId); + + return iconBuffer; + } +#endif +} + +#if !defined(RAYGUI_NO_ICONS) +// Get full icons data pointer +unsigned int *GuiGetIcons(void) { return guiIconsPtr; } + +// Load raygui icons file (.rgi) +// NOTE: In case nameIds are required, they can be requested with loadIconsName, +// they are returned as a guiIconsName[iconCount][RAYGUI_ICON_MAX_NAME_LENGTH], +// WARNING: guiIconsName[]][] memory should be manually freed! +char **GuiLoadIcons(const char *fileName, bool loadIconsName) +{ + // Style File Structure (.rgi) + // ------------------------------------------------------ + // Offset | Size | Type | Description + // ------------------------------------------------------ + // 0 | 4 | char | Signature: "rGI " + // 4 | 2 | short | Version: 100 + // 6 | 2 | short | reserved + + // 8 | 2 | short | Num icons (N) + // 10 | 2 | short | Icons size (Options: 16, 32, 64) (S) + + // Icons name id (32 bytes per name id) + // foreach (icon) + // { + // 12+32*i | 32 | char | Icon NameId + // } + + // Icons data: One bit per pixel, stored as unsigned int array (depends on icon size) + // S*S pixels/32bit per unsigned int = K unsigned int per icon + // foreach (icon) + // { + // ... | K | unsigned int | Icon Data + // } + + FILE *rgiFile = fopen(fileName, "rb"); + + char **guiIconsName = NULL; + + if (rgiFile != NULL) + { + char signature[5] = { 0 }; + short version = 0; + short reserved = 0; + short iconCount = 0; + short iconSize = 0; + + fread(signature, 1, 4, rgiFile); + fread(&version, sizeof(short), 1, rgiFile); + fread(&reserved, sizeof(short), 1, rgiFile); + fread(&iconCount, sizeof(short), 1, rgiFile); + fread(&iconSize, sizeof(short), 1, rgiFile); + + if ((signature[0] == 'r') && + (signature[1] == 'G') && + (signature[2] == 'I') && + (signature[3] == ' ')) + { + if (loadIconsName) + { + guiIconsName = (char **)RAYGUI_MALLOC(iconCount*sizeof(char **)); + for (int i = 0; i < iconCount; i++) + { + guiIconsName[i] = (char *)RAYGUI_MALLOC(RAYGUI_ICON_MAX_NAME_LENGTH); + fread(guiIconsName[i], 1, RAYGUI_ICON_MAX_NAME_LENGTH, rgiFile); + } + } + else fseek(rgiFile, iconCount*RAYGUI_ICON_MAX_NAME_LENGTH, SEEK_CUR); + + // Read icons data directly over internal icons array + fread(guiIconsPtr, sizeof(unsigned int), iconCount*(iconSize*iconSize/32), rgiFile); + } + + fclose(rgiFile); + } + + return guiIconsName; +} + +// Draw selected icon using rectangles pixel-by-pixel +void GuiDrawIcon(int iconId, int posX, int posY, int pixelSize, Color color) +{ + #define BIT_CHECK(a,b) ((a) & (1u<<(b))) + + for (int i = 0, y = 0; i < RAYGUI_ICON_SIZE*RAYGUI_ICON_SIZE/32; i++) + { + for (int k = 0; k < 32; k++) + { + if (BIT_CHECK(guiIconsPtr[iconId*RAYGUI_ICON_DATA_ELEMENTS + i], k)) + { + #if !defined(RAYGUI_STANDALONE) + GuiDrawRectangle(RAYGUI_CLITERAL(Rectangle){ (float)posX + (k%RAYGUI_ICON_SIZE)*pixelSize, (float)posY + y*pixelSize, (float)pixelSize, (float)pixelSize }, 0, BLANK, color); + #endif + } + + if ((k == 15) || (k == 31)) y++; + } + } +} + +// Set icon drawing size +void GuiSetIconScale(int scale) +{ + if (scale >= 1) guiIconScale = scale; +} + +#endif // !RAYGUI_NO_ICONS + +//---------------------------------------------------------------------------------- +// Module specific Functions Definition +//---------------------------------------------------------------------------------- + +// Load style from memory +// WARNING: Binary files only +static void GuiLoadStyleFromMemory(const unsigned char *fileData, int dataSize) +{ + unsigned char *fileDataPtr = (unsigned char *)fileData; + + char signature[5] = { 0 }; + short version = 0; + short reserved = 0; + int propertyCount = 0; + + memcpy(signature, fileDataPtr, 4); + memcpy(&version, fileDataPtr + 4, sizeof(short)); + memcpy(&reserved, fileDataPtr + 4 + 2, sizeof(short)); + memcpy(&propertyCount, fileDataPtr + 4 + 2 + 2, sizeof(int)); + fileDataPtr += 12; + + if ((signature[0] == 'r') && + (signature[1] == 'G') && + (signature[2] == 'S') && + (signature[3] == ' ')) + { + short controlId = 0; + short propertyId = 0; + unsigned int propertyValue = 0; + + for (int i = 0; i < propertyCount; i++) + { + memcpy(&controlId, fileDataPtr, sizeof(short)); + memcpy(&propertyId, fileDataPtr + 2, sizeof(short)); + memcpy(&propertyValue, fileDataPtr + 2 + 2, sizeof(unsigned int)); + fileDataPtr += 8; + + if (controlId == 0) // DEFAULT control + { + // If a DEFAULT property is loaded, it is propagated to all controls + // NOTE: All DEFAULT properties should be defined first in the file + GuiSetStyle(0, (int)propertyId, propertyValue); + + if (propertyId < RAYGUI_MAX_PROPS_BASE) for (int j = 1; j < RAYGUI_MAX_CONTROLS; j++) GuiSetStyle(j, (int)propertyId, propertyValue); + } + else GuiSetStyle((int)controlId, (int)propertyId, propertyValue); + } + + // Font loading is highly dependant on raylib API to load font data and image + +#if !defined(RAYGUI_STANDALONE) + // Load custom font if available + int fontDataSize = 0; + memcpy(&fontDataSize, fileDataPtr, sizeof(int)); + fileDataPtr += 4; + + if (fontDataSize > 0) + { + Font font = { 0 }; + int fontType = 0; // 0-Normal, 1-SDF + + memcpy(&font.baseSize, fileDataPtr, sizeof(int)); + memcpy(&font.glyphCount, fileDataPtr + 4, sizeof(int)); + memcpy(&fontType, fileDataPtr + 4 + 4, sizeof(int)); + fileDataPtr += 12; + + // Load font white rectangle + Rectangle fontWhiteRec = { 0 }; + memcpy(&fontWhiteRec, fileDataPtr, sizeof(Rectangle)); + fileDataPtr += 16; + + // Load font image parameters + int fontImageUncompSize = 0; + int fontImageCompSize = 0; + memcpy(&fontImageUncompSize, fileDataPtr, sizeof(int)); + memcpy(&fontImageCompSize, fileDataPtr + 4, sizeof(int)); + fileDataPtr += 8; + + Image imFont = { 0 }; + imFont.mipmaps = 1; + memcpy(&imFont.width, fileDataPtr, sizeof(int)); + memcpy(&imFont.height, fileDataPtr + 4, sizeof(int)); + memcpy(&imFont.format, fileDataPtr + 4 + 4, sizeof(int)); + fileDataPtr += 12; + + if ((fontImageCompSize > 0) && (fontImageCompSize != fontImageUncompSize)) + { + // Compressed font atlas image data (DEFLATE), it requires DecompressData() + int dataUncompSize = 0; + unsigned char *compData = (unsigned char *)RAYGUI_MALLOC(fontImageCompSize); + memcpy(compData, fileDataPtr, fontImageCompSize); + fileDataPtr += fontImageCompSize; + + imFont.data = DecompressData(compData, fontImageCompSize, &dataUncompSize); + + // Security check, dataUncompSize must match the provided fontImageUncompSize + if (dataUncompSize != fontImageUncompSize) RAYGUI_LOG("WARNING: Uncompressed font atlas image data could be corrupted"); + + RAYGUI_FREE(compData); + } + else + { + // Font atlas image data is not compressed + imFont.data = (unsigned char *)RAYGUI_MALLOC(fontImageUncompSize); + memcpy(imFont.data, fileDataPtr, fontImageUncompSize); + fileDataPtr += fontImageUncompSize; + } + + if (font.texture.id != GetFontDefault().texture.id) UnloadTexture(font.texture); + font.texture = LoadTextureFromImage(imFont); + + RAYGUI_FREE(imFont.data); + + // Validate font atlas texture was loaded correctly + if (font.texture.id != 0) + { + // Load font recs data + int recsDataSize = font.glyphCount*sizeof(Rectangle); + int recsDataCompressedSize = 0; + + // WARNING: Version 400 adds the compression size parameter + if (version >= 400) + { + // RGS files version 400 support compressed recs data + memcpy(&recsDataCompressedSize, fileDataPtr, sizeof(int)); + fileDataPtr += sizeof(int); + } + + if ((recsDataCompressedSize > 0) && (recsDataCompressedSize != recsDataSize)) + { + // Recs data is compressed, uncompress it + unsigned char *recsDataCompressed = (unsigned char *)RAYGUI_MALLOC(recsDataCompressedSize); + + memcpy(recsDataCompressed, fileDataPtr, recsDataCompressedSize); + fileDataPtr += recsDataCompressedSize; + + int recsDataUncompSize = 0; + font.recs = (Rectangle *)DecompressData(recsDataCompressed, recsDataCompressedSize, &recsDataUncompSize); + + // Security check, data uncompressed size must match the expected original data size + if (recsDataUncompSize != recsDataSize) RAYGUI_LOG("WARNING: Uncompressed font recs data could be corrupted"); + + RAYGUI_FREE(recsDataCompressed); + } + else + { + // Recs data is uncompressed + font.recs = (Rectangle *)RAYGUI_CALLOC(font.glyphCount, sizeof(Rectangle)); + for (int i = 0; i < font.glyphCount; i++) + { + memcpy(&font.recs[i], fileDataPtr, sizeof(Rectangle)); + fileDataPtr += sizeof(Rectangle); + } + } + + // Load font glyphs info data + int glyphsDataSize = font.glyphCount*16; // 16 bytes data per glyph + int glyphsDataCompressedSize = 0; + + // WARNING: Version 400 adds the compression size parameter + if (version >= 400) + { + // RGS files version 400 support compressed glyphs data + memcpy(&glyphsDataCompressedSize, fileDataPtr, sizeof(int)); + fileDataPtr += sizeof(int); + } + + // Allocate required glyphs space to fill with data + font.glyphs = (GlyphInfo *)RAYGUI_CALLOC(font.glyphCount, sizeof(GlyphInfo)); + + if ((glyphsDataCompressedSize > 0) && (glyphsDataCompressedSize != glyphsDataSize)) + { + // Glyphs data is compressed, uncompress it + unsigned char *glypsDataCompressed = (unsigned char *)RAYGUI_MALLOC(glyphsDataCompressedSize); + + memcpy(glypsDataCompressed, fileDataPtr, glyphsDataCompressedSize); + fileDataPtr += glyphsDataCompressedSize; + + int glyphsDataUncompSize = 0; + unsigned char *glyphsDataUncomp = DecompressData(glypsDataCompressed, glyphsDataCompressedSize, &glyphsDataUncompSize); + + // Security check, data uncompressed size must match the expected original data size + if (glyphsDataUncompSize != glyphsDataSize) RAYGUI_LOG("WARNING: Uncompressed font glyphs data could be corrupted"); + + unsigned char *glyphsDataUncompPtr = glyphsDataUncomp; + + for (int i = 0; i < font.glyphCount; i++) + { + memcpy(&font.glyphs[i].value, glyphsDataUncompPtr, sizeof(int)); + memcpy(&font.glyphs[i].offsetX, glyphsDataUncompPtr + 4, sizeof(int)); + memcpy(&font.glyphs[i].offsetY, glyphsDataUncompPtr + 8, sizeof(int)); + memcpy(&font.glyphs[i].advanceX, glyphsDataUncompPtr + 12, sizeof(int)); + glyphsDataUncompPtr += 16; + } + + RAYGUI_FREE(glypsDataCompressed); + RAYGUI_FREE(glyphsDataUncomp); + } + else + { + // Glyphs data is uncompressed + for (int i = 0; i < font.glyphCount; i++) + { + memcpy(&font.glyphs[i].value, fileDataPtr, sizeof(int)); + memcpy(&font.glyphs[i].offsetX, fileDataPtr + 4, sizeof(int)); + memcpy(&font.glyphs[i].offsetY, fileDataPtr + 8, sizeof(int)); + memcpy(&font.glyphs[i].advanceX, fileDataPtr + 12, sizeof(int)); + fileDataPtr += 16; + } + } + } + else font = GetFontDefault(); // Fallback in case of errors loading font atlas texture + + GuiSetFont(font); + + // Set font texture source rectangle to be used as white texture to draw shapes + // NOTE: It makes possible to draw shapes and text (full UI) in a single draw call + if ((fontWhiteRec.x > 0) && + (fontWhiteRec.y > 0) && + (fontWhiteRec.width > 0) && + (fontWhiteRec.height > 0)) SetShapesTexture(font.texture, fontWhiteRec); + } +#endif + } +} + +// Gui get text width considering icon +static int GetTextWidth(const char *text) +{ + #if !defined(ICON_TEXT_PADDING) + #define ICON_TEXT_PADDING 4 + #endif + + Vector2 textSize = { 0 }; + int textIconOffset = 0; + + if ((text != NULL) && (text[0] != '\0')) + { + if (text[0] == '#') + { + for (int i = 1; (i < 5) && (text[i] != '\0'); i++) + { + if (text[i] == '#') + { + textIconOffset = i; + break; + } + } + } + + text += textIconOffset; + + // Make sure guiFont is set, GuiGetStyle() initializes it lazynessly + float fontSize = (float)GuiGetStyle(DEFAULT, TEXT_SIZE); + + // Custom MeasureText() implementation + if ((guiFont.texture.id > 0) && (text != NULL)) + { + // Get size in bytes of text, considering end of line and line break + int size = 0; + for (int i = 0; i < MAX_LINE_BUFFER_SIZE; i++) + { + if ((text[i] != '\0') && (text[i] != '\n')) size++; + else break; + } + + float scaleFactor = fontSize/(float)guiFont.baseSize; + textSize.y = (float)guiFont.baseSize*scaleFactor; + float glyphWidth = 0.0f; + + for (int i = 0, codepointSize = 0; i < size; i += codepointSize) + { + int codepoint = GetCodepointNext(&text[i], &codepointSize); + int codepointIndex = GetGlyphIndex(guiFont, codepoint); + + if (guiFont.glyphs[codepointIndex].advanceX == 0) glyphWidth = ((float)guiFont.recs[codepointIndex].width*scaleFactor); + else glyphWidth = ((float)guiFont.glyphs[codepointIndex].advanceX*scaleFactor); + + textSize.x += (glyphWidth + (float)GuiGetStyle(DEFAULT, TEXT_SPACING)); + } + } + + if (textIconOffset > 0) textSize.x += (RAYGUI_ICON_SIZE + ICON_TEXT_PADDING); + } + + return (int)textSize.x; +} + +// Get text bounds considering control bounds +static Rectangle GetTextBounds(int control, Rectangle bounds) +{ + Rectangle textBounds = bounds; + + textBounds.x = bounds.x + GuiGetStyle(control, BORDER_WIDTH); + textBounds.y = bounds.y + GuiGetStyle(control, BORDER_WIDTH) + GuiGetStyle(control, TEXT_PADDING); + textBounds.width = bounds.width - 2*GuiGetStyle(control, BORDER_WIDTH) - 2*GuiGetStyle(control, TEXT_PADDING); + textBounds.height = bounds.height - 2*GuiGetStyle(control, BORDER_WIDTH) - 2*GuiGetStyle(control, TEXT_PADDING); // NOTE: Text is processed line per line! + + // Depending on control, TEXT_PADDING and TEXT_ALIGNMENT properties could affect the text-bounds + switch (control) + { + case COMBOBOX: + case DROPDOWNBOX: + case LISTVIEW: + // TODO: Special cases (no label): COMBOBOX, DROPDOWNBOX, LISTVIEW + case SLIDER: + case CHECKBOX: + case VALUEBOX: + case SPINNER: + // TODO: More special cases (label on side): SLIDER, CHECKBOX, VALUEBOX, SPINNER + default: + { + // TODO: WARNING: TEXT_ALIGNMENT is already considered in GuiDrawText() + if (GuiGetStyle(control, TEXT_ALIGNMENT) == TEXT_ALIGN_RIGHT) textBounds.x -= GuiGetStyle(control, TEXT_PADDING); + else textBounds.x += GuiGetStyle(control, TEXT_PADDING); + } + break; + } + + return textBounds; +} + +// Get text icon if provided and move text cursor +// NOTE: We support up to 999 values for iconId +static const char *GetTextIcon(const char *text, int *iconId) +{ +#if !defined(RAYGUI_NO_ICONS) + *iconId = -1; + if (text[0] == '#') // Maybe we have an icon! + { + char iconValue[4] = { 0 }; // Maximum length for icon value: 3 digits + '\0' + + int pos = 1; + while ((pos < 4) && (text[pos] >= '0') && (text[pos] <= '9')) + { + iconValue[pos - 1] = text[pos]; + pos++; + } + + if (text[pos] == '#') + { + *iconId = TextToInteger(iconValue); + + // Move text pointer after icon + // WARNING: If only icon provided, it could point to EOL character: '\0' + if (*iconId >= 0) text += (pos + 1); + } + } +#endif + + return text; +} + +// Get text divided into lines (by line-breaks '\n') +const char **GetTextLines(const char *text, int *count) +{ + #define RAYGUI_MAX_TEXT_LINES 128 + + static const char *lines[RAYGUI_MAX_TEXT_LINES] = { 0 }; + for (int i = 0; i < RAYGUI_MAX_TEXT_LINES; i++) lines[i] = NULL; // Init NULL pointers to substrings + + int textSize = (int)strlen(text); + + lines[0] = text; + int len = 0; + *count = 1; + //int lineSize = 0; // Stores current line size, not returned + + for (int i = 0, k = 0; (i < textSize) && (*count < RAYGUI_MAX_TEXT_LINES); i++) + { + if (text[i] == '\n') + { + //lineSize = len; + k++; + lines[k] = &text[i + 1]; // WARNING: next value is valid? + len = 0; + *count += 1; + } + else len++; + } + + //lines[*count - 1].size = len; + + return lines; +} + +// Get text width to next space for provided string +static float GetNextSpaceWidth(const char *text, int *nextSpaceIndex) +{ + float width = 0; + int codepointByteCount = 0; + int codepoint = 0; + int index = 0; + float glyphWidth = 0; + float scaleFactor = (float)GuiGetStyle(DEFAULT, TEXT_SIZE)/guiFont.baseSize; + + for (int i = 0; text[i] != '\0'; i++) + { + if (text[i] != ' ') + { + codepoint = GetCodepoint(&text[i], &codepointByteCount); + index = GetGlyphIndex(guiFont, codepoint); + glyphWidth = (guiFont.glyphs[index].advanceX == 0)? guiFont.recs[index].width*scaleFactor : guiFont.glyphs[index].advanceX*scaleFactor; + width += (glyphWidth + (float)GuiGetStyle(DEFAULT, TEXT_SPACING)); + } + else + { + *nextSpaceIndex = i; + break; + } + } + + return width; +} + +// Gui draw text using default font +static void GuiDrawText(const char *text, Rectangle textBounds, int alignment, Color tint) +{ + #define TEXT_VALIGN_PIXEL_OFFSET(h) ((int)h%2) // Vertical alignment for pixel perfect + + #if !defined(ICON_TEXT_PADDING) + #define ICON_TEXT_PADDING 4 + #endif + + if ((text == NULL) || (text[0] == '\0')) return; // Security check + + // PROCEDURE: + // - Text is processed line per line + // - For every line, horizontal alignment is defined + // - For all text, vertical alignment is defined (multiline text only) + // - For every line, wordwrap mode is checked (useful for GuitextBox(), read-only) + + // Get text lines (using '\n' as delimiter) to be processed individually + // WARNING: We can't use GuiTextSplit() function because it can be already used + // before the GuiDrawText() call and its buffer is static, it would be overriden :( + int lineCount = 0; + const char **lines = GetTextLines(text, &lineCount); + + // Text style variables + //int alignment = GuiGetStyle(DEFAULT, TEXT_ALIGNMENT); + int alignmentVertical = GuiGetStyle(DEFAULT, TEXT_ALIGNMENT_VERTICAL); + int wrapMode = GuiGetStyle(DEFAULT, TEXT_WRAP_MODE); // Wrap-mode only available in read-only mode, no for text editing + + // TODO: WARNING: This totalHeight is not valid for vertical alignment in case of word-wrap + float totalHeight = (float)(lineCount*GuiGetStyle(DEFAULT, TEXT_SIZE) + (lineCount - 1)*GuiGetStyle(DEFAULT, TEXT_SIZE)/2); + float posOffsetY = 0.0f; + + for (int i = 0; i < lineCount; i++) + { + int iconId = 0; + lines[i] = GetTextIcon(lines[i], &iconId); // Check text for icon and move cursor + + // Get text position depending on alignment and iconId + //--------------------------------------------------------------------------------- + Vector2 textBoundsPosition = { textBounds.x, textBounds.y }; + float textBoundsWidthOffset = 0.0f; + + // NOTE: We get text size after icon has been processed + // WARNING: GetTextWidth() also processes text icon to get width! -> Really needed? + int textSizeX = GetTextWidth(lines[i]); + + // If text requires an icon, add size to measure + if (iconId >= 0) + { + textSizeX += RAYGUI_ICON_SIZE*guiIconScale; + + // WARNING: If only icon provided, text could be pointing to EOF character: '\0' +#if !defined(RAYGUI_NO_ICONS) + if ((lines[i] != NULL) && (lines[i][0] != '\0')) textSizeX += ICON_TEXT_PADDING; +#endif + } + + // Check guiTextAlign global variables + switch (alignment) + { + case TEXT_ALIGN_LEFT: textBoundsPosition.x = textBounds.x; break; + case TEXT_ALIGN_CENTER: textBoundsPosition.x = textBounds.x + textBounds.width/2 - textSizeX/2; break; + case TEXT_ALIGN_RIGHT: textBoundsPosition.x = textBounds.x + textBounds.width - textSizeX; break; + default: break; + } + + if (textSizeX > textBounds.width && (lines[i] != NULL) && (lines[i][0] != '\0')) textBoundsPosition.x = textBounds.x; + + switch (alignmentVertical) + { + // Only valid in case of wordWrap = 0; + case TEXT_ALIGN_TOP: textBoundsPosition.y = textBounds.y + posOffsetY; break; + case TEXT_ALIGN_MIDDLE: textBoundsPosition.y = textBounds.y + posOffsetY + textBounds.height/2 - totalHeight/2 + TEXT_VALIGN_PIXEL_OFFSET(textBounds.height); break; + case TEXT_ALIGN_BOTTOM: textBoundsPosition.y = textBounds.y + posOffsetY + textBounds.height - totalHeight + TEXT_VALIGN_PIXEL_OFFSET(textBounds.height); break; + default: break; + } + + // NOTE: Make sure we get pixel-perfect coordinates, + // In case of decimals we got weird text positioning + textBoundsPosition.x = (float)((int)textBoundsPosition.x); + textBoundsPosition.y = (float)((int)textBoundsPosition.y); + //--------------------------------------------------------------------------------- + + // Draw text (with icon if available) + //--------------------------------------------------------------------------------- +#if !defined(RAYGUI_NO_ICONS) + if (iconId >= 0) + { + // NOTE: We consider icon height, probably different than text size + GuiDrawIcon(iconId, (int)textBoundsPosition.x, (int)(textBounds.y + textBounds.height/2 - RAYGUI_ICON_SIZE*guiIconScale/2 + TEXT_VALIGN_PIXEL_OFFSET(textBounds.height)), guiIconScale, tint); + textBoundsPosition.x += (float)(RAYGUI_ICON_SIZE*guiIconScale + ICON_TEXT_PADDING); + textBoundsWidthOffset = (float)(RAYGUI_ICON_SIZE*guiIconScale + ICON_TEXT_PADDING); + } +#endif + // Get size in bytes of text, + // considering end of line and line break + int lineSize = 0; + for (int c = 0; (lines[i][c] != '\0') && (lines[i][c] != '\n') && (lines[i][c] != '\r'); c++, lineSize++){ } + float scaleFactor = (float)GuiGetStyle(DEFAULT, TEXT_SIZE)/guiFont.baseSize; + + int lastSpaceIndex = 0; + bool tempWrapCharMode = false; + + int textOffsetY = 0; + float textOffsetX = 0.0f; + float glyphWidth = 0; + + int ellipsisWidth = GetTextWidth("..."); + bool textOverflow = false; + for (int c = 0, codepointSize = 0; c < lineSize; c += codepointSize) + { + int codepoint = GetCodepointNext(&lines[i][c], &codepointSize); + int index = GetGlyphIndex(guiFont, codepoint); + + // NOTE: Normally we exit the decoding sequence as soon as a bad byte is found (and return 0x3f) + // but we need to draw all of the bad bytes using the '?' symbol moving one byte + if (codepoint == 0x3f) codepointSize = 1; // TODO: Review not recognized codepoints size + + // Get glyph width to check if it goes out of bounds + if (guiFont.glyphs[index].advanceX == 0) glyphWidth = ((float)guiFont.recs[index].width*scaleFactor); + else glyphWidth = (float)guiFont.glyphs[index].advanceX*scaleFactor; + + // Wrap mode text measuring, to validate if + // it can be drawn or a new line is required + if (wrapMode == TEXT_WRAP_CHAR) + { + // Jump to next line if current character reach end of the box limits + if ((textOffsetX + glyphWidth) > textBounds.width - textBoundsWidthOffset) + { + textOffsetX = 0.0f; + textOffsetY += GuiGetStyle(DEFAULT, TEXT_LINE_SPACING); + + if (tempWrapCharMode) // Wrap at char level when too long words + { + wrapMode = TEXT_WRAP_WORD; + tempWrapCharMode = false; + } + } + } + else if (wrapMode == TEXT_WRAP_WORD) + { + if (codepoint == 32) lastSpaceIndex = c; + + // Get width to next space in line + int nextSpaceIndex = 0; + float nextSpaceWidth = GetNextSpaceWidth(lines[i] + c, &nextSpaceIndex); + + int nextSpaceIndex2 = 0; + float nextWordSize = GetNextSpaceWidth(lines[i] + lastSpaceIndex + 1, &nextSpaceIndex2); + + if (nextWordSize > textBounds.width - textBoundsWidthOffset) + { + // Considering the case the next word is longer than bounds + tempWrapCharMode = true; + wrapMode = TEXT_WRAP_CHAR; + } + else if ((textOffsetX + nextSpaceWidth) > textBounds.width - textBoundsWidthOffset) + { + textOffsetX = 0.0f; + textOffsetY += GuiGetStyle(DEFAULT, TEXT_LINE_SPACING); + } + } + + if (codepoint == '\n') break; // WARNING: Lines are already processed manually, no need to keep drawing after this codepoint + else + { + // TODO: There are multiple types of spaces in Unicode, + // maybe it's a good idea to add support for more: http://jkorpela.fi/chars/spaces.html + if ((codepoint != ' ') && (codepoint != '\t')) // Do not draw codepoints with no glyph + { + if (wrapMode == TEXT_WRAP_NONE) + { + // Draw only required text glyphs fitting the textBounds.width + if (textSizeX > textBounds.width) + { + if (textOffsetX <= (textBounds.width - glyphWidth - textBoundsWidthOffset - ellipsisWidth)) + { + DrawTextCodepoint(guiFont, codepoint, RAYGUI_CLITERAL(Vector2){ textBoundsPosition.x + textOffsetX, textBoundsPosition.y + textOffsetY }, (float)GuiGetStyle(DEFAULT, TEXT_SIZE), GuiFade(tint, guiAlpha)); + } + else if (!textOverflow) + { + textOverflow = true; + + for (int j = 0; j < ellipsisWidth; j += ellipsisWidth/3) + { + DrawTextCodepoint(guiFont, '.', RAYGUI_CLITERAL(Vector2){ textBoundsPosition.x + textOffsetX + j, textBoundsPosition.y + textOffsetY }, (float)GuiGetStyle(DEFAULT, TEXT_SIZE), GuiFade(tint, guiAlpha)); + } + } + } + else + { + DrawTextCodepoint(guiFont, codepoint, RAYGUI_CLITERAL(Vector2){ textBoundsPosition.x + textOffsetX, textBoundsPosition.y + textOffsetY }, (float)GuiGetStyle(DEFAULT, TEXT_SIZE), GuiFade(tint, guiAlpha)); + } + } + else if ((wrapMode == TEXT_WRAP_CHAR) || (wrapMode == TEXT_WRAP_WORD)) + { + // Draw only glyphs inside the bounds + if ((textBoundsPosition.y + textOffsetY) <= (textBounds.y + textBounds.height - GuiGetStyle(DEFAULT, TEXT_SIZE))) + { + DrawTextCodepoint(guiFont, codepoint, RAYGUI_CLITERAL(Vector2){ textBoundsPosition.x + textOffsetX, textBoundsPosition.y + textOffsetY }, (float)GuiGetStyle(DEFAULT, TEXT_SIZE), GuiFade(tint, guiAlpha)); + } + } + } + + if (guiFont.glyphs[index].advanceX == 0) textOffsetX += ((float)guiFont.recs[index].width*scaleFactor + (float)GuiGetStyle(DEFAULT, TEXT_SPACING)); + else textOffsetX += ((float)guiFont.glyphs[index].advanceX*scaleFactor + (float)GuiGetStyle(DEFAULT, TEXT_SPACING)); + } + } + + if (wrapMode == TEXT_WRAP_NONE) posOffsetY += (float)GuiGetStyle(DEFAULT, TEXT_LINE_SPACING); + else if ((wrapMode == TEXT_WRAP_CHAR) || (wrapMode == TEXT_WRAP_WORD)) posOffsetY += (textOffsetY + (float)GuiGetStyle(DEFAULT, TEXT_LINE_SPACING)); + //--------------------------------------------------------------------------------- + } + +#if defined(RAYGUI_DEBUG_TEXT_BOUNDS) + GuiDrawRectangle(textBounds, 0, WHITE, Fade(BLUE, 0.4f)); +#endif +} + +// Gui draw rectangle using default raygui plain style with borders +static void GuiDrawRectangle(Rectangle rec, int borderWidth, Color borderColor, Color color) +{ + if (color.a > 0) + { + // Draw rectangle filled with color + DrawRectangle((int)rec.x, (int)rec.y, (int)rec.width, (int)rec.height, GuiFade(color, guiAlpha)); + } + + if (borderWidth > 0) + { + // Draw rectangle border lines with color + DrawRectangle((int)rec.x, (int)rec.y, (int)rec.width, borderWidth, GuiFade(borderColor, guiAlpha)); + DrawRectangle((int)rec.x, (int)rec.y + borderWidth, borderWidth, (int)rec.height - 2*borderWidth, GuiFade(borderColor, guiAlpha)); + DrawRectangle((int)rec.x + (int)rec.width - borderWidth, (int)rec.y + borderWidth, borderWidth, (int)rec.height - 2*borderWidth, GuiFade(borderColor, guiAlpha)); + DrawRectangle((int)rec.x, (int)rec.y + (int)rec.height - borderWidth, (int)rec.width, borderWidth, GuiFade(borderColor, guiAlpha)); + } + +#if defined(RAYGUI_DEBUG_RECS_BOUNDS) + DrawRectangle((int)rec.x, (int)rec.y, (int)rec.width, (int)rec.height, Fade(RED, 0.4f)); +#endif +} + +// Draw tooltip using control bounds +static void GuiTooltip(Rectangle controlRec) +{ + if (!guiLocked && guiTooltip && (guiTooltipPtr != NULL) && !guiControlExclusiveMode) + { + Vector2 textSize = MeasureTextEx(GuiGetFont(), guiTooltipPtr, (float)GuiGetStyle(DEFAULT, TEXT_SIZE), (float)GuiGetStyle(DEFAULT, TEXT_SPACING)); + + if ((controlRec.x + textSize.x + 16) > GetScreenWidth()) controlRec.x -= (textSize.x + 16 - controlRec.width); + + GuiPanel(RAYGUI_CLITERAL(Rectangle){ controlRec.x, controlRec.y + controlRec.height + 4, textSize.x + 16, GuiGetStyle(DEFAULT, TEXT_SIZE) + 8.f }, NULL); + + int textPadding = GuiGetStyle(LABEL, TEXT_PADDING); + int textAlignment = GuiGetStyle(LABEL, TEXT_ALIGNMENT); + GuiSetStyle(LABEL, TEXT_PADDING, 0); + GuiSetStyle(LABEL, TEXT_ALIGNMENT, TEXT_ALIGN_CENTER); + GuiLabel(RAYGUI_CLITERAL(Rectangle){ controlRec.x, controlRec.y + controlRec.height + 4, textSize.x + 16, GuiGetStyle(DEFAULT, TEXT_SIZE) + 8.f }, guiTooltipPtr); + GuiSetStyle(LABEL, TEXT_ALIGNMENT, textAlignment); + GuiSetStyle(LABEL, TEXT_PADDING, textPadding); + } +} + +// Split controls text into multiple strings +// Also check for multiple columns (required by GuiToggleGroup()) +static const char **GuiTextSplit(const char *text, char delimiter, int *count, int *textRow) +{ + // NOTE: Current implementation returns a copy of the provided string with '\0' (string end delimiter) + // inserted between strings defined by "delimiter" parameter. No memory is dynamically allocated, + // all used memory is static... it has some limitations: + // 1. Maximum number of possible split strings is set by RAYGUI_TEXTSPLIT_MAX_ITEMS + // 2. Maximum size of text to split is RAYGUI_TEXTSPLIT_MAX_TEXT_SIZE + // NOTE: Those definitions could be externally provided if required + + // TODO: HACK: GuiTextSplit() - Review how textRows are returned to user + // textRow is an externally provided array of integers that stores row number for every splitted string + + #if !defined(RAYGUI_TEXTSPLIT_MAX_ITEMS) + #define RAYGUI_TEXTSPLIT_MAX_ITEMS 128 + #endif + #if !defined(RAYGUI_TEXTSPLIT_MAX_TEXT_SIZE) + #define RAYGUI_TEXTSPLIT_MAX_TEXT_SIZE 1024 + #endif + + static const char *result[RAYGUI_TEXTSPLIT_MAX_ITEMS] = { NULL }; // String pointers array (points to buffer data) + static char buffer[RAYGUI_TEXTSPLIT_MAX_TEXT_SIZE] = { 0 }; // Buffer data (text input copy with '\0' added) + memset(buffer, 0, RAYGUI_TEXTSPLIT_MAX_TEXT_SIZE); + + result[0] = buffer; + int counter = 1; + + if (textRow != NULL) textRow[0] = 0; + + // Count how many substrings we have on text and point to every one + for (int i = 0; i < RAYGUI_TEXTSPLIT_MAX_TEXT_SIZE; i++) + { + buffer[i] = text[i]; + if (buffer[i] == '\0') break; + else if ((buffer[i] == delimiter) || (buffer[i] == '\n')) + { + result[counter] = buffer + i + 1; + + if (textRow != NULL) + { + if (buffer[i] == '\n') textRow[counter] = textRow[counter - 1] + 1; + else textRow[counter] = textRow[counter - 1]; + } + + buffer[i] = '\0'; // Set an end of string at this point + + counter++; + if (counter > RAYGUI_TEXTSPLIT_MAX_ITEMS) break; + } + } + + *count = counter; + + return result; +} + +// Convert color data from RGB to HSV +// NOTE: Color data should be passed normalized +static Vector3 ConvertRGBtoHSV(Vector3 rgb) +{ + Vector3 hsv = { 0 }; + float min = 0.0f; + float max = 0.0f; + float delta = 0.0f; + + min = (rgb.x < rgb.y)? rgb.x : rgb.y; + min = (min < rgb.z)? min : rgb.z; + + max = (rgb.x > rgb.y)? rgb.x : rgb.y; + max = (max > rgb.z)? max : rgb.z; + + hsv.z = max; // Value + delta = max - min; + + if (delta < 0.00001f) + { + hsv.y = 0.0f; + hsv.x = 0.0f; // Undefined, maybe NAN? + return hsv; + } + + if (max > 0.0f) + { + // NOTE: If max is 0, this divide would cause a crash + hsv.y = (delta/max); // Saturation + } + else + { + // NOTE: If max is 0, then r = g = b = 0, s = 0, h is undefined + hsv.y = 0.0f; + hsv.x = 0.0f; // Undefined, maybe NAN? + return hsv; + } + + // NOTE: Comparing float values could not work properly + if (rgb.x >= max) hsv.x = (rgb.y - rgb.z)/delta; // Between yellow & magenta + else + { + if (rgb.y >= max) hsv.x = 2.0f + (rgb.z - rgb.x)/delta; // Between cyan & yellow + else hsv.x = 4.0f + (rgb.x - rgb.y)/delta; // Between magenta & cyan + } + + hsv.x *= 60.0f; // Convert to degrees + + if (hsv.x < 0.0f) hsv.x += 360.0f; + + return hsv; +} + +// Convert color data from HSV to RGB +// NOTE: Color data should be passed normalized +static Vector3 ConvertHSVtoRGB(Vector3 hsv) +{ + Vector3 rgb = { 0 }; + float hh = 0.0f, p = 0.0f, q = 0.0f, t = 0.0f, ff = 0.0f; + long i = 0; + + // NOTE: Comparing float values could not work properly + if (hsv.y <= 0.0f) + { + rgb.x = hsv.z; + rgb.y = hsv.z; + rgb.z = hsv.z; + return rgb; + } + + hh = hsv.x; + if (hh >= 360.0f) hh = 0.0f; + hh /= 60.0f; + + i = (long)hh; + ff = hh - i; + p = hsv.z*(1.0f - hsv.y); + q = hsv.z*(1.0f - (hsv.y*ff)); + t = hsv.z*(1.0f - (hsv.y*(1.0f - ff))); + + switch (i) + { + case 0: + { + rgb.x = hsv.z; + rgb.y = t; + rgb.z = p; + } break; + case 1: + { + rgb.x = q; + rgb.y = hsv.z; + rgb.z = p; + } break; + case 2: + { + rgb.x = p; + rgb.y = hsv.z; + rgb.z = t; + } break; + case 3: + { + rgb.x = p; + rgb.y = q; + rgb.z = hsv.z; + } break; + case 4: + { + rgb.x = t; + rgb.y = p; + rgb.z = hsv.z; + } break; + case 5: + default: + { + rgb.x = hsv.z; + rgb.y = p; + rgb.z = q; + } break; + } + + return rgb; +} + +// Scroll bar control (used by GuiScrollPanel()) +static int GuiScrollBar(Rectangle bounds, int value, int minValue, int maxValue) +{ + GuiState state = guiState; + + // Is the scrollbar horizontal or vertical? + bool isVertical = (bounds.width > bounds.height)? false : true; + + // The size (width or height depending on scrollbar type) of the spinner buttons + const int spinnerSize = GuiGetStyle(SCROLLBAR, ARROWS_VISIBLE)? + (isVertical? (int)bounds.width - 2*GuiGetStyle(SCROLLBAR, BORDER_WIDTH) : + (int)bounds.height - 2*GuiGetStyle(SCROLLBAR, BORDER_WIDTH)) : 0; + + // Arrow buttons [<] [>] [∧] [∨] + Rectangle arrowUpLeft = { 0 }; + Rectangle arrowDownRight = { 0 }; + + // Actual area of the scrollbar excluding the arrow buttons + Rectangle scrollbar = { 0 }; + + // Slider bar that moves --[///]----- + Rectangle slider = { 0 }; + + // Normalize value + if (value > maxValue) value = maxValue; + if (value < minValue) value = minValue; + + int valueRange = maxValue - minValue; + if (valueRange <= 0) valueRange = 1; + + int sliderSize = GuiGetStyle(SCROLLBAR, SCROLL_SLIDER_SIZE); + if (sliderSize < 1) sliderSize = 1; // TODO: Consider a minimum slider size + + // Calculate rectangles for all of the components + arrowUpLeft = RAYGUI_CLITERAL(Rectangle){ + (float)bounds.x + GuiGetStyle(SCROLLBAR, BORDER_WIDTH), + (float)bounds.y + GuiGetStyle(SCROLLBAR, BORDER_WIDTH), + (float)spinnerSize, (float)spinnerSize }; + + if (isVertical) + { + arrowDownRight = RAYGUI_CLITERAL(Rectangle){ (float)bounds.x + GuiGetStyle(SCROLLBAR, BORDER_WIDTH), (float)bounds.y + bounds.height - spinnerSize - GuiGetStyle(SCROLLBAR, BORDER_WIDTH), (float)spinnerSize, (float)spinnerSize }; + scrollbar = RAYGUI_CLITERAL(Rectangle){ bounds.x + GuiGetStyle(SCROLLBAR, BORDER_WIDTH) + GuiGetStyle(SCROLLBAR, SCROLL_PADDING), arrowUpLeft.y + arrowUpLeft.height, bounds.width - 2*(GuiGetStyle(SCROLLBAR, BORDER_WIDTH) + GuiGetStyle(SCROLLBAR, SCROLL_PADDING)), bounds.height - arrowUpLeft.height - arrowDownRight.height - 2*GuiGetStyle(SCROLLBAR, BORDER_WIDTH) }; + + // Make sure the slider won't get outside of the scrollbar + sliderSize = (sliderSize >= scrollbar.height)? ((int)scrollbar.height - 2) : sliderSize; + slider = RAYGUI_CLITERAL(Rectangle){ + bounds.x + GuiGetStyle(SCROLLBAR, BORDER_WIDTH) + GuiGetStyle(SCROLLBAR, SCROLL_SLIDER_PADDING), + scrollbar.y + (int)(((float)(value - minValue)/valueRange)*(scrollbar.height - sliderSize)), + bounds.width - 2*(GuiGetStyle(SCROLLBAR, BORDER_WIDTH) + GuiGetStyle(SCROLLBAR, SCROLL_SLIDER_PADDING)), + (float)sliderSize }; + } + else // horizontal + { + arrowDownRight = RAYGUI_CLITERAL(Rectangle){ (float)bounds.x + bounds.width - spinnerSize - GuiGetStyle(SCROLLBAR, BORDER_WIDTH), (float)bounds.y + GuiGetStyle(SCROLLBAR, BORDER_WIDTH), (float)spinnerSize, (float)spinnerSize }; + scrollbar = RAYGUI_CLITERAL(Rectangle){ arrowUpLeft.x + arrowUpLeft.width, bounds.y + GuiGetStyle(SCROLLBAR, BORDER_WIDTH) + GuiGetStyle(SCROLLBAR, SCROLL_PADDING), bounds.width - arrowUpLeft.width - arrowDownRight.width - 2*GuiGetStyle(SCROLLBAR, BORDER_WIDTH), bounds.height - 2*(GuiGetStyle(SCROLLBAR, BORDER_WIDTH) + GuiGetStyle(SCROLLBAR, SCROLL_PADDING)) }; + + // Make sure the slider won't get outside of the scrollbar + sliderSize = (sliderSize >= scrollbar.width)? ((int)scrollbar.width - 2) : sliderSize; + slider = RAYGUI_CLITERAL(Rectangle){ + scrollbar.x + (int)(((float)(value - minValue)/valueRange)*(scrollbar.width - sliderSize)), + bounds.y + GuiGetStyle(SCROLLBAR, BORDER_WIDTH) + GuiGetStyle(SCROLLBAR, SCROLL_SLIDER_PADDING), + (float)sliderSize, + bounds.height - 2*(GuiGetStyle(SCROLLBAR, BORDER_WIDTH) + GuiGetStyle(SCROLLBAR, SCROLL_SLIDER_PADDING)) }; + } + + // Update control + //-------------------------------------------------------------------- + if ((state != STATE_DISABLED) && !guiLocked) + { + Vector2 mousePoint = GetMousePosition(); + + if (guiControlExclusiveMode) // Allows to keep dragging outside of bounds + { + if (IsMouseButtonDown(MOUSE_LEFT_BUTTON) && + !CheckCollisionPointRec(mousePoint, arrowUpLeft) && + !CheckCollisionPointRec(mousePoint, arrowDownRight)) + { + if (CHECK_BOUNDS_ID(bounds, guiControlExclusiveRec)) + { + state = STATE_PRESSED; + + if (isVertical) value = (int)(((float)(mousePoint.y - scrollbar.y - slider.height/2)*valueRange)/(scrollbar.height - slider.height) + minValue); + else value = (int)(((float)(mousePoint.x - scrollbar.x - slider.width/2)*valueRange)/(scrollbar.width - slider.width) + minValue); + } + } + else + { + guiControlExclusiveMode = false; + guiControlExclusiveRec = RAYGUI_CLITERAL(Rectangle){ 0, 0, 0, 0 }; + } + } + else if (CheckCollisionPointRec(mousePoint, bounds)) + { + state = STATE_FOCUSED; + + // Handle mouse wheel + int wheel = (int)GetMouseWheelMove(); + if (wheel != 0) value += wheel; + + // Handle mouse button down + if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) + { + guiControlExclusiveMode = true; + guiControlExclusiveRec = bounds; // Store bounds as an identifier when dragging starts + + // Check arrows click + if (CheckCollisionPointRec(mousePoint, arrowUpLeft)) value -= valueRange/GuiGetStyle(SCROLLBAR, SCROLL_SPEED); + else if (CheckCollisionPointRec(mousePoint, arrowDownRight)) value += valueRange/GuiGetStyle(SCROLLBAR, SCROLL_SPEED); + else if (!CheckCollisionPointRec(mousePoint, slider)) + { + // If click on scrollbar position but not on slider, place slider directly on that position + if (isVertical) value = (int)(((float)(mousePoint.y - scrollbar.y - slider.height/2)*valueRange)/(scrollbar.height - slider.height) + minValue); + else value = (int)(((float)(mousePoint.x - scrollbar.x - slider.width/2)*valueRange)/(scrollbar.width - slider.width) + minValue); + } + + state = STATE_PRESSED; + } + + // Keyboard control on mouse hover scrollbar + /* + if (isVertical) + { + if (IsKeyDown(KEY_DOWN)) value += 5; + else if (IsKeyDown(KEY_UP)) value -= 5; + } + else + { + if (IsKeyDown(KEY_RIGHT)) value += 5; + else if (IsKeyDown(KEY_LEFT)) value -= 5; + } + */ + } + + // Normalize value + if (value > maxValue) value = maxValue; + if (value < minValue) value = minValue; + } + //-------------------------------------------------------------------- + + // Draw control + //-------------------------------------------------------------------- + GuiDrawRectangle(bounds, GuiGetStyle(SCROLLBAR, BORDER_WIDTH), GetColor(GuiGetStyle(LISTVIEW, BORDER + state*3)), GetColor(GuiGetStyle(DEFAULT, BORDER_COLOR_DISABLED))); // Draw the background + + GuiDrawRectangle(scrollbar, 0, BLANK, GetColor(GuiGetStyle(BUTTON, BASE_COLOR_NORMAL))); // Draw the scrollbar active area background + GuiDrawRectangle(slider, 0, BLANK, GetColor(GuiGetStyle(SLIDER, BORDER + state*3))); // Draw the slider bar + + // Draw arrows (using icon if available) + if (GuiGetStyle(SCROLLBAR, ARROWS_VISIBLE)) + { +#if defined(RAYGUI_NO_ICONS) + GuiDrawText(isVertical? "^" : "<", + RAYGUI_CLITERAL(Rectangle){ arrowUpLeft.x, arrowUpLeft.y, isVertical? bounds.width : bounds.height, isVertical? bounds.width : bounds.height }, + TEXT_ALIGN_CENTER, GetColor(GuiGetStyle(DROPDOWNBOX, TEXT + (state*3)))); + GuiDrawText(isVertical? "v" : ">", + RAYGUI_CLITERAL(Rectangle){ arrowDownRight.x, arrowDownRight.y, isVertical? bounds.width : bounds.height, isVertical? bounds.width : bounds.height }, + TEXT_ALIGN_CENTER, GetColor(GuiGetStyle(DROPDOWNBOX, TEXT + (state*3)))); +#else + GuiDrawText(isVertical? "#121#" : "#118#", + RAYGUI_CLITERAL(Rectangle){ arrowUpLeft.x, arrowUpLeft.y, isVertical? bounds.width : bounds.height, isVertical? bounds.width : bounds.height }, + TEXT_ALIGN_CENTER, GetColor(GuiGetStyle(SCROLLBAR, TEXT + state*3))); // ICON_ARROW_UP_FILL / ICON_ARROW_LEFT_FILL + GuiDrawText(isVertical? "#120#" : "#119#", + RAYGUI_CLITERAL(Rectangle){ arrowDownRight.x, arrowDownRight.y, isVertical? bounds.width : bounds.height, isVertical? bounds.width : bounds.height }, + TEXT_ALIGN_CENTER, GetColor(GuiGetStyle(SCROLLBAR, TEXT + state*3))); // ICON_ARROW_DOWN_FILL / ICON_ARROW_RIGHT_FILL +#endif + } + //-------------------------------------------------------------------- + + return value; +} + +// Color fade-in or fade-out, alpha goes from 0.0f to 1.0f +// WARNING: It multiplies current alpha by alpha scale factor +static Color GuiFade(Color color, float alpha) +{ + if (alpha < 0.0f) alpha = 0.0f; + else if (alpha > 1.0f) alpha = 1.0f; + + Color result = { color.r, color.g, color.b, (unsigned char)(color.a*alpha) }; + + return result; +} + +#if defined(RAYGUI_STANDALONE) +// Returns a Color struct from hexadecimal value +static Color GetColor(int hexValue) +{ + Color color; + + color.r = (unsigned char)(hexValue >> 24) & 0xFF; + color.g = (unsigned char)(hexValue >> 16) & 0xFF; + color.b = (unsigned char)(hexValue >> 8) & 0xFF; + color.a = (unsigned char)hexValue & 0xFF; + + return color; +} + +// Returns hexadecimal value for a Color +static int ColorToInt(Color color) +{ + return (((int)color.r << 24) | ((int)color.g << 16) | ((int)color.b << 8) | (int)color.a); +} + +// Check if point is inside rectangle +static bool CheckCollisionPointRec(Vector2 point, Rectangle rec) +{ + bool collision = false; + + if ((point.x >= rec.x) && (point.x <= (rec.x + rec.width)) && + (point.y >= rec.y) && (point.y <= (rec.y + rec.height))) collision = true; + + return collision; +} + +// Formatting of text with variables to 'embed' +static const char *TextFormat(const char *text, ...) +{ + #if !defined(RAYGUI_TEXTFORMAT_MAX_SIZE) + #define RAYGUI_TEXTFORMAT_MAX_SIZE 256 + #endif + + static char buffer[RAYGUI_TEXTFORMAT_MAX_SIZE]; + + va_list args; + va_start(args, text); + vsprintf(buffer, text, args); + va_end(args); + + return buffer; +} + +// Draw rectangle with vertical gradient fill color +// NOTE: This function is only used by GuiColorPicker() +static void DrawRectangleGradientV(int posX, int posY, int width, int height, Color color1, Color color2) +{ + Rectangle bounds = { (float)posX, (float)posY, (float)width, (float)height }; + DrawRectangleGradientEx(bounds, color1, color2, color2, color1); +} + +// Split string into multiple strings +const char **TextSplit(const char *text, char delimiter, int *count) +{ + // NOTE: Current implementation returns a copy of the provided string with '\0' (string end delimiter) + // inserted between strings defined by "delimiter" parameter. No memory is dynamically allocated, + // all used memory is static... it has some limitations: + // 1. Maximum number of possible split strings is set by RAYGUI_TEXTSPLIT_MAX_ITEMS + // 2. Maximum size of text to split is RAYGUI_TEXTSPLIT_MAX_TEXT_SIZE + + #if !defined(RAYGUI_TEXTSPLIT_MAX_ITEMS) + #define RAYGUI_TEXTSPLIT_MAX_ITEMS 128 + #endif + #if !defined(RAYGUI_TEXTSPLIT_MAX_TEXT_SIZE) + #define RAYGUI_TEXTSPLIT_MAX_TEXT_SIZE 1024 + #endif + + static const char *result[RAYGUI_TEXTSPLIT_MAX_ITEMS] = { NULL }; + static char buffer[RAYGUI_TEXTSPLIT_MAX_TEXT_SIZE] = { 0 }; + memset(buffer, 0, RAYGUI_TEXTSPLIT_MAX_TEXT_SIZE); + + result[0] = buffer; + int counter = 0; + + if (text != NULL) + { + counter = 1; + + // Count how many substrings we have on text and point to every one + for (int i = 0; i < RAYGUI_TEXTSPLIT_MAX_TEXT_SIZE; i++) + { + buffer[i] = text[i]; + if (buffer[i] == '\0') break; + else if (buffer[i] == delimiter) + { + buffer[i] = '\0'; // Set an end of string at this point + result[counter] = buffer + i + 1; + counter++; + + if (counter == RAYGUI_TEXTSPLIT_MAX_ITEMS) break; + } + } + } + + *count = counter; + return result; +} + +// Get integer value from text +// NOTE: This function replaces atoi() [stdlib.h] +static int TextToInteger(const char *text) +{ + int value = 0; + int sign = 1; + + if ((text[0] == '+') || (text[0] == '-')) + { + if (text[0] == '-') sign = -1; + text++; + } + + for (int i = 0; ((text[i] >= '0') && (text[i] <= '9')); ++i) value = value*10 + (int)(text[i] - '0'); + + return value*sign; +} + +// Get float value from text +// NOTE: This function replaces atof() [stdlib.h] +// WARNING: Only '.' character is understood as decimal point +static float TextToFloat(const char *text) +{ + float value = 0.0f; + float sign = 1.0f; + + if ((text[0] == '+') || (text[0] == '-')) + { + if (text[0] == '-') sign = -1.0f; + text++; + } + + int i = 0; + for (; ((text[i] >= '0') && (text[i] <= '9')); i++) value = value*10.0f + (float)(text[i] - '0'); + + if (text[i++] != '.') value *= sign; + else + { + float divisor = 10.0f; + for (; ((text[i] >= '0') && (text[i] <= '9')); i++) + { + value += ((float)(text[i] - '0'))/divisor; + divisor = divisor*10.0f; + } + } + + return value; +} + +// Encode codepoint into UTF-8 text (char array size returned as parameter) +static const char *CodepointToUTF8(int codepoint, int *byteSize) +{ + static char utf8[6] = { 0 }; + int size = 0; + + if (codepoint <= 0x7f) + { + utf8[0] = (char)codepoint; + size = 1; + } + else if (codepoint <= 0x7ff) + { + utf8[0] = (char)(((codepoint >> 6) & 0x1f) | 0xc0); + utf8[1] = (char)((codepoint & 0x3f) | 0x80); + size = 2; + } + else if (codepoint <= 0xffff) + { + utf8[0] = (char)(((codepoint >> 12) & 0x0f) | 0xe0); + utf8[1] = (char)(((codepoint >> 6) & 0x3f) | 0x80); + utf8[2] = (char)((codepoint & 0x3f) | 0x80); + size = 3; + } + else if (codepoint <= 0x10ffff) + { + utf8[0] = (char)(((codepoint >> 18) & 0x07) | 0xf0); + utf8[1] = (char)(((codepoint >> 12) & 0x3f) | 0x80); + utf8[2] = (char)(((codepoint >> 6) & 0x3f) | 0x80); + utf8[3] = (char)((codepoint & 0x3f) | 0x80); + size = 4; + } + + *byteSize = size; + + return utf8; +} + +// Get next codepoint in a UTF-8 encoded text, scanning until '\0' is found +// When a invalid UTF-8 byte is encountered we exit as soon as possible and a '?'(0x3f) codepoint is returned +// Total number of bytes processed are returned as a parameter +// NOTE: the standard says U+FFFD should be returned in case of errors +// but that character is not supported by the default font in raylib +static int GetCodepointNext(const char *text, int *codepointSize) +{ + const char *ptr = text; + int codepoint = 0x3f; // Codepoint (defaults to '?') + *codepointSize = 1; + + // Get current codepoint and bytes processed + if (0xf0 == (0xf8 & ptr[0])) + { + // 4 byte UTF-8 codepoint + if (((ptr[1] & 0xC0) ^ 0x80) || ((ptr[2] & 0xC0) ^ 0x80) || ((ptr[3] & 0xC0) ^ 0x80)) { return codepoint; } //10xxxxxx checks + codepoint = ((0x07 & ptr[0]) << 18) | ((0x3f & ptr[1]) << 12) | ((0x3f & ptr[2]) << 6) | (0x3f & ptr[3]); + *codepointSize = 4; + } + else if (0xe0 == (0xf0 & ptr[0])) + { + // 3 byte UTF-8 codepoint */ + if (((ptr[1] & 0xC0) ^ 0x80) || ((ptr[2] & 0xC0) ^ 0x80)) { return codepoint; } //10xxxxxx checks + codepoint = ((0x0f & ptr[0]) << 12) | ((0x3f & ptr[1]) << 6) | (0x3f & ptr[2]); + *codepointSize = 3; + } + else if (0xc0 == (0xe0 & ptr[0])) + { + // 2 byte UTF-8 codepoint + if ((ptr[1] & 0xC0) ^ 0x80) { return codepoint; } //10xxxxxx checks + codepoint = ((0x1f & ptr[0]) << 6) | (0x3f & ptr[1]); + *codepointSize = 2; + } + else if (0x00 == (0x80 & ptr[0])) + { + // 1 byte UTF-8 codepoint + codepoint = ptr[0]; + *codepointSize = 1; + } + + return codepoint; +} +#endif // RAYGUI_STANDALONE + +#endif // RAYGUI_IMPLEMENTATION diff --git a/third_party/raylib/include/raylib.h b/third_party/raylib/include/raylib.h new file mode 100644 index 0000000000..ea973ff1ba --- /dev/null +++ b/third_party/raylib/include/raylib.h @@ -0,0 +1,1766 @@ +/********************************************************************************************** +* +* raylib v5.6-dev - A simple and easy-to-use library to enjoy videogames programming (www.raylib.com) +* +* FEATURES: +* - NO external dependencies, all required libraries included with raylib +* - Multiplatform: Windows, Linux, FreeBSD, OpenBSD, NetBSD, DragonFly, +* MacOS, Haiku, Android, Raspberry Pi, DRM native, HTML5. +* - Written in plain C code (C99) in PascalCase/camelCase notation +* - Hardware accelerated with OpenGL (1.1, 2.1, 3.3, 4.3, ES2, ES3 - choose at compile) +* - Unique OpenGL abstraction layer (usable as standalone module): [rlgl] +* - Multiple Fonts formats supported (TTF, OTF, FNT, BDF, Sprite fonts) +* - Outstanding texture formats support, including compressed formats (DXT, ETC, ASTC) +* - Full 3d support for 3d Shapes, Models, Billboards, Heightmaps and more! +* - Flexible Materials system, supporting classic maps and PBR maps +* - Animated 3D models supported (skeletal bones animation) (IQM, M3D, GLTF) +* - Shaders support, including Model shaders and Postprocessing shaders +* - Powerful math module for Vector, Matrix and Quaternion operations: [raymath] +* - Audio loading and playing with streaming support (WAV, OGG, MP3, FLAC, QOA, XM, MOD) +* - VR stereo rendering with configurable HMD device parameters +* - Bindings to multiple programming languages available! +* +* NOTES: +* - One default Font is loaded on InitWindow()->LoadFontDefault() [core, text] +* - One default Texture2D is loaded on rlglInit(), 1x1 white pixel R8G8B8A8 [rlgl] (OpenGL 3.3 or ES2) +* - One default Shader is loaded on rlglInit()->rlLoadShaderDefault() [rlgl] (OpenGL 3.3 or ES2) +* - One default RenderBatch is loaded on rlglInit()->rlLoadRenderBatch() [rlgl] (OpenGL 3.3 or ES2) +* +* DEPENDENCIES (included): +* [rcore][GLFW] rglfw (Camilla Löwy - github.com/glfw/glfw) for window/context management and input +* [rcore][RGFW] rgfw (ColleagueRiley - github.com/ColleagueRiley/RGFW) for window/context management and input +* [rlgl] glad/glad_gles2 (David Herberth - github.com/Dav1dde/glad) for OpenGL 3.3 extensions loading +* [raudio] miniaudio (David Reid - github.com/mackron/miniaudio) for audio device/context management +* +* OPTIONAL DEPENDENCIES (included): +* [rcore] msf_gif (Miles Fogle) for GIF recording +* [rcore] sinfl (Micha Mettke) for DEFLATE decompression algorithm +* [rcore] sdefl (Micha Mettke) for DEFLATE compression algorithm +* [rcore] rprand (Ramon Snatamaria) for pseudo-random numbers generation +* [rtextures] qoi (Dominic Szablewski - https://phoboslab.org) for QOI image manage +* [rtextures] stb_image (Sean Barret) for images loading (BMP, TGA, PNG, JPEG, HDR...) +* [rtextures] stb_image_write (Sean Barret) for image writing (BMP, TGA, PNG, JPG) +* [rtextures] stb_image_resize2 (Sean Barret) for image resizing algorithms +* [rtextures] stb_perlin (Sean Barret) for Perlin Noise image generation +* [rtext] stb_truetype (Sean Barret) for ttf fonts loading +* [rtext] stb_rect_pack (Sean Barret) for rectangles packing +* [rmodels] par_shapes (Philip Rideout) for parametric 3d shapes generation +* [rmodels] tinyobj_loader_c (Syoyo Fujita) for models loading (OBJ, MTL) +* [rmodels] cgltf (Johannes Kuhlmann) for models loading (glTF) +* [rmodels] m3d (bzt) for models loading (M3D, https://bztsrc.gitlab.io/model3d) +* [rmodels] vox_loader (Johann Nadalutti) for models loading (VOX) +* [raudio] dr_wav (David Reid) for WAV audio file loading +* [raudio] dr_flac (David Reid) for FLAC audio file loading +* [raudio] dr_mp3 (David Reid) for MP3 audio file loading +* [raudio] stb_vorbis (Sean Barret) for OGG audio loading +* [raudio] jar_xm (Joshua Reisenauer) for XM audio module loading +* [raudio] jar_mod (Joshua Reisenauer) for MOD audio module loading +* [raudio] qoa (Dominic Szablewski - https://phoboslab.org) for QOA audio manage +* +* +* LICENSE: zlib/libpng +* +* raylib is licensed under an unmodified zlib/libpng license, which is an OSI-certified, +* BSD-like license that allows static linking with closed source software: +* +* Copyright (c) 2013-2024 Ramon Santamaria (@raysan5) +* +* This software is provided "as-is", without any express or implied warranty. In no event +* will the authors be held liable for any damages arising from the use of this software. +* +* Permission is granted to anyone to use this software for any purpose, including commercial +* applications, and to alter it and redistribute it freely, subject to the following restrictions: +* +* 1. The origin of this software must not be misrepresented; you must not claim that you +* wrote the original software. If you use this software in a product, an acknowledgment +* in the product documentation would be appreciated but is not required. +* +* 2. Altered source versions must be plainly marked as such, and must not be misrepresented +* as being the original software. +* +* 3. This notice may not be removed or altered from any source distribution. +* +**********************************************************************************************/ + +#ifndef RAYLIB_H +#define RAYLIB_H + +#include // Required for: va_list - Only used by TraceLogCallback + +#define RAYLIB_VERSION_MAJOR 5 +#define RAYLIB_VERSION_MINOR 6 +#define RAYLIB_VERSION_PATCH 0 +#define RAYLIB_VERSION "5.6-dev" + +// Function specifiers in case library is build/used as a shared library +// NOTE: Microsoft specifiers to tell compiler that symbols are imported/exported from a .dll +// NOTE: visibility("default") attribute makes symbols "visible" when compiled with -fvisibility=hidden +#if defined(_WIN32) + #if defined(__TINYC__) + #define __declspec(x) __attribute__((x)) + #endif + #if defined(BUILD_LIBTYPE_SHARED) + #define RLAPI __declspec(dllexport) // We are building the library as a Win32 shared library (.dll) + #elif defined(USE_LIBTYPE_SHARED) + #define RLAPI __declspec(dllimport) // We are using the library as a Win32 shared library (.dll) + #endif +#else + #if defined(BUILD_LIBTYPE_SHARED) + #define RLAPI __attribute__((visibility("default"))) // We are building as a Unix shared library (.so/.dylib) + #endif +#endif + +#ifndef RLAPI + #define RLAPI // Functions defined as 'extern' by default (implicit specifiers) +#endif + +//---------------------------------------------------------------------------------- +// Some basic Defines +//---------------------------------------------------------------------------------- +#ifndef PI + #define PI 3.14159265358979323846f +#endif +#ifndef DEG2RAD + #define DEG2RAD (PI/180.0f) +#endif +#ifndef RAD2DEG + #define RAD2DEG (180.0f/PI) +#endif + +// Allow custom memory allocators +// NOTE: Require recompiling raylib sources +#ifndef RL_MALLOC + #define RL_MALLOC(sz) malloc(sz) +#endif +#ifndef RL_CALLOC + #define RL_CALLOC(n,sz) calloc(n,sz) +#endif +#ifndef RL_REALLOC + #define RL_REALLOC(ptr,sz) realloc(ptr,sz) +#endif +#ifndef RL_FREE + #define RL_FREE(ptr) free(ptr) +#endif + +// NOTE: MSVC C++ compiler does not support compound literals (C99 feature) +// Plain structures in C++ (without constructors) can be initialized with { } +// This is called aggregate initialization (C++11 feature) +#if defined(__cplusplus) + #define CLITERAL(type) type +#else + #define CLITERAL(type) (type) +#endif + +// Some compilers (mostly macos clang) default to C++98, +// where aggregate initialization can't be used +// So, give a more clear error stating how to fix this +#if !defined(_MSC_VER) && (defined(__cplusplus) && __cplusplus < 201103L) + #error "C++11 or later is required. Add -std=c++11" +#endif + +// NOTE: We set some defines with some data types declared by raylib +// Other modules (raymath, rlgl) also require some of those types, so, +// to be able to use those other modules as standalone (not depending on raylib) +// this defines are very useful for internal check and avoid type (re)definitions +#define RL_COLOR_TYPE +#define RL_RECTANGLE_TYPE +#define RL_VECTOR2_TYPE +#define RL_VECTOR3_TYPE +#define RL_VECTOR4_TYPE +#define RL_QUATERNION_TYPE +#define RL_MATRIX_TYPE + +// Some Basic Colors +// NOTE: Custom raylib color palette for amazing visuals on WHITE background +#define _rl_LIGHTGRAY CLITERAL(Color){ 200, 200, 200, 255 } // Light Gray +#define _rl_GRAY CLITERAL(Color){ 130, 130, 130, 255 } // Gray +#define _rl_DARKGRAY CLITERAL(Color){ 80, 80, 80, 255 } // Dark Gray +#define _rl_YELLOW CLITERAL(Color){ 253, 249, 0, 255 } // Yellow +#define _rl_GOLD CLITERAL(Color){ 255, 203, 0, 255 } // Gold +#define _rl_ORANGE CLITERAL(Color){ 255, 161, 0, 255 } // Orange +#define _rl_PINK CLITERAL(Color){ 255, 109, 194, 255 } // Pink +#define _rl_RED CLITERAL(Color){ 230, 41, 55, 255 } // Red +#define _rl_MAROON CLITERAL(Color){ 190, 33, 55, 255 } // Maroon +#define _rl_GREEN CLITERAL(Color){ 0, 228, 48, 255 } // Green +#define _rl_LIME CLITERAL(Color){ 0, 158, 47, 255 } // Lime +#define _rl_DARKGREEN CLITERAL(Color){ 0, 117, 44, 255 } // Dark Green +#define _rl_SKYBLUE CLITERAL(Color){ 102, 191, 255, 255 } // Sky Blue +#define _rl_BLUE CLITERAL(Color){ 0, 121, 241, 255 } // Blue +#define _rl_DARKBLUE CLITERAL(Color){ 0, 82, 172, 255 } // Dark Blue +#define _rl_PURPLE CLITERAL(Color){ 200, 122, 255, 255 } // Purple +#define _rl_VIOLET CLITERAL(Color){ 135, 60, 190, 255 } // Violet +#define _rl_DARKPURPLE CLITERAL(Color){ 112, 31, 126, 255 } // Dark Purple +#define _rl_BEIGE CLITERAL(Color){ 211, 176, 131, 255 } // Beige +#define _rl_BROWN CLITERAL(Color){ 127, 106, 79, 255 } // Brown +#define _rl_DARKBROWN CLITERAL(Color){ 76, 63, 47, 255 } // Dark Brown + +#define _rl_WHITE CLITERAL(Color){ 255, 255, 255, 255 } // White +#define _rl_BLACK CLITERAL(Color){ 0, 0, 0, 255 } // Black +#define _rl_BLANK CLITERAL(Color){ 0, 0, 0, 0 } // Blank (Transparent) +#define _rl_MAGENTA CLITERAL(Color){ 255, 0, 255, 255 } // Magenta +#define _rl_RAYWHITE CLITERAL(Color){ 245, 245, 245, 255 } // My own White (raylib logo) + +#ifndef OPENPILOT_RAYLIB + #define LIGHTGRAY _rl_LIGHTGRAY + #define GRAY _rl_GRAY + #define DARKGRAY _rl_DARKGRAY + #define YELLOW _rl_YELLOW + #define GOLD _rl_GOLD + #define ORANGE _rl_ORANGE + #define PINK _rl_PINK + #define RED _rl_RED + #define MAROON _rl_MAROON + #define GREEN _rl_GREEN + #define LIME _rl_LIME + #define DARKGREEN _rl_DARKGREEN + #define SKYBLUE _rl_SKYBLUE + #define BLUE _rl_BLUE + #define DARKBLUE _rl_DARKBLUE + #define PURPLE _rl_PURPLE + #define VIOLET _rl_VIOLET + #define DARKPURPLE _rl_DARKBLUE + #define BEIGE _rl_BEIGE + #define BROWN _rl_BROWN + #define DARKBROWN _rl_DARKBROWN + + #define WHITE _rl_WHITE + #define BLACK _rl_BLACK + #define BLANK _rl_BLANK + #define MAGENTA _rl_MAGENTA + #define RAYWHITE _rl_RAYWHITE +#else + #define RAYLIB_LIGHTGRAY _rl_LIGHTGRAY + #define RAYLIB_GRAY _rl_GRAY + #define RAYLIB_DARKGRAY _rl_DARKGRAY + #define RAYLIB_YELLOW _rl_YELLOW + #define RAYLIB_GOLD _rl_GOLD + #define RAYLIB_ORANGE _rl_ORANGE + #define RAYLIB_PINK _rl_PINK + #define RAYLIB_RED _rl_RED + #define RAYLIB_MAROON _rl_MAROON + #define RAYLIB_GREEN _rl_GREEN + #define RAYLIB_LIME _rl_LIME + #define RAYLIB_DARKGREEN _rl_DARKGREEN + #define RAYLIB_SKYBLUE _rl_SKYBLUE + #define RAYLIB_BLUE _rl_BLUE + #define RAYLIB_DARKBLUE _rl_DARKBLUE + #define RAYLIB_PURPLE _rl_PURPLE + #define RAYLIB_VIOLET _rl_VIOLET + #define RAYLIB_DARKPURPLE _rl_DARKBLUE + #define RAYLIB_BEIGE _rl_BEIGE + #define RAYLIB_BROWN _rl_BROWN + #define RAYLIB_DARKBROWN _rl_DARKBROWN + + #define RAYLIB_WHITE _rl_WHITE + #define RAYLIB_BLACK _rl_BLACK + #define RAYLIB_BLANK _rl_BLANK + #define RAYLIB_MAGENTA _rl_MAGENTA + #define RAYLIB_RAYWHITE _rl_RAYWHITE +#endif + +//---------------------------------------------------------------------------------- +// Structures Definition +//---------------------------------------------------------------------------------- +// Boolean type +#if (defined(__STDC__) && __STDC_VERSION__ >= 199901L) || (defined(_MSC_VER) && _MSC_VER >= 1800) + #include +#elif !defined(__cplusplus) && !defined(bool) + typedef enum bool { false = 0, true = !false } bool; + #define RL_BOOL_TYPE +#endif + +// Vector2, 2 components +typedef struct Vector2 { + float x; // Vector x component + float y; // Vector y component +} Vector2; + +// Vector3, 3 components +typedef struct Vector3 { + float x; // Vector x component + float y; // Vector y component + float z; // Vector z component +} Vector3; + +// Vector4, 4 components +typedef struct Vector4 { + float x; // Vector x component + float y; // Vector y component + float z; // Vector z component + float w; // Vector w component +} Vector4; + +// Quaternion, 4 components (Vector4 alias) +typedef Vector4 Quaternion; + +// Matrix, 4x4 components, column major, OpenGL style, right-handed +typedef struct Matrix { + float m0, m4, m8, m12; // Matrix first row (4 components) + float m1, m5, m9, m13; // Matrix second row (4 components) + float m2, m6, m10, m14; // Matrix third row (4 components) + float m3, m7, m11, m15; // Matrix fourth row (4 components) +} Matrix; + +// Color, 4 components, R8G8B8A8 (32bit) +typedef struct Color { + unsigned char r; // Color red value + unsigned char g; // Color green value + unsigned char b; // Color blue value + unsigned char a; // Color alpha value +} Color; + +// Rectangle, 4 components +typedef struct Rectangle { + float x; // Rectangle top-left corner position x + float y; // Rectangle top-left corner position y + float width; // Rectangle width + float height; // Rectangle height +} Rectangle; + +// Image, pixel data stored in CPU memory (RAM) +typedef struct Image { + void *data; // Image raw data + int width; // Image base width + int height; // Image base height + int mipmaps; // Mipmap levels, 1 by default + int format; // Data format (PixelFormat type) +} Image; + +// Texture, tex data stored in GPU memory (VRAM) +typedef struct Texture { + unsigned int id; // OpenGL texture id + int width; // Texture base width + int height; // Texture base height + int mipmaps; // Mipmap levels, 1 by default + int format; // Data format (PixelFormat type) +} Texture; + +// Texture2D, same as Texture +typedef Texture Texture2D; + +// TextureCubemap, same as Texture +typedef Texture TextureCubemap; + +// RenderTexture, fbo for texture rendering +typedef struct RenderTexture { + unsigned int id; // OpenGL framebuffer object id + Texture texture; // Color buffer attachment texture + Texture depth; // Depth buffer attachment texture +} RenderTexture; + +// RenderTexture2D, same as RenderTexture +typedef RenderTexture RenderTexture2D; + +// NPatchInfo, n-patch layout info +typedef struct NPatchInfo { + Rectangle source; // Texture source rectangle + int left; // Left border offset + int top; // Top border offset + int right; // Right border offset + int bottom; // Bottom border offset + int layout; // Layout of the n-patch: 3x3, 1x3 or 3x1 +} NPatchInfo; + +// GlyphInfo, font characters glyphs info +typedef struct GlyphInfo { + int value; // Character value (Unicode) + int offsetX; // Character offset X when drawing + int offsetY; // Character offset Y when drawing + int advanceX; // Character advance position X + Image image; // Character image data +} GlyphInfo; + +// Font, font texture and GlyphInfo array data +typedef struct Font { + int baseSize; // Base size (default chars height) + int glyphCount; // Number of glyph characters + int glyphPadding; // Padding around the glyph characters + Texture2D texture; // Texture atlas containing the glyphs + Rectangle *recs; // Rectangles in texture for the glyphs + GlyphInfo *glyphs; // Glyphs info data +} Font; + +// Camera, defines position/orientation in 3d space +typedef struct Camera3D { + Vector3 position; // Camera position + Vector3 target; // Camera target it looks-at + Vector3 up; // Camera up vector (rotation over its axis) + float fovy; // Camera field-of-view aperture in Y (degrees) in perspective, used as near plane width in orthographic + int projection; // Camera projection: CAMERA_PERSPECTIVE or CAMERA_ORTHOGRAPHIC +} Camera3D; + +typedef Camera3D Camera; // Camera type fallback, defaults to Camera3D + +// Camera2D, defines position/orientation in 2d space +typedef struct Camera2D { + Vector2 offset; // Camera offset (displacement from target) + Vector2 target; // Camera target (rotation and zoom origin) + float rotation; // Camera rotation in degrees + float zoom; // Camera zoom (scaling), should be 1.0f by default +} Camera2D; + +// Mesh, vertex data and vao/vbo +typedef struct Mesh { + int vertexCount; // Number of vertices stored in arrays + int triangleCount; // Number of triangles stored (indexed or not) + + // Vertex attributes data + float *vertices; // Vertex position (XYZ - 3 components per vertex) (shader-location = 0) + float *texcoords; // Vertex texture coordinates (UV - 2 components per vertex) (shader-location = 1) + float *texcoords2; // Vertex texture second coordinates (UV - 2 components per vertex) (shader-location = 5) + float *normals; // Vertex normals (XYZ - 3 components per vertex) (shader-location = 2) + float *tangents; // Vertex tangents (XYZW - 4 components per vertex) (shader-location = 4) + unsigned char *colors; // Vertex colors (RGBA - 4 components per vertex) (shader-location = 3) + unsigned short *indices; // Vertex indices (in case vertex data comes indexed) + + // Animation vertex data + float *animVertices; // Animated vertex positions (after bones transformations) + float *animNormals; // Animated normals (after bones transformations) + unsigned char *boneIds; // Vertex bone ids, max 255 bone ids, up to 4 bones influence by vertex (skinning) (shader-location = 6) + float *boneWeights; // Vertex bone weight, up to 4 bones influence by vertex (skinning) (shader-location = 7) + Matrix *boneMatrices; // Bones animated transformation matrices + int boneCount; // Number of bones + + // OpenGL identifiers + unsigned int vaoId; // OpenGL Vertex Array Object id + unsigned int *vboId; // OpenGL Vertex Buffer Objects id (default vertex data) +} Mesh; + +// Shader +typedef struct Shader { + unsigned int id; // Shader program id + int *locs; // Shader locations array (RL_MAX_SHADER_LOCATIONS) +} Shader; + +// MaterialMap +typedef struct MaterialMap { + Texture2D texture; // Material map texture + Color color; // Material map color + float value; // Material map value +} MaterialMap; + +// Material, includes shader and maps +typedef struct Material { + Shader shader; // Material shader + MaterialMap *maps; // Material maps array (MAX_MATERIAL_MAPS) + float params[4]; // Material generic parameters (if required) +} Material; + +// Transform, vertex transformation data +typedef struct Transform { + Vector3 translation; // Translation + Quaternion rotation; // Rotation + Vector3 scale; // Scale +} Transform; + +// Bone, skeletal animation bone +typedef struct BoneInfo { + char name[32]; // Bone name + int parent; // Bone parent +} BoneInfo; + +// Model, meshes, materials and animation data +typedef struct Model { + Matrix transform; // Local transform matrix + + int meshCount; // Number of meshes + int materialCount; // Number of materials + Mesh *meshes; // Meshes array + Material *materials; // Materials array + int *meshMaterial; // Mesh material number + + // Animation data + int boneCount; // Number of bones + BoneInfo *bones; // Bones information (skeleton) + Transform *bindPose; // Bones base transformation (pose) +} Model; + +// ModelAnimation +typedef struct ModelAnimation { + int boneCount; // Number of bones + int frameCount; // Number of animation frames + BoneInfo *bones; // Bones information (skeleton) + Transform **framePoses; // Poses array by frame + char name[32]; // Animation name +} ModelAnimation; + +// Ray, ray for raycasting +typedef struct Ray { + Vector3 position; // Ray position (origin) + Vector3 direction; // Ray direction (normalized) +} Ray; + +// RayCollision, ray hit information +typedef struct RayCollision { + bool hit; // Did the ray hit something? + float distance; // Distance to the nearest hit + Vector3 point; // Point of the nearest hit + Vector3 normal; // Surface normal of hit +} RayCollision; + +// BoundingBox +typedef struct BoundingBox { + Vector3 min; // Minimum vertex box-corner + Vector3 max; // Maximum vertex box-corner +} BoundingBox; + +// Wave, audio wave data +typedef struct Wave { + unsigned int frameCount; // Total number of frames (considering channels) + unsigned int sampleRate; // Frequency (samples per second) + unsigned int sampleSize; // Bit depth (bits per sample): 8, 16, 32 (24 not supported) + unsigned int channels; // Number of channels (1-mono, 2-stereo, ...) + void *data; // Buffer data pointer +} Wave; + +// Opaque structs declaration +// NOTE: Actual structs are defined internally in raudio module +typedef struct rAudioBuffer rAudioBuffer; +typedef struct rAudioProcessor rAudioProcessor; + +// AudioStream, custom audio stream +typedef struct AudioStream { + rAudioBuffer *buffer; // Pointer to internal data used by the audio system + rAudioProcessor *processor; // Pointer to internal data processor, useful for audio effects + + unsigned int sampleRate; // Frequency (samples per second) + unsigned int sampleSize; // Bit depth (bits per sample): 8, 16, 32 (24 not supported) + unsigned int channels; // Number of channels (1-mono, 2-stereo, ...) +} AudioStream; + +// Sound +typedef struct Sound { + AudioStream stream; // Audio stream + unsigned int frameCount; // Total number of frames (considering channels) +} Sound; + +// Music, audio stream, anything longer than ~10 seconds should be streamed +typedef struct Music { + AudioStream stream; // Audio stream + unsigned int frameCount; // Total number of frames (considering channels) + bool looping; // Music looping enable + + int ctxType; // Type of music context (audio filetype) + void *ctxData; // Audio context data, depends on type +} Music; + +// VrDeviceInfo, Head-Mounted-Display device parameters +typedef struct VrDeviceInfo { + int hResolution; // Horizontal resolution in pixels + int vResolution; // Vertical resolution in pixels + float hScreenSize; // Horizontal size in meters + float vScreenSize; // Vertical size in meters + float eyeToScreenDistance; // Distance between eye and display in meters + float lensSeparationDistance; // Lens separation distance in meters + float interpupillaryDistance; // IPD (distance between pupils) in meters + float lensDistortionValues[4]; // Lens distortion constant parameters + float chromaAbCorrection[4]; // Chromatic aberration correction parameters +} VrDeviceInfo; + +// VrStereoConfig, VR stereo rendering configuration for simulator +typedef struct VrStereoConfig { + Matrix projection[2]; // VR projection matrices (per eye) + Matrix viewOffset[2]; // VR view offset matrices (per eye) + float leftLensCenter[2]; // VR left lens center + float rightLensCenter[2]; // VR right lens center + float leftScreenCenter[2]; // VR left screen center + float rightScreenCenter[2]; // VR right screen center + float scale[2]; // VR distortion scale + float scaleIn[2]; // VR distortion scale in +} VrStereoConfig; + +// File path list +typedef struct FilePathList { + unsigned int capacity; // Filepaths max entries + unsigned int count; // Filepaths entries count + char **paths; // Filepaths entries +} FilePathList; + +// Automation event +typedef struct AutomationEvent { + unsigned int frame; // Event frame + unsigned int type; // Event type (AutomationEventType) + int params[4]; // Event parameters (if required) +} AutomationEvent; + +// Automation event list +typedef struct AutomationEventList { + unsigned int capacity; // Events max entries (MAX_AUTOMATION_EVENTS) + unsigned int count; // Events entries count + AutomationEvent *events; // Events entries +} AutomationEventList; + +//---------------------------------------------------------------------------------- +// Enumerators Definition +//---------------------------------------------------------------------------------- +// System/Window config flags +// NOTE: Every bit registers one state (use it with bit masks) +// By default all flags are set to 0 +typedef enum { + FLAG_VSYNC_HINT = 0x00000040, // Set to try enabling V-Sync on GPU + FLAG_FULLSCREEN_MODE = 0x00000002, // Set to run program in fullscreen + FLAG_WINDOW_RESIZABLE = 0x00000004, // Set to allow resizable window + FLAG_WINDOW_UNDECORATED = 0x00000008, // Set to disable window decoration (frame and buttons) + FLAG_WINDOW_HIDDEN = 0x00000080, // Set to hide window + FLAG_WINDOW_MINIMIZED = 0x00000200, // Set to minimize window (iconify) + FLAG_WINDOW_MAXIMIZED = 0x00000400, // Set to maximize window (expanded to monitor) + FLAG_WINDOW_UNFOCUSED = 0x00000800, // Set to window non focused + FLAG_WINDOW_TOPMOST = 0x00001000, // Set to window always on top + FLAG_WINDOW_ALWAYS_RUN = 0x00000100, // Set to allow windows running while minimized + FLAG_WINDOW_TRANSPARENT = 0x00000010, // Set to allow transparent framebuffer + FLAG_WINDOW_HIGHDPI = 0x00002000, // Set to support HighDPI + FLAG_WINDOW_MOUSE_PASSTHROUGH = 0x00004000, // Set to support mouse passthrough, only supported when FLAG_WINDOW_UNDECORATED + FLAG_BORDERLESS_WINDOWED_MODE = 0x00008000, // Set to run program in borderless windowed mode + FLAG_MSAA_4X_HINT = 0x00000020, // Set to try enabling MSAA 4X + FLAG_INTERLACED_HINT = 0x00010000 // Set to try enabling interlaced video format (for V3D) +} ConfigFlags; + +// Trace log level +// NOTE: Organized by priority level +typedef enum { + LOG_ALL = 0, // Display all logs + LOG_TRACE, // Trace logging, intended for internal use only + LOG_DEBUG, // Debug logging, used for internal debugging, it should be disabled on release builds + LOG_INFO, // Info logging, used for program execution info + LOG_WARNING, // Warning logging, used on recoverable failures + LOG_ERROR, // Error logging, used on unrecoverable failures + LOG_FATAL, // Fatal logging, used to abort program: exit(EXIT_FAILURE) + LOG_NONE // Disable logging +} TraceLogLevel; + +// Keyboard keys (US keyboard layout) +// NOTE: Use GetKeyPressed() to allow redefining +// required keys for alternative layouts +typedef enum { + KEY_NULL = 0, // Key: NULL, used for no key pressed + // Alphanumeric keys + KEY_APOSTROPHE = 39, // Key: ' + KEY_COMMA = 44, // Key: , + KEY_MINUS = 45, // Key: - + KEY_PERIOD = 46, // Key: . + KEY_SLASH = 47, // Key: / + KEY_ZERO = 48, // Key: 0 + KEY_ONE = 49, // Key: 1 + KEY_TWO = 50, // Key: 2 + KEY_THREE = 51, // Key: 3 + KEY_FOUR = 52, // Key: 4 + KEY_FIVE = 53, // Key: 5 + KEY_SIX = 54, // Key: 6 + KEY_SEVEN = 55, // Key: 7 + KEY_EIGHT = 56, // Key: 8 + KEY_NINE = 57, // Key: 9 + KEY_SEMICOLON = 59, // Key: ; + KEY_EQUAL = 61, // Key: = + KEY_A = 65, // Key: A | a + KEY_B = 66, // Key: B | b + KEY_C = 67, // Key: C | c + KEY_D = 68, // Key: D | d + KEY_E = 69, // Key: E | e + KEY_F = 70, // Key: F | f + KEY_G = 71, // Key: G | g + KEY_H = 72, // Key: H | h + KEY_I = 73, // Key: I | i + KEY_J = 74, // Key: J | j + KEY_K = 75, // Key: K | k + KEY_L = 76, // Key: L | l + KEY_M = 77, // Key: M | m + KEY_N = 78, // Key: N | n + KEY_O = 79, // Key: O | o + KEY_P = 80, // Key: P | p + KEY_Q = 81, // Key: Q | q + KEY_R = 82, // Key: R | r + KEY_S = 83, // Key: S | s + KEY_T = 84, // Key: T | t + KEY_U = 85, // Key: U | u + KEY_V = 86, // Key: V | v + KEY_W = 87, // Key: W | w + KEY_X = 88, // Key: X | x + KEY_Y = 89, // Key: Y | y + KEY_Z = 90, // Key: Z | z + KEY_LEFT_BRACKET = 91, // Key: [ + KEY_BACKSLASH = 92, // Key: '\' + KEY_RIGHT_BRACKET = 93, // Key: ] + KEY_GRAVE = 96, // Key: ` + // Function keys + KEY_SPACE = 32, // Key: Space + KEY_ESCAPE = 256, // Key: Esc + KEY_ENTER = 257, // Key: Enter + KEY_TAB = 258, // Key: Tab + KEY_BACKSPACE = 259, // Key: Backspace + KEY_INSERT = 260, // Key: Ins + KEY_DELETE = 261, // Key: Del + KEY_RIGHT = 262, // Key: Cursor right + KEY_LEFT = 263, // Key: Cursor left + KEY_DOWN = 264, // Key: Cursor down + KEY_UP = 265, // Key: Cursor up + KEY_PAGE_UP = 266, // Key: Page up + KEY_PAGE_DOWN = 267, // Key: Page down + KEY_HOME = 268, // Key: Home + KEY_END = 269, // Key: End + KEY_CAPS_LOCK = 280, // Key: Caps lock + KEY_SCROLL_LOCK = 281, // Key: Scroll down + KEY_NUM_LOCK = 282, // Key: Num lock + KEY_PRINT_SCREEN = 283, // Key: Print screen + KEY_PAUSE = 284, // Key: Pause + KEY_F1 = 290, // Key: F1 + KEY_F2 = 291, // Key: F2 + KEY_F3 = 292, // Key: F3 + KEY_F4 = 293, // Key: F4 + KEY_F5 = 294, // Key: F5 + KEY_F6 = 295, // Key: F6 + KEY_F7 = 296, // Key: F7 + KEY_F8 = 297, // Key: F8 + KEY_F9 = 298, // Key: F9 + KEY_F10 = 299, // Key: F10 + KEY_F11 = 300, // Key: F11 + KEY_F12 = 301, // Key: F12 + KEY_LEFT_SHIFT = 340, // Key: Shift left + KEY_LEFT_CONTROL = 341, // Key: Control left + KEY_LEFT_ALT = 342, // Key: Alt left + KEY_LEFT_SUPER = 343, // Key: Super left + KEY_RIGHT_SHIFT = 344, // Key: Shift right + KEY_RIGHT_CONTROL = 345, // Key: Control right + KEY_RIGHT_ALT = 346, // Key: Alt right + KEY_RIGHT_SUPER = 347, // Key: Super right + KEY_KB_MENU = 348, // Key: KB menu + // Keypad keys + KEY_KP_0 = 320, // Key: Keypad 0 + KEY_KP_1 = 321, // Key: Keypad 1 + KEY_KP_2 = 322, // Key: Keypad 2 + KEY_KP_3 = 323, // Key: Keypad 3 + KEY_KP_4 = 324, // Key: Keypad 4 + KEY_KP_5 = 325, // Key: Keypad 5 + KEY_KP_6 = 326, // Key: Keypad 6 + KEY_KP_7 = 327, // Key: Keypad 7 + KEY_KP_8 = 328, // Key: Keypad 8 + KEY_KP_9 = 329, // Key: Keypad 9 + KEY_KP_DECIMAL = 330, // Key: Keypad . + KEY_KP_DIVIDE = 331, // Key: Keypad / + KEY_KP_MULTIPLY = 332, // Key: Keypad * + KEY_KP_SUBTRACT = 333, // Key: Keypad - + KEY_KP_ADD = 334, // Key: Keypad + + KEY_KP_ENTER = 335, // Key: Keypad Enter + KEY_KP_EQUAL = 336, // Key: Keypad = + // Android key buttons + KEY_BACK = 4, // Key: Android back button + KEY_MENU = 5, // Key: Android menu button + KEY_VOLUME_UP = 24, // Key: Android volume up button + KEY_VOLUME_DOWN = 25 // Key: Android volume down button +} KeyboardKey; + +// Add backwards compatibility support for deprecated names +#define MOUSE_LEFT_BUTTON MOUSE_BUTTON_LEFT +#define MOUSE_RIGHT_BUTTON MOUSE_BUTTON_RIGHT +#define MOUSE_MIDDLE_BUTTON MOUSE_BUTTON_MIDDLE + +// Mouse buttons +typedef enum { + MOUSE_BUTTON_LEFT = 0, // Mouse button left + MOUSE_BUTTON_RIGHT = 1, // Mouse button right + MOUSE_BUTTON_MIDDLE = 2, // Mouse button middle (pressed wheel) + MOUSE_BUTTON_SIDE = 3, // Mouse button side (advanced mouse device) + MOUSE_BUTTON_EXTRA = 4, // Mouse button extra (advanced mouse device) + MOUSE_BUTTON_FORWARD = 5, // Mouse button forward (advanced mouse device) + MOUSE_BUTTON_BACK = 6, // Mouse button back (advanced mouse device) +} MouseButton; + +// Mouse cursor +typedef enum { + MOUSE_CURSOR_DEFAULT = 0, // Default pointer shape + MOUSE_CURSOR_ARROW = 1, // Arrow shape + MOUSE_CURSOR_IBEAM = 2, // Text writing cursor shape + MOUSE_CURSOR_CROSSHAIR = 3, // Cross shape + MOUSE_CURSOR_POINTING_HAND = 4, // Pointing hand cursor + MOUSE_CURSOR_RESIZE_EW = 5, // Horizontal resize/move arrow shape + MOUSE_CURSOR_RESIZE_NS = 6, // Vertical resize/move arrow shape + MOUSE_CURSOR_RESIZE_NWSE = 7, // Top-left to bottom-right diagonal resize/move arrow shape + MOUSE_CURSOR_RESIZE_NESW = 8, // The top-right to bottom-left diagonal resize/move arrow shape + MOUSE_CURSOR_RESIZE_ALL = 9, // The omnidirectional resize/move cursor shape + MOUSE_CURSOR_NOT_ALLOWED = 10 // The operation-not-allowed shape +} MouseCursor; + +// Gamepad buttons +typedef enum { + GAMEPAD_BUTTON_UNKNOWN = 0, // Unknown button, just for error checking + GAMEPAD_BUTTON_LEFT_FACE_UP, // Gamepad left DPAD up button + GAMEPAD_BUTTON_LEFT_FACE_RIGHT, // Gamepad left DPAD right button + GAMEPAD_BUTTON_LEFT_FACE_DOWN, // Gamepad left DPAD down button + GAMEPAD_BUTTON_LEFT_FACE_LEFT, // Gamepad left DPAD left button + GAMEPAD_BUTTON_RIGHT_FACE_UP, // Gamepad right button up (i.e. PS3: Triangle, Xbox: Y) + GAMEPAD_BUTTON_RIGHT_FACE_RIGHT, // Gamepad right button right (i.e. PS3: Circle, Xbox: B) + GAMEPAD_BUTTON_RIGHT_FACE_DOWN, // Gamepad right button down (i.e. PS3: Cross, Xbox: A) + GAMEPAD_BUTTON_RIGHT_FACE_LEFT, // Gamepad right button left (i.e. PS3: Square, Xbox: X) + GAMEPAD_BUTTON_LEFT_TRIGGER_1, // Gamepad top/back trigger left (first), it could be a trailing button + GAMEPAD_BUTTON_LEFT_TRIGGER_2, // Gamepad top/back trigger left (second), it could be a trailing button + GAMEPAD_BUTTON_RIGHT_TRIGGER_1, // Gamepad top/back trigger right (first), it could be a trailing button + GAMEPAD_BUTTON_RIGHT_TRIGGER_2, // Gamepad top/back trigger right (second), it could be a trailing button + GAMEPAD_BUTTON_MIDDLE_LEFT, // Gamepad center buttons, left one (i.e. PS3: Select) + GAMEPAD_BUTTON_MIDDLE, // Gamepad center buttons, middle one (i.e. PS3: PS, Xbox: XBOX) + GAMEPAD_BUTTON_MIDDLE_RIGHT, // Gamepad center buttons, right one (i.e. PS3: Start) + GAMEPAD_BUTTON_LEFT_THUMB, // Gamepad joystick pressed button left + GAMEPAD_BUTTON_RIGHT_THUMB // Gamepad joystick pressed button right +} GamepadButton; + +// Gamepad axis +typedef enum { + GAMEPAD_AXIS_LEFT_X = 0, // Gamepad left stick X axis + GAMEPAD_AXIS_LEFT_Y = 1, // Gamepad left stick Y axis + GAMEPAD_AXIS_RIGHT_X = 2, // Gamepad right stick X axis + GAMEPAD_AXIS_RIGHT_Y = 3, // Gamepad right stick Y axis + GAMEPAD_AXIS_LEFT_TRIGGER = 4, // Gamepad back trigger left, pressure level: [1..-1] + GAMEPAD_AXIS_RIGHT_TRIGGER = 5 // Gamepad back trigger right, pressure level: [1..-1] +} GamepadAxis; + +// Material map index +typedef enum { + MATERIAL_MAP_ALBEDO = 0, // Albedo material (same as: MATERIAL_MAP_DIFFUSE) + MATERIAL_MAP_METALNESS, // Metalness material (same as: MATERIAL_MAP_SPECULAR) + MATERIAL_MAP_NORMAL, // Normal material + MATERIAL_MAP_ROUGHNESS, // Roughness material + MATERIAL_MAP_OCCLUSION, // Ambient occlusion material + MATERIAL_MAP_EMISSION, // Emission material + MATERIAL_MAP_HEIGHT, // Heightmap material + MATERIAL_MAP_CUBEMAP, // Cubemap material (NOTE: Uses GL_TEXTURE_CUBE_MAP) + MATERIAL_MAP_IRRADIANCE, // Irradiance material (NOTE: Uses GL_TEXTURE_CUBE_MAP) + MATERIAL_MAP_PREFILTER, // Prefilter material (NOTE: Uses GL_TEXTURE_CUBE_MAP) + MATERIAL_MAP_BRDF // Brdf material +} MaterialMapIndex; + +#define MATERIAL_MAP_DIFFUSE MATERIAL_MAP_ALBEDO +#define MATERIAL_MAP_SPECULAR MATERIAL_MAP_METALNESS + +// Shader location index +typedef enum { + SHADER_LOC_VERTEX_POSITION = 0, // Shader location: vertex attribute: position + SHADER_LOC_VERTEX_TEXCOORD01, // Shader location: vertex attribute: texcoord01 + SHADER_LOC_VERTEX_TEXCOORD02, // Shader location: vertex attribute: texcoord02 + SHADER_LOC_VERTEX_NORMAL, // Shader location: vertex attribute: normal + SHADER_LOC_VERTEX_TANGENT, // Shader location: vertex attribute: tangent + SHADER_LOC_VERTEX_COLOR, // Shader location: vertex attribute: color + SHADER_LOC_MATRIX_MVP, // Shader location: matrix uniform: model-view-projection + SHADER_LOC_MATRIX_VIEW, // Shader location: matrix uniform: view (camera transform) + SHADER_LOC_MATRIX_PROJECTION, // Shader location: matrix uniform: projection + SHADER_LOC_MATRIX_MODEL, // Shader location: matrix uniform: model (transform) + SHADER_LOC_MATRIX_NORMAL, // Shader location: matrix uniform: normal + SHADER_LOC_VECTOR_VIEW, // Shader location: vector uniform: view + SHADER_LOC_COLOR_DIFFUSE, // Shader location: vector uniform: diffuse color + SHADER_LOC_COLOR_SPECULAR, // Shader location: vector uniform: specular color + SHADER_LOC_COLOR_AMBIENT, // Shader location: vector uniform: ambient color + SHADER_LOC_MAP_ALBEDO, // Shader location: sampler2d texture: albedo (same as: SHADER_LOC_MAP_DIFFUSE) + SHADER_LOC_MAP_METALNESS, // Shader location: sampler2d texture: metalness (same as: SHADER_LOC_MAP_SPECULAR) + SHADER_LOC_MAP_NORMAL, // Shader location: sampler2d texture: normal + SHADER_LOC_MAP_ROUGHNESS, // Shader location: sampler2d texture: roughness + SHADER_LOC_MAP_OCCLUSION, // Shader location: sampler2d texture: occlusion + SHADER_LOC_MAP_EMISSION, // Shader location: sampler2d texture: emission + SHADER_LOC_MAP_HEIGHT, // Shader location: sampler2d texture: height + SHADER_LOC_MAP_CUBEMAP, // Shader location: samplerCube texture: cubemap + SHADER_LOC_MAP_IRRADIANCE, // Shader location: samplerCube texture: irradiance + SHADER_LOC_MAP_PREFILTER, // Shader location: samplerCube texture: prefilter + SHADER_LOC_MAP_BRDF, // Shader location: sampler2d texture: brdf + SHADER_LOC_VERTEX_BONEIDS, // Shader location: vertex attribute: boneIds + SHADER_LOC_VERTEX_BONEWEIGHTS, // Shader location: vertex attribute: boneWeights + SHADER_LOC_BONE_MATRICES // Shader location: array of matrices uniform: boneMatrices +} ShaderLocationIndex; + +#define SHADER_LOC_MAP_DIFFUSE SHADER_LOC_MAP_ALBEDO +#define SHADER_LOC_MAP_SPECULAR SHADER_LOC_MAP_METALNESS + +// Shader uniform data type +typedef enum { + SHADER_UNIFORM_FLOAT = 0, // Shader uniform type: float + SHADER_UNIFORM_VEC2, // Shader uniform type: vec2 (2 float) + SHADER_UNIFORM_VEC3, // Shader uniform type: vec3 (3 float) + SHADER_UNIFORM_VEC4, // Shader uniform type: vec4 (4 float) + SHADER_UNIFORM_INT, // Shader uniform type: int + SHADER_UNIFORM_IVEC2, // Shader uniform type: ivec2 (2 int) + SHADER_UNIFORM_IVEC3, // Shader uniform type: ivec3 (3 int) + SHADER_UNIFORM_IVEC4, // Shader uniform type: ivec4 (4 int) + SHADER_UNIFORM_SAMPLER2D // Shader uniform type: sampler2d +} ShaderUniformDataType; + +// Shader attribute data types +typedef enum { + SHADER_ATTRIB_FLOAT = 0, // Shader attribute type: float + SHADER_ATTRIB_VEC2, // Shader attribute type: vec2 (2 float) + SHADER_ATTRIB_VEC3, // Shader attribute type: vec3 (3 float) + SHADER_ATTRIB_VEC4 // Shader attribute type: vec4 (4 float) +} ShaderAttributeDataType; + +// Pixel formats +// NOTE: Support depends on OpenGL version and platform +typedef enum { + PIXELFORMAT_UNCOMPRESSED_GRAYSCALE = 1, // 8 bit per pixel (no alpha) + PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA, // 8*2 bpp (2 channels) + PIXELFORMAT_UNCOMPRESSED_R5G6B5, // 16 bpp + PIXELFORMAT_UNCOMPRESSED_R8G8B8, // 24 bpp + PIXELFORMAT_UNCOMPRESSED_R5G5B5A1, // 16 bpp (1 bit alpha) + PIXELFORMAT_UNCOMPRESSED_R4G4B4A4, // 16 bpp (4 bit alpha) + PIXELFORMAT_UNCOMPRESSED_R8G8B8A8, // 32 bpp + PIXELFORMAT_UNCOMPRESSED_R32, // 32 bpp (1 channel - float) + PIXELFORMAT_UNCOMPRESSED_R32G32B32, // 32*3 bpp (3 channels - float) + PIXELFORMAT_UNCOMPRESSED_R32G32B32A32, // 32*4 bpp (4 channels - float) + PIXELFORMAT_UNCOMPRESSED_R16, // 16 bpp (1 channel - half float) + PIXELFORMAT_UNCOMPRESSED_R16G16B16, // 16*3 bpp (3 channels - half float) + PIXELFORMAT_UNCOMPRESSED_R16G16B16A16, // 16*4 bpp (4 channels - half float) + PIXELFORMAT_COMPRESSED_DXT1_RGB, // 4 bpp (no alpha) + PIXELFORMAT_COMPRESSED_DXT1_RGBA, // 4 bpp (1 bit alpha) + PIXELFORMAT_COMPRESSED_DXT3_RGBA, // 8 bpp + PIXELFORMAT_COMPRESSED_DXT5_RGBA, // 8 bpp + PIXELFORMAT_COMPRESSED_ETC1_RGB, // 4 bpp + PIXELFORMAT_COMPRESSED_ETC2_RGB, // 4 bpp + PIXELFORMAT_COMPRESSED_ETC2_EAC_RGBA, // 8 bpp + PIXELFORMAT_COMPRESSED_PVRT_RGB, // 4 bpp + PIXELFORMAT_COMPRESSED_PVRT_RGBA, // 4 bpp + PIXELFORMAT_COMPRESSED_ASTC_4x4_RGBA, // 8 bpp + PIXELFORMAT_COMPRESSED_ASTC_8x8_RGBA // 2 bpp +} PixelFormat; + +// Texture parameters: filter mode +// NOTE 1: Filtering considers mipmaps if available in the texture +// NOTE 2: Filter is accordingly set for minification and magnification +typedef enum { + TEXTURE_FILTER_POINT = 0, // No filter, just pixel approximation + TEXTURE_FILTER_BILINEAR, // Linear filtering + TEXTURE_FILTER_TRILINEAR, // Trilinear filtering (linear with mipmaps) + TEXTURE_FILTER_ANISOTROPIC_4X, // Anisotropic filtering 4x + TEXTURE_FILTER_ANISOTROPIC_8X, // Anisotropic filtering 8x + TEXTURE_FILTER_ANISOTROPIC_16X, // Anisotropic filtering 16x +} TextureFilter; + +// Texture parameters: wrap mode +typedef enum { + TEXTURE_WRAP_REPEAT = 0, // Repeats texture in tiled mode + TEXTURE_WRAP_CLAMP, // Clamps texture to edge pixel in tiled mode + TEXTURE_WRAP_MIRROR_REPEAT, // Mirrors and repeats the texture in tiled mode + TEXTURE_WRAP_MIRROR_CLAMP // Mirrors and clamps to border the texture in tiled mode +} TextureWrap; + +// Cubemap layouts +typedef enum { + CUBEMAP_LAYOUT_AUTO_DETECT = 0, // Automatically detect layout type + CUBEMAP_LAYOUT_LINE_VERTICAL, // Layout is defined by a vertical line with faces + CUBEMAP_LAYOUT_LINE_HORIZONTAL, // Layout is defined by a horizontal line with faces + CUBEMAP_LAYOUT_CROSS_THREE_BY_FOUR, // Layout is defined by a 3x4 cross with cubemap faces + CUBEMAP_LAYOUT_CROSS_FOUR_BY_THREE // Layout is defined by a 4x3 cross with cubemap faces +} CubemapLayout; + +// Font type, defines generation method +typedef enum { + FONT_DEFAULT = 0, // Default font generation, anti-aliased + FONT_BITMAP, // Bitmap font generation, no anti-aliasing + FONT_SDF // SDF font generation, requires external shader +} FontType; + +// Color blending modes (pre-defined) +typedef enum { + BLEND_ALPHA = 0, // Blend textures considering alpha (default) + BLEND_ADDITIVE, // Blend textures adding colors + BLEND_MULTIPLIED, // Blend textures multiplying colors + BLEND_ADD_COLORS, // Blend textures adding colors (alternative) + BLEND_SUBTRACT_COLORS, // Blend textures subtracting colors (alternative) + BLEND_ALPHA_PREMULTIPLY, // Blend premultiplied textures considering alpha + BLEND_CUSTOM, // Blend textures using custom src/dst factors (use rlSetBlendFactors()) + BLEND_CUSTOM_SEPARATE // Blend textures using custom rgb/alpha separate src/dst factors (use rlSetBlendFactorsSeparate()) +} BlendMode; + +// Gesture +// NOTE: Provided as bit-wise flags to enable only desired gestures +typedef enum { + GESTURE_NONE = 0, // No gesture + GESTURE_TAP = 1, // Tap gesture + GESTURE_DOUBLETAP = 2, // Double tap gesture + GESTURE_HOLD = 4, // Hold gesture + GESTURE_DRAG = 8, // Drag gesture + GESTURE_SWIPE_RIGHT = 16, // Swipe right gesture + GESTURE_SWIPE_LEFT = 32, // Swipe left gesture + GESTURE_SWIPE_UP = 64, // Swipe up gesture + GESTURE_SWIPE_DOWN = 128, // Swipe down gesture + GESTURE_PINCH_IN = 256, // Pinch in gesture + GESTURE_PINCH_OUT = 512 // Pinch out gesture +} Gesture; + +// Camera system modes +typedef enum { + CAMERA_CUSTOM = 0, // Camera custom, controlled by user (UpdateCamera() does nothing) + CAMERA_FREE, // Camera free mode + CAMERA_ORBITAL, // Camera orbital, around target, zoom supported + CAMERA_FIRST_PERSON, // Camera first person + CAMERA_THIRD_PERSON // Camera third person +} CameraMode; + +// Camera projection +typedef enum { + CAMERA_PERSPECTIVE = 0, // Perspective projection + CAMERA_ORTHOGRAPHIC // Orthographic projection +} CameraProjection; + +// N-patch layout +typedef enum { + NPATCH_NINE_PATCH = 0, // Npatch layout: 3x3 tiles + NPATCH_THREE_PATCH_VERTICAL, // Npatch layout: 1x3 tiles + NPATCH_THREE_PATCH_HORIZONTAL // Npatch layout: 3x1 tiles +} NPatchLayout; + +// Callbacks to hook some internal functions +// WARNING: These callbacks are intended for advanced users +typedef void (*TraceLogCallback)(int logLevel, const char *text, va_list args); // Logging: Redirect trace log messages +typedef unsigned char *(*LoadFileDataCallback)(const char *fileName, int *dataSize); // FileIO: Load binary data +typedef bool (*SaveFileDataCallback)(const char *fileName, void *data, int dataSize); // FileIO: Save binary data +typedef char *(*LoadFileTextCallback)(const char *fileName); // FileIO: Load text data +typedef bool (*SaveFileTextCallback)(const char *fileName, char *text); // FileIO: Save text data + +//------------------------------------------------------------------------------------ +// Global Variables Definition +//------------------------------------------------------------------------------------ +// It's lonely here... + +//------------------------------------------------------------------------------------ +// Window and Graphics Device Functions (Module: core) +//------------------------------------------------------------------------------------ + +#if defined(__cplusplus) +extern "C" { // Prevents name mangling of functions +#endif + +// Window-related functions +RLAPI void InitWindow(int width, int height, const char *title); // Initialize window and OpenGL context +RLAPI void CloseWindow(void); // Close window and unload OpenGL context +RLAPI bool WindowShouldClose(void); // Check if application should close (KEY_ESCAPE pressed or windows close icon clicked) +RLAPI bool IsWindowReady(void); // Check if window has been initialized successfully +RLAPI bool IsWindowFullscreen(void); // Check if window is currently fullscreen +RLAPI bool IsWindowHidden(void); // Check if window is currently hidden +RLAPI bool IsWindowMinimized(void); // Check if window is currently minimized +RLAPI bool IsWindowMaximized(void); // Check if window is currently maximized +RLAPI bool IsWindowFocused(void); // Check if window is currently focused +RLAPI bool IsWindowResized(void); // Check if window has been resized last frame +RLAPI bool IsWindowState(unsigned int flag); // Check if one specific window flag is enabled +RLAPI void SetWindowState(unsigned int flags); // Set window configuration state using flags +RLAPI void ClearWindowState(unsigned int flags); // Clear window configuration state flags +RLAPI void ToggleFullscreen(void); // Toggle window state: fullscreen/windowed, resizes monitor to match window resolution +RLAPI void ToggleBorderlessWindowed(void); // Toggle window state: borderless windowed, resizes window to match monitor resolution +RLAPI void MaximizeWindow(void); // Set window state: maximized, if resizable +RLAPI void MinimizeWindow(void); // Set window state: minimized, if resizable +RLAPI void RestoreWindow(void); // Set window state: not minimized/maximized +RLAPI void SetWindowIcon(Image image); // Set icon for window (single image, RGBA 32bit) +RLAPI void SetWindowIcons(Image *images, int count); // Set icon for window (multiple images, RGBA 32bit) +RLAPI void SetWindowTitle(const char *title); // Set title for window +RLAPI void SetWindowPosition(int x, int y); // Set window position on screen +RLAPI void SetWindowMonitor(int monitor); // Set monitor for the current window +RLAPI void SetWindowMinSize(int width, int height); // Set window minimum dimensions (for FLAG_WINDOW_RESIZABLE) +RLAPI void SetWindowMaxSize(int width, int height); // Set window maximum dimensions (for FLAG_WINDOW_RESIZABLE) +RLAPI void SetWindowSize(int width, int height); // Set window dimensions +RLAPI void SetWindowOpacity(float opacity); // Set window opacity [0.0f..1.0f] +RLAPI void SetWindowFocused(void); // Set window focused +RLAPI void *GetWindowHandle(void); // Get native window handle +RLAPI int GetScreenWidth(void); // Get current screen width +RLAPI int GetScreenHeight(void); // Get current screen height +RLAPI int GetRenderWidth(void); // Get current render width (it considers HiDPI) +RLAPI int GetRenderHeight(void); // Get current render height (it considers HiDPI) +RLAPI int GetMonitorCount(void); // Get number of connected monitors +RLAPI int GetCurrentMonitor(void); // Get current monitor where window is placed +RLAPI Vector2 GetMonitorPosition(int monitor); // Get specified monitor position +RLAPI int GetMonitorWidth(int monitor); // Get specified monitor width (current video mode used by monitor) +RLAPI int GetMonitorHeight(int monitor); // Get specified monitor height (current video mode used by monitor) +RLAPI int GetMonitorPhysicalWidth(int monitor); // Get specified monitor physical width in millimetres +RLAPI int GetMonitorPhysicalHeight(int monitor); // Get specified monitor physical height in millimetres +RLAPI int GetMonitorRefreshRate(int monitor); // Get specified monitor refresh rate +RLAPI Vector2 GetWindowPosition(void); // Get window position XY on monitor +RLAPI Vector2 GetWindowScaleDPI(void); // Get window scale DPI factor +RLAPI const char *GetMonitorName(int monitor); // Get the human-readable, UTF-8 encoded name of the specified monitor +RLAPI void SetClipboardText(const char *text); // Set clipboard text content +RLAPI const char *GetClipboardText(void); // Get clipboard text content +RLAPI Image GetClipboardImage(void); // Get clipboard image content +RLAPI void EnableEventWaiting(void); // Enable waiting for events on EndDrawing(), no automatic event polling +RLAPI void DisableEventWaiting(void); // Disable waiting for events on EndDrawing(), automatic events polling + +// Cursor-related functions +RLAPI void ShowCursor(void); // Shows cursor +RLAPI void HideCursor(void); // Hides cursor +RLAPI bool IsCursorHidden(void); // Check if cursor is not visible +RLAPI void EnableCursor(void); // Enables cursor (unlock cursor) +RLAPI void DisableCursor(void); // Disables cursor (lock cursor) +RLAPI bool IsCursorOnScreen(void); // Check if cursor is on the screen + +// Drawing-related functions +RLAPI void ClearBackground(Color color); // Set background color (framebuffer clear color) +RLAPI void BeginDrawing(void); // Setup canvas (framebuffer) to start drawing +RLAPI void EndDrawing(void); // End canvas drawing and swap buffers (double buffering) +RLAPI void BeginMode2D(Camera2D camera); // Begin 2D mode with custom camera (2D) +RLAPI void EndMode2D(void); // Ends 2D mode with custom camera +RLAPI void BeginMode3D(Camera3D camera); // Begin 3D mode with custom camera (3D) +RLAPI void EndMode3D(void); // Ends 3D mode and returns to default 2D orthographic mode +RLAPI void BeginTextureMode(RenderTexture2D target); // Begin drawing to render texture +RLAPI void EndTextureMode(void); // Ends drawing to render texture +RLAPI void BeginShaderMode(Shader shader); // Begin custom shader drawing +RLAPI void EndShaderMode(void); // End custom shader drawing (use default shader) +RLAPI void BeginBlendMode(int mode); // Begin blending mode (alpha, additive, multiplied, subtract, custom) +RLAPI void EndBlendMode(void); // End blending mode (reset to default: alpha blending) +RLAPI void BeginScissorMode(int x, int y, int width, int height); // Begin scissor mode (define screen area for following drawing) +RLAPI void EndScissorMode(void); // End scissor mode +RLAPI void BeginVrStereoMode(VrStereoConfig config); // Begin stereo rendering (requires VR simulator) +RLAPI void EndVrStereoMode(void); // End stereo rendering (requires VR simulator) + +// VR stereo config functions for VR simulator +RLAPI VrStereoConfig LoadVrStereoConfig(VrDeviceInfo device); // Load VR stereo config for VR simulator device parameters +RLAPI void UnloadVrStereoConfig(VrStereoConfig config); // Unload VR stereo config + +// Shader management functions +// NOTE: Shader functionality is not available on OpenGL 1.1 +RLAPI Shader LoadShader(const char *vsFileName, const char *fsFileName); // Load shader from files and bind default locations +RLAPI Shader LoadShaderFromMemory(const char *vsCode, const char *fsCode); // Load shader from code strings and bind default locations +RLAPI bool IsShaderValid(Shader shader); // Check if a shader is valid (loaded on GPU) +RLAPI int GetShaderLocation(Shader shader, const char *uniformName); // Get shader uniform location +RLAPI int GetShaderLocationAttrib(Shader shader, const char *attribName); // Get shader attribute location +RLAPI void SetShaderValue(Shader shader, int locIndex, const void *value, int uniformType); // Set shader uniform value +RLAPI void SetShaderValueV(Shader shader, int locIndex, const void *value, int uniformType, int count); // Set shader uniform value vector +RLAPI void SetShaderValueMatrix(Shader shader, int locIndex, Matrix mat); // Set shader uniform value (matrix 4x4) +RLAPI void SetShaderValueTexture(Shader shader, int locIndex, Texture2D texture); // Set shader uniform value for texture (sampler2d) +RLAPI void UnloadShader(Shader shader); // Unload shader from GPU memory (VRAM) + +// Screen-space-related functions +#define GetMouseRay GetScreenToWorldRay // Compatibility hack for previous raylib versions +RLAPI Ray GetScreenToWorldRay(Vector2 position, Camera camera); // Get a ray trace from screen position (i.e mouse) +RLAPI Ray GetScreenToWorldRayEx(Vector2 position, Camera camera, int width, int height); // Get a ray trace from screen position (i.e mouse) in a viewport +RLAPI Vector2 GetWorldToScreen(Vector3 position, Camera camera); // Get the screen space position for a 3d world space position +RLAPI Vector2 GetWorldToScreenEx(Vector3 position, Camera camera, int width, int height); // Get size position for a 3d world space position +RLAPI Vector2 GetWorldToScreen2D(Vector2 position, Camera2D camera); // Get the screen space position for a 2d camera world space position +RLAPI Vector2 GetScreenToWorld2D(Vector2 position, Camera2D camera); // Get the world space position for a 2d camera screen space position +RLAPI Matrix GetCameraMatrix(Camera camera); // Get camera transform matrix (view matrix) +RLAPI Matrix GetCameraMatrix2D(Camera2D camera); // Get camera 2d transform matrix + +// Timing-related functions +RLAPI void SetTargetFPS(int fps); // Set target FPS (maximum) +RLAPI float GetFrameTime(void); // Get time in seconds for last frame drawn (delta time) +RLAPI double GetTime(void); // Get elapsed time in seconds since InitWindow() +RLAPI int GetFPS(void); // Get current FPS + +// Custom frame control functions +// NOTE: Those functions are intended for advanced users that want full control over the frame processing +// By default EndDrawing() does this job: draws everything + SwapScreenBuffer() + manage frame timing + PollInputEvents() +// To avoid that behaviour and control frame processes manually, enable in config.h: SUPPORT_CUSTOM_FRAME_CONTROL +RLAPI void SwapScreenBuffer(void); // Swap back buffer with front buffer (screen drawing) +RLAPI void PollInputEvents(void); // Register all input events +RLAPI void WaitTime(double seconds); // Wait for some time (halt program execution) + +// Random values generation functions +RLAPI void SetRandomSeed(unsigned int seed); // Set the seed for the random number generator +RLAPI int GetRandomValue(int min, int max); // Get a random value between min and max (both included) +RLAPI int *LoadRandomSequence(unsigned int count, int min, int max); // Load random values sequence, no values repeated +RLAPI void UnloadRandomSequence(int *sequence); // Unload random values sequence + +// Misc. functions +RLAPI void TakeScreenshot(const char *fileName); // Takes a screenshot of current screen (filename extension defines format) +RLAPI void SetConfigFlags(unsigned int flags); // Setup init configuration flags (view FLAGS) +RLAPI void OpenURL(const char *url); // Open URL with default system browser (if available) + +// NOTE: Following functions implemented in module [utils] +//------------------------------------------------------------------ +RLAPI void TraceLog(int logLevel, const char *text, ...); // Show trace log messages (LOG_DEBUG, LOG_INFO, LOG_WARNING, LOG_ERROR...) +RLAPI void SetTraceLogLevel(int logLevel); // Set the current threshold (minimum) log level +RLAPI void *MemAlloc(unsigned int size); // Internal memory allocator +RLAPI void *MemRealloc(void *ptr, unsigned int size); // Internal memory reallocator +RLAPI void MemFree(void *ptr); // Internal memory free + +// Set custom callbacks +// WARNING: Callbacks setup is intended for advanced users +RLAPI void SetTraceLogCallback(TraceLogCallback callback); // Set custom trace log +RLAPI void SetLoadFileDataCallback(LoadFileDataCallback callback); // Set custom file binary data loader +RLAPI void SetSaveFileDataCallback(SaveFileDataCallback callback); // Set custom file binary data saver +RLAPI void SetLoadFileTextCallback(LoadFileTextCallback callback); // Set custom file text data loader +RLAPI void SetSaveFileTextCallback(SaveFileTextCallback callback); // Set custom file text data saver + +// Files management functions +RLAPI unsigned char *LoadFileData(const char *fileName, int *dataSize); // Load file data as byte array (read) +RLAPI void UnloadFileData(unsigned char *data); // Unload file data allocated by LoadFileData() +RLAPI bool SaveFileData(const char *fileName, void *data, int dataSize); // Save data to file from byte array (write), returns true on success +RLAPI bool ExportDataAsCode(const unsigned char *data, int dataSize, const char *fileName); // Export data to code (.h), returns true on success +RLAPI char *LoadFileText(const char *fileName); // Load text data from file (read), returns a '\0' terminated string +RLAPI void UnloadFileText(char *text); // Unload file text data allocated by LoadFileText() +RLAPI bool SaveFileText(const char *fileName, char *text); // Save text data to file (write), string must be '\0' terminated, returns true on success +//------------------------------------------------------------------ + +// File system functions +RLAPI bool FileExists(const char *fileName); // Check if file exists +RLAPI bool DirectoryExists(const char *dirPath); // Check if a directory path exists +RLAPI bool IsFileExtension(const char *fileName, const char *ext); // Check file extension (including point: .png, .wav) +RLAPI int GetFileLength(const char *fileName); // Get file length in bytes (NOTE: GetFileSize() conflicts with windows.h) +RLAPI const char *GetFileExtension(const char *fileName); // Get pointer to extension for a filename string (includes dot: '.png') +RLAPI const char *GetFileName(const char *filePath); // Get pointer to filename for a path string +RLAPI const char *GetFileNameWithoutExt(const char *filePath); // Get filename string without extension (uses static string) +RLAPI const char *GetDirectoryPath(const char *filePath); // Get full path for a given fileName with path (uses static string) +RLAPI const char *GetPrevDirectoryPath(const char *dirPath); // Get previous directory path for a given path (uses static string) +RLAPI const char *GetWorkingDirectory(void); // Get current working directory (uses static string) +RLAPI const char *GetApplicationDirectory(void); // Get the directory of the running application (uses static string) +RLAPI int MakeDirectory(const char *dirPath); // Create directories (including full path requested), returns 0 on success +RLAPI bool ChangeDirectory(const char *dir); // Change working directory, return true on success +RLAPI bool IsPathFile(const char *path); // Check if a given path is a file or a directory +RLAPI bool IsFileNameValid(const char *fileName); // Check if fileName is valid for the platform/OS +RLAPI FilePathList LoadDirectoryFiles(const char *dirPath); // Load directory filepaths +RLAPI FilePathList LoadDirectoryFilesEx(const char *basePath, const char *filter, bool scanSubdirs); // Load directory filepaths with extension filtering and recursive directory scan. Use 'DIR' in the filter string to include directories in the result +RLAPI void UnloadDirectoryFiles(FilePathList files); // Unload filepaths +RLAPI bool IsFileDropped(void); // Check if a file has been dropped into window +RLAPI FilePathList LoadDroppedFiles(void); // Load dropped filepaths +RLAPI void UnloadDroppedFiles(FilePathList files); // Unload dropped filepaths +RLAPI long GetFileModTime(const char *fileName); // Get file modification time (last write time) + +// Compression/Encoding functionality +RLAPI unsigned char *CompressData(const unsigned char *data, int dataSize, int *compDataSize); // Compress data (DEFLATE algorithm), memory must be MemFree() +RLAPI unsigned char *DecompressData(const unsigned char *compData, int compDataSize, int *dataSize); // Decompress data (DEFLATE algorithm), memory must be MemFree() +RLAPI char *EncodeDataBase64(const unsigned char *data, int dataSize, int *outputSize); // Encode data to Base64 string, memory must be MemFree() +RLAPI unsigned char *DecodeDataBase64(const unsigned char *data, int *outputSize); // Decode Base64 string data, memory must be MemFree() +RLAPI unsigned int ComputeCRC32(unsigned char *data, int dataSize); // Compute CRC32 hash code +RLAPI unsigned int *ComputeMD5(unsigned char *data, int dataSize); // Compute MD5 hash code, returns static int[4] (16 bytes) +RLAPI unsigned int *ComputeSHA1(unsigned char *data, int dataSize); // Compute SHA1 hash code, returns static int[5] (20 bytes) + + +// Automation events functionality +RLAPI AutomationEventList LoadAutomationEventList(const char *fileName); // Load automation events list from file, NULL for empty list, capacity = MAX_AUTOMATION_EVENTS +RLAPI void UnloadAutomationEventList(AutomationEventList list); // Unload automation events list from file +RLAPI bool ExportAutomationEventList(AutomationEventList list, const char *fileName); // Export automation events list as text file +RLAPI void SetAutomationEventList(AutomationEventList *list); // Set automation event list to record to +RLAPI void SetAutomationEventBaseFrame(int frame); // Set automation event internal base frame to start recording +RLAPI void StartAutomationEventRecording(void); // Start recording automation events (AutomationEventList must be set) +RLAPI void StopAutomationEventRecording(void); // Stop recording automation events +RLAPI void PlayAutomationEvent(AutomationEvent event); // Play a recorded automation event + +//------------------------------------------------------------------------------------ +// Input Handling Functions (Module: core) +//------------------------------------------------------------------------------------ + +// Input-related functions: keyboard +RLAPI bool IsKeyPressed(int key); // Check if a key has been pressed once +RLAPI bool IsKeyPressedRepeat(int key); // Check if a key has been pressed again +RLAPI bool IsKeyDown(int key); // Check if a key is being pressed +RLAPI bool IsKeyReleased(int key); // Check if a key has been released once +RLAPI bool IsKeyUp(int key); // Check if a key is NOT being pressed +RLAPI int GetKeyPressed(void); // Get key pressed (keycode), call it multiple times for keys queued, returns 0 when the queue is empty +RLAPI int GetCharPressed(void); // Get char pressed (unicode), call it multiple times for chars queued, returns 0 when the queue is empty +RLAPI void SetExitKey(int key); // Set a custom key to exit program (default is ESC) + +// Input-related functions: gamepads +RLAPI bool IsGamepadAvailable(int gamepad); // Check if a gamepad is available +RLAPI const char *GetGamepadName(int gamepad); // Get gamepad internal name id +RLAPI bool IsGamepadButtonPressed(int gamepad, int button); // Check if a gamepad button has been pressed once +RLAPI bool IsGamepadButtonDown(int gamepad, int button); // Check if a gamepad button is being pressed +RLAPI bool IsGamepadButtonReleased(int gamepad, int button); // Check if a gamepad button has been released once +RLAPI bool IsGamepadButtonUp(int gamepad, int button); // Check if a gamepad button is NOT being pressed +RLAPI int GetGamepadButtonPressed(void); // Get the last gamepad button pressed +RLAPI int GetGamepadAxisCount(int gamepad); // Get gamepad axis count for a gamepad +RLAPI float GetGamepadAxisMovement(int gamepad, int axis); // Get axis movement value for a gamepad axis +RLAPI int SetGamepadMappings(const char *mappings); // Set internal gamepad mappings (SDL_GameControllerDB) +RLAPI void SetGamepadVibration(int gamepad, float leftMotor, float rightMotor, float duration); // Set gamepad vibration for both motors (duration in seconds) + +// Input-related functions: mouse +RLAPI bool IsMouseButtonPressed(int button); // Check if a mouse button has been pressed once +RLAPI bool IsMouseButtonDown(int button); // Check if a mouse button is being pressed +RLAPI bool IsMouseButtonReleased(int button); // Check if a mouse button has been released once +RLAPI bool IsMouseButtonUp(int button); // Check if a mouse button is NOT being pressed +RLAPI int GetMouseX(void); // Get mouse position X +RLAPI int GetMouseY(void); // Get mouse position Y +RLAPI Vector2 GetMousePosition(void); // Get mouse position XY +RLAPI Vector2 GetMouseDelta(void); // Get mouse delta between frames +RLAPI void SetMousePosition(int x, int y); // Set mouse position XY +RLAPI void SetMouseOffset(int offsetX, int offsetY); // Set mouse offset +RLAPI void SetMouseScale(float scaleX, float scaleY); // Set mouse scaling +RLAPI float GetMouseWheelMove(void); // Get mouse wheel movement for X or Y, whichever is larger +RLAPI Vector2 GetMouseWheelMoveV(void); // Get mouse wheel movement for both X and Y +RLAPI void SetMouseCursor(int cursor); // Set mouse cursor + +// Input-related functions: touch +RLAPI int GetTouchX(void); // Get touch position X for touch point 0 (relative to screen size) +RLAPI int GetTouchY(void); // Get touch position Y for touch point 0 (relative to screen size) +RLAPI Vector2 GetTouchPosition(int index); // Get touch position XY for a touch point index (relative to screen size) +RLAPI int GetTouchPointId(int index); // Get touch point identifier for given index +RLAPI int GetTouchPointCount(void); // Get number of touch points + +//------------------------------------------------------------------------------------ +// Gestures and Touch Handling Functions (Module: rgestures) +//------------------------------------------------------------------------------------ +RLAPI void SetGesturesEnabled(unsigned int flags); // Enable a set of gestures using flags +RLAPI bool IsGestureDetected(unsigned int gesture); // Check if a gesture have been detected +RLAPI int GetGestureDetected(void); // Get latest detected gesture +RLAPI float GetGestureHoldDuration(void); // Get gesture hold time in seconds +RLAPI Vector2 GetGestureDragVector(void); // Get gesture drag vector +RLAPI float GetGestureDragAngle(void); // Get gesture drag angle +RLAPI Vector2 GetGesturePinchVector(void); // Get gesture pinch delta +RLAPI float GetGesturePinchAngle(void); // Get gesture pinch angle + +//------------------------------------------------------------------------------------ +// Camera System Functions (Module: rcamera) +//------------------------------------------------------------------------------------ +RLAPI void UpdateCamera(Camera *camera, int mode); // Update camera position for selected mode +RLAPI void UpdateCameraPro(Camera *camera, Vector3 movement, Vector3 rotation, float zoom); // Update camera movement/rotation + +//------------------------------------------------------------------------------------ +// Basic Shapes Drawing Functions (Module: shapes) +//------------------------------------------------------------------------------------ +// Set texture and rectangle to be used on shapes drawing +// NOTE: It can be useful when using basic shapes and one single font, +// defining a font char white rectangle would allow drawing everything in a single draw call +RLAPI void SetShapesTexture(Texture2D texture, Rectangle source); // Set texture and rectangle to be used on shapes drawing +RLAPI Texture2D GetShapesTexture(void); // Get texture that is used for shapes drawing +RLAPI Rectangle GetShapesTextureRectangle(void); // Get texture source rectangle that is used for shapes drawing + +// Basic shapes drawing functions +RLAPI void DrawPixel(int posX, int posY, Color color); // Draw a pixel using geometry [Can be slow, use with care] +RLAPI void DrawPixelV(Vector2 position, Color color); // Draw a pixel using geometry (Vector version) [Can be slow, use with care] +RLAPI void DrawLine(int startPosX, int startPosY, int endPosX, int endPosY, Color color); // Draw a line +RLAPI void DrawLineV(Vector2 startPos, Vector2 endPos, Color color); // Draw a line (using gl lines) +RLAPI void DrawLineEx(Vector2 startPos, Vector2 endPos, float thick, Color color); // Draw a line (using triangles/quads) +RLAPI void DrawLineStrip(const Vector2 *points, int pointCount, Color color); // Draw lines sequence (using gl lines) +RLAPI void DrawLineBezier(Vector2 startPos, Vector2 endPos, float thick, Color color); // Draw line segment cubic-bezier in-out interpolation +RLAPI void DrawCircle(int centerX, int centerY, float radius, Color color); // Draw a color-filled circle +RLAPI void DrawCircleSector(Vector2 center, float radius, float startAngle, float endAngle, int segments, Color color); // Draw a piece of a circle +RLAPI void DrawCircleSectorLines(Vector2 center, float radius, float startAngle, float endAngle, int segments, Color color); // Draw circle sector outline +RLAPI void DrawCircleGradient(int centerX, int centerY, float radius, Color inner, Color outer); // Draw a gradient-filled circle +RLAPI void DrawCircleV(Vector2 center, float radius, Color color); // Draw a color-filled circle (Vector version) +RLAPI void DrawCircleLines(int centerX, int centerY, float radius, Color color); // Draw circle outline +RLAPI void DrawCircleLinesV(Vector2 center, float radius, Color color); // Draw circle outline (Vector version) +RLAPI void DrawEllipse(int centerX, int centerY, float radiusH, float radiusV, Color color); // Draw ellipse +RLAPI void DrawEllipseLines(int centerX, int centerY, float radiusH, float radiusV, Color color); // Draw ellipse outline +RLAPI void DrawRing(Vector2 center, float innerRadius, float outerRadius, float startAngle, float endAngle, int segments, Color color); // Draw ring +RLAPI void DrawRingLines(Vector2 center, float innerRadius, float outerRadius, float startAngle, float endAngle, int segments, Color color); // Draw ring outline +RLAPI void DrawRectangle(int posX, int posY, int width, int height, Color color); // Draw a color-filled rectangle +RLAPI void DrawRectangleV(Vector2 position, Vector2 size, Color color); // Draw a color-filled rectangle (Vector version) +RLAPI void DrawRectangleRec(Rectangle rec, Color color); // Draw a color-filled rectangle +RLAPI void DrawRectanglePro(Rectangle rec, Vector2 origin, float rotation, Color color); // Draw a color-filled rectangle with pro parameters +RLAPI void DrawRectangleGradientV(int posX, int posY, int width, int height, Color top, Color bottom); // Draw a vertical-gradient-filled rectangle +RLAPI void DrawRectangleGradientH(int posX, int posY, int width, int height, Color left, Color right); // Draw a horizontal-gradient-filled rectangle +RLAPI void DrawRectangleGradientEx(Rectangle rec, Color topLeft, Color bottomLeft, Color topRight, Color bottomRight); // Draw a gradient-filled rectangle with custom vertex colors +RLAPI void DrawRectangleLines(int posX, int posY, int width, int height, Color color); // Draw rectangle outline +RLAPI void DrawRectangleLinesEx(Rectangle rec, float lineThick, Color color); // Draw rectangle outline with extended parameters +RLAPI void DrawRectangleRounded(Rectangle rec, float roundness, int segments, Color color); // Draw rectangle with rounded edges +RLAPI void DrawRectangleRoundedLines(Rectangle rec, float roundness, int segments, Color color); // Draw rectangle lines with rounded edges +RLAPI void DrawRectangleRoundedLinesEx(Rectangle rec, float roundness, int segments, float lineThick, Color color); // Draw rectangle with rounded edges outline +RLAPI void DrawTriangle(Vector2 v1, Vector2 v2, Vector2 v3, Color color); // Draw a color-filled triangle (vertex in counter-clockwise order!) +RLAPI void DrawTriangleLines(Vector2 v1, Vector2 v2, Vector2 v3, Color color); // Draw triangle outline (vertex in counter-clockwise order!) +RLAPI void DrawTriangleFan(const Vector2 *points, int pointCount, Color color); // Draw a triangle fan defined by points (first vertex is the center) +RLAPI void DrawTriangleStrip(const Vector2 *points, int pointCount, Color color); // Draw a triangle strip defined by points +RLAPI void DrawPoly(Vector2 center, int sides, float radius, float rotation, Color color); // Draw a regular polygon (Vector version) +RLAPI void DrawPolyLines(Vector2 center, int sides, float radius, float rotation, Color color); // Draw a polygon outline of n sides +RLAPI void DrawPolyLinesEx(Vector2 center, int sides, float radius, float rotation, float lineThick, Color color); // Draw a polygon outline of n sides with extended parameters + +// Splines drawing functions +RLAPI void DrawSplineLinear(const Vector2 *points, int pointCount, float thick, Color color); // Draw spline: Linear, minimum 2 points +RLAPI void DrawSplineBasis(const Vector2 *points, int pointCount, float thick, Color color); // Draw spline: B-Spline, minimum 4 points +RLAPI void DrawSplineCatmullRom(const Vector2 *points, int pointCount, float thick, Color color); // Draw spline: Catmull-Rom, minimum 4 points +RLAPI void DrawSplineBezierQuadratic(const Vector2 *points, int pointCount, float thick, Color color); // Draw spline: Quadratic Bezier, minimum 3 points (1 control point): [p1, c2, p3, c4...] +RLAPI void DrawSplineBezierCubic(const Vector2 *points, int pointCount, float thick, Color color); // Draw spline: Cubic Bezier, minimum 4 points (2 control points): [p1, c2, c3, p4, c5, c6...] +RLAPI void DrawSplineSegmentLinear(Vector2 p1, Vector2 p2, float thick, Color color); // Draw spline segment: Linear, 2 points +RLAPI void DrawSplineSegmentBasis(Vector2 p1, Vector2 p2, Vector2 p3, Vector2 p4, float thick, Color color); // Draw spline segment: B-Spline, 4 points +RLAPI void DrawSplineSegmentCatmullRom(Vector2 p1, Vector2 p2, Vector2 p3, Vector2 p4, float thick, Color color); // Draw spline segment: Catmull-Rom, 4 points +RLAPI void DrawSplineSegmentBezierQuadratic(Vector2 p1, Vector2 c2, Vector2 p3, float thick, Color color); // Draw spline segment: Quadratic Bezier, 2 points, 1 control point +RLAPI void DrawSplineSegmentBezierCubic(Vector2 p1, Vector2 c2, Vector2 c3, Vector2 p4, float thick, Color color); // Draw spline segment: Cubic Bezier, 2 points, 2 control points + +// Spline segment point evaluation functions, for a given t [0.0f .. 1.0f] +RLAPI Vector2 GetSplinePointLinear(Vector2 startPos, Vector2 endPos, float t); // Get (evaluate) spline point: Linear +RLAPI Vector2 GetSplinePointBasis(Vector2 p1, Vector2 p2, Vector2 p3, Vector2 p4, float t); // Get (evaluate) spline point: B-Spline +RLAPI Vector2 GetSplinePointCatmullRom(Vector2 p1, Vector2 p2, Vector2 p3, Vector2 p4, float t); // Get (evaluate) spline point: Catmull-Rom +RLAPI Vector2 GetSplinePointBezierQuad(Vector2 p1, Vector2 c2, Vector2 p3, float t); // Get (evaluate) spline point: Quadratic Bezier +RLAPI Vector2 GetSplinePointBezierCubic(Vector2 p1, Vector2 c2, Vector2 c3, Vector2 p4, float t); // Get (evaluate) spline point: Cubic Bezier + +// Basic shapes collision detection functions +RLAPI bool CheckCollisionRecs(Rectangle rec1, Rectangle rec2); // Check collision between two rectangles +RLAPI bool CheckCollisionCircles(Vector2 center1, float radius1, Vector2 center2, float radius2); // Check collision between two circles +RLAPI bool CheckCollisionCircleRec(Vector2 center, float radius, Rectangle rec); // Check collision between circle and rectangle +RLAPI bool CheckCollisionCircleLine(Vector2 center, float radius, Vector2 p1, Vector2 p2); // Check if circle collides with a line created betweeen two points [p1] and [p2] +RLAPI bool CheckCollisionPointRec(Vector2 point, Rectangle rec); // Check if point is inside rectangle +RLAPI bool CheckCollisionPointCircle(Vector2 point, Vector2 center, float radius); // Check if point is inside circle +RLAPI bool CheckCollisionPointTriangle(Vector2 point, Vector2 p1, Vector2 p2, Vector2 p3); // Check if point is inside a triangle +RLAPI bool CheckCollisionPointLine(Vector2 point, Vector2 p1, Vector2 p2, int threshold); // Check if point belongs to line created between two points [p1] and [p2] with defined margin in pixels [threshold] +RLAPI bool CheckCollisionPointPoly(Vector2 point, const Vector2 *points, int pointCount); // Check if point is within a polygon described by array of vertices +RLAPI bool CheckCollisionLines(Vector2 startPos1, Vector2 endPos1, Vector2 startPos2, Vector2 endPos2, Vector2 *collisionPoint); // Check the collision between two lines defined by two points each, returns collision point by reference +RLAPI Rectangle GetCollisionRec(Rectangle rec1, Rectangle rec2); // Get collision rectangle for two rectangles collision + +//------------------------------------------------------------------------------------ +// Texture Loading and Drawing Functions (Module: textures) +//------------------------------------------------------------------------------------ + +// Image loading functions +// NOTE: These functions do not require GPU access +RLAPI Image LoadImage(const char *fileName); // Load image from file into CPU memory (RAM) +RLAPI Image LoadImageRaw(const char *fileName, int width, int height, int format, int headerSize); // Load image from RAW file data +RLAPI Image LoadImageAnim(const char *fileName, int *frames); // Load image sequence from file (frames appended to image.data) +RLAPI Image LoadImageAnimFromMemory(const char *fileType, const unsigned char *fileData, int dataSize, int *frames); // Load image sequence from memory buffer +RLAPI Image LoadImageFromMemory(const char *fileType, const unsigned char *fileData, int dataSize); // Load image from memory buffer, fileType refers to extension: i.e. '.png' +RLAPI Image LoadImageFromTexture(Texture2D texture); // Load image from GPU texture data +RLAPI Image LoadImageFromScreen(void); // Load image from screen buffer and (screenshot) +RLAPI bool IsImageValid(Image image); // Check if an image is valid (data and parameters) +RLAPI void UnloadImage(Image image); // Unload image from CPU memory (RAM) +RLAPI bool ExportImage(Image image, const char *fileName); // Export image data to file, returns true on success +RLAPI unsigned char *ExportImageToMemory(Image image, const char *fileType, int *fileSize); // Export image to memory buffer +RLAPI bool ExportImageAsCode(Image image, const char *fileName); // Export image as code file defining an array of bytes, returns true on success + +// Image generation functions +RLAPI Image GenImageColor(int width, int height, Color color); // Generate image: plain color +RLAPI Image GenImageGradientLinear(int width, int height, int direction, Color start, Color end); // Generate image: linear gradient, direction in degrees [0..360], 0=Vertical gradient +RLAPI Image GenImageGradientRadial(int width, int height, float density, Color inner, Color outer); // Generate image: radial gradient +RLAPI Image GenImageGradientSquare(int width, int height, float density, Color inner, Color outer); // Generate image: square gradient +RLAPI Image GenImageChecked(int width, int height, int checksX, int checksY, Color col1, Color col2); // Generate image: checked +RLAPI Image GenImageWhiteNoise(int width, int height, float factor); // Generate image: white noise +RLAPI Image GenImagePerlinNoise(int width, int height, int offsetX, int offsetY, float scale); // Generate image: perlin noise +RLAPI Image GenImageCellular(int width, int height, int tileSize); // Generate image: cellular algorithm, bigger tileSize means bigger cells +RLAPI Image GenImageText(int width, int height, const char *text); // Generate image: grayscale image from text data + +// Image manipulation functions +RLAPI Image ImageCopy(Image image); // Create an image duplicate (useful for transformations) +RLAPI Image ImageFromImage(Image image, Rectangle rec); // Create an image from another image piece +RLAPI Image ImageFromChannel(Image image, int selectedChannel); // Create an image from a selected channel of another image (GRAYSCALE) +RLAPI Image ImageText(const char *text, int fontSize, Color color); // Create an image from text (default font) +RLAPI Image ImageTextEx(Font font, const char *text, float fontSize, float spacing, Color tint); // Create an image from text (custom sprite font) +RLAPI void ImageFormat(Image *image, int newFormat); // Convert image data to desired format +RLAPI void ImageToPOT(Image *image, Color fill); // Convert image to POT (power-of-two) +RLAPI void ImageCrop(Image *image, Rectangle crop); // Crop an image to a defined rectangle +RLAPI void ImageAlphaCrop(Image *image, float threshold); // Crop image depending on alpha value +RLAPI void ImageAlphaClear(Image *image, Color color, float threshold); // Clear alpha channel to desired color +RLAPI void ImageAlphaMask(Image *image, Image alphaMask); // Apply alpha mask to image +RLAPI void ImageAlphaPremultiply(Image *image); // Premultiply alpha channel +RLAPI void ImageBlurGaussian(Image *image, int blurSize); // Apply Gaussian blur using a box blur approximation +RLAPI void ImageKernelConvolution(Image *image, const float *kernel, int kernelSize); // Apply custom square convolution kernel to image +RLAPI void ImageResize(Image *image, int newWidth, int newHeight); // Resize image (Bicubic scaling algorithm) +RLAPI void ImageResizeNN(Image *image, int newWidth,int newHeight); // Resize image (Nearest-Neighbor scaling algorithm) +RLAPI void ImageResizeCanvas(Image *image, int newWidth, int newHeight, int offsetX, int offsetY, Color fill); // Resize canvas and fill with color +RLAPI void ImageMipmaps(Image *image); // Compute all mipmap levels for a provided image +RLAPI void ImageDither(Image *image, int rBpp, int gBpp, int bBpp, int aBpp); // Dither image data to 16bpp or lower (Floyd-Steinberg dithering) +RLAPI void ImageFlipVertical(Image *image); // Flip image vertically +RLAPI void ImageFlipHorizontal(Image *image); // Flip image horizontally +RLAPI void ImageRotate(Image *image, int degrees); // Rotate image by input angle in degrees (-359 to 359) +RLAPI void ImageRotateCW(Image *image); // Rotate image clockwise 90deg +RLAPI void ImageRotateCCW(Image *image); // Rotate image counter-clockwise 90deg +RLAPI void ImageColorTint(Image *image, Color color); // Modify image color: tint +RLAPI void ImageColorInvert(Image *image); // Modify image color: invert +RLAPI void ImageColorGrayscale(Image *image); // Modify image color: grayscale +RLAPI void ImageColorContrast(Image *image, float contrast); // Modify image color: contrast (-100 to 100) +RLAPI void ImageColorBrightness(Image *image, int brightness); // Modify image color: brightness (-255 to 255) +RLAPI void ImageColorReplace(Image *image, Color color, Color replace); // Modify image color: replace color +RLAPI Color *LoadImageColors(Image image); // Load color data from image as a Color array (RGBA - 32bit) +RLAPI Color *LoadImagePalette(Image image, int maxPaletteSize, int *colorCount); // Load colors palette from image as a Color array (RGBA - 32bit) +RLAPI void UnloadImageColors(Color *colors); // Unload color data loaded with LoadImageColors() +RLAPI void UnloadImagePalette(Color *colors); // Unload colors palette loaded with LoadImagePalette() +RLAPI Rectangle GetImageAlphaBorder(Image image, float threshold); // Get image alpha border rectangle +RLAPI Color GetImageColor(Image image, int x, int y); // Get image pixel color at (x, y) position + +// Image drawing functions +// NOTE: Image software-rendering functions (CPU) +RLAPI void ImageClearBackground(Image *dst, Color color); // Clear image background with given color +RLAPI void ImageDrawPixel(Image *dst, int posX, int posY, Color color); // Draw pixel within an image +RLAPI void ImageDrawPixelV(Image *dst, Vector2 position, Color color); // Draw pixel within an image (Vector version) +RLAPI void ImageDrawLine(Image *dst, int startPosX, int startPosY, int endPosX, int endPosY, Color color); // Draw line within an image +RLAPI void ImageDrawLineV(Image *dst, Vector2 start, Vector2 end, Color color); // Draw line within an image (Vector version) +RLAPI void ImageDrawLineEx(Image *dst, Vector2 start, Vector2 end, int thick, Color color); // Draw a line defining thickness within an image +RLAPI void ImageDrawCircle(Image *dst, int centerX, int centerY, int radius, Color color); // Draw a filled circle within an image +RLAPI void ImageDrawCircleV(Image *dst, Vector2 center, int radius, Color color); // Draw a filled circle within an image (Vector version) +RLAPI void ImageDrawCircleLines(Image *dst, int centerX, int centerY, int radius, Color color); // Draw circle outline within an image +RLAPI void ImageDrawCircleLinesV(Image *dst, Vector2 center, int radius, Color color); // Draw circle outline within an image (Vector version) +RLAPI void ImageDrawRectangle(Image *dst, int posX, int posY, int width, int height, Color color); // Draw rectangle within an image +RLAPI void ImageDrawRectangleV(Image *dst, Vector2 position, Vector2 size, Color color); // Draw rectangle within an image (Vector version) +RLAPI void ImageDrawRectangleRec(Image *dst, Rectangle rec, Color color); // Draw rectangle within an image +RLAPI void ImageDrawRectangleLines(Image *dst, Rectangle rec, int thick, Color color); // Draw rectangle lines within an image +RLAPI void ImageDrawTriangle(Image *dst, Vector2 v1, Vector2 v2, Vector2 v3, Color color); // Draw triangle within an image +RLAPI void ImageDrawTriangleEx(Image *dst, Vector2 v1, Vector2 v2, Vector2 v3, Color c1, Color c2, Color c3); // Draw triangle with interpolated colors within an image +RLAPI void ImageDrawTriangleLines(Image *dst, Vector2 v1, Vector2 v2, Vector2 v3, Color color); // Draw triangle outline within an image +RLAPI void ImageDrawTriangleFan(Image *dst, Vector2 *points, int pointCount, Color color); // Draw a triangle fan defined by points within an image (first vertex is the center) +RLAPI void ImageDrawTriangleStrip(Image *dst, Vector2 *points, int pointCount, Color color); // Draw a triangle strip defined by points within an image +RLAPI void ImageDraw(Image *dst, Image src, Rectangle srcRec, Rectangle dstRec, Color tint); // Draw a source image within a destination image (tint applied to source) +RLAPI void ImageDrawText(Image *dst, const char *text, int posX, int posY, int fontSize, Color color); // Draw text (using default font) within an image (destination) +RLAPI void ImageDrawTextEx(Image *dst, Font font, const char *text, Vector2 position, float fontSize, float spacing, Color tint); // Draw text (custom sprite font) within an image (destination) + +// Texture loading functions +// NOTE: These functions require GPU access +RLAPI Texture2D LoadTexture(const char *fileName); // Load texture from file into GPU memory (VRAM) +RLAPI Texture2D LoadTextureFromImage(Image image); // Load texture from image data +RLAPI TextureCubemap LoadTextureCubemap(Image image, int layout); // Load cubemap from image, multiple image cubemap layouts supported +RLAPI RenderTexture2D LoadRenderTexture(int width, int height); // Load texture for rendering (framebuffer) +RLAPI bool IsTextureValid(Texture2D texture); // Check if a texture is valid (loaded in GPU) +RLAPI void UnloadTexture(Texture2D texture); // Unload texture from GPU memory (VRAM) +RLAPI bool IsRenderTextureValid(RenderTexture2D target); // Check if a render texture is valid (loaded in GPU) +RLAPI void UnloadRenderTexture(RenderTexture2D target); // Unload render texture from GPU memory (VRAM) +RLAPI void UpdateTexture(Texture2D texture, const void *pixels); // Update GPU texture with new data +RLAPI void UpdateTextureRec(Texture2D texture, Rectangle rec, const void *pixels); // Update GPU texture rectangle with new data + +// Texture configuration functions +RLAPI void GenTextureMipmaps(Texture2D *texture); // Generate GPU mipmaps for a texture +RLAPI void SetTextureFilter(Texture2D texture, int filter); // Set texture scaling filter mode +RLAPI void SetTextureWrap(Texture2D texture, int wrap); // Set texture wrapping mode + +// Texture drawing functions +RLAPI void DrawTexture(Texture2D texture, int posX, int posY, Color tint); // Draw a Texture2D +RLAPI void DrawTextureV(Texture2D texture, Vector2 position, Color tint); // Draw a Texture2D with position defined as Vector2 +RLAPI void DrawTextureEx(Texture2D texture, Vector2 position, float rotation, float scale, Color tint); // Draw a Texture2D with extended parameters +RLAPI void DrawTextureRec(Texture2D texture, Rectangle source, Vector2 position, Color tint); // Draw a part of a texture defined by a rectangle +RLAPI void DrawTexturePro(Texture2D texture, Rectangle source, Rectangle dest, Vector2 origin, float rotation, Color tint); // Draw a part of a texture defined by a rectangle with 'pro' parameters +RLAPI void DrawTextureNPatch(Texture2D texture, NPatchInfo nPatchInfo, Rectangle dest, Vector2 origin, float rotation, Color tint); // Draws a texture (or part of it) that stretches or shrinks nicely + +// Color/pixel related functions +RLAPI bool ColorIsEqual(Color col1, Color col2); // Check if two colors are equal +RLAPI Color Fade(Color color, float alpha); // Get color with alpha applied, alpha goes from 0.0f to 1.0f +RLAPI int ColorToInt(Color color); // Get hexadecimal value for a Color (0xRRGGBBAA) +RLAPI Vector4 ColorNormalize(Color color); // Get Color normalized as float [0..1] +RLAPI Color ColorFromNormalized(Vector4 normalized); // Get Color from normalized values [0..1] +RLAPI Vector3 ColorToHSV(Color color); // Get HSV values for a Color, hue [0..360], saturation/value [0..1] +RLAPI Color ColorFromHSV(float hue, float saturation, float value); // Get a Color from HSV values, hue [0..360], saturation/value [0..1] +RLAPI Color ColorTint(Color color, Color tint); // Get color multiplied with another color +RLAPI Color ColorBrightness(Color color, float factor); // Get color with brightness correction, brightness factor goes from -1.0f to 1.0f +RLAPI Color ColorContrast(Color color, float contrast); // Get color with contrast correction, contrast values between -1.0f and 1.0f +RLAPI Color ColorAlpha(Color color, float alpha); // Get color with alpha applied, alpha goes from 0.0f to 1.0f +RLAPI Color ColorAlphaBlend(Color dst, Color src, Color tint); // Get src alpha-blended into dst color with tint +RLAPI Color ColorLerp(Color color1, Color color2, float factor); // Get color lerp interpolation between two colors, factor [0.0f..1.0f] +RLAPI Color GetColor(unsigned int hexValue); // Get Color structure from hexadecimal value +RLAPI Color GetPixelColor(void *srcPtr, int format); // Get Color from a source pixel pointer of certain format +RLAPI void SetPixelColor(void *dstPtr, Color color, int format); // Set color formatted into destination pixel pointer +RLAPI int GetPixelDataSize(int width, int height, int format); // Get pixel data size in bytes for certain format + +//------------------------------------------------------------------------------------ +// Font Loading and Text Drawing Functions (Module: text) +//------------------------------------------------------------------------------------ + +// Font loading/unloading functions +RLAPI Font GetFontDefault(void); // Get the default Font +RLAPI Font LoadFont(const char *fileName); // Load font from file into GPU memory (VRAM) +RLAPI Font LoadFontEx(const char *fileName, int fontSize, int *codepoints, int codepointCount); // Load font from file with extended parameters, use NULL for codepoints and 0 for codepointCount to load the default character set, font size is provided in pixels height +RLAPI Font LoadFontFromImage(Image image, Color key, int firstChar); // Load font from Image (XNA style) +RLAPI Font LoadFontFromMemory(const char *fileType, const unsigned char *fileData, int dataSize, int fontSize, int *codepoints, int codepointCount); // Load font from memory buffer, fileType refers to extension: i.e. '.ttf' +RLAPI bool IsFontValid(Font font); // Check if a font is valid (font data loaded, WARNING: GPU texture not checked) +RLAPI GlyphInfo *LoadFontData(const unsigned char *fileData, int dataSize, int fontSize, int *codepoints, int codepointCount, int type); // Load font data for further use +RLAPI Image GenImageFontAtlas(const GlyphInfo *glyphs, Rectangle **glyphRecs, int glyphCount, int fontSize, int padding, int packMethod); // Generate image font atlas using chars info +RLAPI void UnloadFontData(GlyphInfo *glyphs, int glyphCount); // Unload font chars info data (RAM) +RLAPI void UnloadFont(Font font); // Unload font from GPU memory (VRAM) +RLAPI bool ExportFontAsCode(Font font, const char *fileName); // Export font as code file, returns true on success + +// Text drawing functions +RLAPI void DrawFPS(int posX, int posY); // Draw current FPS +RLAPI void DrawText(const char *text, int posX, int posY, int fontSize, Color color); // Draw text (using default font) +RLAPI void DrawTextEx(Font font, const char *text, Vector2 position, float fontSize, float spacing, Color tint); // Draw text using font and additional parameters +RLAPI void DrawTextPro(Font font, const char *text, Vector2 position, Vector2 origin, float rotation, float fontSize, float spacing, Color tint); // Draw text using Font and pro parameters (rotation) +RLAPI void DrawTextCodepoint(Font font, int codepoint, Vector2 position, float fontSize, Color tint); // Draw one character (codepoint) +RLAPI void DrawTextCodepoints(Font font, const int *codepoints, int codepointCount, Vector2 position, float fontSize, float spacing, Color tint); // Draw multiple character (codepoint) + +// Text font info functions +RLAPI void SetTextLineSpacing(int spacing); // Set vertical line spacing when drawing with line-breaks +RLAPI int MeasureText(const char *text, int fontSize); // Measure string width for default font +RLAPI Vector2 MeasureTextEx(Font font, const char *text, float fontSize, float spacing); // Measure string size for Font +RLAPI int GetGlyphIndex(Font font, int codepoint); // Get glyph index position in font for a codepoint (unicode character), fallback to '?' if not found +RLAPI GlyphInfo GetGlyphInfo(Font font, int codepoint); // Get glyph font info data for a codepoint (unicode character), fallback to '?' if not found +RLAPI Rectangle GetGlyphAtlasRec(Font font, int codepoint); // Get glyph rectangle in font atlas for a codepoint (unicode character), fallback to '?' if not found + +// Text codepoints management functions (unicode characters) +RLAPI char *LoadUTF8(const int *codepoints, int length); // Load UTF-8 text encoded from codepoints array +RLAPI void UnloadUTF8(char *text); // Unload UTF-8 text encoded from codepoints array +RLAPI int *LoadCodepoints(const char *text, int *count); // Load all codepoints from a UTF-8 text string, codepoints count returned by parameter +RLAPI void UnloadCodepoints(int *codepoints); // Unload codepoints data from memory +RLAPI int GetCodepointCount(const char *text); // Get total number of codepoints in a UTF-8 encoded string +RLAPI int GetCodepoint(const char *text, int *codepointSize); // Get next codepoint in a UTF-8 encoded string, 0x3f('?') is returned on failure +RLAPI int GetCodepointNext(const char *text, int *codepointSize); // Get next codepoint in a UTF-8 encoded string, 0x3f('?') is returned on failure +RLAPI int GetCodepointPrevious(const char *text, int *codepointSize); // Get previous codepoint in a UTF-8 encoded string, 0x3f('?') is returned on failure +RLAPI const char *CodepointToUTF8(int codepoint, int *utf8Size); // Encode one codepoint into UTF-8 byte array (array length returned as parameter) + +// Text strings management functions (no UTF-8 strings, only byte chars) +// NOTE: Some strings allocate memory internally for returned strings, just be careful! +RLAPI int TextCopy(char *dst, const char *src); // Copy one string to another, returns bytes copied +RLAPI bool TextIsEqual(const char *text1, const char *text2); // Check if two text string are equal +RLAPI unsigned int TextLength(const char *text); // Get text length, checks for '\0' ending +RLAPI const char *TextFormat(const char *text, ...); // Text formatting with variables (sprintf() style) +RLAPI const char *TextSubtext(const char *text, int position, int length); // Get a piece of a text string +RLAPI char *TextReplace(const char *text, const char *replace, const char *by); // Replace text string (WARNING: memory must be freed!) +RLAPI char *TextInsert(const char *text, const char *insert, int position); // Insert text in a position (WARNING: memory must be freed!) +RLAPI const char *TextJoin(const char **textList, int count, const char *delimiter); // Join text strings with delimiter +RLAPI const char **TextSplit(const char *text, char delimiter, int *count); // Split text into multiple strings +RLAPI void TextAppend(char *text, const char *append, int *position); // Append text at specific position and move cursor! +RLAPI int TextFindIndex(const char *text, const char *find); // Find first text occurrence within a string +RLAPI const char *TextToUpper(const char *text); // Get upper case version of provided string +RLAPI const char *TextToLower(const char *text); // Get lower case version of provided string +RLAPI const char *TextToPascal(const char *text); // Get Pascal case notation version of provided string +RLAPI const char *TextToSnake(const char *text); // Get Snake case notation version of provided string +RLAPI const char *TextToCamel(const char *text); // Get Camel case notation version of provided string + +RLAPI int TextToInteger(const char *text); // Get integer value from text (negative values not supported) +RLAPI float TextToFloat(const char *text); // Get float value from text (negative values not supported) + +//------------------------------------------------------------------------------------ +// Basic 3d Shapes Drawing Functions (Module: models) +//------------------------------------------------------------------------------------ + +// Basic geometric 3D shapes drawing functions +RLAPI void DrawLine3D(Vector3 startPos, Vector3 endPos, Color color); // Draw a line in 3D world space +RLAPI void DrawPoint3D(Vector3 position, Color color); // Draw a point in 3D space, actually a small line +RLAPI void DrawCircle3D(Vector3 center, float radius, Vector3 rotationAxis, float rotationAngle, Color color); // Draw a circle in 3D world space +RLAPI void DrawTriangle3D(Vector3 v1, Vector3 v2, Vector3 v3, Color color); // Draw a color-filled triangle (vertex in counter-clockwise order!) +RLAPI void DrawTriangleStrip3D(const Vector3 *points, int pointCount, Color color); // Draw a triangle strip defined by points +RLAPI void DrawCube(Vector3 position, float width, float height, float length, Color color); // Draw cube +RLAPI void DrawCubeV(Vector3 position, Vector3 size, Color color); // Draw cube (Vector version) +RLAPI void DrawCubeWires(Vector3 position, float width, float height, float length, Color color); // Draw cube wires +RLAPI void DrawCubeWiresV(Vector3 position, Vector3 size, Color color); // Draw cube wires (Vector version) +RLAPI void DrawSphere(Vector3 centerPos, float radius, Color color); // Draw sphere +RLAPI void DrawSphereEx(Vector3 centerPos, float radius, int rings, int slices, Color color); // Draw sphere with extended parameters +RLAPI void DrawSphereWires(Vector3 centerPos, float radius, int rings, int slices, Color color); // Draw sphere wires +RLAPI void DrawCylinder(Vector3 position, float radiusTop, float radiusBottom, float height, int slices, Color color); // Draw a cylinder/cone +RLAPI void DrawCylinderEx(Vector3 startPos, Vector3 endPos, float startRadius, float endRadius, int sides, Color color); // Draw a cylinder with base at startPos and top at endPos +RLAPI void DrawCylinderWires(Vector3 position, float radiusTop, float radiusBottom, float height, int slices, Color color); // Draw a cylinder/cone wires +RLAPI void DrawCylinderWiresEx(Vector3 startPos, Vector3 endPos, float startRadius, float endRadius, int sides, Color color); // Draw a cylinder wires with base at startPos and top at endPos +RLAPI void DrawCapsule(Vector3 startPos, Vector3 endPos, float radius, int slices, int rings, Color color); // Draw a capsule with the center of its sphere caps at startPos and endPos +RLAPI void DrawCapsuleWires(Vector3 startPos, Vector3 endPos, float radius, int slices, int rings, Color color); // Draw capsule wireframe with the center of its sphere caps at startPos and endPos +RLAPI void DrawPlane(Vector3 centerPos, Vector2 size, Color color); // Draw a plane XZ +RLAPI void DrawRay(Ray ray, Color color); // Draw a ray line +RLAPI void DrawGrid(int slices, float spacing); // Draw a grid (centered at (0, 0, 0)) + +//------------------------------------------------------------------------------------ +// Model 3d Loading and Drawing Functions (Module: models) +//------------------------------------------------------------------------------------ + +// Model management functions +RLAPI Model LoadModel(const char *fileName); // Load model from files (meshes and materials) +RLAPI Model LoadModelFromMesh(Mesh mesh); // Load model from generated mesh (default material) +RLAPI bool IsModelValid(Model model); // Check if a model is valid (loaded in GPU, VAO/VBOs) +RLAPI void UnloadModel(Model model); // Unload model (including meshes) from memory (RAM and/or VRAM) +RLAPI BoundingBox GetModelBoundingBox(Model model); // Compute model bounding box limits (considers all meshes) + +// Model drawing functions +RLAPI void DrawModel(Model model, Vector3 position, float scale, Color tint); // Draw a model (with texture if set) +RLAPI void DrawModelEx(Model model, Vector3 position, Vector3 rotationAxis, float rotationAngle, Vector3 scale, Color tint); // Draw a model with extended parameters +RLAPI void DrawModelWires(Model model, Vector3 position, float scale, Color tint); // Draw a model wires (with texture if set) +RLAPI void DrawModelWiresEx(Model model, Vector3 position, Vector3 rotationAxis, float rotationAngle, Vector3 scale, Color tint); // Draw a model wires (with texture if set) with extended parameters +RLAPI void DrawModelPoints(Model model, Vector3 position, float scale, Color tint); // Draw a model as points +RLAPI void DrawModelPointsEx(Model model, Vector3 position, Vector3 rotationAxis, float rotationAngle, Vector3 scale, Color tint); // Draw a model as points with extended parameters +RLAPI void DrawBoundingBox(BoundingBox box, Color color); // Draw bounding box (wires) +RLAPI void DrawBillboard(Camera camera, Texture2D texture, Vector3 position, float scale, Color tint); // Draw a billboard texture +RLAPI void DrawBillboardRec(Camera camera, Texture2D texture, Rectangle source, Vector3 position, Vector2 size, Color tint); // Draw a billboard texture defined by source +RLAPI void DrawBillboardPro(Camera camera, Texture2D texture, Rectangle source, Vector3 position, Vector3 up, Vector2 size, Vector2 origin, float rotation, Color tint); // Draw a billboard texture defined by source and rotation + +// Mesh management functions +RLAPI void UploadMesh(Mesh *mesh, bool dynamic); // Upload mesh vertex data in GPU and provide VAO/VBO ids +RLAPI void UpdateMeshBuffer(Mesh mesh, int index, const void *data, int dataSize, int offset); // Update mesh vertex data in GPU for a specific buffer index +RLAPI void UnloadMesh(Mesh mesh); // Unload mesh data from CPU and GPU +RLAPI void DrawMesh(Mesh mesh, Material material, Matrix transform); // Draw a 3d mesh with material and transform +RLAPI void DrawMeshInstanced(Mesh mesh, Material material, const Matrix *transforms, int instances); // Draw multiple mesh instances with material and different transforms +RLAPI BoundingBox GetMeshBoundingBox(Mesh mesh); // Compute mesh bounding box limits +RLAPI void GenMeshTangents(Mesh *mesh); // Compute mesh tangents +RLAPI bool ExportMesh(Mesh mesh, const char *fileName); // Export mesh data to file, returns true on success +RLAPI bool ExportMeshAsCode(Mesh mesh, const char *fileName); // Export mesh as code file (.h) defining multiple arrays of vertex attributes + +// Mesh generation functions +RLAPI Mesh GenMeshPoly(int sides, float radius); // Generate polygonal mesh +RLAPI Mesh GenMeshPlane(float width, float length, int resX, int resZ); // Generate plane mesh (with subdivisions) +RLAPI Mesh GenMeshCube(float width, float height, float length); // Generate cuboid mesh +RLAPI Mesh GenMeshSphere(float radius, int rings, int slices); // Generate sphere mesh (standard sphere) +RLAPI Mesh GenMeshHemiSphere(float radius, int rings, int slices); // Generate half-sphere mesh (no bottom cap) +RLAPI Mesh GenMeshCylinder(float radius, float height, int slices); // Generate cylinder mesh +RLAPI Mesh GenMeshCone(float radius, float height, int slices); // Generate cone/pyramid mesh +RLAPI Mesh GenMeshTorus(float radius, float size, int radSeg, int sides); // Generate torus mesh +RLAPI Mesh GenMeshKnot(float radius, float size, int radSeg, int sides); // Generate trefoil knot mesh +RLAPI Mesh GenMeshHeightmap(Image heightmap, Vector3 size); // Generate heightmap mesh from image data +RLAPI Mesh GenMeshCubicmap(Image cubicmap, Vector3 cubeSize); // Generate cubes-based map mesh from image data + +// Material loading/unloading functions +RLAPI Material *LoadMaterials(const char *fileName, int *materialCount); // Load materials from model file +RLAPI Material LoadMaterialDefault(void); // Load default material (Supports: DIFFUSE, SPECULAR, NORMAL maps) +RLAPI bool IsMaterialValid(Material material); // Check if a material is valid (shader assigned, map textures loaded in GPU) +RLAPI void UnloadMaterial(Material material); // Unload material from GPU memory (VRAM) +RLAPI void SetMaterialTexture(Material *material, int mapType, Texture2D texture); // Set texture for a material map type (MATERIAL_MAP_DIFFUSE, MATERIAL_MAP_SPECULAR...) +RLAPI void SetModelMeshMaterial(Model *model, int meshId, int materialId); // Set material for a mesh + +// Model animations loading/unloading functions +RLAPI ModelAnimation *LoadModelAnimations(const char *fileName, int *animCount); // Load model animations from file +RLAPI void UpdateModelAnimation(Model model, ModelAnimation anim, int frame); // Update model animation pose (CPU) +RLAPI void UpdateModelAnimationBones(Model model, ModelAnimation anim, int frame); // Update model animation mesh bone matrices (GPU skinning) +RLAPI void UnloadModelAnimation(ModelAnimation anim); // Unload animation data +RLAPI void UnloadModelAnimations(ModelAnimation *animations, int animCount); // Unload animation array data +RLAPI bool IsModelAnimationValid(Model model, ModelAnimation anim); // Check model animation skeleton match + +// Collision detection functions +RLAPI bool CheckCollisionSpheres(Vector3 center1, float radius1, Vector3 center2, float radius2); // Check collision between two spheres +RLAPI bool CheckCollisionBoxes(BoundingBox box1, BoundingBox box2); // Check collision between two bounding boxes +RLAPI bool CheckCollisionBoxSphere(BoundingBox box, Vector3 center, float radius); // Check collision between box and sphere +RLAPI RayCollision GetRayCollisionSphere(Ray ray, Vector3 center, float radius); // Get collision info between ray and sphere +RLAPI RayCollision GetRayCollisionBox(Ray ray, BoundingBox box); // Get collision info between ray and box +RLAPI RayCollision GetRayCollisionMesh(Ray ray, Mesh mesh, Matrix transform); // Get collision info between ray and mesh +RLAPI RayCollision GetRayCollisionTriangle(Ray ray, Vector3 p1, Vector3 p2, Vector3 p3); // Get collision info between ray and triangle +RLAPI RayCollision GetRayCollisionQuad(Ray ray, Vector3 p1, Vector3 p2, Vector3 p3, Vector3 p4); // Get collision info between ray and quad + +//------------------------------------------------------------------------------------ +// Audio Loading and Playing Functions (Module: audio) +//------------------------------------------------------------------------------------ +typedef void (*AudioCallback)(void *bufferData, unsigned int frames); + +// Audio device management functions +RLAPI void InitAudioDevice(void); // Initialize audio device and context +RLAPI void CloseAudioDevice(void); // Close the audio device and context +RLAPI bool IsAudioDeviceReady(void); // Check if audio device has been initialized successfully +RLAPI void SetMasterVolume(float volume); // Set master volume (listener) +RLAPI float GetMasterVolume(void); // Get master volume (listener) + +// Wave/Sound loading/unloading functions +RLAPI Wave LoadWave(const char *fileName); // Load wave data from file +RLAPI Wave LoadWaveFromMemory(const char *fileType, const unsigned char *fileData, int dataSize); // Load wave from memory buffer, fileType refers to extension: i.e. '.wav' +RLAPI bool IsWaveValid(Wave wave); // Checks if wave data is valid (data loaded and parameters) +RLAPI Sound LoadSound(const char *fileName); // Load sound from file +RLAPI Sound LoadSoundFromWave(Wave wave); // Load sound from wave data +RLAPI Sound LoadSoundAlias(Sound source); // Create a new sound that shares the same sample data as the source sound, does not own the sound data +RLAPI bool IsSoundValid(Sound sound); // Checks if a sound is valid (data loaded and buffers initialized) +RLAPI void UpdateSound(Sound sound, const void *data, int sampleCount); // Update sound buffer with new data +RLAPI void UnloadWave(Wave wave); // Unload wave data +RLAPI void UnloadSound(Sound sound); // Unload sound +RLAPI void UnloadSoundAlias(Sound alias); // Unload a sound alias (does not deallocate sample data) +RLAPI bool ExportWave(Wave wave, const char *fileName); // Export wave data to file, returns true on success +RLAPI bool ExportWaveAsCode(Wave wave, const char *fileName); // Export wave sample data to code (.h), returns true on success + +// Wave/Sound management functions +RLAPI void PlaySound(Sound sound); // Play a sound +RLAPI void StopSound(Sound sound); // Stop playing a sound +RLAPI void PauseSound(Sound sound); // Pause a sound +RLAPI void ResumeSound(Sound sound); // Resume a paused sound +RLAPI bool IsSoundPlaying(Sound sound); // Check if a sound is currently playing +RLAPI void SetSoundVolume(Sound sound, float volume); // Set volume for a sound (1.0 is max level) +RLAPI void SetSoundPitch(Sound sound, float pitch); // Set pitch for a sound (1.0 is base level) +RLAPI void SetSoundPan(Sound sound, float pan); // Set pan for a sound (0.5 is center) +RLAPI Wave WaveCopy(Wave wave); // Copy a wave to a new wave +RLAPI void WaveCrop(Wave *wave, int initFrame, int finalFrame); // Crop a wave to defined frames range +RLAPI void WaveFormat(Wave *wave, int sampleRate, int sampleSize, int channels); // Convert wave data to desired format +RLAPI float *LoadWaveSamples(Wave wave); // Load samples data from wave as a 32bit float data array +RLAPI void UnloadWaveSamples(float *samples); // Unload samples data loaded with LoadWaveSamples() + +// Music management functions +RLAPI Music LoadMusicStream(const char *fileName); // Load music stream from file +RLAPI Music LoadMusicStreamFromMemory(const char *fileType, const unsigned char *data, int dataSize); // Load music stream from data +RLAPI bool IsMusicValid(Music music); // Checks if a music stream is valid (context and buffers initialized) +RLAPI void UnloadMusicStream(Music music); // Unload music stream +RLAPI void PlayMusicStream(Music music); // Start music playing +RLAPI bool IsMusicStreamPlaying(Music music); // Check if music is playing +RLAPI void UpdateMusicStream(Music music); // Updates buffers for music streaming +RLAPI void StopMusicStream(Music music); // Stop music playing +RLAPI void PauseMusicStream(Music music); // Pause music playing +RLAPI void ResumeMusicStream(Music music); // Resume playing paused music +RLAPI void SeekMusicStream(Music music, float position); // Seek music to a position (in seconds) +RLAPI void SetMusicVolume(Music music, float volume); // Set volume for music (1.0 is max level) +RLAPI void SetMusicPitch(Music music, float pitch); // Set pitch for a music (1.0 is base level) +RLAPI void SetMusicPan(Music music, float pan); // Set pan for a music (0.5 is center) +RLAPI float GetMusicTimeLength(Music music); // Get music time length (in seconds) +RLAPI float GetMusicTimePlayed(Music music); // Get current music time played (in seconds) + +// AudioStream management functions +RLAPI AudioStream LoadAudioStream(unsigned int sampleRate, unsigned int sampleSize, unsigned int channels); // Load audio stream (to stream raw audio pcm data) +RLAPI bool IsAudioStreamValid(AudioStream stream); // Checks if an audio stream is valid (buffers initialized) +RLAPI void UnloadAudioStream(AudioStream stream); // Unload audio stream and free memory +RLAPI void UpdateAudioStream(AudioStream stream, const void *data, int frameCount); // Update audio stream buffers with data +RLAPI bool IsAudioStreamProcessed(AudioStream stream); // Check if any audio stream buffers requires refill +RLAPI void PlayAudioStream(AudioStream stream); // Play audio stream +RLAPI void PauseAudioStream(AudioStream stream); // Pause audio stream +RLAPI void ResumeAudioStream(AudioStream stream); // Resume audio stream +RLAPI bool IsAudioStreamPlaying(AudioStream stream); // Check if audio stream is playing +RLAPI void StopAudioStream(AudioStream stream); // Stop audio stream +RLAPI void SetAudioStreamVolume(AudioStream stream, float volume); // Set volume for audio stream (1.0 is max level) +RLAPI void SetAudioStreamPitch(AudioStream stream, float pitch); // Set pitch for audio stream (1.0 is base level) +RLAPI void SetAudioStreamPan(AudioStream stream, float pan); // Set pan for audio stream (0.5 is centered) +RLAPI void SetAudioStreamBufferSizeDefault(int size); // Default size for new audio streams +RLAPI void SetAudioStreamCallback(AudioStream stream, AudioCallback callback); // Audio thread callback to request new data + +RLAPI void AttachAudioStreamProcessor(AudioStream stream, AudioCallback processor); // Attach audio stream processor to stream, receives the samples as 'float' +RLAPI void DetachAudioStreamProcessor(AudioStream stream, AudioCallback processor); // Detach audio stream processor from stream + +RLAPI void AttachAudioMixedProcessor(AudioCallback processor); // Attach audio stream processor to the entire audio pipeline, receives the samples as 'float' +RLAPI void DetachAudioMixedProcessor(AudioCallback processor); // Detach audio stream processor from the entire audio pipeline + +#if defined(__cplusplus) +} +#endif + +#endif // RAYLIB_H diff --git a/third_party/raylib/include/raymath.h b/third_party/raylib/include/raymath.h new file mode 100644 index 0000000000..5b5e4c74ff --- /dev/null +++ b/third_party/raylib/include/raymath.h @@ -0,0 +1,2949 @@ +/********************************************************************************************** +* +* raymath v2.0 - Math functions to work with Vector2, Vector3, Matrix and Quaternions +* +* CONVENTIONS: +* - Matrix structure is defined as row-major (memory layout) but parameters naming AND all +* math operations performed by the library consider the structure as it was column-major +* It is like transposed versions of the matrices are used for all the maths +* It benefits some functions making them cache-friendly and also avoids matrix +* transpositions sometimes required by OpenGL +* Example: In memory order, row0 is [m0 m4 m8 m12] but in semantic math row0 is [m0 m1 m2 m3] +* - Functions are always self-contained, no function use another raymath function inside, +* required code is directly re-implemented inside +* - Functions input parameters are always received by value (2 unavoidable exceptions) +* - Functions use always a "result" variable for return (except C++ operators) +* - Functions are always defined inline +* - Angles are always in radians (DEG2RAD/RAD2DEG macros provided for convenience) +* - No compound literals used to make sure libray is compatible with C++ +* +* CONFIGURATION: +* #define RAYMATH_IMPLEMENTATION +* Generates the implementation of the library into the included file. +* If not defined, the library is in header only mode and can be included in other headers +* or source files without problems. But only ONE file should hold the implementation. +* +* #define RAYMATH_STATIC_INLINE +* Define static inline functions code, so #include header suffices for use. +* This may use up lots of memory. +* +* #define RAYMATH_DISABLE_CPP_OPERATORS +* Disables C++ operator overloads for raymath types. +* +* LICENSE: zlib/libpng +* +* Copyright (c) 2015-2024 Ramon Santamaria (@raysan5) +* +* This software is provided "as-is", without any express or implied warranty. In no event +* will the authors be held liable for any damages arising from the use of this software. +* +* Permission is granted to anyone to use this software for any purpose, including commercial +* applications, and to alter it and redistribute it freely, subject to the following restrictions: +* +* 1. The origin of this software must not be misrepresented; you must not claim that you +* wrote the original software. If you use this software in a product, an acknowledgment +* in the product documentation would be appreciated but is not required. +* +* 2. Altered source versions must be plainly marked as such, and must not be misrepresented +* as being the original software. +* +* 3. This notice may not be removed or altered from any source distribution. +* +**********************************************************************************************/ + +#ifndef RAYMATH_H +#define RAYMATH_H + +#if defined(RAYMATH_IMPLEMENTATION) && defined(RAYMATH_STATIC_INLINE) + #error "Specifying both RAYMATH_IMPLEMENTATION and RAYMATH_STATIC_INLINE is contradictory" +#endif + +// Function specifiers definition +#if defined(RAYMATH_IMPLEMENTATION) + #if defined(_WIN32) && defined(BUILD_LIBTYPE_SHARED) + #define RMAPI __declspec(dllexport) extern inline // We are building raylib as a Win32 shared library (.dll) + #elif defined(BUILD_LIBTYPE_SHARED) + #define RMAPI __attribute__((visibility("default"))) // We are building raylib as a Unix shared library (.so/.dylib) + #elif defined(_WIN32) && defined(USE_LIBTYPE_SHARED) + #define RMAPI __declspec(dllimport) // We are using raylib as a Win32 shared library (.dll) + #else + #define RMAPI extern inline // Provide external definition + #endif +#elif defined(RAYMATH_STATIC_INLINE) + #define RMAPI static inline // Functions may be inlined, no external out-of-line definition +#else + #if defined(__TINYC__) + #define RMAPI static inline // plain inline not supported by tinycc (See issue #435) + #else + #define RMAPI inline // Functions may be inlined or external definition used + #endif +#endif + + +//---------------------------------------------------------------------------------- +// Defines and Macros +//---------------------------------------------------------------------------------- +#ifndef PI + #define PI 3.14159265358979323846f +#endif + +#ifndef EPSILON + #define EPSILON 0.000001f +#endif + +#ifndef DEG2RAD + #define DEG2RAD (PI/180.0f) +#endif + +#ifndef RAD2DEG + #define RAD2DEG (180.0f/PI) +#endif + +// Get float vector for Matrix +#ifndef MatrixToFloat + #define MatrixToFloat(mat) (MatrixToFloatV(mat).v) +#endif + +// Get float vector for Vector3 +#ifndef Vector3ToFloat + #define Vector3ToFloat(vec) (Vector3ToFloatV(vec).v) +#endif + +//---------------------------------------------------------------------------------- +// Types and Structures Definition +//---------------------------------------------------------------------------------- +#if !defined(RL_VECTOR2_TYPE) +// Vector2 type +typedef struct Vector2 { + float x; + float y; +} Vector2; +#define RL_VECTOR2_TYPE +#endif + +#if !defined(RL_VECTOR3_TYPE) +// Vector3 type +typedef struct Vector3 { + float x; + float y; + float z; +} Vector3; +#define RL_VECTOR3_TYPE +#endif + +#if !defined(RL_VECTOR4_TYPE) +// Vector4 type +typedef struct Vector4 { + float x; + float y; + float z; + float w; +} Vector4; +#define RL_VECTOR4_TYPE +#endif + +#if !defined(RL_QUATERNION_TYPE) +// Quaternion type +typedef Vector4 Quaternion; +#define RL_QUATERNION_TYPE +#endif + +#if !defined(RL_MATRIX_TYPE) +// Matrix type (OpenGL style 4x4 - right handed, column major) +typedef struct Matrix { + float m0, m4, m8, m12; // Matrix first row (4 components) + float m1, m5, m9, m13; // Matrix second row (4 components) + float m2, m6, m10, m14; // Matrix third row (4 components) + float m3, m7, m11, m15; // Matrix fourth row (4 components) +} Matrix; +#define RL_MATRIX_TYPE +#endif + +// NOTE: Helper types to be used instead of array return types for *ToFloat functions +typedef struct float3 { + float v[3]; +} float3; + +typedef struct float16 { + float v[16]; +} float16; + +#include // Required for: sinf(), cosf(), tan(), atan2f(), sqrtf(), floor(), fminf(), fmaxf(), fabsf() + +//---------------------------------------------------------------------------------- +// Module Functions Definition - Utils math +//---------------------------------------------------------------------------------- + +// Clamp float value +RMAPI float Clamp(float value, float min, float max) +{ + float result = (value < min)? min : value; + + if (result > max) result = max; + + return result; +} + +// Calculate linear interpolation between two floats +RMAPI float Lerp(float start, float end, float amount) +{ + float result = start + amount*(end - start); + + return result; +} + +// Normalize input value within input range +RMAPI float Normalize(float value, float start, float end) +{ + float result = (value - start)/(end - start); + + return result; +} + +// Remap input value within input range to output range +RMAPI float Remap(float value, float inputStart, float inputEnd, float outputStart, float outputEnd) +{ + float result = (value - inputStart)/(inputEnd - inputStart)*(outputEnd - outputStart) + outputStart; + + return result; +} + +// Wrap input value from min to max +RMAPI float Wrap(float value, float min, float max) +{ + float result = value - (max - min)*floorf((value - min)/(max - min)); + + return result; +} + +// Check whether two given floats are almost equal +RMAPI int FloatEquals(float x, float y) +{ +#if !defined(EPSILON) + #define EPSILON 0.000001f +#endif + + int result = (fabsf(x - y)) <= (EPSILON*fmaxf(1.0f, fmaxf(fabsf(x), fabsf(y)))); + + return result; +} + +//---------------------------------------------------------------------------------- +// Module Functions Definition - Vector2 math +//---------------------------------------------------------------------------------- + +// Vector with components value 0.0f +RMAPI Vector2 Vector2Zero(void) +{ + Vector2 result = { 0.0f, 0.0f }; + + return result; +} + +// Vector with components value 1.0f +RMAPI Vector2 Vector2One(void) +{ + Vector2 result = { 1.0f, 1.0f }; + + return result; +} + +// Add two vectors (v1 + v2) +RMAPI Vector2 Vector2Add(Vector2 v1, Vector2 v2) +{ + Vector2 result = { v1.x + v2.x, v1.y + v2.y }; + + return result; +} + +// Add vector and float value +RMAPI Vector2 Vector2AddValue(Vector2 v, float add) +{ + Vector2 result = { v.x + add, v.y + add }; + + return result; +} + +// Subtract two vectors (v1 - v2) +RMAPI Vector2 Vector2Subtract(Vector2 v1, Vector2 v2) +{ + Vector2 result = { v1.x - v2.x, v1.y - v2.y }; + + return result; +} + +// Subtract vector by float value +RMAPI Vector2 Vector2SubtractValue(Vector2 v, float sub) +{ + Vector2 result = { v.x - sub, v.y - sub }; + + return result; +} + +// Calculate vector length +RMAPI float Vector2Length(Vector2 v) +{ + float result = sqrtf((v.x*v.x) + (v.y*v.y)); + + return result; +} + +// Calculate vector square length +RMAPI float Vector2LengthSqr(Vector2 v) +{ + float result = (v.x*v.x) + (v.y*v.y); + + return result; +} + +// Calculate two vectors dot product +RMAPI float Vector2DotProduct(Vector2 v1, Vector2 v2) +{ + float result = (v1.x*v2.x + v1.y*v2.y); + + return result; +} + +// Calculate two vectors cross product +RMAPI float Vector2CrossProduct(Vector2 v1, Vector2 v2) +{ + float result = (v1.x*v2.y - v1.y*v2.x); + + return result; +} + +// Calculate distance between two vectors +RMAPI float Vector2Distance(Vector2 v1, Vector2 v2) +{ + float result = sqrtf((v1.x - v2.x)*(v1.x - v2.x) + (v1.y - v2.y)*(v1.y - v2.y)); + + return result; +} + +// Calculate square distance between two vectors +RMAPI float Vector2DistanceSqr(Vector2 v1, Vector2 v2) +{ + float result = ((v1.x - v2.x)*(v1.x - v2.x) + (v1.y - v2.y)*(v1.y - v2.y)); + + return result; +} + +// Calculate angle between two vectors +// NOTE: Angle is calculated from origin point (0, 0) +RMAPI float Vector2Angle(Vector2 v1, Vector2 v2) +{ + float result = 0.0f; + + float dot = v1.x*v2.x + v1.y*v2.y; + float det = v1.x*v2.y - v1.y*v2.x; + + result = atan2f(det, dot); + + return result; +} + +// Calculate angle defined by a two vectors line +// NOTE: Parameters need to be normalized +// Current implementation should be aligned with glm::angle +RMAPI float Vector2LineAngle(Vector2 start, Vector2 end) +{ + float result = 0.0f; + + // TODO(10/9/2023): Currently angles move clockwise, determine if this is wanted behavior + result = -atan2f(end.y - start.y, end.x - start.x); + + return result; +} + +// Scale vector (multiply by value) +RMAPI Vector2 Vector2Scale(Vector2 v, float scale) +{ + Vector2 result = { v.x*scale, v.y*scale }; + + return result; +} + +// Multiply vector by vector +RMAPI Vector2 Vector2Multiply(Vector2 v1, Vector2 v2) +{ + Vector2 result = { v1.x*v2.x, v1.y*v2.y }; + + return result; +} + +// Negate vector +RMAPI Vector2 Vector2Negate(Vector2 v) +{ + Vector2 result = { -v.x, -v.y }; + + return result; +} + +// Divide vector by vector +RMAPI Vector2 Vector2Divide(Vector2 v1, Vector2 v2) +{ + Vector2 result = { v1.x/v2.x, v1.y/v2.y }; + + return result; +} + +// Normalize provided vector +RMAPI Vector2 Vector2Normalize(Vector2 v) +{ + Vector2 result = { 0 }; + float length = sqrtf((v.x*v.x) + (v.y*v.y)); + + if (length > 0) + { + float ilength = 1.0f/length; + result.x = v.x*ilength; + result.y = v.y*ilength; + } + + return result; +} + +// Transforms a Vector2 by a given Matrix +RMAPI Vector2 Vector2Transform(Vector2 v, Matrix mat) +{ + Vector2 result = { 0 }; + + float x = v.x; + float y = v.y; + float z = 0; + + result.x = mat.m0*x + mat.m4*y + mat.m8*z + mat.m12; + result.y = mat.m1*x + mat.m5*y + mat.m9*z + mat.m13; + + return result; +} + +// Calculate linear interpolation between two vectors +RMAPI Vector2 Vector2Lerp(Vector2 v1, Vector2 v2, float amount) +{ + Vector2 result = { 0 }; + + result.x = v1.x + amount*(v2.x - v1.x); + result.y = v1.y + amount*(v2.y - v1.y); + + return result; +} + +// Calculate reflected vector to normal +RMAPI Vector2 Vector2Reflect(Vector2 v, Vector2 normal) +{ + Vector2 result = { 0 }; + + float dotProduct = (v.x*normal.x + v.y*normal.y); // Dot product + + result.x = v.x - (2.0f*normal.x)*dotProduct; + result.y = v.y - (2.0f*normal.y)*dotProduct; + + return result; +} + +// Get min value for each pair of components +RMAPI Vector2 Vector2Min(Vector2 v1, Vector2 v2) +{ + Vector2 result = { 0 }; + + result.x = fminf(v1.x, v2.x); + result.y = fminf(v1.y, v2.y); + + return result; +} + +// Get max value for each pair of components +RMAPI Vector2 Vector2Max(Vector2 v1, Vector2 v2) +{ + Vector2 result = { 0 }; + + result.x = fmaxf(v1.x, v2.x); + result.y = fmaxf(v1.y, v2.y); + + return result; +} + +// Rotate vector by angle +RMAPI Vector2 Vector2Rotate(Vector2 v, float angle) +{ + Vector2 result = { 0 }; + + float cosres = cosf(angle); + float sinres = sinf(angle); + + result.x = v.x*cosres - v.y*sinres; + result.y = v.x*sinres + v.y*cosres; + + return result; +} + +// Move Vector towards target +RMAPI Vector2 Vector2MoveTowards(Vector2 v, Vector2 target, float maxDistance) +{ + Vector2 result = { 0 }; + + float dx = target.x - v.x; + float dy = target.y - v.y; + float value = (dx*dx) + (dy*dy); + + if ((value == 0) || ((maxDistance >= 0) && (value <= maxDistance*maxDistance))) return target; + + float dist = sqrtf(value); + + result.x = v.x + dx/dist*maxDistance; + result.y = v.y + dy/dist*maxDistance; + + return result; +} + +// Invert the given vector +RMAPI Vector2 Vector2Invert(Vector2 v) +{ + Vector2 result = { 1.0f/v.x, 1.0f/v.y }; + + return result; +} + +// Clamp the components of the vector between +// min and max values specified by the given vectors +RMAPI Vector2 Vector2Clamp(Vector2 v, Vector2 min, Vector2 max) +{ + Vector2 result = { 0 }; + + result.x = fminf(max.x, fmaxf(min.x, v.x)); + result.y = fminf(max.y, fmaxf(min.y, v.y)); + + return result; +} + +// Clamp the magnitude of the vector between two min and max values +RMAPI Vector2 Vector2ClampValue(Vector2 v, float min, float max) +{ + Vector2 result = v; + + float length = (v.x*v.x) + (v.y*v.y); + if (length > 0.0f) + { + length = sqrtf(length); + + float scale = 1; // By default, 1 as the neutral element. + if (length < min) + { + scale = min/length; + } + else if (length > max) + { + scale = max/length; + } + + result.x = v.x*scale; + result.y = v.y*scale; + } + + return result; +} + +// Check whether two given vectors are almost equal +RMAPI int Vector2Equals(Vector2 p, Vector2 q) +{ +#if !defined(EPSILON) + #define EPSILON 0.000001f +#endif + + int result = ((fabsf(p.x - q.x)) <= (EPSILON*fmaxf(1.0f, fmaxf(fabsf(p.x), fabsf(q.x))))) && + ((fabsf(p.y - q.y)) <= (EPSILON*fmaxf(1.0f, fmaxf(fabsf(p.y), fabsf(q.y))))); + + return result; +} + +// Compute the direction of a refracted ray +// v: normalized direction of the incoming ray +// n: normalized normal vector of the interface of two optical media +// r: ratio of the refractive index of the medium from where the ray comes +// to the refractive index of the medium on the other side of the surface +RMAPI Vector2 Vector2Refract(Vector2 v, Vector2 n, float r) +{ + Vector2 result = { 0 }; + + float dot = v.x*n.x + v.y*n.y; + float d = 1.0f - r*r*(1.0f - dot*dot); + + if (d >= 0.0f) + { + d = sqrtf(d); + v.x = r*v.x - (r*dot + d)*n.x; + v.y = r*v.y - (r*dot + d)*n.y; + + result = v; + } + + return result; +} + + +//---------------------------------------------------------------------------------- +// Module Functions Definition - Vector3 math +//---------------------------------------------------------------------------------- + +// Vector with components value 0.0f +RMAPI Vector3 Vector3Zero(void) +{ + Vector3 result = { 0.0f, 0.0f, 0.0f }; + + return result; +} + +// Vector with components value 1.0f +RMAPI Vector3 Vector3One(void) +{ + Vector3 result = { 1.0f, 1.0f, 1.0f }; + + return result; +} + +// Add two vectors +RMAPI Vector3 Vector3Add(Vector3 v1, Vector3 v2) +{ + Vector3 result = { v1.x + v2.x, v1.y + v2.y, v1.z + v2.z }; + + return result; +} + +// Add vector and float value +RMAPI Vector3 Vector3AddValue(Vector3 v, float add) +{ + Vector3 result = { v.x + add, v.y + add, v.z + add }; + + return result; +} + +// Subtract two vectors +RMAPI Vector3 Vector3Subtract(Vector3 v1, Vector3 v2) +{ + Vector3 result = { v1.x - v2.x, v1.y - v2.y, v1.z - v2.z }; + + return result; +} + +// Subtract vector by float value +RMAPI Vector3 Vector3SubtractValue(Vector3 v, float sub) +{ + Vector3 result = { v.x - sub, v.y - sub, v.z - sub }; + + return result; +} + +// Multiply vector by scalar +RMAPI Vector3 Vector3Scale(Vector3 v, float scalar) +{ + Vector3 result = { v.x*scalar, v.y*scalar, v.z*scalar }; + + return result; +} + +// Multiply vector by vector +RMAPI Vector3 Vector3Multiply(Vector3 v1, Vector3 v2) +{ + Vector3 result = { v1.x*v2.x, v1.y*v2.y, v1.z*v2.z }; + + return result; +} + +// Calculate two vectors cross product +RMAPI Vector3 Vector3CrossProduct(Vector3 v1, Vector3 v2) +{ + Vector3 result = { v1.y*v2.z - v1.z*v2.y, v1.z*v2.x - v1.x*v2.z, v1.x*v2.y - v1.y*v2.x }; + + return result; +} + +// Calculate one vector perpendicular vector +RMAPI Vector3 Vector3Perpendicular(Vector3 v) +{ + Vector3 result = { 0 }; + + float min = fabsf(v.x); + Vector3 cardinalAxis = {1.0f, 0.0f, 0.0f}; + + if (fabsf(v.y) < min) + { + min = fabsf(v.y); + Vector3 tmp = {0.0f, 1.0f, 0.0f}; + cardinalAxis = tmp; + } + + if (fabsf(v.z) < min) + { + Vector3 tmp = {0.0f, 0.0f, 1.0f}; + cardinalAxis = tmp; + } + + // Cross product between vectors + result.x = v.y*cardinalAxis.z - v.z*cardinalAxis.y; + result.y = v.z*cardinalAxis.x - v.x*cardinalAxis.z; + result.z = v.x*cardinalAxis.y - v.y*cardinalAxis.x; + + return result; +} + +// Calculate vector length +RMAPI float Vector3Length(const Vector3 v) +{ + float result = sqrtf(v.x*v.x + v.y*v.y + v.z*v.z); + + return result; +} + +// Calculate vector square length +RMAPI float Vector3LengthSqr(const Vector3 v) +{ + float result = v.x*v.x + v.y*v.y + v.z*v.z; + + return result; +} + +// Calculate two vectors dot product +RMAPI float Vector3DotProduct(Vector3 v1, Vector3 v2) +{ + float result = (v1.x*v2.x + v1.y*v2.y + v1.z*v2.z); + + return result; +} + +// Calculate distance between two vectors +RMAPI float Vector3Distance(Vector3 v1, Vector3 v2) +{ + float result = 0.0f; + + float dx = v2.x - v1.x; + float dy = v2.y - v1.y; + float dz = v2.z - v1.z; + result = sqrtf(dx*dx + dy*dy + dz*dz); + + return result; +} + +// Calculate square distance between two vectors +RMAPI float Vector3DistanceSqr(Vector3 v1, Vector3 v2) +{ + float result = 0.0f; + + float dx = v2.x - v1.x; + float dy = v2.y - v1.y; + float dz = v2.z - v1.z; + result = dx*dx + dy*dy + dz*dz; + + return result; +} + +// Calculate angle between two vectors +RMAPI float Vector3Angle(Vector3 v1, Vector3 v2) +{ + float result = 0.0f; + + Vector3 cross = { v1.y*v2.z - v1.z*v2.y, v1.z*v2.x - v1.x*v2.z, v1.x*v2.y - v1.y*v2.x }; + float len = sqrtf(cross.x*cross.x + cross.y*cross.y + cross.z*cross.z); + float dot = (v1.x*v2.x + v1.y*v2.y + v1.z*v2.z); + result = atan2f(len, dot); + + return result; +} + +// Negate provided vector (invert direction) +RMAPI Vector3 Vector3Negate(Vector3 v) +{ + Vector3 result = { -v.x, -v.y, -v.z }; + + return result; +} + +// Divide vector by vector +RMAPI Vector3 Vector3Divide(Vector3 v1, Vector3 v2) +{ + Vector3 result = { v1.x/v2.x, v1.y/v2.y, v1.z/v2.z }; + + return result; +} + +// Normalize provided vector +RMAPI Vector3 Vector3Normalize(Vector3 v) +{ + Vector3 result = v; + + float length = sqrtf(v.x*v.x + v.y*v.y + v.z*v.z); + if (length != 0.0f) + { + float ilength = 1.0f/length; + + result.x *= ilength; + result.y *= ilength; + result.z *= ilength; + } + + return result; +} + +//Calculate the projection of the vector v1 on to v2 +RMAPI Vector3 Vector3Project(Vector3 v1, Vector3 v2) +{ + Vector3 result = { 0 }; + + float v1dv2 = (v1.x*v2.x + v1.y*v2.y + v1.z*v2.z); + float v2dv2 = (v2.x*v2.x + v2.y*v2.y + v2.z*v2.z); + + float mag = v1dv2/v2dv2; + + result.x = v2.x*mag; + result.y = v2.y*mag; + result.z = v2.z*mag; + + return result; +} + +//Calculate the rejection of the vector v1 on to v2 +RMAPI Vector3 Vector3Reject(Vector3 v1, Vector3 v2) +{ + Vector3 result = { 0 }; + + float v1dv2 = (v1.x*v2.x + v1.y*v2.y + v1.z*v2.z); + float v2dv2 = (v2.x*v2.x + v2.y*v2.y + v2.z*v2.z); + + float mag = v1dv2/v2dv2; + + result.x = v1.x - (v2.x*mag); + result.y = v1.y - (v2.y*mag); + result.z = v1.z - (v2.z*mag); + + return result; +} + +// Orthonormalize provided vectors +// Makes vectors normalized and orthogonal to each other +// Gram-Schmidt function implementation +RMAPI void Vector3OrthoNormalize(Vector3 *v1, Vector3 *v2) +{ + float length = 0.0f; + float ilength = 0.0f; + + // Vector3Normalize(*v1); + Vector3 v = *v1; + length = sqrtf(v.x*v.x + v.y*v.y + v.z*v.z); + if (length == 0.0f) length = 1.0f; + ilength = 1.0f/length; + v1->x *= ilength; + v1->y *= ilength; + v1->z *= ilength; + + // Vector3CrossProduct(*v1, *v2) + Vector3 vn1 = { v1->y*v2->z - v1->z*v2->y, v1->z*v2->x - v1->x*v2->z, v1->x*v2->y - v1->y*v2->x }; + + // Vector3Normalize(vn1); + v = vn1; + length = sqrtf(v.x*v.x + v.y*v.y + v.z*v.z); + if (length == 0.0f) length = 1.0f; + ilength = 1.0f/length; + vn1.x *= ilength; + vn1.y *= ilength; + vn1.z *= ilength; + + // Vector3CrossProduct(vn1, *v1) + Vector3 vn2 = { vn1.y*v1->z - vn1.z*v1->y, vn1.z*v1->x - vn1.x*v1->z, vn1.x*v1->y - vn1.y*v1->x }; + + *v2 = vn2; +} + +// Transforms a Vector3 by a given Matrix +RMAPI Vector3 Vector3Transform(Vector3 v, Matrix mat) +{ + Vector3 result = { 0 }; + + float x = v.x; + float y = v.y; + float z = v.z; + + result.x = mat.m0*x + mat.m4*y + mat.m8*z + mat.m12; + result.y = mat.m1*x + mat.m5*y + mat.m9*z + mat.m13; + result.z = mat.m2*x + mat.m6*y + mat.m10*z + mat.m14; + + return result; +} + +// Transform a vector by quaternion rotation +RMAPI Vector3 Vector3RotateByQuaternion(Vector3 v, Quaternion q) +{ + Vector3 result = { 0 }; + + result.x = v.x*(q.x*q.x + q.w*q.w - q.y*q.y - q.z*q.z) + v.y*(2*q.x*q.y - 2*q.w*q.z) + v.z*(2*q.x*q.z + 2*q.w*q.y); + result.y = v.x*(2*q.w*q.z + 2*q.x*q.y) + v.y*(q.w*q.w - q.x*q.x + q.y*q.y - q.z*q.z) + v.z*(-2*q.w*q.x + 2*q.y*q.z); + result.z = v.x*(-2*q.w*q.y + 2*q.x*q.z) + v.y*(2*q.w*q.x + 2*q.y*q.z)+ v.z*(q.w*q.w - q.x*q.x - q.y*q.y + q.z*q.z); + + return result; +} + +// Rotates a vector around an axis +RMAPI Vector3 Vector3RotateByAxisAngle(Vector3 v, Vector3 axis, float angle) +{ + // Using Euler-Rodrigues Formula + // Ref.: https://en.wikipedia.org/w/index.php?title=Euler%E2%80%93Rodrigues_formula + + Vector3 result = v; + + // Vector3Normalize(axis); + float length = sqrtf(axis.x*axis.x + axis.y*axis.y + axis.z*axis.z); + if (length == 0.0f) length = 1.0f; + float ilength = 1.0f/length; + axis.x *= ilength; + axis.y *= ilength; + axis.z *= ilength; + + angle /= 2.0f; + float a = sinf(angle); + float b = axis.x*a; + float c = axis.y*a; + float d = axis.z*a; + a = cosf(angle); + Vector3 w = { b, c, d }; + + // Vector3CrossProduct(w, v) + Vector3 wv = { w.y*v.z - w.z*v.y, w.z*v.x - w.x*v.z, w.x*v.y - w.y*v.x }; + + // Vector3CrossProduct(w, wv) + Vector3 wwv = { w.y*wv.z - w.z*wv.y, w.z*wv.x - w.x*wv.z, w.x*wv.y - w.y*wv.x }; + + // Vector3Scale(wv, 2*a) + a *= 2; + wv.x *= a; + wv.y *= a; + wv.z *= a; + + // Vector3Scale(wwv, 2) + wwv.x *= 2; + wwv.y *= 2; + wwv.z *= 2; + + result.x += wv.x; + result.y += wv.y; + result.z += wv.z; + + result.x += wwv.x; + result.y += wwv.y; + result.z += wwv.z; + + return result; +} + +// Move Vector towards target +RMAPI Vector3 Vector3MoveTowards(Vector3 v, Vector3 target, float maxDistance) +{ + Vector3 result = { 0 }; + + float dx = target.x - v.x; + float dy = target.y - v.y; + float dz = target.z - v.z; + float value = (dx*dx) + (dy*dy) + (dz*dz); + + if ((value == 0) || ((maxDistance >= 0) && (value <= maxDistance*maxDistance))) return target; + + float dist = sqrtf(value); + + result.x = v.x + dx/dist*maxDistance; + result.y = v.y + dy/dist*maxDistance; + result.z = v.z + dz/dist*maxDistance; + + return result; +} + +// Calculate linear interpolation between two vectors +RMAPI Vector3 Vector3Lerp(Vector3 v1, Vector3 v2, float amount) +{ + Vector3 result = { 0 }; + + result.x = v1.x + amount*(v2.x - v1.x); + result.y = v1.y + amount*(v2.y - v1.y); + result.z = v1.z + amount*(v2.z - v1.z); + + return result; +} + +// Calculate cubic hermite interpolation between two vectors and their tangents +// as described in the GLTF 2.0 specification: https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#interpolation-cubic +RMAPI Vector3 Vector3CubicHermite(Vector3 v1, Vector3 tangent1, Vector3 v2, Vector3 tangent2, float amount) +{ + Vector3 result = { 0 }; + + float amountPow2 = amount*amount; + float amountPow3 = amount*amount*amount; + + result.x = (2*amountPow3 - 3*amountPow2 + 1)*v1.x + (amountPow3 - 2*amountPow2 + amount)*tangent1.x + (-2*amountPow3 + 3*amountPow2)*v2.x + (amountPow3 - amountPow2)*tangent2.x; + result.y = (2*amountPow3 - 3*amountPow2 + 1)*v1.y + (amountPow3 - 2*amountPow2 + amount)*tangent1.y + (-2*amountPow3 + 3*amountPow2)*v2.y + (amountPow3 - amountPow2)*tangent2.y; + result.z = (2*amountPow3 - 3*amountPow2 + 1)*v1.z + (amountPow3 - 2*amountPow2 + amount)*tangent1.z + (-2*amountPow3 + 3*amountPow2)*v2.z + (amountPow3 - amountPow2)*tangent2.z; + + return result; +} + +// Calculate reflected vector to normal +RMAPI Vector3 Vector3Reflect(Vector3 v, Vector3 normal) +{ + Vector3 result = { 0 }; + + // I is the original vector + // N is the normal of the incident plane + // R = I - (2*N*(DotProduct[I, N])) + + float dotProduct = (v.x*normal.x + v.y*normal.y + v.z*normal.z); + + result.x = v.x - (2.0f*normal.x)*dotProduct; + result.y = v.y - (2.0f*normal.y)*dotProduct; + result.z = v.z - (2.0f*normal.z)*dotProduct; + + return result; +} + +// Get min value for each pair of components +RMAPI Vector3 Vector3Min(Vector3 v1, Vector3 v2) +{ + Vector3 result = { 0 }; + + result.x = fminf(v1.x, v2.x); + result.y = fminf(v1.y, v2.y); + result.z = fminf(v1.z, v2.z); + + return result; +} + +// Get max value for each pair of components +RMAPI Vector3 Vector3Max(Vector3 v1, Vector3 v2) +{ + Vector3 result = { 0 }; + + result.x = fmaxf(v1.x, v2.x); + result.y = fmaxf(v1.y, v2.y); + result.z = fmaxf(v1.z, v2.z); + + return result; +} + +// Compute barycenter coordinates (u, v, w) for point p with respect to triangle (a, b, c) +// NOTE: Assumes P is on the plane of the triangle +RMAPI Vector3 Vector3Barycenter(Vector3 p, Vector3 a, Vector3 b, Vector3 c) +{ + Vector3 result = { 0 }; + + Vector3 v0 = { b.x - a.x, b.y - a.y, b.z - a.z }; // Vector3Subtract(b, a) + Vector3 v1 = { c.x - a.x, c.y - a.y, c.z - a.z }; // Vector3Subtract(c, a) + Vector3 v2 = { p.x - a.x, p.y - a.y, p.z - a.z }; // Vector3Subtract(p, a) + float d00 = (v0.x*v0.x + v0.y*v0.y + v0.z*v0.z); // Vector3DotProduct(v0, v0) + float d01 = (v0.x*v1.x + v0.y*v1.y + v0.z*v1.z); // Vector3DotProduct(v0, v1) + float d11 = (v1.x*v1.x + v1.y*v1.y + v1.z*v1.z); // Vector3DotProduct(v1, v1) + float d20 = (v2.x*v0.x + v2.y*v0.y + v2.z*v0.z); // Vector3DotProduct(v2, v0) + float d21 = (v2.x*v1.x + v2.y*v1.y + v2.z*v1.z); // Vector3DotProduct(v2, v1) + + float denom = d00*d11 - d01*d01; + + result.y = (d11*d20 - d01*d21)/denom; + result.z = (d00*d21 - d01*d20)/denom; + result.x = 1.0f - (result.z + result.y); + + return result; +} + +// Projects a Vector3 from screen space into object space +// NOTE: We are avoiding calling other raymath functions despite available +RMAPI Vector3 Vector3Unproject(Vector3 source, Matrix projection, Matrix view) +{ + Vector3 result = { 0 }; + + // Calculate unprojected matrix (multiply view matrix by projection matrix) and invert it + Matrix matViewProj = { // MatrixMultiply(view, projection); + view.m0*projection.m0 + view.m1*projection.m4 + view.m2*projection.m8 + view.m3*projection.m12, + view.m0*projection.m1 + view.m1*projection.m5 + view.m2*projection.m9 + view.m3*projection.m13, + view.m0*projection.m2 + view.m1*projection.m6 + view.m2*projection.m10 + view.m3*projection.m14, + view.m0*projection.m3 + view.m1*projection.m7 + view.m2*projection.m11 + view.m3*projection.m15, + view.m4*projection.m0 + view.m5*projection.m4 + view.m6*projection.m8 + view.m7*projection.m12, + view.m4*projection.m1 + view.m5*projection.m5 + view.m6*projection.m9 + view.m7*projection.m13, + view.m4*projection.m2 + view.m5*projection.m6 + view.m6*projection.m10 + view.m7*projection.m14, + view.m4*projection.m3 + view.m5*projection.m7 + view.m6*projection.m11 + view.m7*projection.m15, + view.m8*projection.m0 + view.m9*projection.m4 + view.m10*projection.m8 + view.m11*projection.m12, + view.m8*projection.m1 + view.m9*projection.m5 + view.m10*projection.m9 + view.m11*projection.m13, + view.m8*projection.m2 + view.m9*projection.m6 + view.m10*projection.m10 + view.m11*projection.m14, + view.m8*projection.m3 + view.m9*projection.m7 + view.m10*projection.m11 + view.m11*projection.m15, + view.m12*projection.m0 + view.m13*projection.m4 + view.m14*projection.m8 + view.m15*projection.m12, + view.m12*projection.m1 + view.m13*projection.m5 + view.m14*projection.m9 + view.m15*projection.m13, + view.m12*projection.m2 + view.m13*projection.m6 + view.m14*projection.m10 + view.m15*projection.m14, + view.m12*projection.m3 + view.m13*projection.m7 + view.m14*projection.m11 + view.m15*projection.m15 }; + + // Calculate inverted matrix -> MatrixInvert(matViewProj); + // Cache the matrix values (speed optimization) + float a00 = matViewProj.m0, a01 = matViewProj.m1, a02 = matViewProj.m2, a03 = matViewProj.m3; + float a10 = matViewProj.m4, a11 = matViewProj.m5, a12 = matViewProj.m6, a13 = matViewProj.m7; + float a20 = matViewProj.m8, a21 = matViewProj.m9, a22 = matViewProj.m10, a23 = matViewProj.m11; + float a30 = matViewProj.m12, a31 = matViewProj.m13, a32 = matViewProj.m14, a33 = matViewProj.m15; + + float b00 = a00*a11 - a01*a10; + float b01 = a00*a12 - a02*a10; + float b02 = a00*a13 - a03*a10; + float b03 = a01*a12 - a02*a11; + float b04 = a01*a13 - a03*a11; + float b05 = a02*a13 - a03*a12; + float b06 = a20*a31 - a21*a30; + float b07 = a20*a32 - a22*a30; + float b08 = a20*a33 - a23*a30; + float b09 = a21*a32 - a22*a31; + float b10 = a21*a33 - a23*a31; + float b11 = a22*a33 - a23*a32; + + // Calculate the invert determinant (inlined to avoid double-caching) + float invDet = 1.0f/(b00*b11 - b01*b10 + b02*b09 + b03*b08 - b04*b07 + b05*b06); + + Matrix matViewProjInv = { + (a11*b11 - a12*b10 + a13*b09)*invDet, + (-a01*b11 + a02*b10 - a03*b09)*invDet, + (a31*b05 - a32*b04 + a33*b03)*invDet, + (-a21*b05 + a22*b04 - a23*b03)*invDet, + (-a10*b11 + a12*b08 - a13*b07)*invDet, + (a00*b11 - a02*b08 + a03*b07)*invDet, + (-a30*b05 + a32*b02 - a33*b01)*invDet, + (a20*b05 - a22*b02 + a23*b01)*invDet, + (a10*b10 - a11*b08 + a13*b06)*invDet, + (-a00*b10 + a01*b08 - a03*b06)*invDet, + (a30*b04 - a31*b02 + a33*b00)*invDet, + (-a20*b04 + a21*b02 - a23*b00)*invDet, + (-a10*b09 + a11*b07 - a12*b06)*invDet, + (a00*b09 - a01*b07 + a02*b06)*invDet, + (-a30*b03 + a31*b01 - a32*b00)*invDet, + (a20*b03 - a21*b01 + a22*b00)*invDet }; + + // Create quaternion from source point + Quaternion quat = { source.x, source.y, source.z, 1.0f }; + + // Multiply quat point by unprojecte matrix + Quaternion qtransformed = { // QuaternionTransform(quat, matViewProjInv) + matViewProjInv.m0*quat.x + matViewProjInv.m4*quat.y + matViewProjInv.m8*quat.z + matViewProjInv.m12*quat.w, + matViewProjInv.m1*quat.x + matViewProjInv.m5*quat.y + matViewProjInv.m9*quat.z + matViewProjInv.m13*quat.w, + matViewProjInv.m2*quat.x + matViewProjInv.m6*quat.y + matViewProjInv.m10*quat.z + matViewProjInv.m14*quat.w, + matViewProjInv.m3*quat.x + matViewProjInv.m7*quat.y + matViewProjInv.m11*quat.z + matViewProjInv.m15*quat.w }; + + // Normalized world points in vectors + result.x = qtransformed.x/qtransformed.w; + result.y = qtransformed.y/qtransformed.w; + result.z = qtransformed.z/qtransformed.w; + + return result; +} + +// Get Vector3 as float array +RMAPI float3 Vector3ToFloatV(Vector3 v) +{ + float3 buffer = { 0 }; + + buffer.v[0] = v.x; + buffer.v[1] = v.y; + buffer.v[2] = v.z; + + return buffer; +} + +// Invert the given vector +RMAPI Vector3 Vector3Invert(Vector3 v) +{ + Vector3 result = { 1.0f/v.x, 1.0f/v.y, 1.0f/v.z }; + + return result; +} + +// Clamp the components of the vector between +// min and max values specified by the given vectors +RMAPI Vector3 Vector3Clamp(Vector3 v, Vector3 min, Vector3 max) +{ + Vector3 result = { 0 }; + + result.x = fminf(max.x, fmaxf(min.x, v.x)); + result.y = fminf(max.y, fmaxf(min.y, v.y)); + result.z = fminf(max.z, fmaxf(min.z, v.z)); + + return result; +} + +// Clamp the magnitude of the vector between two values +RMAPI Vector3 Vector3ClampValue(Vector3 v, float min, float max) +{ + Vector3 result = v; + + float length = (v.x*v.x) + (v.y*v.y) + (v.z*v.z); + if (length > 0.0f) + { + length = sqrtf(length); + + float scale = 1; // By default, 1 as the neutral element. + if (length < min) + { + scale = min/length; + } + else if (length > max) + { + scale = max/length; + } + + result.x = v.x*scale; + result.y = v.y*scale; + result.z = v.z*scale; + } + + return result; +} + +// Check whether two given vectors are almost equal +RMAPI int Vector3Equals(Vector3 p, Vector3 q) +{ +#if !defined(EPSILON) + #define EPSILON 0.000001f +#endif + + int result = ((fabsf(p.x - q.x)) <= (EPSILON*fmaxf(1.0f, fmaxf(fabsf(p.x), fabsf(q.x))))) && + ((fabsf(p.y - q.y)) <= (EPSILON*fmaxf(1.0f, fmaxf(fabsf(p.y), fabsf(q.y))))) && + ((fabsf(p.z - q.z)) <= (EPSILON*fmaxf(1.0f, fmaxf(fabsf(p.z), fabsf(q.z))))); + + return result; +} + +// Compute the direction of a refracted ray +// v: normalized direction of the incoming ray +// n: normalized normal vector of the interface of two optical media +// r: ratio of the refractive index of the medium from where the ray comes +// to the refractive index of the medium on the other side of the surface +RMAPI Vector3 Vector3Refract(Vector3 v, Vector3 n, float r) +{ + Vector3 result = { 0 }; + + float dot = v.x*n.x + v.y*n.y + v.z*n.z; + float d = 1.0f - r*r*(1.0f - dot*dot); + + if (d >= 0.0f) + { + d = sqrtf(d); + v.x = r*v.x - (r*dot + d)*n.x; + v.y = r*v.y - (r*dot + d)*n.y; + v.z = r*v.z - (r*dot + d)*n.z; + + result = v; + } + + return result; +} + + +//---------------------------------------------------------------------------------- +// Module Functions Definition - Vector4 math +//---------------------------------------------------------------------------------- + +RMAPI Vector4 Vector4Zero(void) +{ + Vector4 result = { 0.0f, 0.0f, 0.0f, 0.0f }; + return result; +} + +RMAPI Vector4 Vector4One(void) +{ + Vector4 result = { 1.0f, 1.0f, 1.0f, 1.0f }; + return result; +} + +RMAPI Vector4 Vector4Add(Vector4 v1, Vector4 v2) +{ + Vector4 result = { + v1.x + v2.x, + v1.y + v2.y, + v1.z + v2.z, + v1.w + v2.w + }; + return result; +} + +RMAPI Vector4 Vector4AddValue(Vector4 v, float add) +{ + Vector4 result = { + v.x + add, + v.y + add, + v.z + add, + v.w + add + }; + return result; +} + +RMAPI Vector4 Vector4Subtract(Vector4 v1, Vector4 v2) +{ + Vector4 result = { + v1.x - v2.x, + v1.y - v2.y, + v1.z - v2.z, + v1.w - v2.w + }; + return result; +} + +RMAPI Vector4 Vector4SubtractValue(Vector4 v, float add) +{ + Vector4 result = { + v.x - add, + v.y - add, + v.z - add, + v.w - add + }; + return result; +} + +RMAPI float Vector4Length(Vector4 v) +{ + float result = sqrtf((v.x*v.x) + (v.y*v.y) + (v.z*v.z) + (v.w*v.w)); + return result; +} + +RMAPI float Vector4LengthSqr(Vector4 v) +{ + float result = (v.x*v.x) + (v.y*v.y) + (v.z*v.z) + (v.w*v.w); + return result; +} + +RMAPI float Vector4DotProduct(Vector4 v1, Vector4 v2) +{ + float result = (v1.x*v2.x + v1.y*v2.y + v1.z*v2.z + v1.w*v2.w); + return result; +} + +// Calculate distance between two vectors +RMAPI float Vector4Distance(Vector4 v1, Vector4 v2) +{ + float result = sqrtf( + (v1.x - v2.x)*(v1.x - v2.x) + (v1.y - v2.y)*(v1.y - v2.y) + + (v1.z - v2.z)*(v1.z - v2.z) + (v1.w - v2.w)*(v1.w - v2.w)); + return result; +} + +// Calculate square distance between two vectors +RMAPI float Vector4DistanceSqr(Vector4 v1, Vector4 v2) +{ + float result = + (v1.x - v2.x)*(v1.x - v2.x) + (v1.y - v2.y)*(v1.y - v2.y) + + (v1.z - v2.z)*(v1.z - v2.z) + (v1.w - v2.w)*(v1.w - v2.w); + + return result; +} + +RMAPI Vector4 Vector4Scale(Vector4 v, float scale) +{ + Vector4 result = { v.x*scale, v.y*scale, v.z*scale, v.w*scale }; + return result; +} + +// Multiply vector by vector +RMAPI Vector4 Vector4Multiply(Vector4 v1, Vector4 v2) +{ + Vector4 result = { v1.x*v2.x, v1.y*v2.y, v1.z*v2.z, v1.w*v2.w }; + return result; +} + +// Negate vector +RMAPI Vector4 Vector4Negate(Vector4 v) +{ + Vector4 result = { -v.x, -v.y, -v.z, -v.w }; + return result; +} + +// Divide vector by vector +RMAPI Vector4 Vector4Divide(Vector4 v1, Vector4 v2) +{ + Vector4 result = { v1.x/v2.x, v1.y/v2.y, v1.z/v2.z, v1.w/v2.w }; + return result; +} + +// Normalize provided vector +RMAPI Vector4 Vector4Normalize(Vector4 v) +{ + Vector4 result = { 0 }; + float length = sqrtf((v.x*v.x) + (v.y*v.y) + (v.z*v.z) + (v.w*v.w)); + + if (length > 0) + { + float ilength = 1.0f/length; + result.x = v.x*ilength; + result.y = v.y*ilength; + result.z = v.z*ilength; + result.w = v.w*ilength; + } + + return result; +} + +// Get min value for each pair of components +RMAPI Vector4 Vector4Min(Vector4 v1, Vector4 v2) +{ + Vector4 result = { 0 }; + + result.x = fminf(v1.x, v2.x); + result.y = fminf(v1.y, v2.y); + result.z = fminf(v1.z, v2.z); + result.w = fminf(v1.w, v2.w); + + return result; +} + +// Get max value for each pair of components +RMAPI Vector4 Vector4Max(Vector4 v1, Vector4 v2) +{ + Vector4 result = { 0 }; + + result.x = fmaxf(v1.x, v2.x); + result.y = fmaxf(v1.y, v2.y); + result.z = fmaxf(v1.z, v2.z); + result.w = fmaxf(v1.w, v2.w); + + return result; +} + +// Calculate linear interpolation between two vectors +RMAPI Vector4 Vector4Lerp(Vector4 v1, Vector4 v2, float amount) +{ + Vector4 result = { 0 }; + + result.x = v1.x + amount*(v2.x - v1.x); + result.y = v1.y + amount*(v2.y - v1.y); + result.z = v1.z + amount*(v2.z - v1.z); + result.w = v1.w + amount*(v2.w - v1.w); + + return result; +} + +// Move Vector towards target +RMAPI Vector4 Vector4MoveTowards(Vector4 v, Vector4 target, float maxDistance) +{ + Vector4 result = { 0 }; + + float dx = target.x - v.x; + float dy = target.y - v.y; + float dz = target.z - v.z; + float dw = target.w - v.w; + float value = (dx*dx) + (dy*dy) + (dz*dz) + (dw*dw); + + if ((value == 0) || ((maxDistance >= 0) && (value <= maxDistance*maxDistance))) return target; + + float dist = sqrtf(value); + + result.x = v.x + dx/dist*maxDistance; + result.y = v.y + dy/dist*maxDistance; + result.z = v.z + dz/dist*maxDistance; + result.w = v.w + dw/dist*maxDistance; + + return result; +} + +// Invert the given vector +RMAPI Vector4 Vector4Invert(Vector4 v) +{ + Vector4 result = { 1.0f/v.x, 1.0f/v.y, 1.0f/v.z, 1.0f/v.w }; + return result; +} + +// Check whether two given vectors are almost equal +RMAPI int Vector4Equals(Vector4 p, Vector4 q) +{ +#if !defined(EPSILON) + #define EPSILON 0.000001f +#endif + + int result = ((fabsf(p.x - q.x)) <= (EPSILON*fmaxf(1.0f, fmaxf(fabsf(p.x), fabsf(q.x))))) && + ((fabsf(p.y - q.y)) <= (EPSILON*fmaxf(1.0f, fmaxf(fabsf(p.y), fabsf(q.y))))) && + ((fabsf(p.z - q.z)) <= (EPSILON*fmaxf(1.0f, fmaxf(fabsf(p.z), fabsf(q.z))))) && + ((fabsf(p.w - q.w)) <= (EPSILON*fmaxf(1.0f, fmaxf(fabsf(p.w), fabsf(q.w))))); + return result; +} + + +//---------------------------------------------------------------------------------- +// Module Functions Definition - Matrix math +//---------------------------------------------------------------------------------- + +// Compute matrix determinant +RMAPI float MatrixDeterminant(Matrix mat) +{ + float result = 0.0f; + + // Cache the matrix values (speed optimization) + float a00 = mat.m0, a01 = mat.m1, a02 = mat.m2, a03 = mat.m3; + float a10 = mat.m4, a11 = mat.m5, a12 = mat.m6, a13 = mat.m7; + float a20 = mat.m8, a21 = mat.m9, a22 = mat.m10, a23 = mat.m11; + float a30 = mat.m12, a31 = mat.m13, a32 = mat.m14, a33 = mat.m15; + + result = a30*a21*a12*a03 - a20*a31*a12*a03 - a30*a11*a22*a03 + a10*a31*a22*a03 + + a20*a11*a32*a03 - a10*a21*a32*a03 - a30*a21*a02*a13 + a20*a31*a02*a13 + + a30*a01*a22*a13 - a00*a31*a22*a13 - a20*a01*a32*a13 + a00*a21*a32*a13 + + a30*a11*a02*a23 - a10*a31*a02*a23 - a30*a01*a12*a23 + a00*a31*a12*a23 + + a10*a01*a32*a23 - a00*a11*a32*a23 - a20*a11*a02*a33 + a10*a21*a02*a33 + + a20*a01*a12*a33 - a00*a21*a12*a33 - a10*a01*a22*a33 + a00*a11*a22*a33; + + return result; +} + +// Get the trace of the matrix (sum of the values along the diagonal) +RMAPI float MatrixTrace(Matrix mat) +{ + float result = (mat.m0 + mat.m5 + mat.m10 + mat.m15); + + return result; +} + +// Transposes provided matrix +RMAPI Matrix MatrixTranspose(Matrix mat) +{ + Matrix result = { 0 }; + + result.m0 = mat.m0; + result.m1 = mat.m4; + result.m2 = mat.m8; + result.m3 = mat.m12; + result.m4 = mat.m1; + result.m5 = mat.m5; + result.m6 = mat.m9; + result.m7 = mat.m13; + result.m8 = mat.m2; + result.m9 = mat.m6; + result.m10 = mat.m10; + result.m11 = mat.m14; + result.m12 = mat.m3; + result.m13 = mat.m7; + result.m14 = mat.m11; + result.m15 = mat.m15; + + return result; +} + +// Invert provided matrix +RMAPI Matrix MatrixInvert(Matrix mat) +{ + Matrix result = { 0 }; + + // Cache the matrix values (speed optimization) + float a00 = mat.m0, a01 = mat.m1, a02 = mat.m2, a03 = mat.m3; + float a10 = mat.m4, a11 = mat.m5, a12 = mat.m6, a13 = mat.m7; + float a20 = mat.m8, a21 = mat.m9, a22 = mat.m10, a23 = mat.m11; + float a30 = mat.m12, a31 = mat.m13, a32 = mat.m14, a33 = mat.m15; + + float b00 = a00*a11 - a01*a10; + float b01 = a00*a12 - a02*a10; + float b02 = a00*a13 - a03*a10; + float b03 = a01*a12 - a02*a11; + float b04 = a01*a13 - a03*a11; + float b05 = a02*a13 - a03*a12; + float b06 = a20*a31 - a21*a30; + float b07 = a20*a32 - a22*a30; + float b08 = a20*a33 - a23*a30; + float b09 = a21*a32 - a22*a31; + float b10 = a21*a33 - a23*a31; + float b11 = a22*a33 - a23*a32; + + // Calculate the invert determinant (inlined to avoid double-caching) + float invDet = 1.0f/(b00*b11 - b01*b10 + b02*b09 + b03*b08 - b04*b07 + b05*b06); + + result.m0 = (a11*b11 - a12*b10 + a13*b09)*invDet; + result.m1 = (-a01*b11 + a02*b10 - a03*b09)*invDet; + result.m2 = (a31*b05 - a32*b04 + a33*b03)*invDet; + result.m3 = (-a21*b05 + a22*b04 - a23*b03)*invDet; + result.m4 = (-a10*b11 + a12*b08 - a13*b07)*invDet; + result.m5 = (a00*b11 - a02*b08 + a03*b07)*invDet; + result.m6 = (-a30*b05 + a32*b02 - a33*b01)*invDet; + result.m7 = (a20*b05 - a22*b02 + a23*b01)*invDet; + result.m8 = (a10*b10 - a11*b08 + a13*b06)*invDet; + result.m9 = (-a00*b10 + a01*b08 - a03*b06)*invDet; + result.m10 = (a30*b04 - a31*b02 + a33*b00)*invDet; + result.m11 = (-a20*b04 + a21*b02 - a23*b00)*invDet; + result.m12 = (-a10*b09 + a11*b07 - a12*b06)*invDet; + result.m13 = (a00*b09 - a01*b07 + a02*b06)*invDet; + result.m14 = (-a30*b03 + a31*b01 - a32*b00)*invDet; + result.m15 = (a20*b03 - a21*b01 + a22*b00)*invDet; + + return result; +} + +// Get identity matrix +RMAPI Matrix MatrixIdentity(void) +{ + Matrix result = { 1.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 1.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 1.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f }; + + return result; +} + +// Add two matrices +RMAPI Matrix MatrixAdd(Matrix left, Matrix right) +{ + Matrix result = { 0 }; + + result.m0 = left.m0 + right.m0; + result.m1 = left.m1 + right.m1; + result.m2 = left.m2 + right.m2; + result.m3 = left.m3 + right.m3; + result.m4 = left.m4 + right.m4; + result.m5 = left.m5 + right.m5; + result.m6 = left.m6 + right.m6; + result.m7 = left.m7 + right.m7; + result.m8 = left.m8 + right.m8; + result.m9 = left.m9 + right.m9; + result.m10 = left.m10 + right.m10; + result.m11 = left.m11 + right.m11; + result.m12 = left.m12 + right.m12; + result.m13 = left.m13 + right.m13; + result.m14 = left.m14 + right.m14; + result.m15 = left.m15 + right.m15; + + return result; +} + +// Subtract two matrices (left - right) +RMAPI Matrix MatrixSubtract(Matrix left, Matrix right) +{ + Matrix result = { 0 }; + + result.m0 = left.m0 - right.m0; + result.m1 = left.m1 - right.m1; + result.m2 = left.m2 - right.m2; + result.m3 = left.m3 - right.m3; + result.m4 = left.m4 - right.m4; + result.m5 = left.m5 - right.m5; + result.m6 = left.m6 - right.m6; + result.m7 = left.m7 - right.m7; + result.m8 = left.m8 - right.m8; + result.m9 = left.m9 - right.m9; + result.m10 = left.m10 - right.m10; + result.m11 = left.m11 - right.m11; + result.m12 = left.m12 - right.m12; + result.m13 = left.m13 - right.m13; + result.m14 = left.m14 - right.m14; + result.m15 = left.m15 - right.m15; + + return result; +} + +// Get two matrix multiplication +// NOTE: When multiplying matrices... the order matters! +RMAPI Matrix MatrixMultiply(Matrix left, Matrix right) +{ + Matrix result = { 0 }; + + result.m0 = left.m0*right.m0 + left.m1*right.m4 + left.m2*right.m8 + left.m3*right.m12; + result.m1 = left.m0*right.m1 + left.m1*right.m5 + left.m2*right.m9 + left.m3*right.m13; + result.m2 = left.m0*right.m2 + left.m1*right.m6 + left.m2*right.m10 + left.m3*right.m14; + result.m3 = left.m0*right.m3 + left.m1*right.m7 + left.m2*right.m11 + left.m3*right.m15; + result.m4 = left.m4*right.m0 + left.m5*right.m4 + left.m6*right.m8 + left.m7*right.m12; + result.m5 = left.m4*right.m1 + left.m5*right.m5 + left.m6*right.m9 + left.m7*right.m13; + result.m6 = left.m4*right.m2 + left.m5*right.m6 + left.m6*right.m10 + left.m7*right.m14; + result.m7 = left.m4*right.m3 + left.m5*right.m7 + left.m6*right.m11 + left.m7*right.m15; + result.m8 = left.m8*right.m0 + left.m9*right.m4 + left.m10*right.m8 + left.m11*right.m12; + result.m9 = left.m8*right.m1 + left.m9*right.m5 + left.m10*right.m9 + left.m11*right.m13; + result.m10 = left.m8*right.m2 + left.m9*right.m6 + left.m10*right.m10 + left.m11*right.m14; + result.m11 = left.m8*right.m3 + left.m9*right.m7 + left.m10*right.m11 + left.m11*right.m15; + result.m12 = left.m12*right.m0 + left.m13*right.m4 + left.m14*right.m8 + left.m15*right.m12; + result.m13 = left.m12*right.m1 + left.m13*right.m5 + left.m14*right.m9 + left.m15*right.m13; + result.m14 = left.m12*right.m2 + left.m13*right.m6 + left.m14*right.m10 + left.m15*right.m14; + result.m15 = left.m12*right.m3 + left.m13*right.m7 + left.m14*right.m11 + left.m15*right.m15; + + return result; +} + +// Get translation matrix +RMAPI Matrix MatrixTranslate(float x, float y, float z) +{ + Matrix result = { 1.0f, 0.0f, 0.0f, x, + 0.0f, 1.0f, 0.0f, y, + 0.0f, 0.0f, 1.0f, z, + 0.0f, 0.0f, 0.0f, 1.0f }; + + return result; +} + +// Create rotation matrix from axis and angle +// NOTE: Angle should be provided in radians +RMAPI Matrix MatrixRotate(Vector3 axis, float angle) +{ + Matrix result = { 0 }; + + float x = axis.x, y = axis.y, z = axis.z; + + float lengthSquared = x*x + y*y + z*z; + + if ((lengthSquared != 1.0f) && (lengthSquared != 0.0f)) + { + float ilength = 1.0f/sqrtf(lengthSquared); + x *= ilength; + y *= ilength; + z *= ilength; + } + + float sinres = sinf(angle); + float cosres = cosf(angle); + float t = 1.0f - cosres; + + result.m0 = x*x*t + cosres; + result.m1 = y*x*t + z*sinres; + result.m2 = z*x*t - y*sinres; + result.m3 = 0.0f; + + result.m4 = x*y*t - z*sinres; + result.m5 = y*y*t + cosres; + result.m6 = z*y*t + x*sinres; + result.m7 = 0.0f; + + result.m8 = x*z*t + y*sinres; + result.m9 = y*z*t - x*sinres; + result.m10 = z*z*t + cosres; + result.m11 = 0.0f; + + result.m12 = 0.0f; + result.m13 = 0.0f; + result.m14 = 0.0f; + result.m15 = 1.0f; + + return result; +} + +// Get x-rotation matrix +// NOTE: Angle must be provided in radians +RMAPI Matrix MatrixRotateX(float angle) +{ + Matrix result = { 1.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 1.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 1.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f }; // MatrixIdentity() + + float cosres = cosf(angle); + float sinres = sinf(angle); + + result.m5 = cosres; + result.m6 = sinres; + result.m9 = -sinres; + result.m10 = cosres; + + return result; +} + +// Get y-rotation matrix +// NOTE: Angle must be provided in radians +RMAPI Matrix MatrixRotateY(float angle) +{ + Matrix result = { 1.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 1.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 1.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f }; // MatrixIdentity() + + float cosres = cosf(angle); + float sinres = sinf(angle); + + result.m0 = cosres; + result.m2 = -sinres; + result.m8 = sinres; + result.m10 = cosres; + + return result; +} + +// Get z-rotation matrix +// NOTE: Angle must be provided in radians +RMAPI Matrix MatrixRotateZ(float angle) +{ + Matrix result = { 1.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 1.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 1.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f }; // MatrixIdentity() + + float cosres = cosf(angle); + float sinres = sinf(angle); + + result.m0 = cosres; + result.m1 = sinres; + result.m4 = -sinres; + result.m5 = cosres; + + return result; +} + + +// Get xyz-rotation matrix +// NOTE: Angle must be provided in radians +RMAPI Matrix MatrixRotateXYZ(Vector3 angle) +{ + Matrix result = { 1.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 1.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 1.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f }; // MatrixIdentity() + + float cosz = cosf(-angle.z); + float sinz = sinf(-angle.z); + float cosy = cosf(-angle.y); + float siny = sinf(-angle.y); + float cosx = cosf(-angle.x); + float sinx = sinf(-angle.x); + + result.m0 = cosz*cosy; + result.m1 = (cosz*siny*sinx) - (sinz*cosx); + result.m2 = (cosz*siny*cosx) + (sinz*sinx); + + result.m4 = sinz*cosy; + result.m5 = (sinz*siny*sinx) + (cosz*cosx); + result.m6 = (sinz*siny*cosx) - (cosz*sinx); + + result.m8 = -siny; + result.m9 = cosy*sinx; + result.m10= cosy*cosx; + + return result; +} + +// Get zyx-rotation matrix +// NOTE: Angle must be provided in radians +RMAPI Matrix MatrixRotateZYX(Vector3 angle) +{ + Matrix result = { 0 }; + + float cz = cosf(angle.z); + float sz = sinf(angle.z); + float cy = cosf(angle.y); + float sy = sinf(angle.y); + float cx = cosf(angle.x); + float sx = sinf(angle.x); + + result.m0 = cz*cy; + result.m4 = cz*sy*sx - cx*sz; + result.m8 = sz*sx + cz*cx*sy; + result.m12 = 0; + + result.m1 = cy*sz; + result.m5 = cz*cx + sz*sy*sx; + result.m9 = cx*sz*sy - cz*sx; + result.m13 = 0; + + result.m2 = -sy; + result.m6 = cy*sx; + result.m10 = cy*cx; + result.m14 = 0; + + result.m3 = 0; + result.m7 = 0; + result.m11 = 0; + result.m15 = 1; + + return result; +} + +// Get scaling matrix +RMAPI Matrix MatrixScale(float x, float y, float z) +{ + Matrix result = { x, 0.0f, 0.0f, 0.0f, + 0.0f, y, 0.0f, 0.0f, + 0.0f, 0.0f, z, 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f }; + + return result; +} + +// Get perspective projection matrix +RMAPI Matrix MatrixFrustum(double left, double right, double bottom, double top, double nearPlane, double farPlane) +{ + Matrix result = { 0 }; + + float rl = (float)(right - left); + float tb = (float)(top - bottom); + float fn = (float)(farPlane - nearPlane); + + result.m0 = ((float)nearPlane*2.0f)/rl; + result.m1 = 0.0f; + result.m2 = 0.0f; + result.m3 = 0.0f; + + result.m4 = 0.0f; + result.m5 = ((float)nearPlane*2.0f)/tb; + result.m6 = 0.0f; + result.m7 = 0.0f; + + result.m8 = ((float)right + (float)left)/rl; + result.m9 = ((float)top + (float)bottom)/tb; + result.m10 = -((float)farPlane + (float)nearPlane)/fn; + result.m11 = -1.0f; + + result.m12 = 0.0f; + result.m13 = 0.0f; + result.m14 = -((float)farPlane*(float)nearPlane*2.0f)/fn; + result.m15 = 0.0f; + + return result; +} + +// Get perspective projection matrix +// NOTE: Fovy angle must be provided in radians +RMAPI Matrix MatrixPerspective(double fovY, double aspect, double nearPlane, double farPlane) +{ + Matrix result = { 0 }; + + double top = nearPlane*tan(fovY*0.5); + double bottom = -top; + double right = top*aspect; + double left = -right; + + // MatrixFrustum(-right, right, -top, top, near, far); + float rl = (float)(right - left); + float tb = (float)(top - bottom); + float fn = (float)(farPlane - nearPlane); + + result.m0 = ((float)nearPlane*2.0f)/rl; + result.m5 = ((float)nearPlane*2.0f)/tb; + result.m8 = ((float)right + (float)left)/rl; + result.m9 = ((float)top + (float)bottom)/tb; + result.m10 = -((float)farPlane + (float)nearPlane)/fn; + result.m11 = -1.0f; + result.m14 = -((float)farPlane*(float)nearPlane*2.0f)/fn; + + return result; +} + +// Get orthographic projection matrix +RMAPI Matrix MatrixOrtho(double left, double right, double bottom, double top, double nearPlane, double farPlane) +{ + Matrix result = { 0 }; + + float rl = (float)(right - left); + float tb = (float)(top - bottom); + float fn = (float)(farPlane - nearPlane); + + result.m0 = 2.0f/rl; + result.m1 = 0.0f; + result.m2 = 0.0f; + result.m3 = 0.0f; + result.m4 = 0.0f; + result.m5 = 2.0f/tb; + result.m6 = 0.0f; + result.m7 = 0.0f; + result.m8 = 0.0f; + result.m9 = 0.0f; + result.m10 = -2.0f/fn; + result.m11 = 0.0f; + result.m12 = -((float)left + (float)right)/rl; + result.m13 = -((float)top + (float)bottom)/tb; + result.m14 = -((float)farPlane + (float)nearPlane)/fn; + result.m15 = 1.0f; + + return result; +} + +// Get camera look-at matrix (view matrix) +RMAPI Matrix MatrixLookAt(Vector3 eye, Vector3 target, Vector3 up) +{ + Matrix result = { 0 }; + + float length = 0.0f; + float ilength = 0.0f; + + // Vector3Subtract(eye, target) + Vector3 vz = { eye.x - target.x, eye.y - target.y, eye.z - target.z }; + + // Vector3Normalize(vz) + Vector3 v = vz; + length = sqrtf(v.x*v.x + v.y*v.y + v.z*v.z); + if (length == 0.0f) length = 1.0f; + ilength = 1.0f/length; + vz.x *= ilength; + vz.y *= ilength; + vz.z *= ilength; + + // Vector3CrossProduct(up, vz) + Vector3 vx = { up.y*vz.z - up.z*vz.y, up.z*vz.x - up.x*vz.z, up.x*vz.y - up.y*vz.x }; + + // Vector3Normalize(x) + v = vx; + length = sqrtf(v.x*v.x + v.y*v.y + v.z*v.z); + if (length == 0.0f) length = 1.0f; + ilength = 1.0f/length; + vx.x *= ilength; + vx.y *= ilength; + vx.z *= ilength; + + // Vector3CrossProduct(vz, vx) + Vector3 vy = { vz.y*vx.z - vz.z*vx.y, vz.z*vx.x - vz.x*vx.z, vz.x*vx.y - vz.y*vx.x }; + + result.m0 = vx.x; + result.m1 = vy.x; + result.m2 = vz.x; + result.m3 = 0.0f; + result.m4 = vx.y; + result.m5 = vy.y; + result.m6 = vz.y; + result.m7 = 0.0f; + result.m8 = vx.z; + result.m9 = vy.z; + result.m10 = vz.z; + result.m11 = 0.0f; + result.m12 = -(vx.x*eye.x + vx.y*eye.y + vx.z*eye.z); // Vector3DotProduct(vx, eye) + result.m13 = -(vy.x*eye.x + vy.y*eye.y + vy.z*eye.z); // Vector3DotProduct(vy, eye) + result.m14 = -(vz.x*eye.x + vz.y*eye.y + vz.z*eye.z); // Vector3DotProduct(vz, eye) + result.m15 = 1.0f; + + return result; +} + +// Get float array of matrix data +RMAPI float16 MatrixToFloatV(Matrix mat) +{ + float16 result = { 0 }; + + result.v[0] = mat.m0; + result.v[1] = mat.m1; + result.v[2] = mat.m2; + result.v[3] = mat.m3; + result.v[4] = mat.m4; + result.v[5] = mat.m5; + result.v[6] = mat.m6; + result.v[7] = mat.m7; + result.v[8] = mat.m8; + result.v[9] = mat.m9; + result.v[10] = mat.m10; + result.v[11] = mat.m11; + result.v[12] = mat.m12; + result.v[13] = mat.m13; + result.v[14] = mat.m14; + result.v[15] = mat.m15; + + return result; +} + +//---------------------------------------------------------------------------------- +// Module Functions Definition - Quaternion math +//---------------------------------------------------------------------------------- + +// Add two quaternions +RMAPI Quaternion QuaternionAdd(Quaternion q1, Quaternion q2) +{ + Quaternion result = {q1.x + q2.x, q1.y + q2.y, q1.z + q2.z, q1.w + q2.w}; + + return result; +} + +// Add quaternion and float value +RMAPI Quaternion QuaternionAddValue(Quaternion q, float add) +{ + Quaternion result = {q.x + add, q.y + add, q.z + add, q.w + add}; + + return result; +} + +// Subtract two quaternions +RMAPI Quaternion QuaternionSubtract(Quaternion q1, Quaternion q2) +{ + Quaternion result = {q1.x - q2.x, q1.y - q2.y, q1.z - q2.z, q1.w - q2.w}; + + return result; +} + +// Subtract quaternion and float value +RMAPI Quaternion QuaternionSubtractValue(Quaternion q, float sub) +{ + Quaternion result = {q.x - sub, q.y - sub, q.z - sub, q.w - sub}; + + return result; +} + +// Get identity quaternion +RMAPI Quaternion QuaternionIdentity(void) +{ + Quaternion result = { 0.0f, 0.0f, 0.0f, 1.0f }; + + return result; +} + +// Computes the length of a quaternion +RMAPI float QuaternionLength(Quaternion q) +{ + float result = sqrtf(q.x*q.x + q.y*q.y + q.z*q.z + q.w*q.w); + + return result; +} + +// Normalize provided quaternion +RMAPI Quaternion QuaternionNormalize(Quaternion q) +{ + Quaternion result = { 0 }; + + float length = sqrtf(q.x*q.x + q.y*q.y + q.z*q.z + q.w*q.w); + if (length == 0.0f) length = 1.0f; + float ilength = 1.0f/length; + + result.x = q.x*ilength; + result.y = q.y*ilength; + result.z = q.z*ilength; + result.w = q.w*ilength; + + return result; +} + +// Invert provided quaternion +RMAPI Quaternion QuaternionInvert(Quaternion q) +{ + Quaternion result = q; + + float lengthSq = q.x*q.x + q.y*q.y + q.z*q.z + q.w*q.w; + + if (lengthSq != 0.0f) + { + float invLength = 1.0f/lengthSq; + + result.x *= -invLength; + result.y *= -invLength; + result.z *= -invLength; + result.w *= invLength; + } + + return result; +} + +// Calculate two quaternion multiplication +RMAPI Quaternion QuaternionMultiply(Quaternion q1, Quaternion q2) +{ + Quaternion result = { 0 }; + + float qax = q1.x, qay = q1.y, qaz = q1.z, qaw = q1.w; + float qbx = q2.x, qby = q2.y, qbz = q2.z, qbw = q2.w; + + result.x = qax*qbw + qaw*qbx + qay*qbz - qaz*qby; + result.y = qay*qbw + qaw*qby + qaz*qbx - qax*qbz; + result.z = qaz*qbw + qaw*qbz + qax*qby - qay*qbx; + result.w = qaw*qbw - qax*qbx - qay*qby - qaz*qbz; + + return result; +} + +// Scale quaternion by float value +RMAPI Quaternion QuaternionScale(Quaternion q, float mul) +{ + Quaternion result = { 0 }; + + result.x = q.x*mul; + result.y = q.y*mul; + result.z = q.z*mul; + result.w = q.w*mul; + + return result; +} + +// Divide two quaternions +RMAPI Quaternion QuaternionDivide(Quaternion q1, Quaternion q2) +{ + Quaternion result = { q1.x/q2.x, q1.y/q2.y, q1.z/q2.z, q1.w/q2.w }; + + return result; +} + +// Calculate linear interpolation between two quaternions +RMAPI Quaternion QuaternionLerp(Quaternion q1, Quaternion q2, float amount) +{ + Quaternion result = { 0 }; + + result.x = q1.x + amount*(q2.x - q1.x); + result.y = q1.y + amount*(q2.y - q1.y); + result.z = q1.z + amount*(q2.z - q1.z); + result.w = q1.w + amount*(q2.w - q1.w); + + return result; +} + +// Calculate slerp-optimized interpolation between two quaternions +RMAPI Quaternion QuaternionNlerp(Quaternion q1, Quaternion q2, float amount) +{ + Quaternion result = { 0 }; + + // QuaternionLerp(q1, q2, amount) + result.x = q1.x + amount*(q2.x - q1.x); + result.y = q1.y + amount*(q2.y - q1.y); + result.z = q1.z + amount*(q2.z - q1.z); + result.w = q1.w + amount*(q2.w - q1.w); + + // QuaternionNormalize(q); + Quaternion q = result; + float length = sqrtf(q.x*q.x + q.y*q.y + q.z*q.z + q.w*q.w); + if (length == 0.0f) length = 1.0f; + float ilength = 1.0f/length; + + result.x = q.x*ilength; + result.y = q.y*ilength; + result.z = q.z*ilength; + result.w = q.w*ilength; + + return result; +} + +// Calculates spherical linear interpolation between two quaternions +RMAPI Quaternion QuaternionSlerp(Quaternion q1, Quaternion q2, float amount) +{ + Quaternion result = { 0 }; + +#if !defined(EPSILON) + #define EPSILON 0.000001f +#endif + + float cosHalfTheta = q1.x*q2.x + q1.y*q2.y + q1.z*q2.z + q1.w*q2.w; + + if (cosHalfTheta < 0) + { + q2.x = -q2.x; q2.y = -q2.y; q2.z = -q2.z; q2.w = -q2.w; + cosHalfTheta = -cosHalfTheta; + } + + if (fabsf(cosHalfTheta) >= 1.0f) result = q1; + else if (cosHalfTheta > 0.95f) result = QuaternionNlerp(q1, q2, amount); + else + { + float halfTheta = acosf(cosHalfTheta); + float sinHalfTheta = sqrtf(1.0f - cosHalfTheta*cosHalfTheta); + + if (fabsf(sinHalfTheta) < EPSILON) + { + result.x = (q1.x*0.5f + q2.x*0.5f); + result.y = (q1.y*0.5f + q2.y*0.5f); + result.z = (q1.z*0.5f + q2.z*0.5f); + result.w = (q1.w*0.5f + q2.w*0.5f); + } + else + { + float ratioA = sinf((1 - amount)*halfTheta)/sinHalfTheta; + float ratioB = sinf(amount*halfTheta)/sinHalfTheta; + + result.x = (q1.x*ratioA + q2.x*ratioB); + result.y = (q1.y*ratioA + q2.y*ratioB); + result.z = (q1.z*ratioA + q2.z*ratioB); + result.w = (q1.w*ratioA + q2.w*ratioB); + } + } + + return result; +} + +// Calculate quaternion cubic spline interpolation using Cubic Hermite Spline algorithm +// as described in the GLTF 2.0 specification: https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#interpolation-cubic +RMAPI Quaternion QuaternionCubicHermiteSpline(Quaternion q1, Quaternion outTangent1, Quaternion q2, Quaternion inTangent2, float t) +{ + float t2 = t*t; + float t3 = t2*t; + float h00 = 2*t3 - 3*t2 + 1; + float h10 = t3 - 2*t2 + t; + float h01 = -2*t3 + 3*t2; + float h11 = t3 - t2; + + Quaternion p0 = QuaternionScale(q1, h00); + Quaternion m0 = QuaternionScale(outTangent1, h10); + Quaternion p1 = QuaternionScale(q2, h01); + Quaternion m1 = QuaternionScale(inTangent2, h11); + + Quaternion result = { 0 }; + + result = QuaternionAdd(p0, m0); + result = QuaternionAdd(result, p1); + result = QuaternionAdd(result, m1); + result = QuaternionNormalize(result); + + return result; +} + +// Calculate quaternion based on the rotation from one vector to another +RMAPI Quaternion QuaternionFromVector3ToVector3(Vector3 from, Vector3 to) +{ + Quaternion result = { 0 }; + + float cos2Theta = (from.x*to.x + from.y*to.y + from.z*to.z); // Vector3DotProduct(from, to) + Vector3 cross = { from.y*to.z - from.z*to.y, from.z*to.x - from.x*to.z, from.x*to.y - from.y*to.x }; // Vector3CrossProduct(from, to) + + result.x = cross.x; + result.y = cross.y; + result.z = cross.z; + result.w = 1.0f + cos2Theta; + + // QuaternionNormalize(q); + // NOTE: Normalize to essentially nlerp the original and identity to 0.5 + Quaternion q = result; + float length = sqrtf(q.x*q.x + q.y*q.y + q.z*q.z + q.w*q.w); + if (length == 0.0f) length = 1.0f; + float ilength = 1.0f/length; + + result.x = q.x*ilength; + result.y = q.y*ilength; + result.z = q.z*ilength; + result.w = q.w*ilength; + + return result; +} + +// Get a quaternion for a given rotation matrix +RMAPI Quaternion QuaternionFromMatrix(Matrix mat) +{ + Quaternion result = { 0 }; + + float fourWSquaredMinus1 = mat.m0 + mat.m5 + mat.m10; + float fourXSquaredMinus1 = mat.m0 - mat.m5 - mat.m10; + float fourYSquaredMinus1 = mat.m5 - mat.m0 - mat.m10; + float fourZSquaredMinus1 = mat.m10 - mat.m0 - mat.m5; + + int biggestIndex = 0; + float fourBiggestSquaredMinus1 = fourWSquaredMinus1; + if (fourXSquaredMinus1 > fourBiggestSquaredMinus1) + { + fourBiggestSquaredMinus1 = fourXSquaredMinus1; + biggestIndex = 1; + } + + if (fourYSquaredMinus1 > fourBiggestSquaredMinus1) + { + fourBiggestSquaredMinus1 = fourYSquaredMinus1; + biggestIndex = 2; + } + + if (fourZSquaredMinus1 > fourBiggestSquaredMinus1) + { + fourBiggestSquaredMinus1 = fourZSquaredMinus1; + biggestIndex = 3; + } + + float biggestVal = sqrtf(fourBiggestSquaredMinus1 + 1.0f)*0.5f; + float mult = 0.25f/biggestVal; + + switch (biggestIndex) + { + case 0: + result.w = biggestVal; + result.x = (mat.m6 - mat.m9)*mult; + result.y = (mat.m8 - mat.m2)*mult; + result.z = (mat.m1 - mat.m4)*mult; + break; + case 1: + result.x = biggestVal; + result.w = (mat.m6 - mat.m9)*mult; + result.y = (mat.m1 + mat.m4)*mult; + result.z = (mat.m8 + mat.m2)*mult; + break; + case 2: + result.y = biggestVal; + result.w = (mat.m8 - mat.m2)*mult; + result.x = (mat.m1 + mat.m4)*mult; + result.z = (mat.m6 + mat.m9)*mult; + break; + case 3: + result.z = biggestVal; + result.w = (mat.m1 - mat.m4)*mult; + result.x = (mat.m8 + mat.m2)*mult; + result.y = (mat.m6 + mat.m9)*mult; + break; + } + + return result; +} + +// Get a matrix for a given quaternion +RMAPI Matrix QuaternionToMatrix(Quaternion q) +{ + Matrix result = { 1.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 1.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 1.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f }; // MatrixIdentity() + + float a2 = q.x*q.x; + float b2 = q.y*q.y; + float c2 = q.z*q.z; + float ac = q.x*q.z; + float ab = q.x*q.y; + float bc = q.y*q.z; + float ad = q.w*q.x; + float bd = q.w*q.y; + float cd = q.w*q.z; + + result.m0 = 1 - 2*(b2 + c2); + result.m1 = 2*(ab + cd); + result.m2 = 2*(ac - bd); + + result.m4 = 2*(ab - cd); + result.m5 = 1 - 2*(a2 + c2); + result.m6 = 2*(bc + ad); + + result.m8 = 2*(ac + bd); + result.m9 = 2*(bc - ad); + result.m10 = 1 - 2*(a2 + b2); + + return result; +} + +// Get rotation quaternion for an angle and axis +// NOTE: Angle must be provided in radians +RMAPI Quaternion QuaternionFromAxisAngle(Vector3 axis, float angle) +{ + Quaternion result = { 0.0f, 0.0f, 0.0f, 1.0f }; + + float axisLength = sqrtf(axis.x*axis.x + axis.y*axis.y + axis.z*axis.z); + + if (axisLength != 0.0f) + { + angle *= 0.5f; + + float length = 0.0f; + float ilength = 0.0f; + + // Vector3Normalize(axis) + length = axisLength; + if (length == 0.0f) length = 1.0f; + ilength = 1.0f/length; + axis.x *= ilength; + axis.y *= ilength; + axis.z *= ilength; + + float sinres = sinf(angle); + float cosres = cosf(angle); + + result.x = axis.x*sinres; + result.y = axis.y*sinres; + result.z = axis.z*sinres; + result.w = cosres; + + // QuaternionNormalize(q); + Quaternion q = result; + length = sqrtf(q.x*q.x + q.y*q.y + q.z*q.z + q.w*q.w); + if (length == 0.0f) length = 1.0f; + ilength = 1.0f/length; + result.x = q.x*ilength; + result.y = q.y*ilength; + result.z = q.z*ilength; + result.w = q.w*ilength; + } + + return result; +} + +// Get the rotation angle and axis for a given quaternion +RMAPI void QuaternionToAxisAngle(Quaternion q, Vector3 *outAxis, float *outAngle) +{ + if (fabsf(q.w) > 1.0f) + { + // QuaternionNormalize(q); + float length = sqrtf(q.x*q.x + q.y*q.y + q.z*q.z + q.w*q.w); + if (length == 0.0f) length = 1.0f; + float ilength = 1.0f/length; + + q.x = q.x*ilength; + q.y = q.y*ilength; + q.z = q.z*ilength; + q.w = q.w*ilength; + } + + Vector3 resAxis = { 0.0f, 0.0f, 0.0f }; + float resAngle = 2.0f*acosf(q.w); + float den = sqrtf(1.0f - q.w*q.w); + + if (den > EPSILON) + { + resAxis.x = q.x/den; + resAxis.y = q.y/den; + resAxis.z = q.z/den; + } + else + { + // This occurs when the angle is zero. + // Not a problem: just set an arbitrary normalized axis. + resAxis.x = 1.0f; + } + + *outAxis = resAxis; + *outAngle = resAngle; +} + +// Get the quaternion equivalent to Euler angles +// NOTE: Rotation order is ZYX +RMAPI Quaternion QuaternionFromEuler(float pitch, float yaw, float roll) +{ + Quaternion result = { 0 }; + + float x0 = cosf(pitch*0.5f); + float x1 = sinf(pitch*0.5f); + float y0 = cosf(yaw*0.5f); + float y1 = sinf(yaw*0.5f); + float z0 = cosf(roll*0.5f); + float z1 = sinf(roll*0.5f); + + result.x = x1*y0*z0 - x0*y1*z1; + result.y = x0*y1*z0 + x1*y0*z1; + result.z = x0*y0*z1 - x1*y1*z0; + result.w = x0*y0*z0 + x1*y1*z1; + + return result; +} + +// Get the Euler angles equivalent to quaternion (roll, pitch, yaw) +// NOTE: Angles are returned in a Vector3 struct in radians +RMAPI Vector3 QuaternionToEuler(Quaternion q) +{ + Vector3 result = { 0 }; + + // Roll (x-axis rotation) + float x0 = 2.0f*(q.w*q.x + q.y*q.z); + float x1 = 1.0f - 2.0f*(q.x*q.x + q.y*q.y); + result.x = atan2f(x0, x1); + + // Pitch (y-axis rotation) + float y0 = 2.0f*(q.w*q.y - q.z*q.x); + y0 = y0 > 1.0f ? 1.0f : y0; + y0 = y0 < -1.0f ? -1.0f : y0; + result.y = asinf(y0); + + // Yaw (z-axis rotation) + float z0 = 2.0f*(q.w*q.z + q.x*q.y); + float z1 = 1.0f - 2.0f*(q.y*q.y + q.z*q.z); + result.z = atan2f(z0, z1); + + return result; +} + +// Transform a quaternion given a transformation matrix +RMAPI Quaternion QuaternionTransform(Quaternion q, Matrix mat) +{ + Quaternion result = { 0 }; + + result.x = mat.m0*q.x + mat.m4*q.y + mat.m8*q.z + mat.m12*q.w; + result.y = mat.m1*q.x + mat.m5*q.y + mat.m9*q.z + mat.m13*q.w; + result.z = mat.m2*q.x + mat.m6*q.y + mat.m10*q.z + mat.m14*q.w; + result.w = mat.m3*q.x + mat.m7*q.y + mat.m11*q.z + mat.m15*q.w; + + return result; +} + +// Check whether two given quaternions are almost equal +RMAPI int QuaternionEquals(Quaternion p, Quaternion q) +{ +#if !defined(EPSILON) + #define EPSILON 0.000001f +#endif + + int result = (((fabsf(p.x - q.x)) <= (EPSILON*fmaxf(1.0f, fmaxf(fabsf(p.x), fabsf(q.x))))) && + ((fabsf(p.y - q.y)) <= (EPSILON*fmaxf(1.0f, fmaxf(fabsf(p.y), fabsf(q.y))))) && + ((fabsf(p.z - q.z)) <= (EPSILON*fmaxf(1.0f, fmaxf(fabsf(p.z), fabsf(q.z))))) && + ((fabsf(p.w - q.w)) <= (EPSILON*fmaxf(1.0f, fmaxf(fabsf(p.w), fabsf(q.w)))))) || + (((fabsf(p.x + q.x)) <= (EPSILON*fmaxf(1.0f, fmaxf(fabsf(p.x), fabsf(q.x))))) && + ((fabsf(p.y + q.y)) <= (EPSILON*fmaxf(1.0f, fmaxf(fabsf(p.y), fabsf(q.y))))) && + ((fabsf(p.z + q.z)) <= (EPSILON*fmaxf(1.0f, fmaxf(fabsf(p.z), fabsf(q.z))))) && + ((fabsf(p.w + q.w)) <= (EPSILON*fmaxf(1.0f, fmaxf(fabsf(p.w), fabsf(q.w)))))); + + return result; +} + +// Decompose a transformation matrix into its rotational, translational and scaling components +RMAPI void MatrixDecompose(Matrix mat, Vector3 *translation, Quaternion *rotation, Vector3 *scale) +{ + // Extract translation. + translation->x = mat.m12; + translation->y = mat.m13; + translation->z = mat.m14; + + // Extract upper-left for determinant computation + const float a = mat.m0; + const float b = mat.m4; + const float c = mat.m8; + const float d = mat.m1; + const float e = mat.m5; + const float f = mat.m9; + const float g = mat.m2; + const float h = mat.m6; + const float i = mat.m10; + const float A = e*i - f*h; + const float B = f*g - d*i; + const float C = d*h - e*g; + + // Extract scale + const float det = a*A + b*B + c*C; + Vector3 abc = { a, b, c }; + Vector3 def = { d, e, f }; + Vector3 ghi = { g, h, i }; + + float scalex = Vector3Length(abc); + float scaley = Vector3Length(def); + float scalez = Vector3Length(ghi); + Vector3 s = { scalex, scaley, scalez }; + + if (det < 0) s = Vector3Negate(s); + + *scale = s; + + // Remove scale from the matrix if it is not close to zero + Matrix clone = mat; + if (!FloatEquals(det, 0)) + { + clone.m0 /= s.x; + clone.m4 /= s.x; + clone.m8 /= s.x; + clone.m1 /= s.y; + clone.m5 /= s.y; + clone.m9 /= s.y; + clone.m2 /= s.z; + clone.m6 /= s.z; + clone.m10 /= s.z; + + // Extract rotation + *rotation = QuaternionFromMatrix(clone); + } + else + { + // Set to identity if close to zero + *rotation = QuaternionIdentity(); + } +} + +#if defined(__cplusplus) && !defined(RAYMATH_DISABLE_CPP_OPERATORS) + +// Optional C++ math operators +//------------------------------------------------------------------------------- + +// Vector2 operators +static constexpr Vector2 Vector2Zeros = { 0, 0 }; +static constexpr Vector2 Vector2Ones = { 1, 1 }; +static constexpr Vector2 Vector2UnitX = { 1, 0 }; +static constexpr Vector2 Vector2UnitY = { 0, 1 }; + +inline Vector2 operator + (const Vector2& lhs, const Vector2& rhs) +{ + return Vector2Add(lhs, rhs); +} + +inline const Vector2& operator += (Vector2& lhs, const Vector2& rhs) +{ + lhs = Vector2Add(lhs, rhs); + return lhs; +} + +inline Vector2 operator - (const Vector2& lhs, const Vector2& rhs) +{ + return Vector2Subtract(lhs, rhs); +} + +inline const Vector2& operator -= (Vector2& lhs, const Vector2& rhs) +{ + lhs = Vector2Subtract(lhs, rhs); + return lhs; +} + +inline Vector2 operator * (const Vector2& lhs, const float& rhs) +{ + return Vector2Scale(lhs, rhs); +} + +inline const Vector2& operator *= (Vector2& lhs, const float& rhs) +{ + lhs = Vector2Scale(lhs, rhs); + return lhs; +} + +inline Vector2 operator * (const Vector2& lhs, const Vector2& rhs) +{ + return Vector2Multiply(lhs, rhs); +} + +inline const Vector2& operator *= (Vector2& lhs, const Vector2& rhs) +{ + lhs = Vector2Multiply(lhs, rhs); + return lhs; +} + +inline Vector2 operator * (const Vector2& lhs, const Matrix& rhs) +{ + return Vector2Transform(lhs, rhs); +} + +inline const Vector2& operator *= (Vector2& lhs, const Matrix& rhs) +{ + lhs = Vector2Transform(lhs, rhs); + return lhs; +} + +inline Vector2 operator / (const Vector2& lhs, const float& rhs) +{ + return Vector2Scale(lhs, 1.0f / rhs); +} + +inline const Vector2& operator /= (Vector2& lhs, const float& rhs) +{ + lhs = Vector2Scale(lhs, 1.0f / rhs); + return lhs; +} + +inline Vector2 operator / (const Vector2& lhs, const Vector2& rhs) +{ + return Vector2Divide(lhs, rhs); +} + +inline const Vector2& operator /= (Vector2& lhs, const Vector2& rhs) +{ + lhs = Vector2Divide(lhs, rhs); + return lhs; +} + +inline bool operator == (const Vector2& lhs, const Vector2& rhs) +{ + return FloatEquals(lhs.x, rhs.x) && FloatEquals(lhs.y, rhs.y); +} + +inline bool operator != (const Vector2& lhs, const Vector2& rhs) +{ + return !FloatEquals(lhs.x, rhs.x) || !FloatEquals(lhs.y, rhs.y); +} + +// Vector3 operators +static constexpr Vector3 Vector3Zeros = { 0, 0, 0 }; +static constexpr Vector3 Vector3Ones = { 1, 1, 1 }; +static constexpr Vector3 Vector3UnitX = { 1, 0, 0 }; +static constexpr Vector3 Vector3UnitY = { 0, 1, 0 }; +static constexpr Vector3 Vector3UnitZ = { 0, 0, 1 }; + +inline Vector3 operator + (const Vector3& lhs, const Vector3& rhs) +{ + return Vector3Add(lhs, rhs); +} + +inline const Vector3& operator += (Vector3& lhs, const Vector3& rhs) +{ + lhs = Vector3Add(lhs, rhs); + return lhs; +} + +inline Vector3 operator - (const Vector3& lhs, const Vector3& rhs) +{ + return Vector3Subtract(lhs, rhs); +} + +inline const Vector3& operator -= (Vector3& lhs, const Vector3& rhs) +{ + lhs = Vector3Subtract(lhs, rhs); + return lhs; +} + +inline Vector3 operator * (const Vector3& lhs, const float& rhs) +{ + return Vector3Scale(lhs, rhs); +} + +inline const Vector3& operator *= (Vector3& lhs, const float& rhs) +{ + lhs = Vector3Scale(lhs, rhs); + return lhs; +} + +inline Vector3 operator * (const Vector3& lhs, const Vector3& rhs) +{ + return Vector3Multiply(lhs, rhs); +} + +inline const Vector3& operator *= (Vector3& lhs, const Vector3& rhs) +{ + lhs = Vector3Multiply(lhs, rhs); + return lhs; +} + +inline Vector3 operator * (const Vector3& lhs, const Matrix& rhs) +{ + return Vector3Transform(lhs, rhs); +} + +inline const Vector3& operator *= (Vector3& lhs, const Matrix& rhs) +{ + lhs = Vector3Transform(lhs, rhs); + return lhs; +} + +inline Vector3 operator / (const Vector3& lhs, const float& rhs) +{ + return Vector3Scale(lhs, 1.0f / rhs); +} + +inline const Vector3& operator /= (Vector3& lhs, const float& rhs) +{ + lhs = Vector3Scale(lhs, 1.0f / rhs); + return lhs; +} + +inline Vector3 operator / (const Vector3& lhs, const Vector3& rhs) +{ + return Vector3Divide(lhs, rhs); +} + +inline const Vector3& operator /= (Vector3& lhs, const Vector3& rhs) +{ + lhs = Vector3Divide(lhs, rhs); + return lhs; +} + +inline bool operator == (const Vector3& lhs, const Vector3& rhs) +{ + return FloatEquals(lhs.x, rhs.x) && FloatEquals(lhs.y, rhs.y) && FloatEquals(lhs.z, rhs.z); +} + +inline bool operator != (const Vector3& lhs, const Vector3& rhs) +{ + return !FloatEquals(lhs.x, rhs.x) || !FloatEquals(lhs.y, rhs.y) || !FloatEquals(lhs.z, rhs.z); +} + +// Vector4 operators +static constexpr Vector4 Vector4Zeros = { 0, 0, 0, 0 }; +static constexpr Vector4 Vector4Ones = { 1, 1, 1, 1 }; +static constexpr Vector4 Vector4UnitX = { 1, 0, 0, 0 }; +static constexpr Vector4 Vector4UnitY = { 0, 1, 0, 0 }; +static constexpr Vector4 Vector4UnitZ = { 0, 0, 1, 0 }; +static constexpr Vector4 Vector4UnitW = { 0, 0, 0, 1 }; + +inline Vector4 operator + (const Vector4& lhs, const Vector4& rhs) +{ + return Vector4Add(lhs, rhs); +} + +inline const Vector4& operator += (Vector4& lhs, const Vector4& rhs) +{ + lhs = Vector4Add(lhs, rhs); + return lhs; +} + +inline Vector4 operator - (const Vector4& lhs, const Vector4& rhs) +{ + return Vector4Subtract(lhs, rhs); +} + +inline const Vector4& operator -= (Vector4& lhs, const Vector4& rhs) +{ + lhs = Vector4Subtract(lhs, rhs); + return lhs; +} + +inline Vector4 operator * (const Vector4& lhs, const float& rhs) +{ + return Vector4Scale(lhs, rhs); +} + +inline const Vector4& operator *= (Vector4& lhs, const float& rhs) +{ + lhs = Vector4Scale(lhs, rhs); + return lhs; +} + +inline Vector4 operator * (const Vector4& lhs, const Vector4& rhs) +{ + return Vector4Multiply(lhs, rhs); +} + +inline const Vector4& operator *= (Vector4& lhs, const Vector4& rhs) +{ + lhs = Vector4Multiply(lhs, rhs); + return lhs; +} + +inline Vector4 operator / (const Vector4& lhs, const float& rhs) +{ + return Vector4Scale(lhs, 1.0f / rhs); +} + +inline const Vector4& operator /= (Vector4& lhs, const float& rhs) +{ + lhs = Vector4Scale(lhs, 1.0f / rhs); + return lhs; +} + +inline Vector4 operator / (const Vector4& lhs, const Vector4& rhs) +{ + return Vector4Divide(lhs, rhs); +} + +inline const Vector4& operator /= (Vector4& lhs, const Vector4& rhs) +{ + lhs = Vector4Divide(lhs, rhs); + return lhs; +} + +inline bool operator == (const Vector4& lhs, const Vector4& rhs) +{ + return FloatEquals(lhs.x, rhs.x) && FloatEquals(lhs.y, rhs.y) && FloatEquals(lhs.z, rhs.z) && FloatEquals(lhs.w, rhs.w); +} + +inline bool operator != (const Vector4& lhs, const Vector4& rhs) +{ + return !FloatEquals(lhs.x, rhs.x) || !FloatEquals(lhs.y, rhs.y) || !FloatEquals(lhs.z, rhs.z) || !FloatEquals(lhs.w, rhs.w); +} + +// Quaternion operators +static constexpr Quaternion QuaternionZeros = { 0, 0, 0, 0 }; +static constexpr Quaternion QuaternionOnes = { 1, 1, 1, 1 }; +static constexpr Quaternion QuaternionUnitX = { 0, 0, 0, 1 }; + +inline Quaternion operator + (const Quaternion& lhs, const float& rhs) +{ + return QuaternionAddValue(lhs, rhs); +} + +inline const Quaternion& operator += (Quaternion& lhs, const float& rhs) +{ + lhs = QuaternionAddValue(lhs, rhs); + return lhs; +} + +inline Quaternion operator - (const Quaternion& lhs, const float& rhs) +{ + return QuaternionSubtractValue(lhs, rhs); +} + +inline const Quaternion& operator -= (Quaternion& lhs, const float& rhs) +{ + lhs = QuaternionSubtractValue(lhs, rhs); + return lhs; +} + +inline Quaternion operator * (const Quaternion& lhs, const Matrix& rhs) +{ + return QuaternionTransform(lhs, rhs); +} + +inline const Quaternion& operator *= (Quaternion& lhs, const Matrix& rhs) +{ + lhs = QuaternionTransform(lhs, rhs); + return lhs; +} + +// Matrix operators +inline Matrix operator + (const Matrix& lhs, const Matrix& rhs) +{ + return MatrixAdd(lhs, rhs); +} + +inline const Matrix& operator += (Matrix& lhs, const Matrix& rhs) +{ + lhs = MatrixAdd(lhs, rhs); + return lhs; +} + +inline Matrix operator - (const Matrix& lhs, const Matrix& rhs) +{ + return MatrixSubtract(lhs, rhs); +} + +inline const Matrix& operator -= (Matrix& lhs, const Matrix& rhs) +{ + lhs = MatrixSubtract(lhs, rhs); + return lhs; +} + +inline Matrix operator * (const Matrix& lhs, const Matrix& rhs) +{ + return MatrixMultiply(lhs, rhs); +} + +inline const Matrix& operator *= (Matrix& lhs, const Matrix& rhs) +{ + lhs = MatrixMultiply(lhs, rhs); + return lhs; +} +//------------------------------------------------------------------------------- +#endif // C++ operators + +#endif // RAYMATH_H diff --git a/third_party/raylib/include/rlgl.h b/third_party/raylib/include/rlgl.h new file mode 100644 index 0000000000..92971df627 --- /dev/null +++ b/third_party/raylib/include/rlgl.h @@ -0,0 +1,5262 @@ +/********************************************************************************************** +* +* rlgl v5.0 - A multi-OpenGL abstraction layer with an immediate-mode style API +* +* DESCRIPTION: +* An abstraction layer for multiple OpenGL versions (1.1, 2.1, 3.3 Core, 4.3 Core, ES 2.0, ES 3.0) +* that provides a pseudo-OpenGL 1.1 immediate-mode style API (rlVertex, rlTranslate, rlRotate...) +* +* ADDITIONAL NOTES: +* When choosing an OpenGL backend different than OpenGL 1.1, some internal buffer are +* initialized on rlglInit() to accumulate vertex data +* +* When an internal state change is required all the stored vertex data is renderer in batch, +* additionally, rlDrawRenderBatchActive() could be called to force flushing of the batch +* +* Some resources are also loaded for convenience, here the complete list: +* - Default batch (RLGL.defaultBatch): RenderBatch system to accumulate vertex data +* - Default texture (RLGL.defaultTextureId): 1x1 white pixel R8G8B8A8 +* - Default shader (RLGL.State.defaultShaderId, RLGL.State.defaultShaderLocs) +* +* Internal buffer (and resources) must be manually unloaded calling rlglClose() +* +* CONFIGURATION: +* #define GRAPHICS_API_OPENGL_11 +* #define GRAPHICS_API_OPENGL_21 +* #define GRAPHICS_API_OPENGL_33 +* #define GRAPHICS_API_OPENGL_43 +* #define GRAPHICS_API_OPENGL_ES2 +* #define GRAPHICS_API_OPENGL_ES3 +* Use selected OpenGL graphics backend, should be supported by platform +* Those preprocessor defines are only used on rlgl module, if OpenGL version is +* required by any other module, use rlGetVersion() to check it +* +* #define RLGL_IMPLEMENTATION +* Generates the implementation of the library into the included file +* If not defined, the library is in header only mode and can be included in other headers +* or source files without problems. But only ONE file should hold the implementation +* +* #define RLGL_RENDER_TEXTURES_HINT +* Enable framebuffer objects (fbo) support (enabled by default) +* Some GPUs could not support them despite the OpenGL version +* +* #define RLGL_SHOW_GL_DETAILS_INFO +* Show OpenGL extensions and capabilities detailed logs on init +* +* #define RLGL_ENABLE_OPENGL_DEBUG_CONTEXT +* Enable debug context (only available on OpenGL 4.3) +* +* rlgl capabilities could be customized just defining some internal +* values before library inclusion (default values listed): +* +* #define RL_DEFAULT_BATCH_BUFFER_ELEMENTS 8192 // Default internal render batch elements limits +* #define RL_DEFAULT_BATCH_BUFFERS 1 // Default number of batch buffers (multi-buffering) +* #define RL_DEFAULT_BATCH_DRAWCALLS 256 // Default number of batch draw calls (by state changes: mode, texture) +* #define RL_DEFAULT_BATCH_MAX_TEXTURE_UNITS 4 // Maximum number of textures units that can be activated on batch drawing (SetShaderValueTexture()) +* +* #define RL_MAX_MATRIX_STACK_SIZE 32 // Maximum size of internal Matrix stack +* #define RL_MAX_SHADER_LOCATIONS 32 // Maximum number of shader locations supported +* #define RL_CULL_DISTANCE_NEAR 0.01 // Default projection matrix near cull distance +* #define RL_CULL_DISTANCE_FAR 1000.0 // Default projection matrix far cull distance +* +* When loading a shader, the following vertex attributes and uniform +* location names are tried to be set automatically: +* +* #define RL_DEFAULT_SHADER_ATTRIB_NAME_POSITION "vertexPosition" // Bound by default to shader location: RL_DEFAULT_SHADER_ATTRIB_LOCATION_POSITION +* #define RL_DEFAULT_SHADER_ATTRIB_NAME_TEXCOORD "vertexTexCoord" // Bound by default to shader location: RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD +* #define RL_DEFAULT_SHADER_ATTRIB_NAME_NORMAL "vertexNormal" // Bound by default to shader location: RL_DEFAULT_SHADER_ATTRIB_LOCATION_NORMAL +* #define RL_DEFAULT_SHADER_ATTRIB_NAME_COLOR "vertexColor" // Bound by default to shader location: RL_DEFAULT_SHADER_ATTRIB_LOCATION_COLOR +* #define RL_DEFAULT_SHADER_ATTRIB_NAME_TANGENT "vertexTangent" // Bound by default to shader location: RL_DEFAULT_SHADER_ATTRIB_LOCATION_TANGENT +* #define RL_DEFAULT_SHADER_ATTRIB_NAME_TEXCOORD2 "vertexTexCoord2" // Bound by default to shader location: RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD2 +* #define RL_DEFAULT_SHADER_ATTRIB_NAME_BONEIDS "vertexBoneIds" // Bound by default to shader location: RL_DEFAULT_SHADER_ATTRIB_LOCATION_BONEIDS +* #define RL_DEFAULT_SHADER_ATTRIB_NAME_BONEWEIGHTS "vertexBoneWeights" // Bound by default to shader location: RL_DEFAULT_SHADER_ATTRIB_LOCATION_BONEWEIGHTS +* #define RL_DEFAULT_SHADER_UNIFORM_NAME_MVP "mvp" // model-view-projection matrix +* #define RL_DEFAULT_SHADER_UNIFORM_NAME_VIEW "matView" // view matrix +* #define RL_DEFAULT_SHADER_UNIFORM_NAME_PROJECTION "matProjection" // projection matrix +* #define RL_DEFAULT_SHADER_UNIFORM_NAME_MODEL "matModel" // model matrix +* #define RL_DEFAULT_SHADER_UNIFORM_NAME_NORMAL "matNormal" // normal matrix (transpose(inverse(matModelView))) +* #define RL_DEFAULT_SHADER_UNIFORM_NAME_COLOR "colDiffuse" // color diffuse (base tint color, multiplied by texture color) +* #define RL_DEFAULT_SHADER_UNIFORM_NAME_BONE_MATRICES "boneMatrices" // bone matrices +* #define RL_DEFAULT_SHADER_SAMPLER2D_NAME_TEXTURE0 "texture0" // texture0 (texture slot active 0) +* #define RL_DEFAULT_SHADER_SAMPLER2D_NAME_TEXTURE1 "texture1" // texture1 (texture slot active 1) +* #define RL_DEFAULT_SHADER_SAMPLER2D_NAME_TEXTURE2 "texture2" // texture2 (texture slot active 2) +* +* DEPENDENCIES: +* - OpenGL libraries (depending on platform and OpenGL version selected) +* - GLAD OpenGL extensions loading library (only for OpenGL 3.3 Core, 4.3 Core) +* +* +* LICENSE: zlib/libpng +* +* Copyright (c) 2014-2024 Ramon Santamaria (@raysan5) +* +* This software is provided "as-is", without any express or implied warranty. In no event +* will the authors be held liable for any damages arising from the use of this software. +* +* Permission is granted to anyone to use this software for any purpose, including commercial +* applications, and to alter it and redistribute it freely, subject to the following restrictions: +* +* 1. The origin of this software must not be misrepresented; you must not claim that you +* wrote the original software. If you use this software in a product, an acknowledgment +* in the product documentation would be appreciated but is not required. +* +* 2. Altered source versions must be plainly marked as such, and must not be misrepresented +* as being the original software. +* +* 3. This notice may not be removed or altered from any source distribution. +* +**********************************************************************************************/ + +#ifndef RLGL_H +#define RLGL_H + +#define RLGL_VERSION "5.0" + +// Function specifiers in case library is build/used as a shared library +// NOTE: Microsoft specifiers to tell compiler that symbols are imported/exported from a .dll +// NOTE: visibility(default) attribute makes symbols "visible" when compiled with -fvisibility=hidden +#if defined(_WIN32) && defined(BUILD_LIBTYPE_SHARED) + #define RLAPI __declspec(dllexport) // We are building the library as a Win32 shared library (.dll) +#elif defined(BUILD_LIBTYPE_SHARED) + #define RLAPI __attribute__((visibility("default"))) // We are building the library as a Unix shared library (.so/.dylib) +#elif defined(_WIN32) && defined(USE_LIBTYPE_SHARED) + #define RLAPI __declspec(dllimport) // We are using the library as a Win32 shared library (.dll) +#endif + +// Function specifiers definition +#ifndef RLAPI + #define RLAPI // Functions defined as 'extern' by default (implicit specifiers) +#endif + +// Support TRACELOG macros +#ifndef TRACELOG + #define TRACELOG(level, ...) (void)0 + #define TRACELOGD(...) (void)0 +#endif + +// Allow custom memory allocators +#ifndef RL_MALLOC + #define RL_MALLOC(sz) malloc(sz) +#endif +#ifndef RL_CALLOC + #define RL_CALLOC(n,sz) calloc(n,sz) +#endif +#ifndef RL_REALLOC + #define RL_REALLOC(n,sz) realloc(n,sz) +#endif +#ifndef RL_FREE + #define RL_FREE(p) free(p) +#endif + +// Security check in case no GRAPHICS_API_OPENGL_* defined +#if !defined(GRAPHICS_API_OPENGL_11) && \ + !defined(GRAPHICS_API_OPENGL_21) && \ + !defined(GRAPHICS_API_OPENGL_33) && \ + !defined(GRAPHICS_API_OPENGL_43) && \ + !defined(GRAPHICS_API_OPENGL_ES2) && \ + !defined(GRAPHICS_API_OPENGL_ES3) + #define GRAPHICS_API_OPENGL_33 +#endif + +// Security check in case multiple GRAPHICS_API_OPENGL_* defined +#if defined(GRAPHICS_API_OPENGL_11) + #if defined(GRAPHICS_API_OPENGL_21) + #undef GRAPHICS_API_OPENGL_21 + #endif + #if defined(GRAPHICS_API_OPENGL_33) + #undef GRAPHICS_API_OPENGL_33 + #endif + #if defined(GRAPHICS_API_OPENGL_43) + #undef GRAPHICS_API_OPENGL_43 + #endif + #if defined(GRAPHICS_API_OPENGL_ES2) + #undef GRAPHICS_API_OPENGL_ES2 + #endif +#endif + +// OpenGL 2.1 uses most of OpenGL 3.3 Core functionality +// WARNING: Specific parts are checked with #if defines +#if defined(GRAPHICS_API_OPENGL_21) + #define GRAPHICS_API_OPENGL_33 +#endif + +// OpenGL 4.3 uses OpenGL 3.3 Core functionality +#if defined(GRAPHICS_API_OPENGL_43) + #define GRAPHICS_API_OPENGL_33 +#endif + +// OpenGL ES 3.0 uses OpenGL ES 2.0 functionality (and more) +#if defined(GRAPHICS_API_OPENGL_ES3) + #define GRAPHICS_API_OPENGL_ES2 +#endif + +// Support framebuffer objects by default +// NOTE: Some driver implementation do not support it, despite they should +#define RLGL_RENDER_TEXTURES_HINT + +//---------------------------------------------------------------------------------- +// Defines and Macros +//---------------------------------------------------------------------------------- + +// Default internal render batch elements limits +#ifndef RL_DEFAULT_BATCH_BUFFER_ELEMENTS + #if defined(GRAPHICS_API_OPENGL_11) || defined(GRAPHICS_API_OPENGL_33) + // This is the maximum amount of elements (quads) per batch + // NOTE: Be careful with text, every letter maps to a quad + #define RL_DEFAULT_BATCH_BUFFER_ELEMENTS 8192 + #endif + #if defined(GRAPHICS_API_OPENGL_ES2) + // We reduce memory sizes for embedded systems (RPI and HTML5) + // NOTE: On HTML5 (emscripten) this is allocated on heap, + // by default it's only 16MB!...just take care... + #define RL_DEFAULT_BATCH_BUFFER_ELEMENTS 2048 + #endif +#endif +#ifndef RL_DEFAULT_BATCH_BUFFERS + #define RL_DEFAULT_BATCH_BUFFERS 1 // Default number of batch buffers (multi-buffering) +#endif +#ifndef RL_DEFAULT_BATCH_DRAWCALLS + #define RL_DEFAULT_BATCH_DRAWCALLS 256 // Default number of batch draw calls (by state changes: mode, texture) +#endif +#ifndef RL_DEFAULT_BATCH_MAX_TEXTURE_UNITS + #define RL_DEFAULT_BATCH_MAX_TEXTURE_UNITS 4 // Maximum number of textures units that can be activated on batch drawing (SetShaderValueTexture()) +#endif + +// Internal Matrix stack +#ifndef RL_MAX_MATRIX_STACK_SIZE + #define RL_MAX_MATRIX_STACK_SIZE 32 // Maximum size of Matrix stack +#endif + +// Shader limits +#ifndef RL_MAX_SHADER_LOCATIONS + #define RL_MAX_SHADER_LOCATIONS 32 // Maximum number of shader locations supported +#endif + +// Projection matrix culling +#ifndef RL_CULL_DISTANCE_NEAR + #define RL_CULL_DISTANCE_NEAR 0.01 // Default near cull distance +#endif +#ifndef RL_CULL_DISTANCE_FAR + #define RL_CULL_DISTANCE_FAR 1000.0 // Default far cull distance +#endif + +// Texture parameters (equivalent to OpenGL defines) +#define RL_TEXTURE_WRAP_S 0x2802 // GL_TEXTURE_WRAP_S +#define RL_TEXTURE_WRAP_T 0x2803 // GL_TEXTURE_WRAP_T +#define RL_TEXTURE_MAG_FILTER 0x2800 // GL_TEXTURE_MAG_FILTER +#define RL_TEXTURE_MIN_FILTER 0x2801 // GL_TEXTURE_MIN_FILTER + +#define RL_TEXTURE_FILTER_NEAREST 0x2600 // GL_NEAREST +#define RL_TEXTURE_FILTER_LINEAR 0x2601 // GL_LINEAR +#define RL_TEXTURE_FILTER_MIP_NEAREST 0x2700 // GL_NEAREST_MIPMAP_NEAREST +#define RL_TEXTURE_FILTER_NEAREST_MIP_LINEAR 0x2702 // GL_NEAREST_MIPMAP_LINEAR +#define RL_TEXTURE_FILTER_LINEAR_MIP_NEAREST 0x2701 // GL_LINEAR_MIPMAP_NEAREST +#define RL_TEXTURE_FILTER_MIP_LINEAR 0x2703 // GL_LINEAR_MIPMAP_LINEAR +#define RL_TEXTURE_FILTER_ANISOTROPIC 0x3000 // Anisotropic filter (custom identifier) +#define RL_TEXTURE_MIPMAP_BIAS_RATIO 0x4000 // Texture mipmap bias, percentage ratio (custom identifier) + +#define RL_TEXTURE_WRAP_REPEAT 0x2901 // GL_REPEAT +#define RL_TEXTURE_WRAP_CLAMP 0x812F // GL_CLAMP_TO_EDGE +#define RL_TEXTURE_WRAP_MIRROR_REPEAT 0x8370 // GL_MIRRORED_REPEAT +#define RL_TEXTURE_WRAP_MIRROR_CLAMP 0x8742 // GL_MIRROR_CLAMP_EXT + +// Matrix modes (equivalent to OpenGL) +#define RL_MODELVIEW 0x1700 // GL_MODELVIEW +#define RL_PROJECTION 0x1701 // GL_PROJECTION +#define RL_TEXTURE 0x1702 // GL_TEXTURE + +// Primitive assembly draw modes +#define RL_LINES 0x0001 // GL_LINES +#define RL_TRIANGLES 0x0004 // GL_TRIANGLES +#define RL_QUADS 0x0007 // GL_QUADS + +// GL equivalent data types +#define RL_UNSIGNED_BYTE 0x1401 // GL_UNSIGNED_BYTE +#define RL_FLOAT 0x1406 // GL_FLOAT + +// GL buffer usage hint +#define RL_STREAM_DRAW 0x88E0 // GL_STREAM_DRAW +#define RL_STREAM_READ 0x88E1 // GL_STREAM_READ +#define RL_STREAM_COPY 0x88E2 // GL_STREAM_COPY +#define RL_STATIC_DRAW 0x88E4 // GL_STATIC_DRAW +#define RL_STATIC_READ 0x88E5 // GL_STATIC_READ +#define RL_STATIC_COPY 0x88E6 // GL_STATIC_COPY +#define RL_DYNAMIC_DRAW 0x88E8 // GL_DYNAMIC_DRAW +#define RL_DYNAMIC_READ 0x88E9 // GL_DYNAMIC_READ +#define RL_DYNAMIC_COPY 0x88EA // GL_DYNAMIC_COPY + +// GL Shader type +#define RL_FRAGMENT_SHADER 0x8B30 // GL_FRAGMENT_SHADER +#define RL_VERTEX_SHADER 0x8B31 // GL_VERTEX_SHADER +#define RL_COMPUTE_SHADER 0x91B9 // GL_COMPUTE_SHADER + +// GL blending factors +#define RL_ZERO 0 // GL_ZERO +#define RL_ONE 1 // GL_ONE +#define RL_SRC_COLOR 0x0300 // GL_SRC_COLOR +#define RL_ONE_MINUS_SRC_COLOR 0x0301 // GL_ONE_MINUS_SRC_COLOR +#define RL_SRC_ALPHA 0x0302 // GL_SRC_ALPHA +#define RL_ONE_MINUS_SRC_ALPHA 0x0303 // GL_ONE_MINUS_SRC_ALPHA +#define RL_DST_ALPHA 0x0304 // GL_DST_ALPHA +#define RL_ONE_MINUS_DST_ALPHA 0x0305 // GL_ONE_MINUS_DST_ALPHA +#define RL_DST_COLOR 0x0306 // GL_DST_COLOR +#define RL_ONE_MINUS_DST_COLOR 0x0307 // GL_ONE_MINUS_DST_COLOR +#define RL_SRC_ALPHA_SATURATE 0x0308 // GL_SRC_ALPHA_SATURATE +#define RL_CONSTANT_COLOR 0x8001 // GL_CONSTANT_COLOR +#define RL_ONE_MINUS_CONSTANT_COLOR 0x8002 // GL_ONE_MINUS_CONSTANT_COLOR +#define RL_CONSTANT_ALPHA 0x8003 // GL_CONSTANT_ALPHA +#define RL_ONE_MINUS_CONSTANT_ALPHA 0x8004 // GL_ONE_MINUS_CONSTANT_ALPHA + +// GL blending functions/equations +#define RL_FUNC_ADD 0x8006 // GL_FUNC_ADD +#define RL_MIN 0x8007 // GL_MIN +#define RL_MAX 0x8008 // GL_MAX +#define RL_FUNC_SUBTRACT 0x800A // GL_FUNC_SUBTRACT +#define RL_FUNC_REVERSE_SUBTRACT 0x800B // GL_FUNC_REVERSE_SUBTRACT +#define RL_BLEND_EQUATION 0x8009 // GL_BLEND_EQUATION +#define RL_BLEND_EQUATION_RGB 0x8009 // GL_BLEND_EQUATION_RGB // (Same as BLEND_EQUATION) +#define RL_BLEND_EQUATION_ALPHA 0x883D // GL_BLEND_EQUATION_ALPHA +#define RL_BLEND_DST_RGB 0x80C8 // GL_BLEND_DST_RGB +#define RL_BLEND_SRC_RGB 0x80C9 // GL_BLEND_SRC_RGB +#define RL_BLEND_DST_ALPHA 0x80CA // GL_BLEND_DST_ALPHA +#define RL_BLEND_SRC_ALPHA 0x80CB // GL_BLEND_SRC_ALPHA +#define RL_BLEND_COLOR 0x8005 // GL_BLEND_COLOR + +#define RL_READ_FRAMEBUFFER 0x8CA8 // GL_READ_FRAMEBUFFER +#define RL_DRAW_FRAMEBUFFER 0x8CA9 // GL_DRAW_FRAMEBUFFER + +// Default shader vertex attribute locations +#ifndef RL_DEFAULT_SHADER_ATTRIB_LOCATION_POSITION + #define RL_DEFAULT_SHADER_ATTRIB_LOCATION_POSITION 0 +#endif +#ifndef RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD + #define RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD 1 +#endif +#ifndef RL_DEFAULT_SHADER_ATTRIB_LOCATION_NORMAL + #define RL_DEFAULT_SHADER_ATTRIB_LOCATION_NORMAL 2 +#endif +#ifndef RL_DEFAULT_SHADER_ATTRIB_LOCATION_COLOR + #define RL_DEFAULT_SHADER_ATTRIB_LOCATION_COLOR 3 +#endif + #ifndef RL_DEFAULT_SHADER_ATTRIB_LOCATION_TANGENT +#define RL_DEFAULT_SHADER_ATTRIB_LOCATION_TANGENT 4 +#endif +#ifndef RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD2 + #define RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD2 5 +#endif +#ifndef RL_DEFAULT_SHADER_ATTRIB_LOCATION_INDICES + #define RL_DEFAULT_SHADER_ATTRIB_LOCATION_INDICES 6 +#endif +#ifdef RL_SUPPORT_MESH_GPU_SKINNING +#ifndef RL_DEFAULT_SHADER_ATTRIB_LOCATION_BONEIDS + #define RL_DEFAULT_SHADER_ATTRIB_LOCATION_BONEIDS 7 +#endif +#ifndef RL_DEFAULT_SHADER_ATTRIB_LOCATION_BONEWEIGHTS + #define RL_DEFAULT_SHADER_ATTRIB_LOCATION_BONEWEIGHTS 8 +#endif +#endif + +//---------------------------------------------------------------------------------- +// Types and Structures Definition +//---------------------------------------------------------------------------------- +#if (defined(__STDC__) && __STDC_VERSION__ >= 199901L) || (defined(_MSC_VER) && _MSC_VER >= 1800) + #include +#elif !defined(__cplusplus) && !defined(bool) && !defined(RL_BOOL_TYPE) + // Boolean type +typedef enum bool { false = 0, true = !false } bool; +#endif + +#if !defined(RL_MATRIX_TYPE) +// Matrix, 4x4 components, column major, OpenGL style, right handed +typedef struct Matrix { + float m0, m4, m8, m12; // Matrix first row (4 components) + float m1, m5, m9, m13; // Matrix second row (4 components) + float m2, m6, m10, m14; // Matrix third row (4 components) + float m3, m7, m11, m15; // Matrix fourth row (4 components) +} Matrix; +#define RL_MATRIX_TYPE +#endif + +// Dynamic vertex buffers (position + texcoords + colors + indices arrays) +typedef struct rlVertexBuffer { + int elementCount; // Number of elements in the buffer (QUADS) + + float *vertices; // Vertex position (XYZ - 3 components per vertex) (shader-location = 0) + float *texcoords; // Vertex texture coordinates (UV - 2 components per vertex) (shader-location = 1) + float *normals; // Vertex normal (XYZ - 3 components per vertex) (shader-location = 2) + unsigned char *colors; // Vertex colors (RGBA - 4 components per vertex) (shader-location = 3) +#if defined(GRAPHICS_API_OPENGL_11) || defined(GRAPHICS_API_OPENGL_33) + unsigned int *indices; // Vertex indices (in case vertex data comes indexed) (6 indices per quad) +#endif +#if defined(GRAPHICS_API_OPENGL_ES2) + unsigned short *indices; // Vertex indices (in case vertex data comes indexed) (6 indices per quad) +#endif + unsigned int vaoId; // OpenGL Vertex Array Object id + unsigned int vboId[5]; // OpenGL Vertex Buffer Objects id (5 types of vertex data) +} rlVertexBuffer; + +// Draw call type +// NOTE: Only texture changes register a new draw, other state-change-related elements are not +// used at this moment (vaoId, shaderId, matrices), raylib just forces a batch draw call if any +// of those state-change happens (this is done in core module) +typedef struct rlDrawCall { + int mode; // Drawing mode: LINES, TRIANGLES, QUADS + int vertexCount; // Number of vertex of the draw + int vertexAlignment; // Number of vertex required for index alignment (LINES, TRIANGLES) + //unsigned int vaoId; // Vertex array id to be used on the draw -> Using RLGL.currentBatch->vertexBuffer.vaoId + //unsigned int shaderId; // Shader id to be used on the draw -> Using RLGL.currentShaderId + unsigned int textureId; // Texture id to be used on the draw -> Use to create new draw call if changes + + //Matrix projection; // Projection matrix for this draw -> Using RLGL.projection by default + //Matrix modelview; // Modelview matrix for this draw -> Using RLGL.modelview by default +} rlDrawCall; + +// rlRenderBatch type +typedef struct rlRenderBatch { + int bufferCount; // Number of vertex buffers (multi-buffering support) + int currentBuffer; // Current buffer tracking in case of multi-buffering + rlVertexBuffer *vertexBuffer; // Dynamic buffer(s) for vertex data + + rlDrawCall *draws; // Draw calls array, depends on textureId + int drawCounter; // Draw calls counter + float currentDepth; // Current depth value for next draw +} rlRenderBatch; + +// OpenGL version +typedef enum { + RL_OPENGL_11 = 1, // OpenGL 1.1 + RL_OPENGL_21, // OpenGL 2.1 (GLSL 120) + RL_OPENGL_33, // OpenGL 3.3 (GLSL 330) + RL_OPENGL_43, // OpenGL 4.3 (using GLSL 330) + RL_OPENGL_ES_20, // OpenGL ES 2.0 (GLSL 100) + RL_OPENGL_ES_30 // OpenGL ES 3.0 (GLSL 300 es) +} rlGlVersion; + +// Trace log level +// NOTE: Organized by priority level +typedef enum { + RL_LOG_ALL = 0, // Display all logs + RL_LOG_TRACE, // Trace logging, intended for internal use only + RL_LOG_DEBUG, // Debug logging, used for internal debugging, it should be disabled on release builds + RL_LOG_INFO, // Info logging, used for program execution info + RL_LOG_WARNING, // Warning logging, used on recoverable failures + RL_LOG_ERROR, // Error logging, used on unrecoverable failures + RL_LOG_FATAL, // Fatal logging, used to abort program: exit(EXIT_FAILURE) + RL_LOG_NONE // Disable logging +} rlTraceLogLevel; + +// Texture pixel formats +// NOTE: Support depends on OpenGL version +typedef enum { + RL_PIXELFORMAT_UNCOMPRESSED_GRAYSCALE = 1, // 8 bit per pixel (no alpha) + RL_PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA, // 8*2 bpp (2 channels) + RL_PIXELFORMAT_UNCOMPRESSED_R5G6B5, // 16 bpp + RL_PIXELFORMAT_UNCOMPRESSED_R8G8B8, // 24 bpp + RL_PIXELFORMAT_UNCOMPRESSED_R5G5B5A1, // 16 bpp (1 bit alpha) + RL_PIXELFORMAT_UNCOMPRESSED_R4G4B4A4, // 16 bpp (4 bit alpha) + RL_PIXELFORMAT_UNCOMPRESSED_R8G8B8A8, // 32 bpp + RL_PIXELFORMAT_UNCOMPRESSED_R32, // 32 bpp (1 channel - float) + RL_PIXELFORMAT_UNCOMPRESSED_R32G32B32, // 32*3 bpp (3 channels - float) + RL_PIXELFORMAT_UNCOMPRESSED_R32G32B32A32, // 32*4 bpp (4 channels - float) + RL_PIXELFORMAT_UNCOMPRESSED_R16, // 16 bpp (1 channel - half float) + RL_PIXELFORMAT_UNCOMPRESSED_R16G16B16, // 16*3 bpp (3 channels - half float) + RL_PIXELFORMAT_UNCOMPRESSED_R16G16B16A16, // 16*4 bpp (4 channels - half float) + RL_PIXELFORMAT_COMPRESSED_DXT1_RGB, // 4 bpp (no alpha) + RL_PIXELFORMAT_COMPRESSED_DXT1_RGBA, // 4 bpp (1 bit alpha) + RL_PIXELFORMAT_COMPRESSED_DXT3_RGBA, // 8 bpp + RL_PIXELFORMAT_COMPRESSED_DXT5_RGBA, // 8 bpp + RL_PIXELFORMAT_COMPRESSED_ETC1_RGB, // 4 bpp + RL_PIXELFORMAT_COMPRESSED_ETC2_RGB, // 4 bpp + RL_PIXELFORMAT_COMPRESSED_ETC2_EAC_RGBA, // 8 bpp + RL_PIXELFORMAT_COMPRESSED_PVRT_RGB, // 4 bpp + RL_PIXELFORMAT_COMPRESSED_PVRT_RGBA, // 4 bpp + RL_PIXELFORMAT_COMPRESSED_ASTC_4x4_RGBA, // 8 bpp + RL_PIXELFORMAT_COMPRESSED_ASTC_8x8_RGBA // 2 bpp +} rlPixelFormat; + +// Texture parameters: filter mode +// NOTE 1: Filtering considers mipmaps if available in the texture +// NOTE 2: Filter is accordingly set for minification and magnification +typedef enum { + RL_TEXTURE_FILTER_POINT = 0, // No filter, just pixel approximation + RL_TEXTURE_FILTER_BILINEAR, // Linear filtering + RL_TEXTURE_FILTER_TRILINEAR, // Trilinear filtering (linear with mipmaps) + RL_TEXTURE_FILTER_ANISOTROPIC_4X, // Anisotropic filtering 4x + RL_TEXTURE_FILTER_ANISOTROPIC_8X, // Anisotropic filtering 8x + RL_TEXTURE_FILTER_ANISOTROPIC_16X, // Anisotropic filtering 16x +} rlTextureFilter; + +// Color blending modes (pre-defined) +typedef enum { + RL_BLEND_ALPHA = 0, // Blend textures considering alpha (default) + RL_BLEND_ADDITIVE, // Blend textures adding colors + RL_BLEND_MULTIPLIED, // Blend textures multiplying colors + RL_BLEND_ADD_COLORS, // Blend textures adding colors (alternative) + RL_BLEND_SUBTRACT_COLORS, // Blend textures subtracting colors (alternative) + RL_BLEND_ALPHA_PREMULTIPLY, // Blend premultiplied textures considering alpha + RL_BLEND_CUSTOM, // Blend textures using custom src/dst factors (use rlSetBlendFactors()) + RL_BLEND_CUSTOM_SEPARATE // Blend textures using custom src/dst factors (use rlSetBlendFactorsSeparate()) +} rlBlendMode; + +// Shader location point type +typedef enum { + RL_SHADER_LOC_VERTEX_POSITION = 0, // Shader location: vertex attribute: position + RL_SHADER_LOC_VERTEX_TEXCOORD01, // Shader location: vertex attribute: texcoord01 + RL_SHADER_LOC_VERTEX_TEXCOORD02, // Shader location: vertex attribute: texcoord02 + RL_SHADER_LOC_VERTEX_NORMAL, // Shader location: vertex attribute: normal + RL_SHADER_LOC_VERTEX_TANGENT, // Shader location: vertex attribute: tangent + RL_SHADER_LOC_VERTEX_COLOR, // Shader location: vertex attribute: color + RL_SHADER_LOC_MATRIX_MVP, // Shader location: matrix uniform: model-view-projection + RL_SHADER_LOC_MATRIX_VIEW, // Shader location: matrix uniform: view (camera transform) + RL_SHADER_LOC_MATRIX_PROJECTION, // Shader location: matrix uniform: projection + RL_SHADER_LOC_MATRIX_MODEL, // Shader location: matrix uniform: model (transform) + RL_SHADER_LOC_MATRIX_NORMAL, // Shader location: matrix uniform: normal + RL_SHADER_LOC_VECTOR_VIEW, // Shader location: vector uniform: view + RL_SHADER_LOC_COLOR_DIFFUSE, // Shader location: vector uniform: diffuse color + RL_SHADER_LOC_COLOR_SPECULAR, // Shader location: vector uniform: specular color + RL_SHADER_LOC_COLOR_AMBIENT, // Shader location: vector uniform: ambient color + RL_SHADER_LOC_MAP_ALBEDO, // Shader location: sampler2d texture: albedo (same as: RL_SHADER_LOC_MAP_DIFFUSE) + RL_SHADER_LOC_MAP_METALNESS, // Shader location: sampler2d texture: metalness (same as: RL_SHADER_LOC_MAP_SPECULAR) + RL_SHADER_LOC_MAP_NORMAL, // Shader location: sampler2d texture: normal + RL_SHADER_LOC_MAP_ROUGHNESS, // Shader location: sampler2d texture: roughness + RL_SHADER_LOC_MAP_OCCLUSION, // Shader location: sampler2d texture: occlusion + RL_SHADER_LOC_MAP_EMISSION, // Shader location: sampler2d texture: emission + RL_SHADER_LOC_MAP_HEIGHT, // Shader location: sampler2d texture: height + RL_SHADER_LOC_MAP_CUBEMAP, // Shader location: samplerCube texture: cubemap + RL_SHADER_LOC_MAP_IRRADIANCE, // Shader location: samplerCube texture: irradiance + RL_SHADER_LOC_MAP_PREFILTER, // Shader location: samplerCube texture: prefilter + RL_SHADER_LOC_MAP_BRDF // Shader location: sampler2d texture: brdf +} rlShaderLocationIndex; + +#define RL_SHADER_LOC_MAP_DIFFUSE RL_SHADER_LOC_MAP_ALBEDO +#define RL_SHADER_LOC_MAP_SPECULAR RL_SHADER_LOC_MAP_METALNESS + +// Shader uniform data type +typedef enum { + RL_SHADER_UNIFORM_FLOAT = 0, // Shader uniform type: float + RL_SHADER_UNIFORM_VEC2, // Shader uniform type: vec2 (2 float) + RL_SHADER_UNIFORM_VEC3, // Shader uniform type: vec3 (3 float) + RL_SHADER_UNIFORM_VEC4, // Shader uniform type: vec4 (4 float) + RL_SHADER_UNIFORM_INT, // Shader uniform type: int + RL_SHADER_UNIFORM_IVEC2, // Shader uniform type: ivec2 (2 int) + RL_SHADER_UNIFORM_IVEC3, // Shader uniform type: ivec3 (3 int) + RL_SHADER_UNIFORM_IVEC4, // Shader uniform type: ivec4 (4 int) + RL_SHADER_UNIFORM_UINT, // Shader uniform type: unsigned int + RL_SHADER_UNIFORM_UIVEC2, // Shader uniform type: uivec2 (2 unsigned int) + RL_SHADER_UNIFORM_UIVEC3, // Shader uniform type: uivec3 (3 unsigned int) + RL_SHADER_UNIFORM_UIVEC4, // Shader uniform type: uivec4 (4 unsigned int) + RL_SHADER_UNIFORM_SAMPLER2D // Shader uniform type: sampler2d +} rlShaderUniformDataType; + +// Shader attribute data types +typedef enum { + RL_SHADER_ATTRIB_FLOAT = 0, // Shader attribute type: float + RL_SHADER_ATTRIB_VEC2, // Shader attribute type: vec2 (2 float) + RL_SHADER_ATTRIB_VEC3, // Shader attribute type: vec3 (3 float) + RL_SHADER_ATTRIB_VEC4 // Shader attribute type: vec4 (4 float) +} rlShaderAttributeDataType; + +// Framebuffer attachment type +// NOTE: By default up to 8 color channels defined, but it can be more +typedef enum { + RL_ATTACHMENT_COLOR_CHANNEL0 = 0, // Framebuffer attachment type: color 0 + RL_ATTACHMENT_COLOR_CHANNEL1 = 1, // Framebuffer attachment type: color 1 + RL_ATTACHMENT_COLOR_CHANNEL2 = 2, // Framebuffer attachment type: color 2 + RL_ATTACHMENT_COLOR_CHANNEL3 = 3, // Framebuffer attachment type: color 3 + RL_ATTACHMENT_COLOR_CHANNEL4 = 4, // Framebuffer attachment type: color 4 + RL_ATTACHMENT_COLOR_CHANNEL5 = 5, // Framebuffer attachment type: color 5 + RL_ATTACHMENT_COLOR_CHANNEL6 = 6, // Framebuffer attachment type: color 6 + RL_ATTACHMENT_COLOR_CHANNEL7 = 7, // Framebuffer attachment type: color 7 + RL_ATTACHMENT_DEPTH = 100, // Framebuffer attachment type: depth + RL_ATTACHMENT_STENCIL = 200, // Framebuffer attachment type: stencil +} rlFramebufferAttachType; + +// Framebuffer texture attachment type +typedef enum { + RL_ATTACHMENT_CUBEMAP_POSITIVE_X = 0, // Framebuffer texture attachment type: cubemap, +X side + RL_ATTACHMENT_CUBEMAP_NEGATIVE_X = 1, // Framebuffer texture attachment type: cubemap, -X side + RL_ATTACHMENT_CUBEMAP_POSITIVE_Y = 2, // Framebuffer texture attachment type: cubemap, +Y side + RL_ATTACHMENT_CUBEMAP_NEGATIVE_Y = 3, // Framebuffer texture attachment type: cubemap, -Y side + RL_ATTACHMENT_CUBEMAP_POSITIVE_Z = 4, // Framebuffer texture attachment type: cubemap, +Z side + RL_ATTACHMENT_CUBEMAP_NEGATIVE_Z = 5, // Framebuffer texture attachment type: cubemap, -Z side + RL_ATTACHMENT_TEXTURE2D = 100, // Framebuffer texture attachment type: texture2d + RL_ATTACHMENT_RENDERBUFFER = 200, // Framebuffer texture attachment type: renderbuffer +} rlFramebufferAttachTextureType; + +// Face culling mode +typedef enum { + RL_CULL_FACE_FRONT = 0, + RL_CULL_FACE_BACK +} rlCullMode; + +//------------------------------------------------------------------------------------ +// Functions Declaration - Matrix operations +//------------------------------------------------------------------------------------ + +#if defined(__cplusplus) +extern "C" { // Prevents name mangling of functions +#endif + +RLAPI void rlMatrixMode(int mode); // Choose the current matrix to be transformed +RLAPI void rlPushMatrix(void); // Push the current matrix to stack +RLAPI void rlPopMatrix(void); // Pop latest inserted matrix from stack +RLAPI void rlLoadIdentity(void); // Reset current matrix to identity matrix +RLAPI void rlTranslatef(float x, float y, float z); // Multiply the current matrix by a translation matrix +RLAPI void rlRotatef(float angle, float x, float y, float z); // Multiply the current matrix by a rotation matrix +RLAPI void rlScalef(float x, float y, float z); // Multiply the current matrix by a scaling matrix +RLAPI void rlMultMatrixf(const float *matf); // Multiply the current matrix by another matrix +RLAPI void rlFrustum(double left, double right, double bottom, double top, double znear, double zfar); +RLAPI void rlOrtho(double left, double right, double bottom, double top, double znear, double zfar); +RLAPI void rlViewport(int x, int y, int width, int height); // Set the viewport area +RLAPI void rlSetClipPlanes(double nearPlane, double farPlane); // Set clip planes distances +RLAPI double rlGetCullDistanceNear(void); // Get cull plane distance near +RLAPI double rlGetCullDistanceFar(void); // Get cull plane distance far + +//------------------------------------------------------------------------------------ +// Functions Declaration - Vertex level operations +//------------------------------------------------------------------------------------ +RLAPI void rlBegin(int mode); // Initialize drawing mode (how to organize vertex) +RLAPI void rlEnd(void); // Finish vertex providing +RLAPI void rlVertex2i(int x, int y); // Define one vertex (position) - 2 int +RLAPI void rlVertex2f(float x, float y); // Define one vertex (position) - 2 float +RLAPI void rlVertex3f(float x, float y, float z); // Define one vertex (position) - 3 float +RLAPI void rlTexCoord2f(float x, float y); // Define one vertex (texture coordinate) - 2 float +RLAPI void rlNormal3f(float x, float y, float z); // Define one vertex (normal) - 3 float +RLAPI void rlColor4ub(unsigned char r, unsigned char g, unsigned char b, unsigned char a); // Define one vertex (color) - 4 byte +RLAPI void rlColor3f(float x, float y, float z); // Define one vertex (color) - 3 float +RLAPI void rlColor4f(float x, float y, float z, float w); // Define one vertex (color) - 4 float + +//------------------------------------------------------------------------------------ +// Functions Declaration - OpenGL style functions (common to 1.1, 3.3+, ES2) +// NOTE: This functions are used to completely abstract raylib code from OpenGL layer, +// some of them are direct wrappers over OpenGL calls, some others are custom +//------------------------------------------------------------------------------------ + +// Vertex buffers state +RLAPI bool rlEnableVertexArray(unsigned int vaoId); // Enable vertex array (VAO, if supported) +RLAPI void rlDisableVertexArray(void); // Disable vertex array (VAO, if supported) +RLAPI void rlEnableVertexBuffer(unsigned int id); // Enable vertex buffer (VBO) +RLAPI void rlDisableVertexBuffer(void); // Disable vertex buffer (VBO) +RLAPI void rlEnableVertexBufferElement(unsigned int id); // Enable vertex buffer element (VBO element) +RLAPI void rlDisableVertexBufferElement(void); // Disable vertex buffer element (VBO element) +RLAPI void rlEnableVertexAttribute(unsigned int index); // Enable vertex attribute index +RLAPI void rlDisableVertexAttribute(unsigned int index); // Disable vertex attribute index +#if defined(GRAPHICS_API_OPENGL_11) +RLAPI void rlEnableStatePointer(int vertexAttribType, void *buffer); // Enable attribute state pointer +RLAPI void rlDisableStatePointer(int vertexAttribType); // Disable attribute state pointer +#endif + +// Textures state +RLAPI void rlActiveTextureSlot(int slot); // Select and active a texture slot +RLAPI void rlEnableTexture(unsigned int id); // Enable texture +RLAPI void rlDisableTexture(void); // Disable texture +RLAPI void rlEnableTextureCubemap(unsigned int id); // Enable texture cubemap +RLAPI void rlDisableTextureCubemap(void); // Disable texture cubemap +RLAPI void rlTextureParameters(unsigned int id, int param, int value); // Set texture parameters (filter, wrap) +RLAPI void rlCubemapParameters(unsigned int id, int param, int value); // Set cubemap parameters (filter, wrap) + +// Shader state +RLAPI void rlEnableShader(unsigned int id); // Enable shader program +RLAPI void rlDisableShader(void); // Disable shader program + +// Framebuffer state +RLAPI void rlEnableFramebuffer(unsigned int id); // Enable render texture (fbo) +RLAPI void rlDisableFramebuffer(void); // Disable render texture (fbo), return to default framebuffer +RLAPI unsigned int rlGetActiveFramebuffer(void); // Get the currently active render texture (fbo), 0 for default framebuffer +RLAPI void rlActiveDrawBuffers(int count); // Activate multiple draw color buffers +RLAPI void rlBlitFramebuffer(int srcX, int srcY, int srcWidth, int srcHeight, int dstX, int dstY, int dstWidth, int dstHeight, int bufferMask); // Blit active framebuffer to main framebuffer +RLAPI void rlBindFramebuffer(unsigned int target, unsigned int framebuffer); // Bind framebuffer (FBO) + +// General render state +RLAPI void rlEnableColorBlend(void); // Enable color blending +RLAPI void rlDisableColorBlend(void); // Disable color blending +RLAPI void rlEnableDepthTest(void); // Enable depth test +RLAPI void rlDisableDepthTest(void); // Disable depth test +RLAPI void rlEnableDepthMask(void); // Enable depth write +RLAPI void rlDisableDepthMask(void); // Disable depth write +RLAPI void rlEnableBackfaceCulling(void); // Enable backface culling +RLAPI void rlDisableBackfaceCulling(void); // Disable backface culling +RLAPI void rlColorMask(bool r, bool g, bool b, bool a); // Color mask control +RLAPI void rlSetCullFace(int mode); // Set face culling mode +RLAPI void rlEnableScissorTest(void); // Enable scissor test +RLAPI void rlDisableScissorTest(void); // Disable scissor test +RLAPI void rlScissor(int x, int y, int width, int height); // Scissor test +RLAPI void rlEnableWireMode(void); // Enable wire mode +RLAPI void rlEnablePointMode(void); // Enable point mode +RLAPI void rlDisableWireMode(void); // Disable wire (and point) mode +RLAPI void rlSetLineWidth(float width); // Set the line drawing width +RLAPI float rlGetLineWidth(void); // Get the line drawing width +RLAPI void rlEnableSmoothLines(void); // Enable line aliasing +RLAPI void rlDisableSmoothLines(void); // Disable line aliasing +RLAPI void rlEnableStereoRender(void); // Enable stereo rendering +RLAPI void rlDisableStereoRender(void); // Disable stereo rendering +RLAPI bool rlIsStereoRenderEnabled(void); // Check if stereo render is enabled + +RLAPI void rlClearColor(unsigned char r, unsigned char g, unsigned char b, unsigned char a); // Clear color buffer with color +RLAPI void rlClearScreenBuffers(void); // Clear used screen buffers (color and depth) +RLAPI void rlCheckErrors(void); // Check and log OpenGL error codes +RLAPI void rlSetBlendMode(int mode); // Set blending mode +RLAPI void rlSetBlendFactors(int glSrcFactor, int glDstFactor, int glEquation); // Set blending mode factor and equation (using OpenGL factors) +RLAPI void rlSetBlendFactorsSeparate(int glSrcRGB, int glDstRGB, int glSrcAlpha, int glDstAlpha, int glEqRGB, int glEqAlpha); // Set blending mode factors and equations separately (using OpenGL factors) + +//------------------------------------------------------------------------------------ +// Functions Declaration - rlgl functionality +//------------------------------------------------------------------------------------ +// rlgl initialization functions +RLAPI void rlglInit(int width, int height); // Initialize rlgl (buffers, shaders, textures, states) +RLAPI void rlglClose(void); // De-initialize rlgl (buffers, shaders, textures) +RLAPI void rlLoadExtensions(void *loader); // Load OpenGL extensions (loader function required) +RLAPI int rlGetVersion(void); // Get current OpenGL version +RLAPI void rlSetFramebufferWidth(int width); // Set current framebuffer width +RLAPI int rlGetFramebufferWidth(void); // Get default framebuffer width +RLAPI void rlSetFramebufferHeight(int height); // Set current framebuffer height +RLAPI int rlGetFramebufferHeight(void); // Get default framebuffer height + +RLAPI unsigned int rlGetTextureIdDefault(void); // Get default texture id +RLAPI unsigned int rlGetShaderIdDefault(void); // Get default shader id +RLAPI int *rlGetShaderLocsDefault(void); // Get default shader locations + +// Render batch management +// NOTE: rlgl provides a default render batch to behave like OpenGL 1.1 immediate mode +// but this render batch API is exposed in case of custom batches are required +RLAPI rlRenderBatch rlLoadRenderBatch(int numBuffers, int bufferElements); // Load a render batch system +RLAPI void rlUnloadRenderBatch(rlRenderBatch batch); // Unload render batch system +RLAPI void rlDrawRenderBatch(rlRenderBatch *batch); // Draw render batch data (Update->Draw->Reset) +RLAPI void rlSetRenderBatchActive(rlRenderBatch *batch); // Set the active render batch for rlgl (NULL for default internal) +RLAPI void rlDrawRenderBatchActive(void); // Update and draw internal render batch +RLAPI bool rlCheckRenderBatchLimit(int vCount); // Check internal buffer overflow for a given number of vertex + +RLAPI void rlSetTexture(unsigned int id); // Set current texture for render batch and check buffers limits + +//------------------------------------------------------------------------------------------------------------------------ + +// Vertex buffers management +RLAPI unsigned int rlLoadVertexArray(void); // Load vertex array (vao) if supported +RLAPI unsigned int rlLoadVertexBuffer(const void *buffer, int size, bool dynamic); // Load a vertex buffer object +RLAPI unsigned int rlLoadVertexBufferElement(const void *buffer, int size, bool dynamic); // Load vertex buffer elements object +RLAPI void rlUpdateVertexBuffer(unsigned int bufferId, const void *data, int dataSize, int offset); // Update vertex buffer object data on GPU buffer +RLAPI void rlUpdateVertexBufferElements(unsigned int id, const void *data, int dataSize, int offset); // Update vertex buffer elements data on GPU buffer +RLAPI void rlUnloadVertexArray(unsigned int vaoId); // Unload vertex array (vao) +RLAPI void rlUnloadVertexBuffer(unsigned int vboId); // Unload vertex buffer object +RLAPI void rlSetVertexAttribute(unsigned int index, int compSize, int type, bool normalized, int stride, int offset); // Set vertex attribute data configuration +RLAPI void rlSetVertexAttributeDivisor(unsigned int index, int divisor); // Set vertex attribute data divisor +RLAPI void rlSetVertexAttributeDefault(int locIndex, const void *value, int attribType, int count); // Set vertex attribute default value, when attribute to provided +RLAPI void rlDrawVertexArray(int offset, int count); // Draw vertex array (currently active vao) +RLAPI void rlDrawVertexArrayElements(int offset, int count, const void *buffer); // Draw vertex array elements +RLAPI void rlDrawVertexArrayInstanced(int offset, int count, int instances); // Draw vertex array (currently active vao) with instancing +RLAPI void rlDrawVertexArrayElementsInstanced(int offset, int count, const void *buffer, int instances); // Draw vertex array elements with instancing + +// Textures management +RLAPI unsigned int rlLoadTexture(const void *data, int width, int height, int format, int mipmapCount); // Load texture data +RLAPI unsigned int rlLoadTextureDepth(int width, int height, bool useRenderBuffer); // Load depth texture/renderbuffer (to be attached to fbo) +RLAPI unsigned int rlLoadTextureCubemap(const void *data, int size, int format, int mipmapCount); // Load texture cubemap data +RLAPI void rlUpdateTexture(unsigned int id, int offsetX, int offsetY, int width, int height, int format, const void *data); // Update texture with new data on GPU +RLAPI void rlGetGlTextureFormats(int format, unsigned int *glInternalFormat, unsigned int *glFormat, unsigned int *glType); // Get OpenGL internal formats +RLAPI const char *rlGetPixelFormatName(unsigned int format); // Get name string for pixel format +RLAPI void rlUnloadTexture(unsigned int id); // Unload texture from GPU memory +RLAPI void rlGenTextureMipmaps(unsigned int id, int width, int height, int format, int *mipmaps); // Generate mipmap data for selected texture +RLAPI void *rlReadTexturePixels(unsigned int id, int width, int height, int format); // Read texture pixel data +RLAPI unsigned char *rlReadScreenPixels(int width, int height); // Read screen pixel data (color buffer) + +// Framebuffer management (fbo) +RLAPI unsigned int rlLoadFramebuffer(void); // Load an empty framebuffer +RLAPI void rlFramebufferAttach(unsigned int fboId, unsigned int texId, int attachType, int texType, int mipLevel); // Attach texture/renderbuffer to a framebuffer +RLAPI bool rlFramebufferComplete(unsigned int id); // Verify framebuffer is complete +RLAPI void rlUnloadFramebuffer(unsigned int id); // Delete framebuffer from GPU + +// Shaders management +RLAPI unsigned int rlLoadShaderCode(const char *vsCode, const char *fsCode); // Load shader from code strings +RLAPI unsigned int rlCompileShader(const char *shaderCode, int type); // Compile custom shader and return shader id (type: RL_VERTEX_SHADER, RL_FRAGMENT_SHADER, RL_COMPUTE_SHADER) +RLAPI unsigned int rlLoadShaderProgram(unsigned int vShaderId, unsigned int fShaderId); // Load custom shader program +RLAPI void rlUnloadShaderProgram(unsigned int id); // Unload shader program +RLAPI int rlGetLocationUniform(unsigned int shaderId, const char *uniformName); // Get shader location uniform +RLAPI int rlGetLocationAttrib(unsigned int shaderId, const char *attribName); // Get shader location attribute +RLAPI void rlSetUniform(int locIndex, const void *value, int uniformType, int count); // Set shader value uniform +RLAPI void rlSetUniformMatrix(int locIndex, Matrix mat); // Set shader value matrix +RLAPI void rlSetUniformMatrices(int locIndex, const Matrix *mat, int count); // Set shader value matrices +RLAPI void rlSetUniformSampler(int locIndex, unsigned int textureId); // Set shader value sampler +RLAPI void rlSetShader(unsigned int id, int *locs); // Set shader currently active (id and locations) + +// Compute shader management +RLAPI unsigned int rlLoadComputeShaderProgram(unsigned int shaderId); // Load compute shader program +RLAPI void rlComputeShaderDispatch(unsigned int groupX, unsigned int groupY, unsigned int groupZ); // Dispatch compute shader (equivalent to *draw* for graphics pipeline) + +// Shader buffer storage object management (ssbo) +RLAPI unsigned int rlLoadShaderBuffer(unsigned int size, const void *data, int usageHint); // Load shader storage buffer object (SSBO) +RLAPI void rlUnloadShaderBuffer(unsigned int ssboId); // Unload shader storage buffer object (SSBO) +RLAPI void rlUpdateShaderBuffer(unsigned int id, const void *data, unsigned int dataSize, unsigned int offset); // Update SSBO buffer data +RLAPI void rlBindShaderBuffer(unsigned int id, unsigned int index); // Bind SSBO buffer +RLAPI void rlReadShaderBuffer(unsigned int id, void *dest, unsigned int count, unsigned int offset); // Read SSBO buffer data (GPU->CPU) +RLAPI void rlCopyShaderBuffer(unsigned int destId, unsigned int srcId, unsigned int destOffset, unsigned int srcOffset, unsigned int count); // Copy SSBO data between buffers +RLAPI unsigned int rlGetShaderBufferSize(unsigned int id); // Get SSBO buffer size + +// Buffer management +RLAPI void rlBindImageTexture(unsigned int id, unsigned int index, int format, bool readonly); // Bind image texture + +// Matrix state management +RLAPI Matrix rlGetMatrixModelview(void); // Get internal modelview matrix +RLAPI Matrix rlGetMatrixProjection(void); // Get internal projection matrix +RLAPI Matrix rlGetMatrixTransform(void); // Get internal accumulated transform matrix +RLAPI Matrix rlGetMatrixProjectionStereo(int eye); // Get internal projection matrix for stereo render (selected eye) +RLAPI Matrix rlGetMatrixViewOffsetStereo(int eye); // Get internal view offset matrix for stereo render (selected eye) +RLAPI void rlSetMatrixProjection(Matrix proj); // Set a custom projection matrix (replaces internal projection matrix) +RLAPI void rlSetMatrixModelview(Matrix view); // Set a custom modelview matrix (replaces internal modelview matrix) +RLAPI void rlSetMatrixProjectionStereo(Matrix right, Matrix left); // Set eyes projection matrices for stereo rendering +RLAPI void rlSetMatrixViewOffsetStereo(Matrix right, Matrix left); // Set eyes view offsets matrices for stereo rendering + +// Quick and dirty cube/quad buffers load->draw->unload +RLAPI void rlLoadDrawCube(void); // Load and draw a cube +RLAPI void rlLoadDrawQuad(void); // Load and draw a quad + +#if defined(__cplusplus) +} +#endif + +#endif // RLGL_H + +/*********************************************************************************** +* +* RLGL IMPLEMENTATION +* +************************************************************************************/ + +#if defined(RLGL_IMPLEMENTATION) + +// Expose OpenGL functions from glad in raylib +#if defined(BUILD_LIBTYPE_SHARED) + #define GLAD_API_CALL_EXPORT + #define GLAD_API_CALL_EXPORT_BUILD +#endif + +#if defined(GRAPHICS_API_OPENGL_11) + #if defined(__APPLE__) + #include // OpenGL 1.1 library for OSX + #include // OpenGL extensions library + #else + // APIENTRY for OpenGL function pointer declarations is required + #if !defined(APIENTRY) + #if defined(_WIN32) + #define APIENTRY __stdcall + #else + #define APIENTRY + #endif + #endif + // WINGDIAPI definition. Some Windows OpenGL headers need it + #if !defined(WINGDIAPI) && defined(_WIN32) + #define WINGDIAPI __declspec(dllimport) + #endif + + #include // OpenGL 1.1 library + #endif +#endif + +#if defined(GRAPHICS_API_OPENGL_33) + #define GLAD_MALLOC RL_MALLOC + #define GLAD_FREE RL_FREE + + #define GLAD_GL_IMPLEMENTATION + #include "external/glad.h" // GLAD extensions loading library, includes OpenGL headers +#endif + +#if defined(GRAPHICS_API_OPENGL_ES3) + #include // OpenGL ES 3.0 library + #define GL_GLEXT_PROTOTYPES + #include // OpenGL ES 2.0 extensions library +#elif defined(GRAPHICS_API_OPENGL_ES2) + // NOTE: OpenGL ES 2.0 can be enabled on Desktop platforms, + // in that case, functions are loaded from a custom glad for OpenGL ES 2.0 + #if defined(PLATFORM_DESKTOP_GLFW) || defined(PLATFORM_DESKTOP_SDL) + #define GLAD_GLES2_IMPLEMENTATION + #include "external/glad_gles2.h" + #else + #define GL_GLEXT_PROTOTYPES + //#include // EGL library -> not required, platform layer + #include // OpenGL ES 2.0 library + #include // OpenGL ES 2.0 extensions library + #endif + + // It seems OpenGL ES 2.0 instancing entry points are not defined on Raspberry Pi + // provided headers (despite being defined in official Khronos GLES2 headers) + #if defined(PLATFORM_DRM) + typedef void (GL_APIENTRYP PFNGLDRAWARRAYSINSTANCEDEXTPROC) (GLenum mode, GLint start, GLsizei count, GLsizei primcount); + typedef void (GL_APIENTRYP PFNGLDRAWELEMENTSINSTANCEDEXTPROC) (GLenum mode, GLsizei count, GLenum type, const void *indices, GLsizei primcount); + typedef void (GL_APIENTRYP PFNGLVERTEXATTRIBDIVISOREXTPROC) (GLuint index, GLuint divisor); + #endif +#endif + +#include // Required for: malloc(), free() +#include // Required for: strcmp(), strlen() [Used in rlglInit(), on extensions loading] +#include // Required for: sqrtf(), sinf(), cosf(), floor(), log() + +//---------------------------------------------------------------------------------- +// Defines and Macros +//---------------------------------------------------------------------------------- +#ifndef PI + #define PI 3.14159265358979323846f +#endif +#ifndef DEG2RAD + #define DEG2RAD (PI/180.0f) +#endif +#ifndef RAD2DEG + #define RAD2DEG (180.0f/PI) +#endif + +#ifndef GL_SHADING_LANGUAGE_VERSION + #define GL_SHADING_LANGUAGE_VERSION 0x8B8C +#endif + +#ifndef GL_COMPRESSED_RGB_S3TC_DXT1_EXT + #define GL_COMPRESSED_RGB_S3TC_DXT1_EXT 0x83F0 +#endif +#ifndef GL_COMPRESSED_RGBA_S3TC_DXT1_EXT + #define GL_COMPRESSED_RGBA_S3TC_DXT1_EXT 0x83F1 +#endif +#ifndef GL_COMPRESSED_RGBA_S3TC_DXT3_EXT + #define GL_COMPRESSED_RGBA_S3TC_DXT3_EXT 0x83F2 +#endif +#ifndef GL_COMPRESSED_RGBA_S3TC_DXT5_EXT + #define GL_COMPRESSED_RGBA_S3TC_DXT5_EXT 0x83F3 +#endif +#ifndef GL_ETC1_RGB8_OES + #define GL_ETC1_RGB8_OES 0x8D64 +#endif +#ifndef GL_COMPRESSED_RGB8_ETC2 + #define GL_COMPRESSED_RGB8_ETC2 0x9274 +#endif +#ifndef GL_COMPRESSED_RGBA8_ETC2_EAC + #define GL_COMPRESSED_RGBA8_ETC2_EAC 0x9278 +#endif +#ifndef GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG + #define GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG 0x8C00 +#endif +#ifndef GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG + #define GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG 0x8C02 +#endif +#ifndef GL_COMPRESSED_RGBA_ASTC_4x4_KHR + #define GL_COMPRESSED_RGBA_ASTC_4x4_KHR 0x93b0 +#endif +#ifndef GL_COMPRESSED_RGBA_ASTC_8x8_KHR + #define GL_COMPRESSED_RGBA_ASTC_8x8_KHR 0x93b7 +#endif + +#ifndef GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT + #define GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT 0x84FF +#endif +#ifndef GL_TEXTURE_MAX_ANISOTROPY_EXT + #define GL_TEXTURE_MAX_ANISOTROPY_EXT 0x84FE +#endif + +#ifndef GL_PROGRAM_POINT_SIZE + #define GL_PROGRAM_POINT_SIZE 0x8642 +#endif + +#ifndef GL_LINE_WIDTH + #define GL_LINE_WIDTH 0x0B21 +#endif + +#if defined(GRAPHICS_API_OPENGL_11) + #define GL_UNSIGNED_SHORT_5_6_5 0x8363 + #define GL_UNSIGNED_SHORT_5_5_5_1 0x8034 + #define GL_UNSIGNED_SHORT_4_4_4_4 0x8033 +#endif + +#if defined(GRAPHICS_API_OPENGL_21) + #define GL_LUMINANCE 0x1909 + #define GL_LUMINANCE_ALPHA 0x190A +#endif + +#if defined(GRAPHICS_API_OPENGL_ES2) + #define glClearDepth glClearDepthf + #if !defined(GRAPHICS_API_OPENGL_ES3) + #define GL_READ_FRAMEBUFFER GL_FRAMEBUFFER + #define GL_DRAW_FRAMEBUFFER GL_FRAMEBUFFER + #endif +#endif + +// Default shader vertex attribute names to set location points +#ifndef RL_DEFAULT_SHADER_ATTRIB_NAME_POSITION + #define RL_DEFAULT_SHADER_ATTRIB_NAME_POSITION "vertexPosition" // Bound by default to shader location: RL_DEFAULT_SHADER_ATTRIB_NAME_POSITION +#endif +#ifndef RL_DEFAULT_SHADER_ATTRIB_NAME_TEXCOORD + #define RL_DEFAULT_SHADER_ATTRIB_NAME_TEXCOORD "vertexTexCoord" // Bound by default to shader location: RL_DEFAULT_SHADER_ATTRIB_NAME_TEXCOORD +#endif +#ifndef RL_DEFAULT_SHADER_ATTRIB_NAME_NORMAL + #define RL_DEFAULT_SHADER_ATTRIB_NAME_NORMAL "vertexNormal" // Bound by default to shader location: RL_DEFAULT_SHADER_ATTRIB_NAME_NORMAL +#endif +#ifndef RL_DEFAULT_SHADER_ATTRIB_NAME_COLOR + #define RL_DEFAULT_SHADER_ATTRIB_NAME_COLOR "vertexColor" // Bound by default to shader location: RL_DEFAULT_SHADER_ATTRIB_NAME_COLOR +#endif +#ifndef RL_DEFAULT_SHADER_ATTRIB_NAME_TANGENT + #define RL_DEFAULT_SHADER_ATTRIB_NAME_TANGENT "vertexTangent" // Bound by default to shader location: RL_DEFAULT_SHADER_ATTRIB_NAME_TANGENT +#endif +#ifndef RL_DEFAULT_SHADER_ATTRIB_NAME_TEXCOORD2 + #define RL_DEFAULT_SHADER_ATTRIB_NAME_TEXCOORD2 "vertexTexCoord2" // Bound by default to shader location: RL_DEFAULT_SHADER_ATTRIB_NAME_TEXCOORD2 +#endif +#ifndef RL_DEFAULT_SHADER_ATTRIB_NAME_BONEIDS + #define RL_DEFAULT_SHADER_ATTRIB_NAME_BONEIDS "vertexBoneIds" // Bound by default to shader location: RL_DEFAULT_SHADER_ATTRIB_NAME_BONEIDS +#endif +#ifndef RL_DEFAULT_SHADER_ATTRIB_NAME_BONEWEIGHTS + #define RL_DEFAULT_SHADER_ATTRIB_NAME_BONEWEIGHTS "vertexBoneWeights" // Bound by default to shader location: RL_DEFAULT_SHADER_ATTRIB_NAME_BONEWEIGHTS +#endif + +#ifndef RL_DEFAULT_SHADER_UNIFORM_NAME_MVP + #define RL_DEFAULT_SHADER_UNIFORM_NAME_MVP "mvp" // model-view-projection matrix +#endif +#ifndef RL_DEFAULT_SHADER_UNIFORM_NAME_VIEW + #define RL_DEFAULT_SHADER_UNIFORM_NAME_VIEW "matView" // view matrix +#endif +#ifndef RL_DEFAULT_SHADER_UNIFORM_NAME_PROJECTION + #define RL_DEFAULT_SHADER_UNIFORM_NAME_PROJECTION "matProjection" // projection matrix +#endif +#ifndef RL_DEFAULT_SHADER_UNIFORM_NAME_MODEL + #define RL_DEFAULT_SHADER_UNIFORM_NAME_MODEL "matModel" // model matrix +#endif +#ifndef RL_DEFAULT_SHADER_UNIFORM_NAME_NORMAL + #define RL_DEFAULT_SHADER_UNIFORM_NAME_NORMAL "matNormal" // normal matrix (transpose(inverse(matModelView)) +#endif +#ifndef RL_DEFAULT_SHADER_UNIFORM_NAME_COLOR + #define RL_DEFAULT_SHADER_UNIFORM_NAME_COLOR "colDiffuse" // color diffuse (base tint color, multiplied by texture color) +#endif +#ifndef RL_DEFAULT_SHADER_UNIFORM_NAME_BONE_MATRICES + #define RL_DEFAULT_SHADER_UNIFORM_NAME_BONE_MATRICES "boneMatrices" // bone matrices +#endif +#ifndef RL_DEFAULT_SHADER_SAMPLER2D_NAME_TEXTURE0 + #define RL_DEFAULT_SHADER_SAMPLER2D_NAME_TEXTURE0 "texture0" // texture0 (texture slot active 0) +#endif +#ifndef RL_DEFAULT_SHADER_SAMPLER2D_NAME_TEXTURE1 + #define RL_DEFAULT_SHADER_SAMPLER2D_NAME_TEXTURE1 "texture1" // texture1 (texture slot active 1) +#endif +#ifndef RL_DEFAULT_SHADER_SAMPLER2D_NAME_TEXTURE2 + #define RL_DEFAULT_SHADER_SAMPLER2D_NAME_TEXTURE2 "texture2" // texture2 (texture slot active 2) +#endif + +//---------------------------------------------------------------------------------- +// Types and Structures Definition +//---------------------------------------------------------------------------------- +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) +typedef struct rlglData { + rlRenderBatch *currentBatch; // Current render batch + rlRenderBatch defaultBatch; // Default internal render batch + + struct { + int vertexCounter; // Current active render batch vertex counter (generic, used for all batches) + float texcoordx, texcoordy; // Current active texture coordinate (added on glVertex*()) + float normalx, normaly, normalz; // Current active normal (added on glVertex*()) + unsigned char colorr, colorg, colorb, colora; // Current active color (added on glVertex*()) + + int currentMatrixMode; // Current matrix mode + Matrix *currentMatrix; // Current matrix pointer + Matrix modelview; // Default modelview matrix + Matrix projection; // Default projection matrix + Matrix transform; // Transform matrix to be used with rlTranslate, rlRotate, rlScale + bool transformRequired; // Require transform matrix application to current draw-call vertex (if required) + Matrix stack[RL_MAX_MATRIX_STACK_SIZE];// Matrix stack for push/pop + int stackCounter; // Matrix stack counter + + unsigned int defaultTextureId; // Default texture used on shapes/poly drawing (required by shader) + unsigned int activeTextureId[RL_DEFAULT_BATCH_MAX_TEXTURE_UNITS]; // Active texture ids to be enabled on batch drawing (0 active by default) + unsigned int defaultVShaderId; // Default vertex shader id (used by default shader program) + unsigned int defaultFShaderId; // Default fragment shader id (used by default shader program) + unsigned int defaultShaderId; // Default shader program id, supports vertex color and diffuse texture + int *defaultShaderLocs; // Default shader locations pointer to be used on rendering + unsigned int currentShaderId; // Current shader id to be used on rendering (by default, defaultShaderId) + int *currentShaderLocs; // Current shader locations pointer to be used on rendering (by default, defaultShaderLocs) + + bool stereoRender; // Stereo rendering flag + Matrix projectionStereo[2]; // VR stereo rendering eyes projection matrices + Matrix viewOffsetStereo[2]; // VR stereo rendering eyes view offset matrices + + // Blending variables + int currentBlendMode; // Blending mode active + int glBlendSrcFactor; // Blending source factor + int glBlendDstFactor; // Blending destination factor + int glBlendEquation; // Blending equation + int glBlendSrcFactorRGB; // Blending source RGB factor + int glBlendDestFactorRGB; // Blending destination RGB factor + int glBlendSrcFactorAlpha; // Blending source alpha factor + int glBlendDestFactorAlpha; // Blending destination alpha factor + int glBlendEquationRGB; // Blending equation for RGB + int glBlendEquationAlpha; // Blending equation for alpha + bool glCustomBlendModeModified; // Custom blending factor and equation modification status + + int framebufferWidth; // Current framebuffer width + int framebufferHeight; // Current framebuffer height + + } State; // Renderer state + struct { + bool vao; // VAO support (OpenGL ES2 could not support VAO extension) (GL_ARB_vertex_array_object) + bool instancing; // Instancing supported (GL_ANGLE_instanced_arrays, GL_EXT_draw_instanced + GL_EXT_instanced_arrays) + bool texNPOT; // NPOT textures full support (GL_ARB_texture_non_power_of_two, GL_OES_texture_npot) + bool texDepth; // Depth textures supported (GL_ARB_depth_texture, GL_OES_depth_texture) + bool texDepthWebGL; // Depth textures supported WebGL specific (GL_WEBGL_depth_texture) + bool texFloat32; // float textures support (32 bit per channel) (GL_OES_texture_float) + bool texFloat16; // half float textures support (16 bit per channel) (GL_OES_texture_half_float) + bool texCompDXT; // DDS texture compression support (GL_EXT_texture_compression_s3tc, GL_WEBGL_compressed_texture_s3tc, GL_WEBKIT_WEBGL_compressed_texture_s3tc) + bool texCompETC1; // ETC1 texture compression support (GL_OES_compressed_ETC1_RGB8_texture, GL_WEBGL_compressed_texture_etc1) + bool texCompETC2; // ETC2/EAC texture compression support (GL_ARB_ES3_compatibility) + bool texCompPVRT; // PVR texture compression support (GL_IMG_texture_compression_pvrtc) + bool texCompASTC; // ASTC texture compression support (GL_KHR_texture_compression_astc_hdr, GL_KHR_texture_compression_astc_ldr) + bool texMirrorClamp; // Clamp mirror wrap mode supported (GL_EXT_texture_mirror_clamp) + bool texAnisoFilter; // Anisotropic texture filtering support (GL_EXT_texture_filter_anisotropic) + bool computeShader; // Compute shaders support (GL_ARB_compute_shader) + bool ssbo; // Shader storage buffer object support (GL_ARB_shader_storage_buffer_object) + + float maxAnisotropyLevel; // Maximum anisotropy level supported (minimum is 2.0f) + int maxDepthBits; // Maximum bits for depth component + + } ExtSupported; // Extensions supported flags +} rlglData; + +typedef void *(*rlglLoadProc)(const char *name); // OpenGL extension functions loader signature (same as GLADloadproc) + +#endif // GRAPHICS_API_OPENGL_33 || GRAPHICS_API_OPENGL_ES2 + +//---------------------------------------------------------------------------------- +// Global Variables Definition +//---------------------------------------------------------------------------------- +static double rlCullDistanceNear = RL_CULL_DISTANCE_NEAR; +static double rlCullDistanceFar = RL_CULL_DISTANCE_FAR; + +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) +static rlglData RLGL = { 0 }; +#endif // GRAPHICS_API_OPENGL_33 || GRAPHICS_API_OPENGL_ES2 + +#if defined(GRAPHICS_API_OPENGL_ES2) && !defined(GRAPHICS_API_OPENGL_ES3) +// NOTE: VAO functionality is exposed through extensions (OES) +static PFNGLGENVERTEXARRAYSOESPROC glGenVertexArrays = NULL; +static PFNGLBINDVERTEXARRAYOESPROC glBindVertexArray = NULL; +static PFNGLDELETEVERTEXARRAYSOESPROC glDeleteVertexArrays = NULL; + +// NOTE: Instancing functionality could also be available through extension +static PFNGLDRAWARRAYSINSTANCEDEXTPROC glDrawArraysInstanced = NULL; +static PFNGLDRAWELEMENTSINSTANCEDEXTPROC glDrawElementsInstanced = NULL; +static PFNGLVERTEXATTRIBDIVISOREXTPROC glVertexAttribDivisor = NULL; +#endif + +//---------------------------------------------------------------------------------- +// Module specific Functions Declaration +//---------------------------------------------------------------------------------- +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) +static void rlLoadShaderDefault(void); // Load default shader +static void rlUnloadShaderDefault(void); // Unload default shader +#if defined(RLGL_SHOW_GL_DETAILS_INFO) +static const char *rlGetCompressedFormatName(int format); // Get compressed format official GL identifier name +#endif // RLGL_SHOW_GL_DETAILS_INFO +#endif // GRAPHICS_API_OPENGL_33 || GRAPHICS_API_OPENGL_ES2 + +static int rlGetPixelDataSize(int width, int height, int format); // Get pixel data size in bytes (image or texture) + +// Auxiliar matrix math functions +typedef struct rl_float16 { + float v[16]; +} rl_float16; +static rl_float16 rlMatrixToFloatV(Matrix mat); // Get float array of matrix data +#define rlMatrixToFloat(mat) (rlMatrixToFloatV(mat).v) // Get float vector for Matrix +static Matrix rlMatrixIdentity(void); // Get identity matrix +static Matrix rlMatrixMultiply(Matrix left, Matrix right); // Multiply two matrices +static Matrix rlMatrixTranspose(Matrix mat); // Transposes provided matrix +static Matrix rlMatrixInvert(Matrix mat); // Invert provided matrix + +//---------------------------------------------------------------------------------- +// Module Functions Definition - Matrix operations +//---------------------------------------------------------------------------------- + +#if defined(GRAPHICS_API_OPENGL_11) +// Fallback to OpenGL 1.1 function calls +//--------------------------------------- +void rlMatrixMode(int mode) +{ + switch (mode) + { + case RL_PROJECTION: glMatrixMode(GL_PROJECTION); break; + case RL_MODELVIEW: glMatrixMode(GL_MODELVIEW); break; + case RL_TEXTURE: glMatrixMode(GL_TEXTURE); break; + default: break; + } +} + +void rlFrustum(double left, double right, double bottom, double top, double znear, double zfar) +{ + glFrustum(left, right, bottom, top, znear, zfar); +} + +void rlOrtho(double left, double right, double bottom, double top, double znear, double zfar) +{ + glOrtho(left, right, bottom, top, znear, zfar); +} + +void rlPushMatrix(void) { glPushMatrix(); } +void rlPopMatrix(void) { glPopMatrix(); } +void rlLoadIdentity(void) { glLoadIdentity(); } +void rlTranslatef(float x, float y, float z) { glTranslatef(x, y, z); } +void rlRotatef(float angle, float x, float y, float z) { glRotatef(angle, x, y, z); } +void rlScalef(float x, float y, float z) { glScalef(x, y, z); } +void rlMultMatrixf(const float *matf) { glMultMatrixf(matf); } +#endif +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) +// Choose the current matrix to be transformed +void rlMatrixMode(int mode) +{ + if (mode == RL_PROJECTION) RLGL.State.currentMatrix = &RLGL.State.projection; + else if (mode == RL_MODELVIEW) RLGL.State.currentMatrix = &RLGL.State.modelview; + //else if (mode == RL_TEXTURE) // Not supported + + RLGL.State.currentMatrixMode = mode; +} + +// Push the current matrix into RLGL.State.stack +void rlPushMatrix(void) +{ + if (RLGL.State.stackCounter >= RL_MAX_MATRIX_STACK_SIZE) TRACELOG(RL_LOG_ERROR, "RLGL: Matrix stack overflow (RL_MAX_MATRIX_STACK_SIZE)"); + + if (RLGL.State.currentMatrixMode == RL_MODELVIEW) + { + RLGL.State.transformRequired = true; + RLGL.State.currentMatrix = &RLGL.State.transform; + } + + RLGL.State.stack[RLGL.State.stackCounter] = *RLGL.State.currentMatrix; + RLGL.State.stackCounter++; +} + +// Pop lattest inserted matrix from RLGL.State.stack +void rlPopMatrix(void) +{ + if (RLGL.State.stackCounter > 0) + { + Matrix mat = RLGL.State.stack[RLGL.State.stackCounter - 1]; + *RLGL.State.currentMatrix = mat; + RLGL.State.stackCounter--; + } + + if ((RLGL.State.stackCounter == 0) && (RLGL.State.currentMatrixMode == RL_MODELVIEW)) + { + RLGL.State.currentMatrix = &RLGL.State.modelview; + RLGL.State.transformRequired = false; + } +} + +// Reset current matrix to identity matrix +void rlLoadIdentity(void) +{ + *RLGL.State.currentMatrix = rlMatrixIdentity(); +} + +// Multiply the current matrix by a translation matrix +void rlTranslatef(float x, float y, float z) +{ + Matrix matTranslation = { + 1.0f, 0.0f, 0.0f, x, + 0.0f, 1.0f, 0.0f, y, + 0.0f, 0.0f, 1.0f, z, + 0.0f, 0.0f, 0.0f, 1.0f + }; + + // NOTE: We transpose matrix with multiplication order + *RLGL.State.currentMatrix = rlMatrixMultiply(matTranslation, *RLGL.State.currentMatrix); +} + +// Multiply the current matrix by a rotation matrix +// NOTE: The provided angle must be in degrees +void rlRotatef(float angle, float x, float y, float z) +{ + Matrix matRotation = rlMatrixIdentity(); + + // Axis vector (x, y, z) normalization + float lengthSquared = x*x + y*y + z*z; + if ((lengthSquared != 1.0f) && (lengthSquared != 0.0f)) + { + float inverseLength = 1.0f/sqrtf(lengthSquared); + x *= inverseLength; + y *= inverseLength; + z *= inverseLength; + } + + // Rotation matrix generation + float sinres = sinf(DEG2RAD*angle); + float cosres = cosf(DEG2RAD*angle); + float t = 1.0f - cosres; + + matRotation.m0 = x*x*t + cosres; + matRotation.m1 = y*x*t + z*sinres; + matRotation.m2 = z*x*t - y*sinres; + matRotation.m3 = 0.0f; + + matRotation.m4 = x*y*t - z*sinres; + matRotation.m5 = y*y*t + cosres; + matRotation.m6 = z*y*t + x*sinres; + matRotation.m7 = 0.0f; + + matRotation.m8 = x*z*t + y*sinres; + matRotation.m9 = y*z*t - x*sinres; + matRotation.m10 = z*z*t + cosres; + matRotation.m11 = 0.0f; + + matRotation.m12 = 0.0f; + matRotation.m13 = 0.0f; + matRotation.m14 = 0.0f; + matRotation.m15 = 1.0f; + + // NOTE: We transpose matrix with multiplication order + *RLGL.State.currentMatrix = rlMatrixMultiply(matRotation, *RLGL.State.currentMatrix); +} + +// Multiply the current matrix by a scaling matrix +void rlScalef(float x, float y, float z) +{ + Matrix matScale = { + x, 0.0f, 0.0f, 0.0f, + 0.0f, y, 0.0f, 0.0f, + 0.0f, 0.0f, z, 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f + }; + + // NOTE: We transpose matrix with multiplication order + *RLGL.State.currentMatrix = rlMatrixMultiply(matScale, *RLGL.State.currentMatrix); +} + +// Multiply the current matrix by another matrix +void rlMultMatrixf(const float *matf) +{ + // Matrix creation from array + Matrix mat = { matf[0], matf[4], matf[8], matf[12], + matf[1], matf[5], matf[9], matf[13], + matf[2], matf[6], matf[10], matf[14], + matf[3], matf[7], matf[11], matf[15] }; + + *RLGL.State.currentMatrix = rlMatrixMultiply(mat, *RLGL.State.currentMatrix); +} + +// Multiply the current matrix by a perspective matrix generated by parameters +void rlFrustum(double left, double right, double bottom, double top, double znear, double zfar) +{ + Matrix matFrustum = { 0 }; + + float rl = (float)(right - left); + float tb = (float)(top - bottom); + float fn = (float)(zfar - znear); + + matFrustum.m0 = ((float) znear*2.0f)/rl; + matFrustum.m1 = 0.0f; + matFrustum.m2 = 0.0f; + matFrustum.m3 = 0.0f; + + matFrustum.m4 = 0.0f; + matFrustum.m5 = ((float) znear*2.0f)/tb; + matFrustum.m6 = 0.0f; + matFrustum.m7 = 0.0f; + + matFrustum.m8 = ((float)right + (float)left)/rl; + matFrustum.m9 = ((float)top + (float)bottom)/tb; + matFrustum.m10 = -((float)zfar + (float)znear)/fn; + matFrustum.m11 = -1.0f; + + matFrustum.m12 = 0.0f; + matFrustum.m13 = 0.0f; + matFrustum.m14 = -((float)zfar*(float)znear*2.0f)/fn; + matFrustum.m15 = 0.0f; + + *RLGL.State.currentMatrix = rlMatrixMultiply(*RLGL.State.currentMatrix, matFrustum); +} + +// Multiply the current matrix by an orthographic matrix generated by parameters +void rlOrtho(double left, double right, double bottom, double top, double znear, double zfar) +{ + // NOTE: If left-right and top-botton values are equal it could create a division by zero, + // response to it is platform/compiler dependant + Matrix matOrtho = { 0 }; + + float rl = (float)(right - left); + float tb = (float)(top - bottom); + float fn = (float)(zfar - znear); + + matOrtho.m0 = 2.0f/rl; + matOrtho.m1 = 0.0f; + matOrtho.m2 = 0.0f; + matOrtho.m3 = 0.0f; + matOrtho.m4 = 0.0f; + matOrtho.m5 = 2.0f/tb; + matOrtho.m6 = 0.0f; + matOrtho.m7 = 0.0f; + matOrtho.m8 = 0.0f; + matOrtho.m9 = 0.0f; + matOrtho.m10 = -2.0f/fn; + matOrtho.m11 = 0.0f; + matOrtho.m12 = -((float)left + (float)right)/rl; + matOrtho.m13 = -((float)top + (float)bottom)/tb; + matOrtho.m14 = -((float)zfar + (float)znear)/fn; + matOrtho.m15 = 1.0f; + + *RLGL.State.currentMatrix = rlMatrixMultiply(*RLGL.State.currentMatrix, matOrtho); +} +#endif + +// Set the viewport area (transformation from normalized device coordinates to window coordinates) +// NOTE: We store current viewport dimensions +void rlViewport(int x, int y, int width, int height) +{ + glViewport(x, y, width, height); +} + +// Set clip planes distances +void rlSetClipPlanes(double nearPlane, double farPlane) +{ + rlCullDistanceNear = nearPlane; + rlCullDistanceFar = farPlane; +} + +// Get cull plane distance near +double rlGetCullDistanceNear(void) +{ + return rlCullDistanceNear; +} + +// Get cull plane distance far +double rlGetCullDistanceFar(void) +{ + return rlCullDistanceFar; +} + +//---------------------------------------------------------------------------------- +// Module Functions Definition - Vertex level operations +//---------------------------------------------------------------------------------- +#if defined(GRAPHICS_API_OPENGL_11) +// Fallback to OpenGL 1.1 function calls +//--------------------------------------- +void rlBegin(int mode) +{ + switch (mode) + { + case RL_LINES: glBegin(GL_LINES); break; + case RL_TRIANGLES: glBegin(GL_TRIANGLES); break; + case RL_QUADS: glBegin(GL_QUADS); break; + default: break; + } +} + +void rlEnd(void) { glEnd(); } +void rlVertex2i(int x, int y) { glVertex2i(x, y); } +void rlVertex2f(float x, float y) { glVertex2f(x, y); } +void rlVertex3f(float x, float y, float z) { glVertex3f(x, y, z); } +void rlTexCoord2f(float x, float y) { glTexCoord2f(x, y); } +void rlNormal3f(float x, float y, float z) { glNormal3f(x, y, z); } +void rlColor4ub(unsigned char r, unsigned char g, unsigned char b, unsigned char a) { glColor4ub(r, g, b, a); } +void rlColor3f(float x, float y, float z) { glColor3f(x, y, z); } +void rlColor4f(float x, float y, float z, float w) { glColor4f(x, y, z, w); } +#endif +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) +// Initialize drawing mode (how to organize vertex) +void rlBegin(int mode) +{ + // Draw mode can be RL_LINES, RL_TRIANGLES and RL_QUADS + // NOTE: In all three cases, vertex are accumulated over default internal vertex buffer + if (RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].mode != mode) + { + if (RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexCount > 0) + { + // Make sure current RLGL.currentBatch->draws[i].vertexCount is aligned a multiple of 4, + // that way, following QUADS drawing will keep aligned with index processing + // It implies adding some extra alignment vertex at the end of the draw, + // those vertex are not processed but they are considered as an additional offset + // for the next set of vertex to be drawn + if (RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].mode == RL_LINES) RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexAlignment = ((RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexCount < 4)? RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexCount : RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexCount%4); + else if (RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].mode == RL_TRIANGLES) RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexAlignment = ((RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexCount < 4)? 1 : (4 - (RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexCount%4))); + else RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexAlignment = 0; + + if (!rlCheckRenderBatchLimit(RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexAlignment)) + { + RLGL.State.vertexCounter += RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexAlignment; + RLGL.currentBatch->drawCounter++; + } + } + + if (RLGL.currentBatch->drawCounter >= RL_DEFAULT_BATCH_DRAWCALLS) rlDrawRenderBatch(RLGL.currentBatch); + + RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].mode = mode; + RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexCount = 0; + RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].textureId = RLGL.State.defaultTextureId; + } +} + +// Finish vertex providing +void rlEnd(void) +{ + // NOTE: Depth increment is dependant on rlOrtho(): z-near and z-far values, + // as well as depth buffer bit-depth (16bit or 24bit or 32bit) + // Correct increment formula would be: depthInc = (zfar - znear)/pow(2, bits) + RLGL.currentBatch->currentDepth += (1.0f/20000.0f); +} + +// Define one vertex (position) +// NOTE: Vertex position data is the basic information required for drawing +void rlVertex3f(float x, float y, float z) +{ + float tx = x; + float ty = y; + float tz = z; + + // Transform provided vector if required + if (RLGL.State.transformRequired) + { + tx = RLGL.State.transform.m0*x + RLGL.State.transform.m4*y + RLGL.State.transform.m8*z + RLGL.State.transform.m12; + ty = RLGL.State.transform.m1*x + RLGL.State.transform.m5*y + RLGL.State.transform.m9*z + RLGL.State.transform.m13; + tz = RLGL.State.transform.m2*x + RLGL.State.transform.m6*y + RLGL.State.transform.m10*z + RLGL.State.transform.m14; + } + + // WARNING: We can't break primitives when launching a new batch + // RL_LINES comes in pairs, RL_TRIANGLES come in groups of 3 vertices and RL_QUADS come in groups of 4 vertices + // We must check current draw.mode when a new vertex is required and finish the batch only if the draw.mode draw.vertexCount is %2, %3 or %4 + if (RLGL.State.vertexCounter > (RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].elementCount*4 - 4)) + { + if ((RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].mode == RL_LINES) && + (RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexCount%2 == 0)) + { + // Reached the maximum number of vertices for RL_LINES drawing + // Launch a draw call but keep current state for next vertices comming + // NOTE: We add +1 vertex to the check for security + rlCheckRenderBatchLimit(2 + 1); + } + else if ((RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].mode == RL_TRIANGLES) && + (RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexCount%3 == 0)) + { + rlCheckRenderBatchLimit(3 + 1); + } + else if ((RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].mode == RL_QUADS) && + (RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexCount%4 == 0)) + { + rlCheckRenderBatchLimit(4 + 1); + } + } + + // Add vertices + RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].vertices[3*RLGL.State.vertexCounter] = tx; + RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].vertices[3*RLGL.State.vertexCounter + 1] = ty; + RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].vertices[3*RLGL.State.vertexCounter + 2] = tz; + + // Add current texcoord + RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].texcoords[2*RLGL.State.vertexCounter] = RLGL.State.texcoordx; + RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].texcoords[2*RLGL.State.vertexCounter + 1] = RLGL.State.texcoordy; + + // Add current normal + RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].normals[3*RLGL.State.vertexCounter] = RLGL.State.normalx; + RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].normals[3*RLGL.State.vertexCounter + 1] = RLGL.State.normaly; + RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].normals[3*RLGL.State.vertexCounter + 2] = RLGL.State.normalz; + + // Add current color + RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].colors[4*RLGL.State.vertexCounter] = RLGL.State.colorr; + RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].colors[4*RLGL.State.vertexCounter + 1] = RLGL.State.colorg; + RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].colors[4*RLGL.State.vertexCounter + 2] = RLGL.State.colorb; + RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].colors[4*RLGL.State.vertexCounter + 3] = RLGL.State.colora; + + RLGL.State.vertexCounter++; + RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexCount++; +} + +// Define one vertex (position) +void rlVertex2f(float x, float y) +{ + rlVertex3f(x, y, RLGL.currentBatch->currentDepth); +} + +// Define one vertex (position) +void rlVertex2i(int x, int y) +{ + rlVertex3f((float)x, (float)y, RLGL.currentBatch->currentDepth); +} + +// Define one vertex (texture coordinate) +// NOTE: Texture coordinates are limited to QUADS only +void rlTexCoord2f(float x, float y) +{ + RLGL.State.texcoordx = x; + RLGL.State.texcoordy = y; +} + +// Define one vertex (normal) +// NOTE: Normals limited to TRIANGLES only? +void rlNormal3f(float x, float y, float z) +{ + float normalx = x; + float normaly = y; + float normalz = z; + if (RLGL.State.transformRequired) + { + normalx = RLGL.State.transform.m0*x + RLGL.State.transform.m4*y + RLGL.State.transform.m8*z; + normaly = RLGL.State.transform.m1*x + RLGL.State.transform.m5*y + RLGL.State.transform.m9*z; + normalz = RLGL.State.transform.m2*x + RLGL.State.transform.m6*y + RLGL.State.transform.m10*z; + } + float length = sqrtf(normalx*normalx + normaly*normaly + normalz*normalz); + if (length != 0.0f) + { + float ilength = 1.0f/length; + normalx *= ilength; + normaly *= ilength; + normalz *= ilength; + } + RLGL.State.normalx = normalx; + RLGL.State.normaly = normaly; + RLGL.State.normalz = normalz; +} + +// Define one vertex (color) +void rlColor4ub(unsigned char x, unsigned char y, unsigned char z, unsigned char w) +{ + RLGL.State.colorr = x; + RLGL.State.colorg = y; + RLGL.State.colorb = z; + RLGL.State.colora = w; +} + +// Define one vertex (color) +void rlColor4f(float r, float g, float b, float a) +{ + rlColor4ub((unsigned char)(r*255), (unsigned char)(g*255), (unsigned char)(b*255), (unsigned char)(a*255)); +} + +// Define one vertex (color) +void rlColor3f(float x, float y, float z) +{ + rlColor4ub((unsigned char)(x*255), (unsigned char)(y*255), (unsigned char)(z*255), 255); +} + +#endif + +//-------------------------------------------------------------------------------------- +// Module Functions Definition - OpenGL style functions (common to 1.1, 3.3+, ES2) +//-------------------------------------------------------------------------------------- + +// Set current texture to use +void rlSetTexture(unsigned int id) +{ + if (id == 0) + { +#if defined(GRAPHICS_API_OPENGL_11) + rlDisableTexture(); +#else + // NOTE: If quads batch limit is reached, we force a draw call and next batch starts + if (RLGL.State.vertexCounter >= + RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].elementCount*4) + { + rlDrawRenderBatch(RLGL.currentBatch); + } +#endif + } + else + { +#if defined(GRAPHICS_API_OPENGL_11) + rlEnableTexture(id); +#else + if (RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].textureId != id) + { + if (RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexCount > 0) + { + // Make sure current RLGL.currentBatch->draws[i].vertexCount is aligned a multiple of 4, + // that way, following QUADS drawing will keep aligned with index processing + // It implies adding some extra alignment vertex at the end of the draw, + // those vertex are not processed but they are considered as an additional offset + // for the next set of vertex to be drawn + if (RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].mode == RL_LINES) RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexAlignment = ((RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexCount < 4)? RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexCount : RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexCount%4); + else if (RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].mode == RL_TRIANGLES) RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexAlignment = ((RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexCount < 4)? 1 : (4 - (RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexCount%4))); + else RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexAlignment = 0; + + if (!rlCheckRenderBatchLimit(RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexAlignment)) + { + RLGL.State.vertexCounter += RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexAlignment; + + RLGL.currentBatch->drawCounter++; + } + } + + if (RLGL.currentBatch->drawCounter >= RL_DEFAULT_BATCH_DRAWCALLS) rlDrawRenderBatch(RLGL.currentBatch); + + RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].textureId = id; + RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexCount = 0; + } +#endif + } +} + +// Select and active a texture slot +void rlActiveTextureSlot(int slot) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + glActiveTexture(GL_TEXTURE0 + slot); +#endif +} + +// Enable texture +void rlEnableTexture(unsigned int id) +{ +#if defined(GRAPHICS_API_OPENGL_11) + glEnable(GL_TEXTURE_2D); +#endif + glBindTexture(GL_TEXTURE_2D, id); +} + +// Disable texture +void rlDisableTexture(void) +{ +#if defined(GRAPHICS_API_OPENGL_11) + glDisable(GL_TEXTURE_2D); +#endif + glBindTexture(GL_TEXTURE_2D, 0); +} + +// Enable texture cubemap +void rlEnableTextureCubemap(unsigned int id) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + glBindTexture(GL_TEXTURE_CUBE_MAP, id); +#endif +} + +// Disable texture cubemap +void rlDisableTextureCubemap(void) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + glBindTexture(GL_TEXTURE_CUBE_MAP, 0); +#endif +} + +// Set texture parameters (wrap mode/filter mode) +void rlTextureParameters(unsigned int id, int param, int value) +{ + glBindTexture(GL_TEXTURE_2D, id); + +#if !defined(GRAPHICS_API_OPENGL_11) + // Reset anisotropy filter, in case it was set + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, 1.0f); +#endif + + switch (param) + { + case RL_TEXTURE_WRAP_S: + case RL_TEXTURE_WRAP_T: + { + if (value == RL_TEXTURE_WRAP_MIRROR_CLAMP) + { +#if !defined(GRAPHICS_API_OPENGL_11) + if (RLGL.ExtSupported.texMirrorClamp) glTexParameteri(GL_TEXTURE_2D, param, value); + else TRACELOG(RL_LOG_WARNING, "GL: Clamp mirror wrap mode not supported (GL_MIRROR_CLAMP_EXT)"); +#endif + } + else glTexParameteri(GL_TEXTURE_2D, param, value); + + } break; + case RL_TEXTURE_MAG_FILTER: + case RL_TEXTURE_MIN_FILTER: glTexParameteri(GL_TEXTURE_2D, param, value); break; + case RL_TEXTURE_FILTER_ANISOTROPIC: + { +#if !defined(GRAPHICS_API_OPENGL_11) + if (value <= RLGL.ExtSupported.maxAnisotropyLevel) glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, (float)value); + else if (RLGL.ExtSupported.maxAnisotropyLevel > 0.0f) + { + TRACELOG(RL_LOG_WARNING, "GL: Maximum anisotropic filter level supported is %iX", id, (int)RLGL.ExtSupported.maxAnisotropyLevel); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, (float)value); + } + else TRACELOG(RL_LOG_WARNING, "GL: Anisotropic filtering not supported"); +#endif + } break; +#if defined(GRAPHICS_API_OPENGL_33) + case RL_TEXTURE_MIPMAP_BIAS_RATIO: glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_LOD_BIAS, value/100.0f); +#endif + default: break; + } + + glBindTexture(GL_TEXTURE_2D, 0); +} + +// Set cubemap parameters (wrap mode/filter mode) +void rlCubemapParameters(unsigned int id, int param, int value) +{ +#if !defined(GRAPHICS_API_OPENGL_11) + glBindTexture(GL_TEXTURE_CUBE_MAP, id); + + // Reset anisotropy filter, in case it was set + glTexParameterf(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAX_ANISOTROPY_EXT, 1.0f); + + switch (param) + { + case RL_TEXTURE_WRAP_S: + case RL_TEXTURE_WRAP_T: + { + if (value == RL_TEXTURE_WRAP_MIRROR_CLAMP) + { + if (RLGL.ExtSupported.texMirrorClamp) glTexParameteri(GL_TEXTURE_CUBE_MAP, param, value); + else TRACELOG(RL_LOG_WARNING, "GL: Clamp mirror wrap mode not supported (GL_MIRROR_CLAMP_EXT)"); + } + else glTexParameteri(GL_TEXTURE_CUBE_MAP, param, value); + + } break; + case RL_TEXTURE_MAG_FILTER: + case RL_TEXTURE_MIN_FILTER: glTexParameteri(GL_TEXTURE_CUBE_MAP, param, value); break; + case RL_TEXTURE_FILTER_ANISOTROPIC: + { + if (value <= RLGL.ExtSupported.maxAnisotropyLevel) glTexParameterf(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAX_ANISOTROPY_EXT, (float)value); + else if (RLGL.ExtSupported.maxAnisotropyLevel > 0.0f) + { + TRACELOG(RL_LOG_WARNING, "GL: Maximum anisotropic filter level supported is %iX", id, (int)RLGL.ExtSupported.maxAnisotropyLevel); + glTexParameterf(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAX_ANISOTROPY_EXT, (float)value); + } + else TRACELOG(RL_LOG_WARNING, "GL: Anisotropic filtering not supported"); + } break; +#if defined(GRAPHICS_API_OPENGL_33) + case RL_TEXTURE_MIPMAP_BIAS_RATIO: glTexParameterf(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_LOD_BIAS, value/100.0f); +#endif + default: break; + } + + glBindTexture(GL_TEXTURE_CUBE_MAP, 0); +#endif +} + +// Enable shader program +void rlEnableShader(unsigned int id) +{ +#if (defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)) + glUseProgram(id); +#endif +} + +// Disable shader program +void rlDisableShader(void) +{ +#if (defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)) + glUseProgram(0); +#endif +} + +// Enable rendering to texture (fbo) +void rlEnableFramebuffer(unsigned int id) +{ +#if (defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)) && defined(RLGL_RENDER_TEXTURES_HINT) + glBindFramebuffer(GL_FRAMEBUFFER, id); +#endif +} + +// return the active render texture (fbo) +unsigned int rlGetActiveFramebuffer(void) +{ + GLint fboId = 0; +#if (defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES3)) && defined(RLGL_RENDER_TEXTURES_HINT) + glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &fboId); +#endif + return fboId; +} + +// Disable rendering to texture +void rlDisableFramebuffer(void) +{ +#if (defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)) && defined(RLGL_RENDER_TEXTURES_HINT) + glBindFramebuffer(GL_FRAMEBUFFER, 0); +#endif +} + +// Blit active framebuffer to main framebuffer +void rlBlitFramebuffer(int srcX, int srcY, int srcWidth, int srcHeight, int dstX, int dstY, int dstWidth, int dstHeight, int bufferMask) +{ +#if (defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES3)) && defined(RLGL_RENDER_TEXTURES_HINT) + glBlitFramebuffer(srcX, srcY, srcWidth, srcHeight, dstX, dstY, dstWidth, dstHeight, bufferMask, GL_NEAREST); +#endif +} + +// Bind framebuffer object (fbo) +void rlBindFramebuffer(unsigned int target, unsigned int framebuffer) +{ +#if (defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)) && defined(RLGL_RENDER_TEXTURES_HINT) + glBindFramebuffer(target, framebuffer); +#endif +} + +// Activate multiple draw color buffers +// NOTE: One color buffer is always active by default +void rlActiveDrawBuffers(int count) +{ +#if ((defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES3)) && defined(RLGL_RENDER_TEXTURES_HINT)) + // NOTE: Maximum number of draw buffers supported is implementation dependant, + // it can be queried with glGet*() but it must be at least 8 + //GLint maxDrawBuffers = 0; + //glGetIntegerv(GL_MAX_DRAW_BUFFERS, &maxDrawBuffers); + + if (count > 0) + { + if (count > 8) TRACELOG(LOG_WARNING, "GL: Max color buffers limited to 8"); + else + { + unsigned int buffers[8] = { +#if defined(GRAPHICS_API_OPENGL_ES3) + GL_COLOR_ATTACHMENT0_EXT, + GL_COLOR_ATTACHMENT1_EXT, + GL_COLOR_ATTACHMENT2_EXT, + GL_COLOR_ATTACHMENT3_EXT, + GL_COLOR_ATTACHMENT4_EXT, + GL_COLOR_ATTACHMENT5_EXT, + GL_COLOR_ATTACHMENT6_EXT, + GL_COLOR_ATTACHMENT7_EXT, +#else + GL_COLOR_ATTACHMENT0, + GL_COLOR_ATTACHMENT1, + GL_COLOR_ATTACHMENT2, + GL_COLOR_ATTACHMENT3, + GL_COLOR_ATTACHMENT4, + GL_COLOR_ATTACHMENT5, + GL_COLOR_ATTACHMENT6, + GL_COLOR_ATTACHMENT7, +#endif + }; + +#if defined(GRAPHICS_API_OPENGL_ES3) + glDrawBuffersEXT(count, buffers); +#else + glDrawBuffers(count, buffers); +#endif + } + } + else TRACELOG(LOG_WARNING, "GL: One color buffer active by default"); +#endif +} + +//---------------------------------------------------------------------------------- +// General render state configuration +//---------------------------------------------------------------------------------- + +// Enable color blending +void rlEnableColorBlend(void) { glEnable(GL_BLEND); } + +// Disable color blending +void rlDisableColorBlend(void) { glDisable(GL_BLEND); } + +// Enable depth test +void rlEnableDepthTest(void) { glEnable(GL_DEPTH_TEST); } + +// Disable depth test +void rlDisableDepthTest(void) { glDisable(GL_DEPTH_TEST); } + +// Enable depth write +void rlEnableDepthMask(void) { glDepthMask(GL_TRUE); } + +// Disable depth write +void rlDisableDepthMask(void) { glDepthMask(GL_FALSE); } + +// Enable backface culling +void rlEnableBackfaceCulling(void) { glEnable(GL_CULL_FACE); } + +// Disable backface culling +void rlDisableBackfaceCulling(void) { glDisable(GL_CULL_FACE); } + +// Set color mask active for screen read/draw +void rlColorMask(bool r, bool g, bool b, bool a) { glColorMask(r, g, b, a); } + +// Set face culling mode +void rlSetCullFace(int mode) +{ + switch (mode) + { + case RL_CULL_FACE_BACK: glCullFace(GL_BACK); break; + case RL_CULL_FACE_FRONT: glCullFace(GL_FRONT); break; + default: break; + } +} + +// Enable scissor test +void rlEnableScissorTest(void) { glEnable(GL_SCISSOR_TEST); } + +// Disable scissor test +void rlDisableScissorTest(void) { glDisable(GL_SCISSOR_TEST); } + +// Scissor test +void rlScissor(int x, int y, int width, int height) { glScissor(x, y, width, height); } + +// Enable wire mode +void rlEnableWireMode(void) +{ +#if defined(GRAPHICS_API_OPENGL_11) || defined(GRAPHICS_API_OPENGL_33) + // NOTE: glPolygonMode() not available on OpenGL ES + glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); +#endif +} + +// Enable point mode +void rlEnablePointMode(void) +{ +#if defined(GRAPHICS_API_OPENGL_11) || defined(GRAPHICS_API_OPENGL_33) + // NOTE: glPolygonMode() not available on OpenGL ES + glPolygonMode(GL_FRONT_AND_BACK, GL_POINT); + glEnable(GL_PROGRAM_POINT_SIZE); +#endif +} + +// Disable wire mode +void rlDisableWireMode(void) +{ +#if defined(GRAPHICS_API_OPENGL_11) || defined(GRAPHICS_API_OPENGL_33) + // NOTE: glPolygonMode() not available on OpenGL ES + glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); +#endif +} + +// Set the line drawing width +void rlSetLineWidth(float width) { glLineWidth(width); } + +// Get the line drawing width +float rlGetLineWidth(void) +{ + float width = 0; + glGetFloatv(GL_LINE_WIDTH, &width); + return width; +} + +// Enable line aliasing +void rlEnableSmoothLines(void) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_11) + glEnable(GL_LINE_SMOOTH); +#endif +} + +// Disable line aliasing +void rlDisableSmoothLines(void) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_11) + glDisable(GL_LINE_SMOOTH); +#endif +} + +// Enable stereo rendering +void rlEnableStereoRender(void) +{ +#if (defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)) + RLGL.State.stereoRender = true; +#endif +} + +// Disable stereo rendering +void rlDisableStereoRender(void) +{ +#if (defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)) + RLGL.State.stereoRender = false; +#endif +} + +// Check if stereo render is enabled +bool rlIsStereoRenderEnabled(void) +{ +#if (defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)) + return RLGL.State.stereoRender; +#else + return false; +#endif +} + +// Clear color buffer with color +void rlClearColor(unsigned char r, unsigned char g, unsigned char b, unsigned char a) +{ + // Color values clamp to 0.0f(0) and 1.0f(255) + float cr = (float)r/255; + float cg = (float)g/255; + float cb = (float)b/255; + float ca = (float)a/255; + + glClearColor(cr, cg, cb, ca); +} + +// Clear used screen buffers (color and depth) +void rlClearScreenBuffers(void) +{ + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // Clear used buffers: Color and Depth (Depth is used for 3D) + //glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); // Stencil buffer not used... +} + +// Check and log OpenGL error codes +void rlCheckErrors(void) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + int check = 1; + while (check) + { + const GLenum err = glGetError(); + switch (err) + { + case GL_NO_ERROR: check = 0; break; + case 0x0500: TRACELOG(RL_LOG_WARNING, "GL: Error detected: GL_INVALID_ENUM"); break; + case 0x0501: TRACELOG(RL_LOG_WARNING, "GL: Error detected: GL_INVALID_VALUE"); break; + case 0x0502: TRACELOG(RL_LOG_WARNING, "GL: Error detected: GL_INVALID_OPERATION"); break; + case 0x0503: TRACELOG(RL_LOG_WARNING, "GL: Error detected: GL_STACK_OVERFLOW"); break; + case 0x0504: TRACELOG(RL_LOG_WARNING, "GL: Error detected: GL_STACK_UNDERFLOW"); break; + case 0x0505: TRACELOG(RL_LOG_WARNING, "GL: Error detected: GL_OUT_OF_MEMORY"); break; + case 0x0506: TRACELOG(RL_LOG_WARNING, "GL: Error detected: GL_INVALID_FRAMEBUFFER_OPERATION"); break; + default: TRACELOG(RL_LOG_WARNING, "GL: Error detected: Unknown error code: %x", err); break; + } + } +#endif +} + +// Set blend mode +void rlSetBlendMode(int mode) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + if ((RLGL.State.currentBlendMode != mode) || ((mode == RL_BLEND_CUSTOM || mode == RL_BLEND_CUSTOM_SEPARATE) && RLGL.State.glCustomBlendModeModified)) + { + rlDrawRenderBatch(RLGL.currentBatch); + + switch (mode) + { + case RL_BLEND_ALPHA: glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glBlendEquation(GL_FUNC_ADD); break; + case RL_BLEND_ADDITIVE: glBlendFunc(GL_SRC_ALPHA, GL_ONE); glBlendEquation(GL_FUNC_ADD); break; + case RL_BLEND_MULTIPLIED: glBlendFunc(GL_DST_COLOR, GL_ONE_MINUS_SRC_ALPHA); glBlendEquation(GL_FUNC_ADD); break; + case RL_BLEND_ADD_COLORS: glBlendFunc(GL_ONE, GL_ONE); glBlendEquation(GL_FUNC_ADD); break; + case RL_BLEND_SUBTRACT_COLORS: glBlendFunc(GL_ONE, GL_ONE); glBlendEquation(GL_FUNC_SUBTRACT); break; + case RL_BLEND_ALPHA_PREMULTIPLY: glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); glBlendEquation(GL_FUNC_ADD); break; + case RL_BLEND_CUSTOM: + { + // NOTE: Using GL blend src/dst factors and GL equation configured with rlSetBlendFactors() + glBlendFunc(RLGL.State.glBlendSrcFactor, RLGL.State.glBlendDstFactor); glBlendEquation(RLGL.State.glBlendEquation); + + } break; + case RL_BLEND_CUSTOM_SEPARATE: + { + // NOTE: Using GL blend src/dst factors and GL equation configured with rlSetBlendFactorsSeparate() + glBlendFuncSeparate(RLGL.State.glBlendSrcFactorRGB, RLGL.State.glBlendDestFactorRGB, RLGL.State.glBlendSrcFactorAlpha, RLGL.State.glBlendDestFactorAlpha); + glBlendEquationSeparate(RLGL.State.glBlendEquationRGB, RLGL.State.glBlendEquationAlpha); + + } break; + default: break; + } + + RLGL.State.currentBlendMode = mode; + RLGL.State.glCustomBlendModeModified = false; + } +#endif +} + +// Set blending mode factor and equation +void rlSetBlendFactors(int glSrcFactor, int glDstFactor, int glEquation) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + if ((RLGL.State.glBlendSrcFactor != glSrcFactor) || + (RLGL.State.glBlendDstFactor != glDstFactor) || + (RLGL.State.glBlendEquation != glEquation)) + { + RLGL.State.glBlendSrcFactor = glSrcFactor; + RLGL.State.glBlendDstFactor = glDstFactor; + RLGL.State.glBlendEquation = glEquation; + + RLGL.State.glCustomBlendModeModified = true; + } +#endif +} + +// Set blending mode factor and equation separately for RGB and alpha +void rlSetBlendFactorsSeparate(int glSrcRGB, int glDstRGB, int glSrcAlpha, int glDstAlpha, int glEqRGB, int glEqAlpha) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + if ((RLGL.State.glBlendSrcFactorRGB != glSrcRGB) || + (RLGL.State.glBlendDestFactorRGB != glDstRGB) || + (RLGL.State.glBlendSrcFactorAlpha != glSrcAlpha) || + (RLGL.State.glBlendDestFactorAlpha != glDstAlpha) || + (RLGL.State.glBlendEquationRGB != glEqRGB) || + (RLGL.State.glBlendEquationAlpha != glEqAlpha)) + { + RLGL.State.glBlendSrcFactorRGB = glSrcRGB; + RLGL.State.glBlendDestFactorRGB = glDstRGB; + RLGL.State.glBlendSrcFactorAlpha = glSrcAlpha; + RLGL.State.glBlendDestFactorAlpha = glDstAlpha; + RLGL.State.glBlendEquationRGB = glEqRGB; + RLGL.State.glBlendEquationAlpha = glEqAlpha; + + RLGL.State.glCustomBlendModeModified = true; + } +#endif +} + +//---------------------------------------------------------------------------------- +// Module Functions Definition - OpenGL Debug +//---------------------------------------------------------------------------------- +#if defined(RLGL_ENABLE_OPENGL_DEBUG_CONTEXT) && defined(GRAPHICS_API_OPENGL_43) +static void GLAPIENTRY rlDebugMessageCallback(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar *message, const void *userParam) +{ + // Ignore non-significant error/warning codes (NVidia drivers) + // NOTE: Here there are the details with a sample output: + // - #131169 - Framebuffer detailed info: The driver allocated storage for renderbuffer 2. (severity: low) + // - #131185 - Buffer detailed info: Buffer object 1 (bound to GL_ELEMENT_ARRAY_BUFFER_ARB, usage hint is GL_ENUM_88e4) + // will use VIDEO memory as the source for buffer object operations. (severity: low) + // - #131218 - Program/shader state performance warning: Vertex shader in program 7 is being recompiled based on GL state. (severity: medium) + // - #131204 - Texture state usage warning: The texture object (0) bound to texture image unit 0 does not have + // a defined base level and cannot be used for texture mapping. (severity: low) + if ((id == 131169) || (id == 131185) || (id == 131218) || (id == 131204)) return; + + const char *msgSource = NULL; + switch (source) + { + case GL_DEBUG_SOURCE_API: msgSource = "API"; break; + case GL_DEBUG_SOURCE_WINDOW_SYSTEM: msgSource = "WINDOW_SYSTEM"; break; + case GL_DEBUG_SOURCE_SHADER_COMPILER: msgSource = "SHADER_COMPILER"; break; + case GL_DEBUG_SOURCE_THIRD_PARTY: msgSource = "THIRD_PARTY"; break; + case GL_DEBUG_SOURCE_APPLICATION: msgSource = "APPLICATION"; break; + case GL_DEBUG_SOURCE_OTHER: msgSource = "OTHER"; break; + default: break; + } + + const char *msgType = NULL; + switch (type) + { + case GL_DEBUG_TYPE_ERROR: msgType = "ERROR"; break; + case GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR: msgType = "DEPRECATED_BEHAVIOR"; break; + case GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR: msgType = "UNDEFINED_BEHAVIOR"; break; + case GL_DEBUG_TYPE_PORTABILITY: msgType = "PORTABILITY"; break; + case GL_DEBUG_TYPE_PERFORMANCE: msgType = "PERFORMANCE"; break; + case GL_DEBUG_TYPE_MARKER: msgType = "MARKER"; break; + case GL_DEBUG_TYPE_PUSH_GROUP: msgType = "PUSH_GROUP"; break; + case GL_DEBUG_TYPE_POP_GROUP: msgType = "POP_GROUP"; break; + case GL_DEBUG_TYPE_OTHER: msgType = "OTHER"; break; + default: break; + } + + const char *msgSeverity = "DEFAULT"; + switch (severity) + { + case GL_DEBUG_SEVERITY_LOW: msgSeverity = "LOW"; break; + case GL_DEBUG_SEVERITY_MEDIUM: msgSeverity = "MEDIUM"; break; + case GL_DEBUG_SEVERITY_HIGH: msgSeverity = "HIGH"; break; + case GL_DEBUG_SEVERITY_NOTIFICATION: msgSeverity = "NOTIFICATION"; break; + default: break; + } + + TRACELOG(LOG_WARNING, "GL: OpenGL debug message: %s", message); + TRACELOG(LOG_WARNING, " > Type: %s", msgType); + TRACELOG(LOG_WARNING, " > Source = %s", msgSource); + TRACELOG(LOG_WARNING, " > Severity = %s", msgSeverity); +} +#endif + +//---------------------------------------------------------------------------------- +// Module Functions Definition - rlgl functionality +//---------------------------------------------------------------------------------- + +// Initialize rlgl: OpenGL extensions, default buffers/shaders/textures, OpenGL states +void rlglInit(int width, int height) +{ + // Enable OpenGL debug context if required +#if defined(RLGL_ENABLE_OPENGL_DEBUG_CONTEXT) && defined(GRAPHICS_API_OPENGL_43) + if ((glDebugMessageCallback != NULL) && (glDebugMessageControl != NULL)) + { + glDebugMessageCallback(rlDebugMessageCallback, 0); + // glDebugMessageControl(GL_DEBUG_SOURCE_API, GL_DEBUG_TYPE_ERROR, GL_DEBUG_SEVERITY_HIGH, 0, 0, GL_TRUE); + + // Debug context options: + // - GL_DEBUG_OUTPUT - Faster version but not useful for breakpoints + // - GL_DEBUG_OUTPUT_SYNCHRONUS - Callback is in sync with errors, so a breakpoint can be placed on the callback in order to get a stacktrace for the GL error + glEnable(GL_DEBUG_OUTPUT); + glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS); + } +#endif + +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + // Init default white texture + unsigned char pixels[4] = { 255, 255, 255, 255 }; // 1 pixel RGBA (4 bytes) + RLGL.State.defaultTextureId = rlLoadTexture(pixels, 1, 1, RL_PIXELFORMAT_UNCOMPRESSED_R8G8B8A8, 1); + + if (RLGL.State.defaultTextureId != 0) TRACELOG(RL_LOG_INFO, "TEXTURE: [ID %i] Default texture loaded successfully", RLGL.State.defaultTextureId); + else TRACELOG(RL_LOG_WARNING, "TEXTURE: Failed to load default texture"); + + // Init default Shader (customized for GL 3.3 and ES2) + // Loaded: RLGL.State.defaultShaderId + RLGL.State.defaultShaderLocs + rlLoadShaderDefault(); + RLGL.State.currentShaderId = RLGL.State.defaultShaderId; + RLGL.State.currentShaderLocs = RLGL.State.defaultShaderLocs; + + // Init default vertex arrays buffers + // Simulate that the default shader has the location RL_SHADER_LOC_VERTEX_NORMAL to bind the normal buffer for the default render batch + RLGL.State.currentShaderLocs[RL_SHADER_LOC_VERTEX_NORMAL] = RL_DEFAULT_SHADER_ATTRIB_LOCATION_NORMAL; + RLGL.defaultBatch = rlLoadRenderBatch(RL_DEFAULT_BATCH_BUFFERS, RL_DEFAULT_BATCH_BUFFER_ELEMENTS); + RLGL.State.currentShaderLocs[RL_SHADER_LOC_VERTEX_NORMAL] = -1; + RLGL.currentBatch = &RLGL.defaultBatch; + + // Init stack matrices (emulating OpenGL 1.1) + for (int i = 0; i < RL_MAX_MATRIX_STACK_SIZE; i++) RLGL.State.stack[i] = rlMatrixIdentity(); + + // Init internal matrices + RLGL.State.transform = rlMatrixIdentity(); + RLGL.State.projection = rlMatrixIdentity(); + RLGL.State.modelview = rlMatrixIdentity(); + RLGL.State.currentMatrix = &RLGL.State.modelview; +#endif // GRAPHICS_API_OPENGL_33 || GRAPHICS_API_OPENGL_ES2 + + // Initialize OpenGL default states + //---------------------------------------------------------- + // Init state: Depth test + glDepthFunc(GL_LEQUAL); // Type of depth testing to apply + glDisable(GL_DEPTH_TEST); // Disable depth testing for 2D (only used for 3D) + + // Init state: Blending mode + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); // Color blending function (how colors are mixed) + glEnable(GL_BLEND); // Enable color blending (required to work with transparencies) + + // Init state: Culling + // NOTE: All shapes/models triangles are drawn CCW + glCullFace(GL_BACK); // Cull the back face (default) + glFrontFace(GL_CCW); // Front face are defined counter clockwise (default) + glEnable(GL_CULL_FACE); // Enable backface culling + + // Init state: Cubemap seamless +#if defined(GRAPHICS_API_OPENGL_33) + glEnable(GL_TEXTURE_CUBE_MAP_SEAMLESS); // Seamless cubemaps (not supported on OpenGL ES 2.0) +#endif + +#if defined(GRAPHICS_API_OPENGL_11) + // Init state: Color hints (deprecated in OpenGL 3.0+) + glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST); // Improve quality of color and texture coordinate interpolation + glShadeModel(GL_SMOOTH); // Smooth shading between vertex (vertex colors interpolation) +#endif + +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + // Store screen size into global variables + RLGL.State.framebufferWidth = width; + RLGL.State.framebufferHeight = height; + + TRACELOG(RL_LOG_INFO, "RLGL: Default OpenGL state initialized successfully"); + //---------------------------------------------------------- +#endif + + // Init state: Color/Depth buffers clear + glClearColor(0.0f, 0.0f, 0.0f, 1.0f); // Set clear color (black) + glClearDepth(1.0f); // Set clear depth value (default) + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // Clear color and depth buffers (depth buffer required for 3D) +} + +// Vertex Buffer Object deinitialization (memory free) +void rlglClose(void) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + rlUnloadRenderBatch(RLGL.defaultBatch); + + rlUnloadShaderDefault(); // Unload default shader + + glDeleteTextures(1, &RLGL.State.defaultTextureId); // Unload default texture + TRACELOG(RL_LOG_INFO, "TEXTURE: [ID %i] Default texture unloaded successfully", RLGL.State.defaultTextureId); +#endif +} + +// Load OpenGL extensions +// NOTE: External loader function must be provided +void rlLoadExtensions(void *loader) +{ +#if defined(GRAPHICS_API_OPENGL_33) // Also defined for GRAPHICS_API_OPENGL_21 + // NOTE: glad is generated and contains only required OpenGL 3.3 Core extensions (and lower versions) + if (gladLoadGL((GLADloadfunc)loader) == 0) TRACELOG(RL_LOG_WARNING, "GLAD: Cannot load OpenGL extensions"); + else TRACELOG(RL_LOG_INFO, "GLAD: OpenGL extensions loaded successfully"); + + // Get number of supported extensions + GLint numExt = 0; + glGetIntegerv(GL_NUM_EXTENSIONS, &numExt); + TRACELOG(RL_LOG_INFO, "GL: Supported extensions count: %i", numExt); + +#if defined(RLGL_SHOW_GL_DETAILS_INFO) + // Get supported extensions list + // WARNING: glGetStringi() not available on OpenGL 2.1 + TRACELOG(RL_LOG_INFO, "GL: OpenGL extensions:"); + for (int i = 0; i < numExt; i++) TRACELOG(RL_LOG_INFO, " %s", glGetStringi(GL_EXTENSIONS, i)); +#endif + +#if defined(GRAPHICS_API_OPENGL_21) + // Register supported extensions flags + // Optional OpenGL 2.1 extensions + RLGL.ExtSupported.vao = GLAD_GL_ARB_vertex_array_object; + RLGL.ExtSupported.instancing = (GLAD_GL_EXT_draw_instanced && GLAD_GL_ARB_instanced_arrays); + RLGL.ExtSupported.texNPOT = GLAD_GL_ARB_texture_non_power_of_two; + RLGL.ExtSupported.texFloat32 = GLAD_GL_ARB_texture_float; + RLGL.ExtSupported.texFloat16 = GLAD_GL_ARB_texture_float; + RLGL.ExtSupported.texDepth = GLAD_GL_ARB_depth_texture; + RLGL.ExtSupported.maxDepthBits = 32; + RLGL.ExtSupported.texAnisoFilter = GLAD_GL_EXT_texture_filter_anisotropic; + RLGL.ExtSupported.texMirrorClamp = GLAD_GL_EXT_texture_mirror_clamp; +#else + // Register supported extensions flags + // OpenGL 3.3 extensions supported by default (core) + RLGL.ExtSupported.vao = true; + RLGL.ExtSupported.instancing = true; + RLGL.ExtSupported.texNPOT = true; + RLGL.ExtSupported.texFloat32 = true; + RLGL.ExtSupported.texFloat16 = true; + RLGL.ExtSupported.texDepth = true; + RLGL.ExtSupported.maxDepthBits = 32; + RLGL.ExtSupported.texAnisoFilter = true; + RLGL.ExtSupported.texMirrorClamp = true; +#endif + + // Optional OpenGL 3.3 extensions + RLGL.ExtSupported.texCompASTC = GLAD_GL_KHR_texture_compression_astc_hdr && GLAD_GL_KHR_texture_compression_astc_ldr; + RLGL.ExtSupported.texCompDXT = GLAD_GL_EXT_texture_compression_s3tc; // Texture compression: DXT + RLGL.ExtSupported.texCompETC2 = GLAD_GL_ARB_ES3_compatibility; // Texture compression: ETC2/EAC + #if defined(GRAPHICS_API_OPENGL_43) + RLGL.ExtSupported.computeShader = GLAD_GL_ARB_compute_shader; + RLGL.ExtSupported.ssbo = GLAD_GL_ARB_shader_storage_buffer_object; + #endif + +#endif // GRAPHICS_API_OPENGL_33 + +#if defined(GRAPHICS_API_OPENGL_ES3) + // Register supported extensions flags + // OpenGL ES 3.0 extensions supported by default (or it should be) + RLGL.ExtSupported.vao = true; + RLGL.ExtSupported.instancing = true; + RLGL.ExtSupported.texNPOT = true; + RLGL.ExtSupported.texFloat32 = true; + RLGL.ExtSupported.texFloat16 = true; + RLGL.ExtSupported.texDepth = true; + RLGL.ExtSupported.texDepthWebGL = true; + RLGL.ExtSupported.maxDepthBits = 24; + RLGL.ExtSupported.texAnisoFilter = true; + RLGL.ExtSupported.texMirrorClamp = true; + // TODO: Check for additional OpenGL ES 3.0 supported extensions: + //RLGL.ExtSupported.texCompDXT = true; + //RLGL.ExtSupported.texCompETC1 = true; + //RLGL.ExtSupported.texCompETC2 = true; + //RLGL.ExtSupported.texCompPVRT = true; + //RLGL.ExtSupported.texCompASTC = true; + //RLGL.ExtSupported.maxAnisotropyLevel = true; + //RLGL.ExtSupported.computeShader = true; + //RLGL.ExtSupported.ssbo = true; + +#elif defined(GRAPHICS_API_OPENGL_ES2) + + #if defined(PLATFORM_DESKTOP_GLFW) || defined(PLATFORM_DESKTOP_SDL) + // TODO: Support GLAD loader for OpenGL ES 3.0 + if (gladLoadGLES2((GLADloadfunc)loader) == 0) TRACELOG(RL_LOG_WARNING, "GLAD: Cannot load OpenGL ES2.0 functions"); + else TRACELOG(RL_LOG_INFO, "GLAD: OpenGL ES 2.0 loaded successfully"); + #endif + + // Get supported extensions list + GLint numExt = 0; + const char **extList = RL_MALLOC(512*sizeof(const char *)); // Allocate 512 strings pointers (2 KB) + const char *extensions = (const char *)glGetString(GL_EXTENSIONS); // One big const string + + // NOTE: We have to duplicate string because glGetString() returns a const string + int size = strlen(extensions) + 1; // Get extensions string size in bytes + char *extensionsDup = (char *)RL_CALLOC(size, sizeof(char)); + strcpy(extensionsDup, extensions); + extList[numExt] = extensionsDup; + + for (int i = 0; i < size; i++) + { + if (extensionsDup[i] == ' ') + { + extensionsDup[i] = '\0'; + numExt++; + extList[numExt] = &extensionsDup[i + 1]; + } + } + + TRACELOG(RL_LOG_INFO, "GL: Supported extensions count: %i", numExt); + +#if defined(RLGL_SHOW_GL_DETAILS_INFO) + TRACELOG(RL_LOG_INFO, "GL: OpenGL extensions:"); + for (int i = 0; i < numExt; i++) TRACELOG(RL_LOG_INFO, " %s", extList[i]); +#endif + + // Check required extensions + for (int i = 0; i < numExt; i++) + { + // Check VAO support + // NOTE: Only check on OpenGL ES, OpenGL 3.3 has VAO support as core feature + if (strcmp(extList[i], (const char *)"GL_OES_vertex_array_object") == 0) + { + // The extension is supported by our hardware and driver, try to get related functions pointers + // NOTE: emscripten does not support VAOs natively, it uses emulation and it reduces overall performance... + glGenVertexArrays = (PFNGLGENVERTEXARRAYSOESPROC)((rlglLoadProc)loader)("glGenVertexArraysOES"); + glBindVertexArray = (PFNGLBINDVERTEXARRAYOESPROC)((rlglLoadProc)loader)("glBindVertexArrayOES"); + glDeleteVertexArrays = (PFNGLDELETEVERTEXARRAYSOESPROC)((rlglLoadProc)loader)("glDeleteVertexArraysOES"); + //glIsVertexArray = (PFNGLISVERTEXARRAYOESPROC)loader("glIsVertexArrayOES"); // NOTE: Fails in WebGL, omitted + + if ((glGenVertexArrays != NULL) && (glBindVertexArray != NULL) && (glDeleteVertexArrays != NULL)) RLGL.ExtSupported.vao = true; + } + + // Check instanced rendering support + if (strstr(extList[i], (const char*)"instanced_arrays") != NULL) // Broad check for instanced_arrays + { + // Specific check + if (strcmp(extList[i], (const char *)"GL_ANGLE_instanced_arrays") == 0) // ANGLE + { + glDrawArraysInstanced = (PFNGLDRAWARRAYSINSTANCEDEXTPROC)((rlglLoadProc)loader)("glDrawArraysInstancedANGLE"); + glDrawElementsInstanced = (PFNGLDRAWELEMENTSINSTANCEDEXTPROC)((rlglLoadProc)loader)("glDrawElementsInstancedANGLE"); + glVertexAttribDivisor = (PFNGLVERTEXATTRIBDIVISOREXTPROC)((rlglLoadProc)loader)("glVertexAttribDivisorANGLE"); + } + else if (strcmp(extList[i], (const char *)"GL_EXT_instanced_arrays") == 0) // EXT + { + glDrawArraysInstanced = (PFNGLDRAWARRAYSINSTANCEDEXTPROC)((rlglLoadProc)loader)("glDrawArraysInstancedEXT"); + glDrawElementsInstanced = (PFNGLDRAWELEMENTSINSTANCEDEXTPROC)((rlglLoadProc)loader)("glDrawElementsInstancedEXT"); + glVertexAttribDivisor = (PFNGLVERTEXATTRIBDIVISOREXTPROC)((rlglLoadProc)loader)("glVertexAttribDivisorEXT"); + } + else if (strcmp(extList[i], (const char *)"GL_NV_instanced_arrays") == 0) // NVIDIA GLES + { + glDrawArraysInstanced = (PFNGLDRAWARRAYSINSTANCEDEXTPROC)((rlglLoadProc)loader)("glDrawArraysInstancedNV"); + glDrawElementsInstanced = (PFNGLDRAWELEMENTSINSTANCEDEXTPROC)((rlglLoadProc)loader)("glDrawElementsInstancedNV"); + glVertexAttribDivisor = (PFNGLVERTEXATTRIBDIVISOREXTPROC)((rlglLoadProc)loader)("glVertexAttribDivisorNV"); + } + + // The feature will only be marked as supported if the elements from GL_XXX_instanced_arrays are present + if ((glDrawArraysInstanced != NULL) && (glDrawElementsInstanced != NULL) && (glVertexAttribDivisor != NULL)) RLGL.ExtSupported.instancing = true; + } + else if (strstr(extList[i], (const char *)"draw_instanced") != NULL) + { + // GL_ANGLE_draw_instanced doesn't exist + if (strcmp(extList[i], (const char *)"GL_EXT_draw_instanced") == 0) + { + glDrawArraysInstanced = (PFNGLDRAWARRAYSINSTANCEDEXTPROC)((rlglLoadProc)loader)("glDrawArraysInstancedEXT"); + glDrawElementsInstanced = (PFNGLDRAWELEMENTSINSTANCEDEXTPROC)((rlglLoadProc)loader)("glDrawElementsInstancedEXT"); + } + else if (strcmp(extList[i], (const char*)"GL_NV_draw_instanced") == 0) + { + glDrawArraysInstanced = (PFNGLDRAWARRAYSINSTANCEDEXTPROC)((rlglLoadProc)loader)("glDrawArraysInstancedNV"); + glDrawElementsInstanced = (PFNGLDRAWELEMENTSINSTANCEDEXTPROC)((rlglLoadProc)loader)("glDrawElementsInstancedNV"); + } + + // But the functions will at least be loaded if only GL_XX_EXT_draw_instanced exist + if ((glDrawArraysInstanced != NULL) && (glDrawElementsInstanced != NULL) && (glVertexAttribDivisor != NULL)) RLGL.ExtSupported.instancing = true; + } + + // Check NPOT textures support + // NOTE: Only check on OpenGL ES, OpenGL 3.3 has NPOT textures full support as core feature + if (strcmp(extList[i], (const char *)"GL_OES_texture_npot") == 0) RLGL.ExtSupported.texNPOT = true; + + // Check texture float support + if (strcmp(extList[i], (const char *)"GL_OES_texture_float") == 0) RLGL.ExtSupported.texFloat32 = true; + if (strcmp(extList[i], (const char *)"GL_OES_texture_half_float") == 0) RLGL.ExtSupported.texFloat16 = true; + + // Check depth texture support + if (strcmp(extList[i], (const char *)"GL_OES_depth_texture") == 0) RLGL.ExtSupported.texDepth = true; + if (strcmp(extList[i], (const char *)"GL_WEBGL_depth_texture") == 0) RLGL.ExtSupported.texDepthWebGL = true; // WebGL requires unsized internal format + if (RLGL.ExtSupported.texDepthWebGL) RLGL.ExtSupported.texDepth = true; + + if (strcmp(extList[i], (const char *)"GL_OES_depth24") == 0) RLGL.ExtSupported.maxDepthBits = 24; // Not available on WebGL + if (strcmp(extList[i], (const char *)"GL_OES_depth32") == 0) RLGL.ExtSupported.maxDepthBits = 32; // Not available on WebGL + + // Check texture compression support: DXT + if ((strcmp(extList[i], (const char *)"GL_EXT_texture_compression_s3tc") == 0) || + (strcmp(extList[i], (const char *)"GL_WEBGL_compressed_texture_s3tc") == 0) || + (strcmp(extList[i], (const char *)"GL_WEBKIT_WEBGL_compressed_texture_s3tc") == 0)) RLGL.ExtSupported.texCompDXT = true; + + // Check texture compression support: ETC1 + if ((strcmp(extList[i], (const char *)"GL_OES_compressed_ETC1_RGB8_texture") == 0) || + (strcmp(extList[i], (const char *)"GL_WEBGL_compressed_texture_etc1") == 0)) RLGL.ExtSupported.texCompETC1 = true; + + // Check texture compression support: ETC2/EAC + if (strcmp(extList[i], (const char *)"GL_ARB_ES3_compatibility") == 0) RLGL.ExtSupported.texCompETC2 = true; + + // Check texture compression support: PVR + if (strcmp(extList[i], (const char *)"GL_IMG_texture_compression_pvrtc") == 0) RLGL.ExtSupported.texCompPVRT = true; + + // Check texture compression support: ASTC + if (strcmp(extList[i], (const char *)"GL_KHR_texture_compression_astc_hdr") == 0) RLGL.ExtSupported.texCompASTC = true; + + // Check anisotropic texture filter support + if (strcmp(extList[i], (const char *)"GL_EXT_texture_filter_anisotropic") == 0) RLGL.ExtSupported.texAnisoFilter = true; + + // Check clamp mirror wrap mode support + if (strcmp(extList[i], (const char *)"GL_EXT_texture_mirror_clamp") == 0) RLGL.ExtSupported.texMirrorClamp = true; + } + + // Free extensions pointers + RL_FREE(extList); + RL_FREE(extensionsDup); // Duplicated string must be deallocated +#endif // GRAPHICS_API_OPENGL_ES2 + + // Check OpenGL information and capabilities + //------------------------------------------------------------------------------ + // Show current OpenGL and GLSL version + TRACELOG(RL_LOG_INFO, "GL: OpenGL device information:"); + TRACELOG(RL_LOG_INFO, " > Vendor: %s", glGetString(GL_VENDOR)); + TRACELOG(RL_LOG_INFO, " > Renderer: %s", glGetString(GL_RENDERER)); + TRACELOG(RL_LOG_INFO, " > Version: %s", glGetString(GL_VERSION)); + TRACELOG(RL_LOG_INFO, " > GLSL: %s", glGetString(GL_SHADING_LANGUAGE_VERSION)); + +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + // NOTE: Anisotropy levels capability is an extension + #ifndef GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT + #define GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT 0x84FF + #endif + glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &RLGL.ExtSupported.maxAnisotropyLevel); + +#if defined(RLGL_SHOW_GL_DETAILS_INFO) + // Show some OpenGL GPU capabilities + TRACELOG(RL_LOG_INFO, "GL: OpenGL capabilities:"); + GLint capability = 0; + glGetIntegerv(GL_MAX_TEXTURE_SIZE, &capability); + TRACELOG(RL_LOG_INFO, " GL_MAX_TEXTURE_SIZE: %i", capability); + glGetIntegerv(GL_MAX_CUBE_MAP_TEXTURE_SIZE, &capability); + TRACELOG(RL_LOG_INFO, " GL_MAX_CUBE_MAP_TEXTURE_SIZE: %i", capability); + glGetIntegerv(GL_MAX_TEXTURE_IMAGE_UNITS, &capability); + TRACELOG(RL_LOG_INFO, " GL_MAX_TEXTURE_IMAGE_UNITS: %i", capability); + glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &capability); + TRACELOG(RL_LOG_INFO, " GL_MAX_VERTEX_ATTRIBS: %i", capability); + #if !defined(GRAPHICS_API_OPENGL_ES2) + glGetIntegerv(GL_MAX_UNIFORM_BLOCK_SIZE, &capability); + TRACELOG(RL_LOG_INFO, " GL_MAX_UNIFORM_BLOCK_SIZE: %i", capability); + glGetIntegerv(GL_MAX_DRAW_BUFFERS, &capability); + TRACELOG(RL_LOG_INFO, " GL_MAX_DRAW_BUFFERS: %i", capability); + if (RLGL.ExtSupported.texAnisoFilter) TRACELOG(RL_LOG_INFO, " GL_MAX_TEXTURE_MAX_ANISOTROPY: %.0f", RLGL.ExtSupported.maxAnisotropyLevel); + #endif + glGetIntegerv(GL_NUM_COMPRESSED_TEXTURE_FORMATS, &capability); + TRACELOG(RL_LOG_INFO, " GL_NUM_COMPRESSED_TEXTURE_FORMATS: %i", capability); + GLint *compFormats = (GLint *)RL_CALLOC(capability, sizeof(GLint)); + glGetIntegerv(GL_COMPRESSED_TEXTURE_FORMATS, compFormats); + for (int i = 0; i < capability; i++) TRACELOG(RL_LOG_INFO, " %s", rlGetCompressedFormatName(compFormats[i])); + RL_FREE(compFormats); + +#if defined(GRAPHICS_API_OPENGL_43) + glGetIntegerv(GL_MAX_VERTEX_ATTRIB_BINDINGS, &capability); + TRACELOG(RL_LOG_INFO, " GL_MAX_VERTEX_ATTRIB_BINDINGS: %i", capability); + glGetIntegerv(GL_MAX_UNIFORM_LOCATIONS, &capability); + TRACELOG(RL_LOG_INFO, " GL_MAX_UNIFORM_LOCATIONS: %i", capability); +#endif // GRAPHICS_API_OPENGL_43 +#else // RLGL_SHOW_GL_DETAILS_INFO + + // Show some basic info about GL supported features + if (RLGL.ExtSupported.vao) TRACELOG(RL_LOG_INFO, "GL: VAO extension detected, VAO functions loaded successfully"); + else TRACELOG(RL_LOG_WARNING, "GL: VAO extension not found, VAO not supported"); + if (RLGL.ExtSupported.texNPOT) TRACELOG(RL_LOG_INFO, "GL: NPOT textures extension detected, full NPOT textures supported"); + else TRACELOG(RL_LOG_WARNING, "GL: NPOT textures extension not found, limited NPOT support (no-mipmaps, no-repeat)"); + if (RLGL.ExtSupported.texCompDXT) TRACELOG(RL_LOG_INFO, "GL: DXT compressed textures supported"); + if (RLGL.ExtSupported.texCompETC1) TRACELOG(RL_LOG_INFO, "GL: ETC1 compressed textures supported"); + if (RLGL.ExtSupported.texCompETC2) TRACELOG(RL_LOG_INFO, "GL: ETC2/EAC compressed textures supported"); + if (RLGL.ExtSupported.texCompPVRT) TRACELOG(RL_LOG_INFO, "GL: PVRT compressed textures supported"); + if (RLGL.ExtSupported.texCompASTC) TRACELOG(RL_LOG_INFO, "GL: ASTC compressed textures supported"); + if (RLGL.ExtSupported.computeShader) TRACELOG(RL_LOG_INFO, "GL: Compute shaders supported"); + if (RLGL.ExtSupported.ssbo) TRACELOG(RL_LOG_INFO, "GL: Shader storage buffer objects supported"); +#endif // RLGL_SHOW_GL_DETAILS_INFO + +#endif // GRAPHICS_API_OPENGL_33 || GRAPHICS_API_OPENGL_ES2 +} + +// Get current OpenGL version +int rlGetVersion(void) +{ + int glVersion = 0; +#if defined(GRAPHICS_API_OPENGL_11) + glVersion = RL_OPENGL_11; +#endif +#if defined(GRAPHICS_API_OPENGL_21) + glVersion = RL_OPENGL_21; +#elif defined(GRAPHICS_API_OPENGL_43) + glVersion = RL_OPENGL_43; +#elif defined(GRAPHICS_API_OPENGL_33) + glVersion = RL_OPENGL_33; +#endif +#if defined(GRAPHICS_API_OPENGL_ES3) + glVersion = RL_OPENGL_ES_30; +#elif defined(GRAPHICS_API_OPENGL_ES2) + glVersion = RL_OPENGL_ES_20; +#endif + + return glVersion; +} + +// Set current framebuffer width +void rlSetFramebufferWidth(int width) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + RLGL.State.framebufferWidth = width; +#endif +} + +// Set current framebuffer height +void rlSetFramebufferHeight(int height) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + RLGL.State.framebufferHeight = height; +#endif +} + +// Get default framebuffer width +int rlGetFramebufferWidth(void) +{ + int width = 0; +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + width = RLGL.State.framebufferWidth; +#endif + return width; +} + +// Get default framebuffer height +int rlGetFramebufferHeight(void) +{ + int height = 0; +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + height = RLGL.State.framebufferHeight; +#endif + return height; +} + +// Get default internal texture (white texture) +// NOTE: Default texture is a 1x1 pixel UNCOMPRESSED_R8G8B8A8 +unsigned int rlGetTextureIdDefault(void) +{ + unsigned int id = 0; +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + id = RLGL.State.defaultTextureId; +#endif + return id; +} + +// Get default shader id +unsigned int rlGetShaderIdDefault(void) +{ + unsigned int id = 0; +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + id = RLGL.State.defaultShaderId; +#endif + return id; +} + +// Get default shader locs +int *rlGetShaderLocsDefault(void) +{ + int *locs = NULL; +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + locs = RLGL.State.defaultShaderLocs; +#endif + return locs; +} + +// Render batch management +//------------------------------------------------------------------------------------------------ +// Load render batch +rlRenderBatch rlLoadRenderBatch(int numBuffers, int bufferElements) +{ + rlRenderBatch batch = { 0 }; + +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + // Initialize CPU (RAM) vertex buffers (position, texcoord, color data and indexes) + //-------------------------------------------------------------------------------------------- + batch.vertexBuffer = (rlVertexBuffer *)RL_MALLOC(numBuffers*sizeof(rlVertexBuffer)); + + for (int i = 0; i < numBuffers; i++) + { + batch.vertexBuffer[i].elementCount = bufferElements; + + batch.vertexBuffer[i].vertices = (float *)RL_MALLOC(bufferElements*3*4*sizeof(float)); // 3 float by vertex, 4 vertex by quad + batch.vertexBuffer[i].texcoords = (float *)RL_MALLOC(bufferElements*2*4*sizeof(float)); // 2 float by texcoord, 4 texcoord by quad + batch.vertexBuffer[i].normals = (float *)RL_MALLOC(bufferElements*3*4*sizeof(float)); // 3 float by vertex, 4 vertex by quad + batch.vertexBuffer[i].colors = (unsigned char *)RL_MALLOC(bufferElements*4*4*sizeof(unsigned char)); // 4 float by color, 4 colors by quad +#if defined(GRAPHICS_API_OPENGL_33) + batch.vertexBuffer[i].indices = (unsigned int *)RL_MALLOC(bufferElements*6*sizeof(unsigned int)); // 6 int by quad (indices) +#endif +#if defined(GRAPHICS_API_OPENGL_ES2) + batch.vertexBuffer[i].indices = (unsigned short *)RL_MALLOC(bufferElements*6*sizeof(unsigned short)); // 6 int by quad (indices) +#endif + + for (int j = 0; j < (3*4*bufferElements); j++) batch.vertexBuffer[i].vertices[j] = 0.0f; + for (int j = 0; j < (2*4*bufferElements); j++) batch.vertexBuffer[i].texcoords[j] = 0.0f; + for (int j = 0; j < (3*4*bufferElements); j++) batch.vertexBuffer[i].normals[j] = 0.0f; + for (int j = 0; j < (4*4*bufferElements); j++) batch.vertexBuffer[i].colors[j] = 0; + + int k = 0; + + // Indices can be initialized right now + for (int j = 0; j < (6*bufferElements); j += 6) + { + batch.vertexBuffer[i].indices[j] = 4*k; + batch.vertexBuffer[i].indices[j + 1] = 4*k + 1; + batch.vertexBuffer[i].indices[j + 2] = 4*k + 2; + batch.vertexBuffer[i].indices[j + 3] = 4*k; + batch.vertexBuffer[i].indices[j + 4] = 4*k + 2; + batch.vertexBuffer[i].indices[j + 5] = 4*k + 3; + + k++; + } + + RLGL.State.vertexCounter = 0; + } + + TRACELOG(RL_LOG_INFO, "RLGL: Render batch vertex buffers loaded successfully in RAM (CPU)"); + //-------------------------------------------------------------------------------------------- + + // Upload to GPU (VRAM) vertex data and initialize VAOs/VBOs + //-------------------------------------------------------------------------------------------- + for (int i = 0; i < numBuffers; i++) + { + if (RLGL.ExtSupported.vao) + { + // Initialize Quads VAO + glGenVertexArrays(1, &batch.vertexBuffer[i].vaoId); + glBindVertexArray(batch.vertexBuffer[i].vaoId); + } + + // Quads - Vertex buffers binding and attributes enable + // Vertex position buffer (shader-location = 0) + glGenBuffers(1, &batch.vertexBuffer[i].vboId[0]); + glBindBuffer(GL_ARRAY_BUFFER, batch.vertexBuffer[i].vboId[0]); + glBufferData(GL_ARRAY_BUFFER, bufferElements*3*4*sizeof(float), batch.vertexBuffer[i].vertices, GL_DYNAMIC_DRAW); + glEnableVertexAttribArray(RLGL.State.currentShaderLocs[RL_SHADER_LOC_VERTEX_POSITION]); + glVertexAttribPointer(RLGL.State.currentShaderLocs[RL_SHADER_LOC_VERTEX_POSITION], 3, GL_FLOAT, 0, 0, 0); + + // Vertex texcoord buffer (shader-location = 1) + glGenBuffers(1, &batch.vertexBuffer[i].vboId[1]); + glBindBuffer(GL_ARRAY_BUFFER, batch.vertexBuffer[i].vboId[1]); + glBufferData(GL_ARRAY_BUFFER, bufferElements*2*4*sizeof(float), batch.vertexBuffer[i].texcoords, GL_DYNAMIC_DRAW); + glEnableVertexAttribArray(RLGL.State.currentShaderLocs[RL_SHADER_LOC_VERTEX_TEXCOORD01]); + glVertexAttribPointer(RLGL.State.currentShaderLocs[RL_SHADER_LOC_VERTEX_TEXCOORD01], 2, GL_FLOAT, 0, 0, 0); + + // Vertex normal buffer (shader-location = 2) + glGenBuffers(1, &batch.vertexBuffer[i].vboId[2]); + glBindBuffer(GL_ARRAY_BUFFER, batch.vertexBuffer[i].vboId[2]); + glBufferData(GL_ARRAY_BUFFER, bufferElements*3*4*sizeof(float), batch.vertexBuffer[i].normals, GL_DYNAMIC_DRAW); + glEnableVertexAttribArray(RLGL.State.currentShaderLocs[RL_SHADER_LOC_VERTEX_NORMAL]); + glVertexAttribPointer(RLGL.State.currentShaderLocs[RL_SHADER_LOC_VERTEX_NORMAL], 3, GL_FLOAT, 0, 0, 0); + + // Vertex color buffer (shader-location = 3) + glGenBuffers(1, &batch.vertexBuffer[i].vboId[3]); + glBindBuffer(GL_ARRAY_BUFFER, batch.vertexBuffer[i].vboId[3]); + glBufferData(GL_ARRAY_BUFFER, bufferElements*4*4*sizeof(unsigned char), batch.vertexBuffer[i].colors, GL_DYNAMIC_DRAW); + glEnableVertexAttribArray(RLGL.State.currentShaderLocs[RL_SHADER_LOC_VERTEX_COLOR]); + glVertexAttribPointer(RLGL.State.currentShaderLocs[RL_SHADER_LOC_VERTEX_COLOR], 4, GL_UNSIGNED_BYTE, GL_TRUE, 0, 0); + + // Fill index buffer + glGenBuffers(1, &batch.vertexBuffer[i].vboId[4]); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, batch.vertexBuffer[i].vboId[4]); +#if defined(GRAPHICS_API_OPENGL_33) + glBufferData(GL_ELEMENT_ARRAY_BUFFER, bufferElements*6*sizeof(int), batch.vertexBuffer[i].indices, GL_STATIC_DRAW); +#endif +#if defined(GRAPHICS_API_OPENGL_ES2) + glBufferData(GL_ELEMENT_ARRAY_BUFFER, bufferElements*6*sizeof(short), batch.vertexBuffer[i].indices, GL_STATIC_DRAW); +#endif + } + + TRACELOG(RL_LOG_INFO, "RLGL: Render batch vertex buffers loaded successfully in VRAM (GPU)"); + + // Unbind the current VAO + if (RLGL.ExtSupported.vao) glBindVertexArray(0); + //-------------------------------------------------------------------------------------------- + + // Init draw calls tracking system + //-------------------------------------------------------------------------------------------- + batch.draws = (rlDrawCall *)RL_MALLOC(RL_DEFAULT_BATCH_DRAWCALLS*sizeof(rlDrawCall)); + + for (int i = 0; i < RL_DEFAULT_BATCH_DRAWCALLS; i++) + { + batch.draws[i].mode = RL_QUADS; + batch.draws[i].vertexCount = 0; + batch.draws[i].vertexAlignment = 0; + //batch.draws[i].vaoId = 0; + //batch.draws[i].shaderId = 0; + batch.draws[i].textureId = RLGL.State.defaultTextureId; + //batch.draws[i].RLGL.State.projection = rlMatrixIdentity(); + //batch.draws[i].RLGL.State.modelview = rlMatrixIdentity(); + } + + batch.bufferCount = numBuffers; // Record buffer count + batch.drawCounter = 1; // Reset draws counter + batch.currentDepth = -1.0f; // Reset depth value + //-------------------------------------------------------------------------------------------- +#endif + + return batch; +} + +// Unload default internal buffers vertex data from CPU and GPU +void rlUnloadRenderBatch(rlRenderBatch batch) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + // Unbind everything + glBindBuffer(GL_ARRAY_BUFFER, 0); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); + + // Unload all vertex buffers data + for (int i = 0; i < batch.bufferCount; i++) + { + // Unbind VAO attribs data + if (RLGL.ExtSupported.vao) + { + glBindVertexArray(batch.vertexBuffer[i].vaoId); + glDisableVertexAttribArray(RL_DEFAULT_SHADER_ATTRIB_LOCATION_POSITION); + glDisableVertexAttribArray(RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD); + glDisableVertexAttribArray(RL_DEFAULT_SHADER_ATTRIB_LOCATION_NORMAL); + glDisableVertexAttribArray(RL_DEFAULT_SHADER_ATTRIB_LOCATION_COLOR); + glBindVertexArray(0); + } + + // Delete VBOs from GPU (VRAM) + glDeleteBuffers(1, &batch.vertexBuffer[i].vboId[0]); + glDeleteBuffers(1, &batch.vertexBuffer[i].vboId[1]); + glDeleteBuffers(1, &batch.vertexBuffer[i].vboId[2]); + glDeleteBuffers(1, &batch.vertexBuffer[i].vboId[3]); + glDeleteBuffers(1, &batch.vertexBuffer[i].vboId[4]); + + // Delete VAOs from GPU (VRAM) + if (RLGL.ExtSupported.vao) glDeleteVertexArrays(1, &batch.vertexBuffer[i].vaoId); + + // Free vertex arrays memory from CPU (RAM) + RL_FREE(batch.vertexBuffer[i].vertices); + RL_FREE(batch.vertexBuffer[i].texcoords); + RL_FREE(batch.vertexBuffer[i].normals); + RL_FREE(batch.vertexBuffer[i].colors); + RL_FREE(batch.vertexBuffer[i].indices); + } + + // Unload arrays + RL_FREE(batch.vertexBuffer); + RL_FREE(batch.draws); +#endif +} + +// Draw render batch +// NOTE: We require a pointer to reset batch and increase current buffer (multi-buffer) +void rlDrawRenderBatch(rlRenderBatch *batch) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + // Update batch vertex buffers + //------------------------------------------------------------------------------------------------------------ + // NOTE: If there is not vertex data, buffers doesn't need to be updated (vertexCount > 0) + // TODO: If no data changed on the CPU arrays --> No need to re-update GPU arrays (use a change detector flag?) + if (RLGL.State.vertexCounter > 0) + { + // Activate elements VAO + if (RLGL.ExtSupported.vao) glBindVertexArray(batch->vertexBuffer[batch->currentBuffer].vaoId); + + // Vertex positions buffer + glBindBuffer(GL_ARRAY_BUFFER, batch->vertexBuffer[batch->currentBuffer].vboId[0]); + glBufferSubData(GL_ARRAY_BUFFER, 0, RLGL.State.vertexCounter*3*sizeof(float), batch->vertexBuffer[batch->currentBuffer].vertices); + //glBufferData(GL_ARRAY_BUFFER, sizeof(float)*3*4*batch->vertexBuffer[batch->currentBuffer].elementCount, batch->vertexBuffer[batch->currentBuffer].vertices, GL_DYNAMIC_DRAW); // Update all buffer + + // Texture coordinates buffer + glBindBuffer(GL_ARRAY_BUFFER, batch->vertexBuffer[batch->currentBuffer].vboId[1]); + glBufferSubData(GL_ARRAY_BUFFER, 0, RLGL.State.vertexCounter*2*sizeof(float), batch->vertexBuffer[batch->currentBuffer].texcoords); + //glBufferData(GL_ARRAY_BUFFER, sizeof(float)*2*4*batch->vertexBuffer[batch->currentBuffer].elementCount, batch->vertexBuffer[batch->currentBuffer].texcoords, GL_DYNAMIC_DRAW); // Update all buffer + + // Normals buffer + glBindBuffer(GL_ARRAY_BUFFER, batch->vertexBuffer[batch->currentBuffer].vboId[2]); + glBufferSubData(GL_ARRAY_BUFFER, 0, RLGL.State.vertexCounter*3*sizeof(float), batch->vertexBuffer[batch->currentBuffer].normals); + //glBufferData(GL_ARRAY_BUFFER, sizeof(float)*3*4*batch->vertexBuffer[batch->currentBuffer].elementCount, batch->vertexBuffer[batch->currentBuffer].normals, GL_DYNAMIC_DRAW); // Update all buffer + + // Colors buffer + glBindBuffer(GL_ARRAY_BUFFER, batch->vertexBuffer[batch->currentBuffer].vboId[3]); + glBufferSubData(GL_ARRAY_BUFFER, 0, RLGL.State.vertexCounter*4*sizeof(unsigned char), batch->vertexBuffer[batch->currentBuffer].colors); + //glBufferData(GL_ARRAY_BUFFER, sizeof(float)*4*4*batch->vertexBuffer[batch->currentBuffer].elementCount, batch->vertexBuffer[batch->currentBuffer].colors, GL_DYNAMIC_DRAW); // Update all buffer + + // NOTE: glMapBuffer() causes sync issue + // If GPU is working with this buffer, glMapBuffer() will wait(stall) until GPU to finish its job + // To avoid waiting (idle), you can call first glBufferData() with NULL pointer before glMapBuffer() + // If you do that, the previous data in PBO will be discarded and glMapBuffer() returns a new + // allocated pointer immediately even if GPU is still working with the previous data + + // Another option: map the buffer object into client's memory + // Probably this code could be moved somewhere else... + // batch->vertexBuffer[batch->currentBuffer].vertices = (float *)glMapBuffer(GL_ARRAY_BUFFER, GL_READ_WRITE); + // if (batch->vertexBuffer[batch->currentBuffer].vertices) + // { + // Update vertex data + // } + // glUnmapBuffer(GL_ARRAY_BUFFER); + + // Unbind the current VAO + if (RLGL.ExtSupported.vao) glBindVertexArray(0); + } + //------------------------------------------------------------------------------------------------------------ + + // Draw batch vertex buffers (considering VR stereo if required) + //------------------------------------------------------------------------------------------------------------ + Matrix matProjection = RLGL.State.projection; + Matrix matModelView = RLGL.State.modelview; + + int eyeCount = 1; + if (RLGL.State.stereoRender) eyeCount = 2; + + for (int eye = 0; eye < eyeCount; eye++) + { + if (eyeCount == 2) + { + // Setup current eye viewport (half screen width) + rlViewport(eye*RLGL.State.framebufferWidth/2, 0, RLGL.State.framebufferWidth/2, RLGL.State.framebufferHeight); + + // Set current eye view offset to modelview matrix + rlSetMatrixModelview(rlMatrixMultiply(matModelView, RLGL.State.viewOffsetStereo[eye])); + // Set current eye projection matrix + rlSetMatrixProjection(RLGL.State.projectionStereo[eye]); + } + + // Draw buffers + if (RLGL.State.vertexCounter > 0) + { + // Set current shader and upload current MVP matrix + glUseProgram(RLGL.State.currentShaderId); + + // Create modelview-projection matrix and upload to shader + Matrix matMVP = rlMatrixMultiply(RLGL.State.modelview, RLGL.State.projection); + glUniformMatrix4fv(RLGL.State.currentShaderLocs[RL_SHADER_LOC_MATRIX_MVP], 1, false, rlMatrixToFloat(matMVP)); + + if (RLGL.State.currentShaderLocs[RL_SHADER_LOC_MATRIX_PROJECTION] != -1) + { + glUniformMatrix4fv(RLGL.State.currentShaderLocs[RL_SHADER_LOC_MATRIX_PROJECTION], 1, false, rlMatrixToFloat(RLGL.State.projection)); + } + + // WARNING: For the following setup of the view, model, and normal matrices, it is expected that + // transformations and rendering occur between rlPushMatrix() and rlPopMatrix() + + if (RLGL.State.currentShaderLocs[RL_SHADER_LOC_MATRIX_VIEW] != -1) + { + glUniformMatrix4fv(RLGL.State.currentShaderLocs[RL_SHADER_LOC_MATRIX_VIEW], 1, false, rlMatrixToFloat(RLGL.State.modelview)); + } + + if (RLGL.State.currentShaderLocs[RL_SHADER_LOC_MATRIX_MODEL] != -1) + { + glUniformMatrix4fv(RLGL.State.currentShaderLocs[RL_SHADER_LOC_MATRIX_MODEL], 1, false, rlMatrixToFloat(RLGL.State.transform)); + } + + if (RLGL.State.currentShaderLocs[RL_SHADER_LOC_MATRIX_NORMAL] != -1) + { + glUniformMatrix4fv(RLGL.State.currentShaderLocs[RL_SHADER_LOC_MATRIX_NORMAL], 1, false, rlMatrixToFloat(rlMatrixTranspose(rlMatrixInvert(RLGL.State.transform)))); + } + + if (RLGL.ExtSupported.vao) glBindVertexArray(batch->vertexBuffer[batch->currentBuffer].vaoId); + else + { + // Bind vertex attrib: position (shader-location = 0) + glBindBuffer(GL_ARRAY_BUFFER, batch->vertexBuffer[batch->currentBuffer].vboId[0]); + glVertexAttribPointer(RLGL.State.currentShaderLocs[RL_SHADER_LOC_VERTEX_POSITION], 3, GL_FLOAT, 0, 0, 0); + glEnableVertexAttribArray(RLGL.State.currentShaderLocs[RL_SHADER_LOC_VERTEX_POSITION]); + + // Bind vertex attrib: texcoord (shader-location = 1) + glBindBuffer(GL_ARRAY_BUFFER, batch->vertexBuffer[batch->currentBuffer].vboId[1]); + glVertexAttribPointer(RLGL.State.currentShaderLocs[RL_SHADER_LOC_VERTEX_TEXCOORD01], 2, GL_FLOAT, 0, 0, 0); + glEnableVertexAttribArray(RLGL.State.currentShaderLocs[RL_SHADER_LOC_VERTEX_TEXCOORD01]); + + // Bind vertex attrib: normal (shader-location = 2) + glBindBuffer(GL_ARRAY_BUFFER, batch->vertexBuffer[batch->currentBuffer].vboId[2]); + glVertexAttribPointer(RLGL.State.currentShaderLocs[RL_SHADER_LOC_VERTEX_NORMAL], 3, GL_FLOAT, 0, 0, 0); + glEnableVertexAttribArray(RLGL.State.currentShaderLocs[RL_SHADER_LOC_VERTEX_NORMAL]); + + // Bind vertex attrib: color (shader-location = 3) + glBindBuffer(GL_ARRAY_BUFFER, batch->vertexBuffer[batch->currentBuffer].vboId[3]); + glVertexAttribPointer(RLGL.State.currentShaderLocs[RL_SHADER_LOC_VERTEX_COLOR], 4, GL_UNSIGNED_BYTE, GL_TRUE, 0, 0); + glEnableVertexAttribArray(RLGL.State.currentShaderLocs[RL_SHADER_LOC_VERTEX_COLOR]); + + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, batch->vertexBuffer[batch->currentBuffer].vboId[4]); + } + + // Setup some default shader values + glUniform4f(RLGL.State.currentShaderLocs[RL_SHADER_LOC_COLOR_DIFFUSE], 1.0f, 1.0f, 1.0f, 1.0f); + glUniform1i(RLGL.State.currentShaderLocs[RL_SHADER_LOC_MAP_DIFFUSE], 0); // Active default sampler2D: texture0 + + // Activate additional sampler textures + // Those additional textures will be common for all draw calls of the batch + for (int i = 0; i < RL_DEFAULT_BATCH_MAX_TEXTURE_UNITS; i++) + { + if (RLGL.State.activeTextureId[i] > 0) + { + glActiveTexture(GL_TEXTURE0 + 1 + i); + glBindTexture(GL_TEXTURE_2D, RLGL.State.activeTextureId[i]); + } + } + + // Activate default sampler2D texture0 (one texture is always active for default batch shader) + // NOTE: Batch system accumulates calls by texture0 changes, additional textures are enabled for all the draw calls + glActiveTexture(GL_TEXTURE0); + + for (int i = 0, vertexOffset = 0; i < batch->drawCounter; i++) + { + // Bind current draw call texture, activated as GL_TEXTURE0 and Bound to sampler2D texture0 by default + glBindTexture(GL_TEXTURE_2D, batch->draws[i].textureId); + + if ((batch->draws[i].mode == RL_LINES) || (batch->draws[i].mode == RL_TRIANGLES)) glDrawArrays(batch->draws[i].mode, vertexOffset, batch->draws[i].vertexCount); + else + { + #if defined(GRAPHICS_API_OPENGL_33) + // We need to define the number of indices to be processed: elementCount*6 + // NOTE: The final parameter tells the GPU the offset in bytes from the + // start of the index buffer to the location of the first index to process + glDrawElements(GL_TRIANGLES, batch->draws[i].vertexCount/4*6, GL_UNSIGNED_INT, (GLvoid *)(vertexOffset/4*6*sizeof(GLuint))); + #endif + #if defined(GRAPHICS_API_OPENGL_ES2) + glDrawElements(GL_TRIANGLES, batch->draws[i].vertexCount/4*6, GL_UNSIGNED_SHORT, (GLvoid *)(vertexOffset/4*6*sizeof(GLushort))); + #endif + } + + vertexOffset += (batch->draws[i].vertexCount + batch->draws[i].vertexAlignment); + } + + if (!RLGL.ExtSupported.vao) + { + glBindBuffer(GL_ARRAY_BUFFER, 0); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); + } + + glBindTexture(GL_TEXTURE_2D, 0); // Unbind textures + } + + if (RLGL.ExtSupported.vao) glBindVertexArray(0); // Unbind VAO + + glUseProgram(0); // Unbind shader program + } + + // Restore viewport to default measures + if (eyeCount == 2) rlViewport(0, 0, RLGL.State.framebufferWidth, RLGL.State.framebufferHeight); + //------------------------------------------------------------------------------------------------------------ + + // Reset batch buffers + //------------------------------------------------------------------------------------------------------------ + // Reset vertex counter for next frame + RLGL.State.vertexCounter = 0; + + // Reset depth for next draw + batch->currentDepth = -1.0f; + + // Restore projection/modelview matrices + RLGL.State.projection = matProjection; + RLGL.State.modelview = matModelView; + + // Reset RLGL.currentBatch->draws array + for (int i = 0; i < RL_DEFAULT_BATCH_DRAWCALLS; i++) + { + batch->draws[i].mode = RL_QUADS; + batch->draws[i].vertexCount = 0; + batch->draws[i].textureId = RLGL.State.defaultTextureId; + } + + // Reset active texture units for next batch + for (int i = 0; i < RL_DEFAULT_BATCH_MAX_TEXTURE_UNITS; i++) RLGL.State.activeTextureId[i] = 0; + + // Reset draws counter to one draw for the batch + batch->drawCounter = 1; + //------------------------------------------------------------------------------------------------------------ + + // Change to next buffer in the list (in case of multi-buffering) + batch->currentBuffer++; + if (batch->currentBuffer >= batch->bufferCount) batch->currentBuffer = 0; +#endif +} + +// Set the active render batch for rlgl +void rlSetRenderBatchActive(rlRenderBatch *batch) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + rlDrawRenderBatch(RLGL.currentBatch); + + if (batch != NULL) RLGL.currentBatch = batch; + else RLGL.currentBatch = &RLGL.defaultBatch; +#endif +} + +// Update and draw internal render batch +void rlDrawRenderBatchActive(void) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + rlDrawRenderBatch(RLGL.currentBatch); // NOTE: Stereo rendering is checked inside +#endif +} + +// Check internal buffer overflow for a given number of vertex +// and force a rlRenderBatch draw call if required +bool rlCheckRenderBatchLimit(int vCount) +{ + bool overflow = false; + +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + if ((RLGL.State.vertexCounter + vCount) >= + (RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].elementCount*4)) + { + overflow = true; + + // Store current primitive drawing mode and texture id + int currentMode = RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].mode; + int currentTexture = RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].textureId; + + rlDrawRenderBatch(RLGL.currentBatch); // NOTE: Stereo rendering is checked inside + + // Restore state of last batch so we can continue adding vertices + RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].mode = currentMode; + RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].textureId = currentTexture; + } +#endif + + return overflow; +} + +// Textures data management +//----------------------------------------------------------------------------------------- +// Convert image data to OpenGL texture (returns OpenGL valid Id) +unsigned int rlLoadTexture(const void *data, int width, int height, int format, int mipmapCount) +{ + unsigned int id = 0; + + glBindTexture(GL_TEXTURE_2D, 0); // Free any old binding + + // Check texture format support by OpenGL 1.1 (compressed textures not supported) +#if defined(GRAPHICS_API_OPENGL_11) + if (format >= RL_PIXELFORMAT_COMPRESSED_DXT1_RGB) + { + TRACELOG(RL_LOG_WARNING, "GL: OpenGL 1.1 does not support GPU compressed texture formats"); + return id; + } +#else + if ((!RLGL.ExtSupported.texCompDXT) && ((format == RL_PIXELFORMAT_COMPRESSED_DXT1_RGB) || (format == RL_PIXELFORMAT_COMPRESSED_DXT1_RGBA) || + (format == RL_PIXELFORMAT_COMPRESSED_DXT3_RGBA) || (format == RL_PIXELFORMAT_COMPRESSED_DXT5_RGBA))) + { + TRACELOG(RL_LOG_WARNING, "GL: DXT compressed texture format not supported"); + return id; + } +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + if ((!RLGL.ExtSupported.texCompETC1) && (format == RL_PIXELFORMAT_COMPRESSED_ETC1_RGB)) + { + TRACELOG(RL_LOG_WARNING, "GL: ETC1 compressed texture format not supported"); + return id; + } + + if ((!RLGL.ExtSupported.texCompETC2) && ((format == RL_PIXELFORMAT_COMPRESSED_ETC2_RGB) || (format == RL_PIXELFORMAT_COMPRESSED_ETC2_EAC_RGBA))) + { + TRACELOG(RL_LOG_WARNING, "GL: ETC2 compressed texture format not supported"); + return id; + } + + if ((!RLGL.ExtSupported.texCompPVRT) && ((format == RL_PIXELFORMAT_COMPRESSED_PVRT_RGB) || (format == RL_PIXELFORMAT_COMPRESSED_PVRT_RGBA))) + { + TRACELOG(RL_LOG_WARNING, "GL: PVRT compressed texture format not supported"); + return id; + } + + if ((!RLGL.ExtSupported.texCompASTC) && ((format == RL_PIXELFORMAT_COMPRESSED_ASTC_4x4_RGBA) || (format == RL_PIXELFORMAT_COMPRESSED_ASTC_8x8_RGBA))) + { + TRACELOG(RL_LOG_WARNING, "GL: ASTC compressed texture format not supported"); + return id; + } +#endif +#endif // GRAPHICS_API_OPENGL_11 + + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); + + glGenTextures(1, &id); // Generate texture id + + glBindTexture(GL_TEXTURE_2D, id); + + int mipWidth = width; + int mipHeight = height; + int mipOffset = 0; // Mipmap data offset, only used for tracelog + + // NOTE: Added pointer math separately from function to avoid UBSAN complaining + unsigned char *dataPtr = NULL; + if (data != NULL) dataPtr = (unsigned char *)data; + + // Load the different mipmap levels + for (int i = 0; i < mipmapCount; i++) + { + unsigned int mipSize = rlGetPixelDataSize(mipWidth, mipHeight, format); + + unsigned int glInternalFormat, glFormat, glType; + rlGetGlTextureFormats(format, &glInternalFormat, &glFormat, &glType); + + TRACELOGD("TEXTURE: Load mipmap level %i (%i x %i), size: %i, offset: %i", i, mipWidth, mipHeight, mipSize, mipOffset); + + if (glInternalFormat != 0) + { + if (format < RL_PIXELFORMAT_COMPRESSED_DXT1_RGB) glTexImage2D(GL_TEXTURE_2D, i, glInternalFormat, mipWidth, mipHeight, 0, glFormat, glType, dataPtr); +#if !defined(GRAPHICS_API_OPENGL_11) + else glCompressedTexImage2D(GL_TEXTURE_2D, i, glInternalFormat, mipWidth, mipHeight, 0, mipSize, dataPtr); +#endif + +#if defined(GRAPHICS_API_OPENGL_33) + if (format == RL_PIXELFORMAT_UNCOMPRESSED_GRAYSCALE) + { + GLint swizzleMask[] = { GL_RED, GL_RED, GL_RED, GL_ONE }; + glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_RGBA, swizzleMask); + } + else if (format == RL_PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA) + { +#if defined(GRAPHICS_API_OPENGL_21) + GLint swizzleMask[] = { GL_RED, GL_RED, GL_RED, GL_ALPHA }; +#elif defined(GRAPHICS_API_OPENGL_33) + GLint swizzleMask[] = { GL_RED, GL_RED, GL_RED, GL_GREEN }; +#endif + glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_RGBA, swizzleMask); + } +#endif + } + + mipWidth /= 2; + mipHeight /= 2; + mipOffset += mipSize; // Increment offset position to next mipmap + if (data != NULL) dataPtr += mipSize; // Increment data pointer to next mipmap + + // Security check for NPOT textures + if (mipWidth < 1) mipWidth = 1; + if (mipHeight < 1) mipHeight = 1; + } + + // Texture parameters configuration + // NOTE: glTexParameteri does NOT affect texture uploading, just the way it's used +#if defined(GRAPHICS_API_OPENGL_ES2) + // NOTE: OpenGL ES 2.0 with no GL_OES_texture_npot support (i.e. WebGL) has limited NPOT support, so CLAMP_TO_EDGE must be used + if (RLGL.ExtSupported.texNPOT) + { + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); // Set texture to repeat on x-axis + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); // Set texture to repeat on y-axis + } + else + { + // NOTE: If using negative texture coordinates (LoadOBJ()), it does not work! + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); // Set texture to clamp on x-axis + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); // Set texture to clamp on y-axis + } +#else + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); // Set texture to repeat on x-axis + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); // Set texture to repeat on y-axis +#endif + + // Magnification and minification filters + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); // Alternative: GL_LINEAR + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); // Alternative: GL_LINEAR + +#if defined(GRAPHICS_API_OPENGL_33) + if (mipmapCount > 1) + { + // Activate Trilinear filtering if mipmaps are available + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); + } +#endif + + // At this point we have the texture loaded in GPU and texture parameters configured + + // NOTE: If mipmaps were not in data, they are not generated automatically + + // Unbind current texture + glBindTexture(GL_TEXTURE_2D, 0); + + if (id > 0) TRACELOG(RL_LOG_INFO, "TEXTURE: [ID %i] Texture loaded successfully (%ix%i | %s | %i mipmaps)", id, width, height, rlGetPixelFormatName(format), mipmapCount); + else TRACELOG(RL_LOG_WARNING, "TEXTURE: Failed to load texture"); + + return id; +} + +// Load depth texture/renderbuffer (to be attached to fbo) +// WARNING: OpenGL ES 2.0 requires GL_OES_depth_texture and WebGL requires WEBGL_depth_texture extensions +unsigned int rlLoadTextureDepth(int width, int height, bool useRenderBuffer) +{ + unsigned int id = 0; + +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + // In case depth textures not supported, we force renderbuffer usage + if (!RLGL.ExtSupported.texDepth) useRenderBuffer = true; + + // NOTE: We let the implementation to choose the best bit-depth + // Possible formats: GL_DEPTH_COMPONENT16, GL_DEPTH_COMPONENT24, GL_DEPTH_COMPONENT32 and GL_DEPTH_COMPONENT32F + unsigned int glInternalFormat = GL_DEPTH_COMPONENT; + +#if (defined(GRAPHICS_API_OPENGL_ES2) || defined(GRAPHICS_API_OPENGL_ES3)) + // WARNING: WebGL platform requires unsized internal format definition (GL_DEPTH_COMPONENT) + // while other platforms using OpenGL ES 2.0 require/support sized internal formats depending on the GPU capabilities + if (!RLGL.ExtSupported.texDepthWebGL || useRenderBuffer) + { + if (RLGL.ExtSupported.maxDepthBits == 32) glInternalFormat = GL_DEPTH_COMPONENT32_OES; + else if (RLGL.ExtSupported.maxDepthBits == 24) glInternalFormat = GL_DEPTH_COMPONENT24_OES; + else glInternalFormat = GL_DEPTH_COMPONENT16; + } +#endif + + if (!useRenderBuffer && RLGL.ExtSupported.texDepth) + { + glGenTextures(1, &id); + glBindTexture(GL_TEXTURE_2D, id); + glTexImage2D(GL_TEXTURE_2D, 0, glInternalFormat, width, height, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_INT, NULL); + + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + + glBindTexture(GL_TEXTURE_2D, 0); + + TRACELOG(RL_LOG_INFO, "TEXTURE: Depth texture loaded successfully"); + } + else + { + // Create the renderbuffer that will serve as the depth attachment for the framebuffer + // NOTE: A renderbuffer is simpler than a texture and could offer better performance on embedded devices + glGenRenderbuffers(1, &id); + glBindRenderbuffer(GL_RENDERBUFFER, id); + glRenderbufferStorage(GL_RENDERBUFFER, glInternalFormat, width, height); + + glBindRenderbuffer(GL_RENDERBUFFER, 0); + + TRACELOG(RL_LOG_INFO, "TEXTURE: [ID %i] Depth renderbuffer loaded successfully (%i bits)", id, (RLGL.ExtSupported.maxDepthBits >= 24)? RLGL.ExtSupported.maxDepthBits : 16); + } +#endif + + return id; +} + +// Load texture cubemap +// NOTE: Cubemap data is expected to be 6 images in a single data array (one after the other), +// expected the following convention: +X, -X, +Y, -Y, +Z, -Z +unsigned int rlLoadTextureCubemap(const void *data, int size, int format, int mipmapCount) +{ + unsigned int id = 0; + +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + int mipSize = size; + + // NOTE: Added pointer math separately from function to avoid UBSAN complaining + unsigned char *dataPtr = NULL; + if (data != NULL) dataPtr = (unsigned char *)data; + + unsigned int dataSize = rlGetPixelDataSize(size, size, format); + + glGenTextures(1, &id); + glBindTexture(GL_TEXTURE_CUBE_MAP, id); + + unsigned int glInternalFormat, glFormat, glType; + rlGetGlTextureFormats(format, &glInternalFormat, &glFormat, &glType); + + if (glInternalFormat != 0) + { + // Load cubemap faces/mipmaps + for (int i = 0; i < 6*mipmapCount; i++) + { + int mipmapLevel = i/6; + int face = i%6; + + if (data == NULL) + { + if (format < RL_PIXELFORMAT_COMPRESSED_DXT1_RGB) + { + if ((format == RL_PIXELFORMAT_UNCOMPRESSED_R32) || + (format == RL_PIXELFORMAT_UNCOMPRESSED_R32G32B32A32) || + (format == RL_PIXELFORMAT_UNCOMPRESSED_R16) || + (format == RL_PIXELFORMAT_UNCOMPRESSED_R16G16B16A16)) TRACELOG(RL_LOG_WARNING, "TEXTURES: Cubemap requested format not supported"); + else glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + face, mipmapLevel, glInternalFormat, mipSize, mipSize, 0, glFormat, glType, NULL); + } + else TRACELOG(RL_LOG_WARNING, "TEXTURES: Empty cubemap creation does not support compressed format"); + } + else + { + if (format < RL_PIXELFORMAT_COMPRESSED_DXT1_RGB) glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + face, mipmapLevel, glInternalFormat, mipSize, mipSize, 0, glFormat, glType, (unsigned char *)dataPtr + face*dataSize); + else glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + face, mipmapLevel, glInternalFormat, mipSize, mipSize, 0, dataSize, (unsigned char *)dataPtr + face*dataSize); + } + +#if defined(GRAPHICS_API_OPENGL_33) + if (format == RL_PIXELFORMAT_UNCOMPRESSED_GRAYSCALE) + { + GLint swizzleMask[] = { GL_RED, GL_RED, GL_RED, GL_ONE }; + glTexParameteriv(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_SWIZZLE_RGBA, swizzleMask); + } + else if (format == RL_PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA) + { +#if defined(GRAPHICS_API_OPENGL_21) + GLint swizzleMask[] = { GL_RED, GL_RED, GL_RED, GL_ALPHA }; +#elif defined(GRAPHICS_API_OPENGL_33) + GLint swizzleMask[] = { GL_RED, GL_RED, GL_RED, GL_GREEN }; +#endif + glTexParameteriv(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_SWIZZLE_RGBA, swizzleMask); + } +#endif + if (face == 5) + { + mipSize /= 2; + if (data != NULL) dataPtr += dataSize*6; // Increment data pointer to next mipmap + + // Security check for NPOT textures + if (mipSize < 1) mipSize = 1; + + dataSize = rlGetPixelDataSize(mipSize, mipSize, format); + } + } + } + + // Set cubemap texture sampling parameters + if (mipmapCount > 1) glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); + else glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); +#if defined(GRAPHICS_API_OPENGL_33) + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); // Flag not supported on OpenGL ES 2.0 +#endif + + glBindTexture(GL_TEXTURE_CUBE_MAP, 0); +#endif + + if (id > 0) TRACELOG(RL_LOG_INFO, "TEXTURE: [ID %i] Cubemap texture loaded successfully (%ix%i)", id, size, size); + else TRACELOG(RL_LOG_WARNING, "TEXTURE: Failed to load cubemap texture"); + + return id; +} + +// Update already loaded texture in GPU with new data +// NOTE: We don't know safely if internal texture format is the expected one... +void rlUpdateTexture(unsigned int id, int offsetX, int offsetY, int width, int height, int format, const void *data) +{ + glBindTexture(GL_TEXTURE_2D, id); + + unsigned int glInternalFormat, glFormat, glType; + rlGetGlTextureFormats(format, &glInternalFormat, &glFormat, &glType); + + if ((glInternalFormat != 0) && (format < RL_PIXELFORMAT_COMPRESSED_DXT1_RGB)) + { + glTexSubImage2D(GL_TEXTURE_2D, 0, offsetX, offsetY, width, height, glFormat, glType, data); + } + else TRACELOG(RL_LOG_WARNING, "TEXTURE: [ID %i] Failed to update for current texture format (%i)", id, format); +} + +// Get OpenGL internal formats and data type from raylib PixelFormat +void rlGetGlTextureFormats(int format, unsigned int *glInternalFormat, unsigned int *glFormat, unsigned int *glType) +{ + *glInternalFormat = 0; + *glFormat = 0; + *glType = 0; + + switch (format) + { + #if defined(GRAPHICS_API_OPENGL_11) || defined(GRAPHICS_API_OPENGL_21) || defined(GRAPHICS_API_OPENGL_ES2) + // NOTE: on OpenGL ES 2.0 (WebGL), internalFormat must match format and options allowed are: GL_LUMINANCE, GL_RGB, GL_RGBA + case RL_PIXELFORMAT_UNCOMPRESSED_GRAYSCALE: *glInternalFormat = GL_LUMINANCE; *glFormat = GL_LUMINANCE; *glType = GL_UNSIGNED_BYTE; break; + case RL_PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA: *glInternalFormat = GL_LUMINANCE_ALPHA; *glFormat = GL_LUMINANCE_ALPHA; *glType = GL_UNSIGNED_BYTE; break; + case RL_PIXELFORMAT_UNCOMPRESSED_R5G6B5: *glInternalFormat = GL_RGB; *glFormat = GL_RGB; *glType = GL_UNSIGNED_SHORT_5_6_5; break; + case RL_PIXELFORMAT_UNCOMPRESSED_R8G8B8: *glInternalFormat = GL_RGB; *glFormat = GL_RGB; *glType = GL_UNSIGNED_BYTE; break; + case RL_PIXELFORMAT_UNCOMPRESSED_R5G5B5A1: *glInternalFormat = GL_RGBA; *glFormat = GL_RGBA; *glType = GL_UNSIGNED_SHORT_5_5_5_1; break; + case RL_PIXELFORMAT_UNCOMPRESSED_R4G4B4A4: *glInternalFormat = GL_RGBA; *glFormat = GL_RGBA; *glType = GL_UNSIGNED_SHORT_4_4_4_4; break; + case RL_PIXELFORMAT_UNCOMPRESSED_R8G8B8A8: *glInternalFormat = GL_RGBA; *glFormat = GL_RGBA; *glType = GL_UNSIGNED_BYTE; break; + #if !defined(GRAPHICS_API_OPENGL_11) + #if defined(GRAPHICS_API_OPENGL_ES3) + case RL_PIXELFORMAT_UNCOMPRESSED_R32: if (RLGL.ExtSupported.texFloat32) *glInternalFormat = GL_R32F_EXT; *glFormat = GL_RED_EXT; *glType = GL_FLOAT; break; + case RL_PIXELFORMAT_UNCOMPRESSED_R32G32B32: if (RLGL.ExtSupported.texFloat32) *glInternalFormat = GL_RGB32F_EXT; *glFormat = GL_RGB; *glType = GL_FLOAT; break; + case RL_PIXELFORMAT_UNCOMPRESSED_R32G32B32A32: if (RLGL.ExtSupported.texFloat32) *glInternalFormat = GL_RGBA32F_EXT; *glFormat = GL_RGBA; *glType = GL_FLOAT; break; + case RL_PIXELFORMAT_UNCOMPRESSED_R16: if (RLGL.ExtSupported.texFloat16) *glInternalFormat = GL_R16F_EXT; *glFormat = GL_RED_EXT; *glType = GL_HALF_FLOAT; break; + case RL_PIXELFORMAT_UNCOMPRESSED_R16G16B16: if (RLGL.ExtSupported.texFloat16) *glInternalFormat = GL_RGB16F_EXT; *glFormat = GL_RGB; *glType = GL_HALF_FLOAT; break; + case RL_PIXELFORMAT_UNCOMPRESSED_R16G16B16A16: if (RLGL.ExtSupported.texFloat16) *glInternalFormat = GL_RGBA16F_EXT; *glFormat = GL_RGBA; *glType = GL_HALF_FLOAT; break; + #else + case RL_PIXELFORMAT_UNCOMPRESSED_R32: if (RLGL.ExtSupported.texFloat32) *glInternalFormat = GL_LUMINANCE; *glFormat = GL_LUMINANCE; *glType = GL_FLOAT; break; // NOTE: Requires extension OES_texture_float + case RL_PIXELFORMAT_UNCOMPRESSED_R32G32B32: if (RLGL.ExtSupported.texFloat32) *glInternalFormat = GL_RGB; *glFormat = GL_RGB; *glType = GL_FLOAT; break; // NOTE: Requires extension OES_texture_float + case RL_PIXELFORMAT_UNCOMPRESSED_R32G32B32A32: if (RLGL.ExtSupported.texFloat32) *glInternalFormat = GL_RGBA; *glFormat = GL_RGBA; *glType = GL_FLOAT; break; // NOTE: Requires extension OES_texture_float + #if defined(GRAPHICS_API_OPENGL_21) + case RL_PIXELFORMAT_UNCOMPRESSED_R16: if (RLGL.ExtSupported.texFloat16) *glInternalFormat = GL_LUMINANCE; *glFormat = GL_LUMINANCE; *glType = GL_HALF_FLOAT_ARB; break; + case RL_PIXELFORMAT_UNCOMPRESSED_R16G16B16: if (RLGL.ExtSupported.texFloat16) *glInternalFormat = GL_RGB; *glFormat = GL_RGB; *glType = GL_HALF_FLOAT_ARB; break; + case RL_PIXELFORMAT_UNCOMPRESSED_R16G16B16A16: if (RLGL.ExtSupported.texFloat16) *glInternalFormat = GL_RGBA; *glFormat = GL_RGBA; *glType = GL_HALF_FLOAT_ARB; break; + #else // defined(GRAPHICS_API_OPENGL_ES2) + case RL_PIXELFORMAT_UNCOMPRESSED_R16: if (RLGL.ExtSupported.texFloat16) *glInternalFormat = GL_LUMINANCE; *glFormat = GL_LUMINANCE; *glType = GL_HALF_FLOAT_OES; break; // NOTE: Requires extension OES_texture_half_float + case RL_PIXELFORMAT_UNCOMPRESSED_R16G16B16: if (RLGL.ExtSupported.texFloat16) *glInternalFormat = GL_RGB; *glFormat = GL_RGB; *glType = GL_HALF_FLOAT_OES; break; // NOTE: Requires extension OES_texture_half_float + case RL_PIXELFORMAT_UNCOMPRESSED_R16G16B16A16: if (RLGL.ExtSupported.texFloat16) *glInternalFormat = GL_RGBA; *glFormat = GL_RGBA; *glType = GL_HALF_FLOAT_OES; break; // NOTE: Requires extension OES_texture_half_float + #endif + #endif + #endif + #elif defined(GRAPHICS_API_OPENGL_33) + case RL_PIXELFORMAT_UNCOMPRESSED_GRAYSCALE: *glInternalFormat = GL_R8; *glFormat = GL_RED; *glType = GL_UNSIGNED_BYTE; break; + case RL_PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA: *glInternalFormat = GL_RG8; *glFormat = GL_RG; *glType = GL_UNSIGNED_BYTE; break; + case RL_PIXELFORMAT_UNCOMPRESSED_R5G6B5: *glInternalFormat = GL_RGB565; *glFormat = GL_RGB; *glType = GL_UNSIGNED_SHORT_5_6_5; break; + case RL_PIXELFORMAT_UNCOMPRESSED_R8G8B8: *glInternalFormat = GL_RGB8; *glFormat = GL_RGB; *glType = GL_UNSIGNED_BYTE; break; + case RL_PIXELFORMAT_UNCOMPRESSED_R5G5B5A1: *glInternalFormat = GL_RGB5_A1; *glFormat = GL_RGBA; *glType = GL_UNSIGNED_SHORT_5_5_5_1; break; + case RL_PIXELFORMAT_UNCOMPRESSED_R4G4B4A4: *glInternalFormat = GL_RGBA4; *glFormat = GL_RGBA; *glType = GL_UNSIGNED_SHORT_4_4_4_4; break; + case RL_PIXELFORMAT_UNCOMPRESSED_R8G8B8A8: *glInternalFormat = GL_RGBA8; *glFormat = GL_RGBA; *glType = GL_UNSIGNED_BYTE; break; + case RL_PIXELFORMAT_UNCOMPRESSED_R32: if (RLGL.ExtSupported.texFloat32) *glInternalFormat = GL_R32F; *glFormat = GL_RED; *glType = GL_FLOAT; break; + case RL_PIXELFORMAT_UNCOMPRESSED_R32G32B32: if (RLGL.ExtSupported.texFloat32) *glInternalFormat = GL_RGB32F; *glFormat = GL_RGB; *glType = GL_FLOAT; break; + case RL_PIXELFORMAT_UNCOMPRESSED_R32G32B32A32: if (RLGL.ExtSupported.texFloat32) *glInternalFormat = GL_RGBA32F; *glFormat = GL_RGBA; *glType = GL_FLOAT; break; + case RL_PIXELFORMAT_UNCOMPRESSED_R16: if (RLGL.ExtSupported.texFloat16) *glInternalFormat = GL_R16F; *glFormat = GL_RED; *glType = GL_HALF_FLOAT; break; + case RL_PIXELFORMAT_UNCOMPRESSED_R16G16B16: if (RLGL.ExtSupported.texFloat16) *glInternalFormat = GL_RGB16F; *glFormat = GL_RGB; *glType = GL_HALF_FLOAT; break; + case RL_PIXELFORMAT_UNCOMPRESSED_R16G16B16A16: if (RLGL.ExtSupported.texFloat16) *glInternalFormat = GL_RGBA16F; *glFormat = GL_RGBA; *glType = GL_HALF_FLOAT; break; + #endif + #if !defined(GRAPHICS_API_OPENGL_11) + case RL_PIXELFORMAT_COMPRESSED_DXT1_RGB: if (RLGL.ExtSupported.texCompDXT) *glInternalFormat = GL_COMPRESSED_RGB_S3TC_DXT1_EXT; break; + case RL_PIXELFORMAT_COMPRESSED_DXT1_RGBA: if (RLGL.ExtSupported.texCompDXT) *glInternalFormat = GL_COMPRESSED_RGBA_S3TC_DXT1_EXT; break; + case RL_PIXELFORMAT_COMPRESSED_DXT3_RGBA: if (RLGL.ExtSupported.texCompDXT) *glInternalFormat = GL_COMPRESSED_RGBA_S3TC_DXT3_EXT; break; + case RL_PIXELFORMAT_COMPRESSED_DXT5_RGBA: if (RLGL.ExtSupported.texCompDXT) *glInternalFormat = GL_COMPRESSED_RGBA_S3TC_DXT5_EXT; break; + case RL_PIXELFORMAT_COMPRESSED_ETC1_RGB: if (RLGL.ExtSupported.texCompETC1) *glInternalFormat = GL_ETC1_RGB8_OES; break; // NOTE: Requires OpenGL ES 2.0 or OpenGL 4.3 + case RL_PIXELFORMAT_COMPRESSED_ETC2_RGB: if (RLGL.ExtSupported.texCompETC2) *glInternalFormat = GL_COMPRESSED_RGB8_ETC2; break; // NOTE: Requires OpenGL ES 3.0 or OpenGL 4.3 + case RL_PIXELFORMAT_COMPRESSED_ETC2_EAC_RGBA: if (RLGL.ExtSupported.texCompETC2) *glInternalFormat = GL_COMPRESSED_RGBA8_ETC2_EAC; break; // NOTE: Requires OpenGL ES 3.0 or OpenGL 4.3 + case RL_PIXELFORMAT_COMPRESSED_PVRT_RGB: if (RLGL.ExtSupported.texCompPVRT) *glInternalFormat = GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG; break; // NOTE: Requires PowerVR GPU + case RL_PIXELFORMAT_COMPRESSED_PVRT_RGBA: if (RLGL.ExtSupported.texCompPVRT) *glInternalFormat = GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG; break; // NOTE: Requires PowerVR GPU + case RL_PIXELFORMAT_COMPRESSED_ASTC_4x4_RGBA: if (RLGL.ExtSupported.texCompASTC) *glInternalFormat = GL_COMPRESSED_RGBA_ASTC_4x4_KHR; break; // NOTE: Requires OpenGL ES 3.1 or OpenGL 4.3 + case RL_PIXELFORMAT_COMPRESSED_ASTC_8x8_RGBA: if (RLGL.ExtSupported.texCompASTC) *glInternalFormat = GL_COMPRESSED_RGBA_ASTC_8x8_KHR; break; // NOTE: Requires OpenGL ES 3.1 or OpenGL 4.3 + #endif + default: TRACELOG(RL_LOG_WARNING, "TEXTURE: Current format not supported (%i)", format); break; + } +} + +// Unload texture from GPU memory +void rlUnloadTexture(unsigned int id) +{ + glDeleteTextures(1, &id); +} + +// Generate mipmap data for selected texture +// NOTE: Only supports GPU mipmap generation +void rlGenTextureMipmaps(unsigned int id, int width, int height, int format, int *mipmaps) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + glBindTexture(GL_TEXTURE_2D, id); + + // Check if texture is power-of-two (POT) + bool texIsPOT = false; + + if (((width > 0) && ((width & (width - 1)) == 0)) && + ((height > 0) && ((height & (height - 1)) == 0))) texIsPOT = true; + + if ((texIsPOT) || (RLGL.ExtSupported.texNPOT)) + { + //glHint(GL_GENERATE_MIPMAP_HINT, GL_DONT_CARE); // Hint for mipmaps generation algorithm: GL_FASTEST, GL_NICEST, GL_DONT_CARE + glGenerateMipmap(GL_TEXTURE_2D); // Generate mipmaps automatically + + #define MIN(a,b) (((a)<(b))? (a):(b)) + #define MAX(a,b) (((a)>(b))? (a):(b)) + + *mipmaps = 1 + (int)floor(log(MAX(width, height))/log(2)); + TRACELOG(RL_LOG_INFO, "TEXTURE: [ID %i] Mipmaps generated automatically, total: %i", id, *mipmaps); + } + else TRACELOG(RL_LOG_WARNING, "TEXTURE: [ID %i] Failed to generate mipmaps", id); + + glBindTexture(GL_TEXTURE_2D, 0); +#else + TRACELOG(RL_LOG_WARNING, "TEXTURE: [ID %i] GPU mipmap generation not supported", id); +#endif +} + +// Read texture pixel data +void *rlReadTexturePixels(unsigned int id, int width, int height, int format) +{ + void *pixels = NULL; + +#if defined(GRAPHICS_API_OPENGL_11) || defined(GRAPHICS_API_OPENGL_33) + glBindTexture(GL_TEXTURE_2D, id); + + // NOTE: Using texture id, we can retrieve some texture info (but not on OpenGL ES 2.0) + // Possible texture info: GL_TEXTURE_RED_SIZE, GL_TEXTURE_GREEN_SIZE, GL_TEXTURE_BLUE_SIZE, GL_TEXTURE_ALPHA_SIZE + //int width, height, format; + //glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &width); + //glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &height); + //glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_INTERNAL_FORMAT, &format); + + // NOTE: Each row written to or read from by OpenGL pixel operations like glGetTexImage are aligned to a 4 byte boundary by default, which may add some padding + // Use glPixelStorei to modify padding with the GL_[UN]PACK_ALIGNMENT setting + // GL_PACK_ALIGNMENT affects operations that read from OpenGL memory (glReadPixels, glGetTexImage, etc.) + // GL_UNPACK_ALIGNMENT affects operations that write to OpenGL memory (glTexImage, etc.) + glPixelStorei(GL_PACK_ALIGNMENT, 1); + + unsigned int glInternalFormat, glFormat, glType; + rlGetGlTextureFormats(format, &glInternalFormat, &glFormat, &glType); + unsigned int size = rlGetPixelDataSize(width, height, format); + + if ((glInternalFormat != 0) && (format < RL_PIXELFORMAT_COMPRESSED_DXT1_RGB)) + { + pixels = RL_MALLOC(size); + glGetTexImage(GL_TEXTURE_2D, 0, glFormat, glType, pixels); + } + else TRACELOG(RL_LOG_WARNING, "TEXTURE: [ID %i] Data retrieval not suported for pixel format (%i)", id, format); + + glBindTexture(GL_TEXTURE_2D, 0); +#endif + +#if defined(GRAPHICS_API_OPENGL_ES2) + // glGetTexImage() is not available on OpenGL ES 2.0 + // Texture width and height are required on OpenGL ES 2.0, there is no way to get it from texture id + // Two possible Options: + // 1 - Bind texture to color fbo attachment and glReadPixels() + // 2 - Create an fbo, activate it, render quad with texture, glReadPixels() + // We are using Option 1, just need to care for texture format on retrieval + // NOTE: This behaviour could be conditioned by graphic driver... + unsigned int fboId = rlLoadFramebuffer(); + + glBindFramebuffer(GL_FRAMEBUFFER, fboId); + glBindTexture(GL_TEXTURE_2D, 0); + + // Attach our texture to FBO + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, id, 0); + + // We read data as RGBA because FBO texture is configured as RGBA, despite binding another texture format + pixels = (unsigned char *)RL_MALLOC(rlGetPixelDataSize(width, height, RL_PIXELFORMAT_UNCOMPRESSED_R8G8B8A8)); + glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, pixels); + + glBindFramebuffer(GL_FRAMEBUFFER, 0); + + // Clean up temporal fbo + rlUnloadFramebuffer(fboId); +#endif + + return pixels; +} + +// Read screen pixel data (color buffer) +unsigned char *rlReadScreenPixels(int width, int height) +{ + unsigned char *screenData = (unsigned char *)RL_CALLOC(width*height*4, sizeof(unsigned char)); + + // NOTE 1: glReadPixels returns image flipped vertically -> (0,0) is the bottom left corner of the framebuffer + // NOTE 2: We are getting alpha channel! Be careful, it can be transparent if not cleared properly! + glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, screenData); + + // Flip image vertically! + unsigned char *imgData = (unsigned char *)RL_MALLOC(width*height*4*sizeof(unsigned char)); + + for (int y = height - 1; y >= 0; y--) + { + for (int x = 0; x < (width*4); x++) + { + imgData[((height - 1) - y)*width*4 + x] = screenData[(y*width*4) + x]; // Flip line + + // Set alpha component value to 255 (no trasparent image retrieval) + // NOTE: Alpha value has already been applied to RGB in framebuffer, we don't need it! + if (((x + 1)%4) == 0) imgData[((height - 1) - y)*width*4 + x] = 255; + } + } + + RL_FREE(screenData); + + return imgData; // NOTE: image data should be freed +} + +// Framebuffer management (fbo) +//----------------------------------------------------------------------------------------- +// Load a framebuffer to be used for rendering +// NOTE: No textures attached +unsigned int rlLoadFramebuffer(void) +{ + unsigned int fboId = 0; + +#if (defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)) && defined(RLGL_RENDER_TEXTURES_HINT) + glGenFramebuffers(1, &fboId); // Create the framebuffer object + glBindFramebuffer(GL_FRAMEBUFFER, 0); // Unbind any framebuffer +#endif + + return fboId; +} + +// Attach color buffer texture to an fbo (unloads previous attachment) +// NOTE: Attach type: 0-Color, 1-Depth renderbuffer, 2-Depth texture +void rlFramebufferAttach(unsigned int fboId, unsigned int texId, int attachType, int texType, int mipLevel) +{ +#if (defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)) && defined(RLGL_RENDER_TEXTURES_HINT) + glBindFramebuffer(GL_FRAMEBUFFER, fboId); + + switch (attachType) + { + case RL_ATTACHMENT_COLOR_CHANNEL0: + case RL_ATTACHMENT_COLOR_CHANNEL1: + case RL_ATTACHMENT_COLOR_CHANNEL2: + case RL_ATTACHMENT_COLOR_CHANNEL3: + case RL_ATTACHMENT_COLOR_CHANNEL4: + case RL_ATTACHMENT_COLOR_CHANNEL5: + case RL_ATTACHMENT_COLOR_CHANNEL6: + case RL_ATTACHMENT_COLOR_CHANNEL7: + { + if (texType == RL_ATTACHMENT_TEXTURE2D) glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + attachType, GL_TEXTURE_2D, texId, mipLevel); + else if (texType == RL_ATTACHMENT_RENDERBUFFER) glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + attachType, GL_RENDERBUFFER, texId); + else if (texType >= RL_ATTACHMENT_CUBEMAP_POSITIVE_X) glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + attachType, GL_TEXTURE_CUBE_MAP_POSITIVE_X + texType, texId, mipLevel); + + } break; + case RL_ATTACHMENT_DEPTH: + { + if (texType == RL_ATTACHMENT_TEXTURE2D) glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, texId, mipLevel); + else if (texType == RL_ATTACHMENT_RENDERBUFFER) glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, texId); + + } break; + case RL_ATTACHMENT_STENCIL: + { + if (texType == RL_ATTACHMENT_TEXTURE2D) glFramebufferTexture2D(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_TEXTURE_2D, texId, mipLevel); + else if (texType == RL_ATTACHMENT_RENDERBUFFER) glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, texId); + + } break; + default: break; + } + + glBindFramebuffer(GL_FRAMEBUFFER, 0); +#endif +} + +// Verify render texture is complete +bool rlFramebufferComplete(unsigned int id) +{ + bool result = false; + +#if (defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)) && defined(RLGL_RENDER_TEXTURES_HINT) + glBindFramebuffer(GL_FRAMEBUFFER, id); + + GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER); + + if (status != GL_FRAMEBUFFER_COMPLETE) + { + switch (status) + { + case GL_FRAMEBUFFER_UNSUPPORTED: TRACELOG(RL_LOG_WARNING, "FBO: [ID %i] Framebuffer is unsupported", id); break; + case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT: TRACELOG(RL_LOG_WARNING, "FBO: [ID %i] Framebuffer has incomplete attachment", id); break; +#if defined(GRAPHICS_API_OPENGL_ES2) + case GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS: TRACELOG(RL_LOG_WARNING, "FBO: [ID %i] Framebuffer has incomplete dimensions", id); break; +#endif + case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT: TRACELOG(RL_LOG_WARNING, "FBO: [ID %i] Framebuffer has a missing attachment", id); break; + default: break; + } + } + + glBindFramebuffer(GL_FRAMEBUFFER, 0); + + result = (status == GL_FRAMEBUFFER_COMPLETE); +#endif + + return result; +} + +// Unload framebuffer from GPU memory +// NOTE: All attached textures/cubemaps/renderbuffers are also deleted +void rlUnloadFramebuffer(unsigned int id) +{ +#if (defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)) && defined(RLGL_RENDER_TEXTURES_HINT) + // Query depth attachment to automatically delete texture/renderbuffer + int depthType = 0, depthId = 0; + glBindFramebuffer(GL_FRAMEBUFFER, id); // Bind framebuffer to query depth texture type + glGetFramebufferAttachmentParameteriv(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE, &depthType); + + // TODO: Review warning retrieving object name in WebGL + // WARNING: WebGL: INVALID_ENUM: getFramebufferAttachmentParameter: invalid parameter name + // https://registry.khronos.org/webgl/specs/latest/1.0/ + glGetFramebufferAttachmentParameteriv(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME, &depthId); + + unsigned int depthIdU = (unsigned int)depthId; + if (depthType == GL_RENDERBUFFER) glDeleteRenderbuffers(1, &depthIdU); + else if (depthType == GL_TEXTURE) glDeleteTextures(1, &depthIdU); + + // NOTE: If a texture object is deleted while its image is attached to the *currently bound* framebuffer, + // the texture image is automatically detached from the currently bound framebuffer + + glBindFramebuffer(GL_FRAMEBUFFER, 0); + glDeleteFramebuffers(1, &id); + + TRACELOG(RL_LOG_INFO, "FBO: [ID %i] Unloaded framebuffer from VRAM (GPU)", id); +#endif +} + +// Vertex data management +//----------------------------------------------------------------------------------------- +// Load a new attributes buffer +unsigned int rlLoadVertexBuffer(const void *buffer, int size, bool dynamic) +{ + unsigned int id = 0; + +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + glGenBuffers(1, &id); + glBindBuffer(GL_ARRAY_BUFFER, id); + glBufferData(GL_ARRAY_BUFFER, size, buffer, dynamic? GL_DYNAMIC_DRAW : GL_STATIC_DRAW); +#endif + + return id; +} + +// Load a new attributes element buffer +unsigned int rlLoadVertexBufferElement(const void *buffer, int size, bool dynamic) +{ + unsigned int id = 0; + +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + glGenBuffers(1, &id); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, id); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, size, buffer, dynamic? GL_DYNAMIC_DRAW : GL_STATIC_DRAW); +#endif + + return id; +} + +// Enable vertex buffer (VBO) +void rlEnableVertexBuffer(unsigned int id) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + glBindBuffer(GL_ARRAY_BUFFER, id); +#endif +} + +// Disable vertex buffer (VBO) +void rlDisableVertexBuffer(void) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + glBindBuffer(GL_ARRAY_BUFFER, 0); +#endif +} + +// Enable vertex buffer element (VBO element) +void rlEnableVertexBufferElement(unsigned int id) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, id); +#endif +} + +// Disable vertex buffer element (VBO element) +void rlDisableVertexBufferElement(void) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); +#endif +} + +// Update vertex buffer with new data +// NOTE: dataSize and offset must be provided in bytes +void rlUpdateVertexBuffer(unsigned int id, const void *data, int dataSize, int offset) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + glBindBuffer(GL_ARRAY_BUFFER, id); + glBufferSubData(GL_ARRAY_BUFFER, offset, dataSize, data); +#endif +} + +// Update vertex buffer elements with new data +// NOTE: dataSize and offset must be provided in bytes +void rlUpdateVertexBufferElements(unsigned int id, const void *data, int dataSize, int offset) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, id); + glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, offset, dataSize, data); +#endif +} + +// Enable vertex array object (VAO) +bool rlEnableVertexArray(unsigned int vaoId) +{ + bool result = false; +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + if (RLGL.ExtSupported.vao) + { + glBindVertexArray(vaoId); + result = true; + } +#endif + return result; +} + +// Disable vertex array object (VAO) +void rlDisableVertexArray(void) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + if (RLGL.ExtSupported.vao) glBindVertexArray(0); +#endif +} + +// Enable vertex attribute index +void rlEnableVertexAttribute(unsigned int index) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + glEnableVertexAttribArray(index); +#endif +} + +// Disable vertex attribute index +void rlDisableVertexAttribute(unsigned int index) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + glDisableVertexAttribArray(index); +#endif +} + +// Draw vertex array +void rlDrawVertexArray(int offset, int count) +{ + glDrawArrays(GL_TRIANGLES, offset, count); +} + +// Draw vertex array elements +void rlDrawVertexArrayElements(int offset, int count, const void *buffer) +{ + // NOTE: Added pointer math separately from function to avoid UBSAN complaining + unsigned short *bufferPtr = (unsigned short *)buffer; + if (offset > 0) bufferPtr += offset; + + glDrawElements(GL_TRIANGLES, count, GL_UNSIGNED_SHORT, (const unsigned short *)bufferPtr); +} + +// Draw vertex array instanced +void rlDrawVertexArrayInstanced(int offset, int count, int instances) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + glDrawArraysInstanced(GL_TRIANGLES, 0, count, instances); +#endif +} + +// Draw vertex array elements instanced +void rlDrawVertexArrayElementsInstanced(int offset, int count, const void *buffer, int instances) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + // NOTE: Added pointer math separately from function to avoid UBSAN complaining + unsigned short *bufferPtr = (unsigned short *)buffer; + if (offset > 0) bufferPtr += offset; + + glDrawElementsInstanced(GL_TRIANGLES, count, GL_UNSIGNED_SHORT, (const unsigned short *)bufferPtr, instances); +#endif +} + +#if defined(GRAPHICS_API_OPENGL_11) +// Enable vertex state pointer +void rlEnableStatePointer(int vertexAttribType, void *buffer) +{ + if (buffer != NULL) glEnableClientState(vertexAttribType); + switch (vertexAttribType) + { + case GL_VERTEX_ARRAY: glVertexPointer(3, GL_FLOAT, 0, buffer); break; + case GL_TEXTURE_COORD_ARRAY: glTexCoordPointer(2, GL_FLOAT, 0, buffer); break; + case GL_NORMAL_ARRAY: if (buffer != NULL) glNormalPointer(GL_FLOAT, 0, buffer); break; + case GL_COLOR_ARRAY: if (buffer != NULL) glColorPointer(4, GL_UNSIGNED_BYTE, 0, buffer); break; + //case GL_INDEX_ARRAY: if (buffer != NULL) glIndexPointer(GL_SHORT, 0, buffer); break; // Indexed colors + default: break; + } +} + +// Disable vertex state pointer +void rlDisableStatePointer(int vertexAttribType) +{ + glDisableClientState(vertexAttribType); +} +#endif + +// Load vertex array object (VAO) +unsigned int rlLoadVertexArray(void) +{ + unsigned int vaoId = 0; +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + if (RLGL.ExtSupported.vao) + { + glGenVertexArrays(1, &vaoId); + } +#endif + return vaoId; +} + +// Set vertex attribute +void rlSetVertexAttribute(unsigned int index, int compSize, int type, bool normalized, int stride, int offset) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + // NOTE: Data type could be: GL_BYTE, GL_UNSIGNED_BYTE, GL_SHORT, GL_UNSIGNED_SHORT, GL_INT, GL_UNSIGNED_INT + // Additional types (depends on OpenGL version or extensions): + // - GL_HALF_FLOAT, GL_FLOAT, GL_DOUBLE, GL_FIXED, + // - GL_INT_2_10_10_10_REV, GL_UNSIGNED_INT_2_10_10_10_REV, GL_UNSIGNED_INT_10F_11F_11F_REV + + size_t offsetNative = offset; + glVertexAttribPointer(index, compSize, type, normalized, stride, (void *)offsetNative); +#endif +} + +// Set vertex attribute divisor +void rlSetVertexAttributeDivisor(unsigned int index, int divisor) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + glVertexAttribDivisor(index, divisor); +#endif +} + +// Unload vertex array object (VAO) +void rlUnloadVertexArray(unsigned int vaoId) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + if (RLGL.ExtSupported.vao) + { + glBindVertexArray(0); + glDeleteVertexArrays(1, &vaoId); + TRACELOG(RL_LOG_INFO, "VAO: [ID %i] Unloaded vertex array data from VRAM (GPU)", vaoId); + } +#endif +} + +// Unload vertex buffer (VBO) +void rlUnloadVertexBuffer(unsigned int vboId) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + glDeleteBuffers(1, &vboId); + //TRACELOG(RL_LOG_INFO, "VBO: Unloaded vertex data from VRAM (GPU)"); +#endif +} + +// Shaders management +//----------------------------------------------------------------------------------------------- +// Load shader from code strings +// NOTE: If shader string is NULL, using default vertex/fragment shaders +unsigned int rlLoadShaderCode(const char *vsCode, const char *fsCode) +{ + unsigned int id = 0; + +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + unsigned int vertexShaderId = 0; + unsigned int fragmentShaderId = 0; + + // Compile vertex shader (if provided) + // NOTE: If not vertex shader is provided, use default one + if (vsCode != NULL) vertexShaderId = rlCompileShader(vsCode, GL_VERTEX_SHADER); + else vertexShaderId = RLGL.State.defaultVShaderId; + + // Compile fragment shader (if provided) + // NOTE: If not vertex shader is provided, use default one + if (fsCode != NULL) fragmentShaderId = rlCompileShader(fsCode, GL_FRAGMENT_SHADER); + else fragmentShaderId = RLGL.State.defaultFShaderId; + + // In case vertex and fragment shader are the default ones, no need to recompile, we can just assign the default shader program id + if ((vertexShaderId == RLGL.State.defaultVShaderId) && (fragmentShaderId == RLGL.State.defaultFShaderId)) id = RLGL.State.defaultShaderId; + else if ((vertexShaderId > 0) && (fragmentShaderId > 0)) + { + // One of or both shader are new, we need to compile a new shader program + id = rlLoadShaderProgram(vertexShaderId, fragmentShaderId); + + // We can detach and delete vertex/fragment shaders (if not default ones) + // NOTE: We detach shader before deletion to make sure memory is freed + if (vertexShaderId != RLGL.State.defaultVShaderId) + { + // WARNING: Shader program linkage could fail and returned id is 0 + if (id > 0) glDetachShader(id, vertexShaderId); + glDeleteShader(vertexShaderId); + } + if (fragmentShaderId != RLGL.State.defaultFShaderId) + { + // WARNING: Shader program linkage could fail and returned id is 0 + if (id > 0) glDetachShader(id, fragmentShaderId); + glDeleteShader(fragmentShaderId); + } + + // In case shader program loading failed, we assign default shader + if (id == 0) + { + // In case shader loading fails, we return the default shader + TRACELOG(RL_LOG_WARNING, "SHADER: Failed to load custom shader code, using default shader"); + id = RLGL.State.defaultShaderId; + } + /* + else + { + // Get available shader uniforms + // NOTE: This information is useful for debug... + int uniformCount = -1; + glGetProgramiv(id, GL_ACTIVE_UNIFORMS, &uniformCount); + + for (int i = 0; i < uniformCount; i++) + { + int namelen = -1; + int num = -1; + char name[256] = { 0 }; // Assume no variable names longer than 256 + GLenum type = GL_ZERO; + + // Get the name of the uniforms + glGetActiveUniform(id, i, sizeof(name) - 1, &namelen, &num, &type, name); + + name[namelen] = 0; + TRACELOGD("SHADER: [ID %i] Active uniform (%s) set at location: %i", id, name, glGetUniformLocation(id, name)); + } + } + */ + } +#endif + + return id; +} + +// Compile custom shader and return shader id +unsigned int rlCompileShader(const char *shaderCode, int type) +{ + unsigned int shader = 0; + +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + shader = glCreateShader(type); + glShaderSource(shader, 1, &shaderCode, NULL); + + GLint success = 0; + glCompileShader(shader); + glGetShaderiv(shader, GL_COMPILE_STATUS, &success); + + if (success == GL_FALSE) + { + switch (type) + { + case GL_VERTEX_SHADER: TRACELOG(RL_LOG_WARNING, "SHADER: [ID %i] Failed to compile vertex shader code", shader); break; + case GL_FRAGMENT_SHADER: TRACELOG(RL_LOG_WARNING, "SHADER: [ID %i] Failed to compile fragment shader code", shader); break; + //case GL_GEOMETRY_SHADER: + #if defined(GRAPHICS_API_OPENGL_43) + case GL_COMPUTE_SHADER: TRACELOG(RL_LOG_WARNING, "SHADER: [ID %i] Failed to compile compute shader code", shader); break; + #elif defined(GRAPHICS_API_OPENGL_33) + case GL_COMPUTE_SHADER: TRACELOG(RL_LOG_WARNING, "SHADER: Compute shaders not enabled. Define GRAPHICS_API_OPENGL_43", shader); break; + #endif + default: break; + } + + int maxLength = 0; + glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &maxLength); + + if (maxLength > 0) + { + int length = 0; + char *log = (char *)RL_CALLOC(maxLength, sizeof(char)); + glGetShaderInfoLog(shader, maxLength, &length, log); + TRACELOG(RL_LOG_WARNING, "SHADER: [ID %i] Compile error: %s", shader, log); + RL_FREE(log); + } + + shader = 0; + } + else + { + switch (type) + { + case GL_VERTEX_SHADER: TRACELOG(RL_LOG_INFO, "SHADER: [ID %i] Vertex shader compiled successfully", shader); break; + case GL_FRAGMENT_SHADER: TRACELOG(RL_LOG_INFO, "SHADER: [ID %i] Fragment shader compiled successfully", shader); break; + //case GL_GEOMETRY_SHADER: + #if defined(GRAPHICS_API_OPENGL_43) + case GL_COMPUTE_SHADER: TRACELOG(RL_LOG_INFO, "SHADER: [ID %i] Compute shader compiled successfully", shader); break; + #elif defined(GRAPHICS_API_OPENGL_33) + case GL_COMPUTE_SHADER: TRACELOG(RL_LOG_WARNING, "SHADER: Compute shaders not enabled. Define GRAPHICS_API_OPENGL_43", shader); break; + #endif + default: break; + } + } +#endif + + return shader; +} + +// Load custom shader strings and return program id +unsigned int rlLoadShaderProgram(unsigned int vShaderId, unsigned int fShaderId) +{ + unsigned int program = 0; + +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + GLint success = 0; + program = glCreateProgram(); + + glAttachShader(program, vShaderId); + glAttachShader(program, fShaderId); + + // NOTE: Default attribute shader locations must be Bound before linking + glBindAttribLocation(program, RL_DEFAULT_SHADER_ATTRIB_LOCATION_POSITION, RL_DEFAULT_SHADER_ATTRIB_NAME_POSITION); + glBindAttribLocation(program, RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD, RL_DEFAULT_SHADER_ATTRIB_NAME_TEXCOORD); + glBindAttribLocation(program, RL_DEFAULT_SHADER_ATTRIB_LOCATION_NORMAL, RL_DEFAULT_SHADER_ATTRIB_NAME_NORMAL); + glBindAttribLocation(program, RL_DEFAULT_SHADER_ATTRIB_LOCATION_COLOR, RL_DEFAULT_SHADER_ATTRIB_NAME_COLOR); + glBindAttribLocation(program, RL_DEFAULT_SHADER_ATTRIB_LOCATION_TANGENT, RL_DEFAULT_SHADER_ATTRIB_NAME_TANGENT); + glBindAttribLocation(program, RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD2, RL_DEFAULT_SHADER_ATTRIB_NAME_TEXCOORD2); + +#ifdef RL_SUPPORT_MESH_GPU_SKINNING + glBindAttribLocation(program, RL_DEFAULT_SHADER_ATTRIB_LOCATION_BONEIDS, RL_DEFAULT_SHADER_ATTRIB_NAME_BONEIDS); + glBindAttribLocation(program, RL_DEFAULT_SHADER_ATTRIB_LOCATION_BONEWEIGHTS, RL_DEFAULT_SHADER_ATTRIB_NAME_BONEWEIGHTS); +#endif + + // NOTE: If some attrib name is no found on the shader, it locations becomes -1 + + glLinkProgram(program); + + // NOTE: All uniform variables are intitialised to 0 when a program links + + glGetProgramiv(program, GL_LINK_STATUS, &success); + + if (success == GL_FALSE) + { + TRACELOG(RL_LOG_WARNING, "SHADER: [ID %i] Failed to link shader program", program); + + int maxLength = 0; + glGetProgramiv(program, GL_INFO_LOG_LENGTH, &maxLength); + + if (maxLength > 0) + { + int length = 0; + char *log = (char *)RL_CALLOC(maxLength, sizeof(char)); + glGetProgramInfoLog(program, maxLength, &length, log); + TRACELOG(RL_LOG_WARNING, "SHADER: [ID %i] Link error: %s", program, log); + RL_FREE(log); + } + + glDeleteProgram(program); + + program = 0; + } + else + { + // Get the size of compiled shader program (not available on OpenGL ES 2.0) + // NOTE: If GL_LINK_STATUS is GL_FALSE, program binary length is zero + //GLint binarySize = 0; + //glGetProgramiv(id, GL_PROGRAM_BINARY_LENGTH, &binarySize); + + TRACELOG(RL_LOG_INFO, "SHADER: [ID %i] Program shader loaded successfully", program); + } +#endif + return program; +} + +// Unload shader program +void rlUnloadShaderProgram(unsigned int id) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + glDeleteProgram(id); + + TRACELOG(RL_LOG_INFO, "SHADER: [ID %i] Unloaded shader program data from VRAM (GPU)", id); +#endif +} + +// Get shader location uniform +int rlGetLocationUniform(unsigned int shaderId, const char *uniformName) +{ + int location = -1; +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + location = glGetUniformLocation(shaderId, uniformName); + + //if (location == -1) TRACELOG(RL_LOG_WARNING, "SHADER: [ID %i] Failed to find shader uniform: %s", shaderId, uniformName); + //else TRACELOG(RL_LOG_INFO, "SHADER: [ID %i] Shader uniform (%s) set at location: %i", shaderId, uniformName, location); +#endif + return location; +} + +// Get shader location attribute +int rlGetLocationAttrib(unsigned int shaderId, const char *attribName) +{ + int location = -1; +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + location = glGetAttribLocation(shaderId, attribName); + + //if (location == -1) TRACELOG(RL_LOG_WARNING, "SHADER: [ID %i] Failed to find shader attribute: %s", shaderId, attribName); + //else TRACELOG(RL_LOG_INFO, "SHADER: [ID %i] Shader attribute (%s) set at location: %i", shaderId, attribName, location); +#endif + return location; +} + +// Set shader value uniform +void rlSetUniform(int locIndex, const void *value, int uniformType, int count) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + switch (uniformType) + { + case RL_SHADER_UNIFORM_FLOAT: glUniform1fv(locIndex, count, (float *)value); break; + case RL_SHADER_UNIFORM_VEC2: glUniform2fv(locIndex, count, (float *)value); break; + case RL_SHADER_UNIFORM_VEC3: glUniform3fv(locIndex, count, (float *)value); break; + case RL_SHADER_UNIFORM_VEC4: glUniform4fv(locIndex, count, (float *)value); break; + case RL_SHADER_UNIFORM_INT: glUniform1iv(locIndex, count, (int *)value); break; + case RL_SHADER_UNIFORM_IVEC2: glUniform2iv(locIndex, count, (int *)value); break; + case RL_SHADER_UNIFORM_IVEC3: glUniform3iv(locIndex, count, (int *)value); break; + case RL_SHADER_UNIFORM_IVEC4: glUniform4iv(locIndex, count, (int *)value); break; + #if !defined(GRAPHICS_API_OPENGL_ES2) + case RL_SHADER_UNIFORM_UINT: glUniform1uiv(locIndex, count, (unsigned int *)value); break; + case RL_SHADER_UNIFORM_UIVEC2: glUniform2uiv(locIndex, count, (unsigned int *)value); break; + case RL_SHADER_UNIFORM_UIVEC3: glUniform3uiv(locIndex, count, (unsigned int *)value); break; + case RL_SHADER_UNIFORM_UIVEC4: glUniform4uiv(locIndex, count, (unsigned int *)value); break; + #endif + case RL_SHADER_UNIFORM_SAMPLER2D: glUniform1iv(locIndex, count, (int *)value); break; + default: TRACELOG(RL_LOG_WARNING, "SHADER: Failed to set uniform value, data type not recognized"); + + // TODO: Support glUniform1uiv(), glUniform2uiv(), glUniform3uiv(), glUniform4uiv() + } +#endif +} + +// Set shader value attribute +void rlSetVertexAttributeDefault(int locIndex, const void *value, int attribType, int count) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + switch (attribType) + { + case RL_SHADER_ATTRIB_FLOAT: if (count == 1) glVertexAttrib1fv(locIndex, (float *)value); break; + case RL_SHADER_ATTRIB_VEC2: if (count == 2) glVertexAttrib2fv(locIndex, (float *)value); break; + case RL_SHADER_ATTRIB_VEC3: if (count == 3) glVertexAttrib3fv(locIndex, (float *)value); break; + case RL_SHADER_ATTRIB_VEC4: if (count == 4) glVertexAttrib4fv(locIndex, (float *)value); break; + default: TRACELOG(RL_LOG_WARNING, "SHADER: Failed to set attrib default value, data type not recognized"); + } +#endif +} + +// Set shader value uniform matrix +void rlSetUniformMatrix(int locIndex, Matrix mat) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + float matfloat[16] = { + mat.m0, mat.m1, mat.m2, mat.m3, + mat.m4, mat.m5, mat.m6, mat.m7, + mat.m8, mat.m9, mat.m10, mat.m11, + mat.m12, mat.m13, mat.m14, mat.m15 + }; + glUniformMatrix4fv(locIndex, 1, false, matfloat); +#endif +} + +// Set shader value uniform matrix +void rlSetUniformMatrices(int locIndex, const Matrix *matrices, int count) +{ +#if defined(GRAPHICS_API_OPENGL_33) + glUniformMatrix4fv(locIndex, count, true, (const float *)matrices); +#elif defined(GRAPHICS_API_OPENGL_ES2) + // WARNING: WebGL does not support Matrix transpose ("true" parameter) + // REF: https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/uniformMatrix + glUniformMatrix4fv(locIndex, count, false, (const float *)matrices); +#endif +} + +// Set shader value uniform sampler +void rlSetUniformSampler(int locIndex, unsigned int textureId) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + // Check if texture is already active + for (int i = 0; i < RL_DEFAULT_BATCH_MAX_TEXTURE_UNITS; i++) + { + if (RLGL.State.activeTextureId[i] == textureId) + { + glUniform1i(locIndex, 1 + i); + return; + } + } + + // Register a new active texture for the internal batch system + // NOTE: Default texture is always activated as GL_TEXTURE0 + for (int i = 0; i < RL_DEFAULT_BATCH_MAX_TEXTURE_UNITS; i++) + { + if (RLGL.State.activeTextureId[i] == 0) + { + glUniform1i(locIndex, 1 + i); // Activate new texture unit + RLGL.State.activeTextureId[i] = textureId; // Save texture id for binding on drawing + break; + } + } +#endif +} + +// Set shader currently active (id and locations) +void rlSetShader(unsigned int id, int *locs) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + if (RLGL.State.currentShaderId != id) + { + rlDrawRenderBatch(RLGL.currentBatch); + RLGL.State.currentShaderId = id; + RLGL.State.currentShaderLocs = locs; + } +#endif +} + +// Load compute shader program +unsigned int rlLoadComputeShaderProgram(unsigned int shaderId) +{ + unsigned int program = 0; + +#if defined(GRAPHICS_API_OPENGL_43) + GLint success = 0; + program = glCreateProgram(); + glAttachShader(program, shaderId); + glLinkProgram(program); + + // NOTE: All uniform variables are intitialised to 0 when a program links + + glGetProgramiv(program, GL_LINK_STATUS, &success); + + if (success == GL_FALSE) + { + TRACELOG(RL_LOG_WARNING, "SHADER: [ID %i] Failed to link compute shader program", program); + + int maxLength = 0; + glGetProgramiv(program, GL_INFO_LOG_LENGTH, &maxLength); + + if (maxLength > 0) + { + int length = 0; + char *log = (char *)RL_CALLOC(maxLength, sizeof(char)); + glGetProgramInfoLog(program, maxLength, &length, log); + TRACELOG(RL_LOG_WARNING, "SHADER: [ID %i] Link error: %s", program, log); + RL_FREE(log); + } + + glDeleteProgram(program); + + program = 0; + } + else + { + // Get the size of compiled shader program (not available on OpenGL ES 2.0) + // NOTE: If GL_LINK_STATUS is GL_FALSE, program binary length is zero + //GLint binarySize = 0; + //glGetProgramiv(id, GL_PROGRAM_BINARY_LENGTH, &binarySize); + + TRACELOG(RL_LOG_INFO, "SHADER: [ID %i] Compute shader program loaded successfully", program); + } +#else + TRACELOG(RL_LOG_WARNING, "SHADER: Compute shaders not enabled. Define GRAPHICS_API_OPENGL_43"); +#endif + + return program; +} + +// Dispatch compute shader (equivalent to *draw* for graphics pilepine) +void rlComputeShaderDispatch(unsigned int groupX, unsigned int groupY, unsigned int groupZ) +{ +#if defined(GRAPHICS_API_OPENGL_43) + glDispatchCompute(groupX, groupY, groupZ); +#endif +} + +// Load shader storage buffer object (SSBO) +unsigned int rlLoadShaderBuffer(unsigned int size, const void *data, int usageHint) +{ + unsigned int ssbo = 0; + +#if defined(GRAPHICS_API_OPENGL_43) + glGenBuffers(1, &ssbo); + glBindBuffer(GL_SHADER_STORAGE_BUFFER, ssbo); + glBufferData(GL_SHADER_STORAGE_BUFFER, size, data, usageHint? usageHint : RL_STREAM_COPY); + if (data == NULL) glClearBufferData(GL_SHADER_STORAGE_BUFFER, GL_R8UI, GL_RED_INTEGER, GL_UNSIGNED_BYTE, NULL); // Clear buffer data to 0 + glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0); +#else + TRACELOG(RL_LOG_WARNING, "SSBO: SSBO not enabled. Define GRAPHICS_API_OPENGL_43"); +#endif + + return ssbo; +} + +// Unload shader storage buffer object (SSBO) +void rlUnloadShaderBuffer(unsigned int ssboId) +{ +#if defined(GRAPHICS_API_OPENGL_43) + glDeleteBuffers(1, &ssboId); +#else + TRACELOG(RL_LOG_WARNING, "SSBO: SSBO not enabled. Define GRAPHICS_API_OPENGL_43"); +#endif + +} + +// Update SSBO buffer data +void rlUpdateShaderBuffer(unsigned int id, const void *data, unsigned int dataSize, unsigned int offset) +{ +#if defined(GRAPHICS_API_OPENGL_43) + glBindBuffer(GL_SHADER_STORAGE_BUFFER, id); + glBufferSubData(GL_SHADER_STORAGE_BUFFER, offset, dataSize, data); +#endif +} + +// Get SSBO buffer size +unsigned int rlGetShaderBufferSize(unsigned int id) +{ +#if defined(GRAPHICS_API_OPENGL_43) + GLint64 size = 0; + glBindBuffer(GL_SHADER_STORAGE_BUFFER, id); + glGetBufferParameteri64v(GL_SHADER_STORAGE_BUFFER, GL_BUFFER_SIZE, &size); + return (size > 0)? (unsigned int)size : 0; +#else + return 0; +#endif +} + +// Read SSBO buffer data (GPU->CPU) +void rlReadShaderBuffer(unsigned int id, void *dest, unsigned int count, unsigned int offset) +{ +#if defined(GRAPHICS_API_OPENGL_43) + glBindBuffer(GL_SHADER_STORAGE_BUFFER, id); + glGetBufferSubData(GL_SHADER_STORAGE_BUFFER, offset, count, dest); +#endif +} + +// Bind SSBO buffer +void rlBindShaderBuffer(unsigned int id, unsigned int index) +{ +#if defined(GRAPHICS_API_OPENGL_43) + glBindBufferBase(GL_SHADER_STORAGE_BUFFER, index, id); +#endif +} + +// Copy SSBO buffer data +void rlCopyShaderBuffer(unsigned int destId, unsigned int srcId, unsigned int destOffset, unsigned int srcOffset, unsigned int count) +{ +#if defined(GRAPHICS_API_OPENGL_43) + glBindBuffer(GL_COPY_READ_BUFFER, srcId); + glBindBuffer(GL_COPY_WRITE_BUFFER, destId); + glCopyBufferSubData(GL_COPY_READ_BUFFER, GL_COPY_WRITE_BUFFER, srcOffset, destOffset, count); +#endif +} + +// Bind image texture +void rlBindImageTexture(unsigned int id, unsigned int index, int format, bool readonly) +{ +#if defined(GRAPHICS_API_OPENGL_43) + unsigned int glInternalFormat = 0, glFormat = 0, glType = 0; + + rlGetGlTextureFormats(format, &glInternalFormat, &glFormat, &glType); + glBindImageTexture(index, id, 0, 0, 0, readonly? GL_READ_ONLY : GL_READ_WRITE, glInternalFormat); +#else + TRACELOG(RL_LOG_WARNING, "TEXTURE: Image texture binding not enabled. Define GRAPHICS_API_OPENGL_43"); +#endif +} + +// Matrix state management +//----------------------------------------------------------------------------------------- +// Get internal modelview matrix +Matrix rlGetMatrixModelview(void) +{ + Matrix matrix = rlMatrixIdentity(); +#if defined(GRAPHICS_API_OPENGL_11) + float mat[16]; + glGetFloatv(GL_MODELVIEW_MATRIX, mat); + matrix.m0 = mat[0]; + matrix.m1 = mat[1]; + matrix.m2 = mat[2]; + matrix.m3 = mat[3]; + matrix.m4 = mat[4]; + matrix.m5 = mat[5]; + matrix.m6 = mat[6]; + matrix.m7 = mat[7]; + matrix.m8 = mat[8]; + matrix.m9 = mat[9]; + matrix.m10 = mat[10]; + matrix.m11 = mat[11]; + matrix.m12 = mat[12]; + matrix.m13 = mat[13]; + matrix.m14 = mat[14]; + matrix.m15 = mat[15]; +#else + matrix = RLGL.State.modelview; +#endif + return matrix; +} + +// Get internal projection matrix +Matrix rlGetMatrixProjection(void) +{ +#if defined(GRAPHICS_API_OPENGL_11) + float mat[16]; + glGetFloatv(GL_PROJECTION_MATRIX,mat); + Matrix m; + m.m0 = mat[0]; + m.m1 = mat[1]; + m.m2 = mat[2]; + m.m3 = mat[3]; + m.m4 = mat[4]; + m.m5 = mat[5]; + m.m6 = mat[6]; + m.m7 = mat[7]; + m.m8 = mat[8]; + m.m9 = mat[9]; + m.m10 = mat[10]; + m.m11 = mat[11]; + m.m12 = mat[12]; + m.m13 = mat[13]; + m.m14 = mat[14]; + m.m15 = mat[15]; + return m; +#else + return RLGL.State.projection; +#endif +} + +// Get internal accumulated transform matrix +Matrix rlGetMatrixTransform(void) +{ + Matrix mat = rlMatrixIdentity(); +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + // TODO: Consider possible transform matrices in the RLGL.State.stack + // Is this the right order? or should we start with the first stored matrix instead of the last one? + //Matrix matStackTransform = rlMatrixIdentity(); + //for (int i = RLGL.State.stackCounter; i > 0; i--) matStackTransform = rlMatrixMultiply(RLGL.State.stack[i], matStackTransform); + mat = RLGL.State.transform; +#endif + return mat; +} + +// Get internal projection matrix for stereo render (selected eye) +Matrix rlGetMatrixProjectionStereo(int eye) +{ + Matrix mat = rlMatrixIdentity(); +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + mat = RLGL.State.projectionStereo[eye]; +#endif + return mat; +} + +// Get internal view offset matrix for stereo render (selected eye) +Matrix rlGetMatrixViewOffsetStereo(int eye) +{ + Matrix mat = rlMatrixIdentity(); +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + mat = RLGL.State.viewOffsetStereo[eye]; +#endif + return mat; +} + +// Set a custom modelview matrix (replaces internal modelview matrix) +void rlSetMatrixModelview(Matrix view) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + RLGL.State.modelview = view; +#endif +} + +// Set a custom projection matrix (replaces internal projection matrix) +void rlSetMatrixProjection(Matrix projection) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + RLGL.State.projection = projection; +#endif +} + +// Set eyes projection matrices for stereo rendering +void rlSetMatrixProjectionStereo(Matrix right, Matrix left) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + RLGL.State.projectionStereo[0] = right; + RLGL.State.projectionStereo[1] = left; +#endif +} + +// Set eyes view offsets matrices for stereo rendering +void rlSetMatrixViewOffsetStereo(Matrix right, Matrix left) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + RLGL.State.viewOffsetStereo[0] = right; + RLGL.State.viewOffsetStereo[1] = left; +#endif +} + +// Load and draw a quad in NDC +void rlLoadDrawQuad(void) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + unsigned int quadVAO = 0; + unsigned int quadVBO = 0; + + float vertices[] = { + // Positions Texcoords + -1.0f, 1.0f, 0.0f, 0.0f, 1.0f, + -1.0f, -1.0f, 0.0f, 0.0f, 0.0f, + 1.0f, 1.0f, 0.0f, 1.0f, 1.0f, + 1.0f, -1.0f, 0.0f, 1.0f, 0.0f, + }; + + // Gen VAO to contain VBO + glGenVertexArrays(1, &quadVAO); + glBindVertexArray(quadVAO); + + // Gen and fill vertex buffer (VBO) + glGenBuffers(1, &quadVBO); + glBindBuffer(GL_ARRAY_BUFFER, quadVBO); + glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), &vertices, GL_STATIC_DRAW); + + // Bind vertex attributes (position, texcoords) + glEnableVertexAttribArray(RL_DEFAULT_SHADER_ATTRIB_LOCATION_POSITION); + glVertexAttribPointer(RL_DEFAULT_SHADER_ATTRIB_LOCATION_POSITION, 3, GL_FLOAT, GL_FALSE, 5*sizeof(float), (void *)0); // Positions + glEnableVertexAttribArray(RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD); + glVertexAttribPointer(RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD, 2, GL_FLOAT, GL_FALSE, 5*sizeof(float), (void *)(3*sizeof(float))); // Texcoords + + // Draw quad + glBindVertexArray(quadVAO); + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); + glBindVertexArray(0); + + // Delete buffers (VBO and VAO) + glDeleteBuffers(1, &quadVBO); + glDeleteVertexArrays(1, &quadVAO); +#endif +} + +// Load and draw a cube in NDC +void rlLoadDrawCube(void) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + unsigned int cubeVAO = 0; + unsigned int cubeVBO = 0; + + float vertices[] = { + // Positions Normals Texcoords + -1.0f, -1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, + 1.0f, 1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 1.0f, 1.0f, + 1.0f, -1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 1.0f, 0.0f, + 1.0f, 1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 1.0f, 1.0f, + -1.0f, -1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, + -1.0f, 1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 0.0f, 1.0f, + -1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, + 1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, + 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, + 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, + -1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, + -1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, + -1.0f, 1.0f, 1.0f, -1.0f, 0.0f, 0.0f, 1.0f, 0.0f, + -1.0f, 1.0f, -1.0f, -1.0f, 0.0f, 0.0f, 1.0f, 1.0f, + -1.0f, -1.0f, -1.0f, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f, + -1.0f, -1.0f, -1.0f, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f, + -1.0f, -1.0f, 1.0f, -1.0f, 0.0f, 0.0f, 0.0f, 0.0f, + -1.0f, 1.0f, 1.0f, -1.0f, 0.0f, 0.0f, 1.0f, 0.0f, + 1.0f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, + 1.0f, -1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, + 1.0f, 1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, + 1.0f, -1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, + 1.0f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, + 1.0f, -1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, + -1.0f, -1.0f, -1.0f, 0.0f, -1.0f, 0.0f, 0.0f, 1.0f, + 1.0f, -1.0f, -1.0f, 0.0f, -1.0f, 0.0f, 1.0f, 1.0f, + 1.0f, -1.0f, 1.0f, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f, + 1.0f, -1.0f, 1.0f, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f, + -1.0f, -1.0f, 1.0f, 0.0f, -1.0f, 0.0f, 0.0f, 0.0f, + -1.0f, -1.0f, -1.0f, 0.0f, -1.0f, 0.0f, 0.0f, 1.0f, + -1.0f, 1.0f, -1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, + 1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, + 1.0f, 1.0f, -1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, + 1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, + -1.0f, 1.0f, -1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, + -1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f + }; + + // Gen VAO to contain VBO + glGenVertexArrays(1, &cubeVAO); + glBindVertexArray(cubeVAO); + + // Gen and fill vertex buffer (VBO) + glGenBuffers(1, &cubeVBO); + glBindBuffer(GL_ARRAY_BUFFER, cubeVBO); + glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); + + // Bind vertex attributes (position, normals, texcoords) + glBindVertexArray(cubeVAO); + glEnableVertexAttribArray(RL_DEFAULT_SHADER_ATTRIB_LOCATION_POSITION); + glVertexAttribPointer(RL_DEFAULT_SHADER_ATTRIB_LOCATION_POSITION, 3, GL_FLOAT, GL_FALSE, 8*sizeof(float), (void *)0); // Positions + glEnableVertexAttribArray(RL_DEFAULT_SHADER_ATTRIB_LOCATION_NORMAL); + glVertexAttribPointer(RL_DEFAULT_SHADER_ATTRIB_LOCATION_NORMAL, 3, GL_FLOAT, GL_FALSE, 8*sizeof(float), (void *)(3*sizeof(float))); // Normals + glEnableVertexAttribArray(RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD); + glVertexAttribPointer(RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD, 2, GL_FLOAT, GL_FALSE, 8*sizeof(float), (void *)(6*sizeof(float))); // Texcoords + glBindBuffer(GL_ARRAY_BUFFER, 0); + glBindVertexArray(0); + + // Draw cube + glBindVertexArray(cubeVAO); + glDrawArrays(GL_TRIANGLES, 0, 36); + glBindVertexArray(0); + + // Delete VBO and VAO + glDeleteBuffers(1, &cubeVBO); + glDeleteVertexArrays(1, &cubeVAO); +#endif +} + +// Get name string for pixel format +const char *rlGetPixelFormatName(unsigned int format) +{ + switch (format) + { + case RL_PIXELFORMAT_UNCOMPRESSED_GRAYSCALE: return "GRAYSCALE"; break; // 8 bit per pixel (no alpha) + case RL_PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA: return "GRAY_ALPHA"; break; // 8*2 bpp (2 channels) + case RL_PIXELFORMAT_UNCOMPRESSED_R5G6B5: return "R5G6B5"; break; // 16 bpp + case RL_PIXELFORMAT_UNCOMPRESSED_R8G8B8: return "R8G8B8"; break; // 24 bpp + case RL_PIXELFORMAT_UNCOMPRESSED_R5G5B5A1: return "R5G5B5A1"; break; // 16 bpp (1 bit alpha) + case RL_PIXELFORMAT_UNCOMPRESSED_R4G4B4A4: return "R4G4B4A4"; break; // 16 bpp (4 bit alpha) + case RL_PIXELFORMAT_UNCOMPRESSED_R8G8B8A8: return "R8G8B8A8"; break; // 32 bpp + case RL_PIXELFORMAT_UNCOMPRESSED_R32: return "R32"; break; // 32 bpp (1 channel - float) + case RL_PIXELFORMAT_UNCOMPRESSED_R32G32B32: return "R32G32B32"; break; // 32*3 bpp (3 channels - float) + case RL_PIXELFORMAT_UNCOMPRESSED_R32G32B32A32: return "R32G32B32A32"; break; // 32*4 bpp (4 channels - float) + case RL_PIXELFORMAT_UNCOMPRESSED_R16: return "R16"; break; // 16 bpp (1 channel - half float) + case RL_PIXELFORMAT_UNCOMPRESSED_R16G16B16: return "R16G16B16"; break; // 16*3 bpp (3 channels - half float) + case RL_PIXELFORMAT_UNCOMPRESSED_R16G16B16A16: return "R16G16B16A16"; break; // 16*4 bpp (4 channels - half float) + case RL_PIXELFORMAT_COMPRESSED_DXT1_RGB: return "DXT1_RGB"; break; // 4 bpp (no alpha) + case RL_PIXELFORMAT_COMPRESSED_DXT1_RGBA: return "DXT1_RGBA"; break; // 4 bpp (1 bit alpha) + case RL_PIXELFORMAT_COMPRESSED_DXT3_RGBA: return "DXT3_RGBA"; break; // 8 bpp + case RL_PIXELFORMAT_COMPRESSED_DXT5_RGBA: return "DXT5_RGBA"; break; // 8 bpp + case RL_PIXELFORMAT_COMPRESSED_ETC1_RGB: return "ETC1_RGB"; break; // 4 bpp + case RL_PIXELFORMAT_COMPRESSED_ETC2_RGB: return "ETC2_RGB"; break; // 4 bpp + case RL_PIXELFORMAT_COMPRESSED_ETC2_EAC_RGBA: return "ETC2_RGBA"; break; // 8 bpp + case RL_PIXELFORMAT_COMPRESSED_PVRT_RGB: return "PVRT_RGB"; break; // 4 bpp + case RL_PIXELFORMAT_COMPRESSED_PVRT_RGBA: return "PVRT_RGBA"; break; // 4 bpp + case RL_PIXELFORMAT_COMPRESSED_ASTC_4x4_RGBA: return "ASTC_4x4_RGBA"; break; // 8 bpp + case RL_PIXELFORMAT_COMPRESSED_ASTC_8x8_RGBA: return "ASTC_8x8_RGBA"; break; // 2 bpp + default: return "UNKNOWN"; break; + } +} + +//---------------------------------------------------------------------------------- +// Module specific Functions Definition +//---------------------------------------------------------------------------------- +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) +// Load default shader (just vertex positioning and texture coloring) +// NOTE: This shader program is used for internal buffers +// NOTE: Loaded: RLGL.State.defaultShaderId, RLGL.State.defaultShaderLocs +static void rlLoadShaderDefault(void) +{ + RLGL.State.defaultShaderLocs = (int *)RL_CALLOC(RL_MAX_SHADER_LOCATIONS, sizeof(int)); + + // NOTE: All locations must be reseted to -1 (no location) + for (int i = 0; i < RL_MAX_SHADER_LOCATIONS; i++) RLGL.State.defaultShaderLocs[i] = -1; + + // Vertex shader directly defined, no external file required + const char *defaultVShaderCode = +#if defined(GRAPHICS_API_OPENGL_21) + "#version 120 \n" + "attribute vec3 vertexPosition; \n" + "attribute vec2 vertexTexCoord; \n" + "attribute vec4 vertexColor; \n" + "varying vec2 fragTexCoord; \n" + "varying vec4 fragColor; \n" +#elif defined(GRAPHICS_API_OPENGL_33) + "#version 330 \n" + "in vec3 vertexPosition; \n" + "in vec2 vertexTexCoord; \n" + "in vec4 vertexColor; \n" + "out vec2 fragTexCoord; \n" + "out vec4 fragColor; \n" +#endif + +#if defined(GRAPHICS_API_OPENGL_ES3) + "#version 300 es \n" + "precision mediump float; \n" // Precision required for OpenGL ES3 (WebGL 2) (on some browsers) + "in vec3 vertexPosition; \n" + "in vec2 vertexTexCoord; \n" + "in vec4 vertexColor; \n" + "out vec2 fragTexCoord; \n" + "out vec4 fragColor; \n" +#elif defined(GRAPHICS_API_OPENGL_ES2) + "#version 100 \n" + "precision mediump float; \n" // Precision required for OpenGL ES2 (WebGL) (on some browsers) + "attribute vec3 vertexPosition; \n" + "attribute vec2 vertexTexCoord; \n" + "attribute vec4 vertexColor; \n" + "varying vec2 fragTexCoord; \n" + "varying vec4 fragColor; \n" +#endif + + "uniform mat4 mvp; \n" + "void main() \n" + "{ \n" + " fragTexCoord = vertexTexCoord; \n" + " fragColor = vertexColor; \n" + " gl_Position = mvp*vec4(vertexPosition, 1.0); \n" + "} \n"; + + // Fragment shader directly defined, no external file required + const char *defaultFShaderCode = +#if defined(GRAPHICS_API_OPENGL_21) + "#version 120 \n" + "varying vec2 fragTexCoord; \n" + "varying vec4 fragColor; \n" + "uniform sampler2D texture0; \n" + "uniform vec4 colDiffuse; \n" + "void main() \n" + "{ \n" + " vec4 texelColor = texture2D(texture0, fragTexCoord); \n" + " gl_FragColor = texelColor*colDiffuse*fragColor; \n" + "} \n"; +#elif defined(GRAPHICS_API_OPENGL_33) + "#version 330 \n" + "in vec2 fragTexCoord; \n" + "in vec4 fragColor; \n" + "out vec4 finalColor; \n" + "uniform sampler2D texture0; \n" + "uniform vec4 colDiffuse; \n" + "void main() \n" + "{ \n" + " vec4 texelColor = texture(texture0, fragTexCoord); \n" + " finalColor = texelColor*colDiffuse*fragColor; \n" + "} \n"; +#endif + +#if defined(GRAPHICS_API_OPENGL_ES3) + "#version 300 es \n" + "precision mediump float; \n" // Precision required for OpenGL ES3 (WebGL 2) + "in vec2 fragTexCoord; \n" + "in vec4 fragColor; \n" + "out vec4 finalColor; \n" + "uniform sampler2D texture0; \n" + "uniform vec4 colDiffuse; \n" + "void main() \n" + "{ \n" + " vec4 texelColor = texture(texture0, fragTexCoord); \n" + " finalColor = texelColor*colDiffuse*fragColor; \n" + "} \n"; +#elif defined(GRAPHICS_API_OPENGL_ES2) + "#version 100 \n" + "precision mediump float; \n" // Precision required for OpenGL ES2 (WebGL) + "varying vec2 fragTexCoord; \n" + "varying vec4 fragColor; \n" + "uniform sampler2D texture0; \n" + "uniform vec4 colDiffuse; \n" + "void main() \n" + "{ \n" + " vec4 texelColor = texture2D(texture0, fragTexCoord); \n" + " gl_FragColor = texelColor*colDiffuse*fragColor; \n" + "} \n"; +#endif + + // NOTE: Compiled vertex/fragment shaders are not deleted, + // they are kept for re-use as default shaders in case some shader loading fails + RLGL.State.defaultVShaderId = rlCompileShader(defaultVShaderCode, GL_VERTEX_SHADER); // Compile default vertex shader + RLGL.State.defaultFShaderId = rlCompileShader(defaultFShaderCode, GL_FRAGMENT_SHADER); // Compile default fragment shader + + RLGL.State.defaultShaderId = rlLoadShaderProgram(RLGL.State.defaultVShaderId, RLGL.State.defaultFShaderId); + + if (RLGL.State.defaultShaderId > 0) + { + TRACELOG(RL_LOG_INFO, "SHADER: [ID %i] Default shader loaded successfully", RLGL.State.defaultShaderId); + + // Set default shader locations: attributes locations + RLGL.State.defaultShaderLocs[RL_SHADER_LOC_VERTEX_POSITION] = glGetAttribLocation(RLGL.State.defaultShaderId, RL_DEFAULT_SHADER_ATTRIB_NAME_POSITION); + RLGL.State.defaultShaderLocs[RL_SHADER_LOC_VERTEX_TEXCOORD01] = glGetAttribLocation(RLGL.State.defaultShaderId, RL_DEFAULT_SHADER_ATTRIB_NAME_TEXCOORD); + RLGL.State.defaultShaderLocs[RL_SHADER_LOC_VERTEX_COLOR] = glGetAttribLocation(RLGL.State.defaultShaderId, RL_DEFAULT_SHADER_ATTRIB_NAME_COLOR); + + // Set default shader locations: uniform locations + RLGL.State.defaultShaderLocs[RL_SHADER_LOC_MATRIX_MVP] = glGetUniformLocation(RLGL.State.defaultShaderId, RL_DEFAULT_SHADER_UNIFORM_NAME_MVP); + RLGL.State.defaultShaderLocs[RL_SHADER_LOC_COLOR_DIFFUSE] = glGetUniformLocation(RLGL.State.defaultShaderId, RL_DEFAULT_SHADER_UNIFORM_NAME_COLOR); + RLGL.State.defaultShaderLocs[RL_SHADER_LOC_MAP_DIFFUSE] = glGetUniformLocation(RLGL.State.defaultShaderId, RL_DEFAULT_SHADER_SAMPLER2D_NAME_TEXTURE0); + } + else TRACELOG(RL_LOG_WARNING, "SHADER: [ID %i] Failed to load default shader", RLGL.State.defaultShaderId); +} + +// Unload default shader +// NOTE: Unloads: RLGL.State.defaultShaderId, RLGL.State.defaultShaderLocs +static void rlUnloadShaderDefault(void) +{ + glUseProgram(0); + + glDetachShader(RLGL.State.defaultShaderId, RLGL.State.defaultVShaderId); + glDetachShader(RLGL.State.defaultShaderId, RLGL.State.defaultFShaderId); + glDeleteShader(RLGL.State.defaultVShaderId); + glDeleteShader(RLGL.State.defaultFShaderId); + + glDeleteProgram(RLGL.State.defaultShaderId); + + RL_FREE(RLGL.State.defaultShaderLocs); + + TRACELOG(RL_LOG_INFO, "SHADER: [ID %i] Default shader unloaded successfully", RLGL.State.defaultShaderId); +} + +#if defined(RLGL_SHOW_GL_DETAILS_INFO) +// Get compressed format official GL identifier name +static const char *rlGetCompressedFormatName(int format) +{ + switch (format) + { + // GL_EXT_texture_compression_s3tc + case 0x83F0: return "GL_COMPRESSED_RGB_S3TC_DXT1_EXT"; break; + case 0x83F1: return "GL_COMPRESSED_RGBA_S3TC_DXT1_EXT"; break; + case 0x83F2: return "GL_COMPRESSED_RGBA_S3TC_DXT3_EXT"; break; + case 0x83F3: return "GL_COMPRESSED_RGBA_S3TC_DXT5_EXT"; break; + // GL_3DFX_texture_compression_FXT1 + case 0x86B0: return "GL_COMPRESSED_RGB_FXT1_3DFX"; break; + case 0x86B1: return "GL_COMPRESSED_RGBA_FXT1_3DFX"; break; + // GL_IMG_texture_compression_pvrtc + case 0x8C00: return "GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG"; break; + case 0x8C01: return "GL_COMPRESSED_RGB_PVRTC_2BPPV1_IMG"; break; + case 0x8C02: return "GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG"; break; + case 0x8C03: return "GL_COMPRESSED_RGBA_PVRTC_2BPPV1_IMG"; break; + // GL_OES_compressed_ETC1_RGB8_texture + case 0x8D64: return "GL_ETC1_RGB8_OES"; break; + // GL_ARB_texture_compression_rgtc + case 0x8DBB: return "GL_COMPRESSED_RED_RGTC1"; break; + case 0x8DBC: return "GL_COMPRESSED_SIGNED_RED_RGTC1"; break; + case 0x8DBD: return "GL_COMPRESSED_RG_RGTC2"; break; + case 0x8DBE: return "GL_COMPRESSED_SIGNED_RG_RGTC2"; break; + // GL_ARB_texture_compression_bptc + case 0x8E8C: return "GL_COMPRESSED_RGBA_BPTC_UNORM_ARB"; break; + case 0x8E8D: return "GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM_ARB"; break; + case 0x8E8E: return "GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT_ARB"; break; + case 0x8E8F: return "GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT_ARB"; break; + // GL_ARB_ES3_compatibility + case 0x9274: return "GL_COMPRESSED_RGB8_ETC2"; break; + case 0x9275: return "GL_COMPRESSED_SRGB8_ETC2"; break; + case 0x9276: return "GL_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2"; break; + case 0x9277: return "GL_COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2"; break; + case 0x9278: return "GL_COMPRESSED_RGBA8_ETC2_EAC"; break; + case 0x9279: return "GL_COMPRESSED_SRGB8_ALPHA8_ETC2_EAC"; break; + case 0x9270: return "GL_COMPRESSED_R11_EAC"; break; + case 0x9271: return "GL_COMPRESSED_SIGNED_R11_EAC"; break; + case 0x9272: return "GL_COMPRESSED_RG11_EAC"; break; + case 0x9273: return "GL_COMPRESSED_SIGNED_RG11_EAC"; break; + // GL_KHR_texture_compression_astc_hdr + case 0x93B0: return "GL_COMPRESSED_RGBA_ASTC_4x4_KHR"; break; + case 0x93B1: return "GL_COMPRESSED_RGBA_ASTC_5x4_KHR"; break; + case 0x93B2: return "GL_COMPRESSED_RGBA_ASTC_5x5_KHR"; break; + case 0x93B3: return "GL_COMPRESSED_RGBA_ASTC_6x5_KHR"; break; + case 0x93B4: return "GL_COMPRESSED_RGBA_ASTC_6x6_KHR"; break; + case 0x93B5: return "GL_COMPRESSED_RGBA_ASTC_8x5_KHR"; break; + case 0x93B6: return "GL_COMPRESSED_RGBA_ASTC_8x6_KHR"; break; + case 0x93B7: return "GL_COMPRESSED_RGBA_ASTC_8x8_KHR"; break; + case 0x93B8: return "GL_COMPRESSED_RGBA_ASTC_10x5_KHR"; break; + case 0x93B9: return "GL_COMPRESSED_RGBA_ASTC_10x6_KHR"; break; + case 0x93BA: return "GL_COMPRESSED_RGBA_ASTC_10x8_KHR"; break; + case 0x93BB: return "GL_COMPRESSED_RGBA_ASTC_10x10_KHR"; break; + case 0x93BC: return "GL_COMPRESSED_RGBA_ASTC_12x10_KHR"; break; + case 0x93BD: return "GL_COMPRESSED_RGBA_ASTC_12x12_KHR"; break; + case 0x93D0: return "GL_COMPRESSED_SRGB8_ALPHA8_ASTC_4x4_KHR"; break; + case 0x93D1: return "GL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x4_KHR"; break; + case 0x93D2: return "GL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x5_KHR"; break; + case 0x93D3: return "GL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x5_KHR"; break; + case 0x93D4: return "GL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x6_KHR"; break; + case 0x93D5: return "GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x5_KHR"; break; + case 0x93D6: return "GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x6_KHR"; break; + case 0x93D7: return "GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x8_KHR"; break; + case 0x93D8: return "GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x5_KHR"; break; + case 0x93D9: return "GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x6_KHR"; break; + case 0x93DA: return "GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x8_KHR"; break; + case 0x93DB: return "GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x10_KHR"; break; + case 0x93DC: return "GL_COMPRESSED_SRGB8_ALPHA8_ASTC_12x10_KHR"; break; + case 0x93DD: return "GL_COMPRESSED_SRGB8_ALPHA8_ASTC_12x12_KHR"; break; + default: return "GL_COMPRESSED_UNKNOWN"; break; + } +} +#endif // RLGL_SHOW_GL_DETAILS_INFO + +#endif // GRAPHICS_API_OPENGL_33 || GRAPHICS_API_OPENGL_ES2 + +// Get pixel data size in bytes (image or texture) +// NOTE: Size depends on pixel format +static int rlGetPixelDataSize(int width, int height, int format) +{ + int dataSize = 0; // Size in bytes + int bpp = 0; // Bits per pixel + + switch (format) + { + case RL_PIXELFORMAT_UNCOMPRESSED_GRAYSCALE: bpp = 8; break; + case RL_PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA: + case RL_PIXELFORMAT_UNCOMPRESSED_R5G6B5: + case RL_PIXELFORMAT_UNCOMPRESSED_R5G5B5A1: + case RL_PIXELFORMAT_UNCOMPRESSED_R4G4B4A4: bpp = 16; break; + case RL_PIXELFORMAT_UNCOMPRESSED_R8G8B8A8: bpp = 32; break; + case RL_PIXELFORMAT_UNCOMPRESSED_R8G8B8: bpp = 24; break; + case RL_PIXELFORMAT_UNCOMPRESSED_R32: bpp = 32; break; + case RL_PIXELFORMAT_UNCOMPRESSED_R32G32B32: bpp = 32*3; break; + case RL_PIXELFORMAT_UNCOMPRESSED_R32G32B32A32: bpp = 32*4; break; + case RL_PIXELFORMAT_UNCOMPRESSED_R16: bpp = 16; break; + case RL_PIXELFORMAT_UNCOMPRESSED_R16G16B16: bpp = 16*3; break; + case RL_PIXELFORMAT_UNCOMPRESSED_R16G16B16A16: bpp = 16*4; break; + case RL_PIXELFORMAT_COMPRESSED_DXT1_RGB: + case RL_PIXELFORMAT_COMPRESSED_DXT1_RGBA: + case RL_PIXELFORMAT_COMPRESSED_ETC1_RGB: + case RL_PIXELFORMAT_COMPRESSED_ETC2_RGB: + case RL_PIXELFORMAT_COMPRESSED_PVRT_RGB: + case RL_PIXELFORMAT_COMPRESSED_PVRT_RGBA: bpp = 4; break; + case RL_PIXELFORMAT_COMPRESSED_DXT3_RGBA: + case RL_PIXELFORMAT_COMPRESSED_DXT5_RGBA: + case RL_PIXELFORMAT_COMPRESSED_ETC2_EAC_RGBA: + case RL_PIXELFORMAT_COMPRESSED_ASTC_4x4_RGBA: bpp = 8; break; + case RL_PIXELFORMAT_COMPRESSED_ASTC_8x8_RGBA: bpp = 2; break; + default: break; + } + + double bytesPerPixel = (double)bpp/8.0; + dataSize = (int)(bytesPerPixel*width*height); // Total data size in bytes + + // Most compressed formats works on 4x4 blocks, + // if texture is smaller, minimum dataSize is 8 or 16 + if ((width < 4) && (height < 4)) + { + if ((format >= RL_PIXELFORMAT_COMPRESSED_DXT1_RGB) && (format < RL_PIXELFORMAT_COMPRESSED_DXT3_RGBA)) dataSize = 8; + else if ((format >= RL_PIXELFORMAT_COMPRESSED_DXT3_RGBA) && (format < RL_PIXELFORMAT_COMPRESSED_ASTC_8x8_RGBA)) dataSize = 16; + } + + return dataSize; +} + +// Auxiliar math functions + +// Get float array of matrix data +static rl_float16 rlMatrixToFloatV(Matrix mat) +{ + rl_float16 result = { 0 }; + + result.v[0] = mat.m0; + result.v[1] = mat.m1; + result.v[2] = mat.m2; + result.v[3] = mat.m3; + result.v[4] = mat.m4; + result.v[5] = mat.m5; + result.v[6] = mat.m6; + result.v[7] = mat.m7; + result.v[8] = mat.m8; + result.v[9] = mat.m9; + result.v[10] = mat.m10; + result.v[11] = mat.m11; + result.v[12] = mat.m12; + result.v[13] = mat.m13; + result.v[14] = mat.m14; + result.v[15] = mat.m15; + + return result; +} + +// Get identity matrix +static Matrix rlMatrixIdentity(void) +{ + Matrix result = { + 1.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 1.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 1.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f + }; + + return result; +} + +// Get two matrix multiplication +// NOTE: When multiplying matrices... the order matters! +static Matrix rlMatrixMultiply(Matrix left, Matrix right) +{ + Matrix result = { 0 }; + + result.m0 = left.m0*right.m0 + left.m1*right.m4 + left.m2*right.m8 + left.m3*right.m12; + result.m1 = left.m0*right.m1 + left.m1*right.m5 + left.m2*right.m9 + left.m3*right.m13; + result.m2 = left.m0*right.m2 + left.m1*right.m6 + left.m2*right.m10 + left.m3*right.m14; + result.m3 = left.m0*right.m3 + left.m1*right.m7 + left.m2*right.m11 + left.m3*right.m15; + result.m4 = left.m4*right.m0 + left.m5*right.m4 + left.m6*right.m8 + left.m7*right.m12; + result.m5 = left.m4*right.m1 + left.m5*right.m5 + left.m6*right.m9 + left.m7*right.m13; + result.m6 = left.m4*right.m2 + left.m5*right.m6 + left.m6*right.m10 + left.m7*right.m14; + result.m7 = left.m4*right.m3 + left.m5*right.m7 + left.m6*right.m11 + left.m7*right.m15; + result.m8 = left.m8*right.m0 + left.m9*right.m4 + left.m10*right.m8 + left.m11*right.m12; + result.m9 = left.m8*right.m1 + left.m9*right.m5 + left.m10*right.m9 + left.m11*right.m13; + result.m10 = left.m8*right.m2 + left.m9*right.m6 + left.m10*right.m10 + left.m11*right.m14; + result.m11 = left.m8*right.m3 + left.m9*right.m7 + left.m10*right.m11 + left.m11*right.m15; + result.m12 = left.m12*right.m0 + left.m13*right.m4 + left.m14*right.m8 + left.m15*right.m12; + result.m13 = left.m12*right.m1 + left.m13*right.m5 + left.m14*right.m9 + left.m15*right.m13; + result.m14 = left.m12*right.m2 + left.m13*right.m6 + left.m14*right.m10 + left.m15*right.m14; + result.m15 = left.m12*right.m3 + left.m13*right.m7 + left.m14*right.m11 + left.m15*right.m15; + + return result; +} + +// Transposes provided matrix +static Matrix rlMatrixTranspose(Matrix mat) +{ + Matrix result = { 0 }; + + result.m0 = mat.m0; + result.m1 = mat.m4; + result.m2 = mat.m8; + result.m3 = mat.m12; + result.m4 = mat.m1; + result.m5 = mat.m5; + result.m6 = mat.m9; + result.m7 = mat.m13; + result.m8 = mat.m2; + result.m9 = mat.m6; + result.m10 = mat.m10; + result.m11 = mat.m14; + result.m12 = mat.m3; + result.m13 = mat.m7; + result.m14 = mat.m11; + result.m15 = mat.m15; + + return result; +} + +// Invert provided matrix +static Matrix rlMatrixInvert(Matrix mat) +{ + Matrix result = { 0 }; + + // Cache the matrix values (speed optimization) + float a00 = mat.m0, a01 = mat.m1, a02 = mat.m2, a03 = mat.m3; + float a10 = mat.m4, a11 = mat.m5, a12 = mat.m6, a13 = mat.m7; + float a20 = mat.m8, a21 = mat.m9, a22 = mat.m10, a23 = mat.m11; + float a30 = mat.m12, a31 = mat.m13, a32 = mat.m14, a33 = mat.m15; + + float b00 = a00*a11 - a01*a10; + float b01 = a00*a12 - a02*a10; + float b02 = a00*a13 - a03*a10; + float b03 = a01*a12 - a02*a11; + float b04 = a01*a13 - a03*a11; + float b05 = a02*a13 - a03*a12; + float b06 = a20*a31 - a21*a30; + float b07 = a20*a32 - a22*a30; + float b08 = a20*a33 - a23*a30; + float b09 = a21*a32 - a22*a31; + float b10 = a21*a33 - a23*a31; + float b11 = a22*a33 - a23*a32; + + // Calculate the invert determinant (inlined to avoid double-caching) + float invDet = 1.0f/(b00*b11 - b01*b10 + b02*b09 + b03*b08 - b04*b07 + b05*b06); + + result.m0 = (a11*b11 - a12*b10 + a13*b09)*invDet; + result.m1 = (-a01*b11 + a02*b10 - a03*b09)*invDet; + result.m2 = (a31*b05 - a32*b04 + a33*b03)*invDet; + result.m3 = (-a21*b05 + a22*b04 - a23*b03)*invDet; + result.m4 = (-a10*b11 + a12*b08 - a13*b07)*invDet; + result.m5 = (a00*b11 - a02*b08 + a03*b07)*invDet; + result.m6 = (-a30*b05 + a32*b02 - a33*b01)*invDet; + result.m7 = (a20*b05 - a22*b02 + a23*b01)*invDet; + result.m8 = (a10*b10 - a11*b08 + a13*b06)*invDet; + result.m9 = (-a00*b10 + a01*b08 - a03*b06)*invDet; + result.m10 = (a30*b04 - a31*b02 + a33*b00)*invDet; + result.m11 = (-a20*b04 + a21*b02 - a23*b00)*invDet; + result.m12 = (-a10*b09 + a11*b07 - a12*b06)*invDet; + result.m13 = (a00*b09 - a01*b07 + a02*b06)*invDet; + result.m14 = (-a30*b03 + a31*b01 - a32*b00)*invDet; + result.m15 = (a20*b03 - a21*b01 + a22*b00)*invDet; + + return result; +} + +#endif // RLGL_IMPLEMENTATION diff --git a/third_party/raylib/larch64/libraylib.a b/third_party/raylib/larch64/libraylib.a new file mode 100644 index 0000000000..fa538e5214 --- /dev/null +++ b/third_party/raylib/larch64/libraylib.a @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f760af8b4693cf60e3760341e5275890d78d933da2354c4bad0572ec575b970a +size 2001860 diff --git a/third_party/raylib/x86_64/libraylib.a b/third_party/raylib/x86_64/libraylib.a new file mode 100644 index 0000000000..ea124c1bcf --- /dev/null +++ b/third_party/raylib/x86_64/libraylib.a @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3c928e849b51b04d8e3603cd649184299efed0e9e0fb02201612b967b31efd73 +size 2771092 diff --git a/uv.lock b/uv.lock index e12842ac5b..f5c128fcb7 100644 --- a/uv.lock +++ b/uv.lock @@ -116,12 +116,12 @@ wheels = [ [[package]] name = "bzip2" version = "1.0.8" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=bzip2&rev=releases#de7c914a461f68a5fb80d57d534181b6350afe8f" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=bzip2&rev=releases#12581f30b45b570dd0bbc36055fe1532f5a8ef60" } [[package]] name = "capnproto" version = "1.0.1" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=capnproto&rev=releases#de7c914a461f68a5fb80d57d534181b6350afe8f" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=capnproto&rev=releases#12581f30b45b570dd0bbc36055fe1532f5a8ef60" } [[package]] name = "casadi" @@ -381,7 +381,7 @@ wheels = [ [[package]] name = "eigen" version = "3.4.0" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=eigen&rev=releases#de7c914a461f68a5fb80d57d534181b6350afe8f" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=eigen&rev=releases#12581f30b45b570dd0bbc36055fe1532f5a8ef60" } [[package]] name = "execnet" @@ -395,7 +395,7 @@ wheels = [ [[package]] name = "ffmpeg" version = "7.1.0" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=ffmpeg&rev=releases#de7c914a461f68a5fb80d57d534181b6350afe8f" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=ffmpeg&rev=releases#12581f30b45b570dd0bbc36055fe1532f5a8ef60" } [[package]] name = "fonttools" @@ -442,7 +442,7 @@ wheels = [ [[package]] name = "gcc-arm-none-eabi" version = "13.2.1" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=gcc-arm-none-eabi&rev=releases#de7c914a461f68a5fb80d57d534181b6350afe8f" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=gcc-arm-none-eabi&rev=releases#12581f30b45b570dd0bbc36055fe1532f5a8ef60" } [[package]] name = "ghp-import" @@ -459,7 +459,7 @@ wheels = [ [[package]] name = "git-lfs" version = "3.6.1" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=git-lfs&rev=releases#de7c914a461f68a5fb80d57d534181b6350afe8f" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=git-lfs&rev=releases#12581f30b45b570dd0bbc36055fe1532f5a8ef60" } [[package]] name = "google-crc32c" @@ -577,7 +577,7 @@ wheels = [ [[package]] name = "libjpeg" version = "3.1.0" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=libjpeg&rev=releases#de7c914a461f68a5fb80d57d534181b6350afe8f" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=libjpeg&rev=releases#12581f30b45b570dd0bbc36055fe1532f5a8ef60" } [[package]] name = "libusb1" @@ -593,7 +593,7 @@ wheels = [ [[package]] name = "libyuv" version = "1922.0" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=libyuv&rev=releases#de7c914a461f68a5fb80d57d534181b6350afe8f" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=libyuv&rev=releases#12581f30b45b570dd0bbc36055fe1532f5a8ef60" } [[package]] name = "markdown" @@ -745,7 +745,7 @@ wheels = [ [[package]] name = "ncurses" version = "6.5" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=ncurses&rev=releases#de7c914a461f68a5fb80d57d534181b6350afe8f" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=ncurses&rev=releases#12581f30b45b570dd0bbc36055fe1532f5a8ef60" } [[package]] name = "numpy" @@ -912,7 +912,7 @@ requires-dist = [ { name = "python3-dev", git = "https://github.com/commaai/dependencies.git?subdirectory=python3-dev&rev=releases" }, { name = "pyzmq" }, { name = "qrcode" }, - { name = "raylib", git = "https://github.com/commaai/dependencies.git?subdirectory=raylib&rev=releases" }, + { name = "raylib", specifier = ">5.5.0.3" }, { name = "requests" }, { name = "ruff", marker = "extra == 'testing'" }, { name = "scons" }, @@ -935,7 +935,7 @@ provides-extras = ["docs", "testing", "dev", "tools"] [[package]] name = "openssl3" version = "3.4.1" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=openssl3&rev=releases#de7c914a461f68a5fb80d57d534181b6350afe8f" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=openssl3&rev=releases#12581f30b45b570dd0bbc36055fe1532f5a8ef60" } [[package]] name = "packaging" @@ -1306,7 +1306,7 @@ wheels = [ [[package]] name = "python3-dev" version = "3.12.8" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=python3-dev&rev=releases#de7c914a461f68a5fb80d57d534181b6350afe8f" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=python3-dev&rev=releases#12581f30b45b570dd0bbc36055fe1532f5a8ef60" } [[package]] name = "pyyaml" @@ -1374,10 +1374,20 @@ wheels = [ [[package]] name = "raylib" version = "5.5.0.4" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=raylib&rev=releases#de7c914a461f68a5fb80d57d534181b6350afe8f" } +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cffi" }, ] +sdist = { url = "https://files.pythonhosted.org/packages/7c/4b/858958762c075c54058ee3b0771838fd505ca908871e6a0397b01086e526/raylib-5.5.0.4.tar.gz", hash = "sha256:996506e8a533cd7a6a3ef6c44ec11f9d6936698f2c394a991af8022be33079a0", size = 184413, upload-time = "2025-12-11T15:32:12.465Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/95/21/9117d7013997a65f6d51c6f56145b2c583eeba8f7c1af71a60776eaae9b9/raylib-5.5.0.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:31f64f71e42fed10e8f3629028c9f5700906e0e573b915cfc2244d7a3f3b2ed9", size = 1635486, upload-time = "2025-12-11T15:27:31.05Z" }, + { url = "https://files.pythonhosted.org/packages/1c/a3/e55039c8f49856c5a194f2b81f27ca6ba2d5900024f09435587e177bfaf2/raylib-5.5.0.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:80bfa053e765d47a9f58d59e321a999184b5a5190e369dd015c12fcfd08d6217", size = 1554132, upload-time = "2025-12-11T15:27:33.291Z" }, + { url = "https://files.pythonhosted.org/packages/58/1c/86bee75ecaa577214da16b374f8de70b45885452703f622c63e06baa0b8e/raylib-5.5.0.4-cp312-cp312-manylinux2010_i686.manylinux_2_12_i686.whl", hash = "sha256:033240c61c1a1fc06fecff747a183671431a4ce63a0c8aafec59217845f86888", size = 2039888, upload-time = "2025-12-11T15:27:36.059Z" }, + { url = "https://files.pythonhosted.org/packages/fb/f9/00763899bb8a178a927b5dda90aca692c80ff6cec5f51e6fee88db3f45c2/raylib-5.5.0.4-cp312-cp312-manylinux2014_aarch64.whl", hash = "sha256:ba87ca50c5748cab75de37a991b7f3f836ce500efbb2d737a923a5f464169088", size = 2198926, upload-time = "2025-12-11T18:50:08.813Z" }, + { url = "https://files.pythonhosted.org/packages/6b/e9/0123385e369904335985ebd59157f7a10c89c3a706dffcf6dace863a1fa2/raylib-5.5.0.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:788830bc371ce067c4930ff46a1b6eca0c9cf27bac88f81b035e4b73cc6bf197", size = 2205629, upload-time = "2025-12-11T15:27:39.491Z" }, + { url = "https://files.pythonhosted.org/packages/5c/fa/c25087b39d2db2d833a52b4056ae62db74e64b4be677f816e0b368e65453/raylib-5.5.0.4-cp312-cp312-win32.whl", hash = "sha256:e09f395035484337776c90e6c9955c5876b988db7e13168dcadb6ed11974f8ee", size = 1457266, upload-time = "2025-12-11T15:27:43.798Z" }, + { url = "https://files.pythonhosted.org/packages/2c/66/a307e61c953ace906ba68ba1174ed8f1e90e68d5fc3e3af9fb7dc46d68d1/raylib-5.5.0.4-cp312-cp312-win_amd64.whl", hash = "sha256:553043a050a31f2ef072f26d3a70373f838a04733f7c5b26a4e9ee3f8caf06ec", size = 1708354, upload-time = "2025-12-11T15:27:45.979Z" }, +] [[package]] name = "requests" @@ -1664,7 +1674,7 @@ wheels = [ [[package]] name = "zeromq" version = "4.3.5" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=zeromq&rev=releases#de7c914a461f68a5fb80d57d534181b6350afe8f" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=zeromq&rev=releases#12581f30b45b570dd0bbc36055fe1532f5a8ef60" } [[package]] name = "zstandard" @@ -1694,4 +1704,4 @@ wheels = [ [[package]] name = "zstd" version = "1.5.6" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=zstd&rev=releases#de7c914a461f68a5fb80d57d534181b6350afe8f" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=zstd&rev=releases#12581f30b45b570dd0bbc36055fe1532f5a8ef60" } From 56ed3771970ea92d4ba4b33e86802208416f7e99 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 3 Mar 2026 17:23:48 -0800 Subject: [PATCH 015/141] Zipapp fixes (#37538) * zip app fixes * add nl * rename * emoji was brok * bytes --- release/pack.py | 2 +- .../mici/layouts/settings/network/__init__.py | 155 +----------------- .../settings/network/network_layout.py | 154 +++++++++++++++++ .../ui/mici/layouts/settings/settings.py | 2 +- system/ui/lib/emoji.py | 12 +- system/ui/lib/multilang.py | 2 +- system/ui/mici_setup.py | 3 +- 7 files changed, 168 insertions(+), 162 deletions(-) create mode 100644 selfdrive/ui/mici/layouts/settings/network/network_layout.py diff --git a/release/pack.py b/release/pack.py index 92ff68fe76..8979e0e618 100755 --- a/release/pack.py +++ b/release/pack.py @@ -12,7 +12,7 @@ from openpilot.common.basedir import BASEDIR DIRS = ['cereal', 'openpilot'] -EXTS = ['.png', '.py', '.ttf', '.capnp', '.json', '.fnt', '.mo'] +EXTS = ['.png', '.py', '.ttf', '.capnp', '.json', '.fnt', '.mo', '.po'] INTERPRETER = '/usr/bin/env python3' diff --git a/selfdrive/ui/mici/layouts/settings/network/__init__.py b/selfdrive/ui/mici/layouts/settings/network/__init__.py index 553a74fc60..ddbab4b478 100644 --- a/selfdrive/ui/mici/layouts/settings/network/__init__.py +++ b/selfdrive/ui/mici/layouts/settings/network/__init__.py @@ -1,13 +1,9 @@ import pyray as rl -from openpilot.system.ui.widgets.scroller import NavScroller -from openpilot.selfdrive.ui.mici.layouts.settings.network.wifi_ui import WifiUIMici, WifiIcon -from openpilot.selfdrive.ui.mici.widgets.button import BigButton, BigMultiToggle, BigParamControl, BigToggle -from openpilot.selfdrive.ui.mici.widgets.dialog import BigInputDialog -from openpilot.selfdrive.ui.ui_state import ui_state -from openpilot.selfdrive.ui.lib.prime_state import PrimeType +from openpilot.selfdrive.ui.mici.layouts.settings.network.wifi_ui import WifiIcon +from openpilot.selfdrive.ui.mici.widgets.button import BigButton from openpilot.system.ui.lib.application import gui_app -from openpilot.system.ui.lib.wifi_manager import WifiManager, Network, MeteredType, ConnectStatus, SecurityType, normalize_ssid +from openpilot.system.ui.lib.wifi_manager import WifiManager, ConnectStatus, SecurityType, normalize_ssid class WifiNetworkButton(BigButton): @@ -62,148 +58,3 @@ class WifiNetworkButton(BigButton): lock_x = icon_x + self._txt_icon.width - self._lock_txt.width + 7 lock_y = icon_y + self._txt_icon.height - self._lock_txt.height + 8 rl.draw_texture_ex(self._lock_txt, (lock_x, lock_y), 0.0, 1.0, rl.WHITE) - - -class NetworkLayoutMici(NavScroller): - def __init__(self): - super().__init__() - - self._wifi_manager = WifiManager() - self._wifi_manager.set_active(False) - self._wifi_ui = WifiUIMici(self._wifi_manager) - - self._wifi_manager.add_callbacks( - networks_updated=self._on_network_updated, - ) - - # ******** Tethering ******** - def tethering_toggle_callback(checked: bool): - self._tethering_toggle_btn.set_enabled(False) - self._tethering_password_btn.set_enabled(False) - self._network_metered_btn.set_enabled(False) - self._wifi_manager.set_tethering_active(checked) - - self._tethering_toggle_btn = BigToggle("enable tethering", "", toggle_callback=tethering_toggle_callback) - - def tethering_password_callback(password: str): - if password: - self._tethering_toggle_btn.set_enabled(False) - self._tethering_password_btn.set_enabled(False) - self._wifi_manager.set_tethering_password(password) - - def tethering_password_clicked(): - tethering_password = self._wifi_manager.tethering_password - dlg = BigInputDialog("enter password...", tethering_password, minimum_length=8, - confirm_callback=tethering_password_callback) - gui_app.push_widget(dlg) - - txt_tethering = gui_app.texture("icons_mici/settings/network/tethering.png", 64, 54) - self._tethering_password_btn = BigButton("tethering password", "", txt_tethering) - self._tethering_password_btn.set_click_callback(tethering_password_clicked) - - # ******** Network Metered ******** - def network_metered_callback(value: str): - self._network_metered_btn.set_enabled(False) - metered = { - 'default': MeteredType.UNKNOWN, - 'metered': MeteredType.YES, - 'unmetered': MeteredType.NO - }.get(value, MeteredType.UNKNOWN) - self._wifi_manager.set_current_network_metered(metered) - - # TODO: signal for current network metered type when changing networks, this is wrong until you press it once - # TODO: disable when not connected - self._network_metered_btn = BigMultiToggle("network usage", ["default", "metered", "unmetered"], select_callback=network_metered_callback) - self._network_metered_btn.set_enabled(False) - - self._wifi_button = WifiNetworkButton(self._wifi_manager) - self._wifi_button.set_click_callback(lambda: gui_app.push_widget(self._wifi_ui)) - - # ******** Advanced settings ******** - # ******** Roaming toggle ******** - self._roaming_btn = BigParamControl("enable roaming", "GsmRoaming", toggle_callback=self._toggle_roaming) - - # ******** APN settings ******** - self._apn_btn = BigButton("apn settings", "edit") - self._apn_btn.set_click_callback(self._edit_apn) - - # ******** Cellular metered toggle ******** - self._cellular_metered_btn = BigParamControl("cellular metered", "GsmMetered", toggle_callback=self._toggle_cellular_metered) - - # Main scroller ---------------------------------- - self._scroller.add_widgets([ - self._wifi_button, - self._network_metered_btn, - self._tethering_toggle_btn, - self._tethering_password_btn, - # /* Advanced settings - self._roaming_btn, - self._apn_btn, - self._cellular_metered_btn, - # */ - ]) - - # Set initial config - roaming_enabled = ui_state.params.get_bool("GsmRoaming") - metered = ui_state.params.get_bool("GsmMetered") - self._wifi_manager.update_gsm_settings(roaming_enabled, ui_state.params.get("GsmApn") or "", metered) - - def _update_state(self): - super()._update_state() - - # If not using prime SIM, show GSM settings and enable IPv4 forwarding - show_cell_settings = ui_state.prime_state.get_type() in (PrimeType.NONE, PrimeType.LITE) - self._wifi_manager.set_ipv4_forward(show_cell_settings) - self._roaming_btn.set_visible(show_cell_settings) - self._apn_btn.set_visible(show_cell_settings) - self._cellular_metered_btn.set_visible(show_cell_settings) - - def show_event(self): - super().show_event() - self._wifi_manager.set_active(True) - - # Process wifi callbacks while at any point in the nav stack - gui_app.add_nav_stack_tick(self._wifi_manager.process_callbacks) - - def hide_event(self): - super().hide_event() - self._wifi_manager.set_active(False) - - gui_app.remove_nav_stack_tick(self._wifi_manager.process_callbacks) - - def _toggle_roaming(self, checked: bool): - self._wifi_manager.update_gsm_settings(checked, ui_state.params.get("GsmApn") or "", ui_state.params.get_bool("GsmMetered")) - - def _edit_apn(self): - def update_apn(apn: str): - apn = apn.strip() - if apn == "": - ui_state.params.remove("GsmApn") - else: - ui_state.params.put("GsmApn", apn) - - self._wifi_manager.update_gsm_settings(ui_state.params.get_bool("GsmRoaming"), apn, ui_state.params.get_bool("GsmMetered")) - - current_apn = ui_state.params.get("GsmApn") or "" - dlg = BigInputDialog("enter APN...", current_apn, minimum_length=0, confirm_callback=update_apn) - gui_app.push_widget(dlg) - - def _toggle_cellular_metered(self, checked: bool): - self._wifi_manager.update_gsm_settings(ui_state.params.get_bool("GsmRoaming"), ui_state.params.get("GsmApn") or "", checked) - - def _on_network_updated(self, networks: list[Network]): - # Update tethering state - tethering_active = self._wifi_manager.is_tethering_active() - # TODO: use real signals (like activated/settings changed, etc.) to speed up re-enabling buttons - self._tethering_toggle_btn.set_enabled(True) - self._tethering_password_btn.set_enabled(True) - self._network_metered_btn.set_enabled(lambda: not tethering_active and bool(self._wifi_manager.ipv4_address)) - self._tethering_toggle_btn.set_checked(tethering_active) - - # Update network metered - self._network_metered_btn.set_value( - { - MeteredType.UNKNOWN: 'default', - MeteredType.YES: 'metered', - MeteredType.NO: 'unmetered' - }.get(self._wifi_manager.current_network_metered, 'default')) diff --git a/selfdrive/ui/mici/layouts/settings/network/network_layout.py b/selfdrive/ui/mici/layouts/settings/network/network_layout.py new file mode 100644 index 0000000000..9f6fae4b5f --- /dev/null +++ b/selfdrive/ui/mici/layouts/settings/network/network_layout.py @@ -0,0 +1,154 @@ +from openpilot.system.ui.widgets.scroller import NavScroller +from openpilot.selfdrive.ui.mici.layouts.settings.network import WifiNetworkButton +from openpilot.selfdrive.ui.mici.layouts.settings.network.wifi_ui import WifiUIMici +from openpilot.selfdrive.ui.mici.widgets.button import BigButton, BigMultiToggle, BigParamControl, BigToggle +from openpilot.selfdrive.ui.mici.widgets.dialog import BigInputDialog +from openpilot.selfdrive.ui.ui_state import ui_state +from openpilot.selfdrive.ui.lib.prime_state import PrimeType +from openpilot.system.ui.lib.application import gui_app +from openpilot.system.ui.lib.wifi_manager import WifiManager, Network, MeteredType + + +class NetworkLayoutMici(NavScroller): + def __init__(self): + super().__init__() + + self._wifi_manager = WifiManager() + self._wifi_manager.set_active(False) + self._wifi_ui = WifiUIMici(self._wifi_manager) + + self._wifi_manager.add_callbacks( + networks_updated=self._on_network_updated, + ) + + # ******** Tethering ******** + def tethering_toggle_callback(checked: bool): + self._tethering_toggle_btn.set_enabled(False) + self._tethering_password_btn.set_enabled(False) + self._network_metered_btn.set_enabled(False) + self._wifi_manager.set_tethering_active(checked) + + self._tethering_toggle_btn = BigToggle("enable tethering", "", toggle_callback=tethering_toggle_callback) + + def tethering_password_callback(password: str): + if password: + self._tethering_toggle_btn.set_enabled(False) + self._tethering_password_btn.set_enabled(False) + self._wifi_manager.set_tethering_password(password) + + def tethering_password_clicked(): + tethering_password = self._wifi_manager.tethering_password + dlg = BigInputDialog("enter password...", tethering_password, minimum_length=8, + confirm_callback=tethering_password_callback) + gui_app.push_widget(dlg) + + txt_tethering = gui_app.texture("icons_mici/settings/network/tethering.png", 64, 54) + self._tethering_password_btn = BigButton("tethering password", "", txt_tethering) + self._tethering_password_btn.set_click_callback(tethering_password_clicked) + + # ******** Network Metered ******** + def network_metered_callback(value: str): + self._network_metered_btn.set_enabled(False) + metered = { + 'default': MeteredType.UNKNOWN, + 'metered': MeteredType.YES, + 'unmetered': MeteredType.NO + }.get(value, MeteredType.UNKNOWN) + self._wifi_manager.set_current_network_metered(metered) + + # TODO: signal for current network metered type when changing networks, this is wrong until you press it once + # TODO: disable when not connected + self._network_metered_btn = BigMultiToggle("network usage", ["default", "metered", "unmetered"], select_callback=network_metered_callback) + self._network_metered_btn.set_enabled(False) + + self._wifi_button = WifiNetworkButton(self._wifi_manager) + self._wifi_button.set_click_callback(lambda: gui_app.push_widget(self._wifi_ui)) + + # ******** Advanced settings ******** + # ******** Roaming toggle ******** + self._roaming_btn = BigParamControl("enable roaming", "GsmRoaming", toggle_callback=self._toggle_roaming) + + # ******** APN settings ******** + self._apn_btn = BigButton("apn settings", "edit") + self._apn_btn.set_click_callback(self._edit_apn) + + # ******** Cellular metered toggle ******** + self._cellular_metered_btn = BigParamControl("cellular metered", "GsmMetered", toggle_callback=self._toggle_cellular_metered) + + # Main scroller ---------------------------------- + self._scroller.add_widgets([ + self._wifi_button, + self._network_metered_btn, + self._tethering_toggle_btn, + self._tethering_password_btn, + # /* Advanced settings + self._roaming_btn, + self._apn_btn, + self._cellular_metered_btn, + # */ + ]) + + # Set initial config + roaming_enabled = ui_state.params.get_bool("GsmRoaming") + metered = ui_state.params.get_bool("GsmMetered") + self._wifi_manager.update_gsm_settings(roaming_enabled, ui_state.params.get("GsmApn") or "", metered) + + def _update_state(self): + super()._update_state() + + # If not using prime SIM, show GSM settings and enable IPv4 forwarding + show_cell_settings = ui_state.prime_state.get_type() in (PrimeType.NONE, PrimeType.LITE) + self._wifi_manager.set_ipv4_forward(show_cell_settings) + self._roaming_btn.set_visible(show_cell_settings) + self._apn_btn.set_visible(show_cell_settings) + self._cellular_metered_btn.set_visible(show_cell_settings) + + def show_event(self): + super().show_event() + self._wifi_manager.set_active(True) + + # Process wifi callbacks while at any point in the nav stack + gui_app.add_nav_stack_tick(self._wifi_manager.process_callbacks) + + def hide_event(self): + super().hide_event() + self._wifi_manager.set_active(False) + + gui_app.remove_nav_stack_tick(self._wifi_manager.process_callbacks) + + def _toggle_roaming(self, checked: bool): + self._wifi_manager.update_gsm_settings(checked, ui_state.params.get("GsmApn") or "", ui_state.params.get_bool("GsmMetered")) + + def _edit_apn(self): + def update_apn(apn: str): + apn = apn.strip() + if apn == "": + ui_state.params.remove("GsmApn") + else: + ui_state.params.put("GsmApn", apn) + + self._wifi_manager.update_gsm_settings(ui_state.params.get_bool("GsmRoaming"), apn, ui_state.params.get_bool("GsmMetered")) + + current_apn = ui_state.params.get("GsmApn") or "" + dlg = BigInputDialog("enter APN...", current_apn, minimum_length=0, confirm_callback=update_apn) + gui_app.push_widget(dlg) + + def _toggle_cellular_metered(self, checked: bool): + self._wifi_manager.update_gsm_settings(ui_state.params.get_bool("GsmRoaming"), ui_state.params.get("GsmApn") or "", checked) + + def _on_network_updated(self, networks: list[Network]): + # Update tethering state + tethering_active = self._wifi_manager.is_tethering_active() + # TODO: use real signals (like activated/settings changed, etc.) to speed up re-enabling buttons + self._tethering_toggle_btn.set_enabled(True) + self._tethering_password_btn.set_enabled(True) + self._network_metered_btn.set_enabled(lambda: not tethering_active and bool(self._wifi_manager.ipv4_address)) + self._tethering_toggle_btn.set_checked(tethering_active) + + # Update network metered + self._network_metered_btn.set_value( + { + MeteredType.UNKNOWN: 'default', + MeteredType.YES: 'metered', + MeteredType.NO: 'unmetered' + }.get(self._wifi_manager.current_network_metered, 'default')) diff --git a/selfdrive/ui/mici/layouts/settings/settings.py b/selfdrive/ui/mici/layouts/settings/settings.py index c7fb3201f5..8e037ccf88 100644 --- a/selfdrive/ui/mici/layouts/settings/settings.py +++ b/selfdrive/ui/mici/layouts/settings/settings.py @@ -2,7 +2,7 @@ from openpilot.common.params import Params from openpilot.system.ui.widgets.scroller import NavScroller from openpilot.selfdrive.ui.mici.widgets.button import BigButton from openpilot.selfdrive.ui.mici.layouts.settings.toggles import TogglesLayoutMici -from openpilot.selfdrive.ui.mici.layouts.settings.network import NetworkLayoutMici +from openpilot.selfdrive.ui.mici.layouts.settings.network.network_layout import NetworkLayoutMici from openpilot.selfdrive.ui.mici.layouts.settings.device import DeviceLayoutMici, PairBigButton from openpilot.selfdrive.ui.mici.layouts.settings.developer import DeveloperLayoutMici from openpilot.selfdrive.ui.mici.layouts.settings.firehose import FirehoseLayout diff --git a/system/ui/lib/emoji.py b/system/ui/lib/emoji.py index 37228e2d45..ad4c272c8d 100644 --- a/system/ui/lib/emoji.py +++ b/system/ui/lib/emoji.py @@ -1,12 +1,13 @@ import io import re +import functools +from importlib.resources import as_file from PIL import Image, ImageDraw, ImageFont import pyray as rl from openpilot.system.ui.lib.application import FONT_DIR -_emoji_font: ImageFont.FreeTypeFont | None = None _cache: dict[str, rl.Texture] = {} EMOJI_REGEX = re.compile( @@ -33,11 +34,10 @@ EMOJI_REGEX = re.compile( flags=re.UNICODE ) -def _load_emoji_font() -> ImageFont.FreeTypeFont | None: - global _emoji_font - if _emoji_font is None: - _emoji_font = ImageFont.truetype(str(FONT_DIR.joinpath("NotoColorEmoji.ttf")), 109) - return _emoji_font +@functools.cache +def _load_emoji_font() -> ImageFont.FreeTypeFont: + with as_file(FONT_DIR.joinpath("NotoColorEmoji.ttf")) as font_path: + return ImageFont.truetype(io.BytesIO(font_path.read_bytes()), 109) def find_emoji(text): return [(m.start(), m.end(), m.group()) for m in EMOJI_REGEX.finditer(text)] diff --git a/system/ui/lib/multilang.py b/system/ui/lib/multilang.py index 7f191bc75e..3c6a6b8564 100644 --- a/system/ui/lib/multilang.py +++ b/system/ui/lib/multilang.py @@ -74,7 +74,7 @@ def load_translations(path) -> tuple[dict[str, str], dict[str, list[str]]]: translations: msgid -> msgstr plurals: msgid -> [msgstr[0], msgstr[1], ...] """ - with open(str(path), encoding='utf-8') as f: + with path.open(encoding='utf-8') as f: lines = f.readlines() translations: dict[str, str] = {} diff --git a/system/ui/mici_setup.py b/system/ui/mici_setup.py index 22ba8c4032..5dc9905ddf 100755 --- a/system/ui/mici_setup.py +++ b/system/ui/mici_setup.py @@ -25,7 +25,8 @@ from openpilot.system.ui.widgets.button import SmallButton from openpilot.system.ui.widgets.label import UnifiedLabel from openpilot.system.ui.widgets.scroller import Scroller, NavScroller, ITEM_SPACING from openpilot.system.ui.widgets.slider import LargerSlider, SmallSlider -from openpilot.selfdrive.ui.mici.layouts.settings.network import WifiNetworkButton, WifiUIMici +from openpilot.selfdrive.ui.mici.layouts.settings.network import WifiNetworkButton +from openpilot.selfdrive.ui.mici.layouts.settings.network.wifi_ui import WifiUIMici from openpilot.selfdrive.ui.mici.widgets.dialog import BigInputDialog from openpilot.selfdrive.ui.mici.widgets.button import BigButton From 0c452dbafe223cfe971803b80d3d2e20009536f5 Mon Sep 17 00:00:00 2001 From: royjr Date: Tue, 3 Mar 2026 23:12:53 -0500 Subject: [PATCH 016/141] cabana: fix right pane width limitation (#37527) Update chartswidget.cc --- tools/cabana/chart/chartswidget.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/cabana/chart/chartswidget.cc b/tools/cabana/chart/chartswidget.cc index acdb6d064d..44dca42152 100644 --- a/tools/cabana/chart/chartswidget.cc +++ b/tools/cabana/chart/chartswidget.cc @@ -427,7 +427,7 @@ void ChartsWidget::doAutoScroll() { } QSize ChartsWidget::minimumSizeHint() const { - return QSize(CHART_MIN_WIDTH * 1.5 * qApp->devicePixelRatio(), QWidget::minimumSizeHint().height()); + return QSize(CHART_MIN_WIDTH * 1.5, QWidget::minimumSizeHint().height()); } void ChartsWidget::newChart() { From 7e1a8d41a1b83e53ef483956513f7e2974ac042d Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 3 Mar 2026 21:45:49 -0800 Subject: [PATCH 017/141] steering arc: enable for angle cars (#37078) * enable for angle cars * use carparams * less roll at low speed, it's too pronounced * clean up --- selfdrive/ui/mici/onroad/hud_renderer.py | 3 +-- selfdrive/ui/mici/onroad/torque_bar.py | 20 +++++++++++++++----- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/selfdrive/ui/mici/onroad/hud_renderer.py b/selfdrive/ui/mici/onroad/hud_renderer.py index 3b056c3e0f..21e30e7426 100644 --- a/selfdrive/ui/mici/onroad/hud_renderer.py +++ b/selfdrive/ui/mici/onroad/hud_renderer.py @@ -172,8 +172,7 @@ class HudRenderer(Widget): def _render(self, rect: rl.Rectangle) -> None: """Render HUD elements to the screen.""" - if ui_state.sm['controlsState'].lateralControlState.which() != 'angleState': - self._torque_bar.render(rect) + self._torque_bar.render(rect) if self.is_cruise_set: self._draw_set_speed(rect) diff --git a/selfdrive/ui/mici/onroad/torque_bar.py b/selfdrive/ui/mici/onroad/torque_bar.py index d7c9f27a92..1338c8dfb3 100644 --- a/selfdrive/ui/mici/onroad/torque_bar.py +++ b/selfdrive/ui/mici/onroad/torque_bar.py @@ -145,6 +145,9 @@ def arc_bar_pts(cx: float, cy: float, return pts +DEFAULT_MAX_LAT_ACCEL = 3.0 # m/s^2 + + class TorqueBar(Widget): def __init__(self, demo: bool = False): super().__init__() @@ -165,16 +168,23 @@ class TorqueBar(Widget): controls_state = ui_state.sm['controlsState'] car_state = ui_state.sm['carState'] live_parameters = ui_state.sm['liveParameters'] - lateral_acceleration = controls_state.curvature * car_state.vEgo ** 2 - live_parameters.roll * ACCELERATION_DUE_TO_GRAVITY - # TODO: pull from carparams - max_lateral_acceleration = 3 + car_control = ui_state.sm['carControl'] - # from selfdrived + # Include lateral accel error in estimated torque utilization actual_lateral_accel = controls_state.curvature * car_state.vEgo ** 2 desired_lateral_accel = controls_state.desiredCurvature * car_state.vEgo ** 2 accel_diff = (desired_lateral_accel - actual_lateral_accel) - self._torque_filter.update(min(max(lateral_acceleration / max_lateral_acceleration + accel_diff, -1), 1)) + # Include road roll in estimated torque utilization + # Roll is less accurate near standstill, so reduce its effect at low speed + roll_compensation = live_parameters.roll * ACCELERATION_DUE_TO_GRAVITY * np.interp(car_state.vEgo, [5, 15], [0.0, 1.0]) + lateral_acceleration = actual_lateral_accel - roll_compensation + max_lateral_acceleration = ui_state.CP.maxLateralAccel if ui_state.CP else DEFAULT_MAX_LAT_ACCEL + + if not car_control.latActive: + self._torque_filter.update(0.0) + else: + self._torque_filter.update(np.clip((lateral_acceleration + accel_diff) / max_lateral_acceleration, -1, 1)) else: self._torque_filter.update(-ui_state.sm['carOutput'].actuatorsOutput.torque) From 20d484c7cb0791de581dc59955e92a166f996106 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 4 Mar 2026 01:23:56 -0800 Subject: [PATCH 018/141] reset: recover needs to reboot (#37546) fix not rebooting --- system/ui/mici_reset.py | 1 + 1 file changed, 1 insertion(+) diff --git a/system/ui/mici_reset.py b/system/ui/mici_reset.py index a459927eeb..5ad959badb 100755 --- a/system/ui/mici_reset.py +++ b/system/ui/mici_reset.py @@ -91,6 +91,7 @@ class Reset(Widget): if self._mode == ResetMode.RECOVER: self._cancel_button.set_text("reboot") + self._cancel_button.set_click_callback(self._do_reboot) self._cancel_button.render(rl.Rectangle( rect.x + 8, rect.y + rect.height - self._cancel_button.rect.height, From 6795b09d0a84f460d2ce673dff454c2c8c8bacd1 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 4 Mar 2026 03:16:29 -0800 Subject: [PATCH 019/141] file_downloader: stream downloads in a single HTTP request (#37549) The Python file downloader was making a separate HTTP Range request per 1MB chunk via URLFile.read(), causing massive latency overhead. Use a single streaming GET request instead, matching the old C++ behavior. Co-authored-by: Claude Opus 4.6 --- tools/lib/file_downloader.py | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/tools/lib/file_downloader.py b/tools/lib/file_downloader.py index c9c26bb307..5b31a5894c 100755 --- a/tools/lib/file_downloader.py +++ b/tools/lib/file_downloader.py @@ -60,8 +60,16 @@ def cmd_download(args): return try: - uf = URLFile(url, cache=False) - total = uf.get_length() + # Stream the file in a single HTTP request instead of making + # a separate Range request per chunk (which was very slow). + pool = URLFile.pool_manager() + r = pool.request("GET", url, preload_content=False) + if r.status not in (200, 206): + sys.stderr.write(f"ERROR:HTTP {r.status}\n") + sys.stderr.flush() + sys.exit(1) + + total = int(r.headers.get('content-length', 0)) if total <= 0: sys.stderr.write("ERROR:File not found or empty\n") sys.stderr.flush() @@ -73,8 +81,7 @@ def cmd_download(args): downloaded = 0 chunk_size = 1024 * 1024 with os.fdopen(tmp_fd, 'wb') as f: - while downloaded < total: - data = uf.read(min(chunk_size, total - downloaded)) + for data in r.stream(chunk_size): f.write(data) downloaded += len(data) sys.stderr.write(f"PROGRESS:{downloaded}:{total}\n") @@ -91,6 +98,8 @@ def cmd_download(args): except OSError: pass raise + finally: + r.release_conn() except Exception as e: sys.stderr.write(f"ERROR:{e}\n") From e97a1d1a44d2b7e1b56e2368524094f8c9108b12 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 4 Mar 2026 04:34:48 -0800 Subject: [PATCH 020/141] updater: zipapp and additional fixes (#37550) * new updater zipapp * fix deadlock from agnos.py throwing timeout errors, never hitting failed screen! + try catch the whole process for errors while starting process * add todo * set core affinity like setup in updater * fix import * rezip --- system/hardware/tici/updater_magic | 4 ++-- system/ui/mici_updater.py | 23 +++++++++++++++++++---- system/ui/tici_updater.py | 11 ++++++++--- 3 files changed, 29 insertions(+), 9 deletions(-) diff --git a/system/hardware/tici/updater_magic b/system/hardware/tici/updater_magic index ec586dbcb3..9674d85f00 100755 --- a/system/hardware/tici/updater_magic +++ b/system/hardware/tici/updater_magic @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c44fb88b3b1643b6b44ae8ac9880348bd0257ff90f4084cbe889de91d71653fe -size 25111329 +oid sha256:d815a9140e69242d85e57f74a14c6372809eebefe8958be9152efa7874928ccc +size 71215510 diff --git a/system/ui/mici_updater.py b/system/ui/mici_updater.py index 50ea412925..cd1f99062c 100755 --- a/system/ui/mici_updater.py +++ b/system/ui/mici_updater.py @@ -5,7 +5,9 @@ import threading import pyray as rl from enum import IntEnum -from openpilot.system.hardware import HARDWARE +from openpilot.common.realtime import config_realtime_process, set_core_affinity +from openpilot.system.hardware import HARDWARE, TICI +from openpilot.common.swaglog import cloudlog from openpilot.system.ui.lib.application import gui_app, FontWeight from openpilot.system.ui.widgets import Widget from openpilot.system.ui.widgets.label import UnifiedLabel @@ -34,6 +36,7 @@ class Updater(Widget): self._network_monitor = NetworkConnectivityMonitor() self._network_monitor.start() + # TODO: network page is rendered inline, not pushed on nav stack, so auto-dismiss on internet connect doesn't work self._network_setup_page = NetworkSetupPageBase(self._network_monitor, self._network_setup_continue_callback, disable_connect_hint=True) self._network_setup_page.set_is_updater() @@ -93,9 +96,13 @@ class Updater(Widget): def _run_update_process(self): # TODO: just import it and run in a thread without a subprocess - cmd = [self.updater, "--swap", self.manifest] - self.process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, - text=True, bufsize=1, universal_newlines=True) + try: + cmd = [self.updater, "--swap", self.manifest] + self.process = subprocess.Popen(cmd, stdout=subprocess.PIPE, + text=True, bufsize=1, universal_newlines=True) + except Exception: + self.set_current_screen(Screen.FAILED) + return if self.process.stdout is not None: for line in self.process.stdout: @@ -169,6 +176,14 @@ class Updater(Widget): def main(): + config_realtime_process(0, 51) + # attempt to affine. AGNOS will start setup with all cores, should only fail when manually launching with screen off + if TICI: + try: + set_core_affinity([5]) + except OSError: + cloudlog.exception("Failed to set core affinity for updater process") + if len(sys.argv) < 3: print("Usage: updater.py ") sys.exit(1) diff --git a/system/ui/tici_updater.py b/system/ui/tici_updater.py index 9824638cd0..3a3b0987d0 100755 --- a/system/ui/tici_updater.py +++ b/system/ui/tici_updater.py @@ -67,9 +67,14 @@ class Updater(Widget): def _run_update_process(self): # TODO: just import it and run in a thread without a subprocess - cmd = [self.updater, "--swap", self.manifest] - self.process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, - text=True, bufsize=1, universal_newlines=True) + try: + cmd = [self.updater, "--swap", self.manifest] + self.process = subprocess.Popen(cmd, stdout=subprocess.PIPE, + text=True, bufsize=1, universal_newlines=True) + except Exception: + self.progress_text = "Update failed" + self.show_reboot_button = True + return if self.process.stdout is not None: for line in self.process.stdout: From cd22ee3327fcb5c6b286db2c67fded7931bfc8f7 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Wed, 4 Mar 2026 09:50:23 -0800 Subject: [PATCH 021/141] rm openssl3 package (#37551) * rm openssl3 package * upgrade * lil more --- SConstruct | 3 +- pyproject.toml | 4 +- uv.lock | 129 +++++++++++++++++++++---------------------------- 3 files changed, 58 insertions(+), 78 deletions(-) diff --git a/SConstruct b/SConstruct index da70bc3924..18bf040cf0 100644 --- a/SConstruct +++ b/SConstruct @@ -46,11 +46,10 @@ if arch != "larch64": import libjpeg import libyuv import ncurses - import openssl3 import python3_dev import zeromq import zstd - pkgs = [bzip2, capnproto, eigen, ffmpeg_pkg, libjpeg, libyuv, ncurses, openssl3, zeromq, zstd] + pkgs = [bzip2, capnproto, eigen, ffmpeg_pkg, libjpeg, libyuv, ncurses, zeromq, zstd] py_include = python3_dev.INCLUDE_DIR else: # TODO: remove when AGNOS has our new vendor pkgs diff --git a/pyproject.toml b/pyproject.toml index 6516c8cd5b..c581dbfd8b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -32,12 +32,12 @@ dependencies = [ "ffmpeg @ git+https://github.com/commaai/dependencies.git@releases#subdirectory=ffmpeg", "libjpeg @ git+https://github.com/commaai/dependencies.git@releases#subdirectory=libjpeg", "libyuv @ git+https://github.com/commaai/dependencies.git@releases#subdirectory=libyuv", - "openssl3 @ git+https://github.com/commaai/dependencies.git@releases#subdirectory=openssl3", "python3-dev @ git+https://github.com/commaai/dependencies.git@releases#subdirectory=python3-dev", "zstd @ git+https://github.com/commaai/dependencies.git@releases#subdirectory=zstd", "ncurses @ git+https://github.com/commaai/dependencies.git@releases#subdirectory=ncurses", "zeromq @ git+https://github.com/commaai/dependencies.git@releases#subdirectory=zeromq", "git-lfs @ git+https://github.com/commaai/dependencies.git@releases#subdirectory=git-lfs", + "gcc-arm-none-eabi @ git+https://github.com/commaai/dependencies.git@releases#subdirectory=gcc-arm-none-eabi", # body / webrtcd "av", @@ -103,12 +103,10 @@ testing = [ dev = [ "matplotlib", "opencv-python-headless", - "gcc-arm-none-eabi @ git+https://github.com/commaai/dependencies.git@releases#subdirectory=gcc-arm-none-eabi", ] tools = [ "metadrive-simulator @ git+https://github.com/commaai/metadrive.git@minimal ; (platform_machine != 'aarch64')", - "dearpygui>=2.1.0; (sys_platform != 'linux' or platform_machine != 'aarch64')", # not vended for linux aarch64 ] [project.urls] diff --git a/uv.lock b/uv.lock index f5c128fcb7..4f04da00c6 100644 --- a/uv.lock +++ b/uv.lock @@ -116,12 +116,12 @@ wheels = [ [[package]] name = "bzip2" version = "1.0.8" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=bzip2&rev=releases#12581f30b45b570dd0bbc36055fe1532f5a8ef60" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=bzip2&rev=releases#9777ee38aa5ca9439843125392af38ed1262e500" } [[package]] name = "capnproto" version = "1.0.1" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=capnproto&rev=releases#12581f30b45b570dd0bbc36055fe1532f5a8ef60" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=capnproto&rev=releases#9777ee38aa5ca9439843125392af38ed1262e500" } [[package]] name = "casadi" @@ -359,16 +359,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ff/fa/d3c15189f7c52aaefbaea76fb012119b04b9013f4bf446cb4eb4c26c4e6b/cython-3.2.4-py3-none-any.whl", hash = "sha256:732fc93bc33ae4b14f6afaca663b916c2fdd5dcbfad7114e17fb2434eeaea45c", size = 1257078, upload-time = "2026-01-04T14:14:12.373Z" }, ] -[[package]] -name = "dearpygui" -version = "2.2" -source = { registry = "https://pypi.org/simple" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/17/c8/b4afdac89c7bf458513366af3143f7383d7b09721637989c95788d93e24c/dearpygui-2.2-cp312-cp312-macosx_13_0_arm64.whl", hash = "sha256:34ceae1ca1b65444e49012d6851312e44f08713da1b8cc0150cf41f1c207af9c", size = 1931443, upload-time = "2026-02-17T14:21:54.394Z" }, - { url = "https://files.pythonhosted.org/packages/43/93/a2d083b2e0edb095be815662cc41e40cf9ea7b65d6323e47bb30df7eb284/dearpygui-2.2-cp312-cp312-manylinux1_x86_64.whl", hash = "sha256:e1fae9ae59fec0e41773df64c80311a6ba67696219dde5506a2a4c013e8bcdfa", size = 2592645, upload-time = "2026-02-17T14:22:02.869Z" }, - { url = "https://files.pythonhosted.org/packages/80/ba/eae13acaad479f522db853e8b1ccd695a7bc8da2b9685c1d70a3b318df89/dearpygui-2.2-cp312-cp312-win_amd64.whl", hash = "sha256:7d399543b5a26ab6426ef3bbd776e55520b491b3e169647bde5e6b2de3701b35", size = 1830531, upload-time = "2026-02-17T14:21:43.386Z" }, -] - [[package]] name = "dnspython" version = "2.8.0" @@ -381,7 +371,7 @@ wheels = [ [[package]] name = "eigen" version = "3.4.0" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=eigen&rev=releases#12581f30b45b570dd0bbc36055fe1532f5a8ef60" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=eigen&rev=releases#9777ee38aa5ca9439843125392af38ed1262e500" } [[package]] name = "execnet" @@ -395,7 +385,7 @@ wheels = [ [[package]] name = "ffmpeg" version = "7.1.0" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=ffmpeg&rev=releases#12581f30b45b570dd0bbc36055fe1532f5a8ef60" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=ffmpeg&rev=releases#9777ee38aa5ca9439843125392af38ed1262e500" } [[package]] name = "fonttools" @@ -442,7 +432,7 @@ wheels = [ [[package]] name = "gcc-arm-none-eabi" version = "13.2.1" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=gcc-arm-none-eabi&rev=releases#12581f30b45b570dd0bbc36055fe1532f5a8ef60" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=gcc-arm-none-eabi&rev=releases#9777ee38aa5ca9439843125392af38ed1262e500" } [[package]] name = "ghp-import" @@ -459,7 +449,7 @@ wheels = [ [[package]] name = "git-lfs" version = "3.6.1" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=git-lfs&rev=releases#12581f30b45b570dd0bbc36055fe1532f5a8ef60" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=git-lfs&rev=releases#9777ee38aa5ca9439843125392af38ed1262e500" } [[package]] name = "google-crc32c" @@ -577,7 +567,7 @@ wheels = [ [[package]] name = "libjpeg" version = "3.1.0" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=libjpeg&rev=releases#12581f30b45b570dd0bbc36055fe1532f5a8ef60" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=libjpeg&rev=releases#9777ee38aa5ca9439843125392af38ed1262e500" } [[package]] name = "libusb1" @@ -593,7 +583,7 @@ wheels = [ [[package]] name = "libyuv" version = "1922.0" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=libyuv&rev=releases#12581f30b45b570dd0bbc36055fe1532f5a8ef60" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=libyuv&rev=releases#9777ee38aa5ca9439843125392af38ed1262e500" } [[package]] name = "markdown" @@ -745,7 +735,7 @@ wheels = [ [[package]] name = "ncurses" version = "6.5" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=ncurses&rev=releases#12581f30b45b570dd0bbc36055fe1532f5a8ef60" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=ncurses&rev=releases#9777ee38aa5ca9439843125392af38ed1262e500" } [[package]] name = "numpy" @@ -800,6 +790,7 @@ dependencies = [ { name = "cython" }, { name = "eigen" }, { name = "ffmpeg" }, + { name = "gcc-arm-none-eabi" }, { name = "git-lfs" }, { name = "inputs" }, { name = "jeepney" }, @@ -809,7 +800,6 @@ dependencies = [ { name = "libyuv" }, { name = "ncurses" }, { name = "numpy" }, - { name = "openssl3" }, { name = "psutil" }, { name = "pycapnp" }, { name = "pycryptodome" }, @@ -837,7 +827,6 @@ dependencies = [ [package.optional-dependencies] dev = [ - { name = "gcc-arm-none-eabi" }, { name = "matplotlib" }, { name = "opencv-python-headless" }, ] @@ -860,7 +849,6 @@ testing = [ { name = "ty" }, ] tools = [ - { name = "dearpygui", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, { name = "metadrive-simulator", marker = "platform_machine != 'aarch64'" }, ] @@ -877,10 +865,9 @@ requires-dist = [ { name = "coverage", marker = "extra == 'testing'" }, { name = "crcmod-plus" }, { name = "cython" }, - { name = "dearpygui", marker = "(platform_machine != 'aarch64' and extra == 'tools') or (sys_platform != 'linux' and extra == 'tools')", specifier = ">=2.1.0" }, { name = "eigen", git = "https://github.com/commaai/dependencies.git?subdirectory=eigen&rev=releases" }, { name = "ffmpeg", git = "https://github.com/commaai/dependencies.git?subdirectory=ffmpeg&rev=releases" }, - { name = "gcc-arm-none-eabi", marker = "extra == 'dev'", git = "https://github.com/commaai/dependencies.git?subdirectory=gcc-arm-none-eabi&rev=releases" }, + { name = "gcc-arm-none-eabi", git = "https://github.com/commaai/dependencies.git?subdirectory=gcc-arm-none-eabi&rev=releases" }, { name = "git-lfs", git = "https://github.com/commaai/dependencies.git?subdirectory=git-lfs&rev=releases" }, { name = "hypothesis", marker = "extra == 'testing'", specifier = "==6.47.*" }, { name = "inputs" }, @@ -896,7 +883,6 @@ requires-dist = [ { name = "ncurses", git = "https://github.com/commaai/dependencies.git?subdirectory=ncurses&rev=releases" }, { name = "numpy", specifier = ">=2.0" }, { name = "opencv-python-headless", marker = "extra == 'dev'" }, - { name = "openssl3", git = "https://github.com/commaai/dependencies.git?subdirectory=openssl3&rev=releases" }, { name = "pre-commit-hooks", marker = "extra == 'testing'" }, { name = "psutil" }, { name = "pycapnp" }, @@ -932,11 +918,6 @@ requires-dist = [ ] provides-extras = ["docs", "testing", "dev", "tools"] -[[package]] -name = "openssl3" -version = "3.4.1" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=openssl3&rev=releases#12581f30b45b570dd0bbc36055fe1532f5a8ef60" } - [[package]] name = "packaging" version = "26.0" @@ -1306,7 +1287,7 @@ wheels = [ [[package]] name = "python3-dev" version = "3.12.8" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=python3-dev&rev=releases#12581f30b45b570dd0bbc36055fe1532f5a8ef60" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=python3-dev&rev=releases#9777ee38aa5ca9439843125392af38ed1262e500" } [[package]] name = "pyyaml" @@ -1449,15 +1430,15 @@ wheels = [ [[package]] name = "sentry-sdk" -version = "2.53.0" +version = "2.54.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "certifi" }, { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d3/06/66c8b705179bc54087845f28fd1b72f83751b6e9a195628e2e9af9926505/sentry_sdk-2.53.0.tar.gz", hash = "sha256:6520ef2c4acd823f28efc55e43eb6ce2e6d9f954a95a3aa96b6fd14871e92b77", size = 412369, upload-time = "2026-02-16T11:11:14.743Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c8/e9/2e3a46c304e7fa21eaa70612f60354e32699c7102eb961f67448e222ad7c/sentry_sdk-2.54.0.tar.gz", hash = "sha256:2620c2575128d009b11b20f7feb81e4e4e8ae08ec1d36cbc845705060b45cc1b", size = 413813, upload-time = "2026-03-02T15:12:41.355Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/47/d4/2fdf854bc3b9c7f55219678f812600a20a138af2dd847d99004994eada8f/sentry_sdk-2.53.0-py2.py3-none-any.whl", hash = "sha256:46e1ed8d84355ae54406c924f6b290c3d61f4048625989a723fd622aab838899", size = 437908, upload-time = "2026-02-16T11:11:13.227Z" }, + { url = "https://files.pythonhosted.org/packages/53/39/be412cc86bc6247b8f69e9383d7950711bd86f8d0a4a4b0fe8fad685bc21/sentry_sdk-2.54.0-py2.py3-none-any.whl", hash = "sha256:fd74e0e281dcda63afff095d23ebcd6e97006102cdc8e78a29f19ecdf796a0de", size = 439198, upload-time = "2026-03-02T15:12:39.546Z" }, ] [[package]] @@ -1553,26 +1534,26 @@ wheels = [ [[package]] name = "ty" -version = "0.0.19" +version = "0.0.20" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/84/5e/da108b9eeb392e02ff0478a34e9651490b36af295881cb56575b83f0cc3a/ty-0.0.19.tar.gz", hash = "sha256:ee3d9ed4cb586e77f6efe3d0fe5a855673ca438a3d533a27598e1d3502a2948a", size = 5220026, upload-time = "2026-02-26T12:13:15.215Z" } +sdist = { url = "https://files.pythonhosted.org/packages/56/95/8de69bb98417227b01f1b1d743c819d6456c9fd140255b6124b05b17dfd6/ty-0.0.20.tar.gz", hash = "sha256:ebba6be7974c14efbb2a9adda6ac59848f880d7259f089dfa72a093039f1dcc6", size = 5262529, upload-time = "2026-03-02T15:51:36.587Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5a/31/fd8c6067abb275bea11523d21ecf64e1d870b1ce80cac529cf6636df1471/ty-0.0.19-py3-none-linux_armv6l.whl", hash = "sha256:29bed05d34c8a7597567b8e327c53c1aed4a07dcfbe6c81e6d60c7444936ad77", size = 10268470, upload-time = "2026-02-26T12:13:42.881Z" }, - { url = "https://files.pythonhosted.org/packages/15/de/16a11bbf7d98c75849fc41f5d008b89bb5d080a4b10dc8ea851ee2bd371b/ty-0.0.19-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:79140870c688c97ec68e723c28935ddef9d91a76d48c68e665fe7c851e628b8a", size = 10098562, upload-time = "2026-02-26T12:13:31.618Z" }, - { url = "https://files.pythonhosted.org/packages/e7/4f/086d6ff6686eadf903913c45b53ab96694b62bbfee1d8cf3e55a9b5aa4b2/ty-0.0.19-py3-none-macosx_11_0_arm64.whl", hash = "sha256:6e9c1f9cfa6a26f7881d14d75cf963af743f6c4189e6aa3e3b4056a65f22e730", size = 9604073, upload-time = "2026-02-26T12:13:24.645Z" }, - { url = "https://files.pythonhosted.org/packages/95/13/888a6b6c7ed4a880fee91bec997f775153ce86215ee4c56b868516314734/ty-0.0.19-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bbca43b050edf1db2e64ae7b79add233c2aea2855b8a876081bbd032edcd0610", size = 10106295, upload-time = "2026-02-26T12:13:40.584Z" }, - { url = "https://files.pythonhosted.org/packages/cb/e8/05a372cae8da482de73b8246fb43236bf11e24ac28c879804568108759db/ty-0.0.19-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8acaa88ab1955ca6b15a0ccc274011c4961377fe65c3948e5d2b212f2517b87c", size = 10098234, upload-time = "2026-02-26T12:13:33.725Z" }, - { url = "https://files.pythonhosted.org/packages/c5/f1/5b0958e9e9576e7662192fe689bbb3dc88e631a4e073db3047793a547d58/ty-0.0.19-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a901b6a6dd9d17d5b3b2e7bafc3057294e88da3f5de507347316687d7f191a1", size = 10607218, upload-time = "2026-02-26T12:13:17.576Z" }, - { url = "https://files.pythonhosted.org/packages/fb/ab/358c78b77844f58ff5aca368550ab16c719f1ab0ec892ceb1114d7500f4e/ty-0.0.19-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8deafdaaaee65fd121c66064da74a922d8501be4a2d50049c71eab521a23eff7", size = 11160593, upload-time = "2026-02-26T12:13:36.008Z" }, - { url = "https://files.pythonhosted.org/packages/95/59/827fc346d66a59fe48e9689a5ceb67dbbd5b4de2e8d4625371af39a2e8b7/ty-0.0.19-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:035e56071af280897441018f74f921b97d53aec0856f8af85f4f949df8eda07d", size = 10822392, upload-time = "2026-02-26T12:13:29.415Z" }, - { url = "https://files.pythonhosted.org/packages/81/f9/3bbfbbe35478de9bcd63848f4bc9bffda72278dd9732dbad3efc3978432e/ty-0.0.19-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:abdf5885130393ce74501dba792f48ce0a515756ec81c33a4b324bdf3509df6e", size = 10707139, upload-time = "2026-02-26T12:13:20.148Z" }, - { url = "https://files.pythonhosted.org/packages/12/9e/597023b183ec4ade83a36a0cea5c103f3bffa34f70813d46386c61447fb8/ty-0.0.19-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:877e89005c8f9d1dbff5ad14cbac9f35c528406fde38926f9b44f24830de8d6a", size = 10096933, upload-time = "2026-02-26T12:13:45.266Z" }, - { url = "https://files.pythonhosted.org/packages/1e/76/d0d2f6e674db2a17c8efa5e26682b9dfa8d34774705f35902a7b45ebd3bd/ty-0.0.19-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:39bd1da051c1e4d316efaf79dbed313255633f7c6ad6e24d29f4d9c6ffaf4de6", size = 10109547, upload-time = "2026-02-26T12:13:22.17Z" }, - { url = "https://files.pythonhosted.org/packages/a4/b0/76026c06b852a3aa4fdb5bd329fdc2175aaf3c64a3fafece9cc4df167cee/ty-0.0.19-py3-none-musllinux_1_2_i686.whl", hash = "sha256:87df8415a6c9cb27b8f1382fcdc6052e59f5b9f50f78bc14663197eb5c8d3699", size = 10289110, upload-time = "2026-02-26T12:13:38.29Z" }, - { url = "https://files.pythonhosted.org/packages/14/6c/f3b3a189816b4f079b20fe5d0d7ee38e38a472f53cc6770bb6571147e3de/ty-0.0.19-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:89b6bb23c332ed5c38dd859eb5793f887abcc936f681a40d4ea68e35eac1af33", size = 10796479, upload-time = "2026-02-26T12:13:10.992Z" }, - { url = "https://files.pythonhosted.org/packages/3d/18/caee33d1ce9dd50bd94c26cde7cda4f6971e22e474e7d72a5c86d745ad58/ty-0.0.19-py3-none-win32.whl", hash = "sha256:19b33df3aa7af7b1a9eaa4e1175c3b4dec0f5f2e140243e3492c8355c37418f3", size = 9677215, upload-time = "2026-02-26T12:13:08.519Z" }, - { url = "https://files.pythonhosted.org/packages/81/41/18fc0771d0b1da7d7cc2fc9af278d3122b754fe8b521a748734f4e16ecfd/ty-0.0.19-py3-none-win_amd64.whl", hash = "sha256:b9052c61464cdd76bc8e6796f2588c08700f25d0dcbc225bb165e390ea9d96a4", size = 10651252, upload-time = "2026-02-26T12:13:13.035Z" }, - { url = "https://files.pythonhosted.org/packages/8b/8c/26f7ce8863eb54510082747b3dfb1046ba24f16fc11de18c0e5feb36ff18/ty-0.0.19-py3-none-win_arm64.whl", hash = "sha256:9329804b66dcbae8e7af916ef4963221ed53b8ec7d09b0793591c5ae8a0f3270", size = 10093195, upload-time = "2026-02-26T12:13:26.816Z" }, + { url = "https://files.pythonhosted.org/packages/0b/2c/718abe48393e521bf852cd6b0f984766869b09c258d6e38a118768a91731/ty-0.0.20-py3-none-linux_armv6l.whl", hash = "sha256:7cc12769c169c9709a829c2248ee2826b7aae82e92caeac813d856f07c021eae", size = 10333656, upload-time = "2026-03-02T15:51:56.461Z" }, + { url = "https://files.pythonhosted.org/packages/41/0e/eb1c4cc4a12862e2327b72657bcebb10b7d9f17046f1bdcd6457a0211615/ty-0.0.20-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:3b777c1bf13bc0a95985ebb8a324b8668a4a9b2e514dde5ccf09e4d55d2ff232", size = 10168505, upload-time = "2026-03-02T15:51:51.895Z" }, + { url = "https://files.pythonhosted.org/packages/89/7f/10230798e673f0dd3094dfd16e43bfd90e9494e7af6e8e7db516fb431ddf/ty-0.0.20-py3-none-macosx_11_0_arm64.whl", hash = "sha256:b2a4a7db48bf8cba30365001bc2cad7fd13c1a5aacdd704cc4b7925de8ca5eb3", size = 9678510, upload-time = "2026-03-02T15:51:48.451Z" }, + { url = "https://files.pythonhosted.org/packages/7a/3d/59d9159577494edd1728f7db77b51bb07884bd21384f517963114e3ab5f6/ty-0.0.20-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6846427b8b353a43483e9c19936dc6a25612573b44c8f7d983dfa317e7f00d4c", size = 10162926, upload-time = "2026-03-02T15:51:40.558Z" }, + { url = "https://files.pythonhosted.org/packages/9c/a8/b7273eec3e802f78eb913fbe0ce0c16ef263723173e06a5776a8359b2c66/ty-0.0.20-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:245ceef5bd88df366869385cf96411cb14696334f8daa75597cf7e41c3012eb8", size = 10171702, upload-time = "2026-03-02T15:51:44.069Z" }, + { url = "https://files.pythonhosted.org/packages/9f/32/5f1144f2f04a275109db06e3498450c4721554215b80ae73652ef412eeab/ty-0.0.20-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c4d21d1cdf67a444d3c37583c17291ddba9382a9871021f3f5d5735e09e85efe", size = 10682552, upload-time = "2026-03-02T15:51:33.102Z" }, + { url = "https://files.pythonhosted.org/packages/6a/db/9f1f637310792f12bd6ed37d5fc8ab39ba1a9b0c6c55a33865e9f1cad840/ty-0.0.20-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bd4ffd907d1bd70e46af9e9a2f88622f215e1bf44658ea43b32c2c0b357299e4", size = 11242605, upload-time = "2026-03-02T15:51:34.895Z" }, + { url = "https://files.pythonhosted.org/packages/1a/68/cc9cae2e732fcfd20ccdffc508407905a023fc8493b8771c392d915528dc/ty-0.0.20-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b6594b58d8b0e9d16a22b3045fc1305db4b132c8d70c17784ab8c7a7cc986807", size = 10974655, upload-time = "2026-03-02T15:51:46.011Z" }, + { url = "https://files.pythonhosted.org/packages/1c/c1/b9e3e3f28fe63486331e653f6aeb4184af8b1fe80542fcf74d2dda40a93d/ty-0.0.20-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3662f890518ce6cf4d7568f57d03906912d2afbf948a01089a28e325b1ef198c", size = 10761325, upload-time = "2026-03-02T15:51:26.818Z" }, + { url = "https://files.pythonhosted.org/packages/39/9e/67db935bdedf219a00fb69ec5437ba24dab66e0f2e706dd54a4eca234b84/ty-0.0.20-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:0e3ffbae58f9f0d17cdc4ac6d175ceae560b7ed7d54f9ddfb1c9f31054bcdc2c", size = 10145793, upload-time = "2026-03-02T15:51:38.562Z" }, + { url = "https://files.pythonhosted.org/packages/c7/de/b0eb815d4dc5a819c7e4faddc2a79058611169f7eef07ccc006531ce228c/ty-0.0.20-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:176e52bc8bb00b0e84efd34583962878a447a3a0e34ecc45fd7097a37554261b", size = 10189640, upload-time = "2026-03-02T15:51:50.202Z" }, + { url = "https://files.pythonhosted.org/packages/b8/71/63734923965cbb70df1da3e93e4b8875434e326b89e9f850611122f279bf/ty-0.0.20-py3-none-musllinux_1_2_i686.whl", hash = "sha256:b2bc73025418e976ca4143dde71fb9025a90754a08ac03e6aa9b80d4bed1294b", size = 10370568, upload-time = "2026-03-02T15:51:42.295Z" }, + { url = "https://files.pythonhosted.org/packages/32/a0/a532c2048533347dff48e9ca98bd86d2c224356e101688a8edaf8d6973fb/ty-0.0.20-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:d52f7c9ec6e363e094b3c389c344d5a140401f14a77f0625e3f28c21918552f5", size = 10853999, upload-time = "2026-03-02T15:51:58.963Z" }, + { url = "https://files.pythonhosted.org/packages/48/88/36c652c658fe96658043e4abc8ea97801de6fb6e63ab50aaa82807bff1d8/ty-0.0.20-py3-none-win32.whl", hash = "sha256:c7d32bfe93f8fcaa52b6eef3f1b930fd7da410c2c94e96f7412c30cfbabf1d17", size = 9744206, upload-time = "2026-03-02T15:51:54.183Z" }, + { url = "https://files.pythonhosted.org/packages/ff/a7/a4a13bed1d7fd9d97aaa3c5bb5e6d3e9a689e6984806cbca2ab4c9233cac/ty-0.0.20-py3-none-win_amd64.whl", hash = "sha256:a5e10f40fc4a0a1cbcb740a4aad5c7ce35d79f030836ea3183b7a28f43170248", size = 10711999, upload-time = "2026-03-02T15:51:29.212Z" }, + { url = "https://files.pythonhosted.org/packages/8d/7e/6bfd748a9f4ff9267ed3329b86a0f02cdf6ab49f87bc36c8a164852f99fc/ty-0.0.20-py3-none-win_arm64.whl", hash = "sha256:53f7a5c12c960e71f160b734f328eff9a35d578af4b67a36b0bb5990ac5cdc27", size = 10150143, upload-time = "2026-03-02T15:51:31.283Z" }, ] [[package]] @@ -1643,38 +1624,40 @@ wheels = [ [[package]] name = "yarl" -version = "1.22.0" +version = "1.23.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "idna" }, { name = "multidict" }, { name = "propcache" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/57/63/0c6ebca57330cd313f6102b16dd57ffaf3ec4c83403dcb45dbd15c6f3ea1/yarl-1.22.0.tar.gz", hash = "sha256:bebf8557577d4401ba8bd9ff33906f1376c877aa78d1fe216ad01b4d6745af71", size = 187169, upload-time = "2025-10-06T14:12:55.963Z" } +sdist = { url = "https://files.pythonhosted.org/packages/23/6e/beb1beec874a72f23815c1434518bfc4ed2175065173fb138c3705f658d4/yarl-1.23.0.tar.gz", hash = "sha256:53b1ea6ca88ebd4420379c330aea57e258408dd0df9af0992e5de2078dc9f5d5", size = 194676, upload-time = "2026-03-01T22:07:53.373Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/75/ff/46736024fee3429b80a165a732e38e5d5a238721e634ab41b040d49f8738/yarl-1.22.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e340382d1afa5d32b892b3ff062436d592ec3d692aeea3bef3a5cfe11bbf8c6f", size = 142000, upload-time = "2025-10-06T14:09:44.631Z" }, - { url = "https://files.pythonhosted.org/packages/5a/9a/b312ed670df903145598914770eb12de1bac44599549b3360acc96878df8/yarl-1.22.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f1e09112a2c31ffe8d80be1b0988fa6a18c5d5cad92a9ffbb1c04c91bfe52ad2", size = 94338, upload-time = "2025-10-06T14:09:46.372Z" }, - { url = "https://files.pythonhosted.org/packages/ba/f5/0601483296f09c3c65e303d60c070a5c19fcdbc72daa061e96170785bc7d/yarl-1.22.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:939fe60db294c786f6b7c2d2e121576628468f65453d86b0fe36cb52f987bd74", size = 94909, upload-time = "2025-10-06T14:09:48.648Z" }, - { url = "https://files.pythonhosted.org/packages/60/41/9a1fe0b73dbcefce72e46cf149b0e0a67612d60bfc90fb59c2b2efdfbd86/yarl-1.22.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e1651bf8e0398574646744c1885a41198eba53dc8a9312b954073f845c90a8df", size = 372940, upload-time = "2025-10-06T14:09:50.089Z" }, - { url = "https://files.pythonhosted.org/packages/17/7a/795cb6dfee561961c30b800f0ed616b923a2ec6258b5def2a00bf8231334/yarl-1.22.0-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:b8a0588521a26bf92a57a1705b77b8b59044cdceccac7151bd8d229e66b8dedb", size = 345825, upload-time = "2025-10-06T14:09:52.142Z" }, - { url = "https://files.pythonhosted.org/packages/d7/93/a58f4d596d2be2ae7bab1a5846c4d270b894958845753b2c606d666744d3/yarl-1.22.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:42188e6a615c1a75bcaa6e150c3fe8f3e8680471a6b10150c5f7e83f47cc34d2", size = 386705, upload-time = "2025-10-06T14:09:54.128Z" }, - { url = "https://files.pythonhosted.org/packages/61/92/682279d0e099d0e14d7fd2e176bd04f48de1484f56546a3e1313cd6c8e7c/yarl-1.22.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f6d2cb59377d99718913ad9a151030d6f83ef420a2b8f521d94609ecc106ee82", size = 396518, upload-time = "2025-10-06T14:09:55.762Z" }, - { url = "https://files.pythonhosted.org/packages/db/0f/0d52c98b8a885aeda831224b78f3be7ec2e1aa4a62091f9f9188c3c65b56/yarl-1.22.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:50678a3b71c751d58d7908edc96d332af328839eea883bb554a43f539101277a", size = 377267, upload-time = "2025-10-06T14:09:57.958Z" }, - { url = "https://files.pythonhosted.org/packages/22/42/d2685e35908cbeaa6532c1fc73e89e7f2efb5d8a7df3959ea8e37177c5a3/yarl-1.22.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1e8fbaa7cec507aa24ea27a01456e8dd4b6fab829059b69844bd348f2d467124", size = 365797, upload-time = "2025-10-06T14:09:59.527Z" }, - { url = "https://files.pythonhosted.org/packages/a2/83/cf8c7bcc6355631762f7d8bdab920ad09b82efa6b722999dfb05afa6cfac/yarl-1.22.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:433885ab5431bc3d3d4f2f9bd15bfa1614c522b0f1405d62c4f926ccd69d04fa", size = 365535, upload-time = "2025-10-06T14:10:01.139Z" }, - { url = "https://files.pythonhosted.org/packages/25/e1/5302ff9b28f0c59cac913b91fe3f16c59a033887e57ce9ca5d41a3a94737/yarl-1.22.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:b790b39c7e9a4192dc2e201a282109ed2985a1ddbd5ac08dc56d0e121400a8f7", size = 382324, upload-time = "2025-10-06T14:10:02.756Z" }, - { url = "https://files.pythonhosted.org/packages/bf/cd/4617eb60f032f19ae3a688dc990d8f0d89ee0ea378b61cac81ede3e52fae/yarl-1.22.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:31f0b53913220599446872d757257be5898019c85e7971599065bc55065dc99d", size = 383803, upload-time = "2025-10-06T14:10:04.552Z" }, - { url = "https://files.pythonhosted.org/packages/59/65/afc6e62bb506a319ea67b694551dab4a7e6fb7bf604e9bd9f3e11d575fec/yarl-1.22.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a49370e8f711daec68d09b821a34e1167792ee2d24d405cbc2387be4f158b520", size = 374220, upload-time = "2025-10-06T14:10:06.489Z" }, - { url = "https://files.pythonhosted.org/packages/e7/3d/68bf18d50dc674b942daec86a9ba922d3113d8399b0e52b9897530442da2/yarl-1.22.0-cp312-cp312-win32.whl", hash = "sha256:70dfd4f241c04bd9239d53b17f11e6ab672b9f1420364af63e8531198e3f5fe8", size = 81589, upload-time = "2025-10-06T14:10:09.254Z" }, - { url = "https://files.pythonhosted.org/packages/c8/9a/6ad1a9b37c2f72874f93e691b2e7ecb6137fb2b899983125db4204e47575/yarl-1.22.0-cp312-cp312-win_amd64.whl", hash = "sha256:8884d8b332a5e9b88e23f60bb166890009429391864c685e17bd73a9eda9105c", size = 87213, upload-time = "2025-10-06T14:10:11.369Z" }, - { url = "https://files.pythonhosted.org/packages/44/c5/c21b562d1680a77634d748e30c653c3ca918beb35555cff24986fff54598/yarl-1.22.0-cp312-cp312-win_arm64.whl", hash = "sha256:ea70f61a47f3cc93bdf8b2f368ed359ef02a01ca6393916bc8ff877427181e74", size = 81330, upload-time = "2025-10-06T14:10:13.112Z" }, - { url = "https://files.pythonhosted.org/packages/73/ae/b48f95715333080afb75a4504487cbe142cae1268afc482d06692d605ae6/yarl-1.22.0-py3-none-any.whl", hash = "sha256:1380560bdba02b6b6c90de54133c81c9f2a453dee9912fe58c1dcced1edb7cff", size = 46814, upload-time = "2025-10-06T14:12:53.872Z" }, + { url = "https://files.pythonhosted.org/packages/88/8a/94615bc31022f711add374097ad4144d569e95ff3c38d39215d07ac153a0/yarl-1.23.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:1932b6b8bba8d0160a9d1078aae5838a66039e8832d41d2992daa9a3a08f7860", size = 124737, upload-time = "2026-03-01T22:05:12.897Z" }, + { url = "https://files.pythonhosted.org/packages/e3/6f/c6554045d59d64052698add01226bc867b52fe4a12373415d7991fdca95d/yarl-1.23.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:411225bae281f114067578891bc75534cfb3d92a3b4dfef7a6ca78ba354e6069", size = 87029, upload-time = "2026-03-01T22:05:14.376Z" }, + { url = "https://files.pythonhosted.org/packages/19/2a/725ecc166d53438bc88f76822ed4b1e3b10756e790bafd7b523fe97c322d/yarl-1.23.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:13a563739ae600a631c36ce096615fe307f131344588b0bc0daec108cdb47b25", size = 86310, upload-time = "2026-03-01T22:05:15.71Z" }, + { url = "https://files.pythonhosted.org/packages/99/30/58260ed98e6ff7f90ba84442c1ddd758c9170d70327394a6227b310cd60f/yarl-1.23.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9cbf44c5cb4a7633d078788e1b56387e3d3cf2b8139a3be38040b22d6c3221c8", size = 97587, upload-time = "2026-03-01T22:05:17.384Z" }, + { url = "https://files.pythonhosted.org/packages/76/0a/8b08aac08b50682e65759f7f8dde98ae8168f72487e7357a5d684c581ef9/yarl-1.23.0-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:53ad387048f6f09a8969631e4de3f1bf70c50e93545d64af4f751b2498755072", size = 92528, upload-time = "2026-03-01T22:05:18.804Z" }, + { url = "https://files.pythonhosted.org/packages/52/07/0b7179101fe5f8385ec6c6bb5d0cb9f76bd9fb4a769591ab6fb5cdbfc69a/yarl-1.23.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4a59ba56f340334766f3a4442e0efd0af895fae9e2b204741ef885c446b3a1a8", size = 105339, upload-time = "2026-03-01T22:05:20.235Z" }, + { url = "https://files.pythonhosted.org/packages/d3/8a/36d82869ab5ec829ca8574dfcb92b51286fcfb1e9c7a73659616362dc880/yarl-1.23.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:803a3c3ce4acc62eaf01eaca1208dcf0783025ef27572c3336502b9c232005e7", size = 105061, upload-time = "2026-03-01T22:05:22.268Z" }, + { url = "https://files.pythonhosted.org/packages/66/3e/868e5c3364b6cee19ff3e1a122194fa4ce51def02c61023970442162859e/yarl-1.23.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a3d2bff8f37f8d0f96c7ec554d16945050d54462d6e95414babaa18bfafc7f51", size = 100132, upload-time = "2026-03-01T22:05:23.638Z" }, + { url = "https://files.pythonhosted.org/packages/cf/26/9c89acf82f08a52cb52d6d39454f8d18af15f9d386a23795389d1d423823/yarl-1.23.0-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c75eb09e8d55bceb4367e83496ff8ef2bc7ea6960efb38e978e8073ea59ecb67", size = 99289, upload-time = "2026-03-01T22:05:25.749Z" }, + { url = "https://files.pythonhosted.org/packages/6f/54/5b0db00d2cb056922356104468019c0a132e89c8d3ab67d8ede9f4483d2a/yarl-1.23.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:877b0738624280e34c55680d6054a307aa94f7d52fa0e3034a9cc6e790871da7", size = 96950, upload-time = "2026-03-01T22:05:27.318Z" }, + { url = "https://files.pythonhosted.org/packages/f6/40/10fa93811fd439341fad7e0718a86aca0de9548023bbb403668d6555acab/yarl-1.23.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:b5405bb8f0e783a988172993cfc627e4d9d00432d6bbac65a923041edacf997d", size = 93960, upload-time = "2026-03-01T22:05:28.738Z" }, + { url = "https://files.pythonhosted.org/packages/bc/d2/8ae2e6cd77d0805f4526e30ec43b6f9a3dfc542d401ac4990d178e4bf0cf/yarl-1.23.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:1c3a3598a832590c5a3ce56ab5576361b5688c12cb1d39429cf5dba30b510760", size = 104703, upload-time = "2026-03-01T22:05:30.438Z" }, + { url = "https://files.pythonhosted.org/packages/2f/0c/b3ceacf82c3fe21183ce35fa2acf5320af003d52bc1fcf5915077681142e/yarl-1.23.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:8419ebd326430d1cbb7efb5292330a2cf39114e82df5cc3d83c9a0d5ebeaf2f2", size = 98325, upload-time = "2026-03-01T22:05:31.835Z" }, + { url = "https://files.pythonhosted.org/packages/9d/e0/12900edd28bdab91a69bd2554b85ad7b151f64e8b521fe16f9ad2f56477a/yarl-1.23.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:be61f6fff406ca40e3b1d84716fde398fc08bc63dd96d15f3a14230a0973ed86", size = 105067, upload-time = "2026-03-01T22:05:33.358Z" }, + { url = "https://files.pythonhosted.org/packages/15/61/74bb1182cf79c9bbe4eb6b1f14a57a22d7a0be5e9cedf8e2d5c2086474c3/yarl-1.23.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3ceb13c5c858d01321b5d9bb65e4cf37a92169ea470b70fec6f236b2c9dd7e34", size = 100285, upload-time = "2026-03-01T22:05:35.4Z" }, + { url = "https://files.pythonhosted.org/packages/69/7f/cd5ef733f2550de6241bd8bd8c3febc78158b9d75f197d9c7baa113436af/yarl-1.23.0-cp312-cp312-win32.whl", hash = "sha256:fffc45637bcd6538de8b85f51e3df3223e4ad89bccbfca0481c08c7fc8b7ed7d", size = 82359, upload-time = "2026-03-01T22:05:36.811Z" }, + { url = "https://files.pythonhosted.org/packages/f5/be/25216a49daeeb7af2bec0db22d5e7df08ed1d7c9f65d78b14f3b74fd72fc/yarl-1.23.0-cp312-cp312-win_amd64.whl", hash = "sha256:f69f57305656a4852f2a7203efc661d8c042e6cc67f7acd97d8667fb448a426e", size = 87674, upload-time = "2026-03-01T22:05:38.171Z" }, + { url = "https://files.pythonhosted.org/packages/d2/35/aeab955d6c425b227d5b7247eafb24f2653fedc32f95373a001af5dfeb9e/yarl-1.23.0-cp312-cp312-win_arm64.whl", hash = "sha256:6e87a6e8735b44816e7db0b2fbc9686932df473c826b0d9743148432e10bb9b9", size = 81879, upload-time = "2026-03-01T22:05:40.006Z" }, + { url = "https://files.pythonhosted.org/packages/69/68/c8739671f5699c7dc470580a4f821ef37c32c4cb0b047ce223a7f115757f/yarl-1.23.0-py3-none-any.whl", hash = "sha256:a2df6afe50dea8ae15fa34c9f824a3ee958d785fd5d089063d960bae1daa0a3f", size = 48288, upload-time = "2026-03-01T22:07:51.388Z" }, ] [[package]] name = "zeromq" version = "4.3.5" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=zeromq&rev=releases#12581f30b45b570dd0bbc36055fe1532f5a8ef60" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=zeromq&rev=releases#9777ee38aa5ca9439843125392af38ed1262e500" } [[package]] name = "zstandard" @@ -1704,4 +1687,4 @@ wheels = [ [[package]] name = "zstd" version = "1.5.6" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=zstd&rev=releases#12581f30b45b570dd0bbc36055fe1532f5a8ef60" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=zstd&rev=releases#9777ee38aa5ca9439843125392af38ed1262e500" } From fc372e2ae1741399cac662abbf4dc5f68eae5598 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Wed, 4 Mar 2026 12:36:40 -0800 Subject: [PATCH 022/141] ui needs pillow --- pyproject.toml | 1 + uv.lock | 2 ++ 2 files changed, 3 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index c581dbfd8b..5b77eb76d8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -76,6 +76,7 @@ dependencies = [ "raylib > 5.5.0.3", "qrcode", "jeepney", + "pillow", ] [project.optional-dependencies] diff --git a/uv.lock b/uv.lock index 4f04da00c6..743521fb2f 100644 --- a/uv.lock +++ b/uv.lock @@ -800,6 +800,7 @@ dependencies = [ { name = "libyuv" }, { name = "ncurses" }, { name = "numpy" }, + { name = "pillow" }, { name = "psutil" }, { name = "pycapnp" }, { name = "pycryptodome" }, @@ -883,6 +884,7 @@ requires-dist = [ { name = "ncurses", git = "https://github.com/commaai/dependencies.git?subdirectory=ncurses&rev=releases" }, { name = "numpy", specifier = ">=2.0" }, { name = "opencv-python-headless", marker = "extra == 'dev'" }, + { name = "pillow" }, { name = "pre-commit-hooks", marker = "extra == 'testing'" }, { name = "psutil" }, { name = "pycapnp" }, From fef89d1039bb1e9526d4880aa3520ad940c7678d Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Wed, 4 Mar 2026 14:18:35 -0800 Subject: [PATCH 023/141] op adb: find free port --- tools/scripts/adb_ssh.sh | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/tools/scripts/adb_ssh.sh b/tools/scripts/adb_ssh.sh index 4527a0296d..b9668e7e0b 100755 --- a/tools/scripts/adb_ssh.sh +++ b/tools/scripts/adb_ssh.sh @@ -31,8 +31,12 @@ for name, port in sorted(ports): PY ) -# Forward SSH port first for interactive shell access. -adb forward tcp:2222 tcp:22 +# Forward SSH port, finding a free local port if 2222 is taken. +SSH_PORT=2222 +while ss -tln | grep -q ":${SSH_PORT} "; do + SSH_PORT=$((SSH_PORT + 1)) +done +adb forward tcp:${SSH_PORT} tcp:22 # SSH! -ssh comma@localhost -p 2222 "$@" +ssh comma@localhost -p ${SSH_PORT} "$@" From e264b4269f7a556a4f588d25fd29bb7e1277f750 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Wed, 4 Mar 2026 14:39:11 -0800 Subject: [PATCH 024/141] reset: don't timeout if partition is corrupt --- system/ui/mici_reset.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system/ui/mici_reset.py b/system/ui/mici_reset.py index 5ad959badb..de22dea5d6 100755 --- a/system/ui/mici_reset.py +++ b/system/ui/mici_reset.py @@ -73,7 +73,7 @@ class Reset(Widget): if self._reset_state != self._previous_reset_state: self._previous_reset_state = self._reset_state self._timeout_st = time.monotonic() - elif self._reset_state != ResetState.RESETTING and (time.monotonic() - self._timeout_st) > TIMEOUT: + elif self._mode != ResetMode.RECOVER and self._reset_state != ResetState.RESETTING and (time.monotonic() - self._timeout_st) > TIMEOUT: exit(0) def _render(self, rect: rl.Rectangle): From 2c4e114b51ff1526c295bcb7ee49ecc52103191a Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 4 Mar 2026 17:35:24 -0800 Subject: [PATCH 025/141] updater: new scroller style (#37556) * good start * reset on push * clean up * why tf it remove comments * no more base unnav * repack --- system/hardware/tici/updater_magic | 4 +- system/ui/mici_setup.py | 14 +-- system/ui/mici_updater.py | 194 ++++++++++++----------------- 3 files changed, 87 insertions(+), 125 deletions(-) diff --git a/system/hardware/tici/updater_magic b/system/hardware/tici/updater_magic index 9674d85f00..b4f776565d 100755 --- a/system/hardware/tici/updater_magic +++ b/system/hardware/tici/updater_magic @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d815a9140e69242d85e57f74a14c6372809eebefe8958be9152efa7874928ccc -size 71215510 +oid sha256:d376e7aa4bb44bf78db9965ff55a5362ac823335b8d2a81dd3aebc5c8c2f239f +size 71214189 diff --git a/system/ui/mici_setup.py b/system/ui/mici_setup.py index 5dc9905ddf..8a72712527 100755 --- a/system/ui/mici_setup.py +++ b/system/ui/mici_setup.py @@ -216,9 +216,11 @@ class DownloadingPage(Widget): )) -class FailedPageBase(Widget): +class FailedPage(NavWidget): def __init__(self, reboot_callback: Callable, retry_callback: Callable, title: str = "download failed"): super().__init__() + self.set_back_callback(retry_callback) + self._title_label = UnifiedLabel(title, 64, text_color=rl.Color(255, 255, 255, int(255 * 0.9)), font_weight=FontWeight.DISPLAY) self._reason_label = UnifiedLabel("", 36, text_color=rl.Color(255, 255, 255, int(255 * 0.9 * 0.65)), @@ -269,12 +271,6 @@ class FailedPageBase(Widget): )) -class FailedPage(FailedPageBase, NavWidget): - def __init__(self, reboot_callback: Callable, retry_callback: Callable, title: str = "download failed"): - super().__init__(reboot_callback, retry_callback, title) - self.set_back_callback(retry_callback) - - class GreyBigButton(BigButton): """Users should manage newlines with this class themselves""" @@ -426,10 +422,6 @@ class NetworkSetupPageBase(Scroller): self._continue_button.set_text("install openpilot" if not custom_software else "choose software") self._continue_button.set_green(not custom_software) - def set_is_updater(self): - self._continue_button.set_text("download\n& install") - self._continue_button.set_green(False) - def _update_state(self): super()._update_state() diff --git a/system/ui/mici_updater.py b/system/ui/mici_updater.py index cd1f99062c..8e6f0a195c 100755 --- a/system/ui/mici_updater.py +++ b/system/ui/mici_updater.py @@ -3,57 +3,28 @@ import sys import subprocess import threading import pyray as rl -from enum import IntEnum from openpilot.common.realtime import config_realtime_process, set_core_affinity from openpilot.system.hardware import HARDWARE, TICI from openpilot.common.swaglog import cloudlog from openpilot.system.ui.lib.application import gui_app, FontWeight from openpilot.system.ui.widgets import Widget +from openpilot.system.ui.widgets.scroller import Scroller from openpilot.system.ui.widgets.label import UnifiedLabel -from openpilot.system.ui.widgets.button import FullRoundedButton -from openpilot.system.ui.mici_setup import NetworkSetupPageBase, FailedPageBase, NetworkConnectivityMonitor +from openpilot.system.ui.mici_setup import (NetworkSetupPage, FailedPage, NetworkConnectivityMonitor, + GreyBigButton, BigPillButton) -class Screen(IntEnum): - PROMPT = 0 - WIFI = 1 - PROGRESS = 2 - FAILED = 3 +class UpdaterNetworkSetupPage(NetworkSetupPage): + def __init__(self, network_monitor, continue_callback): + super().__init__(network_monitor, continue_callback, back_callback=None) + self._continue_button.set_text("download\n& install") + self._continue_button.set_green(False) -class Updater(Widget): - def __init__(self, updater_path, manifest_path): +class ProgressPage(Widget): + def __init__(self): super().__init__() - self.updater = updater_path - self.manifest = manifest_path - self.current_screen = Screen.PROMPT - - self.progress_value = 0 - self.progress_text = "loading" - self.process = None - self.update_thread = None - self._network_monitor = NetworkConnectivityMonitor() - self._network_monitor.start() - - # TODO: network page is rendered inline, not pushed on nav stack, so auto-dismiss on internet connect doesn't work - self._network_setup_page = NetworkSetupPageBase(self._network_monitor, self._network_setup_continue_callback, - disable_connect_hint=True) - self._network_setup_page.set_is_updater() - self._network_setup_page.set_enabled(lambda: self.enabled) # for nav stack - - # Buttons - self._continue_button = FullRoundedButton("continue") - self._continue_button.set_click_callback(lambda: self.set_current_screen(Screen.WIFI)) - - self._title_label = UnifiedLabel("update required", 48, text_color=rl.Color(255, 255, 255, int(255 * 0.9)), - font_weight=FontWeight.DISPLAY) - self._subtitle_label = UnifiedLabel("The download size is approximately 1GB.", 36, - text_color=rl.Color(255, 255, 255, int(255 * 0.9)), - font_weight=FontWeight.ROMAN) - - self._update_failed_page = FailedPageBase(HARDWARE.reboot, self._update_failed_retry_callback, - title="update failed") self._progress_title_label = UnifiedLabel("", 64, text_color=rl.Color(255, 255, 255, int(255 * 0.9)), font_weight=FontWeight.DISPLAY, line_height=0.8) @@ -61,37 +32,87 @@ class Updater(Widget): font_weight=FontWeight.ROMAN, alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_BOTTOM) + def set_progress(self, text: str, value: int): + self._progress_title_label.set_text(text.replace("_", "_\n") + "...") + self._progress_percent_label.set_text(f"{value}%") + + def show_event(self): + super().show_event() + self.set_progress("downloading", 0) + + def _render(self, rect: rl.Rectangle): + rl.draw_rectangle_rec(rect, rl.BLACK) + self._progress_title_label.render(rl.Rectangle( + rect.x + 12, + rect.y + 2, + rect.width, + self._progress_title_label.get_content_height(int(rect.width - 20)), + )) + + self._progress_percent_label.render(rl.Rectangle( + rect.x + 12, + rect.y + 18, + rect.width, + rect.height, + )) + + +class Updater(Scroller): + def __init__(self, updater_path, manifest_path): + super().__init__() + self.updater = updater_path + self.manifest = manifest_path + + self.progress_value = 0 + self.progress_text = "loading" + self.process = None + self.update_thread = None + self._update_failed = False + + self._network_monitor = NetworkConnectivityMonitor() + self._network_monitor.start() + + self._network_setup_page = UpdaterNetworkSetupPage(self._network_monitor, self._network_setup_continue_callback) + + self._progress_page = ProgressPage() + + self._failed_page = FailedPage(HARDWARE.reboot, self._retry, title="update failed") + + self._continue_button = BigPillButton("next") + self._continue_button.set_click_callback(lambda: gui_app.push_widget(self._network_setup_page)) + + self._scroller.add_widgets([ + GreyBigButton("update required", "The download size is\napproximately 1 GB", + gui_app.texture("icons_mici/offroad_alerts/green_wheel.png", 64, 64)), + self._continue_button, + ]) + + gui_app.add_nav_stack_tick(self._nav_stack_tick) + def _network_setup_continue_callback(self, _): self.install_update() - def _update_failed_retry_callback(self): - self.set_current_screen(Screen.PROMPT) + def _retry(self): + gui_app.pop_widgets_to(self) - def set_current_screen(self, screen: Screen): - if self.current_screen != screen: - if screen == Screen.PROGRESS: - if self._network_setup_page: - self._network_setup_page.hide_event() - elif screen == Screen.WIFI: - if self._network_setup_page: - self._network_setup_page.show_event() - elif screen == Screen.PROMPT: - if self._network_setup_page: - self._network_setup_page.hide_event() - elif screen == Screen.FAILED: - if self._network_setup_page: - self._network_setup_page.hide_event() + def _nav_stack_tick(self): + self._progress_page.set_progress(self.progress_text, self.progress_value) - self.current_screen = screen + if self._update_failed: + self._update_failed = False + self.show_event() + gui_app.pop_widgets_to(self, instant=True) + gui_app.push_widget(self._failed_page) def install_update(self): - self.set_current_screen(Screen.PROGRESS) self.progress_value = 0 self.progress_text = "downloading" + gui_app.pop_widgets_to(self, instant=True) + gui_app.push_widget(self._progress_page) + # Start the update process in a separate thread - self.update_thread = threading.Thread(target=self._run_update_process) - self.update_thread.daemon = True + self.update_thread = threading.Thread(target=self._run_update_process, daemon=True) self.update_thread.start() def _run_update_process(self): @@ -101,7 +122,7 @@ class Updater(Widget): self.process = subprocess.Popen(cmd, stdout=subprocess.PIPE, text=True, bufsize=1, universal_newlines=True) except Exception: - self.set_current_screen(Screen.FAILED) + self._update_failed = True return if self.process.stdout is not None: @@ -118,58 +139,7 @@ class Updater(Widget): if exit_code == 0: HARDWARE.reboot() else: - self.set_current_screen(Screen.FAILED) - - def render_prompt_screen(self, rect: rl.Rectangle): - self._title_label.render(rl.Rectangle( - rect.x + 8, - rect.y - 5, - rect.width, - 48, - )) - - subtitle_width = rect.width - 16 - subtitle_height = self._subtitle_label.get_content_height(int(subtitle_width)) - self._subtitle_label.render(rl.Rectangle( - rect.x + 8, - rect.y + 48, - subtitle_width, - subtitle_height, - )) - - self._continue_button.render(rl.Rectangle( - rect.x + 8, - rect.y + rect.height - self._continue_button.rect.height, - self._continue_button.rect.width, - self._continue_button.rect.height, - )) - - def render_progress_screen(self, rect: rl.Rectangle): - self._progress_title_label.set_text(self.progress_text.replace("_", "_\n") + "...") - self._progress_title_label.render(rl.Rectangle( - rect.x + 12, - rect.y + 2, - rect.width, - self._progress_title_label.get_content_height(int(rect.width - 20)), - )) - - self._progress_percent_label.set_text(f"{self.progress_value}%") - self._progress_percent_label.render(rl.Rectangle( - rect.x + 12, - rect.y + 18, - rect.width, - rect.height, - )) - - def _render(self, rect: rl.Rectangle): - if self.current_screen == Screen.PROMPT: - self.render_prompt_screen(rect) - elif self.current_screen == Screen.WIFI: - self._network_setup_page.render(rect) - elif self.current_screen == Screen.PROGRESS: - self.render_progress_screen(rect) - elif self.current_screen == Screen.FAILED: - self._update_failed_page.render(rect) + self._update_failed = True def close(self): self._network_monitor.stop() From 6330a9c53a317b2ad3ad70477c57bc234c17a955 Mon Sep 17 00:00:00 2001 From: Jacob Pfeifer Date: Wed, 4 Mar 2026 21:59:57 -0500 Subject: [PATCH 026/141] add explicit include for cstdint instead of relying on leaky include (#37559) --- third_party/json11/json11.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/third_party/json11/json11.cpp b/third_party/json11/json11.cpp index bc4045f07d..3bd4fde2f2 100644 --- a/third_party/json11/json11.cpp +++ b/third_party/json11/json11.cpp @@ -25,6 +25,7 @@ #include #include #include +#include namespace json11 { From 055b29b22625627a3f129ef52d550a654fa77901 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 4 Mar 2026 19:37:24 -0800 Subject: [PATCH 027/141] updater: better flow (#37560) * better update flow * clean up * clean up * cmt * clean up * todo --- common/filter_simple.py | 2 +- system/ui/mici_updater.py | 24 ++++++++++++++---------- system/ui/widgets/nav_widget.py | 9 +++++++++ 3 files changed, 24 insertions(+), 11 deletions(-) diff --git a/common/filter_simple.py b/common/filter_simple.py index 212e1a8f40..b28c3d68f5 100644 --- a/common/filter_simple.py +++ b/common/filter_simple.py @@ -28,7 +28,7 @@ class BounceFilter(FirstOrderFilter): scale = self.dt / (1.0 / 60.0) # tuned at 60 fps self.velocity.x += (x - self.x) * self.bounce * scale * self.dt self.velocity.update(0.0) - if abs(self.velocity.x) < 1e-5: + if abs(self.velocity.x) < 1e-3: self.velocity.x = 0.0 self.x += self.velocity.x return self.x diff --git a/system/ui/mici_updater.py b/system/ui/mici_updater.py index 8e6f0a195c..42aa1090bc 100755 --- a/system/ui/mici_updater.py +++ b/system/ui/mici_updater.py @@ -8,7 +8,7 @@ from openpilot.common.realtime import config_realtime_process, set_core_affinity from openpilot.system.hardware import HARDWARE, TICI from openpilot.common.swaglog import cloudlog from openpilot.system.ui.lib.application import gui_app, FontWeight -from openpilot.system.ui.widgets import Widget +from openpilot.system.ui.widgets.nav_widget import NavWidget from openpilot.system.ui.widgets.scroller import Scroller from openpilot.system.ui.widgets.label import UnifiedLabel from openpilot.system.ui.mici_setup import (NetworkSetupPage, FailedPage, NetworkConnectivityMonitor, @@ -22,7 +22,7 @@ class UpdaterNetworkSetupPage(NetworkSetupPage): self._continue_button.set_green(False) -class ProgressPage(Widget): +class ProgressPage(NavWidget): def __init__(self): super().__init__() @@ -32,12 +32,16 @@ class ProgressPage(Widget): font_weight=FontWeight.ROMAN, alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_BOTTOM) + def _back_enabled(self) -> bool: + return False + def set_progress(self, text: str, value: int): self._progress_title_label.set_text(text.replace("_", "_\n") + "...") self._progress_percent_label.set_text(f"{value}%") def show_event(self): super().show_event() + self._nav_bar._alpha = 0.0 # not dismissable self.set_progress("downloading", 0) def _render(self, rect: rl.Rectangle): @@ -82,7 +86,7 @@ class Updater(Scroller): self._continue_button.set_click_callback(lambda: gui_app.push_widget(self._network_setup_page)) self._scroller.add_widgets([ - GreyBigButton("update required", "The download size is\napproximately 1 GB", + GreyBigButton("update required", "the download size\nis approximately 1 GB", gui_app.texture("icons_mici/offroad_alerts/green_wheel.png", 64, 64)), self._continue_button, ]) @@ -101,19 +105,19 @@ class Updater(Scroller): if self._update_failed: self._update_failed = False self.show_event() - gui_app.pop_widgets_to(self, instant=True) - gui_app.push_widget(self._failed_page) + gui_app.pop_widgets_to(self, lambda: gui_app.push_widget(self._failed_page)) def install_update(self): self.progress_value = 0 self.progress_text = "downloading" - gui_app.pop_widgets_to(self, instant=True) - gui_app.push_widget(self._progress_page) + def start_update(): + self.update_thread = threading.Thread(target=self._run_update_process, daemon=True) + self.update_thread.start() - # Start the update process in a separate thread - self.update_thread = threading.Thread(target=self._run_update_process, daemon=True) - self.update_thread.start() + # Start the update process in a separate thread *after* show animation completes + self._progress_page.set_shown_callback(start_update) + gui_app.push_widget(self._progress_page) def _run_update_process(self): # TODO: just import it and run in a thread without a subprocess diff --git a/system/ui/widgets/nav_widget.py b/system/ui/widgets/nav_widget.py index 3292c53ce8..5cf8715c0c 100644 --- a/system/ui/widgets/nav_widget.py +++ b/system/ui/widgets/nav_widget.py @@ -65,6 +65,8 @@ class NavWidget(Widget, abc.ABC): self._back_callback: Callable[[], None] | None = None # persistent callback for user-initiated back navigation self._dismiss_callback: Callable[[], None] | None = None # transient callback for programmatic dismiss + # TODO: add this functionality to push_widget + self._shown_callback: Callable[[], None] | None = None # transient callback fired after show animation completes # TODO: move this state into NavBar self._nav_bar = NavBar() @@ -79,6 +81,9 @@ class NavWidget(Widget, abc.ABC): def set_back_callback(self, callback: Callable[[], None]) -> None: self._back_callback = callback + def set_shown_callback(self, callback: Callable[[], None] | None) -> None: + self._shown_callback = callback + def _handle_mouse_event(self, mouse_event: MouseEvent) -> None: super()._handle_mouse_event(mouse_event) @@ -147,6 +152,10 @@ class NavWidget(Widget, abc.ABC): if abs(new_y) < 1 and self._y_pos_filter.velocity.x == 0.0: new_y = self._y_pos_filter.x = 0.0 + if self._shown_callback is not None: + self._shown_callback() + self._shown_callback = None + if new_y > self._rect.height + DISMISS_PUSH_OFFSET - 10: gui_app.pop_widget() From 6bea70ac865b6814639332dd1f7dcd3cd2ee7f29 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Wed, 4 Mar 2026 23:15:10 -0500 Subject: [PATCH 028/141] pandad: gate unsupported pandas before flashing (#1754) --- selfdrive/pandad/pandad.py | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/selfdrive/pandad/pandad.py b/selfdrive/pandad/pandad.py index a0bca3f239..bda349aec2 100755 --- a/selfdrive/pandad/pandad.py +++ b/selfdrive/pandad/pandad.py @@ -32,8 +32,7 @@ def flash_panda(panda_serial: str) -> Panda: raise # skip flashing if the detected panda is not supported - supported_panda = check_panda_support(panda) - if not supported_panda: + if panda.get_type() not in Panda.SUPPORTED_DEVICES: cloudlog.warning(f"Panda {panda_serial} is not supported (hw_type: {panda.get_type()}), skipping flash...") return panda @@ -69,10 +68,19 @@ def flash_panda(panda_serial: str) -> Panda: return panda -def check_panda_support(panda) -> bool: - hw_type = panda.get_type() - if hw_type in Panda.SUPPORTED_DEVICES: - return True +def check_panda_support(panda_serials: list[str]) -> bool: + unsupported = [] + for serial in panda_serials: + panda = Panda(serial) + hw_type = panda.get_type() + panda.close() + if hw_type in Panda.SUPPORTED_DEVICES: + return True + + unsupported.append((serial, hw_type)) + + for serial, hw_type in unsupported: + cloudlog.warning(f"Panda {serial} is not supported (hw_type: {hw_type}), skipping...") return False @@ -126,6 +134,10 @@ def main() -> None: cloudlog.info(f"{len(panda_serials)} panda(s) found, connecting - {panda_serials}") + # skip flashing and health check if no supported panda is detected + if not check_panda_support(panda_serials): + continue + # Flash the first panda panda_serial = panda_serials[0] panda = flash_panda(panda_serial) @@ -143,12 +155,6 @@ def main() -> None: # log panda fw version params.put("PandaSignatures", panda.get_signature()) - # skip health check if the detected panda is not supported - supported_panda = check_panda_support(panda) - if not supported_panda: - cloudlog.warning(f"Panda {panda.get_usb_serial()} is not supported (hw_type: {panda.get_type()}), skipping health check...") - continue - # check health for lost heartbeat health = panda.health() if health["heartbeat_lost"]: From 0274b737607571048008a044991d132390d7634d Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Wed, 4 Mar 2026 20:20:07 -0800 Subject: [PATCH 029/141] jenkins: always run pandad tests --- Jenkinsfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index c5ebf6162b..5c785f1d98 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -218,14 +218,14 @@ node { 'camerad OX03C10': { deviceStage("OX03C10", "tizi-ox03c10", ["UNSAFE=1"], [ step("build", "cd system/manager && ./build.py"), - step("test pandad", "pytest selfdrive/pandad/tests/test_pandad.py", [diffPaths: ["panda", "selfdrive/pandad/"]]), + step("test pandad", "pytest selfdrive/pandad/tests/test_pandad.py"), step("test camerad", "pytest system/camerad/test/test_camerad.py", [timeout: 90]), ]) }, 'camerad OS04C10': { deviceStage("OS04C10", "tici-os04c10", ["UNSAFE=1"], [ step("build", "cd system/manager && ./build.py"), - step("test pandad", "pytest selfdrive/pandad/tests/test_pandad.py", [diffPaths: ["panda", "selfdrive/pandad/"]]), + step("test pandad", "pytest selfdrive/pandad/tests/test_pandad.py"), step("test camerad", "pytest system/camerad/test/test_camerad.py", [timeout: 90]), ]) }, From 5beae930e458521b08df20a2d665ff3929fa5be5 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 4 Mar 2026 20:44:29 -0800 Subject: [PATCH 030/141] setup: new scroller failed screen (#37561) * better update flow * clean up * clean up * cmt * clean up * todo * failed scroller * fix for setup * show wrong url * setup failed is red not orange * clean up and fix all flashing in setup --- system/ui/mici_setup.py | 108 ++++++++++++++++---------------------- system/ui/mici_updater.py | 2 +- 2 files changed, 47 insertions(+), 63 deletions(-) diff --git a/system/ui/mici_setup.py b/system/ui/mici_setup.py index 8a72712527..3265a325e2 100755 --- a/system/ui/mici_setup.py +++ b/system/ui/mici_setup.py @@ -21,14 +21,13 @@ from openpilot.system.ui.lib.application import gui_app, FontWeight from openpilot.system.ui.lib.wifi_manager import WifiManager from openpilot.system.ui.widgets import Widget from openpilot.system.ui.widgets.nav_widget import NavWidget -from openpilot.system.ui.widgets.button import SmallButton from openpilot.system.ui.widgets.label import UnifiedLabel from openpilot.system.ui.widgets.scroller import Scroller, NavScroller, ITEM_SPACING -from openpilot.system.ui.widgets.slider import LargerSlider, SmallSlider +from openpilot.system.ui.widgets.slider import LargerSlider from openpilot.selfdrive.ui.mici.layouts.settings.network import WifiNetworkButton from openpilot.selfdrive.ui.mici.layouts.settings.network.wifi_ui import WifiUIMici -from openpilot.selfdrive.ui.mici.widgets.dialog import BigInputDialog -from openpilot.selfdrive.ui.mici.widgets.button import BigButton +from openpilot.selfdrive.ui.mici.widgets.dialog import BigInputDialog, BigConfirmationDialogV2 +from openpilot.selfdrive.ui.mici.widgets.button import BigCircleButton, BigButton NetworkType = log.DeviceState.NetworkType @@ -181,7 +180,8 @@ class CustomSoftwareWarningPage(NavScroller): ]) -class DownloadingPage(Widget): +# TODO: unifi with updater's progress page +class DownloadingPage(NavWidget): def __init__(self): super().__init__() @@ -191,8 +191,12 @@ class DownloadingPage(Widget): font_weight=FontWeight.ROMAN, alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_BOTTOM) self._progress = 0 + def _back_enabled(self) -> bool: + return False + def show_event(self): super().show_event() + self._nav_bar._alpha = 0.0 # not dismissable self.set_progress(0) def set_progress(self, progress: int): @@ -216,59 +220,37 @@ class DownloadingPage(Widget): )) -class FailedPage(NavWidget): - def __init__(self, reboot_callback: Callable, retry_callback: Callable, title: str = "download failed"): +class FailedPage(NavScroller): + def __init__(self, retry_callback: Callable, title: str = "download failed", red_icon: bool = False): super().__init__() self.set_back_callback(retry_callback) - self._title_label = UnifiedLabel(title, 64, text_color=rl.Color(255, 255, 255, int(255 * 0.9)), - font_weight=FontWeight.DISPLAY) - self._reason_label = UnifiedLabel("", 36, text_color=rl.Color(255, 255, 255, int(255 * 0.9 * 0.65)), - font_weight=FontWeight.ROMAN) + def show_reboot_dialog(): + dialog = BigConfirmationDialogV2("slide to reboot", "icons_mici/settings/device/reboot.png", + exit_on_confirm=False, confirm_callback=HARDWARE.reboot) + gui_app.push_widget(dialog) - self._reboot_slider = SmallSlider("reboot", reboot_callback) - self._reboot_slider.set_enabled(lambda: self.enabled) # for nav stack + reboot_button = BigCircleButton("icons_mici/settings/device/reboot.png", red=False, icon_size=(64, 70)) + reboot_button.set_click_callback(show_reboot_dialog) - self._retry_button = SmallButton("retry") - self._retry_button.set_click_callback(retry_callback) - self._retry_button.set_enabled(lambda: self.enabled) # for nav stack + self._reason_card = GreyBigButton("", "") + self._reason_card.set_visible(False) + + warning_icon = "icons_mici/setup/red_warning.png" if red_icon else "icons_mici/setup/warning.png" + + self._scroller.add_widgets([ + GreyBigButton(title, "swipe down to go\nback and try again", + gui_app.texture(warning_icon, 64, 58)), + self._reason_card, + reboot_button, + ]) def set_reason(self, reason: str): - self._reason_label.set_text(reason) - - def show_event(self): - super().show_event() - self._reboot_slider.reset() - - def _render(self, rect: rl.Rectangle): - self._title_label.render(rl.Rectangle( - rect.x + 8, - rect.y + 10, - rect.width, - 64, - )) - - self._reason_label.render(rl.Rectangle( - rect.x + 8, - rect.y + 10 + 64, - rect.width, - 36, - )) - - self._retry_button.set_opacity(1 - self._reboot_slider.slider_percentage) - self._retry_button.render(rl.Rectangle( - self._rect.x + 8, - self._rect.y + self._rect.height - self._retry_button.rect.height, - self._retry_button.rect.width, - self._retry_button.rect.height, - )) - - self._reboot_slider.render(rl.Rectangle( - self._rect.x + self._rect.width - self._reboot_slider.rect.width, - self._rect.y + self._rect.height - self._reboot_slider.rect.height, - self._reboot_slider.rect.width, - self._reboot_slider.rect.height, - )) + if reason: + self._reason_card.set_value(reason) + self._reason_card.set_visible(True) + else: + self._reason_card.set_visible(False) class GreyBigButton(BigButton): @@ -300,6 +282,9 @@ class GreyBigButton(BigButton): def _width_hint(self) -> int: return int(self._rect.width - self.LABEL_HORIZONTAL_PADDING * 2) + def _get_label_font_size(self): + return 36 + def _render(self, _): rl.draw_rectangle_rounded(self._rect, 0.4, 10, rl.Color(255, 255, 255, int(255 * 0.15))) self._draw_content(self._rect.y) @@ -474,7 +459,7 @@ class Setup(Widget): self._software_selection_page = SoftwareSelectionPage(self._use_openpilot, lambda: gui_app.push_widget(self._custom_software_warning_page)) - self._download_failed_page = FailedPage(HARDWARE.reboot, self._pop_to_software_selection) + self._download_failed_page = FailedPage(self._pop_to_software_selection, red_icon=True) self._custom_software_warning_page = CustomSoftwareWarningPage(lambda: self._push_network_setup(True), self._pop_to_software_selection) @@ -489,8 +474,7 @@ class Setup(Widget): reason = self._download_failed_reason self._download_failed_reason = None self._download_failed_page.set_reason(reason) - gui_app.pop_widgets_to(self._software_selection_page, instant=True) # don't reset sliders - gui_app.push_widget(self._download_failed_page) + gui_app.pop_widgets_to(self._software_selection_page, lambda: gui_app.push_widget(self._download_failed_page)) def _render(self, rect: rl.Rectangle): self._start_page.render(rect) @@ -524,13 +508,11 @@ class Setup(Widget): def _network_setup_continue_callback(self, custom_software: bool): if not custom_software: - gui_app.pop_widgets_to(self._software_selection_page, instant=True) # don't reset sliders self._download(OPENPILOT_URL) else: def handle_keyboard_result(text): url = text.strip() if url: - gui_app.pop_widgets_to(self._software_selection_page, instant=True) # don't reset sliders self._download(url) keyboard = BigInputDialog("custom software URL...", confirm_callback=handle_keyboard_result, auto_return_to_letters="./") @@ -545,10 +527,12 @@ class Setup(Widget): self.download_url = (urlparse(f"https://{url}") if not parsed.netloc else parsed).geturl() self.download_progress = 0 - gui_app.push_widget(self._downloading_page) + def start_download(): + self.download_thread = threading.Thread(target=self._download_thread, daemon=True) + self.download_thread.start() - self.download_thread = threading.Thread(target=self._download_thread, daemon=True) - self.download_thread.start() + self._downloading_page.set_shown_callback(start_download) + gui_app.push_widget(self._downloading_page) def _download_thread(self): try: @@ -583,7 +567,7 @@ class Setup(Widget): is_elf = header == b'\x7fELF' if not is_elf: - self._download_failed_reason = "No custom software found at this URL." + self._download_failed_reason = "No custom software found at this URL: " + self.download_url.replace("https://", "", 1) return # AGNOS might try to execute the installer before this process exits. @@ -600,9 +584,9 @@ class Setup(Widget): except urllib.error.HTTPError as e: if e.code == 409: - self._download_failed_reason = "Incompatible openpilot version" + self._download_failed_reason = "Incompatible openpilot version." except Exception: - self._download_failed_reason = "Invalid URL" + self._download_failed_reason = "Invalid URL: " + self.download_url.replace("https://", "", 1) def main(): diff --git a/system/ui/mici_updater.py b/system/ui/mici_updater.py index 42aa1090bc..8437e6fa60 100755 --- a/system/ui/mici_updater.py +++ b/system/ui/mici_updater.py @@ -80,7 +80,7 @@ class Updater(Scroller): self._progress_page = ProgressPage() - self._failed_page = FailedPage(HARDWARE.reboot, self._retry, title="update failed") + self._failed_page = FailedPage(self._retry, title="update failed") self._continue_button = BigPillButton("next") self._continue_button.set_click_callback(lambda: gui_app.push_widget(self._network_setup_page)) From e59f675715cff7df8406cff741fbe95921e393bb Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 4 Mar 2026 22:36:25 -0800 Subject: [PATCH 031/141] new reset (#37563) * start new reset w navwidgets * full port * clean up * clean up * clean up * fixes * rm --- .../assets/icons_mici/setup/factory_reset.png | 3 + .../assets/icons_mici/setup/reset_failed.png | 3 + system/ui/mici_reset.py | 176 +++++++++--------- system/ui/mici_setup.py | 11 +- 4 files changed, 103 insertions(+), 90 deletions(-) create mode 100644 selfdrive/assets/icons_mici/setup/factory_reset.png create mode 100644 selfdrive/assets/icons_mici/setup/reset_failed.png diff --git a/selfdrive/assets/icons_mici/setup/factory_reset.png b/selfdrive/assets/icons_mici/setup/factory_reset.png new file mode 100644 index 0000000000..bcb3ea92cb --- /dev/null +++ b/selfdrive/assets/icons_mici/setup/factory_reset.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:122a614d1aa26187507951f932160eebfddfebcb4293e78f8d23e350fc97bc0f +size 11489 diff --git a/selfdrive/assets/icons_mici/setup/reset_failed.png b/selfdrive/assets/icons_mici/setup/reset_failed.png new file mode 100644 index 0000000000..680df97cbc --- /dev/null +++ b/selfdrive/assets/icons_mici/setup/reset_failed.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8d5b8f76e5f47e77e5af3016ebdbe548ad3bc9af83a1111b3214bf4017c95a28 +size 11792 diff --git a/system/ui/mici_reset.py b/system/ui/mici_reset.py index de22dea5d6..5cbba5e8ef 100755 --- a/system/ui/mici_reset.py +++ b/system/ui/mici_reset.py @@ -1,18 +1,18 @@ #!/usr/bin/env python3 import os import sys -import threading import time from enum import IntEnum import pyray as rl -from openpilot.system.hardware import PC -from openpilot.system.ui.lib.application import gui_app, FontWeight -from openpilot.system.ui.widgets import Widget -from openpilot.system.ui.widgets.slider import SmallSlider -from openpilot.system.ui.widgets.button import SmallButton, FullRoundedButton -from openpilot.system.ui.widgets.label import gui_label, gui_text_box +from openpilot.system.hardware import HARDWARE, PC +from openpilot.system.ui.lib.application import gui_app +from openpilot.system.ui.widgets.scroller import Scroller +from openpilot.system.ui.widgets.nav_widget import NavWidget +from openpilot.system.ui.mici_setup import GreyBigButton, FailedPage +from openpilot.selfdrive.ui.mici.widgets.dialog import BigConfirmationDialogV2 +from openpilot.selfdrive.ui.mici.widgets.button import BigCircleButton USERDATA = "/dev/disk/by-partlabel/userdata" TIMEOUT = 3*60 @@ -24,32 +24,87 @@ class ResetMode(IntEnum): FORMAT = 2 # finish up a factory reset from a tool that doesn't flash an empty partition to userdata -class ResetState(IntEnum): - NONE = 0 - RESETTING = 1 - FAILED = 2 +class ResetFailedPage(FailedPage): + def __init__(self): + super().__init__(None, "reset failed", "reboot to try again", icon="icons_mici/setup/reset_failed.png") + + def show_event(self): + super().show_event() + self._nav_bar._alpha = 0.0 # not dismissable + + def _back_enabled(self) -> bool: + return False -class Reset(Widget): +class ResettingPage(NavWidget): + def __init__(self): + super().__init__() + + self._resetting_card = GreyBigButton("resetting device", "this may take up to\na minute...", + gui_app.texture("icons_mici/setup/factory_reset.png", 64, 64)) + + def show_event(self): + super().show_event() + self._nav_bar._alpha = 0.0 # not dismissable + + def _back_enabled(self) -> bool: + return False + + def _render(self, _): + self._resetting_card.render(rl.Rectangle( + self._rect.x + self._rect.width / 2 - self._resetting_card.rect.width / 2, + self._rect.y + self._rect.height / 2 - self._resetting_card.rect.height / 2, + self._resetting_card.rect.width, + self._resetting_card.rect.height, + )) + + +class Reset(Scroller): def __init__(self, mode): super().__init__() self._mode = mode - self._previous_reset_state = None - self._reset_state = ResetState.NONE + self._previous_active_widget = None + self._reset_failed = False + self._timeout_st = time.monotonic() - self._cancel_button = SmallButton("cancel") - self._cancel_button.set_click_callback(gui_app.request_close) + self._resetting_page = ResettingPage() + self._reset_failed_page = ResetFailedPage() - self._reboot_button = FullRoundedButton("reboot") - self._reboot_button.set_click_callback(self._do_reboot) + def show_confirm_dialog(): + dialog = BigConfirmationDialogV2("erase\ndevice", "icons_mici/settings/device/uninstall.png", red=True, + confirm_callback=self.start_reset) + gui_app.push_widget(dialog) - self._confirm_slider = SmallSlider("reset", self._confirm) + def show_cancel_dialog(): + dialog = BigConfirmationDialogV2("normal\nstartup", "icons_mici/settings/device/reboot.png", + exit_on_confirm=False, confirm_callback=gui_app.request_close) + gui_app.push_widget(dialog) - def _do_reboot(self): - if PC: - return + def show_reboot_dialog(): + dialog = BigConfirmationDialogV2("reboot\ndevice", "icons_mici/settings/device/reboot.png", + exit_on_confirm=False, confirm_callback=HARDWARE.reboot) + gui_app.push_widget(dialog) - os.system("sudo reboot") + self._reset_button = BigCircleButton("icons_mici/settings/device/uninstall.png", red=True) + self._reset_button.set_click_callback(show_confirm_dialog) + + self._cancel_button = BigCircleButton("icons_mici/settings/device/reboot.png") + self._cancel_button.set_click_callback(show_cancel_dialog) + + main_card = GreyBigButton("factory reset", "all content and\nsettings will be erased", + gui_app.texture("icons_mici/setup/factory_reset.png", 64, 64)) + + # cancel button becomes reboot button + if mode == ResetMode.RECOVER: + main_card.set_text("unable to mount\ndata partition") + main_card.set_value("it may be corrupted") + self._cancel_button.set_click_callback(show_reboot_dialog) + + self._scroller.add_widgets([ + main_card, + self._reset_button, + self._cancel_button, + ]) def _do_erase(self): if PC: @@ -63,72 +118,26 @@ class Reset(Widget): if rm == 0 or fmt == 0: os.system("sudo reboot") else: - self._reset_state = ResetState.FAILED + self._reset_failed = True def start_reset(self): - self._reset_state = ResetState.RESETTING - threading.Timer(0.1, self._do_erase).start() + self._resetting_page.set_shown_callback(self._do_erase) + gui_app.push_widget(self._resetting_page) def _update_state(self): - if self._reset_state != self._previous_reset_state: - self._previous_reset_state = self._reset_state + super()._update_state() + + if self._reset_failed: + self._reset_failed = False + gui_app.pop_widgets_to(self, lambda: gui_app.push_widget(self._reset_failed_page)) + + active_widget = gui_app.get_active_widget() + if active_widget != self._previous_active_widget: + self._previous_active_widget = active_widget self._timeout_st = time.monotonic() - elif self._mode != ResetMode.RECOVER and self._reset_state != ResetState.RESETTING and (time.monotonic() - self._timeout_st) > TIMEOUT: + elif self._mode != ResetMode.RECOVER and active_widget != self._resetting_page and (time.monotonic() - self._timeout_st) > TIMEOUT: exit(0) - def _render(self, rect: rl.Rectangle): - label_rect = rl.Rectangle(rect.x + 8, rect.y + 8, rect.width, 50) - gui_label(label_rect, "factory reset", 48, font_weight=FontWeight.BOLD, - color=rl.Color(255, 255, 255, int(255 * 0.9))) - - text_rect = rl.Rectangle(rect.x + 8, rect.y + 56, rect.width - 8 * 2, rect.height - 80) - gui_text_box(text_rect, self._get_body_text(), 36, font_weight=FontWeight.ROMAN, line_scale=0.9) - - if self._reset_state != ResetState.RESETTING: - # fade out cancel button as slider is moved, set visible to prevent pressing invisible cancel - self._cancel_button.set_opacity(1.0 - self._confirm_slider.slider_percentage) - self._cancel_button.set_visible(self._confirm_slider.slider_percentage < 0.8) - - if self._mode == ResetMode.RECOVER: - self._cancel_button.set_text("reboot") - self._cancel_button.set_click_callback(self._do_reboot) - self._cancel_button.render(rl.Rectangle( - rect.x + 8, - rect.y + rect.height - self._cancel_button.rect.height, - self._cancel_button.rect.width, - self._cancel_button.rect.height)) - elif self._mode == ResetMode.USER_RESET and self._reset_state != ResetState.FAILED: - self._cancel_button.render(rl.Rectangle( - rect.x + 8, - rect.y + rect.height - self._cancel_button.rect.height, - self._cancel_button.rect.width, - self._cancel_button.rect.height)) - - if self._reset_state != ResetState.FAILED: - self._confirm_slider.render(rl.Rectangle( - rect.x + rect.width - self._confirm_slider.rect.width, - rect.y + rect.height - self._confirm_slider.rect.height, - self._confirm_slider.rect.width, - self._confirm_slider.rect.height)) - else: - self._reboot_button.render(rl.Rectangle( - rect.x + 8, - rect.y + rect.height - self._reboot_button.rect.height, - self._reboot_button.rect.width, - self._reboot_button.rect.height)) - - def _confirm(self): - self.start_reset() - - def _get_body_text(self): - if self._reset_state == ResetState.RESETTING: - return "Resetting device... This may take up to a minute." - if self._reset_state == ResetState.FAILED: - return "Reset failed. Reboot to try again." - if self._mode == ResetMode.RECOVER: - return "Unable to mount data partition. It may be corrupted." - return "All content and settings will be erased." - def main(): mode = ResetMode.USER_RESET @@ -140,12 +149,11 @@ def main(): gui_app.init_window("System Reset") reset = Reset(mode) + gui_app.push_widget(reset) if mode == ResetMode.FORMAT: reset.start_reset() - gui_app.push_widget(reset) - for _ in gui_app.render(): pass diff --git a/system/ui/mici_setup.py b/system/ui/mici_setup.py index 3265a325e2..92ac1df44e 100755 --- a/system/ui/mici_setup.py +++ b/system/ui/mici_setup.py @@ -221,7 +221,8 @@ class DownloadingPage(NavWidget): class FailedPage(NavScroller): - def __init__(self, retry_callback: Callable, title: str = "download failed", red_icon: bool = False): + def __init__(self, retry_callback: Callable | None, title: str = "download failed", + description: str | None = None, icon: str = "icons_mici/setup/warning.png"): super().__init__() self.set_back_callback(retry_callback) @@ -236,11 +237,9 @@ class FailedPage(NavScroller): self._reason_card = GreyBigButton("", "") self._reason_card.set_visible(False) - warning_icon = "icons_mici/setup/red_warning.png" if red_icon else "icons_mici/setup/warning.png" - self._scroller.add_widgets([ - GreyBigButton(title, "swipe down to go\nback and try again", - gui_app.texture(warning_icon, 64, 58)), + GreyBigButton(title, description or "swipe down to go\nback and try again", + gui_app.texture(icon, 64, 58)), self._reason_card, reboot_button, ]) @@ -459,7 +458,7 @@ class Setup(Widget): self._software_selection_page = SoftwareSelectionPage(self._use_openpilot, lambda: gui_app.push_widget(self._custom_software_warning_page)) - self._download_failed_page = FailedPage(self._pop_to_software_selection, red_icon=True) + self._download_failed_page = FailedPage(self._pop_to_software_selection, icon="icons_mici/setup/red_warning.png") self._custom_software_warning_page = CustomSoftwareWarningPage(lambda: self._push_network_setup(True), self._pop_to_software_selection) From 3cc9d89d4578b1b42f1ecfee87aa38bff7ecb3d6 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 4 Mar 2026 23:07:37 -0800 Subject: [PATCH 032/141] mici ui: wifi scanning card (#37564) * start * yes * no more show * clean up --- .../mici/layouts/settings/network/wifi_ui.py | 76 +++++++++---------- 1 file changed, 35 insertions(+), 41 deletions(-) diff --git a/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py b/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py index 22d3d1d0da..9cd4db7bf0 100644 --- a/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py +++ b/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py @@ -3,7 +3,6 @@ import numpy as np import pyray as rl from collections.abc import Callable -from openpilot.common.filter_simple import FirstOrderFilter from openpilot.common.swaglog import cloudlog from openpilot.selfdrive.ui.mici.widgets.dialog import BigInputDialog, BigConfirmationDialogV2 from openpilot.selfdrive.ui.mici.widgets.button import BigButton, LABEL_COLOR @@ -14,39 +13,26 @@ from openpilot.system.ui.lib.wifi_manager import WifiManager, Network, SecurityT class LoadingAnimation(Widget): - HIDE_TIME = 4 + RADIUS = 8 + SPACING = 24 # center-to-center: diameter (16) + gap (8) + Y_MAG = 11.2 def __init__(self): super().__init__() - self._opacity_filter = FirstOrderFilter(0.0, 0.1, 1 / gui_app.target_fps) - self._opacity_target = 1.0 - self._hide_time = 0.0 - - def show_event(self): - self._opacity_target = 1.0 - self._hide_time = rl.get_time() + w = self.SPACING * 2 + self.RADIUS * 2 + h = self.RADIUS * 2 + int(self.Y_MAG) + self.set_rect(rl.Rectangle(0, 0, w, h)) def _render(self, _): - if rl.get_time() - self._hide_time > self.HIDE_TIME: - self._opacity_target = 0.0 - - self._opacity_filter.update(self._opacity_target) - - if self._opacity_filter.x < 0.01: - return - - cx = int(self._rect.x + self._rect.width / 2) - cy = int(self._rect.y + self._rect.height / 2) - - y_mag = 7 - anim_scale = 4 - spacing = 14 + # Balls rest at bottom center; bounce upward + base_x = int(self._rect.x + self._rect.width / 2) + base_y = int(self._rect.y + self._rect.height - self.RADIUS) for i in range(3): - x = cx - spacing + i * spacing - y = int(cy + min(math.sin((rl.get_time() - i * 0.2) * anim_scale) * y_mag, 0)) - alpha = int(np.interp(cy - y, [0, y_mag], [255 * 0.45, 255 * 0.9]) * self._opacity_filter.x) - rl.draw_circle(x, y, 5, rl.Color(255, 255, 255, alpha)) + x = base_x + (i - 1) * self.SPACING + y = int(base_y + min(math.sin((rl.get_time() - i * 0.2) * 4) * self.Y_MAG, 0)) + alpha = int(np.interp(base_y - y, [0, self.Y_MAG], [255 * 0.45, 255 * 0.9])) + rl.draw_circle(x, y, self.RADIUS, rl.Color(255, 255, 255, alpha)) class WifiIcon(Widget): @@ -270,11 +256,26 @@ class ForgetButton(Widget): rl.draw_texture_ex(self._trash_txt, (trash_x, trash_y), 0, 1.0, rl.WHITE) +class ScanningButton(BigButton): + def __init__(self): + super().__init__("", "searching for networks") + self.set_enabled(False) + self._loading_animation = LoadingAnimation() + + def _draw_content(self, btn_y: float): + super()._draw_content(btn_y) + anim = self._loading_animation + x = self._rect.x + self._rect.width - anim.rect.width - 40 + y = btn_y + self._rect.height - anim.rect.height - 30 + anim.set_position(x, y) + anim.render() + + class WifiUIMici(NavScroller): def __init__(self, wifi_manager: WifiManager): super().__init__() - self._loading_animation = LoadingAnimation() + self._scanning_btn = ScanningButton() self._wifi_manager = wifi_manager self._networks: dict[str, Network] = {} @@ -288,9 +289,9 @@ class WifiUIMici(NavScroller): def show_event(self): # Clear scroller items and update from latest scan results super().show_event() - self._loading_animation.show_event() self._wifi_manager.set_active(True) self._scroller.items.clear() + self._scroller.add_widget(self._scanning_btn) # trigger button update on latest sorted networks self._on_network_updated(self._wifi_manager.networks) @@ -310,6 +311,11 @@ class WifiUIMici(NavScroller): btn.set_click_callback(lambda ssid=network.ssid: self._connect_to_network(ssid)) self._scroller.add_widget(btn) + # Keep scanning button at the end + items = self._scroller.items + if self._scanning_btn in items: + items.append(items.pop(items.index(self._scanning_btn))) + # Mark networks no longer in scan results (display handled by _update_state) for btn in self._scroller.items: if isinstance(btn, WifiButton) and btn.network.ssid not in self._networks: @@ -371,16 +377,4 @@ class WifiUIMici(NavScroller): self._move_network_to_front(self._wifi_manager.wifi_state.ssid) - # Show loading animation near end - max_scroll = max(self._scroller.content_size - self._scroller.rect.width, 1) - progress = -self._scroller.scroll_panel.get_offset() / max_scroll - if progress > 0.8 or len(self._scroller.items) <= 1: - self._loading_animation.show_event() - def _render(self, _): - super()._render(self._rect) - - anim_w = 90 - anim_x = self._rect.x + self._rect.width - anim_w - anim_y = self._rect.y + self._rect.height - 25 + 2 - self._loading_animation.render(rl.Rectangle(anim_x, anim_y, anim_w, 20)) From 4f5df6589d5c87f8b554f726096817208dd6662a Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 4 Mar 2026 23:47:34 -0800 Subject: [PATCH 033/141] mici setup: set WifiManager active on network setup page show (#37566) * set active * cmt --- system/ui/mici_setup.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/system/ui/mici_setup.py b/system/ui/mici_setup.py index 92ac1df44e..c1474728c7 100755 --- a/system/ui/mici_setup.py +++ b/system/ui/mici_setup.py @@ -371,6 +371,8 @@ class NetworkSetupPageBase(Scroller): def show_event(self): super().show_event() + # make sure we populate strength and ip immediately if already have wifi + self._wifi_manager.set_active(True) self._show_time = rl.get_time() self._prev_has_internet = False self._pending_has_internet_scroll = False From dcc166343fbd6b8eb5800ba4ba65a6c05e3003b1 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Thu, 5 Mar 2026 00:25:09 -0800 Subject: [PATCH 034/141] mici setup: get time immediately after internet (#37565) * should be instant * guard on disconnect * just time fix --- system/ui/mici_setup.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/system/ui/mici_setup.py b/system/ui/mici_setup.py index c1474728c7..5a0b751d3b 100755 --- a/system/ui/mici_setup.py +++ b/system/ui/mici_setup.py @@ -1,6 +1,7 @@ #!/usr/bin/env python3 import os import re +import ssl import threading import time import urllib.request @@ -16,6 +17,7 @@ from openpilot.common.filter_simple import FirstOrderFilter from openpilot.system.hardware import HARDWARE, TICI from openpilot.common.realtime import config_realtime_process, set_core_affinity from openpilot.common.swaglog import cloudlog +from openpilot.common.time_helpers import system_time_valid from openpilot.common.utils import run_cmd from openpilot.system.ui.lib.application import gui_app, FontWeight from openpilot.system.ui.lib.wifi_manager import WifiManager @@ -55,6 +57,7 @@ class NetworkConnectivityMonitor: self.wifi_connected = threading.Event() self._should_check = should_check or (lambda: True) self._stop_event = threading.Event() + self._last_timesyncd_restart = 0.0 self._thread: threading.Thread | None = None def start(self): @@ -82,6 +85,13 @@ class NetworkConnectivityMonitor: self.network_connected.set() if HARDWARE.get_network_type() == NetworkType.wifi: self.wifi_connected.set() + except urllib.error.URLError as e: + if (isinstance(e.reason, ssl.SSLCertVerificationError) and + not system_time_valid() and + time.monotonic() - self._last_timesyncd_restart > 5): + self._last_timesyncd_restart = time.monotonic() + run_cmd(["sudo", "systemctl", "restart", "systemd-timesyncd"]) + self.reset() except Exception: self.reset() else: From 3a19f85512d4dff7f9c5bc824fda2105a4d33fa7 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Thu, 5 Mar 2026 01:04:16 -0800 Subject: [PATCH 035/141] WifiManager: guard AP paths failure --- system/ui/lib/wifi_manager.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/system/ui/lib/wifi_manager.py b/system/ui/lib/wifi_manager.py index 4820c7aaba..d3c855d9bc 100644 --- a/system/ui/lib/wifi_manager.py +++ b/system/ui/lib/wifi_manager.py @@ -854,8 +854,12 @@ class WifiManager: # NOTE: AccessPoints property may exclude hidden APs (use GetAllAccessPoints method if needed) wifi_addr = DBusAddress(self._wifi_device, NM, interface=NM_WIRELESS_IFACE) - wifi_props = self._router_main.send_and_get_reply(Properties(wifi_addr).get_all()).body[0] - ap_paths = wifi_props.get('AccessPoints', ('ao', []))[1] + wifi_props_reply = self._router_main.send_and_get_reply(Properties(wifi_addr).get_all()) + if wifi_props_reply.header.message_type == MessageType.error: + cloudlog.warning(f"Failed to get WiFi properties: {wifi_props_reply}") + return + + ap_paths = wifi_props_reply.body[0].get('AccessPoints', ('ao', []))[1] aps: dict[str, list[AccessPoint]] = {} From d801cebb2e6e9a1d0931522cb2e86ba9d481b8a7 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Thu, 5 Mar 2026 01:23:29 -0800 Subject: [PATCH 036/141] mici setup: guard continue button when forgetting/connecting (#37568) * test * fix * test * too much * simple to ship * revert * bug free * simpler * fix * even safer guard --- .../mici/layouts/settings/network/wifi_ui.py | 9 ++++++ system/ui/mici_setup.py | 31 +++++++++++++------ 2 files changed, 31 insertions(+), 9 deletions(-) diff --git a/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py b/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py index 9cd4db7bf0..02b561d81e 100644 --- a/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py +++ b/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py @@ -110,6 +110,10 @@ class WifiButton(BigButton): if self._is_connected or self._is_connecting: self._wrong_password = False + @property + def network_forgetting(self) -> bool: + return self._network_forgetting + def _forget_network(self): if self._network_forgetting: return @@ -286,6 +290,11 @@ class WifiUIMici(NavScroller): networks_updated=self._on_network_updated, ) + @property + def any_network_forgetting(self) -> bool: + # TODO: deactivate before forget and add DISCONNECTING state + return any(btn.network_forgetting for btn in self._scroller.items if isinstance(btn, WifiButton)) + def show_event(self): # Clear scroller items and update from latest scan results super().show_event() diff --git a/system/ui/mici_setup.py b/system/ui/mici_setup.py index 5a0b751d3b..2702287706 100755 --- a/system/ui/mici_setup.py +++ b/system/ui/mici_setup.py @@ -20,7 +20,7 @@ from openpilot.common.swaglog import cloudlog from openpilot.common.time_helpers import system_time_valid from openpilot.common.utils import run_cmd from openpilot.system.ui.lib.application import gui_app, FontWeight -from openpilot.system.ui.lib.wifi_manager import WifiManager +from openpilot.system.ui.lib.wifi_manager import WifiManager, ConnectStatus from openpilot.system.ui.widgets import Widget from openpilot.system.ui.widgets.nav_widget import NavWidget from openpilot.system.ui.widgets.label import UnifiedLabel @@ -55,6 +55,7 @@ class NetworkConnectivityMonitor: def __init__(self, should_check: Callable[[], bool] | None = None): self.network_connected = threading.Event() self.wifi_connected = threading.Event() + self.recheck_event = threading.Event() self._should_check = should_check or (lambda: True) self._stop_event = threading.Event() self._last_timesyncd_restart = 0.0 @@ -76,12 +77,21 @@ class NetworkConnectivityMonitor: self.network_connected.clear() self.wifi_connected.clear() + def invalidate(self): + self.recheck_event.set() + def _run(self): while not self._stop_event.is_set(): if self._should_check(): try: request = urllib.request.Request(OPENPILOT_URL, method="HEAD") urllib.request.urlopen(request, timeout=2.0) + + # Discard stale result if invalidated during request + if self.recheck_event.is_set(): + self.recheck_event.clear() + continue + self.network_connected.set() if HARDWARE.get_network_type() == NetworkType.wifi: self.wifi_connected.set() @@ -392,7 +402,17 @@ class NetworkSetupPageBase(Scroller): def _nav_stack_tick(self): self._wifi_manager.process_callbacks() - has_internet = self._network_monitor.network_connected.is_set() + # Discard stale poll results while network state is changing + network_changing = self._wifi_ui.any_network_forgetting or self._wifi_manager.wifi_state.status == ConnectStatus.CONNECTING + if network_changing: + self._network_monitor.invalidate() + + has_internet = (self._network_monitor.network_connected.is_set() and + not network_changing and + not self._network_monitor.recheck_event.is_set()) + self._continue_button.set_visible(has_internet) + self._waiting_button.set_visible(not has_internet) + if has_internet and not self._prev_has_internet: self._pending_has_internet_scroll = True self._prev_has_internet = has_internet @@ -432,13 +452,6 @@ class NetworkSetupPageBase(Scroller): self._pending_wifi_grow_animation = False self._wifi_button.trigger_grow_animation() - if self._network_monitor.network_connected.is_set(): - self._continue_button.set_visible(True) - self._waiting_button.set_visible(False) - else: - self._continue_button.set_visible(False) - self._waiting_button.set_visible(True) - class NetworkSetupPage(NetworkSetupPageBase, NavScroller): def __init__(self, network_monitor: NetworkConnectivityMonitor, continue_callback: Callable[[bool], None], From 41bba2b55ae7b915a6ce4b356ae2837fbf4d7da2 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Thu, 5 Mar 2026 02:11:23 -0800 Subject: [PATCH 037/141] mici setup: fix race on disconnect guard --- system/ui/mici_setup.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/system/ui/mici_setup.py b/system/ui/mici_setup.py index 2702287706..cc65fc4bb0 100755 --- a/system/ui/mici_setup.py +++ b/system/ui/mici_setup.py @@ -79,6 +79,7 @@ class NetworkConnectivityMonitor: def invalidate(self): self.recheck_event.set() + self.reset() def _run(self): while not self._stop_event.is_set(): @@ -400,9 +401,8 @@ class NetworkSetupPageBase(Scroller): self._pending_wifi_grow_animation = False def _nav_stack_tick(self): - self._wifi_manager.process_callbacks() - - # Discard stale poll results while network state is changing + # Check network state before processing callbacks so forgetting flag + # is still set on the frame the forgotten callback fires network_changing = self._wifi_ui.any_network_forgetting or self._wifi_manager.wifi_state.status == ConnectStatus.CONNECTING if network_changing: self._network_monitor.invalidate() @@ -413,6 +413,8 @@ class NetworkSetupPageBase(Scroller): self._continue_button.set_visible(has_internet) self._waiting_button.set_visible(not has_internet) + self._wifi_manager.process_callbacks() + if has_internet and not self._prev_has_internet: self._pending_has_internet_scroll = True self._prev_has_internet = has_internet From 4a1101c032abc8c49ad6e67d998af09a3493e12c Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Thu, 5 Mar 2026 02:54:24 -0800 Subject: [PATCH 038/141] mici setup: don't run network tick while not in network setup page --- system/ui/mici_setup.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/system/ui/mici_setup.py b/system/ui/mici_setup.py index cc65fc4bb0..186f8e58e7 100755 --- a/system/ui/mici_setup.py +++ b/system/ui/mici_setup.py @@ -401,6 +401,11 @@ class NetworkSetupPageBase(Scroller): self._pending_wifi_grow_animation = False def _nav_stack_tick(self): + # Only run tick when this page or its WiFi UI is on the stack + if gui_app.get_active_widget() is not self and not gui_app.widget_in_stack(self._wifi_ui): + self._wifi_manager.process_callbacks() + return + # Check network state before processing callbacks so forgetting flag # is still set on the frame the forgotten callback fires network_changing = self._wifi_ui.any_network_forgetting or self._wifi_manager.wifi_state.status == ConnectStatus.CONNECTING @@ -425,7 +430,7 @@ class NetworkSetupPageBase(Scroller): if elapsed > 0.5: self._pending_has_internet_scroll = False - def scroll_to_download(): + def scroll_to_end(): self._scroller._layout() end_offset = -(self._scroller.content_size - self._rect.width) remaining = self._scroller.scroll_panel.get_offset() - end_offset @@ -433,7 +438,7 @@ class NetworkSetupPageBase(Scroller): self._pending_continue_grow_animation = True # Animate WifiUi down first before scroll - gui_app.pop_widgets_to(self, scroll_to_download) + gui_app.pop_widgets_to(self, scroll_to_end) def set_custom_software(self, custom_software: bool): self._custom_software = custom_software From 2d53f4cf019df478542493a6f92722187947c3a1 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Thu, 5 Mar 2026 03:36:37 -0800 Subject: [PATCH 039/141] WifiUi: re-sort buttons on show (#37570) sort --- .../mici/layouts/settings/network/wifi_ui.py | 27 +++++++++++-------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py b/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py index 02b561d81e..54834debc8 100644 --- a/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py +++ b/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py @@ -296,19 +296,17 @@ class WifiUIMici(NavScroller): return any(btn.network_forgetting for btn in self._scroller.items if isinstance(btn, WifiButton)) def show_event(self): - # Clear scroller items and update from latest scan results + # Re-sort scroller items and update from latest scan results super().show_event() self._wifi_manager.set_active(True) - self._scroller.items.clear() - self._scroller.add_widget(self._scanning_btn) - # trigger button update on latest sorted networks - self._on_network_updated(self._wifi_manager.networks) + self._networks = {n.ssid: n for n in self._wifi_manager.networks} + self._update_buttons(re_sort=True) def _on_network_updated(self, networks: list[Network]): self._networks = {network.ssid: network for network in networks} self._update_buttons() - def _update_buttons(self): + def _update_buttons(self, re_sort: bool = False): # Update existing buttons, add new ones to the end existing = {btn.network.ssid: btn for btn in self._scroller.items if isinstance(btn, WifiButton)} @@ -320,15 +318,22 @@ class WifiUIMici(NavScroller): btn.set_click_callback(lambda ssid=network.ssid: self._connect_to_network(ssid)) self._scroller.add_widget(btn) + if re_sort: + # Remove stale buttons and sort to match scan order, preserving eager state + btn_map = {btn.network.ssid: btn for btn in self._scroller.items if isinstance(btn, WifiButton)} + self._scroller.items[:] = [btn_map[ssid] for ssid in self._networks if ssid in btn_map] + else: + # Mark networks no longer in scan results (display handled by _update_state) + for btn in self._scroller.items: + if isinstance(btn, WifiButton) and btn.network.ssid not in self._networks: + btn.set_network_missing(True) + # Keep scanning button at the end items = self._scroller.items if self._scanning_btn in items: items.append(items.pop(items.index(self._scanning_btn))) - - # Mark networks no longer in scan results (display handled by _update_state) - for btn in self._scroller.items: - if isinstance(btn, WifiButton) and btn.network.ssid not in self._networks: - btn.set_network_missing(True) + else: + self._scroller.add_widget(self._scanning_btn) def _connect_with_password(self, ssid: str, password: str): self._wifi_manager.connect_to_network(ssid, password) From b4b747e5cb005b136aefedd1030c62536981db48 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Thu, 5 Mar 2026 04:48:30 -0800 Subject: [PATCH 040/141] mici scroller: fix scroll bar direction with less content than viewport (#37571) fix --- system/ui/widgets/scroller.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system/ui/widgets/scroller.py b/system/ui/widgets/scroller.py index c48be6b80b..5becef7939 100644 --- a/system/ui/widgets/scroller.py +++ b/system/ui/widgets/scroller.py @@ -47,7 +47,7 @@ class ScrollIndicator(Widget): # position based on scroll ratio slide_range = self._viewport.width - indicator_w max_scroll = self._content_size - self._viewport.width - scroll_ratio = -self._scroll_offset / max_scroll + scroll_ratio = (-self._scroll_offset / abs(max_scroll)) if abs(max_scroll) > 1e-3 else 0.0 x = self._viewport.x + scroll_ratio * slide_range # don't bounce up when NavWidget shows y = max(self._viewport.y, 0) + self._viewport.height - self._txt_scroll_indicator.height / 2 From 6922d587626ec80db1ce1ae5515738164b15fd39 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Thu, 5 Mar 2026 04:58:18 -0800 Subject: [PATCH 041/141] mici setup: swipe down on wifi connect, then wait for internet (#37569) * try this * try this * fix * delay hide on wifi/internet * 0.5 * fix flash on forgetting * also reset * fix * todo * dupl * wifi after * bring back cmts * fix spotty internet check while downloading! * cmt * cmt * todo * resort * more delay * redundtant * nl * scroll over for wifi (waiting) OR internet (continue) * fix scroll * fix scroll * show_event fully manages its scroll over, not some weiird delay mixed with other triggers via fake rising edge * instant if not popping * cmt --- system/ui/mici_setup.py | 72 +++++++++++++++++++++++++---------------- 1 file changed, 44 insertions(+), 28 deletions(-) diff --git a/system/ui/mici_setup.py b/system/ui/mici_setup.py index 186f8e58e7..c2034ffda7 100755 --- a/system/ui/mici_setup.py +++ b/system/ui/mici_setup.py @@ -355,7 +355,6 @@ class NetworkSetupPageBase(Scroller): self._wifi_manager.set_active(True) self._network_monitor = network_monitor self._custom_software = False - self._prev_has_internet = False self._wifi_ui = WifiUIMici(self._wifi_manager) self._connect_button = GreyBigButton("connect to\ninternet", "swipe down to go back", @@ -365,8 +364,9 @@ class NetworkSetupPageBase(Scroller): self._wifi_button = WifiNetworkButton(self._wifi_manager) self._wifi_button.set_click_callback(lambda: gui_app.push_widget(self._wifi_ui)) - self._show_time = 0.0 - self._pending_has_internet_scroll = False + self._prev_has_internet = False + self._prev_wifi_connected = False + self._pending_has_internet_scroll: float | None = None # stores time to use as delay self._pending_continue_grow_animation = False self._pending_wifi_grow_animation = False @@ -394,12 +394,26 @@ class NetworkSetupPageBase(Scroller): super().show_event() # make sure we populate strength and ip immediately if already have wifi self._wifi_manager.set_active(True) - self._show_time = rl.get_time() - self._prev_has_internet = False - self._pending_has_internet_scroll = False + self._prev_has_internet = self._has_internet + self._prev_wifi_connected = self._wifi_manager.wifi_state.status == ConnectStatus.CONNECTED + self._pending_has_internet_scroll = None self._pending_continue_grow_animation = False self._pending_wifi_grow_animation = False + if self._prev_has_internet or self._prev_wifi_connected: + self.set_shown_callback(lambda: self._scroll_to_end_and_grow()) + + @property + def _has_internet(self) -> bool: + network_changing = self._wifi_ui.any_network_forgetting or self._wifi_manager.wifi_state.status == ConnectStatus.CONNECTING + if network_changing: + self._network_monitor.invalidate() + + has_internet = (self._network_monitor.network_connected.is_set() and + not network_changing and + not self._network_monitor.recheck_event.is_set()) + return has_internet + def _nav_stack_tick(self): # Only run tick when this page or its WiFi UI is on the stack if gui_app.get_active_widget() is not self and not gui_app.widget_in_stack(self._wifi_ui): @@ -408,37 +422,39 @@ class NetworkSetupPageBase(Scroller): # Check network state before processing callbacks so forgetting flag # is still set on the frame the forgotten callback fires - network_changing = self._wifi_ui.any_network_forgetting or self._wifi_manager.wifi_state.status == ConnectStatus.CONNECTING - if network_changing: - self._network_monitor.invalidate() - - has_internet = (self._network_monitor.network_connected.is_set() and - not network_changing and - not self._network_monitor.recheck_event.is_set()) + has_internet = self._has_internet self._continue_button.set_visible(has_internet) self._waiting_button.set_visible(not has_internet) + # TODO: fire show/hide events on visibility changes + if not has_internet: + self._pending_continue_grow_animation = False + self._wifi_manager.process_callbacks() - if has_internet and not self._prev_has_internet: - self._pending_has_internet_scroll = True + # Dismiss WiFi UI and scroll on WiFi connect or internet gain + wifi_connected = self._wifi_manager.wifi_state.status == ConnectStatus.CONNECTED + if (has_internet and not self._prev_has_internet) or (wifi_connected and not self._prev_wifi_connected): + # TODO: cancel if connect is transient + self._pending_has_internet_scroll = rl.get_time() + self._prev_has_internet = has_internet + self._prev_wifi_connected = wifi_connected - if self._pending_has_internet_scroll: + if self._pending_has_internet_scroll is not None: # Scrolls over to continue button, then grows once in view - elapsed = rl.get_time() - self._show_time - if elapsed > 0.5: - self._pending_has_internet_scroll = False - - def scroll_to_end(): - self._scroller._layout() - end_offset = -(self._scroller.content_size - self._rect.width) - remaining = self._scroller.scroll_panel.get_offset() - end_offset - self._scroller.scroll_to(remaining, smooth=True, block_interaction=True) - self._pending_continue_grow_animation = True - + elapsed = rl.get_time() - self._pending_has_internet_scroll + if elapsed > 0.7 or gui_app.get_active_widget() is self: # instant scroll + grow if not popping # Animate WifiUi down first before scroll - gui_app.pop_widgets_to(self, scroll_to_end) + self._pending_has_internet_scroll = None + gui_app.pop_widgets_to(self, self._scroll_to_end_and_grow) + + def _scroll_to_end_and_grow(self): + self._scroller._layout() + end_offset = -(self._scroller.content_size - self._rect.width) + remaining = self._scroller.scroll_panel.get_offset() - end_offset + self._scroller.scroll_to(remaining, smooth=True, block_interaction=True) + self._pending_continue_grow_animation = True def set_custom_software(self, custom_software: bool): self._custom_software = custom_software From 93eb8418b72d45dcf13f867548345980a3e5ecc6 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Thu, 5 Mar 2026 05:54:44 -0800 Subject: [PATCH 042/141] Zip app updater (#37572) replace --- system/hardware/tici/updater_magic | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/system/hardware/tici/updater_magic b/system/hardware/tici/updater_magic index b4f776565d..8bf150744a 100755 --- a/system/hardware/tici/updater_magic +++ b/system/hardware/tici/updater_magic @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d376e7aa4bb44bf78db9965ff55a5362ac823335b8d2a81dd3aebc5c8c2f239f -size 71214189 +oid sha256:3236a08d318c26022e536fa76627ca15c0517f050f4c4e91a1aafc5bfefb44d0 +size 71240680 From 118d903e2dd04f146261d2fe1d775b3a89bcd39d Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Thu, 5 Mar 2026 06:04:01 -0800 Subject: [PATCH 043/141] mici ui: slim review terms (#37573) * replace * fix --- selfdrive/ui/mici/layouts/onboarding.py | 9 ++++++--- selfdrive/ui/mici/layouts/settings/device.py | 8 ++------ 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/selfdrive/ui/mici/layouts/onboarding.py b/selfdrive/ui/mici/layouts/onboarding.py index cf633192ea..557a8de8b2 100644 --- a/selfdrive/ui/mici/layouts/onboarding.py +++ b/selfdrive/ui/mici/layouts/onboarding.py @@ -337,13 +337,16 @@ class TermsPage(Scroller): self._decline_button = BigCircleButton("icons_mici/setup/cancel.png", red=True) self._decline_button.set_click_callback(show_decline_dialog) + self._terms_header = GreyBigButton("terms and\nconditions", "scroll to continue", + gui_app.texture("icons_mici/setup/green_info.png", 64, 64)) + self._must_accept_card = GreyBigButton("", "You must accept the Terms & Conditions to use openpilot.") + self._scroller.add_widgets([ - GreyBigButton("terms and\nconditions", "scroll to continue", - gui_app.texture("icons_mici/setup/green_info.png", 64, 64)), + self._terms_header, GreyBigButton("swipe for QR code", "or go to https://comma.ai/terms", gui_app.texture("icons_mici/setup/small_slider/slider_arrow.png", 64, 56, flip_x=True)), QRCodeWidget("https://comma.ai/terms"), - GreyBigButton("", "You must accept the Terms & Conditions to use openpilot."), + self._must_accept_card, self._accept_button, self._decline_button, ]) diff --git a/selfdrive/ui/mici/layouts/settings/device.py b/selfdrive/ui/mici/layouts/settings/device.py index ed29a6a84a..78ec67ccb9 100644 --- a/selfdrive/ui/mici/layouts/settings/device.py +++ b/selfdrive/ui/mici/layouts/settings/device.py @@ -13,7 +13,6 @@ from openpilot.selfdrive.ui.mici.widgets.dialog import BigDialog, BigConfirmatio from openpilot.selfdrive.ui.mici.widgets.pairing_dialog import PairingDialog from openpilot.selfdrive.ui.mici.onroad.driver_camera_dialog import DriverCameraDialog from openpilot.selfdrive.ui.mici.layouts.onboarding import TrainingGuide, TermsPage -from openpilot.system.ui.mici_setup import BigPillButton from openpilot.system.ui.lib.application import gui_app, FontWeight, MousePos from openpilot.system.ui.lib.multilang import tr from openpilot.system.ui.widgets import Widget @@ -27,13 +26,11 @@ class ReviewTermsPage(TermsPage, NavScroller): """TermsPage with NavWidget swipe-to-dismiss for reviewing in device settings.""" def __init__(self): super().__init__(on_accept=self.dismiss, on_decline=self.dismiss) + self._terms_header.set_visible(False) + self._must_accept_card.set_visible(False) self._accept_button.set_visible(False) self._decline_button.set_visible(False) - close_button = BigPillButton("close") - close_button.set_click_callback(self.dismiss) - self._scroller.add_widget(close_button) - class ReviewTrainingGuide(TrainingGuide): def show_event(self): @@ -340,7 +337,6 @@ class DeviceLayoutMici(NavScroller): terms_btn = BigButton("terms &\nconditions", "", "icons_mici/settings/device/info.png") terms_btn.set_click_callback(lambda: gui_app.push_widget(ReviewTermsPage())) - terms_btn.set_enabled(lambda: ui_state.is_offroad()) self._scroller.add_widgets([ DeviceInfoLayoutMici(), From 5303afb0dcc9c9a599ccca3abf93a959e68f00e3 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Thu, 5 Mar 2026 07:20:50 -0800 Subject: [PATCH 044/141] mici installer: bring back finishing setup (#37574) need this :( --- selfdrive/ui/installer/installer.cc | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/selfdrive/ui/installer/installer.cc b/selfdrive/ui/installer/installer.cc index 9c35b0465e..7599454194 100644 --- a/selfdrive/ui/installer/installer.cc +++ b/selfdrive/ui/installer/installer.cc @@ -81,14 +81,16 @@ void run(const char* cmd) { } void finishInstall() { - if (tici_device) { - BeginDrawing(); - ClearBackground(BLACK); + BeginDrawing(); + ClearBackground(BLACK); + if (tici_device) { const char *m = "Finishing install..."; int text_width = MeasureText(m, FONT_SIZE); DrawTextEx(font_display, m, (Vector2){(float)(GetScreenWidth() - text_width)/2 + FONT_SIZE, (float)(GetScreenHeight() - FONT_SIZE)/2}, FONT_SIZE, 0, WHITE); - EndDrawing(); - } + } else { + DrawTextEx(font_display, "finishing setup", (Vector2){12, 0}, 77, 0, (Color){255, 255, 255, (unsigned char)(255 * 0.9)}); + } + EndDrawing(); util::sleep_for(60 * 1000); } From 7c3759e1474a836f45f3b5f78a7adbb273d45fa3 Mon Sep 17 00:00:00 2001 From: Lukas Heintz <61192133+lukasloetkolben@users.noreply.github.com> Date: Thu, 5 Mar 2026 18:34:08 +0100 Subject: [PATCH 045/141] Rivian: Flash xnor's Longitudinal Upgrade Kit prior supported panda check (#1752) * fixed missing internal panda * lets do it like that * cleanup * move up --------- Co-authored-by: Jason Wen --- selfdrive/pandad/pandad.py | 6 ++--- .../selfdrive/pandad/rivian_long_flasher.py | 22 ++++++++++++------- 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/selfdrive/pandad/pandad.py b/selfdrive/pandad/pandad.py index bda349aec2..a8300de321 100755 --- a/selfdrive/pandad/pandad.py +++ b/selfdrive/pandad/pandad.py @@ -134,6 +134,9 @@ def main() -> None: cloudlog.info(f"{len(panda_serials)} panda(s) found, connecting - {panda_serials}") + # custom flasher for xnor's Rivian Longitudinal Upgrade Kit + flash_rivian_long(panda_serials) + # skip flashing and health check if no supported panda is detected if not check_panda_support(panda_serials): continue @@ -142,9 +145,6 @@ def main() -> None: panda_serial = panda_serials[0] panda = flash_panda(panda_serial) - # flash Rivian longitudinal upgrade panda - flash_rivian_long(panda) - # Ensure internal panda is present if expected if HARDWARE.has_internal_panda() and not panda.is_internal(): cloudlog.error("Internal panda is missing, trying again") diff --git a/sunnypilot/selfdrive/pandad/rivian_long_flasher.py b/sunnypilot/selfdrive/pandad/rivian_long_flasher.py index 24191a73a2..70ddb44358 100755 --- a/sunnypilot/selfdrive/pandad/rivian_long_flasher.py +++ b/sunnypilot/selfdrive/pandad/rivian_long_flasher.py @@ -72,9 +72,10 @@ def _flash_panda(panda: Panda) -> None: _flash_static(panda._handle, code) panda.reconnect() + cloudlog.info(f"Successfully flashed xnor's Rivian Longitudinal Upgrade Kit: {panda.get_usb_serial()}") -def flash_rivian_long(panda: Panda) -> None: +def flash_rivian_long(panda_serials: list[str]) -> None: if not os.path.isfile(FW_PATH): cloudlog.error(f"Rivian longitudinal upgrade firmware not found at {FW_PATH}") return @@ -83,13 +84,18 @@ def flash_rivian_long(panda: Panda) -> None: cloudlog.info("Not a Rivian, skipping longitudinal upgrade...") return - # only flash external black pandas (HW_TYPE_BLACK = 0x03) - if panda.get_type() == b'\x03' and not panda.is_internal(): - try: - _flash_panda(panda) - except Exception: - cloudlog.exception(f"Failed to flash F4 panda {panda.get_usb_serial()}") + for serial in panda_serials: + panda = Panda(serial) + # only flash external black pandas (HW_TYPE_BLACK = 0x03) + if panda.get_type() == b'\x03' and not panda.is_internal(): + try: + _flash_panda(panda) + except Exception: + cloudlog.exception(f"Failed to flash xnor's Rivian Longitudinal Upgrade Kit: {serial}") + panda.close() + + return if __name__ == '__main__': - flash_rivian_long(Panda()) + flash_rivian_long(Panda.list()) From 363735f7ce936755c3c39c35a5e131c280a6fa0f Mon Sep 17 00:00:00 2001 From: YassineYousfi Date: Thu, 5 Mar 2026 09:38:51 -0800 Subject: [PATCH 046/141] Update RELEASES.md --- RELEASES.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/RELEASES.md b/RELEASES.md index 895dcbba7a..d6d22d4891 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -1,5 +1,8 @@ Version 0.10.4 (2026-02-17) ======================== +* New driving model #36798 + * Fully trained using a learned simulator + * Improved longitudinal performance in experimental mode * Kia K7 2017 support thanks to royjr! * Lexus LS 2018 support thanks to Hacheoy! * Reduce comma four standby power usage by 77% to 52 mW From 4e0a26be8d8b4383ec6f0fb4feb47c1f1a6358c4 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Thu, 5 Mar 2026 17:03:22 -0500 Subject: [PATCH 047/141] [TIZI/TICI] ui: add back gate steering arc behind toggle (#1756) --- selfdrive/ui/sunnypilot/onroad/hud_renderer.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/selfdrive/ui/sunnypilot/onroad/hud_renderer.py b/selfdrive/ui/sunnypilot/onroad/hud_renderer.py index 1063809c32..f8e4257733 100644 --- a/selfdrive/ui/sunnypilot/onroad/hud_renderer.py +++ b/selfdrive/ui/sunnypilot/onroad/hud_renderer.py @@ -131,10 +131,11 @@ class HudRendererSP(HudRenderer): def _render(self, rect: rl.Rectangle) -> None: super()._render(rect) - torque_rect = rect - if ui_state.developer_ui in (DeveloperUiState.BOTTOM, DeveloperUiState.BOTH): - torque_rect = rl.Rectangle(rect.x, rect.y, rect.width, rect.height - get_bottom_dev_ui_offset()) - self._torque_bar.render(torque_rect) + if ui_state.torque_bar: + torque_rect = rect + if ui_state.developer_ui in (DeveloperUiState.BOTTOM, DeveloperUiState.BOTH): + torque_rect = rl.Rectangle(rect.x, rect.y, rect.width, rect.height - get_bottom_dev_ui_offset()) + self._torque_bar.render(torque_rect) self.developer_ui.render(rect) self.road_name_renderer.render(rect) From 6dd72973eca41d61cdcfe8b63709f7255caaf119 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Thu, 5 Mar 2026 18:13:36 -0500 Subject: [PATCH 048/141] [TIZI/TICI] ui: more add back gate steering arc behind toggle --- selfdrive/ui/sunnypilot/onroad/augmented_road_view.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/ui/sunnypilot/onroad/augmented_road_view.py b/selfdrive/ui/sunnypilot/onroad/augmented_road_view.py index 64d7a3d946..c7dedee540 100644 --- a/selfdrive/ui/sunnypilot/onroad/augmented_road_view.py +++ b/selfdrive/ui/sunnypilot/onroad/augmented_road_view.py @@ -23,7 +23,7 @@ class AugmentedRoadViewSP: def update_fade_out_bottom_overlay(self, _content_rect): # Fade out bottom of overlays for looks (only when engaged) fade_alpha = self._fade_alpha_filter.update(ui_state.status != UIStatus.DISENGAGED) - if ui_state.torque_bar and ui_state.sm['controlsState'].lateralControlState.which() != 'angleState' and fade_alpha > 1e-2: + if ui_state.torque_bar and fade_alpha > 1e-2: # Scale the fade texture to the content rect rl.draw_texture_pro(self._fade_texture, rl.Rectangle(0, 0, self._fade_texture.width, self._fade_texture.height), From ac1dd692af83febeaf30ae2f4f66d4e430315f72 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Fri, 6 Mar 2026 17:18:41 -0800 Subject: [PATCH 049/141] ui: fix BigButton shake on startup (#37577) _shake_start defaults to None, but `None or 0.0` treated it as time zero, so any button rendered within 0.5s of window creation would play the shake animation. Co-authored-by: Claude Opus 4.6 --- selfdrive/ui/mici/widgets/button.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/selfdrive/ui/mici/widgets/button.py b/selfdrive/ui/mici/widgets/button.py index 5559a1181c..18413dbc3a 100644 --- a/selfdrive/ui/mici/widgets/button.py +++ b/selfdrive/ui/mici/widgets/button.py @@ -194,7 +194,9 @@ class BigButton(Widget): SHAKE_DURATION = 0.5 SHAKE_AMPLITUDE = 24.0 SHAKE_FREQUENCY = 32.0 - t = rl.get_time() - (self._shake_start or 0.0) + if self._shake_start is None: + return 0.0 + t = rl.get_time() - self._shake_start if t > SHAKE_DURATION: return 0.0 decay = 1.0 - t / SHAKE_DURATION From 4651bc6a1f57886294bbbdb3a10912aef82d7c41 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Fri, 6 Mar 2026 17:33:50 -0800 Subject: [PATCH 050/141] ui: rename BigConfirmationDialogV2 (#37578) * ui: rename BigConfirmationDialogV2 * clean up --- selfdrive/ui/mici/layouts/onboarding.py | 16 ++++++++-------- selfdrive/ui/mici/layouts/settings/device.py | 8 ++++---- .../ui/mici/layouts/settings/network/wifi_ui.py | 8 +++----- selfdrive/ui/mici/tests/test_widget_leaks.py | 6 +++--- selfdrive/ui/mici/widgets/dialog.py | 2 +- system/ui/mici_reset.py | 14 +++++++------- system/ui/mici_setup.py | 6 +++--- system/ui/widgets/slider.py | 2 +- 8 files changed, 30 insertions(+), 32 deletions(-) diff --git a/selfdrive/ui/mici/layouts/onboarding.py b/selfdrive/ui/mici/layouts/onboarding.py index 557a8de8b2..3a3625d714 100644 --- a/selfdrive/ui/mici/layouts/onboarding.py +++ b/selfdrive/ui/mici/layouts/onboarding.py @@ -15,7 +15,7 @@ from openpilot.system.ui.lib.multilang import tr from openpilot.system.version import terms_version, training_version from openpilot.selfdrive.ui.ui_state import ui_state, device from openpilot.selfdrive.ui.mici.widgets.button import BigCircleButton -from openpilot.selfdrive.ui.mici.widgets.dialog import BigConfirmationDialogV2 +from openpilot.selfdrive.ui.mici.widgets.dialog import BigConfirmationDialog from openpilot.selfdrive.ui.mici.onroad.driver_state import DriverStateRenderer from openpilot.selfdrive.ui.mici.onroad.driver_camera_dialog import BaseDriverCameraDialog @@ -220,15 +220,15 @@ class TrainingGuideRecordFront(NavScroller): ui_state.params.put_bool_nonblocking("RecordFront", True) continue_callback() - gui_app.push_widget(BigConfirmationDialogV2("allow data uploading", "icons_mici/setup/driver_monitoring/dm_check.png", exit_on_confirm=False, - confirm_callback=on_accept)) + gui_app.push_widget(BigConfirmationDialog("allow data uploading", "icons_mici/setup/driver_monitoring/dm_check.png", exit_on_confirm=False, + confirm_callback=on_accept)) def show_decline_dialog(): def on_decline(): ui_state.params.put_bool_nonblocking("RecordFront", False) continue_callback() - gui_app.push_widget(BigConfirmationDialogV2("no, don't upload", "icons_mici/setup/cancel.png", exit_on_confirm=False, confirm_callback=on_decline)) + gui_app.push_widget(BigConfirmationDialog("no, don't upload", "icons_mici/setup/cancel.png", exit_on_confirm=False, confirm_callback=on_decline)) self._accept_button = BigCircleButton("icons_mici/setup/driver_monitoring/dm_check.png") self._accept_button.set_click_callback(show_accept_dialog) @@ -324,12 +324,12 @@ class TermsPage(Scroller): super().__init__() def show_accept_dialog(): - gui_app.push_widget(BigConfirmationDialogV2("accept\nterms", "icons_mici/setup/driver_monitoring/dm_check.png", - confirm_callback=on_accept)) + gui_app.push_widget(BigConfirmationDialog("accept\nterms", "icons_mici/setup/driver_monitoring/dm_check.png", + confirm_callback=on_accept)) def show_decline_dialog(): - gui_app.push_widget(BigConfirmationDialogV2("decline &\nuninstall", "icons_mici/setup/cancel.png", - red=True, exit_on_confirm=False, confirm_callback=on_decline)) + gui_app.push_widget(BigConfirmationDialog("decline &\nuninstall", "icons_mici/setup/cancel.png", + red=True, exit_on_confirm=False, confirm_callback=on_decline)) self._accept_button = BigCircleButton("icons_mici/setup/driver_monitoring/dm_check.png") self._accept_button.set_click_callback(show_accept_dialog) diff --git a/selfdrive/ui/mici/layouts/settings/device.py b/selfdrive/ui/mici/layouts/settings/device.py index 78ec67ccb9..0bc1ac2958 100644 --- a/selfdrive/ui/mici/layouts/settings/device.py +++ b/selfdrive/ui/mici/layouts/settings/device.py @@ -9,7 +9,7 @@ from openpilot.common.params import Params from openpilot.common.time_helpers import system_time_valid from openpilot.system.ui.widgets.scroller import NavRawScrollPanel, NavScroller from openpilot.selfdrive.ui.mici.widgets.button import BigButton, BigCircleButton -from openpilot.selfdrive.ui.mici.widgets.dialog import BigDialog, BigConfirmationDialogV2 +from openpilot.selfdrive.ui.mici.widgets.dialog import BigDialog, BigConfirmationDialog from openpilot.selfdrive.ui.mici.widgets.pairing_dialog import PairingDialog from openpilot.selfdrive.ui.mici.onroad.driver_camera_dialog import DriverCameraDialog from openpilot.selfdrive.ui.mici.layouts.onboarding import TrainingGuide, TermsPage @@ -85,9 +85,9 @@ def _engaged_confirmation_callback(callback: Callable, action_text: str): # TODO: check icon = "icons_mici/settings/comma_icon.png" - dlg: BigConfirmationDialogV2 | BigDialog = BigConfirmationDialogV2(f"slide to\n{action_text.lower()}", icon, red=red, - exit_on_confirm=action_text == "reset", - confirm_callback=confirm_callback) + dlg: BigConfirmationDialog | BigDialog = BigConfirmationDialog(f"slide to\n{action_text.lower()}", icon, red=red, + exit_on_confirm=action_text == "reset", + confirm_callback=confirm_callback) gui_app.push_widget(dlg) else: dlg = BigDialog(f"Disengage to {action_text}", "") diff --git a/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py b/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py index 54834debc8..cda3cb365a 100644 --- a/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py +++ b/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py @@ -4,7 +4,7 @@ import pyray as rl from collections.abc import Callable from openpilot.common.swaglog import cloudlog -from openpilot.selfdrive.ui.mici.widgets.dialog import BigInputDialog, BigConfirmationDialogV2 +from openpilot.selfdrive.ui.mici.widgets.dialog import BigInputDialog, BigConfirmationDialog from openpilot.selfdrive.ui.mici.widgets.button import BigButton, LABEL_COLOR from openpilot.system.ui.lib.application import gui_app, MousePos, FontWeight from openpilot.system.ui.widgets import Widget @@ -246,8 +246,8 @@ class ForgetButton(Widget): def _handle_mouse_release(self, mouse_pos: MousePos): super()._handle_mouse_release(mouse_pos) - dlg = BigConfirmationDialogV2("slide to forget", "icons_mici/settings/network/new/trash.png", red=True, - confirm_callback=self._forget_network) + dlg = BigConfirmationDialog("slide to forget", "icons_mici/settings/network/new/trash.png", red=True, + confirm_callback=self._forget_network) gui_app.push_widget(dlg) def _render(self, _): @@ -390,5 +390,3 @@ class WifiUIMici(NavScroller): super()._update_state() self._move_network_to_front(self._wifi_manager.wifi_state.ssid) - - diff --git a/selfdrive/ui/mici/tests/test_widget_leaks.py b/selfdrive/ui/mici/tests/test_widget_leaks.py index ea7af84293..c1065659f1 100755 --- a/selfdrive/ui/mici/tests/test_widget_leaks.py +++ b/selfdrive/ui/mici/tests/test_widget_leaks.py @@ -10,7 +10,7 @@ from openpilot.system.ui.widgets import Widget from openpilot.selfdrive.ui.mici.layouts.onboarding import TrainingGuide as MiciTrainingGuide, OnboardingWindow as MiciOnboardingWindow from openpilot.selfdrive.ui.mici.onroad.driver_camera_dialog import DriverCameraDialog as MiciDriverCameraDialog from openpilot.selfdrive.ui.mici.widgets.pairing_dialog import PairingDialog as MiciPairingDialog -from openpilot.selfdrive.ui.mici.widgets.dialog import BigDialog, BigConfirmationDialogV2, BigInputDialog +from openpilot.selfdrive.ui.mici.widgets.dialog import BigDialog, BigConfirmationDialog, BigInputDialog from openpilot.selfdrive.ui.mici.layouts.settings.device import MiciFccModal # tici dialogs @@ -44,7 +44,7 @@ KNOWN_LEAKS = { "openpilot.system.ui.widgets.scroller_tici.Scroller", "openpilot.system.ui.widgets.label.UnifiedLabel", "openpilot.system.ui.widgets.mici_keyboard.MiciKeyboard", - "openpilot.selfdrive.ui.mici.widgets.dialog.BigConfirmationDialogV2", + "openpilot.selfdrive.ui.mici.widgets.dialog.BigConfirmationDialog", "openpilot.system.ui.widgets.keyboard.Keyboard", "openpilot.system.ui.widgets.slider.BigSlider", "openpilot.selfdrive.ui.mici.widgets.dialog.BigInputDialog", @@ -72,7 +72,7 @@ def test_dialogs_do_not_leak(): lambda: MiciTrainingGuide(lambda: None), lambda: MiciOnboardingWindow(lambda: None), lambda: BigDialog("test", "test"), - lambda: BigConfirmationDialogV2("test", "icons_mici/settings/network/new/trash.png"), + lambda: BigConfirmationDialog("test", "icons_mici/settings/network/new/trash.png"), lambda: BigInputDialog("test"), lambda: MiciFccModal(text="test"), # tici diff --git a/selfdrive/ui/mici/widgets/dialog.py b/selfdrive/ui/mici/widgets/dialog.py index b2662d8a3b..387f4c02d0 100644 --- a/selfdrive/ui/mici/widgets/dialog.py +++ b/selfdrive/ui/mici/widgets/dialog.py @@ -63,7 +63,7 @@ class BigDialog(BigDialogBase): alignment=rl.GuiTextAlignment.TEXT_ALIGN_CENTER) -class BigConfirmationDialogV2(BigDialogBase): +class BigConfirmationDialog(BigDialogBase): def __init__(self, title: str, icon: str, red: bool = False, exit_on_confirm: bool = True, confirm_callback: Callable | None = None): diff --git a/system/ui/mici_reset.py b/system/ui/mici_reset.py index 5cbba5e8ef..92cd7e7cb8 100755 --- a/system/ui/mici_reset.py +++ b/system/ui/mici_reset.py @@ -11,7 +11,7 @@ from openpilot.system.ui.lib.application import gui_app from openpilot.system.ui.widgets.scroller import Scroller from openpilot.system.ui.widgets.nav_widget import NavWidget from openpilot.system.ui.mici_setup import GreyBigButton, FailedPage -from openpilot.selfdrive.ui.mici.widgets.dialog import BigConfirmationDialogV2 +from openpilot.selfdrive.ui.mici.widgets.dialog import BigConfirmationDialog from openpilot.selfdrive.ui.mici.widgets.button import BigCircleButton USERDATA = "/dev/disk/by-partlabel/userdata" @@ -71,18 +71,18 @@ class Reset(Scroller): self._reset_failed_page = ResetFailedPage() def show_confirm_dialog(): - dialog = BigConfirmationDialogV2("erase\ndevice", "icons_mici/settings/device/uninstall.png", red=True, - confirm_callback=self.start_reset) + dialog = BigConfirmationDialog("erase\ndevice", "icons_mici/settings/device/uninstall.png", red=True, + confirm_callback=self.start_reset) gui_app.push_widget(dialog) def show_cancel_dialog(): - dialog = BigConfirmationDialogV2("normal\nstartup", "icons_mici/settings/device/reboot.png", - exit_on_confirm=False, confirm_callback=gui_app.request_close) + dialog = BigConfirmationDialog("normal\nstartup", "icons_mici/settings/device/reboot.png", + exit_on_confirm=False, confirm_callback=gui_app.request_close) gui_app.push_widget(dialog) def show_reboot_dialog(): - dialog = BigConfirmationDialogV2("reboot\ndevice", "icons_mici/settings/device/reboot.png", - exit_on_confirm=False, confirm_callback=HARDWARE.reboot) + dialog = BigConfirmationDialog("reboot\ndevice", "icons_mici/settings/device/reboot.png", + exit_on_confirm=False, confirm_callback=HARDWARE.reboot) gui_app.push_widget(dialog) self._reset_button = BigCircleButton("icons_mici/settings/device/uninstall.png", red=True) diff --git a/system/ui/mici_setup.py b/system/ui/mici_setup.py index c2034ffda7..5e9d34c658 100755 --- a/system/ui/mici_setup.py +++ b/system/ui/mici_setup.py @@ -28,7 +28,7 @@ from openpilot.system.ui.widgets.scroller import Scroller, NavScroller, ITEM_SPA from openpilot.system.ui.widgets.slider import LargerSlider from openpilot.selfdrive.ui.mici.layouts.settings.network import WifiNetworkButton from openpilot.selfdrive.ui.mici.layouts.settings.network.wifi_ui import WifiUIMici -from openpilot.selfdrive.ui.mici.widgets.dialog import BigInputDialog, BigConfirmationDialogV2 +from openpilot.selfdrive.ui.mici.widgets.dialog import BigInputDialog, BigConfirmationDialog from openpilot.selfdrive.ui.mici.widgets.button import BigCircleButton, BigButton NetworkType = log.DeviceState.NetworkType @@ -248,8 +248,8 @@ class FailedPage(NavScroller): self.set_back_callback(retry_callback) def show_reboot_dialog(): - dialog = BigConfirmationDialogV2("slide to reboot", "icons_mici/settings/device/reboot.png", - exit_on_confirm=False, confirm_callback=HARDWARE.reboot) + dialog = BigConfirmationDialog("slide to reboot", "icons_mici/settings/device/reboot.png", + exit_on_confirm=False, confirm_callback=HARDWARE.reboot) gui_app.push_widget(dialog) reboot_button = BigCircleButton("icons_mici/settings/device/reboot.png", red=False, icon_size=(64, 70)) diff --git a/system/ui/widgets/slider.py b/system/ui/widgets/slider.py index 8f4bbfc011..57786565e2 100644 --- a/system/ui/widgets/slider.py +++ b/system/ui/widgets/slider.py @@ -13,7 +13,7 @@ class SmallSlider(Widget): CONFIRM_DELAY = 0.2 def __init__(self, title: str, confirm_callback: Callable | None = None): - # TODO: unify this with BigConfirmationDialogV2 + # TODO: unify this with BigConfirmationDialog super().__init__() self._confirm_callback = confirm_callback From af1fb2644ebb7d4a7ba825f36f8c0f46ff9ffd57 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Fri, 6 Mar 2026 18:17:26 -0800 Subject: [PATCH 051/141] mici ui: remove unused widgets (#37579) * remove small buttons! * remove those assets --- .../icons_mici/setup/medium_button_bg.png | 3 - .../setup/medium_button_pressed_bg.png | 3 - .../icons_mici/setup/reset/small_button.png | 3 - .../setup/reset/small_button_pressed.png | 3 - .../icons_mici/setup/reset/wide_button.png | 3 - .../setup/reset/wide_button_pressed.png | 3 - .../icons_mici/setup/small_red_pill.png | 3 - .../setup/small_red_pill_pressed.png | 3 - .../icons_mici/setup/smaller_button.png | 3 - .../setup/smaller_button_disabled.png | 3 - .../setup/smaller_button_pressed.png | 3 - .../assets/icons_mici/setup/widish_button.png | 3 - .../setup/widish_button_disabled.png | 3 - .../setup/widish_button_pressed.png | 3 - system/ui/widgets/button.py | 81 +------------------ 15 files changed, 1 insertion(+), 122 deletions(-) delete mode 100644 selfdrive/assets/icons_mici/setup/medium_button_bg.png delete mode 100644 selfdrive/assets/icons_mici/setup/medium_button_pressed_bg.png delete mode 100644 selfdrive/assets/icons_mici/setup/reset/small_button.png delete mode 100644 selfdrive/assets/icons_mici/setup/reset/small_button_pressed.png delete mode 100644 selfdrive/assets/icons_mici/setup/reset/wide_button.png delete mode 100644 selfdrive/assets/icons_mici/setup/reset/wide_button_pressed.png delete mode 100644 selfdrive/assets/icons_mici/setup/small_red_pill.png delete mode 100644 selfdrive/assets/icons_mici/setup/small_red_pill_pressed.png delete mode 100644 selfdrive/assets/icons_mici/setup/smaller_button.png delete mode 100644 selfdrive/assets/icons_mici/setup/smaller_button_disabled.png delete mode 100644 selfdrive/assets/icons_mici/setup/smaller_button_pressed.png delete mode 100644 selfdrive/assets/icons_mici/setup/widish_button.png delete mode 100644 selfdrive/assets/icons_mici/setup/widish_button_disabled.png delete mode 100644 selfdrive/assets/icons_mici/setup/widish_button_pressed.png diff --git a/selfdrive/assets/icons_mici/setup/medium_button_bg.png b/selfdrive/assets/icons_mici/setup/medium_button_bg.png deleted file mode 100644 index e79dc2eb58..0000000000 --- a/selfdrive/assets/icons_mici/setup/medium_button_bg.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:9e363a79dc35ca4c4e9efaa6a843d37ad219efa5299d3e538d8249affa230096 -size 7935 diff --git a/selfdrive/assets/icons_mici/setup/medium_button_pressed_bg.png b/selfdrive/assets/icons_mici/setup/medium_button_pressed_bg.png deleted file mode 100644 index e52fb0c17d..0000000000 --- a/selfdrive/assets/icons_mici/setup/medium_button_pressed_bg.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:cc6fb48520143b6fa1f060d8212e6d929917ab616ce943b5fab5a60665f00da5 -size 18225 diff --git a/selfdrive/assets/icons_mici/setup/reset/small_button.png b/selfdrive/assets/icons_mici/setup/reset/small_button.png deleted file mode 100644 index e3f58b1078..0000000000 --- a/selfdrive/assets/icons_mici/setup/reset/small_button.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:7a198f13f30b3dbc09f30d7fd8033a0bc07a0da9b010b7ca6ed2678430c9e5b4 -size 6949 diff --git a/selfdrive/assets/icons_mici/setup/reset/small_button_pressed.png b/selfdrive/assets/icons_mici/setup/reset/small_button_pressed.png deleted file mode 100644 index 5b502e00aa..0000000000 --- a/selfdrive/assets/icons_mici/setup/reset/small_button_pressed.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:75289d004709def2a2d6101a0330ec867895068ec3807aefc2a26d423d907a13 -size 13437 diff --git a/selfdrive/assets/icons_mici/setup/reset/wide_button.png b/selfdrive/assets/icons_mici/setup/reset/wide_button.png deleted file mode 100644 index 3892f6eb8c..0000000000 --- a/selfdrive/assets/icons_mici/setup/reset/wide_button.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:2452aaf59da18be1b74b475851d66e5c73c50aa49820419a288b1fdb7b42dee1 -size 9071 diff --git a/selfdrive/assets/icons_mici/setup/reset/wide_button_pressed.png b/selfdrive/assets/icons_mici/setup/reset/wide_button_pressed.png deleted file mode 100644 index 3a34af8846..0000000000 --- a/selfdrive/assets/icons_mici/setup/reset/wide_button_pressed.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:6478f7c1c5ef2013e94fc4218ab370889883c5c12231ba3e0975874cb0b6fec9 -size 21893 diff --git a/selfdrive/assets/icons_mici/setup/small_red_pill.png b/selfdrive/assets/icons_mici/setup/small_red_pill.png deleted file mode 100644 index 4a7db930a0..0000000000 --- a/selfdrive/assets/icons_mici/setup/small_red_pill.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b3a336afddad80dc91caca91d54bd29897ce491f180374edf9a5ba517cbc00e9 -size 8765 diff --git a/selfdrive/assets/icons_mici/setup/small_red_pill_pressed.png b/selfdrive/assets/icons_mici/setup/small_red_pill_pressed.png deleted file mode 100644 index a8d51960c4..0000000000 --- a/selfdrive/assets/icons_mici/setup/small_red_pill_pressed.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:8eee9f10ca80a4e6100c00c02bb46aa5f253b14b086ab9982cfa85ee94eec162 -size 22512 diff --git a/selfdrive/assets/icons_mici/setup/smaller_button.png b/selfdrive/assets/icons_mici/setup/smaller_button.png deleted file mode 100644 index 9b4851c568..0000000000 --- a/selfdrive/assets/icons_mici/setup/smaller_button.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:89ca7e6bb01dfa78300126ce828cb2a64e7a2e68e1e9152de242f57a36d0e57a -size 8604 diff --git a/selfdrive/assets/icons_mici/setup/smaller_button_disabled.png b/selfdrive/assets/icons_mici/setup/smaller_button_disabled.png deleted file mode 100644 index 6514791de7..0000000000 --- a/selfdrive/assets/icons_mici/setup/smaller_button_disabled.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b3242a411b559f1d0308f189fe0d25b81d6c7d964ca418a0c599a1bab4bffcbb -size 5341 diff --git a/selfdrive/assets/icons_mici/setup/smaller_button_pressed.png b/selfdrive/assets/icons_mici/setup/smaller_button_pressed.png deleted file mode 100644 index 64235b3a2f..0000000000 --- a/selfdrive/assets/icons_mici/setup/smaller_button_pressed.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d354651c0c8107dcc5f599777d260f53ef1901123315785ed8190466166cdce8 -size 17554 diff --git a/selfdrive/assets/icons_mici/setup/widish_button.png b/selfdrive/assets/icons_mici/setup/widish_button.png deleted file mode 100644 index 529b7c80cc..0000000000 --- a/selfdrive/assets/icons_mici/setup/widish_button.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:74fc21132b1e761ea54ce64617730c6ee79d01668244ab555b3b89870cfea181 -size 7112 diff --git a/selfdrive/assets/icons_mici/setup/widish_button_disabled.png b/selfdrive/assets/icons_mici/setup/widish_button_disabled.png deleted file mode 100644 index 5028a8cd21..0000000000 --- a/selfdrive/assets/icons_mici/setup/widish_button_disabled.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:9728423bd5e3197ef02d62e4bae415e6694aab875ca8630ffc9f188c38e18e5f -size 4141 diff --git a/selfdrive/assets/icons_mici/setup/widish_button_pressed.png b/selfdrive/assets/icons_mici/setup/widish_button_pressed.png deleted file mode 100644 index 1095d4fc23..0000000000 --- a/selfdrive/assets/icons_mici/setup/widish_button_pressed.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:0ff179f93f421edcb503ca5c22a12b37e3a2aaabc414bf90f57e20ff5255dd75 -size 15572 diff --git a/system/ui/widgets/button.py b/system/ui/widgets/button.py index a608344710..60f9e60735 100644 --- a/system/ui/widgets/button.py +++ b/system/ui/widgets/button.py @@ -5,7 +5,7 @@ import pyray as rl from openpilot.system.ui.lib.application import gui_app, FontWeight, MousePos from openpilot.system.ui.widgets import Widget -from openpilot.system.ui.widgets.label import Label, UnifiedLabel +from openpilot.system.ui.widgets.label import Label from openpilot.common.filter_simple import FirstOrderFilter @@ -223,82 +223,3 @@ class SmallCircleIconButton(Widget): icon_x = self.rect.x + (self.rect.width - self._icon_txt.width) / 2 icon_y = self.rect.y + (self.rect.height - self._icon_txt.height) / 2 rl.draw_texture(self._icon_txt, int(icon_x), int(icon_y), icon_white) - - -class SmallButton(Widget): - def __init__(self, text: str): - super().__init__() - self._click_delay = 0.075 - self._opacity_filter = FirstOrderFilter(1.0, 0.1, 1 / gui_app.target_fps) - - self._load_assets() - - self._label = UnifiedLabel(text, 36, font_weight=FontWeight.SEMI_BOLD, - text_color=rl.Color(255, 255, 255, int(255 * 0.9)), - alignment=rl.GuiTextAlignment.TEXT_ALIGN_CENTER, - alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_MIDDLE) - - self._bg_disabled_txt = None - - def _load_assets(self): - self.set_rect(rl.Rectangle(0, 0, 194, 100)) - self._bg_txt = gui_app.texture("icons_mici/setup/reset/small_button.png", 194, 100) - self._bg_pressed_txt = gui_app.texture("icons_mici/setup/reset/small_button_pressed.png", 194, 100) - - def set_text(self, text: str): - self._label.set_text(text) - - def set_opacity(self, opacity: float, smooth: bool = False): - if smooth: - self._opacity_filter.update(opacity) - else: - self._opacity_filter.x = opacity - - def _render(self, _): - if not self.enabled and self._bg_disabled_txt is not None: - rl.draw_texture(self._bg_disabled_txt, int(self.rect.x), int(self.rect.y), rl.Color(255, 255, 255, int(255 * self._opacity_filter.x))) - elif self.is_pressed: - rl.draw_texture(self._bg_pressed_txt, int(self.rect.x), int(self.rect.y), rl.Color(255, 255, 255, int(255 * self._opacity_filter.x))) - else: - rl.draw_texture(self._bg_txt, int(self.rect.x), int(self.rect.y), rl.Color(255, 255, 255, int(255 * self._opacity_filter.x))) - - opacity = 0.9 if self.enabled else 0.35 - self._label.set_color(rl.Color(255, 255, 255, int(255 * opacity * self._opacity_filter.x))) - self._label.render(self._rect) - - -class SmallRedPillButton(SmallButton): - def _load_assets(self): - self.set_rect(rl.Rectangle(0, 0, 194, 100)) - self._bg_txt = gui_app.texture("icons_mici/setup/small_red_pill.png", 194, 100) - self._bg_pressed_txt = gui_app.texture("icons_mici/setup/small_red_pill_pressed.png", 194, 100) - - -class SmallerRoundedButton(SmallButton): - def _load_assets(self): - self.set_rect(rl.Rectangle(0, 0, 150, 100)) - self._bg_txt = gui_app.texture("icons_mici/setup/smaller_button.png", 150, 100) - self._bg_disabled_txt = gui_app.texture("icons_mici/setup/smaller_button_disabled.png", 150, 100) - self._bg_pressed_txt = gui_app.texture("icons_mici/setup/smaller_button_pressed.png", 150, 100) - - -class WideRoundedButton(SmallButton): - def _load_assets(self): - self.set_rect(rl.Rectangle(0, 0, 316, 100)) - self._bg_txt = gui_app.texture("icons_mici/setup/medium_button_bg.png", 316, 100) - self._bg_pressed_txt = gui_app.texture("icons_mici/setup/medium_button_pressed_bg.png", 316, 100) - - -class WidishRoundedButton(SmallButton): - def _load_assets(self): - self.set_rect(rl.Rectangle(0, 0, 250, 100)) - self._bg_txt = gui_app.texture("icons_mici/setup/widish_button.png", 250, 100) - self._bg_pressed_txt = gui_app.texture("icons_mici/setup/widish_button_pressed.png", 250, 100) - self._bg_disabled_txt = gui_app.texture("icons_mici/setup/widish_button_disabled.png", 250, 100) - - -class FullRoundedButton(SmallButton): - def _load_assets(self): - self.set_rect(rl.Rectangle(0, 0, 520, 100)) - self._bg_txt = gui_app.texture("icons_mici/setup/reset/wide_button.png", 520, 100) - self._bg_pressed_txt = gui_app.texture("icons_mici/setup/reset/wide_button_pressed.png", 520, 100) From 60ec7dc7b623bb057bdaf2a369a759ea106ec1a5 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Fri, 6 Mar 2026 18:33:26 -0800 Subject: [PATCH 052/141] Remove unused icons --- selfdrive/assets/icons_mici/onroad/bookmark_fill.png | 3 --- selfdrive/assets/icons_mici/settings/device/language.png | 3 --- selfdrive/assets/icons_mici/setup/back_new.png | 3 --- selfdrive/assets/icons_mici/setup/scroll_down_indicator.png | 3 --- 4 files changed, 12 deletions(-) delete mode 100644 selfdrive/assets/icons_mici/onroad/bookmark_fill.png delete mode 100644 selfdrive/assets/icons_mici/settings/device/language.png delete mode 100644 selfdrive/assets/icons_mici/setup/back_new.png delete mode 100644 selfdrive/assets/icons_mici/setup/scroll_down_indicator.png diff --git a/selfdrive/assets/icons_mici/onroad/bookmark_fill.png b/selfdrive/assets/icons_mici/onroad/bookmark_fill.png deleted file mode 100644 index 531d5db1cf..0000000000 --- a/selfdrive/assets/icons_mici/onroad/bookmark_fill.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f3f57346a1cf9a66f9fd746f87bcebb23b7a403e9d6e4fd7701b126abcdd47ea -size 18476 diff --git a/selfdrive/assets/icons_mici/settings/device/language.png b/selfdrive/assets/icons_mici/settings/device/language.png deleted file mode 100644 index d2ef27de36..0000000000 --- a/selfdrive/assets/icons_mici/settings/device/language.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f646263b26de46f79cac836ef6865b0f25ddc91e386b99311723b68bd06693c9 -size 3304 diff --git a/selfdrive/assets/icons_mici/setup/back_new.png b/selfdrive/assets/icons_mici/setup/back_new.png deleted file mode 100644 index 20e7fe3b88..0000000000 --- a/selfdrive/assets/icons_mici/setup/back_new.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d29a9c295b33b3164c37a68ad77795595e6ac877a5b308d28112b0315ecd498f -size 1687 diff --git a/selfdrive/assets/icons_mici/setup/scroll_down_indicator.png b/selfdrive/assets/icons_mici/setup/scroll_down_indicator.png deleted file mode 100644 index 3cd26e5181..0000000000 --- a/selfdrive/assets/icons_mici/setup/scroll_down_indicator.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a733c425113a7f6ff5ec3dc50ef94b5481c0f2d306e33d1485be8ee6b2798532 -size 1136 From 44ec08c112fb2c3f1491287d592c5c3ec05b632a Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Fri, 6 Mar 2026 18:36:12 -0800 Subject: [PATCH 053/141] sliders: clean up (#37580) * remove small buttons! * remove those assets * clean up sliders * fix * abc * base --- .../setup/small_slider/slider_bg.png | 3 --- .../setup/small_slider/slider_red_circle.png | 3 --- .../slider_red_circle_pressed.png | 3 --- system/ui/widgets/slider.py | 23 +++++++++---------- 4 files changed, 11 insertions(+), 21 deletions(-) delete mode 100644 selfdrive/assets/icons_mici/setup/small_slider/slider_bg.png delete mode 100644 selfdrive/assets/icons_mici/setup/small_slider/slider_red_circle.png delete mode 100644 selfdrive/assets/icons_mici/setup/small_slider/slider_red_circle_pressed.png diff --git a/selfdrive/assets/icons_mici/setup/small_slider/slider_bg.png b/selfdrive/assets/icons_mici/setup/small_slider/slider_bg.png deleted file mode 100644 index 43c10a54ad..0000000000 --- a/selfdrive/assets/icons_mici/setup/small_slider/slider_bg.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:94a86fac6ffe8a8179812cf55350ab9ca6935f36244c6f679c1cf521a842316b -size 5723 diff --git a/selfdrive/assets/icons_mici/setup/small_slider/slider_red_circle.png b/selfdrive/assets/icons_mici/setup/small_slider/slider_red_circle.png deleted file mode 100644 index 541433be76..0000000000 --- a/selfdrive/assets/icons_mici/setup/small_slider/slider_red_circle.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:6ccb5f2298389ae36df87de84d85440ee5a82c50e803c9bd362c9b89ea45aa69 -size 6611 diff --git a/selfdrive/assets/icons_mici/setup/small_slider/slider_red_circle_pressed.png b/selfdrive/assets/icons_mici/setup/small_slider/slider_red_circle_pressed.png deleted file mode 100644 index eea6eded86..0000000000 --- a/selfdrive/assets/icons_mici/setup/small_slider/slider_red_circle_pressed.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a804da77b268f0a625f93949642ae74cdfe5b5caa5baea1c52c4605ae25c80e4 -size 12916 diff --git a/system/ui/widgets/slider.py b/system/ui/widgets/slider.py index 57786565e2..203c9eb4a0 100644 --- a/system/ui/widgets/slider.py +++ b/system/ui/widgets/slider.py @@ -1,3 +1,4 @@ +import abc from collections.abc import Callable import pyray as rl @@ -8,17 +9,19 @@ from openpilot.system.ui.widgets.label import UnifiedLabel from openpilot.common.filter_simple import FirstOrderFilter -class SmallSlider(Widget): +class SliderBase(Widget, abc.ABC): HORIZONTAL_PADDING = 8 CONFIRM_DELAY = 0.2 + _bg_txt: rl.Texture + _circle_bg_txt: rl.Texture + _circle_bg_pressed_txt: rl.Texture + _circle_arrow_txt: rl.Texture + def __init__(self, title: str, confirm_callback: Callable | None = None): - # TODO: unify this with BigConfirmationDialog super().__init__() self._confirm_callback = confirm_callback - self._font = gui_app.font(FontWeight.DISPLAY) - self._load_assets() self._drag_threshold = -self._rect.width // 2 @@ -37,13 +40,9 @@ class SmallSlider(Widget): alignment=rl.GuiTextAlignment.TEXT_ALIGN_RIGHT, alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_MIDDLE, line_height=0.9) + @abc.abstractmethod def _load_assets(self): - self.set_rect(rl.Rectangle(0, 0, 316 + self.HORIZONTAL_PADDING * 2, 100)) - - self._bg_txt = gui_app.texture("icons_mici/setup/small_slider/slider_bg.png", 316, 100) - self._circle_bg_txt = gui_app.texture("icons_mici/setup/small_slider/slider_red_circle.png", 100, 100) - self._circle_bg_pressed_txt = gui_app.texture("icons_mici/setup/small_slider/slider_red_circle_pressed.png", 100, 100) - self._circle_arrow_txt = gui_app.texture("icons_mici/setup/small_slider/slider_arrow.png", 37, 32) + ... @property def confirmed(self) -> bool: @@ -149,7 +148,7 @@ class SmallSlider(Widget): rl.draw_texture_ex(self._circle_arrow_txt, rl.Vector2(arrow_x, arrow_y), 0.0, 1.0, white) -class LargerSlider(SmallSlider): +class LargerSlider(SliderBase): def __init__(self, title: str, confirm_callback: Callable | None = None, green: bool = True): self._green = green super().__init__(title, confirm_callback=confirm_callback) @@ -164,7 +163,7 @@ class LargerSlider(SmallSlider): self._circle_arrow_txt = gui_app.texture("icons_mici/setup/small_slider/slider_arrow.png", 64, 55) -class BigSlider(SmallSlider): +class BigSlider(SliderBase): def __init__(self, title: str, icon: rl.Texture, confirm_callback: Callable | None = None): self._icon = icon super().__init__(title, confirm_callback=confirm_callback) From 5e2a5b53553dab1fbacf964aac695c9c4c7e2df0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20R=C4=85czy?= Date: Fri, 6 Mar 2026 19:00:15 -0800 Subject: [PATCH 054/141] lagd: smooth lat accel + min lat accel range (#37424) * Smooth * Min lat accel range * Make the moving average masked * Bring back the range * Update test * Smooth desired signal too * Diff * Gaussian * Fix fmt * Remove newline --- selfdrive/locationd/lagd.py | 20 +++++++++++++++++++- selfdrive/locationd/test/test_lagd.py | 4 ++-- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/selfdrive/locationd/lagd.py b/selfdrive/locationd/lagd.py index 361bb79cce..8bbee6604b 100755 --- a/selfdrive/locationd/lagd.py +++ b/selfdrive/locationd/lagd.py @@ -28,11 +28,26 @@ MIN_LAG = 0.15 MAX_LAG_STD = 0.1 MAX_LAT_ACCEL = 2.0 MAX_LAT_ACCEL_DIFF = 0.6 +MIN_LAT_ACCEL_RANGE = 0.5 MIN_CONFIDENCE = 0.7 CORR_BORDER_OFFSET = 5 LAG_CANDIDATE_CORR_THRESHOLD = 0.9 +SMOOTH_K = 5 +SMOOTH_SIGMA = 1.0 +def masked_symmetric_moving_average(x: np.ndarray, mask: np.ndarray, k: int, sigma: float) -> np.ndarray: + assert k >= 1 and k % 2 == 1, "k must be positive and odd" + pad = k // 2 + i = np.arange(k) - pad + w = np.exp(-0.5 * (i / sigma) ** 2) + w /= w.sum() + xp = np.pad(x * mask, pad, mode="edge") + mp = np.pad(mask, pad, mode="edge") + num = np.convolve(xp, w, mode="valid") + den = np.convolve(mp, w, mode="valid") + return np.divide(num, den, out=np.full_like(num, np.nan, dtype=np.float64), where=den != 0) + def masked_normalized_cross_correlation(expected_sig: np.ndarray, actual_sig: np.ndarray, mask: np.ndarray, n: int): """ References: @@ -294,11 +309,14 @@ class LateralLagEstimator: times, desired, actual, okay = self.points.get() # check if there are any new valid data points since the last update - is_valid = self.points_valid() + is_valid = self.points_valid() and (actual.max() - actual.min() >= MIN_LAT_ACCEL_RANGE) if self.last_estimate_t != 0 and times[0] <= self.last_estimate_t: new_values_start_idx = next(-i for i, t in enumerate(reversed(times)) if t <= self.last_estimate_t) is_valid = is_valid and not (new_values_start_idx == 0 or not np.any(okay[new_values_start_idx:])) + desired = masked_symmetric_moving_average(desired, okay, SMOOTH_K, SMOOTH_SIGMA) + actual = masked_symmetric_moving_average(actual, okay, SMOOTH_K, SMOOTH_SIGMA) + delay, corr, confidence = self.actuator_delay(desired, actual, okay, self.dt, MIN_LAG, MAX_LAG) if corr < self.min_ncc or confidence < self.min_confidence or not is_valid: return diff --git a/selfdrive/locationd/test/test_lagd.py b/selfdrive/locationd/test/test_lagd.py index 4728413d9d..6249e6b04b 100644 --- a/selfdrive/locationd/test/test_lagd.py +++ b/selfdrive/locationd/test/test_lagd.py @@ -19,8 +19,8 @@ DT = 0.05 def process_messages(estimator, lag_frames, n_frames, vego=20.0, rejection_threshold=0.0): for i in range(n_frames): t = i * estimator.dt - desired_la = np.cos(10 * t) * 0.1 - actual_la = np.cos(10 * (t - lag_frames * estimator.dt)) * 0.1 + desired_la = np.cos(10 * t) * 0.3 + actual_la = np.cos(10 * (t - lag_frames * estimator.dt)) * 0.3 # if sample is masked out, set it to desired value (no lag) rejected = random.uniform(0, 1) < rejection_threshold From 4cc68f57cf5387842790da633d8591726b3e0f0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20R=C4=85czy?= Date: Fri, 6 Mar 2026 20:17:26 -0800 Subject: [PATCH 055/141] lagd: change lag candidate threshold range (#37581) * Use extended_roi_ncc instead of roi_ncc * It doesnt make sense to use non-positive lags in thresholding --- selfdrive/locationd/lagd.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/selfdrive/locationd/lagd.py b/selfdrive/locationd/lagd.py index 8bbee6604b..6232404c30 100755 --- a/selfdrive/locationd/lagd.py +++ b/selfdrive/locationd/lagd.py @@ -328,16 +328,16 @@ class LateralLagEstimator: def actuator_delay(expected_sig: np.ndarray, actual_sig: np.ndarray, mask: np.ndarray, dt: float, min_lag: float, max_lag: float) -> tuple[float, float, float]: assert len(expected_sig) == len(actual_sig) - min_lag_samples, max_lag_samples = int(round(min_lag / dt)), int(round(max_lag / dt)) - padded_size = fft_next_good_size(len(expected_sig) + max_lag_samples) + min_lag_samples, max_lag_samples, one_sec_samples = int(round(min_lag / dt)), int(round(max_lag / dt)), int(round(1.0 / dt)) + padded_size = fft_next_good_size(len(expected_sig) + max(max_lag_samples, one_sec_samples)) ncc = masked_normalized_cross_correlation(expected_sig, actual_sig, mask, padded_size) - # only consider lags from min_lag to max_lag - roi = np.s_[len(expected_sig) - 1 + min_lag_samples: len(expected_sig) - 1 + max_lag_samples] - extended_roi = np.s_[roi.start - CORR_BORDER_OFFSET: roi.stop + CORR_BORDER_OFFSET] - roi_ncc = ncc[roi] - extended_roi_ncc = ncc[extended_roi] + # only consider lags from ranges: + roi = np.s_[len(expected_sig) - 1 + min_lag_samples: len(expected_sig) - 1 + max_lag_samples] # min_lag - max_lag range + threshold_roi = np.s_[len(expected_sig) - 1: len(expected_sig) - 1 + one_sec_samples] # 0 - 1 second range + confidence_roi = np.s_[threshold_roi.start - CORR_BORDER_OFFSET: threshold_roi.stop + CORR_BORDER_OFFSET] # threshold range +/- border + roi_ncc, confidence_roi_ncc, threshold_roi_ncc = ncc[roi], ncc[confidence_roi], ncc[threshold_roi] max_corr_index = np.argmax(roi_ncc) corr = roi_ncc[max_corr_index] @@ -345,8 +345,8 @@ class LateralLagEstimator: # to estimate lag confidence, gather all high-correlation candidates and see how spread they are # if e.g. 0.8 and 0.4 are both viable, this is an ambiguous case - ncc_thresh = (roi_ncc.max() - roi_ncc.min()) * LAG_CANDIDATE_CORR_THRESHOLD + roi_ncc.min() - good_lag_candidate_mask = extended_roi_ncc >= ncc_thresh + ncc_thresh = (threshold_roi_ncc.max() - threshold_roi_ncc.min()) * LAG_CANDIDATE_CORR_THRESHOLD + threshold_roi_ncc.min() + good_lag_candidate_mask = confidence_roi_ncc >= ncc_thresh good_lag_candidate_edges = np.diff(good_lag_candidate_mask.astype(int), prepend=0, append=0) starts, ends = np.where(good_lag_candidate_edges == 1)[0], np.where(good_lag_candidate_edges == -1)[0] - 1 run_idx = np.searchsorted(starts, max_corr_index + CORR_BORDER_OFFSET, side='right') - 1 From c01719bb99811a6fe3a4c17227662fd0cc96c952 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Fri, 6 Mar 2026 23:38:38 -0500 Subject: [PATCH 056/141] ui: gate Onroad Brightness Delay on readiness (#1761) ui: gate Onroad Brightness Timer on readiness --- selfdrive/ui/mici/onroad/alert_renderer.py | 2 +- selfdrive/ui/onroad/alert_renderer.py | 2 +- selfdrive/ui/sunnypilot/ui_state.py | 7 +++++-- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/selfdrive/ui/mici/onroad/alert_renderer.py b/selfdrive/ui/mici/onroad/alert_renderer.py index 68b2aef7a5..a9d4f3a48b 100644 --- a/selfdrive/ui/mici/onroad/alert_renderer.py +++ b/selfdrive/ui/mici/onroad/alert_renderer.py @@ -231,7 +231,7 @@ class AlertRenderer(Widget, SpeedLimitAlertRenderer): self._alpha_filter.update(0 if alert is None else 1) if gui_app.sunnypilot_ui(): - ui_state.onroad_brightness_handle_alerts(ui_state.started, alert) + ui_state.onroad_brightness_handle_alerts(ui_state, alert) if alert is None: # If still animating out, keep the previous alert diff --git a/selfdrive/ui/onroad/alert_renderer.py b/selfdrive/ui/onroad/alert_renderer.py index 2c21b4006e..6e79d23253 100644 --- a/selfdrive/ui/onroad/alert_renderer.py +++ b/selfdrive/ui/onroad/alert_renderer.py @@ -118,7 +118,7 @@ class AlertRenderer(Widget): alert = self.get_alert(ui_state.sm) if gui_app.sunnypilot_ui(): - ui_state.onroad_brightness_handle_alerts(ui_state.started, alert) + ui_state.onroad_brightness_handle_alerts(ui_state, alert) if not alert: return diff --git a/selfdrive/ui/sunnypilot/ui_state.py b/selfdrive/ui/sunnypilot/ui_state.py index 050df6d26d..7766c353ad 100644 --- a/selfdrive/ui/sunnypilot/ui_state.py +++ b/selfdrive/ui/sunnypilot/ui_state.py @@ -49,8 +49,11 @@ class UIStateSP: else: self.sunnylink_state.stop() - def onroad_brightness_handle_alerts(self, started: bool, alert): - has_alert = started and self.onroad_brightness != OnroadBrightness.AUTO and alert is not None + def onroad_brightness_handle_alerts(self, _ui_state, alert): + if _ui_state.sm.recv_frame["carState"] < _ui_state.started_frame: + return + + has_alert = _ui_state.started and self.onroad_brightness != OnroadBrightness.AUTO and alert is not None self.update_onroad_brightness(has_alert) if has_alert: From 2f1a58f9912c4207360da933c5765c5f2813592f Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Fri, 6 Mar 2026 20:45:39 -0800 Subject: [PATCH 057/141] mici setup: connect to continue (#37583) * connect to continue * fix --- system/ui/mici_setup.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/system/ui/mici_setup.py b/system/ui/mici_setup.py index 5e9d34c658..77d0fc56e7 100755 --- a/system/ui/mici_setup.py +++ b/system/ui/mici_setup.py @@ -376,7 +376,7 @@ class NetworkSetupPageBase(Scroller): # trigger grow when wifi button in view self._pending_wifi_grow_animation = True - self._waiting_button = BigPillButton("waiting for\ninternet...", disabled_background=True) + self._waiting_button = BigPillButton("connect to\ncontinue", disabled_background=True) self._waiting_button.set_click_callback(on_waiting_click) self._continue_button = BigPillButton("install openpilot", green=True) self._continue_button.set_click_callback(lambda: continue_callback(self._custom_software)) @@ -423,17 +423,19 @@ class NetworkSetupPageBase(Scroller): # Check network state before processing callbacks so forgetting flag # is still set on the frame the forgotten callback fires has_internet = self._has_internet + wifi_connected = self._wifi_manager.wifi_state.status == ConnectStatus.CONNECTED + self._continue_button.set_visible(has_internet) self._waiting_button.set_visible(not has_internet) # TODO: fire show/hide events on visibility changes if not has_internet: self._pending_continue_grow_animation = False + self._waiting_button.set_text("waiting for\ninternet..." if wifi_connected else "connect to\ncontinue") self._wifi_manager.process_callbacks() # Dismiss WiFi UI and scroll on WiFi connect or internet gain - wifi_connected = self._wifi_manager.wifi_state.status == ConnectStatus.CONNECTED if (has_internet and not self._prev_has_internet) or (wifi_connected and not self._prev_wifi_connected): # TODO: cancel if connect is transient self._pending_has_internet_scroll = rl.get_time() From fd98db72abad00fbe317f478e9adbcf3c434f570 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Fri, 6 Mar 2026 21:36:43 -0800 Subject: [PATCH 058/141] ui: make confirm callback required for confirmation dialog (#37585) * always required! * reoreder * reorder again * make required so better order * not clear better --- selfdrive/ui/mici/layouts/onboarding.py | 12 +++++------- selfdrive/ui/mici/layouts/settings/device.py | 5 ++--- .../ui/mici/layouts/settings/network/wifi_ui.py | 3 +-- selfdrive/ui/mici/tests/test_widget_leaks.py | 2 +- selfdrive/ui/mici/widgets/dialog.py | 5 ++--- system/ui/mici_reset.py | 9 +++------ system/ui/mici_setup.py | 3 +-- 7 files changed, 15 insertions(+), 24 deletions(-) diff --git a/selfdrive/ui/mici/layouts/onboarding.py b/selfdrive/ui/mici/layouts/onboarding.py index 3a3625d714..3b9b36f701 100644 --- a/selfdrive/ui/mici/layouts/onboarding.py +++ b/selfdrive/ui/mici/layouts/onboarding.py @@ -220,15 +220,14 @@ class TrainingGuideRecordFront(NavScroller): ui_state.params.put_bool_nonblocking("RecordFront", True) continue_callback() - gui_app.push_widget(BigConfirmationDialog("allow data uploading", "icons_mici/setup/driver_monitoring/dm_check.png", exit_on_confirm=False, - confirm_callback=on_accept)) + gui_app.push_widget(BigConfirmationDialog("allow data uploading", "icons_mici/setup/driver_monitoring/dm_check.png", on_accept, exit_on_confirm=False)) def show_decline_dialog(): def on_decline(): ui_state.params.put_bool_nonblocking("RecordFront", False) continue_callback() - gui_app.push_widget(BigConfirmationDialog("no, don't upload", "icons_mici/setup/cancel.png", exit_on_confirm=False, confirm_callback=on_decline)) + gui_app.push_widget(BigConfirmationDialog("no, don't upload", "icons_mici/setup/cancel.png", on_decline, exit_on_confirm=False)) self._accept_button = BigCircleButton("icons_mici/setup/driver_monitoring/dm_check.png") self._accept_button.set_click_callback(show_accept_dialog) @@ -324,12 +323,11 @@ class TermsPage(Scroller): super().__init__() def show_accept_dialog(): - gui_app.push_widget(BigConfirmationDialog("accept\nterms", "icons_mici/setup/driver_monitoring/dm_check.png", - confirm_callback=on_accept)) + gui_app.push_widget(BigConfirmationDialog("accept\nterms", "icons_mici/setup/driver_monitoring/dm_check.png", on_accept)) def show_decline_dialog(): - gui_app.push_widget(BigConfirmationDialog("decline &\nuninstall", "icons_mici/setup/cancel.png", - red=True, exit_on_confirm=False, confirm_callback=on_decline)) + gui_app.push_widget(BigConfirmationDialog("decline &\nuninstall", "icons_mici/setup/cancel.png", on_decline, + red=True, exit_on_confirm=False)) self._accept_button = BigCircleButton("icons_mici/setup/driver_monitoring/dm_check.png") self._accept_button.set_click_callback(show_accept_dialog) diff --git a/selfdrive/ui/mici/layouts/settings/device.py b/selfdrive/ui/mici/layouts/settings/device.py index 0bc1ac2958..bee8cbce5f 100644 --- a/selfdrive/ui/mici/layouts/settings/device.py +++ b/selfdrive/ui/mici/layouts/settings/device.py @@ -85,9 +85,8 @@ def _engaged_confirmation_callback(callback: Callable, action_text: str): # TODO: check icon = "icons_mici/settings/comma_icon.png" - dlg: BigConfirmationDialog | BigDialog = BigConfirmationDialog(f"slide to\n{action_text.lower()}", icon, red=red, - exit_on_confirm=action_text == "reset", - confirm_callback=confirm_callback) + dlg: BigConfirmationDialog | BigDialog = BigConfirmationDialog(f"slide to\n{action_text.lower()}", icon, confirm_callback, + red=red, exit_on_confirm=action_text == "reset") gui_app.push_widget(dlg) else: dlg = BigDialog(f"Disengage to {action_text}", "") diff --git a/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py b/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py index cda3cb365a..0f5ac977b9 100644 --- a/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py +++ b/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py @@ -246,8 +246,7 @@ class ForgetButton(Widget): def _handle_mouse_release(self, mouse_pos: MousePos): super()._handle_mouse_release(mouse_pos) - dlg = BigConfirmationDialog("slide to forget", "icons_mici/settings/network/new/trash.png", red=True, - confirm_callback=self._forget_network) + dlg = BigConfirmationDialog("slide to forget", "icons_mici/settings/network/new/trash.png", self._forget_network, red=True) gui_app.push_widget(dlg) def _render(self, _): diff --git a/selfdrive/ui/mici/tests/test_widget_leaks.py b/selfdrive/ui/mici/tests/test_widget_leaks.py index c1065659f1..43b2cf79f6 100755 --- a/selfdrive/ui/mici/tests/test_widget_leaks.py +++ b/selfdrive/ui/mici/tests/test_widget_leaks.py @@ -72,7 +72,7 @@ def test_dialogs_do_not_leak(): lambda: MiciTrainingGuide(lambda: None), lambda: MiciOnboardingWindow(lambda: None), lambda: BigDialog("test", "test"), - lambda: BigConfirmationDialog("test", "icons_mici/settings/network/new/trash.png"), + lambda: BigConfirmationDialog("test", "icons_mici/settings/network/new/trash.png", lambda: None), lambda: BigInputDialog("test"), lambda: MiciFccModal(text="test"), # tici diff --git a/selfdrive/ui/mici/widgets/dialog.py b/selfdrive/ui/mici/widgets/dialog.py index 387f4c02d0..fc4e3edabe 100644 --- a/selfdrive/ui/mici/widgets/dialog.py +++ b/selfdrive/ui/mici/widgets/dialog.py @@ -64,9 +64,8 @@ class BigDialog(BigDialogBase): class BigConfirmationDialog(BigDialogBase): - def __init__(self, title: str, icon: str, red: bool = False, - exit_on_confirm: bool = True, - confirm_callback: Callable | None = None): + def __init__(self, title: str, icon: str, confirm_callback: Callable[[], None], + exit_on_confirm: bool = True, red: bool = False): super().__init__() self._confirm_callback = confirm_callback self._exit_on_confirm = exit_on_confirm diff --git a/system/ui/mici_reset.py b/system/ui/mici_reset.py index 92cd7e7cb8..b56e38941a 100755 --- a/system/ui/mici_reset.py +++ b/system/ui/mici_reset.py @@ -71,18 +71,15 @@ class Reset(Scroller): self._reset_failed_page = ResetFailedPage() def show_confirm_dialog(): - dialog = BigConfirmationDialog("erase\ndevice", "icons_mici/settings/device/uninstall.png", red=True, - confirm_callback=self.start_reset) + dialog = BigConfirmationDialog("erase\ndevice", "icons_mici/settings/device/uninstall.png", self.start_reset, red=True) gui_app.push_widget(dialog) def show_cancel_dialog(): - dialog = BigConfirmationDialog("normal\nstartup", "icons_mici/settings/device/reboot.png", - exit_on_confirm=False, confirm_callback=gui_app.request_close) + dialog = BigConfirmationDialog("normal\nstartup", "icons_mici/settings/device/reboot.png", gui_app.request_close, exit_on_confirm=False) gui_app.push_widget(dialog) def show_reboot_dialog(): - dialog = BigConfirmationDialog("reboot\ndevice", "icons_mici/settings/device/reboot.png", - exit_on_confirm=False, confirm_callback=HARDWARE.reboot) + dialog = BigConfirmationDialog("reboot\ndevice", "icons_mici/settings/device/reboot.png", HARDWARE.reboot, exit_on_confirm=False) gui_app.push_widget(dialog) self._reset_button = BigCircleButton("icons_mici/settings/device/uninstall.png", red=True) diff --git a/system/ui/mici_setup.py b/system/ui/mici_setup.py index 77d0fc56e7..31edbe86e8 100755 --- a/system/ui/mici_setup.py +++ b/system/ui/mici_setup.py @@ -248,8 +248,7 @@ class FailedPage(NavScroller): self.set_back_callback(retry_callback) def show_reboot_dialog(): - dialog = BigConfirmationDialog("slide to reboot", "icons_mici/settings/device/reboot.png", - exit_on_confirm=False, confirm_callback=HARDWARE.reboot) + dialog = BigConfirmationDialog("slide to reboot", "icons_mici/settings/device/reboot.png", HARDWARE.reboot, exit_on_confirm=False) gui_app.push_widget(dialog) reboot_button = BigCircleButton("icons_mici/settings/device/reboot.png", red=False, icon_size=(64, 70)) From 5e1a576f3d88d76df6230eec931073419a2fe870 Mon Sep 17 00:00:00 2001 From: Lukas Heintz <61192133+lukasloetkolben@users.noreply.github.com> Date: Sat, 7 Mar 2026 07:13:16 +0100 Subject: [PATCH 059/141] cabana: exclude SocketCAN on macOS (#37553) fix cabana on macos Co-authored-by: Adeeb Shihadeh --- tools/cabana/SConscript | 15 +++++++++------ tools/cabana/cabana.cc | 6 ++++++ tools/cabana/streamselector.cc | 5 ++++- 3 files changed, 19 insertions(+), 7 deletions(-) diff --git a/tools/cabana/SConscript b/tools/cabana/SConscript index 098a6351b7..1f7ba3eaef 100644 --- a/tools/cabana/SConscript +++ b/tools/cabana/SConscript @@ -88,12 +88,15 @@ assets_src = "assets/assets.qrc" cabana_env.Command(assets, assets_src, f"rcc $SOURCES -o $TARGET") cabana_env.Depends(assets, Glob('/assets/*', exclude=[assets, assets_src, "assets/assets.o"])) -cabana_lib = cabana_env.Library("cabana_lib", ['mainwin.cc', 'streams/socketcanstream.cc', 'streams/pandastream.cc', 'streams/devicestream.cc', 'streams/livestream.cc', 'streams/abstractstream.cc', 'streams/replaystream.cc', 'binaryview.cc', 'historylog.cc', 'videowidget.cc', 'signalview.cc', - 'streams/routes.cc', 'dbc/dbc.cc', 'dbc/dbcfile.cc', 'dbc/dbcmanager.cc', - 'utils/export.cc', 'utils/util.cc', 'utils/elidedlabel.cc', - 'chart/chartswidget.cc', 'chart/chart.cc', 'chart/signalselector.cc', 'chart/tiplabel.cc', 'chart/sparkline.cc', - 'commands.cc', 'messageswidget.cc', 'streamselector.cc', 'settings.cc', 'panda.cc', - 'cameraview.cc', 'detailwidget.cc', 'tools/findsimilarbits.cc', 'tools/findsignal.cc', 'tools/routeinfo.cc'], LIBS=cabana_libs, FRAMEWORKS=base_frameworks) +cabana_srcs = ['mainwin.cc', 'streams/pandastream.cc', 'streams/devicestream.cc', 'streams/livestream.cc', 'streams/abstractstream.cc', 'streams/replaystream.cc', 'binaryview.cc', 'historylog.cc', 'videowidget.cc', 'signalview.cc', + 'streams/routes.cc', 'dbc/dbc.cc', 'dbc/dbcfile.cc', 'dbc/dbcmanager.cc', + 'utils/export.cc', 'utils/util.cc', 'utils/elidedlabel.cc', + 'chart/chartswidget.cc', 'chart/chart.cc', 'chart/signalselector.cc', 'chart/tiplabel.cc', 'chart/sparkline.cc', + 'commands.cc', 'messageswidget.cc', 'streamselector.cc', 'settings.cc', 'panda.cc', + 'cameraview.cc', 'detailwidget.cc', 'tools/findsimilarbits.cc', 'tools/findsignal.cc', 'tools/routeinfo.cc'] +if arch != "Darwin": + cabana_srcs += ['streams/socketcanstream.cc'] +cabana_lib = cabana_env.Library("cabana_lib", cabana_srcs, LIBS=cabana_libs, FRAMEWORKS=base_frameworks) cabana_env.Program('_cabana', ['cabana.cc', cabana_lib, assets], LIBS=cabana_libs, FRAMEWORKS=base_frameworks) if GetOption('extras'): diff --git a/tools/cabana/cabana.cc b/tools/cabana/cabana.cc index 9e4cfc4a51..db26b4067a 100644 --- a/tools/cabana/cabana.cc +++ b/tools/cabana/cabana.cc @@ -5,7 +5,9 @@ #include "tools/cabana/streams/devicestream.h" #include "tools/cabana/streams/pandastream.h" #include "tools/cabana/streams/replaystream.h" +#ifdef __linux__ #include "tools/cabana/streams/socketcanstream.h" +#endif int main(int argc, char *argv[]) { QCoreApplication::setApplicationName("Cabana"); @@ -29,9 +31,11 @@ int main(int argc, char *argv[]) { cmd_parser.addOption({"msgq", "read can messages from the msgq"}); cmd_parser.addOption({"panda", "read can messages from panda"}); cmd_parser.addOption({"panda-serial", "read can messages from panda with given serial", "panda-serial"}); +#ifdef __linux__ if (SocketCanStream::available()) { cmd_parser.addOption({"socketcan", "read can messages from given SocketCAN device", "socketcan"}); } +#endif cmd_parser.addOption({"zmq", "read can messages from zmq at the specified ip-address", "ip-address"}); cmd_parser.addOption({"data_dir", "local directory with routes", "data_dir"}); cmd_parser.addOption({"no-vipc", "do not output video"}); @@ -51,8 +55,10 @@ int main(int argc, char *argv[]) { qWarning() << e.what(); return 0; } +#ifdef __linux__ } else if (SocketCanStream::available() && cmd_parser.isSet("socketcan")) { stream = new SocketCanStream(&app, {.device = cmd_parser.value("socketcan").toStdString()}); +#endif } else { uint32_t replay_flags = REPLAY_FLAG_NONE; if (cmd_parser.isSet("ecam")) replay_flags |= REPLAY_FLAG_ECAM; diff --git a/tools/cabana/streamselector.cc b/tools/cabana/streamselector.cc index efd00d3985..4ad552d4b4 100644 --- a/tools/cabana/streamselector.cc +++ b/tools/cabana/streamselector.cc @@ -4,11 +4,12 @@ #include #include -#include "streams/socketcanstream.h" #include "tools/cabana/streams/devicestream.h" #include "tools/cabana/streams/pandastream.h" #include "tools/cabana/streams/replaystream.h" +#ifdef __linux__ #include "tools/cabana/streams/socketcanstream.h" +#endif StreamSelector::StreamSelector(QWidget *parent) : QDialog(parent) { setWindowTitle(tr("Open stream")); @@ -35,9 +36,11 @@ StreamSelector::StreamSelector(QWidget *parent) : QDialog(parent) { addStreamWidget(new OpenReplayWidget, tr("&Replay")); addStreamWidget(new OpenPandaWidget, tr("&Panda")); +#ifdef __linux__ if (SocketCanStream::available()) { addStreamWidget(new OpenSocketCanWidget, tr("&SocketCAN")); } +#endif addStreamWidget(new OpenDeviceWidget, tr("&Device")); QObject::connect(btn_box, &QDialogButtonBox::rejected, this, &QDialog::reject); From 793f8fee328fc4ce2ef4b1685af9ea0d9755bd3d Mon Sep 17 00:00:00 2001 From: Utkarsh Gill <46515280+utkarshgill@users.noreply.github.com> Date: Sat, 7 Mar 2026 11:44:31 +0530 Subject: [PATCH 060/141] fix(sim): use getRamImageAs for correct channel order (#37528) getRamImage() returns panda3d's internal BGRA format. on macOS this produces swapped red/blue channels in the sim camera feed. getRamImageAs("RGBA") requests explicit RGBA reordering from panda3d, correct on all platforms. no-op where internal format is already RGBA. ref: https://docs.panda3d.org/1.10/python/reference/panda3d.core.Texture#panda3d.core.Texture.getRamImageAs fixes #37526 --- tools/sim/bridge/metadrive/metadrive_common.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tools/sim/bridge/metadrive/metadrive_common.py b/tools/sim/bridge/metadrive/metadrive_common.py index 42a7eb60dd..079d933d1a 100644 --- a/tools/sim/bridge/metadrive/metadrive_common.py +++ b/tools/sim/bridge/metadrive/metadrive_common.py @@ -13,9 +13,9 @@ class CopyRamRGBCamera(RGBCamera): def get_rgb_array_cpu(self): origin_img = self.cpu_texture - img = np.frombuffer(origin_img.getRamImage().getData(), dtype=np.uint8) - img = img.reshape((origin_img.getYSize(), origin_img.getXSize(), -1)) - img = img[:,:,:3] # RGBA to RGB + img = np.frombuffer(origin_img.getRamImageAs("RGBA").getData(), dtype=np.uint8) + img = img.reshape((origin_img.getYSize(), origin_img.getXSize(), 4)) + img = img[:, :, :3] # img = np.swapaxes(img, 1, 0) img = img[::-1] # Flip on vertical axis return img From a9d5c9e23ad4584acac4b702ee7311f18d2d8408 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Sat, 7 Mar 2026 01:36:24 -0500 Subject: [PATCH 061/141] ui: add new timer options for Onroad Brightness Delay (#1760) * ui: add new timer options for OnOnroad Brightness Delay * migrate * in future pr * Revert "in future pr" This reverts commit ca9940f809d8ce8541e4a044c9cc367c7af1346c. * consolidate * update * gate * fix --- common/params_keys.h | 1 + .../ui/sunnypilot/layouts/settings/display.py | 11 ++++++---- sunnypilot/sunnylink/params_metadata.json | 22 ++++++++++++++++++- .../sunnylink/tools/update_params_metadata.py | 22 +++++++++++++++++++ sunnypilot/system/params_migration.py | 22 ++++++++++++++++++- system/manager/manager.py | 3 ++- 6 files changed, 74 insertions(+), 7 deletions(-) diff --git a/common/params_keys.h b/common/params_keys.h index 3164fe5365..a247798e30 100644 --- a/common/params_keys.h +++ b/common/params_keys.h @@ -172,6 +172,7 @@ inline static std::unordered_map keys = { {"OnroadScreenOffBrightness", {PERSISTENT | BACKUP, INT, "0"}}, {"OnroadScreenOffBrightnessMigrated", {PERSISTENT | BACKUP, STRING, "0.0"}}, {"OnroadScreenOffTimer", {PERSISTENT | BACKUP, INT, "15"}}, + {"OnroadScreenOffTimerMigrated", {PERSISTENT | BACKUP, STRING, "0.0"}}, {"OnroadUploads", {PERSISTENT | BACKUP, BOOL, "1"}}, {"QuickBootToggle", {PERSISTENT | BACKUP, BOOL, "0"}}, {"QuietMode", {PERSISTENT | BACKUP, BOOL, "0"}}, diff --git a/selfdrive/ui/sunnypilot/layouts/settings/display.py b/selfdrive/ui/sunnypilot/layouts/settings/display.py index d44118d8f4..acd7b52dcc 100644 --- a/selfdrive/ui/sunnypilot/layouts/settings/display.py +++ b/selfdrive/ui/sunnypilot/layouts/settings/display.py @@ -12,8 +12,7 @@ from openpilot.system.ui.widgets import Widget from openpilot.system.ui.lib.multilang import tr from openpilot.system.ui.widgets.scroller_tici import Scroller from openpilot.system.ui.sunnypilot.widgets.list_view import option_item_sp, ToggleActionSP - -ONROAD_BRIGHTNESS_TIMER_VALUES = {0: 15, 1: 30, **{i: (i - 1) * 60 for i in range(2, 12)}} +from openpilot.sunnypilot.system.params_migration import ONROAD_BRIGHTNESS_TIMER_VALUES class OnroadBrightness(IntEnum): @@ -46,7 +45,7 @@ class DisplayLayout(Widget): title=lambda: tr("Onroad Brightness Delay"), description="", min_value=0, - max_value=11, + max_value=15, value_change_step=1, value_map=ONROAD_BRIGHTNESS_TIMER_VALUES, label_callback=lambda value: f"{value} s" if value < 60 else f"{int(value/60)} m", @@ -92,7 +91,11 @@ class DisplayLayout(Widget): if isinstance(_item.action_item, ToggleActionSP) and _item.action_item.toggle.param_key is not None: _item.action_item.set_state(self._params.get_bool(_item.action_item.toggle.param_key)) elif isinstance(_item.action_item, OptionControlSP) and _item.action_item.param_key is not None: - _item.action_item.set_value(self._params.get(_item.action_item.param_key, return_default=True)) + raw_value = self._params.get(_item.action_item.param_key, return_default=True) + if _item.action_item.value_map: + reverse_map = {v: k for k, v in _item.action_item.value_map.items()} + raw_value = reverse_map.get(raw_value, _item.action_item.current_value) + _item.action_item.set_value(raw_value) brightness_val = self._params.get("OnroadScreenOffBrightness", return_default=True) self._onroad_brightness_timer.action_item.set_enabled(brightness_val not in (OnroadBrightness.AUTO, OnroadBrightness.AUTO_DARK)) diff --git a/sunnypilot/sunnylink/params_metadata.json b/sunnypilot/sunnylink/params_metadata.json index 79c2b5caf9..4d66bf8762 100644 --- a/sunnypilot/sunnylink/params_metadata.json +++ b/sunnypilot/sunnylink/params_metadata.json @@ -941,6 +941,22 @@ "title": "Onroad Brightness Delay", "description": "", "options": [ + { + "value": 3, + "label": "3s" + }, + { + "value": 5, + "label": "5s" + }, + { + "value": 7, + "label": "7s" + }, + { + "value": 10, + "label": "10s" + }, { "value": 15, "label": "15s" @@ -991,6 +1007,10 @@ } ] }, + "OnroadScreenOffTimerMigrated": { + "title": "Onroad Brightness Delay Migration Version", + "description": "This param is to track whether OnroadScreenOffTimer needs to be migrated." + }, "OnroadUploads": { "title": "Onroad Uploads", "description": "" @@ -1290,7 +1310,7 @@ "min": 0.1, "max": 5.0, "step": 0.1, - "unit": "m/s²" + "unit": "m/s\u00b2" }, "ToyotaEnforceStockLongitudinal": { "title": "Toyota: Enforce Factory Longitudinal Control", diff --git a/sunnypilot/sunnylink/tools/update_params_metadata.py b/sunnypilot/sunnylink/tools/update_params_metadata.py index 013544005b..ea3765420d 100755 --- a/sunnypilot/sunnylink/tools/update_params_metadata.py +++ b/sunnypilot/sunnylink/tools/update_params_metadata.py @@ -10,6 +10,7 @@ import os from openpilot.common.basedir import BASEDIR from openpilot.common.params import Params +from openpilot.sunnypilot.system.params_migration import ONROAD_BRIGHTNESS_TIMER_VALUES METADATA_PATH = os.path.join(os.path.dirname(__file__), "../params_metadata.json") TORQUE_VERSIONS_JSON = os.path.join(BASEDIR, "sunnypilot", "selfdrive", "controls", "lib", "latcontrol_torque_versions.json") @@ -56,6 +57,9 @@ def main(): # update onroad screen brightness params update_onroad_brightness_param() + # update onroad screen brightness timer params + update_onroad_brightness_timer_param() + # update torque versions param update_torque_versions_param() @@ -81,6 +85,24 @@ def update_onroad_brightness_param(): print(f"Failed to update OnroadScreenOffBrightness versions in params_metadata.json: {e}") +def update_onroad_brightness_timer_param(): + try: + with open(METADATA_PATH) as f: + params_metadata = json.load(f) + if "OnroadScreenOffTimer" in params_metadata: + options = [] + for _index, seconds in sorted(ONROAD_BRIGHTNESS_TIMER_VALUES.items()): + label = f"{seconds}s" if seconds < 60 else f"{seconds // 60}m" + options.append({"value": seconds, "label": label}) + params_metadata["OnroadScreenOffTimer"]["options"] = options + with open(METADATA_PATH, 'w') as f: + json.dump(params_metadata, f, indent=2) + f.write('\n') + print(f"Updated OnroadScreenOffTimer options in params_metadata.json with {len(options)} options.") + except Exception as e: + print(f"Failed to update OnroadScreenOffTimer options in params_metadata.json: {e}") + + def update_torque_versions_param(): with open(TORQUE_VERSIONS_JSON) as f: current_versions = json.load(f) diff --git a/sunnypilot/system/params_migration.py b/sunnypilot/system/params_migration.py index 6a82f1866d..5e524de06e 100644 --- a/sunnypilot/system/params_migration.py +++ b/sunnypilot/system/params_migration.py @@ -7,13 +7,18 @@ See the LICENSE.md file in the root directory for more details. from openpilot.common.swaglog import cloudlog ONROAD_BRIGHTNESS_MIGRATION_VERSION: str = "1.0" +ONROAD_BRIGHTNESS_TIMER_MIGRATION_VERSION: str = "1.0" + +# index → seconds mapping for OnroadScreenOffTimer (SSoT) +ONROAD_BRIGHTNESS_TIMER_VALUES = {0: 3, 1: 5, 2: 7, 3: 10, 4: 15, 5: 30, **{i: (i - 5) * 60 for i in range(6, 16)}} +VALID_TIMER_VALUES = set(ONROAD_BRIGHTNESS_TIMER_VALUES.values()) def run_migration(_params): # migrate OnroadScreenOffBrightness if _params.get("OnroadScreenOffBrightnessMigrated") != ONROAD_BRIGHTNESS_MIGRATION_VERSION: try: - val = _params.get("OnroadScreenOffBrightness") + val = _params.get("OnroadScreenOffBrightness", return_default=True) if val >= 2: # old: 5%, new: Screen Off new_val = val + 1 _params.put("OnroadScreenOffBrightness", new_val) @@ -25,3 +30,18 @@ def run_migration(_params): cloudlog.info(log_str + f" Setting OnroadScreenOffBrightnessMigrated to {ONROAD_BRIGHTNESS_MIGRATION_VERSION}") except Exception as e: cloudlog.exception(f"Error migrating OnroadScreenOffBrightness: {e}") + + # migrate OnroadScreenOffTimer + if _params.get("OnroadScreenOffTimerMigrated") != ONROAD_BRIGHTNESS_TIMER_MIGRATION_VERSION: + try: + val = _params.get("OnroadScreenOffTimer", return_default=True) + if val not in VALID_TIMER_VALUES: + _params.put("OnroadScreenOffTimer", 15) + log_str = f"Successfully migrated OnroadScreenOffTimer from {val} to 15 (default)." + else: + log_str = "Migration not required for OnroadScreenOffTimer." + + _params.put("OnroadScreenOffTimerMigrated", ONROAD_BRIGHTNESS_TIMER_MIGRATION_VERSION) + cloudlog.info(log_str + f" Setting OnroadScreenOffTimerMigrated to {ONROAD_BRIGHTNESS_TIMER_MIGRATION_VERSION}") + except Exception as e: + cloudlog.exception(f"Error migrating OnroadScreenOffTimer: {e}") diff --git a/system/manager/manager.py b/system/manager/manager.py index 8c219909e4..3ec554f904 100755 --- a/system/manager/manager.py +++ b/system/manager/manager.py @@ -51,7 +51,8 @@ def manager_init() -> None: if params.get_bool("RecordFrontLock"): params.put_bool("RecordFront", True) - run_migration(params) + if not PC: + run_migration(params) # set unset params to their default value for k in params.all_keys(): From 0557283e3d506cd1f92e6fcbae17771ae9fcdecf Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Fri, 6 Mar 2026 22:38:00 -0800 Subject: [PATCH 062/141] ui: add confirmation circle button (#37586) * try this * clean up and use it * clean up * simpler * do this later * do onboarding & reset * do setup * temp * Revert "temp" This reverts commit 22fbbf5c813b4915e784b9ee235ed3bde2229048. * simpler again * missing size * fix * Revert "fix" This reverts commit 53c4e29e614181029dc8e9a2baea7694957dc8fb. * nl --- selfdrive/ui/mici/layouts/onboarding.py | 42 +++++++------------------ selfdrive/ui/mici/widgets/dialog.py | 14 ++++++++- system/ui/mici_reset.py | 28 +++++------------ system/ui/mici_setup.py | 14 +++------ 4 files changed, 37 insertions(+), 61 deletions(-) diff --git a/selfdrive/ui/mici/layouts/onboarding.py b/selfdrive/ui/mici/layouts/onboarding.py index 3b9b36f701..f145a47734 100644 --- a/selfdrive/ui/mici/layouts/onboarding.py +++ b/selfdrive/ui/mici/layouts/onboarding.py @@ -14,8 +14,7 @@ from openpilot.system.ui.widgets.label import gui_label from openpilot.system.ui.lib.multilang import tr from openpilot.system.version import terms_version, training_version from openpilot.selfdrive.ui.ui_state import ui_state, device -from openpilot.selfdrive.ui.mici.widgets.button import BigCircleButton -from openpilot.selfdrive.ui.mici.widgets.dialog import BigConfirmationDialog +from openpilot.selfdrive.ui.mici.widgets.dialog import BigConfirmationCircleButton from openpilot.selfdrive.ui.mici.onroad.driver_state import DriverStateRenderer from openpilot.selfdrive.ui.mici.onroad.driver_camera_dialog import BaseDriverCameraDialog @@ -215,25 +214,18 @@ class TrainingGuideRecordFront(NavScroller): def __init__(self, continue_callback: Callable[[], None]): super().__init__() - def show_accept_dialog(): - def on_accept(): - ui_state.params.put_bool_nonblocking("RecordFront", True) - continue_callback() + def on_accept(): + ui_state.params.put_bool_nonblocking("RecordFront", True) + continue_callback() - gui_app.push_widget(BigConfirmationDialog("allow data uploading", "icons_mici/setup/driver_monitoring/dm_check.png", on_accept, exit_on_confirm=False)) + def on_decline(): + ui_state.params.put_bool_nonblocking("RecordFront", False) + continue_callback() - def show_decline_dialog(): - def on_decline(): - ui_state.params.put_bool_nonblocking("RecordFront", False) - continue_callback() + self._accept_button = BigConfirmationCircleButton("allow data uploading", "icons_mici/setup/driver_monitoring/dm_check.png", + on_accept, exit_on_confirm=False) - gui_app.push_widget(BigConfirmationDialog("no, don't upload", "icons_mici/setup/cancel.png", on_decline, exit_on_confirm=False)) - - self._accept_button = BigCircleButton("icons_mici/setup/driver_monitoring/dm_check.png") - self._accept_button.set_click_callback(show_accept_dialog) - - self._decline_button = BigCircleButton("icons_mici/setup/cancel.png") - self._decline_button.set_click_callback(show_decline_dialog) + self._decline_button = BigConfirmationCircleButton("no, don't upload", "icons_mici/setup/cancel.png", on_decline, exit_on_confirm=False) self._scroller.add_widgets([ GreyBigButton("driver camera data", "do you want to share video data for training?", @@ -322,18 +314,8 @@ class TermsPage(Scroller): def __init__(self, on_accept, on_decline): super().__init__() - def show_accept_dialog(): - gui_app.push_widget(BigConfirmationDialog("accept\nterms", "icons_mici/setup/driver_monitoring/dm_check.png", on_accept)) - - def show_decline_dialog(): - gui_app.push_widget(BigConfirmationDialog("decline &\nuninstall", "icons_mici/setup/cancel.png", on_decline, - red=True, exit_on_confirm=False)) - - self._accept_button = BigCircleButton("icons_mici/setup/driver_monitoring/dm_check.png") - self._accept_button.set_click_callback(show_accept_dialog) - - self._decline_button = BigCircleButton("icons_mici/setup/cancel.png", red=True) - self._decline_button.set_click_callback(show_decline_dialog) + self._accept_button = BigConfirmationCircleButton("accept\nterms", "icons_mici/setup/driver_monitoring/dm_check.png", on_accept) + self._decline_button = BigConfirmationCircleButton("decline &\nuninstall", "icons_mici/setup/cancel.png", on_decline, red=True, exit_on_confirm=False) self._terms_header = GreyBigButton("terms and\nconditions", "scroll to continue", gui_app.texture("icons_mici/setup/green_info.png", 64, 64)) diff --git a/selfdrive/ui/mici/widgets/dialog.py b/selfdrive/ui/mici/widgets/dialog.py index fc4e3edabe..53c419b414 100644 --- a/selfdrive/ui/mici/widgets/dialog.py +++ b/selfdrive/ui/mici/widgets/dialog.py @@ -11,7 +11,7 @@ from openpilot.system.ui.lib.wrap_text import wrap_text from openpilot.system.ui.lib.application import gui_app, FontWeight, MousePos from openpilot.system.ui.widgets.slider import RedBigSlider, BigSlider from openpilot.common.filter_simple import FirstOrderFilter -from openpilot.selfdrive.ui.mici.widgets.button import BigButton +from openpilot.selfdrive.ui.mici.widgets.button import BigCircleButton, BigButton DEBUG = False @@ -253,3 +253,15 @@ class BigDialogButton(BigButton): dlg = BigDialog(self.text, self._description) gui_app.push_widget(dlg) + + +class BigConfirmationCircleButton(BigCircleButton): + def __init__(self, title: str, icon: str, confirm_callback: Callable[[], None], exit_on_confirm: bool = True, + red: bool = False, icon_size: tuple[int, int] = (64, 53), icon_offset: tuple[int, int] = (0, 0)): + super().__init__(icon, red, icon_size, icon_offset) + + def show_confirm_dialog(): + gui_app.push_widget(BigConfirmationDialog(title, icon, confirm_callback, + exit_on_confirm=exit_on_confirm, red=red)) + + self.set_click_callback(show_confirm_dialog) diff --git a/system/ui/mici_reset.py b/system/ui/mici_reset.py index b56e38941a..e88f5f029e 100755 --- a/system/ui/mici_reset.py +++ b/system/ui/mici_reset.py @@ -11,8 +11,7 @@ from openpilot.system.ui.lib.application import gui_app from openpilot.system.ui.widgets.scroller import Scroller from openpilot.system.ui.widgets.nav_widget import NavWidget from openpilot.system.ui.mici_setup import GreyBigButton, FailedPage -from openpilot.selfdrive.ui.mici.widgets.dialog import BigConfirmationDialog -from openpilot.selfdrive.ui.mici.widgets.button import BigCircleButton +from openpilot.selfdrive.ui.mici.widgets.dialog import BigConfirmationCircleButton USERDATA = "/dev/disk/by-partlabel/userdata" TIMEOUT = 3*60 @@ -70,37 +69,26 @@ class Reset(Scroller): self._resetting_page = ResettingPage() self._reset_failed_page = ResetFailedPage() - def show_confirm_dialog(): - dialog = BigConfirmationDialog("erase\ndevice", "icons_mici/settings/device/uninstall.png", self.start_reset, red=True) - gui_app.push_widget(dialog) + self._reset_button = BigConfirmationCircleButton("erase\ndevice", "icons_mici/settings/device/uninstall.png", self.start_reset, red=True) + self._cancel_button = BigConfirmationCircleButton("normal\nstartup", "icons_mici/settings/device/reboot.png", gui_app.request_close, exit_on_confirm=False) + self._reboot_button = BigConfirmationCircleButton("reboot\ndevice", "icons_mici/settings/device/reboot.png", HARDWARE.reboot, exit_on_confirm=False) - def show_cancel_dialog(): - dialog = BigConfirmationDialog("normal\nstartup", "icons_mici/settings/device/reboot.png", gui_app.request_close, exit_on_confirm=False) - gui_app.push_widget(dialog) - - def show_reboot_dialog(): - dialog = BigConfirmationDialog("reboot\ndevice", "icons_mici/settings/device/reboot.png", HARDWARE.reboot, exit_on_confirm=False) - gui_app.push_widget(dialog) - - self._reset_button = BigCircleButton("icons_mici/settings/device/uninstall.png", red=True) - self._reset_button.set_click_callback(show_confirm_dialog) - - self._cancel_button = BigCircleButton("icons_mici/settings/device/reboot.png") - self._cancel_button.set_click_callback(show_cancel_dialog) + # show reboot button if in recover mode + self._cancel_button.set_visible(mode != ResetMode.RECOVER) + self._reboot_button.set_visible(mode == ResetMode.RECOVER) main_card = GreyBigButton("factory reset", "all content and\nsettings will be erased", gui_app.texture("icons_mici/setup/factory_reset.png", 64, 64)) - # cancel button becomes reboot button if mode == ResetMode.RECOVER: main_card.set_text("unable to mount\ndata partition") main_card.set_value("it may be corrupted") - self._cancel_button.set_click_callback(show_reboot_dialog) self._scroller.add_widgets([ main_card, self._reset_button, self._cancel_button, + self._reboot_button, ]) def _do_erase(self): diff --git a/system/ui/mici_setup.py b/system/ui/mici_setup.py index 31edbe86e8..2f409f2d54 100755 --- a/system/ui/mici_setup.py +++ b/system/ui/mici_setup.py @@ -28,8 +28,8 @@ from openpilot.system.ui.widgets.scroller import Scroller, NavScroller, ITEM_SPA from openpilot.system.ui.widgets.slider import LargerSlider from openpilot.selfdrive.ui.mici.layouts.settings.network import WifiNetworkButton from openpilot.selfdrive.ui.mici.layouts.settings.network.wifi_ui import WifiUIMici -from openpilot.selfdrive.ui.mici.widgets.dialog import BigInputDialog, BigConfirmationDialog -from openpilot.selfdrive.ui.mici.widgets.button import BigCircleButton, BigButton +from openpilot.selfdrive.ui.mici.widgets.dialog import BigInputDialog, BigConfirmationCircleButton +from openpilot.selfdrive.ui.mici.widgets.button import BigButton NetworkType = log.DeviceState.NetworkType @@ -247,13 +247,6 @@ class FailedPage(NavScroller): super().__init__() self.set_back_callback(retry_callback) - def show_reboot_dialog(): - dialog = BigConfirmationDialog("slide to reboot", "icons_mici/settings/device/reboot.png", HARDWARE.reboot, exit_on_confirm=False) - gui_app.push_widget(dialog) - - reboot_button = BigCircleButton("icons_mici/settings/device/reboot.png", red=False, icon_size=(64, 70)) - reboot_button.set_click_callback(show_reboot_dialog) - self._reason_card = GreyBigButton("", "") self._reason_card.set_visible(False) @@ -261,7 +254,8 @@ class FailedPage(NavScroller): GreyBigButton(title, description or "swipe down to go\nback and try again", gui_app.texture(icon, 64, 58)), self._reason_card, - reboot_button, + BigConfirmationCircleButton("slide to reboot", "icons_mici/settings/device/reboot.png", + HARDWARE.reboot, exit_on_confirm=False, icon_size=(64, 70)), ]) def set_reason(self, reason: str): From b71914e0066a96e1c90536e0748ac7bc99f8ab21 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Sat, 7 Mar 2026 01:48:35 -0500 Subject: [PATCH 063/141] [TIZI/TICI] ui: branch switcher is always available (#1762) --- selfdrive/ui/layouts/settings/software.py | 1 - 1 file changed, 1 deletion(-) diff --git a/selfdrive/ui/layouts/settings/software.py b/selfdrive/ui/layouts/settings/software.py index f7424b974d..8086b1f635 100644 --- a/selfdrive/ui/layouts/settings/software.py +++ b/selfdrive/ui/layouts/settings/software.py @@ -69,7 +69,6 @@ class SoftwareLayout(Widget): # Branch switcher self._branch_btn = button_item(lambda: tr("Target Branch"), lambda: tr("SELECT"), callback=self._on_select_branch) - self._branch_btn.set_visible(not ui_state.params.get_bool("IsTestedBranch")) self._branch_btn.action_item.set_value(ui_state.params.get("UpdaterTargetBranch") or "") self._branch_dialog: MultiOptionDialog | None = None From 1f9ec135a46df4d3524a620f83989953484ce976 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Fri, 6 Mar 2026 23:40:42 -0800 Subject: [PATCH 064/141] BigButton: take icon texture and fix image sizes (#37590) * more explicit pass texture like everything else, esp since sizes are not all same * fix some confirmation dialog images * fix image sizes * do bigbutton * fix * static --- selfdrive/ui/mici/layouts/onboarding.py | 10 ++++--- .../ui/mici/layouts/settings/developer.py | 4 +-- selfdrive/ui/mici/layouts/settings/device.py | 28 +++++++++---------- .../mici/layouts/settings/network/wifi_ui.py | 2 +- .../ui/mici/layouts/settings/settings.py | 10 +++---- selfdrive/ui/mici/tests/test_widget_leaks.py | 2 +- selfdrive/ui/mici/widgets/button.py | 24 ++++++++-------- selfdrive/ui/mici/widgets/dialog.py | 13 ++++----- system/ui/mici_reset.py | 9 ++++-- system/ui/mici_setup.py | 4 +-- 10 files changed, 54 insertions(+), 52 deletions(-) diff --git a/selfdrive/ui/mici/layouts/onboarding.py b/selfdrive/ui/mici/layouts/onboarding.py index f145a47734..ac19628738 100644 --- a/selfdrive/ui/mici/layouts/onboarding.py +++ b/selfdrive/ui/mici/layouts/onboarding.py @@ -222,10 +222,11 @@ class TrainingGuideRecordFront(NavScroller): ui_state.params.put_bool_nonblocking("RecordFront", False) continue_callback() - self._accept_button = BigConfirmationCircleButton("allow data uploading", "icons_mici/setup/driver_monitoring/dm_check.png", + self._accept_button = BigConfirmationCircleButton("allow data uploading", gui_app.texture("icons_mici/setup/driver_monitoring/dm_check.png", 64, 64), on_accept, exit_on_confirm=False) - self._decline_button = BigConfirmationCircleButton("no, don't upload", "icons_mici/setup/cancel.png", on_decline, exit_on_confirm=False) + self._decline_button = BigConfirmationCircleButton("no, don't upload", gui_app.texture("icons_mici/setup/cancel.png", 64, 64), on_decline, + exit_on_confirm=False) self._scroller.add_widgets([ GreyBigButton("driver camera data", "do you want to share video data for training?", @@ -314,8 +315,9 @@ class TermsPage(Scroller): def __init__(self, on_accept, on_decline): super().__init__() - self._accept_button = BigConfirmationCircleButton("accept\nterms", "icons_mici/setup/driver_monitoring/dm_check.png", on_accept) - self._decline_button = BigConfirmationCircleButton("decline &\nuninstall", "icons_mici/setup/cancel.png", on_decline, red=True, exit_on_confirm=False) + self._accept_button = BigConfirmationCircleButton("accept\nterms", gui_app.texture("icons_mici/setup/driver_monitoring/dm_check.png", 64, 64), on_accept) + self._decline_button = BigConfirmationCircleButton("decline &\nuninstall", gui_app.texture("icons_mici/setup/cancel.png", 64, 64), on_decline, + red=True, exit_on_confirm=False) self._terms_header = GreyBigButton("terms and\nconditions", "scroll to continue", gui_app.texture("icons_mici/setup/green_info.png", 64, 64)) diff --git a/selfdrive/ui/mici/layouts/settings/developer.py b/selfdrive/ui/mici/layouts/settings/developer.py index 4e7796814e..eccaa3ec09 100644 --- a/selfdrive/ui/mici/layouts/settings/developer.py +++ b/selfdrive/ui/mici/layouts/settings/developer.py @@ -42,8 +42,8 @@ class DeveloperLayoutMici(NavScroller): # adb, ssh, ssh keys, debug mode, joystick debug mode, longitudinal maneuver mode, ip address # ******** Main Scroller ******** - self._adb_toggle = BigCircleParamControl("icons_mici/adb_short.png", "AdbEnabled", icon_size=(82, 82), icon_offset=(0, 12)) - self._ssh_toggle = BigCircleParamControl("icons_mici/ssh_short.png", "SshEnabled", icon_size=(82, 82), icon_offset=(0, 12)) + self._adb_toggle = BigCircleParamControl(gui_app.texture("icons_mici/adb_short.png", 82, 82), "AdbEnabled", icon_offset=(0, 12)) + self._ssh_toggle = BigCircleParamControl(gui_app.texture("icons_mici/ssh_short.png", 82, 82), "SshEnabled", icon_offset=(0, 12)) self._joystick_toggle = BigToggle("joystick debug mode", initial_state=ui_state.params.get_bool("JoystickDebugMode"), toggle_callback=self._on_joystick_debug_mode) diff --git a/selfdrive/ui/mici/layouts/settings/device.py b/selfdrive/ui/mici/layouts/settings/device.py index bee8cbce5f..d4c8fda5ce 100644 --- a/selfdrive/ui/mici/layouts/settings/device.py +++ b/selfdrive/ui/mici/layouts/settings/device.py @@ -73,17 +73,17 @@ def _engaged_confirmation_callback(callback: Callable, action_text: str): red = False if action_text == "power off": - icon = "icons_mici/settings/device/power.png" + icon = gui_app.texture("icons_mici/settings/device/power.png", 64, 66) red = True elif action_text == "reboot": - icon = "icons_mici/settings/device/reboot.png" + icon = gui_app.texture("icons_mici/settings/device/reboot.png", 64, 70) elif action_text == "reset": - icon = "icons_mici/settings/device/lkas.png" + icon = gui_app.texture("icons_mici/settings/device/lkas.png", 122, 64) elif action_text == "uninstall": - icon = "icons_mici/settings/device/uninstall.png" + icon = gui_app.texture("icons_mici/settings/device/uninstall.png", 64, 64) else: # TODO: check - icon = "icons_mici/settings/comma_icon.png" + icon = gui_app.texture("icons_mici/settings/comma_icon.png", 36, 64) dlg: BigConfirmationDialog | BigDialog = BigConfirmationDialog(f"slide to\n{action_text.lower()}", icon, confirm_callback, red=red, exit_on_confirm=action_text == "reset") @@ -131,7 +131,7 @@ class UpdaterState(IntEnum): class PairBigButton(BigButton): def __init__(self): - super().__init__("pair", "connect.comma.ai", "icons_mici/settings/comma_icon.png", icon_size=(33, 60)) + super().__init__("pair", "connect.comma.ai", gui_app.texture("icons_mici/settings/comma_icon.png", 33, 60)) def _get_label_font_size(self): return 64 @@ -310,31 +310,31 @@ class DeviceLayoutMici(NavScroller): def uninstall_openpilot_callback(): ui_state.params.put_bool("DoUninstall", True) - reset_calibration_btn = BigButton("reset calibration", "", "icons_mici/settings/device/lkas.png", icon_size=(114, 60)) + reset_calibration_btn = BigButton("reset calibration", "", gui_app.texture("icons_mici/settings/device/lkas.png", 122, 64)) reset_calibration_btn.set_click_callback(lambda: _engaged_confirmation_callback(reset_calibration_callback, "reset")) - uninstall_openpilot_btn = BigButton("uninstall openpilot", "", "icons_mici/settings/device/uninstall.png") + uninstall_openpilot_btn = BigButton("uninstall openpilot", "", gui_app.texture("icons_mici/settings/device/uninstall.png", 64, 64)) uninstall_openpilot_btn.set_click_callback(lambda: _engaged_confirmation_callback(uninstall_openpilot_callback, "uninstall")) - reboot_btn = BigCircleButton("icons_mici/settings/device/reboot.png", red=False, icon_size=(64, 70)) + reboot_btn = BigCircleButton(gui_app.texture("icons_mici/settings/device/reboot.png", 64, 70)) reboot_btn.set_click_callback(lambda: _engaged_confirmation_callback(reboot_callback, "reboot")) - self._power_off_btn = BigCircleButton("icons_mici/settings/device/power.png", red=True, icon_size=(64, 66)) + self._power_off_btn = BigCircleButton(gui_app.texture("icons_mici/settings/device/power.png", 64, 66), red=True) self._power_off_btn.set_click_callback(lambda: _engaged_confirmation_callback(power_off_callback, "power off")) self._power_off_btn.set_visible(lambda: not ui_state.ignition) - regulatory_btn = BigButton("regulatory info", "", "icons_mici/settings/device/info.png") + regulatory_btn = BigButton("regulatory info", "", gui_app.texture("icons_mici/settings/device/info.png", 64, 64)) regulatory_btn.set_click_callback(self._on_regulatory) - driver_cam_btn = BigButton("driver\ncamera preview", "", "icons_mici/settings/device/cameras.png") + driver_cam_btn = BigButton("driver\ncamera preview", "", gui_app.texture("icons_mici/settings/device/cameras.png", 64, 64)) driver_cam_btn.set_click_callback(lambda: gui_app.push_widget(DriverCameraDialog())) driver_cam_btn.set_enabled(lambda: ui_state.is_offroad()) - review_training_guide_btn = BigButton("review\ntraining guide", "", "icons_mici/settings/device/info.png") + review_training_guide_btn = BigButton("review\ntraining guide", "", gui_app.texture("icons_mici/settings/device/info.png", 64, 64)) review_training_guide_btn.set_click_callback(lambda: gui_app.push_widget(ReviewTrainingGuide(completed_callback=lambda: gui_app.pop_widgets_to(self)))) review_training_guide_btn.set_enabled(lambda: ui_state.is_offroad()) - terms_btn = BigButton("terms &\nconditions", "", "icons_mici/settings/device/info.png") + terms_btn = BigButton("terms &\nconditions", "", gui_app.texture("icons_mici/settings/device/info.png", 64, 64)) terms_btn.set_click_callback(lambda: gui_app.push_widget(ReviewTermsPage())) self._scroller.add_widgets([ diff --git a/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py b/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py index 0f5ac977b9..10d828fbd8 100644 --- a/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py +++ b/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py @@ -246,7 +246,7 @@ class ForgetButton(Widget): def _handle_mouse_release(self, mouse_pos: MousePos): super()._handle_mouse_release(mouse_pos) - dlg = BigConfirmationDialog("slide to forget", "icons_mici/settings/network/new/trash.png", self._forget_network, red=True) + dlg = BigConfirmationDialog("slide to forget", gui_app.texture("icons_mici/settings/network/new/trash.png", 54, 64), self._forget_network, red=True) gui_app.push_widget(dlg) def _render(self, _): diff --git a/selfdrive/ui/mici/layouts/settings/settings.py b/selfdrive/ui/mici/layouts/settings/settings.py index 8e037ccf88..4ccc5ba139 100644 --- a/selfdrive/ui/mici/layouts/settings/settings.py +++ b/selfdrive/ui/mici/layouts/settings/settings.py @@ -20,23 +20,23 @@ class SettingsLayout(NavScroller): self._params = Params() toggles_panel = TogglesLayoutMici() - toggles_btn = SettingsBigButton("toggles", "", "icons_mici/settings.png") + toggles_btn = SettingsBigButton("toggles", "", gui_app.texture("icons_mici/settings.png", 64, 64)) toggles_btn.set_click_callback(lambda: gui_app.push_widget(toggles_panel)) network_panel = NetworkLayoutMici() - network_btn = SettingsBigButton("network", "", "icons_mici/settings/network/wifi_strength_full.png", icon_size=(76, 56)) + network_btn = SettingsBigButton("network", "", gui_app.texture("icons_mici/settings/network/wifi_strength_full.png", 76, 56)) network_btn.set_click_callback(lambda: gui_app.push_widget(network_panel)) device_panel = DeviceLayoutMici() - device_btn = SettingsBigButton("device", "", "icons_mici/settings/device_icon.png", icon_size=(74, 60)) + device_btn = SettingsBigButton("device", "", gui_app.texture("icons_mici/settings/device_icon.png", 72, 58)) device_btn.set_click_callback(lambda: gui_app.push_widget(device_panel)) developer_panel = DeveloperLayoutMici() - developer_btn = SettingsBigButton("developer", "", "icons_mici/settings/developer_icon.png", icon_size=(64, 60)) + developer_btn = SettingsBigButton("developer", "", gui_app.texture("icons_mici/settings/developer_icon.png", 64, 60)) developer_btn.set_click_callback(lambda: gui_app.push_widget(developer_panel)) firehose_panel = FirehoseLayout() - firehose_btn = SettingsBigButton("firehose", "", "icons_mici/settings/firehose.png", icon_size=(52, 62)) + firehose_btn = SettingsBigButton("firehose", "", gui_app.texture("icons_mici/settings/firehose.png", 52, 62)) firehose_btn.set_click_callback(lambda: gui_app.push_widget(firehose_panel)) self._scroller.add_widgets([ diff --git a/selfdrive/ui/mici/tests/test_widget_leaks.py b/selfdrive/ui/mici/tests/test_widget_leaks.py index 43b2cf79f6..e35cb44776 100755 --- a/selfdrive/ui/mici/tests/test_widget_leaks.py +++ b/selfdrive/ui/mici/tests/test_widget_leaks.py @@ -72,7 +72,7 @@ def test_dialogs_do_not_leak(): lambda: MiciTrainingGuide(lambda: None), lambda: MiciOnboardingWindow(lambda: None), lambda: BigDialog("test", "test"), - lambda: BigConfirmationDialog("test", "icons_mici/settings/network/new/trash.png", lambda: None), + lambda: BigConfirmationDialog("test", gui_app.texture("icons_mici/settings/network/new/trash.png", 54, 64), lambda: None), lambda: BigInputDialog("test"), lambda: MiciFccModal(text="test"), # tici diff --git a/selfdrive/ui/mici/widgets/button.py b/selfdrive/ui/mici/widgets/button.py index 18413dbc3a..9724a18192 100644 --- a/selfdrive/ui/mici/widgets/button.py +++ b/selfdrive/ui/mici/widgets/button.py @@ -28,7 +28,7 @@ class ScrollState(Enum): class BigCircleButton(Widget): - def __init__(self, icon: str, red: bool = False, icon_size: tuple[int, int] = (64, 53), icon_offset: tuple[int, int] = (0, 0)): + def __init__(self, icon: rl.Texture, red: bool = False, icon_offset: tuple[int, int] = (0, 0)): super().__init__() self._red = red self._icon_offset = icon_offset @@ -39,7 +39,7 @@ class BigCircleButton(Widget): self._click_delay = 0.075 # Icons - self._txt_icon = gui_app.texture(icon, *icon_size) + self._txt_icon = icon self._txt_btn_disabled_bg = gui_app.texture("icons_mici/buttons/button_circle_disabled.png", 180, 180) self._txt_btn_bg = gui_app.texture("icons_mici/buttons/button_circle.png", 180, 180) @@ -71,8 +71,8 @@ class BigCircleButton(Widget): class BigCircleToggle(BigCircleButton): - def __init__(self, icon: str, toggle_callback: Callable | None = None, icon_size: tuple[int, int] = (64, 53), icon_offset: tuple[int, int] = (0, 0)): - super().__init__(icon, False, icon_size=icon_size, icon_offset=icon_offset) + def __init__(self, icon: rl.Texture, toggle_callback: Callable | None = None, icon_offset: tuple[int, int] = (0, 0)): + super().__init__(icon, False, icon_offset=icon_offset) self._toggle_callback = toggle_callback # State @@ -107,15 +107,13 @@ class BigButton(Widget): """A lightweight stand-in for the Qt BigButton, drawn & updated each frame.""" - def __init__(self, text: str, value: str = "", icon: Union[str, rl.Texture] = "", icon_size: tuple[int, int] = (64, 64), - scroll: bool = False): + def __init__(self, text: str, value: str = "", icon: Union[rl.Texture, None] = None, scroll: bool = False): super().__init__() self.set_rect(rl.Rectangle(0, 0, 402, 180)) self.text = text self.value = value - self._icon_size = icon_size + self._txt_icon = icon self._scroll = scroll - self.set_icon(icon) self._scale_filter = BounceFilter(1.0, 0.1, 1 / gui_app.target_fps) self._click_delay = 0.075 @@ -133,8 +131,8 @@ class BigButton(Widget): self._load_images() - def set_icon(self, icon: Union[str, rl.Texture]): - self._txt_icon = gui_app.texture(icon, *self._icon_size) if isinstance(icon, str) and len(icon) else icon + def set_icon(self, icon: Union[rl.Texture, None]): + self._txt_icon = icon def set_rotate_icon(self, rotate: bool): if rotate and self._rotate_icon_t is not None: @@ -151,7 +149,7 @@ class BigButton(Widget): def _width_hint(self) -> int: # Single line if scrolling, so hide behind icon if exists - icon_size = self._icon_size[0] if self._txt_icon and self._scroll and self.value else 0 + icon_size = self._txt_icon.width if self._txt_icon and self._scroll and self.value else 0 return int(self._rect.width - self.LABEL_HORIZONTAL_PADDING * 2 - icon_size) def _get_label_font_size(self): @@ -372,9 +370,9 @@ class BigParamControl(BigToggle): # TODO: param control base class class BigCircleParamControl(BigCircleToggle): - def __init__(self, icon: str, param: str, toggle_callback: Callable | None = None, icon_size: tuple[int, int] = (64, 53), + def __init__(self, icon: rl.Texture, param: str, toggle_callback: Callable | None = None, icon_offset: tuple[int, int] = (0, 0)): - super().__init__(icon, toggle_callback, icon_size=icon_size, icon_offset=icon_offset) + super().__init__(icon, toggle_callback, icon_offset=icon_offset) self._param = param self.params = Params() self.set_checked(self.params.get_bool(self._param, False)) diff --git a/selfdrive/ui/mici/widgets/dialog.py b/selfdrive/ui/mici/widgets/dialog.py index 53c419b414..94091b9269 100644 --- a/selfdrive/ui/mici/widgets/dialog.py +++ b/selfdrive/ui/mici/widgets/dialog.py @@ -64,18 +64,17 @@ class BigDialog(BigDialogBase): class BigConfirmationDialog(BigDialogBase): - def __init__(self, title: str, icon: str, confirm_callback: Callable[[], None], + def __init__(self, title: str, icon: rl.Texture, confirm_callback: Callable[[], None], exit_on_confirm: bool = True, red: bool = False): super().__init__() self._confirm_callback = confirm_callback self._exit_on_confirm = exit_on_confirm - icon_txt = gui_app.texture(icon, 64, 53) self._slider: BigSlider | RedBigSlider if red: - self._slider = RedBigSlider(title, icon_txt, confirm_callback=self._on_confirm) + self._slider = RedBigSlider(title, icon, confirm_callback=self._on_confirm) else: - self._slider = BigSlider(title, icon_txt, confirm_callback=self._on_confirm) + self._slider = BigSlider(title, icon, confirm_callback=self._on_confirm) self._slider.set_enabled(lambda: self.enabled and not self.is_dismissing) # for nav stack + NavWidget def _on_confirm(self): @@ -256,9 +255,9 @@ class BigDialogButton(BigButton): class BigConfirmationCircleButton(BigCircleButton): - def __init__(self, title: str, icon: str, confirm_callback: Callable[[], None], exit_on_confirm: bool = True, - red: bool = False, icon_size: tuple[int, int] = (64, 53), icon_offset: tuple[int, int] = (0, 0)): - super().__init__(icon, red, icon_size, icon_offset) + def __init__(self, title: str, icon: rl.Texture, confirm_callback: Callable[[], None], exit_on_confirm: bool = True, + red: bool = False, icon_offset: tuple[int, int] = (0, 0)): + super().__init__(icon, red, icon_offset) def show_confirm_dialog(): gui_app.push_widget(BigConfirmationDialog(title, icon, confirm_callback, diff --git a/system/ui/mici_reset.py b/system/ui/mici_reset.py index e88f5f029e..49fbf71b76 100755 --- a/system/ui/mici_reset.py +++ b/system/ui/mici_reset.py @@ -69,9 +69,12 @@ class Reset(Scroller): self._resetting_page = ResettingPage() self._reset_failed_page = ResetFailedPage() - self._reset_button = BigConfirmationCircleButton("erase\ndevice", "icons_mici/settings/device/uninstall.png", self.start_reset, red=True) - self._cancel_button = BigConfirmationCircleButton("normal\nstartup", "icons_mici/settings/device/reboot.png", gui_app.request_close, exit_on_confirm=False) - self._reboot_button = BigConfirmationCircleButton("reboot\ndevice", "icons_mici/settings/device/reboot.png", HARDWARE.reboot, exit_on_confirm=False) + self._reset_button = BigConfirmationCircleButton("erase\ndevice", gui_app.texture("icons_mici/settings/device/uninstall.png", 64, 64), + self.start_reset, red=True) + self._cancel_button = BigConfirmationCircleButton("normal\nstartup", gui_app.texture("icons_mici/settings/device/reboot.png", 64, 70), + gui_app.request_close, exit_on_confirm=False) + self._reboot_button = BigConfirmationCircleButton("reboot\ndevice", gui_app.texture("icons_mici/settings/device/reboot.png", 64, 70), + HARDWARE.reboot, exit_on_confirm=False) # show reboot button if in recover mode self._cancel_button.set_visible(mode != ResetMode.RECOVER) diff --git a/system/ui/mici_setup.py b/system/ui/mici_setup.py index 2f409f2d54..40b4c7a881 100755 --- a/system/ui/mici_setup.py +++ b/system/ui/mici_setup.py @@ -254,8 +254,8 @@ class FailedPage(NavScroller): GreyBigButton(title, description or "swipe down to go\nback and try again", gui_app.texture(icon, 64, 58)), self._reason_card, - BigConfirmationCircleButton("slide to reboot", "icons_mici/settings/device/reboot.png", - HARDWARE.reboot, exit_on_confirm=False, icon_size=(64, 70)), + BigConfirmationCircleButton("slide to reboot", gui_app.texture("icons_mici/settings/device/reboot.png", 64, 70), + HARDWARE.reboot, exit_on_confirm=False), ]) def set_reason(self, reason: str): From c36c30e74be7510a8b3f8e44d9470b28b2237910 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Sat, 7 Mar 2026 00:14:01 -0800 Subject: [PATCH 065/141] reset: rm --format (#37591) * reset: rm --format * same for tici --- system/ui/mici_reset.py | 10 ++-------- system/ui/tici_reset.py | 10 ++-------- 2 files changed, 4 insertions(+), 16 deletions(-) diff --git a/system/ui/mici_reset.py b/system/ui/mici_reset.py index 49fbf71b76..03593b9900 100755 --- a/system/ui/mici_reset.py +++ b/system/ui/mici_reset.py @@ -20,7 +20,6 @@ TIMEOUT = 3*60 class ResetMode(IntEnum): USER_RESET = 0 # user initiated a factory reset from openpilot RECOVER = 1 # userdata is corrupt for some reason, give a chance to recover - FORMAT = 2 # finish up a factory reset from a tool that doesn't flash an empty partition to userdata class ResetFailedPage(FailedPage): @@ -70,7 +69,7 @@ class Reset(Scroller): self._reset_failed_page = ResetFailedPage() self._reset_button = BigConfirmationCircleButton("erase\ndevice", gui_app.texture("icons_mici/settings/device/uninstall.png", 64, 64), - self.start_reset, red=True) + self._start_reset, red=True) self._cancel_button = BigConfirmationCircleButton("normal\nstartup", gui_app.texture("icons_mici/settings/device/reboot.png", 64, 70), gui_app.request_close, exit_on_confirm=False) self._reboot_button = BigConfirmationCircleButton("reboot\ndevice", gui_app.texture("icons_mici/settings/device/reboot.png", 64, 70), @@ -108,7 +107,7 @@ class Reset(Scroller): else: self._reset_failed = True - def start_reset(self): + def _start_reset(self): self._resetting_page.set_shown_callback(self._do_erase) gui_app.push_widget(self._resetting_page) @@ -132,16 +131,11 @@ def main(): if len(sys.argv) > 1: if sys.argv[1] == '--recover': mode = ResetMode.RECOVER - elif sys.argv[1] == "--format": - mode = ResetMode.FORMAT gui_app.init_window("System Reset") reset = Reset(mode) gui_app.push_widget(reset) - if mode == ResetMode.FORMAT: - reset.start_reset() - for _ in gui_app.render(): pass diff --git a/system/ui/tici_reset.py b/system/ui/tici_reset.py index 23f6b344ec..a6603d547e 100755 --- a/system/ui/tici_reset.py +++ b/system/ui/tici_reset.py @@ -20,7 +20,6 @@ TIMEOUT = 3*60 class ResetMode(IntEnum): USER_RESET = 0 # user initiated a factory reset from openpilot RECOVER = 1 # userdata is corrupt for some reason, give a chance to recover - FORMAT = 2 # finish up a factory reset from a tool that doesn't flash an empty partition to userdata class ResetState(IntEnum): @@ -54,7 +53,7 @@ class Reset(Widget): else: self._reset_state = ResetState.FAILED - def start_reset(self): + def _start_reset(self): self._reset_state = ResetState.RESETTING threading.Timer(0.1, self._do_erase).start() @@ -92,7 +91,7 @@ class Reset(Widget): def _confirm(self): if self._reset_state == ResetState.CONFIRM: - self.start_reset() + self._start_reset() else: self._reset_state = ResetState.CONFIRM @@ -113,15 +112,10 @@ def main(): if len(sys.argv) > 1: if sys.argv[1] == '--recover': mode = ResetMode.RECOVER - elif sys.argv[1] == "--format": - mode = ResetMode.FORMAT gui_app.init_window("System Reset", 20) reset = Reset(mode) - if mode == ResetMode.FORMAT: - reset.start_reset() - gui_app.push_widget(reset) for _ in gui_app.render(): From 7061c18ceedbfba058cc4d9e48d8cf3e96ab2122 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Sat, 7 Mar 2026 01:45:46 -0800 Subject: [PATCH 066/141] ui: antialias text (#37592) aa --- system/ui/lib/application.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/system/ui/lib/application.py b/system/ui/lib/application.py index 8bb919cfe1..ddcde01496 100644 --- a/system/ui/lib/application.py +++ b/system/ui/lib/application.py @@ -656,7 +656,8 @@ class GuiApplication: fnt_path = fspath / font_weight_file font = rl.load_font(fnt_path.as_posix()) if font_weight_file != FontWeight.UNIFONT: - rl.set_texture_filter(font.texture, rl.TextureFilter.TEXTURE_FILTER_BILINEAR) + rl.gen_texture_mipmaps(font.texture) + rl.set_texture_filter(font.texture, rl.TextureFilter.TEXTURE_FILTER_TRILINEAR) self._fonts[font_weight_file] = font rl.gui_set_font(self._fonts[FontWeight.NORMAL]) From 08162be765a1cf88c53d2fd0098ba0b0ad5c4ae3 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Sat, 7 Mar 2026 01:53:41 -0800 Subject: [PATCH 067/141] mici reset: new flow (#37584) * copy * add back * stash * fix * more * dot animation * fix anim * 0.6 * fix --- system/ui/mici_reset.py | 30 ++++++++++++++++++++++-------- system/ui/mici_setup.py | 2 +- 2 files changed, 23 insertions(+), 9 deletions(-) diff --git a/system/ui/mici_reset.py b/system/ui/mici_reset.py index 03593b9900..2f89b6d62a 100755 --- a/system/ui/mici_reset.py +++ b/system/ui/mici_reset.py @@ -20,6 +20,7 @@ TIMEOUT = 3*60 class ResetMode(IntEnum): USER_RESET = 0 # user initiated a factory reset from openpilot RECOVER = 1 # userdata is corrupt for some reason, give a chance to recover + TAP_RESET = 2 # user initiated a factory reset by tapping the screen during boot class ResetFailedPage(FailedPage): @@ -35,8 +36,11 @@ class ResetFailedPage(FailedPage): class ResettingPage(NavWidget): + DOT_STEP = 0.6 + def __init__(self): super().__init__() + self._show_time = 0.0 self._resetting_card = GreyBigButton("resetting device", "this may take up to\na minute...", gui_app.texture("icons_mici/setup/factory_reset.png", 64, 64)) @@ -44,11 +48,15 @@ class ResettingPage(NavWidget): def show_event(self): super().show_event() self._nav_bar._alpha = 0.0 # not dismissable + self._show_time = rl.get_time() def _back_enabled(self) -> bool: return False def _render(self, _): + t = (rl.get_time() - self._show_time) % (self.DOT_STEP * 2) + dots = "." * min(int(t / (self.DOT_STEP / 4)), 3) + self._resetting_card.set_value(f"this may take up to\na minute{dots}") self._resetting_card.render(rl.Rectangle( self._rect.x + self._rect.width / 2 - self._resetting_card.rect.width / 2, self._rect.y + self._rect.height / 2 - self._resetting_card.rect.height / 2, @@ -68,9 +76,9 @@ class Reset(Scroller): self._resetting_page = ResettingPage() self._reset_failed_page = ResetFailedPage() - self._reset_button = BigConfirmationCircleButton("erase\ndevice", gui_app.texture("icons_mici/settings/device/uninstall.png", 64, 64), + self._reset_button = BigConfirmationCircleButton("reset &\nerase", gui_app.texture("icons_mici/settings/device/uninstall.png", 70, 70), self._start_reset, red=True) - self._cancel_button = BigConfirmationCircleButton("normal\nstartup", gui_app.texture("icons_mici/settings/device/reboot.png", 64, 70), + self._cancel_button = BigConfirmationCircleButton("cancel", gui_app.texture("icons_mici/setup/cancel.png", 64, 64), gui_app.request_close, exit_on_confirm=False) self._reboot_button = BigConfirmationCircleButton("reboot\ndevice", gui_app.texture("icons_mici/settings/device/reboot.png", 64, 70), HARDWARE.reboot, exit_on_confirm=False) @@ -79,18 +87,22 @@ class Reset(Scroller): self._cancel_button.set_visible(mode != ResetMode.RECOVER) self._reboot_button.set_visible(mode == ResetMode.RECOVER) - main_card = GreyBigButton("factory reset", "all content and\nsettings will be erased", + main_card = GreyBigButton("factory reset", "resetting erases\nall user content & data", gui_app.texture("icons_mici/setup/factory_reset.png", 64, 64)) + self._scroller.add_widget(main_card) - if mode == ResetMode.RECOVER: - main_card.set_text("unable to mount\ndata partition") - main_card.set_value("it may be corrupted") + if mode != ResetMode.USER_RESET: + self._scroller.add_widget(GreyBigButton("", "Resetting erases all user content & data.")) + if mode == ResetMode.RECOVER: + main_card.set_value("user data partition\ncould not be mounted") + elif mode == ResetMode.TAP_RESET: + main_card.set_value("reset triggered by\ntapping the screen") self._scroller.add_widgets([ - main_card, - self._reset_button, + GreyBigButton("", "For a deeper reset, go to\nhttps://flash.comma.ai"), self._cancel_button, self._reboot_button, + self._reset_button, ]) def _do_erase(self): @@ -131,6 +143,8 @@ def main(): if len(sys.argv) > 1: if sys.argv[1] == '--recover': mode = ResetMode.RECOVER + elif sys.argv[1] == '--tap-reset': + mode = ResetMode.TAP_RESET gui_app.init_window("System Reset") reset = Reset(mode) diff --git a/system/ui/mici_setup.py b/system/ui/mici_setup.py index 40b4c7a881..9a339d1a7d 100755 --- a/system/ui/mici_setup.py +++ b/system/ui/mici_setup.py @@ -254,7 +254,7 @@ class FailedPage(NavScroller): GreyBigButton(title, description or "swipe down to go\nback and try again", gui_app.texture(icon, 64, 58)), self._reason_card, - BigConfirmationCircleButton("slide to reboot", gui_app.texture("icons_mici/settings/device/reboot.png", 64, 70), + BigConfirmationCircleButton("reboot\ndevice", gui_app.texture("icons_mici/settings/device/reboot.png", 64, 70), HARDWARE.reboot, exit_on_confirm=False), ]) From 6607283cec77308886714ae8a0a6d49b23c9b270 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Sat, 7 Mar 2026 02:17:36 -0800 Subject: [PATCH 068/141] mici ui: engaged confirmation buttons (#37589) * do deviec * clean up * clean up * todo * action text * back --- selfdrive/ui/mici/layouts/settings/device.py | 55 ++++++++++---------- 1 file changed, 27 insertions(+), 28 deletions(-) diff --git a/selfdrive/ui/mici/layouts/settings/device.py b/selfdrive/ui/mici/layouts/settings/device.py index d4c8fda5ce..2be32d66bd 100644 --- a/selfdrive/ui/mici/layouts/settings/device.py +++ b/selfdrive/ui/mici/layouts/settings/device.py @@ -64,33 +64,31 @@ class MiciFccModal(NavRawScrollPanel): rl.draw_texture_ex(self._fcc_logo, fcc_pos, 0.0, 1.0, rl.WHITE) -def _engaged_confirmation_callback(callback: Callable, action_text: str): +def _engaged_confirmation_click(callback: Callable, action_text: str, icon: rl.Texture, exit_on_confirm: bool = True, red: bool = False): if not ui_state.engaged: def confirm_callback(): # Check engaged again in case it changed while the dialog was open + # TODO: if true, we stay on the dialog if not exit_on_confirm until normal onroad timeout if not ui_state.engaged: callback() - red = False - if action_text == "power off": - icon = gui_app.texture("icons_mici/settings/device/power.png", 64, 66) - red = True - elif action_text == "reboot": - icon = gui_app.texture("icons_mici/settings/device/reboot.png", 64, 70) - elif action_text == "reset": - icon = gui_app.texture("icons_mici/settings/device/lkas.png", 122, 64) - elif action_text == "uninstall": - icon = gui_app.texture("icons_mici/settings/device/uninstall.png", 64, 64) - else: - # TODO: check - icon = gui_app.texture("icons_mici/settings/comma_icon.png", 36, 64) - - dlg: BigConfirmationDialog | BigDialog = BigConfirmationDialog(f"slide to\n{action_text.lower()}", icon, confirm_callback, - red=red, exit_on_confirm=action_text == "reset") - gui_app.push_widget(dlg) + gui_app.push_widget(BigConfirmationDialog(f"slide to\n{action_text.lower()}", icon, confirm_callback, exit_on_confirm=exit_on_confirm, red=red)) else: - dlg = BigDialog(f"Disengage to {action_text}", "") - gui_app.push_widget(dlg) + gui_app.push_widget(BigDialog(f"Disengage to {action_text}", "")) + + +class EngagedConfirmationCircleButton(BigCircleButton): + def __init__(self, title: str, icon: rl.Texture, callback: Callable[[], None], exit_on_confirm: bool = True, + red: bool = False, icon_offset: tuple[int, int] = (0, 0)): + super().__init__(icon, red, icon_offset) + self.set_click_callback(lambda: _engaged_confirmation_click(callback, title, icon, exit_on_confirm=exit_on_confirm, red=red)) + + +class EngagedConfirmationButton(BigButton): + def __init__(self, text: str, action_text: str, icon: rl.Texture, callback: Callable[[], None], + exit_on_confirm: bool = True, red: bool = False): + super().__init__(text, "", icon) + self.set_click_callback(lambda: _engaged_confirmation_click(callback, action_text, icon, exit_on_confirm=exit_on_confirm, red=red)) class DeviceInfoLayoutMici(Widget): @@ -310,17 +308,18 @@ class DeviceLayoutMici(NavScroller): def uninstall_openpilot_callback(): ui_state.params.put_bool("DoUninstall", True) - reset_calibration_btn = BigButton("reset calibration", "", gui_app.texture("icons_mici/settings/device/lkas.png", 122, 64)) - reset_calibration_btn.set_click_callback(lambda: _engaged_confirmation_callback(reset_calibration_callback, "reset")) + reset_calibration_btn = EngagedConfirmationButton("reset calibration", "reset", gui_app.texture("icons_mici/settings/device/lkas.png", 122, 64), + reset_calibration_callback) - uninstall_openpilot_btn = BigButton("uninstall openpilot", "", gui_app.texture("icons_mici/settings/device/uninstall.png", 64, 64)) - uninstall_openpilot_btn.set_click_callback(lambda: _engaged_confirmation_callback(uninstall_openpilot_callback, "uninstall")) + uninstall_openpilot_btn = EngagedConfirmationButton("uninstall openpilot", "uninstall", + gui_app.texture("icons_mici/settings/device/uninstall.png", 64, 64), + uninstall_openpilot_callback, exit_on_confirm=False) - reboot_btn = BigCircleButton(gui_app.texture("icons_mici/settings/device/reboot.png", 64, 70)) - reboot_btn.set_click_callback(lambda: _engaged_confirmation_callback(reboot_callback, "reboot")) + reboot_btn = EngagedConfirmationCircleButton("reboot", gui_app.texture("icons_mici/settings/device/reboot.png", 64, 70), + reboot_callback, exit_on_confirm=False) - self._power_off_btn = BigCircleButton(gui_app.texture("icons_mici/settings/device/power.png", 64, 66), red=True) - self._power_off_btn.set_click_callback(lambda: _engaged_confirmation_callback(power_off_callback, "power off")) + self._power_off_btn = EngagedConfirmationCircleButton("power off", gui_app.texture("icons_mici/settings/device/power.png", 64, 66), + power_off_callback, exit_on_confirm=False, red=True) self._power_off_btn.set_visible(lambda: not ui_state.ignition) regulatory_btn = BigButton("regulatory info", "", gui_app.texture("icons_mici/settings/device/info.png", 64, 64)) From e35513afc458e4c7b970274ef30ecc8f0cc58629 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Sat, 7 Mar 2026 02:55:10 -0800 Subject: [PATCH 069/141] ui: fix 1px overshoot on NavWidget show (#37593) fix --- system/ui/widgets/nav_widget.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/system/ui/widgets/nav_widget.py b/system/ui/widgets/nav_widget.py index 5cf8715c0c..d397fe441a 100644 --- a/system/ui/widgets/nav_widget.py +++ b/system/ui/widgets/nav_widget.py @@ -149,8 +149,9 @@ class NavWidget(Widget, abc.ABC): new_y = self._rect.height + DISMISS_PUSH_OFFSET new_y = round(self._y_pos_filter.update(new_y)) - if abs(new_y) < 1 and self._y_pos_filter.velocity.x == 0.0: + if abs(new_y) < 1 and abs(self._y_pos_filter.velocity.x) < 0.5: new_y = self._y_pos_filter.x = 0.0 + self._y_pos_filter.velocity.x = 0.0 if self._shown_callback is not None: self._shown_callback() @@ -223,6 +224,7 @@ class NavWidget(Widget, abc.ABC): # Start NavWidget off-screen, no matter how tall it is self._y_pos_filter.update_alpha(0.1) self._y_pos_filter.x = gui_app.height + self._y_pos_filter.velocity.x = 0.0 self._nav_bar_y_filter.x = -NAV_BAR_MARGIN - NAV_BAR_HEIGHT self._nav_bar_show_time = rl.get_time() From 024e2af2692b77033f7d04c46d3b3bda3fbe9c73 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Sat, 7 Mar 2026 03:10:29 -0800 Subject: [PATCH 070/141] slider: use self.confirmed --- system/ui/widgets/slider.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/system/ui/widgets/slider.py b/system/ui/widgets/slider.py index 203c9eb4a0..43fb97be80 100644 --- a/system/ui/widgets/slider.py +++ b/system/ui/widgets/slider.py @@ -100,7 +100,7 @@ class SliderBase(Widget, abc.ABC): activated_pos = int(-self._bg_txt.width + self._circle_bg_txt.width) self._scroll_x_circle = max(min(self._scroll_x_circle, 0), activated_pos) - if self._confirmed_time > 0: + if self.confirmed: # swiped left to confirm self._scroll_x_circle_filter.update(activated_pos) @@ -129,7 +129,7 @@ class SliderBase(Widget, abc.ABC): btn_x = bg_txt_x + self._bg_txt.width - self._circle_bg_txt.width + self._scroll_x_circle_filter.x btn_y = self._rect.y + (self._rect.height - self._circle_bg_txt.height) / 2 - if self._confirmed_time == 0.0 or self._scroll_x_circle > 0: + if not self.confirmed: self._label.set_text_color(rl.Color(255, 255, 255, int(255 * 0.65 * (1.0 - self.slider_percentage) * self._opacity_filter.x))) label_rect = rl.Rectangle( self._rect.x + 20, @@ -140,7 +140,7 @@ class SliderBase(Widget, abc.ABC): self._label.render(label_rect) # circle and arrow - circle_bg_txt = self._circle_bg_pressed_txt if self._is_dragging_circle or self._confirmed_time > 0 else self._circle_bg_txt + circle_bg_txt = self._circle_bg_pressed_txt if self._is_dragging_circle or self.confirmed else self._circle_bg_txt rl.draw_texture_ex(circle_bg_txt, rl.Vector2(btn_x, btn_y), 0.0, 1.0, white) arrow_x = btn_x + (self._circle_bg_txt.width - self._circle_arrow_txt.width) / 2 From 797b769478e2b1fc2d640da3d50ad69c123647b1 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Sat, 7 Mar 2026 04:32:47 -0800 Subject: [PATCH 071/141] ui: sliders bounce (#37595) * sliders bounce * start page should bounce too * clean up * bouncy sliders * bouncy everything * tiny bounce * clean up * no scroll bounce --- system/ui/mici_setup.py | 4 ++-- system/ui/widgets/slider.py | 17 ++++++++++++----- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/system/ui/mici_setup.py b/system/ui/mici_setup.py index 9a339d1a7d..33dfe98f37 100755 --- a/system/ui/mici_setup.py +++ b/system/ui/mici_setup.py @@ -13,7 +13,7 @@ from collections.abc import Callable import pyray as rl from cereal import log -from openpilot.common.filter_simple import FirstOrderFilter +from openpilot.common.filter_simple import BounceFilter from openpilot.system.hardware import HARDWARE, TICI from openpilot.common.realtime import config_realtime_process, set_core_affinity from openpilot.common.swaglog import cloudlog @@ -122,7 +122,7 @@ class StartPage(Widget): self._start_bg_txt = gui_app.texture("icons_mici/setup/start_button.png", 500, 224, keep_aspect_ratio=False) self._start_bg_pressed_txt = gui_app.texture("icons_mici/setup/start_button_pressed.png", 500, 224, keep_aspect_ratio=False) - self._scale_filter = FirstOrderFilter(1.0, 0.1, 1 / gui_app.target_fps) + self._scale_filter = BounceFilter(1.0, 0.1, 1 / gui_app.target_fps) self._click_delay = 0.075 def _render(self, rect: rl.Rectangle): diff --git a/system/ui/widgets/slider.py b/system/ui/widgets/slider.py index 43fb97be80..900efed73a 100644 --- a/system/ui/widgets/slider.py +++ b/system/ui/widgets/slider.py @@ -6,12 +6,13 @@ import pyray as rl from openpilot.system.ui.lib.application import gui_app, FontWeight from openpilot.system.ui.widgets import Widget from openpilot.system.ui.widgets.label import UnifiedLabel -from openpilot.common.filter_simple import FirstOrderFilter +from openpilot.common.filter_simple import FirstOrderFilter, BounceFilter class SliderBase(Widget, abc.ABC): HORIZONTAL_PADDING = 8 CONFIRM_DELAY = 0.2 + PRESSED_SCALE = 1.07 _bg_txt: rl.Texture _circle_bg_txt: rl.Texture @@ -33,6 +34,8 @@ class SliderBase(Widget, abc.ABC): self._start_x_circle = 0.0 self._scroll_x_circle = 0.0 self._scroll_x_circle_filter = FirstOrderFilter(0, 0.05, 1 / gui_app.target_fps) + self._circle_scale_filter = BounceFilter(1.0, 0.1, 1 / gui_app.target_fps) + self._click_delay = 0.075 self._is_dragging_circle = False @@ -139,12 +142,16 @@ class SliderBase(Widget, abc.ABC): ) self._label.render(label_rect) - # circle and arrow - circle_bg_txt = self._circle_bg_pressed_txt if self._is_dragging_circle or self.confirmed else self._circle_bg_txt - rl.draw_texture_ex(circle_bg_txt, rl.Vector2(btn_x, btn_y), 0.0, 1.0, white) + # circle and arrow with grow animation + circle_pressed = self._is_dragging_circle or self.confirmed or self.is_pressed + circle_bg_txt = self._circle_bg_pressed_txt if circle_pressed else self._circle_bg_txt + scale = self._circle_scale_filter.update(self.PRESSED_SCALE if circle_pressed else 1.0) + scaled_btn_x = btn_x + (self._circle_bg_txt.width * (1 - scale)) / 2 + scaled_btn_y = btn_y + (self._circle_bg_txt.height * (1 - scale)) / 2 + rl.draw_texture_ex(circle_bg_txt, rl.Vector2(scaled_btn_x, scaled_btn_y), 0.0, scale, white) arrow_x = btn_x + (self._circle_bg_txt.width - self._circle_arrow_txt.width) / 2 - arrow_y = btn_y + (self._circle_bg_txt.height - self._circle_arrow_txt.height) / 2 + arrow_y = scaled_btn_y + (self._circle_bg_txt.height - self._circle_arrow_txt.height) / 2 rl.draw_texture_ex(self._circle_arrow_txt, rl.Vector2(arrow_x, arrow_y), 0.0, 1.0, white) From 4bf2bfb122f86ef68659f9dad27693f4f3451e8b Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Sat, 7 Mar 2026 05:07:03 -0800 Subject: [PATCH 072/141] ui: child widget support (#37594) * child widgets! * cmt * missing * group * add debug flag * use in scroller * not clean yet * restore --- selfdrive/ui/mici/layouts/home.py | 1 + system/ui/widgets/__init__.py | 43 +++++++++++++++++++++++++++---- system/ui/widgets/nav_widget.py | 3 +-- system/ui/widgets/scroller.py | 10 +------ 4 files changed, 41 insertions(+), 16 deletions(-) diff --git a/selfdrive/ui/mici/layouts/home.py b/selfdrive/ui/mici/layouts/home.py index 730a7ca7b7..e87e62503e 100644 --- a/selfdrive/ui/mici/layouts/home.py +++ b/selfdrive/ui/mici/layouts/home.py @@ -111,6 +111,7 @@ class MiciHomeLayout(Widget): self._version_commit_label = MiciLabel("", font_size=36, color=rl.GRAY, font_weight=FontWeight.ROMAN) def show_event(self): + super().show_event() self._version_text = self._get_version_text() self._update_params() diff --git a/system/ui/widgets/__init__.py b/system/ui/widgets/__init__.py index 568f58b985..2a0dc7be2d 100644 --- a/system/ui/widgets/__init__.py +++ b/system/ui/widgets/__init__.py @@ -3,6 +3,7 @@ from __future__ import annotations import abc import pyray as rl from enum import IntEnum +from typing import TypeVar from collections.abc import Callable from openpilot.system.ui.lib.application import gui_app, MousePos, MAX_TOUCH_SLOTS, MouseEvent @@ -13,6 +14,10 @@ except ImportError: awake = True device = Device() +W = TypeVar('W', bound='Widget') + +DEBUG = True + class DialogResult(IntEnum): CANCEL = 0 @@ -24,11 +29,14 @@ class Widget(abc.ABC): def __init__(self): self._rect: rl.Rectangle = rl.Rectangle(0, 0, 0, 0) self._parent_rect: rl.Rectangle | None = None + self._children: list[Widget] = [] + + self._enabled: bool | Callable[[], bool] = True + self._is_visible: bool | Callable[[], bool] = True + self.__is_pressed = [False] * MAX_TOUCH_SLOTS # if current mouse/touch down started within the widget's rectangle self.__tracking_is_pressed = [False] * MAX_TOUCH_SLOTS - self._enabled: bool | Callable[[], bool] = True - self._is_visible: bool | Callable[[], bool] = True self._touch_valid_callback: Callable[[], bool] | None = None self._click_delay: float | None = None # seconds to hold is_pressed after release self._click_release_time: float | None = None @@ -197,12 +205,37 @@ class Widget(abc.ABC): """Optionally handle mouse events. This is called before rendering.""" # Default implementation does nothing, can be overridden by subclasses + def _child(self, widget: W) -> W: + """ + Register a widget as a child. Lifecycle events (show/hide) propagate to registered children. + - If the widget is pushed onto the nav stack, do NOT register it (gui_app manages its lifecycle). + - If the widget is rendered inline in _render(), register it. + """ + assert widget not in self._children, f"{type(widget).__name__} already a child of {type(self).__name__}" + self._children.append(widget) + return widget + + _show_hide_depth = 0 + def show_event(self): - """Optionally handle show event. Parent must manually call this""" - # TODO: iterate through all child objects, check for subclassing from Widget/Layout (Scroller) + """Called when widget becomes visible. Propagates to registered children.""" + if DEBUG: + print(f"{' ' * Widget._show_hide_depth}show_event: {type(self).__name__}") + Widget._show_hide_depth += 1 + for child in self._children: + child.show_event() + if DEBUG: + Widget._show_hide_depth -= 1 def hide_event(self): - """Optionally handle hide event. Parent must manually call this""" + """Called when widget is hidden. Propagates to registered children.""" + if DEBUG: + print(f"{' ' * Widget._show_hide_depth}hide_event: {type(self).__name__}") + Widget._show_hide_depth += 1 + for child in self._children: + child.hide_event() + if DEBUG: + Widget._show_hide_depth -= 1 def dismiss(self, callback: Callable[[], None] | None = None): """Immediately dismiss the widget, firing the callback after.""" diff --git a/system/ui/widgets/nav_widget.py b/system/ui/widgets/nav_widget.py index d397fe441a..6f2bbd025b 100644 --- a/system/ui/widgets/nav_widget.py +++ b/system/ui/widgets/nav_widget.py @@ -69,7 +69,7 @@ class NavWidget(Widget, abc.ABC): self._shown_callback: Callable[[], None] | None = None # transient callback fired after show animation completes # TODO: move this state into NavBar - self._nav_bar = NavBar() + self._nav_bar = self._child(NavBar()) self._nav_bar_show_time = 0.0 self._nav_bar_y_filter = FirstOrderFilter(0.0, 0.1, 1 / gui_app.target_fps) @@ -214,7 +214,6 @@ class NavWidget(Widget, abc.ABC): def show_event(self): super().show_event() - self._nav_bar.show_event() # Reset state self._drag_start_pos = None diff --git a/system/ui/widgets/scroller.py b/system/ui/widgets/scroller.py index 5becef7939..65f23739c1 100644 --- a/system/ui/widgets/scroller.py +++ b/system/ui/widgets/scroller.py @@ -422,18 +422,10 @@ class Scroller(Widget): """Wrapper for _Scroller so that children do not need to call events or pass down enabled for nav stack.""" def __init__(self, **kwargs): super().__init__() - self._scroller = _Scroller([], **kwargs) + self._scroller = self._child(_Scroller([], **kwargs)) # pass down enabled to child widget for nav stack self._scroller.set_enabled(lambda: self.enabled) - def show_event(self): - super().show_event() - self._scroller.show_event() - - def hide_event(self): - super().hide_event() - self._scroller.hide_event() - def _render(self, _): self._scroller.render(self._rect) From 4742bf0230112dfbd12af50fa2f27b82885d4081 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Sat, 7 Mar 2026 05:08:44 -0800 Subject: [PATCH 073/141] HBoxLayout: use children --- system/ui/widgets/layouts.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/system/ui/widgets/layouts.py b/system/ui/widgets/layouts.py index 6f97fe5ed8..6fd3bffd8c 100644 --- a/system/ui/widgets/layouts.py +++ b/system/ui/widgets/layouts.py @@ -21,7 +21,6 @@ class HBoxLayout(Widget): def __init__(self, widgets: list[Widget] | None = None, spacing: int = 0, alignment: Alignment = Alignment.LEFT | Alignment.V_CENTER): super().__init__() - self._widgets: list[Widget] = [] self._spacing = spacing self._alignment = alignment @@ -31,13 +30,13 @@ class HBoxLayout(Widget): @property def widgets(self) -> list[Widget]: - return self._widgets + return self._children def add_widget(self, widget: Widget) -> None: - self._widgets.append(widget) + self._child(widget) def _render(self, _): - visible_widgets = [w for w in self._widgets if w.is_visible] + visible_widgets = [w for w in self._children if w.is_visible] cur_offset_x = 0 From 7a5d8a813b5dafcb9d5420b7807e2aacd7de2856 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Sat, 7 Mar 2026 05:08:58 -0800 Subject: [PATCH 074/141] Turn off Widget debug mode --- system/ui/widgets/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system/ui/widgets/__init__.py b/system/ui/widgets/__init__.py index 2a0dc7be2d..4ce1c1b694 100644 --- a/system/ui/widgets/__init__.py +++ b/system/ui/widgets/__init__.py @@ -16,7 +16,7 @@ except ImportError: W = TypeVar('W', bound='Widget') -DEBUG = True +DEBUG = False class DialogResult(IntEnum): From 6e851ff886eb100b89f0ac726681ef284d82c87f Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Sat, 7 Mar 2026 05:21:06 -0800 Subject: [PATCH 075/141] ui: missing super show event (#37597) missing --- selfdrive/ui/layouts/home.py | 1 + selfdrive/ui/layouts/settings/developer.py | 1 + selfdrive/ui/layouts/settings/device.py | 1 + selfdrive/ui/layouts/settings/software.py | 1 + selfdrive/ui/layouts/settings/toggles.py | 1 + selfdrive/ui/widgets/exp_mode_button.py | 1 + selfdrive/ui/widgets/offroad_alerts.py | 1 + system/ui/widgets/list_view.py | 1 + system/ui/widgets/network.py | 4 ++++ 9 files changed, 12 insertions(+) diff --git a/selfdrive/ui/layouts/home.py b/selfdrive/ui/layouts/home.py index a8404a20c3..183c2d4588 100644 --- a/selfdrive/ui/layouts/home.py +++ b/selfdrive/ui/layouts/home.py @@ -62,6 +62,7 @@ class HomeLayout(Widget): self._setup_callbacks() def show_event(self): + super().show_event() self._exp_mode_button.show_event() self.last_refresh = time.monotonic() self._refresh() diff --git a/selfdrive/ui/layouts/settings/developer.py b/selfdrive/ui/layouts/settings/developer.py index 17ab60172a..bd064ab834 100644 --- a/selfdrive/ui/layouts/settings/developer.py +++ b/selfdrive/ui/layouts/settings/developer.py @@ -100,6 +100,7 @@ class DeveloperLayout(Widget): self._scroller.render(rect) def show_event(self): + super().show_event() self._scroller.show_event() self._update_toggles() diff --git a/selfdrive/ui/layouts/settings/device.py b/selfdrive/ui/layouts/settings/device.py index 751373dba6..5c3dae869b 100644 --- a/selfdrive/ui/layouts/settings/device.py +++ b/selfdrive/ui/layouts/settings/device.py @@ -72,6 +72,7 @@ class DeviceLayout(Widget): self._power_off_btn.action_item.right_button.set_visible(ui_state.is_offroad()) def show_event(self): + super().show_event() self._scroller.show_event() def _render(self, rect): diff --git a/selfdrive/ui/layouts/settings/software.py b/selfdrive/ui/layouts/settings/software.py index c197b45453..83a66ef3bd 100644 --- a/selfdrive/ui/layouts/settings/software.py +++ b/selfdrive/ui/layouts/settings/software.py @@ -80,6 +80,7 @@ class SoftwareLayout(Widget): ], line_separator=True, spacing=0) def show_event(self): + super().show_event() self._scroller.show_event() def _render(self, rect): diff --git a/selfdrive/ui/layouts/settings/toggles.py b/selfdrive/ui/layouts/settings/toggles.py index dbe5e241aa..711392bdb0 100644 --- a/selfdrive/ui/layouts/settings/toggles.py +++ b/selfdrive/ui/layouts/settings/toggles.py @@ -148,6 +148,7 @@ class TogglesLayout(Widget): ui_state.personality = personality def show_event(self): + super().show_event() self._scroller.show_event() self._update_toggles() diff --git a/selfdrive/ui/widgets/exp_mode_button.py b/selfdrive/ui/widgets/exp_mode_button.py index faa3bf877f..0b5bff7da4 100644 --- a/selfdrive/ui/widgets/exp_mode_button.py +++ b/selfdrive/ui/widgets/exp_mode_button.py @@ -20,6 +20,7 @@ class ExperimentalModeButton(Widget): self.experimental_pixmap = gui_app.texture("icons/experimental_grey.png", self.img_width, self.img_width) def show_event(self): + super().show_event() self.experimental_mode = self.params.get_bool("ExperimentalMode") def _get_gradient_colors(self): diff --git a/selfdrive/ui/widgets/offroad_alerts.py b/selfdrive/ui/widgets/offroad_alerts.py index a87727e27f..110ca714a9 100644 --- a/selfdrive/ui/widgets/offroad_alerts.py +++ b/selfdrive/ui/widgets/offroad_alerts.py @@ -118,6 +118,7 @@ class AbstractAlert(Widget, ABC): self.scroll_panel = GuiScrollPanel() def show_event(self): + super().show_event() self.scroll_panel.set_offset(0) def set_dismiss_callback(self, callback: Callable): diff --git a/system/ui/widgets/list_view.py b/system/ui/widgets/list_view.py index 32bf01cfc8..1cf530a66a 100644 --- a/system/ui/widgets/list_view.py +++ b/system/ui/widgets/list_view.py @@ -293,6 +293,7 @@ class ListItem(Widget): self._prev_description: str | None = self.description def show_event(self): + super().show_event() self._set_description_visible(False) def set_description_opened_callback(self, callback: Callable) -> None: diff --git a/system/ui/widgets/network.py b/system/ui/widgets/network.py index 668565a033..9027dc4c2a 100644 --- a/system/ui/widgets/network.py +++ b/system/ui/widgets/network.py @@ -75,10 +75,12 @@ class NetworkUI(Widget): self._nav_button.set_click_callback(self._cycle_panel) def show_event(self): + super().show_event() self._set_current_panel(PanelType.WIFI) self._wifi_panel.show_event() def hide_event(self): + super().hide_event() self._wifi_panel.hide_event() def _cycle_panel(self): @@ -299,10 +301,12 @@ class WifiManagerUI(Widget): disconnected=self._on_disconnected) def show_event(self): + super().show_event() # start/stop scanning when widget is visible self._wifi_manager.set_active(True) def hide_event(self): + super().hide_event() self._wifi_manager.set_active(False) def _load_icons(self): From 6a3dcc74e8c9357f632ac63d64815aff975cfd59 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Sat, 7 Mar 2026 05:28:51 -0800 Subject: [PATCH 076/141] ui: mark more child widgets (#37596) * do onboarding * do tici * clean * hide event reset state :( --- selfdrive/ui/mici/layouts/onboarding.py | 5 +---- system/ui/widgets/network.py | 11 +++-------- 2 files changed, 4 insertions(+), 12 deletions(-) diff --git a/selfdrive/ui/mici/layouts/onboarding.py b/selfdrive/ui/mici/layouts/onboarding.py index ac19628738..781f60ee21 100644 --- a/selfdrive/ui/mici/layouts/onboarding.py +++ b/selfdrive/ui/mici/layouts/onboarding.py @@ -266,12 +266,9 @@ class TrainingGuide(NavWidget): TrainingGuideRecordFront(continue_callback=completed_callback), ] + self._child(self._steps[0]) self._steps[0].set_enabled(lambda: self.enabled and not self.is_dismissing) # for nav stack - def show_event(self): - super().show_event() - self._steps[0].show_event() - def _render(self, _): self._steps[0].render(self._rect) diff --git a/system/ui/widgets/network.py b/system/ui/widgets/network.py index 9027dc4c2a..e739eef63d 100644 --- a/system/ui/widgets/network.py +++ b/system/ui/widgets/network.py @@ -69,19 +69,14 @@ class NetworkUI(Widget): super().__init__() self._wifi_manager = wifi_manager self._current_panel: PanelType = PanelType.WIFI - self._wifi_panel = WifiManagerUI(wifi_manager) - self._advanced_panel = AdvancedNetworkSettings(wifi_manager) - self._nav_button = NavButton(tr("Advanced")) + self._wifi_panel = self._child(WifiManagerUI(wifi_manager)) + self._advanced_panel = self._child(AdvancedNetworkSettings(wifi_manager)) + self._nav_button = self._child(NavButton(tr("Advanced"))) self._nav_button.set_click_callback(self._cycle_panel) def show_event(self): super().show_event() self._set_current_panel(PanelType.WIFI) - self._wifi_panel.show_event() - - def hide_event(self): - super().hide_event() - self._wifi_panel.hide_event() def _cycle_panel(self): if self._current_panel == PanelType.WIFI: From acec60d19e5b08a397453512a153d55330dd21fd Mon Sep 17 00:00:00 2001 From: David <49467229+TheSecurityDev@users.noreply.github.com> Date: Sat, 7 Mar 2026 20:23:20 -0600 Subject: [PATCH 077/141] docs: update WSL2 hardware acceleration note (#37603) * docs: update WSL2 hardware acceleration note for improved UI performance * space * clarify --- tools/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/README.md b/tools/README.md index d52c8f4522..90696ab4e6 100644 --- a/tools/README.md +++ b/tools/README.md @@ -38,7 +38,7 @@ scons -u -j$(nproc) Follow [these instructions](https://docs.microsoft.com/en-us/windows/wsl/install) to setup the WSL and install the `Ubuntu-24.04` distribution. Once your Ubuntu WSL environment is setup, follow the Linux setup instructions to finish setting up your environment. See [these instructions](https://learn.microsoft.com/en-us/windows/wsl/tutorials/gui-apps) for running GUI apps. -**NOTE**: If you are running WSL and any GUIs are failing (segfaulting or other strange issues) even after following the steps above, you may need to enable software rendering with `LIBGL_ALWAYS_SOFTWARE=1`, e.g. `LIBGL_ALWAYS_SOFTWARE=1 selfdrive/ui/ui`. +**NOTE**: If you are running WSL 2 and experiencing performance issues with the UI or simulator, you may need to explicitly enable hardware acceleration by setting `GALLIUM_DRIVER=d3d12` before commands. Add `export GALLIUM_DRIVER=d3d12` to your `~/.bashrc` file to make it automatic for future sessions. ## CTF Learn about the openpilot ecosystem and tools by playing our [CTF](/tools/CTF.md). From 9d7edbf57a52059adc2230553437031571849fd9 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Sat, 7 Mar 2026 23:11:38 -0800 Subject: [PATCH 078/141] ui: remove MiciLabel (#37599) * unified * newl * do home too * pairing * match style * delete micilabel! * default color --- selfdrive/ui/mici/layouts/home.py | 18 +- selfdrive/ui/mici/layouts/settings/device.py | 13 +- selfdrive/ui/mici/widgets/pairing_dialog.py | 7 +- system/ui/widgets/label.py | 170 +------------------ 4 files changed, 28 insertions(+), 180 deletions(-) diff --git a/selfdrive/ui/mici/layouts/home.py b/selfdrive/ui/mici/layouts/home.py index e87e62503e..da5b0ac5e0 100644 --- a/selfdrive/ui/mici/layouts/home.py +++ b/selfdrive/ui/mici/layouts/home.py @@ -7,7 +7,7 @@ from collections.abc import Callable from openpilot.system.ui.widgets import Widget from openpilot.system.ui.widgets.layouts import HBoxLayout from openpilot.system.ui.widgets.icon_widget import IconWidget -from openpilot.system.ui.widgets.label import MiciLabel, UnifiedLabel +from openpilot.system.ui.widgets.label import UnifiedLabel from openpilot.system.ui.lib.application import gui_app, FontWeight, MousePos from openpilot.selfdrive.ui.ui_state import ui_state from openpilot.system.version import RELEASE_BRANCHES @@ -103,12 +103,12 @@ class MiciHomeLayout(Widget): self._mic_icon, ], spacing=18) - self._openpilot_label = MiciLabel("openpilot", font_size=96, color=rl.Color(255, 255, 255, int(255 * 0.9)), font_weight=FontWeight.DISPLAY) - self._version_label = MiciLabel("", font_size=36, font_weight=FontWeight.ROMAN) - self._large_version_label = MiciLabel("", font_size=64, color=rl.GRAY, font_weight=FontWeight.ROMAN) - self._date_label = MiciLabel("", font_size=36, color=rl.GRAY, font_weight=FontWeight.ROMAN) + self._openpilot_label = UnifiedLabel("openpilot", font_size=96, font_weight=FontWeight.DISPLAY, max_width=480, wrap_text=False) + self._version_label = UnifiedLabel("", font_size=36, font_weight=FontWeight.ROMAN, max_width=480, wrap_text=False) + self._large_version_label = UnifiedLabel("", font_size=64, text_color=rl.GRAY, font_weight=FontWeight.ROMAN, max_width=480, wrap_text=False) + self._date_label = UnifiedLabel("", font_size=36, text_color=rl.GRAY, font_weight=FontWeight.ROMAN, max_width=480, wrap_text=False) self._branch_label = UnifiedLabel("", font_size=36, text_color=rl.GRAY, font_weight=FontWeight.ROMAN, scroll=True) - self._version_commit_label = MiciLabel("", font_size=36, color=rl.GRAY, font_weight=FontWeight.ROMAN) + self._version_commit_label = UnifiedLabel("", font_size=36, text_color=rl.GRAY, font_weight=FontWeight.ROMAN, max_width=480, wrap_text=False) def show_event(self): super().show_event() @@ -183,12 +183,12 @@ class MiciHomeLayout(Widget): self._version_label.render() self._date_label.set_text(" " + self._version_text[3]) - self._date_label.set_position(version_pos.x + self._version_label.rect.width + 10, version_pos.y) + self._date_label.set_position(version_pos.x + self._version_label.text_width + 10, version_pos.y) self._date_label.render() - self._branch_label.set_max_width(gui_app.width - self._version_label.rect.width - self._date_label.rect.width - 32) + self._branch_label.set_max_width(gui_app.width - self._version_label.text_width - self._date_label.text_width - 32) self._branch_label.set_text(" " + ("release" if release_branch else self._version_text[1])) - self._branch_label.set_position(version_pos.x + self._version_label.rect.width + self._date_label.rect.width + 20, version_pos.y) + self._branch_label.set_position(version_pos.x + self._version_label.text_width + self._date_label.text_width + 20, version_pos.y) self._branch_label.render() if not release_branch: diff --git a/selfdrive/ui/mici/layouts/settings/device.py b/selfdrive/ui/mici/layouts/settings/device.py index 2be32d66bd..909e30ac71 100644 --- a/selfdrive/ui/mici/layouts/settings/device.py +++ b/selfdrive/ui/mici/layouts/settings/device.py @@ -17,7 +17,7 @@ from openpilot.system.ui.lib.application import gui_app, FontWeight, MousePos from openpilot.system.ui.lib.multilang import tr from openpilot.system.ui.widgets import Widget from openpilot.selfdrive.ui.ui_state import device, ui_state -from openpilot.system.ui.widgets.label import MiciLabel +from openpilot.system.ui.widgets.label import UnifiedLabel from openpilot.system.ui.widgets.html_render import HtmlModal, HtmlRenderer from openpilot.system.athena.registration import UNREGISTERED_DONGLE_ID @@ -98,14 +98,15 @@ class DeviceInfoLayoutMici(Widget): self.set_rect(rl.Rectangle(0, 0, 360, 180)) params = Params() - header_color = rl.Color(255, 255, 255, int(255 * 0.9)) subheader_color = rl.Color(255, 255, 255, int(255 * 0.9 * 0.65)) max_width = int(self._rect.width - 20) - self._dongle_id_label = MiciLabel("device ID", 48, width=max_width, color=header_color, font_weight=FontWeight.DISPLAY) - self._dongle_id_text_label = MiciLabel(params.get("DongleId") or 'N/A', 32, width=max_width, color=subheader_color, font_weight=FontWeight.ROMAN) + self._dongle_id_label = UnifiedLabel("device ID", 48, max_width=max_width, font_weight=FontWeight.DISPLAY, wrap_text=False) + self._dongle_id_text_label = UnifiedLabel(params.get("DongleId") or 'N/A', 32, max_width=max_width, text_color=subheader_color, + font_weight=FontWeight.ROMAN, wrap_text=False) - self._serial_number_label = MiciLabel("serial", 48, color=header_color, font_weight=FontWeight.DISPLAY) - self._serial_number_text_label = MiciLabel(params.get("HardwareSerial") or 'N/A', 32, width=max_width, color=subheader_color, font_weight=FontWeight.ROMAN) + self._serial_number_label = UnifiedLabel("serial", 48, max_width=max_width, font_weight=FontWeight.DISPLAY, wrap_text=False) + self._serial_number_text_label = UnifiedLabel(params.get("HardwareSerial") or 'N/A', 32, max_width=max_width, text_color=subheader_color, + font_weight=FontWeight.ROMAN, wrap_text=False) def _render(self, _): self._dongle_id_label.set_position(self._rect.x + 20, self._rect.y - 10) diff --git a/selfdrive/ui/mici/widgets/pairing_dialog.py b/selfdrive/ui/mici/widgets/pairing_dialog.py index 991cb05a8c..8421b516d2 100644 --- a/selfdrive/ui/mici/widgets/pairing_dialog.py +++ b/selfdrive/ui/mici/widgets/pairing_dialog.py @@ -9,7 +9,7 @@ from openpilot.common.params import Params from openpilot.selfdrive.ui.ui_state import ui_state from openpilot.system.ui.widgets.nav_widget import NavWidget from openpilot.system.ui.lib.application import FontWeight, gui_app -from openpilot.system.ui.widgets.label import MiciLabel +from openpilot.system.ui.widgets.label import UnifiedLabel class PairingDialog(NavWidget): @@ -24,8 +24,7 @@ class PairingDialog(NavWidget): self._last_qr_generation = float("-inf") self._txt_pair = gui_app.texture("icons_mici/settings/device/pair.png", 33, 60) - self._pair_label = MiciLabel("pair with comma connect", 48, font_weight=FontWeight.BOLD, - color=rl.Color(255, 255, 255, int(255 * 0.9)), line_height=40, wrap_text=True) + self._pair_label = UnifiedLabel("pair with comma connect", font_size=48, font_weight=FontWeight.BOLD, line_height=0.8) def _get_pairing_url(self) -> str: try: @@ -77,7 +76,7 @@ class PairingDialog(NavWidget): self._render_qr_code() label_x = self._rect.x + 8 + self._rect.height + 24 - self._pair_label.set_width(int(self._rect.width - label_x)) + self._pair_label.set_max_width(int(self._rect.width - label_x)) self._pair_label.set_position(label_x, self._rect.y + 16) self._pair_label.render() diff --git a/system/ui/widgets/label.py b/system/ui/widgets/label.py index 8ed9ec62f5..0b2444ec65 100644 --- a/system/ui/widgets/label.py +++ b/system/ui/widgets/label.py @@ -26,166 +26,6 @@ class ScrollState(IntEnum): SCROLLING = 1 -# TODO: merge anything new here to master -class MiciLabel(Widget): - def __init__(self, - text: str, - font_size: int = DEFAULT_TEXT_SIZE, - width: int | None = None, - color: rl.Color = DEFAULT_TEXT_COLOR, - font_weight: FontWeight = FontWeight.NORMAL, - alignment: int = rl.GuiTextAlignment.TEXT_ALIGN_LEFT, - alignment_vertical: int = rl.GuiTextAlignmentVertical.TEXT_ALIGN_TOP, - spacing: int = 0, - line_height: int | None = None, - elide_right: bool = True, - wrap_text: bool = False, - scroll: bool = False): - super().__init__() - self.text = text - self.wrapped_text: list[str] = [] - self.font_size = font_size - self.width = width - self.color = color - self.font_weight = font_weight - self.alignment = alignment - self.alignment_vertical = alignment_vertical - self.spacing = spacing - self.line_height = line_height if line_height is not None else font_size - self.elide_right = elide_right - self.wrap_text = wrap_text - self._height = 0 - - # Scroll state - self.scroll = scroll - self._needs_scroll = False - self._scroll_offset = 0 - self._scroll_pause_t: float | None = None - self._scroll_state: ScrollState = ScrollState.STARTING - - assert not (self.scroll and self.wrap_text), "Cannot enable both scroll and wrap_text" - assert not (self.scroll and self.elide_right), "Cannot enable both scroll and elide_right" - - self.set_text(text) - - @property - def text_height(self): - return self._height - - def set_font_size(self, font_size: int): - self.font_size = font_size - self.set_text(self.text) - - def set_width(self, width: int): - self.width = width - self._rect.width = width - self.set_text(self.text) - - def set_text(self, txt: str): - self.text = txt - text_size = measure_text_cached(gui_app.font(self.font_weight), self.text, self.font_size, self.spacing) - if self.width is not None: - self._rect.width = self.width - else: - self._rect.width = text_size.x - - if self.wrap_text: - self.wrapped_text = wrap_text(gui_app.font(self.font_weight), self.text, self.font_size, int(self._rect.width)) - self._height = len(self.wrapped_text) * self.line_height - elif self.scroll: - self._needs_scroll = self.scroll and text_size.x > self._rect.width - self._rect.height = text_size.y - - def set_color(self, color: rl.Color): - self.color = color - - def set_font_weight(self, font_weight: FontWeight): - self.font_weight = font_weight - self.set_text(self.text) - - def _render(self, rect: rl.Rectangle): - # Only scissor when we know there is a single scrolling line - if self._needs_scroll: - rl.begin_scissor_mode(int(rect.x), int(rect.y), int(rect.width), int(rect.height)) - - font = gui_app.font(self.font_weight) - - text_y_offset = 0 - # Draw the text in the specified rectangle - lines = self.wrapped_text or [self.text] - if self.alignment_vertical == rl.GuiTextAlignmentVertical.TEXT_ALIGN_BOTTOM: - lines = lines[::-1] - - for display_text in lines: - text_size = measure_text_cached(font, display_text, self.font_size, self.spacing) - - # Elide text to fit within the rectangle - if self.elide_right and text_size.x > rect.width: - ellipsis = "..." - left, right = 0, len(display_text) - while left < right: - mid = (left + right) // 2 - candidate = display_text[:mid] + ellipsis - candidate_size = measure_text_cached(font, candidate, self.font_size, self.spacing) - if candidate_size.x <= rect.width: - left = mid + 1 - else: - right = mid - display_text = display_text[: left - 1] + ellipsis if left > 0 else ellipsis - text_size = measure_text_cached(font, display_text, self.font_size, self.spacing) - - # Handle scroll state - elif self.scroll and self._needs_scroll: - if self._scroll_state == ScrollState.STARTING: - if self._scroll_pause_t is None: - self._scroll_pause_t = rl.get_time() + 2.0 - if rl.get_time() >= self._scroll_pause_t: - self._scroll_state = ScrollState.SCROLLING - self._scroll_pause_t = None - - elif self._scroll_state == ScrollState.SCROLLING: - self._scroll_offset -= 0.8 / 60. * gui_app.target_fps - # don't fully hide - if self._scroll_offset <= -text_size.x - self._rect.width / 3: - self._scroll_offset = 0 - self._scroll_state = ScrollState.STARTING - self._scroll_pause_t = None - - # Calculate horizontal position based on alignment - text_x = rect.x + { - rl.GuiTextAlignment.TEXT_ALIGN_LEFT: 0, - rl.GuiTextAlignment.TEXT_ALIGN_CENTER: (rect.width - text_size.x) / 2, - rl.GuiTextAlignment.TEXT_ALIGN_RIGHT: rect.width - text_size.x, - }.get(self.alignment, 0) + self._scroll_offset - - # Calculate vertical position based on alignment - text_y = rect.y + { - rl.GuiTextAlignmentVertical.TEXT_ALIGN_TOP: 0, - rl.GuiTextAlignmentVertical.TEXT_ALIGN_MIDDLE: (rect.height - text_size.y) / 2, - rl.GuiTextAlignmentVertical.TEXT_ALIGN_BOTTOM: rect.height - text_size.y, - }.get(self.alignment_vertical, 0) - text_y += text_y_offset - - rl.draw_text_ex(font, display_text, rl.Vector2(round(text_x), text_y), self.font_size, self.spacing, self.color) - # Draw 2nd instance for scrolling - if self._needs_scroll and self._scroll_state != ScrollState.STARTING: - text2_scroll_offset = text_size.x + self._rect.width / 3 - rl.draw_text_ex(font, display_text, rl.Vector2(round(text_x + text2_scroll_offset), text_y), self.font_size, self.spacing, self.color) - if self.alignment_vertical == rl.GuiTextAlignmentVertical.TEXT_ALIGN_BOTTOM: - text_y_offset -= self.line_height - else: - text_y_offset += self.line_height - - if self._needs_scroll: - # draw black fade on left and right - fade_width = 20 - rl.draw_rectangle_gradient_h(int(rect.x + rect.width - fade_width), int(rect.y), fade_width, int(rect.height), rl.BLANK, rl.BLACK) - if self._scroll_state != ScrollState.STARTING: - rl.draw_rectangle_gradient_h(int(rect.x), int(rect.y), fade_width, int(rect.height), rl.BLACK, rl.BLANK) - - rl.end_scissor_mode() - - # TODO: This should be a Widget class def gui_label( rect: rl.Rectangle, @@ -392,7 +232,7 @@ class Label(Widget): class UnifiedLabel(Widget): """ - Unified label widget that combines functionality from gui_label, gui_text_box, Label, and MiciLabel. + Unified label widget that combines functionality from gui_label, gui_text_box, and Label. Supports: - Emoji rendering @@ -467,6 +307,14 @@ class UnifiedLabel(Widget): """Get the current text content.""" return str(_resolve_value(self._text)) + @property + def font_size(self) -> int: + return self._font_size + + @property + def text_width(self) -> float: + return max((s.x for s in self._cached_line_sizes), default=0.0) + def set_text_color(self, color: rl.Color): """Update the text color.""" self._text_color = color From 1197ea9ab92223672ea3e9487ecc199a49113e1b Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Sun, 8 Mar 2026 00:13:08 -0800 Subject: [PATCH 079/141] sliders: fix clicking anywhere activates press (#37605) * fix * finish * fix --- system/ui/widgets/slider.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/system/ui/widgets/slider.py b/system/ui/widgets/slider.py index 900efed73a..7a10548acc 100644 --- a/system/ui/widgets/slider.py +++ b/system/ui/widgets/slider.py @@ -35,7 +35,7 @@ class SliderBase(Widget, abc.ABC): self._scroll_x_circle = 0.0 self._scroll_x_circle_filter = FirstOrderFilter(0, 0.05, 1 / gui_app.target_fps) self._circle_scale_filter = BounceFilter(1.0, 0.1, 1 / gui_app.target_fps) - self._click_delay = 0.075 + self._circle_press_time: float | None = None self._is_dragging_circle = False @@ -54,6 +54,7 @@ class SliderBase(Widget, abc.ABC): def reset(self): # reset all slider state self._is_dragging_circle = False + self._circle_press_time = None self._confirmed_time = 0.0 self._confirm_callback_called = False @@ -86,6 +87,7 @@ class SliderBase(Widget, abc.ABC): if rl.check_collision_point_rec(mouse_event.pos, circle_button_rect): self._start_x_circle = mouse_event.pos.x self._is_dragging_circle = True + self._circle_press_time = rl.get_time() elif mouse_event.left_released: # swiped to left @@ -143,7 +145,7 @@ class SliderBase(Widget, abc.ABC): self._label.render(label_rect) # circle and arrow with grow animation - circle_pressed = self._is_dragging_circle or self.confirmed or self.is_pressed + circle_pressed = self._is_dragging_circle or self.confirmed or (self._circle_press_time is not None and rl.get_time() - self._circle_press_time < 0.075) circle_bg_txt = self._circle_bg_pressed_txt if circle_pressed else self._circle_bg_txt scale = self._circle_scale_filter.update(self.PRESSED_SCALE if circle_pressed else 1.0) scaled_btn_x = btn_x + (self._circle_bg_txt.width * (1 - scale)) / 2 From 6e87e66bc53dac055d49b9a72bcca13c3772b569 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sun, 8 Mar 2026 11:54:15 -0700 Subject: [PATCH 080/141] 0.11 time --- RELEASES.md | 6 +++--- common/version.h | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/RELEASES.md b/RELEASES.md index d6d22d4891..bf4e0c75aa 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -1,11 +1,11 @@ -Version 0.10.4 (2026-02-17) +Version 0.11.0 (2026-03-17) ======================== * New driving model #36798 * Fully trained using a learned simulator - * Improved longitudinal performance in experimental mode + * Improved longitudinal performance in Experimental mode +* Reduce comma four standby power usage by 77% to 52 mW * Kia K7 2017 support thanks to royjr! * Lexus LS 2018 support thanks to Hacheoy! -* Reduce comma four standby power usage by 77% to 52 mW Version 0.10.3 (2025-12-17) ======================== diff --git a/common/version.h b/common/version.h index 7e78d64b22..78264e074c 100644 --- a/common/version.h +++ b/common/version.h @@ -1 +1 @@ -#define COMMA_VERSION "0.10.4" +#define COMMA_VERSION "0.11.0" From 9510e05dc0012757f6156f012fc37f87f6340d06 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sun, 8 Mar 2026 18:07:05 -0700 Subject: [PATCH 081/141] setup & reset tuneups (#37611) * period * no exit there * fasle * edit those * swipe down to go back * fix weird animation --- system/ui/mici_reset.py | 2 +- system/ui/mici_setup.py | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/system/ui/mici_reset.py b/system/ui/mici_reset.py index 2f89b6d62a..c547d9185d 100755 --- a/system/ui/mici_reset.py +++ b/system/ui/mici_reset.py @@ -77,7 +77,7 @@ class Reset(Scroller): self._reset_failed_page = ResetFailedPage() self._reset_button = BigConfirmationCircleButton("reset &\nerase", gui_app.texture("icons_mici/settings/device/uninstall.png", 70, 70), - self._start_reset, red=True) + self._start_reset, exit_on_confirm=False, red=True) self._cancel_button = BigConfirmationCircleButton("cancel", gui_app.texture("icons_mici/setup/cancel.png", 64, 64), gui_app.request_close, exit_on_confirm=False) self._reboot_button = BigConfirmationCircleButton("reboot\ndevice", gui_app.texture("icons_mici/settings/device/reboot.png", 64, 70), diff --git a/system/ui/mici_setup.py b/system/ui/mici_setup.py index 33dfe98f37..4e340335b9 100755 --- a/system/ui/mici_setup.py +++ b/system/ui/mici_setup.py @@ -144,7 +144,7 @@ class SoftwareSelectionPage(NavWidget): self._openpilot_slider = LargerSlider("slide to install\nopenpilot", use_openpilot_callback) self._openpilot_slider.set_enabled(lambda: self.enabled and not self.is_dismissing) - self._custom_software_slider = LargerSlider("slide to install\nother software", use_custom_software_callback, green=False) + self._custom_software_slider = LargerSlider("slide to install\ncustom software", use_custom_software_callback, green=False) self._custom_software_slider.set_enabled(lambda: self.enabled and not self.is_dismissing) def show_event(self): @@ -190,11 +190,11 @@ class CustomSoftwareWarningPage(NavScroller): self._continue_button.set_click_callback(continue_callback) self._scroller.add_widgets([ - GreyBigButton("use caution", "when installing\n3rd party software", + GreyBigButton("caution: installing\n3rd party software", "swipe down to go back", gui_app.texture("icons_mici/setup/warning.png", 64, 58)), - GreyBigButton("", "• It has not been tested by comma"), - GreyBigButton("", "• It may not comply with relevant safety standards."), - GreyBigButton("", "• It may cause damage to your device and/or vehicle."), + GreyBigButton("", "• It has not been tested by comma."), + GreyBigButton("", "• It may not comply with safety standards."), + GreyBigButton("", "• It may damage your device and/or vehicle."), GreyBigButton("how to restore to a\nfactory state later", "https://flash.comma.ai", gui_app.texture("icons_mici/setup/restore.png", 64, 64)), self._continue_button, @@ -546,7 +546,7 @@ class Setup(Widget): def _push_network_setup(self, custom_software: bool = False): # to fire the correct continue callback later self._network_setup_page.set_custom_software(custom_software) - gui_app.pop_widgets_to(self._software_selection_page, lambda: gui_app.push_widget(self._network_setup_page)) + gui_app.push_widget(self._network_setup_page) def _network_setup_continue_callback(self, custom_software: bool): if not custom_software: From e42ee228c2dce761bb339fdb74d690ceec783a4c Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sun, 8 Mar 2026 18:31:11 -0700 Subject: [PATCH 082/141] gitignore cleanups (#37615) * gitignore cleanups * lil more * one more --- .gitignore | 36 ++++++++---------------- common/.gitignore | 1 - common/transformations/.gitignore | 2 -- conftest.py | 1 - selfdrive/assets/.gitignore | 2 -- selfdrive/car/tests/.gitignore | 1 - selfdrive/controls/.gitignore | 2 -- selfdrive/locationd/.gitignore | 2 -- selfdrive/locationd/test/.gitignore | 1 - selfdrive/test/.gitignore | 2 +- selfdrive/test/process_replay/.gitignore | 1 - selfdrive/ui/.gitignore | 3 ++ selfdrive/ui/tests/.gitignore | 2 -- selfdrive/ui/tests/diff/.gitignore | 2 -- third_party/.gitignore | 1 - tools/bodyteleop/.gitignore | 4 --- tools/cabana/assets/.gitignore | 1 - tools/plotjuggler/.gitignore | 3 -- tools/replay/.gitignore | 3 -- 19 files changed, 15 insertions(+), 55 deletions(-) delete mode 100644 common/.gitignore delete mode 100644 common/transformations/.gitignore delete mode 100644 selfdrive/car/tests/.gitignore delete mode 100644 selfdrive/controls/.gitignore delete mode 100644 selfdrive/locationd/.gitignore delete mode 100644 selfdrive/locationd/test/.gitignore delete mode 100644 selfdrive/test/process_replay/.gitignore delete mode 100644 selfdrive/ui/tests/.gitignore delete mode 100644 selfdrive/ui/tests/diff/.gitignore delete mode 100644 third_party/.gitignore delete mode 100644 tools/bodyteleop/.gitignore delete mode 100644 tools/cabana/assets/.gitignore delete mode 100644 tools/plotjuggler/.gitignore diff --git a/.gitignore b/.gitignore index 062801d787..1f58a371e0 100644 --- a/.gitignore +++ b/.gitignore @@ -13,13 +13,13 @@ venv/ a.out .hypothesis .cache/ - -/docs_site/ +bin/ *.mp4 *.dylib *.DSYM *.d +*.pem *.pyc *.pyo .*.swp @@ -39,11 +39,13 @@ a.out *.mo *_pyx.cpp *.stats +*.pkl +*.pkl* config.json -clcache compile_commands.json compare_runtime*.html +# build artifacts selfdrive/pandad/pandad cereal/services.h cereal/gen @@ -56,46 +58,30 @@ system/camerad/test/ae_gray_test .coverage* coverage.xml htmlcov -pandaextra - -.mypy_cache/ -flycheck_* - -cppcheck_report.txt -comma*.sh - -selfdrive/modeld/models/*.pkl* # openpilot log files *.bz2 *.zst +*.rlog build/ !**/.gitkeep -poetry.toml -Pipfile ### VisualStudioCode ### +*.vsix +.history +.ionide .vscode/* +.history/ !.vscode/settings.json !.vscode/tasks.json !.vscode/launch.json !.vscode/extensions.json !.vscode/*.code-snippets -# Local History for Visual Studio Code -.history/ - -# Built Visual Studio Code Extensions -*.vsix - -### VisualStudioCode Patch ### -# Ignore all local history of files -.history -.ionide - +# agents .claude/ .context/ PLAN.md diff --git a/common/.gitignore b/common/.gitignore deleted file mode 100644 index ce1da4c53c..0000000000 --- a/common/.gitignore +++ /dev/null @@ -1 +0,0 @@ -*.cpp diff --git a/common/transformations/.gitignore b/common/transformations/.gitignore deleted file mode 100644 index a67290f09a..0000000000 --- a/common/transformations/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -transformations -transformations.cpp diff --git a/conftest.py b/conftest.py index 7e40ec3ed7..a01ddc2f6b 100644 --- a/conftest.py +++ b/conftest.py @@ -10,7 +10,6 @@ from openpilot.system.hardware import TICI, HARDWARE # TODO: pytest-cpp doesn't support FAIL, and we need to create test translations in sessionstart # pending https://github.com/pytest-dev/pytest-cpp/pull/147 collect_ignore = [ - "selfdrive/ui/tests/test_translations", "selfdrive/test/process_replay/test_processes.py", "selfdrive/test/process_replay/test_regen.py", ] diff --git a/selfdrive/assets/.gitignore b/selfdrive/assets/.gitignore index fffd4b4ed9..2d97f8b111 100644 --- a/selfdrive/assets/.gitignore +++ b/selfdrive/assets/.gitignore @@ -1,4 +1,2 @@ -*.cc fonts/*.fnt fonts/*.png -translations_assets.qrc diff --git a/selfdrive/car/tests/.gitignore b/selfdrive/car/tests/.gitignore deleted file mode 100644 index 192fb0945e..0000000000 --- a/selfdrive/car/tests/.gitignore +++ /dev/null @@ -1 +0,0 @@ -*.bz2 diff --git a/selfdrive/controls/.gitignore b/selfdrive/controls/.gitignore deleted file mode 100644 index 22a371d8ff..0000000000 --- a/selfdrive/controls/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -calibration_param -traces diff --git a/selfdrive/locationd/.gitignore b/selfdrive/locationd/.gitignore deleted file mode 100644 index 1a8c72388a..0000000000 --- a/selfdrive/locationd/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -params_learner -paramsd diff --git a/selfdrive/locationd/test/.gitignore b/selfdrive/locationd/test/.gitignore deleted file mode 100644 index 89f9ac04aa..0000000000 --- a/selfdrive/locationd/test/.gitignore +++ /dev/null @@ -1 +0,0 @@ -out/ diff --git a/selfdrive/test/.gitignore b/selfdrive/test/.gitignore index 5801faadf4..b8c6bebd95 100644 --- a/selfdrive/test/.gitignore +++ b/selfdrive/test/.gitignore @@ -3,7 +3,7 @@ docker_out/ process_replay/diff.txt process_replay/model_diff.txt +process_replay/fakedata/ valgrind_logs.txt -*.bz2 *.hevc diff --git a/selfdrive/test/process_replay/.gitignore b/selfdrive/test/process_replay/.gitignore deleted file mode 100644 index a35cd58d41..0000000000 --- a/selfdrive/test/process_replay/.gitignore +++ /dev/null @@ -1 +0,0 @@ -fakedata/ diff --git a/selfdrive/ui/.gitignore b/selfdrive/ui/.gitignore index 945928f617..30ae77d885 100644 --- a/selfdrive/ui/.gitignore +++ b/selfdrive/ui/.gitignore @@ -1 +1,4 @@ installer/installers/* + +tests/diff/report +.coverage diff --git a/selfdrive/ui/tests/.gitignore b/selfdrive/ui/tests/.gitignore deleted file mode 100644 index 74ab2675db..0000000000 --- a/selfdrive/ui/tests/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -test -test_translations diff --git a/selfdrive/ui/tests/diff/.gitignore b/selfdrive/ui/tests/diff/.gitignore deleted file mode 100644 index e21a8d896e..0000000000 --- a/selfdrive/ui/tests/diff/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -report -.coverage diff --git a/third_party/.gitignore b/third_party/.gitignore deleted file mode 100644 index 0d20b6487c..0000000000 --- a/third_party/.gitignore +++ /dev/null @@ -1 +0,0 @@ -*.pyc diff --git a/tools/bodyteleop/.gitignore b/tools/bodyteleop/.gitignore deleted file mode 100644 index adeab99a95..0000000000 --- a/tools/bodyteleop/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -av -av-10.0.0/* -key.pem -cert.pem \ No newline at end of file diff --git a/tools/cabana/assets/.gitignore b/tools/cabana/assets/.gitignore deleted file mode 100644 index 283034ca8b..0000000000 --- a/tools/cabana/assets/.gitignore +++ /dev/null @@ -1 +0,0 @@ -*.cc diff --git a/tools/plotjuggler/.gitignore b/tools/plotjuggler/.gitignore deleted file mode 100644 index 45559d0b09..0000000000 --- a/tools/plotjuggler/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -bin/ -bin -*.rlog diff --git a/tools/replay/.gitignore b/tools/replay/.gitignore index 83f0e99a8b..aa615770a2 100644 --- a/tools/replay/.gitignore +++ b/tools/replay/.gitignore @@ -1,5 +1,2 @@ -moc_* -*.moc - replay tests/test_replay From 71290f38054197421aecae24748a1be993a19464 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sun, 8 Mar 2026 19:16:38 -0700 Subject: [PATCH 083/141] cabana: gitignore assets.cc --- tools/cabana/.gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tools/cabana/.gitignore b/tools/cabana/.gitignore index 3d64f83204..1ee6c92236 100644 --- a/tools/cabana/.gitignore +++ b/tools/cabana/.gitignore @@ -1,6 +1,8 @@ moc_* *.moc +assets.cc + _cabana dbc/car_fingerprint_to_dbc.json tests/test_cabana From ad181ba501a36164cc891b8ff1dbdc6daf2647ed Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sun, 8 Mar 2026 20:54:31 -0700 Subject: [PATCH 084/141] agnos 17 (#37552) --- SConstruct | 21 ++-------- launch_env.sh | 2 +- system/hardware/tici/agnos.json | 22 +++++------ system/hardware/tici/all-partitions.json | 49 +++++++++--------------- system/qcomgpsd/qcomgpsd.py | 40 ++++++++++++++----- system/qcomgpsd/tests/test_qcomgpsd.py | 22 +++++------ 6 files changed, 77 insertions(+), 79 deletions(-) diff --git a/SConstruct b/SConstruct index 18bf040cf0..3f974f09c1 100644 --- a/SConstruct +++ b/SConstruct @@ -4,6 +4,7 @@ import sys import sysconfig import platform import shlex +import importlib import numpy as np import SCons.Errors @@ -38,23 +39,9 @@ assert arch in [ "Darwin", # macOS arm64 (x86 not supported) ] -if arch != "larch64": - import bzip2 - import capnproto - import eigen - import ffmpeg as ffmpeg_pkg - import libjpeg - import libyuv - import ncurses - import python3_dev - import zeromq - import zstd - pkgs = [bzip2, capnproto, eigen, ffmpeg_pkg, libjpeg, libyuv, ncurses, zeromq, zstd] - py_include = python3_dev.INCLUDE_DIR -else: - # TODO: remove when AGNOS has our new vendor pkgs - pkgs = [] - py_include = sysconfig.get_paths()['include'] +pkg_names = ['bzip2', 'capnproto', 'eigen', 'ffmpeg', 'libjpeg', 'libyuv', 'ncurses', 'zeromq', 'zstd'] +pkgs = [importlib.import_module(name) for name in pkg_names] +py_include = importlib.import_module('python3_dev').INCLUDE_DIR env = Environment( ENV={ diff --git a/launch_env.sh b/launch_env.sh index 314366f429..097f5fbe94 100755 --- a/launch_env.sh +++ b/launch_env.sh @@ -16,7 +16,7 @@ export VECLIB_MAXIMUM_THREADS=1 export QCOM_PRIORITY=12 if [ -z "$AGNOS_VERSION" ]; then - export AGNOS_VERSION="16" + export AGNOS_VERSION="17" fi export STAGING_ROOT="/data/safe_staging" diff --git a/system/hardware/tici/agnos.json b/system/hardware/tici/agnos.json index e33a26bb2e..ec5f43fa7b 100644 --- a/system/hardware/tici/agnos.json +++ b/system/hardware/tici/agnos.json @@ -56,28 +56,28 @@ }, { "name": "boot", - "url": "https://commadist.azureedge.net/agnosupdate/boot-a0185fa5ffc860de2179e4d0fec703fef6d560eacd730f79f60891ca79c72756.img.xz", - "hash": "a0185fa5ffc860de2179e4d0fec703fef6d560eacd730f79f60891ca79c72756", - "hash_raw": "a0185fa5ffc860de2179e4d0fec703fef6d560eacd730f79f60891ca79c72756", - "size": 17496064, + "url": "https://commadist.azureedge.net/agnosupdate/boot-1bce912831b41cc65d651acbc07ca68a1d528298c22ebae7c060db5f524f4ee0.img.xz", + "hash": "1bce912831b41cc65d651acbc07ca68a1d528298c22ebae7c060db5f524f4ee0", + "hash_raw": "1bce912831b41cc65d651acbc07ca68a1d528298c22ebae7c060db5f524f4ee0", + "size": 17500160, "sparse": false, "full_check": true, "has_ab": true, - "ondevice_hash": "0ee1ab104bb46d0f72e7d0b7d3e94629a7644a368896c6d4c558554fb955a08a" + "ondevice_hash": "a64fc06e2508dbb2af4fa20808c10009bb5a31517ff39b0fb1dad882c9bec808" }, { "name": "system", - "url": "https://commadist.azureedge.net/agnosupdate/system-0cf8cb01e40d05d6d325afe68b934a6c0dda3a56703b2ef3e3de637d754ae5dd.img.xz", - "hash": "7c58308be461126677ba02e9c9739556520ee02958934733867d86ecfe2e58e9", - "hash_raw": "0cf8cb01e40d05d6d325afe68b934a6c0dda3a56703b2ef3e3de637d754ae5dd", + "url": "https://commadist.azureedge.net/agnosupdate/system-1a64e41bf8d9f63c1a4d5e3e0a5a4c6c60c7dfb9d1e677c03adc2c668bc3fcb6.img.xz", + "hash": "ef7ad7d290d74285e9e73f30442d2adc8ff3a616e1d7ddcceee6ba5420aa2fb7", + "hash_raw": "1a64e41bf8d9f63c1a4d5e3e0a5a4c6c60c7dfb9d1e677c03adc2c668bc3fcb6", "size": 4718592000, "sparse": true, "full_check": false, "has_ab": true, - "ondevice_hash": "826790516410c325aa30265846946d06a556f0a7b23c957f65fd11c055a663da", + "ondevice_hash": "bfe5187bb754fd0df5069d0b2b12f79f5938588f97bb87063532da5d6e7a1cfb", "alt": { - "hash": "0cf8cb01e40d05d6d325afe68b934a6c0dda3a56703b2ef3e3de637d754ae5dd", - "url": "https://commadist.azureedge.net/agnosupdate/system-0cf8cb01e40d05d6d325afe68b934a6c0dda3a56703b2ef3e3de637d754ae5dd.img", + "hash": "1a64e41bf8d9f63c1a4d5e3e0a5a4c6c60c7dfb9d1e677c03adc2c668bc3fcb6", + "url": "https://commadist.azureedge.net/agnosupdate/system-1a64e41bf8d9f63c1a4d5e3e0a5a4c6c60c7dfb9d1e677c03adc2c668bc3fcb6.img", "size": 4718592000 } } diff --git a/system/hardware/tici/all-partitions.json b/system/hardware/tici/all-partitions.json index b6718fe97d..8b4c0d4312 100644 --- a/system/hardware/tici/all-partitions.json +++ b/system/hardware/tici/all-partitions.json @@ -339,62 +339,51 @@ }, { "name": "boot", - "url": "https://commadist.azureedge.net/agnosupdate/boot-a0185fa5ffc860de2179e4d0fec703fef6d560eacd730f79f60891ca79c72756.img.xz", - "hash": "a0185fa5ffc860de2179e4d0fec703fef6d560eacd730f79f60891ca79c72756", - "hash_raw": "a0185fa5ffc860de2179e4d0fec703fef6d560eacd730f79f60891ca79c72756", - "size": 17496064, + "url": "https://commadist.azureedge.net/agnosupdate/boot-1bce912831b41cc65d651acbc07ca68a1d528298c22ebae7c060db5f524f4ee0.img.xz", + "hash": "1bce912831b41cc65d651acbc07ca68a1d528298c22ebae7c060db5f524f4ee0", + "hash_raw": "1bce912831b41cc65d651acbc07ca68a1d528298c22ebae7c060db5f524f4ee0", + "size": 17500160, "sparse": false, "full_check": true, "has_ab": true, - "ondevice_hash": "0ee1ab104bb46d0f72e7d0b7d3e94629a7644a368896c6d4c558554fb955a08a" + "ondevice_hash": "a64fc06e2508dbb2af4fa20808c10009bb5a31517ff39b0fb1dad882c9bec808" }, { "name": "system", - "url": "https://commadist.azureedge.net/agnosupdate/system-0cf8cb01e40d05d6d325afe68b934a6c0dda3a56703b2ef3e3de637d754ae5dd.img.xz", - "hash": "7c58308be461126677ba02e9c9739556520ee02958934733867d86ecfe2e58e9", - "hash_raw": "0cf8cb01e40d05d6d325afe68b934a6c0dda3a56703b2ef3e3de637d754ae5dd", + "url": "https://commadist.azureedge.net/agnosupdate/system-1a64e41bf8d9f63c1a4d5e3e0a5a4c6c60c7dfb9d1e677c03adc2c668bc3fcb6.img.xz", + "hash": "ef7ad7d290d74285e9e73f30442d2adc8ff3a616e1d7ddcceee6ba5420aa2fb7", + "hash_raw": "1a64e41bf8d9f63c1a4d5e3e0a5a4c6c60c7dfb9d1e677c03adc2c668bc3fcb6", "size": 4718592000, "sparse": true, "full_check": false, "has_ab": true, - "ondevice_hash": "826790516410c325aa30265846946d06a556f0a7b23c957f65fd11c055a663da", + "ondevice_hash": "bfe5187bb754fd0df5069d0b2b12f79f5938588f97bb87063532da5d6e7a1cfb", "alt": { - "hash": "0cf8cb01e40d05d6d325afe68b934a6c0dda3a56703b2ef3e3de637d754ae5dd", - "url": "https://commadist.azureedge.net/agnosupdate/system-0cf8cb01e40d05d6d325afe68b934a6c0dda3a56703b2ef3e3de637d754ae5dd.img", + "hash": "1a64e41bf8d9f63c1a4d5e3e0a5a4c6c60c7dfb9d1e677c03adc2c668bc3fcb6", + "url": "https://commadist.azureedge.net/agnosupdate/system-1a64e41bf8d9f63c1a4d5e3e0a5a4c6c60c7dfb9d1e677c03adc2c668bc3fcb6.img", "size": 4718592000 } }, { "name": "userdata_90", - "url": "https://commadist.azureedge.net/agnosupdate/userdata_90-ec31b8116125a95755adb32853c401c462a14a74f538535532bf2c34d72c60eb.img.xz", - "hash": "aa0f0fe32187493e6135aee9e984d3f9705fc58560d537b34687bb6b51a38428", - "hash_raw": "ec31b8116125a95755adb32853c401c462a14a74f538535532bf2c34d72c60eb", + "url": "https://commadist.azureedge.net/agnosupdate/userdata_90-3711c021ee7a6512c93452857893325a6ed845e4cfad52398b531eaede22f913.img.xz", + "hash": "67e0066cc5d2b7173f6280a7a8836d6681e2cd55b0f4a79eafac5b9fdd4fd7c8", + "hash_raw": "3711c021ee7a6512c93452857893325a6ed845e4cfad52398b531eaede22f913", "size": 96636764160, "sparse": true, "full_check": true, "has_ab": false, - "ondevice_hash": "9c916b7d05543d4608b0401bc867639f44ce9671639a1a6da83b6d58b4eaa1b4" + "ondevice_hash": "31fe9e6e1b4ae1fe267868daff1b56660c9b4345e6a6272fce161b30ba70ed4c" }, { "name": "userdata_89", - "url": "https://commadist.azureedge.net/agnosupdate/userdata_89-7f092cc841124c10300e43574e90e3367e983bfbe4faa0969024e79e5ce90b11.img.xz", - "hash": "fa83d4b7096857136820b0b0a8785c90677256b054c5c14039cd7b9b1065a90b", - "hash_raw": "7f092cc841124c10300e43574e90e3367e983bfbe4faa0969024e79e5ce90b11", + "url": "https://commadist.azureedge.net/agnosupdate/userdata_89-8fcd15c164625c81199d7d3cca11009c862efa3ae19112f57fb10b4202474363.img.xz", + "hash": "ccb3bde6f31b340816d8499a5a205ba0e9984c189451807501a76f05820c3f4c", + "hash_raw": "8fcd15c164625c81199d7d3cca11009c862efa3ae19112f57fb10b4202474363", "size": 95563022336, "sparse": true, "full_check": true, "has_ab": false, - "ondevice_hash": "1699e38de769eb32c21dfa6a5ac21eb3ad620a362c7b8abf1a2c0afe0f717530" - }, - { - "name": "userdata_30", - "url": "https://commadist.azureedge.net/agnosupdate/userdata_30-3df2dcd5e1f426c90b090fdbcd1a95b035d96a4bdaf88d5517245db5ee84f5ed.img.xz", - "hash": "890910f20b1ad88a728ee822a47b1234eb3d70cab28ca8a935679c8c2d33cbe9", - "hash_raw": "3df2dcd5e1f426c90b090fdbcd1a95b035d96a4bdaf88d5517245db5ee84f5ed", - "size": 32212254720, - "sparse": true, - "full_check": true, - "has_ab": false, - "ondevice_hash": "8e7cb392dd6e49c7d59fa850be7d1f44901314c86ba9c88be5bb27a0cd1123c9" + "ondevice_hash": "4167c69304e8038050793dbde70ff230614146836d9d31e5eb83aa03b6ed787f" } ] \ No newline at end of file diff --git a/system/qcomgpsd/qcomgpsd.py b/system/qcomgpsd/qcomgpsd.py index 59f5ac0b50..82b9ea927f 100755 --- a/system/qcomgpsd/qcomgpsd.py +++ b/system/qcomgpsd/qcomgpsd.py @@ -7,7 +7,7 @@ import math import time import requests import shutil -import subprocess +from serial import Serial import datetime from multiprocessing import Process, Event from typing import NoReturn @@ -90,9 +90,24 @@ measurementStatusGlonassFields = { def try_setup_logs(diag, logs): return setup_logs(diag, logs) -@retry(attempts=3, delay=1.0) -def at_cmd(cmd: str) -> str | None: - return subprocess.check_output(f"mmcli -m any --timeout 30 --command='{cmd}'", shell=True, encoding='utf8') +AT_PORT = "/dev/modem_at0" + +@retry(attempts=5, delay=1.0) +def at_cmd(cmd: str) -> str: + with Serial(AT_PORT, baudrate=115200, timeout=5) as ser: + ser.reset_input_buffer() + ser.write(f"{cmd}\r".encode()) + lines = [] + while True: + line = ser.readline() + if not line: + raise RuntimeError(f"AT command timeout: {cmd}") + line = line.decode('utf-8', errors='replace').strip() + if line in ("OK", "ERROR") or line.startswith("+CME ERROR"): + break + if line and line != cmd: + lines.append(line) + return '\n'.join(lines) def gps_enabled() -> bool: return "QGPS: 1" in at_cmd("AT+QGPS?") @@ -131,6 +146,7 @@ def downloader_loop(event): @retry(attempts=5, delay=0.2, ignore_failure=True) def inject_assistance(): + import subprocess cmd = f"mmcli -m any --timeout 30 --location-inject-assistance-data={ASSIST_DATA_FILE}" subprocess.check_output(cmd, stderr=subprocess.PIPE, shell=True) cloudlog.info("successfully loaded assistance data") @@ -207,13 +223,19 @@ def teardown_quectel(diag): try_setup_logs(diag, []) -def wait_for_modem(cmd="AT+QGPS?"): +def wait_for_modem(): cloudlog.warning("waiting for modem to come up") + while not os.path.exists(AT_PORT): + time.sleep(0.5) + # wait until the modem GNSS subsystem responds while True: - ret = subprocess.call(f"mmcli -m any --timeout 10 --command=\"{cmd}\"", stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, shell=True) - if ret == 0: - return - time.sleep(0.1) + try: + resp = at_cmd("AT+QGPS?") + if "+QGPS:" in resp: + return + except Exception: + pass + time.sleep(0.5) def main() -> NoReturn: diff --git a/system/qcomgpsd/tests/test_qcomgpsd.py b/system/qcomgpsd/tests/test_qcomgpsd.py index 2fc6205ea4..75e8707591 100644 --- a/system/qcomgpsd/tests/test_qcomgpsd.py +++ b/system/qcomgpsd/tests/test_qcomgpsd.py @@ -1,9 +1,7 @@ import os import pytest -import json import time import datetime -import subprocess import cereal.messaging as messaging from openpilot.system.qcomgpsd.qcomgpsd import at_cmd, wait_for_modem @@ -44,15 +42,13 @@ class TestRawgpsd: return self.sm.updated['qcomGnss'] def test_no_crash_double_command(self): + wait_for_modem() at_cmd("AT+QGPSDEL=0") at_cmd("AT+QGPSDEL=0") def test_wait_for_modem(self): os.system("sudo systemctl stop ModemManager") managed_processes['qcomgpsd'].start() - assert not self._wait_for_output(5) - - os.system("sudo systemctl restart ModemManager") assert self._wait_for_output(30) def test_startup_time(self, subtests): @@ -61,7 +57,7 @@ class TestRawgpsd: os.system("sudo systemctl stop systemd-resolved") with subtests.test(internet=internet): managed_processes['qcomgpsd'].start() - assert self._wait_for_output(7) + assert self._wait_for_output(30) managed_processes['qcomgpsd'].stop() def test_turns_off_gnss(self, subtests): @@ -71,14 +67,15 @@ class TestRawgpsd: time.sleep(s) managed_processes['qcomgpsd'].stop() - ls = subprocess.check_output("mmcli -m any --location-status --output-json", shell=True, encoding='utf-8') - loc_status = json.loads(ls) - assert set(loc_status['modem']['location']['enabled']) <= {'3gpp-lac-ci'} + wait_for_modem() + resp = at_cmd("AT+QGPS?") + assert "+QGPS: 0" in resp def check_assistance(self, should_be_loaded): # after QGPSDEL: '+QGPSXTRADATA: 0,"1980/01/05,19:00:00"' # after loading: '+QGPSXTRADATA: 10080,"2023/06/24,19:00:00"' + wait_for_modem() out = at_cmd("AT+QGPSXTRADATA?") out = out.split("+QGPSXTRADATA:")[1].split("'")[0].strip() valid_duration, injected_time_str = out.split(",", 1) @@ -92,20 +89,23 @@ class TestRawgpsd: assert injected_time_str[:] == '1980/01/05,19:00:00'[:] assert valid_duration == '0' + @pytest.mark.skip(reason="XTRA injection via QMI needs debugging on AGNOS 17") def test_assistance_loading(self): managed_processes['qcomgpsd'].start() - assert self._wait_for_output(10) + assert self._wait_for_output(30) managed_processes['qcomgpsd'].stop() self.check_assistance(True) + @pytest.mark.skip(reason="XTRA injection via QMI needs debugging on AGNOS 17") def test_no_assistance_loading(self): os.system("sudo systemctl stop systemd-resolved") managed_processes['qcomgpsd'].start() - assert self._wait_for_output(10) + assert self._wait_for_output(30) managed_processes['qcomgpsd'].stop() self.check_assistance(False) + @pytest.mark.skip(reason="XTRA injection via QMI needs debugging on AGNOS 17") def test_late_assistance_loading(self): os.system("sudo systemctl stop systemd-resolved") From 76458d175fe34c0e8610914a413c23e9be257850 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 9 Mar 2026 09:33:00 -0700 Subject: [PATCH 085/141] [bot] Update translations (#37530) Update translations Co-authored-by: Vehicle Researcher --- selfdrive/ui/translations/app.pot | 1567 +++++++++++------------ selfdrive/ui/translations/app_de.po | 737 ++++------- selfdrive/ui/translations/app_en.po | 715 ++++------- selfdrive/ui/translations/app_es.po | 749 ++++------- selfdrive/ui/translations/app_fr.po | 765 ++++------- selfdrive/ui/translations/app_ja.po | 712 ++++------ selfdrive/ui/translations/app_ko.po | 697 ++++------ selfdrive/ui/translations/app_pt-BR.po | 738 ++++------- selfdrive/ui/translations/app_th.po | 619 ++++----- selfdrive/ui/translations/app_tr.po | 726 ++++------- selfdrive/ui/translations/app_uk.po | 752 ++++------- selfdrive/ui/translations/app_zh-CHS.po | 670 ++++------ selfdrive/ui/translations/app_zh-CHT.po | 669 ++++------ 13 files changed, 3994 insertions(+), 6122 deletions(-) diff --git a/selfdrive/ui/translations/app.pot b/selfdrive/ui/translations/app.pot index abb6940a54..468ff35fd9 100644 --- a/selfdrive/ui/translations/app.pot +++ b/selfdrive/ui/translations/app.pot @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-10-23 00:51-0700\n" +"POT-Creation-Date: 2026-03-09 14:21+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -18,1113 +18,1018 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n" -#: system/ui/widgets/html_render.py:263 system/ui/widgets/confirm_dialog.py:93 -#: selfdrive/ui/layouts/sidebar.py:127 -#, python-format -msgid "OK" -msgstr "" - -#: system/ui/widgets/confirm_dialog.py:23 system/ui/widgets/option_dialog.py:35 -#: system/ui/widgets/keyboard.py:81 system/ui/widgets/network.py:318 -#, python-format -msgid "Cancel" -msgstr "" - -#: system/ui/widgets/option_dialog.py:36 -#, python-format -msgid "Select" -msgstr "" - -#: system/ui/widgets/network.py:74 system/ui/widgets/network.py:95 +#: system/ui/widgets/network.py:92 +#: system/ui/widgets/network.py:74 #, python-format msgid "Advanced" msgstr "" -#: system/ui/widgets/network.py:99 selfdrive/ui/layouts/onboarding.py:147 +#: system/ui/widgets/network.py:96 +#: openpilot/selfdrive/ui/layouts/onboarding.py:151 #, python-format msgid "Back" msgstr "" -#: system/ui/widgets/network.py:120 +#: system/ui/widgets/network.py:201 +#, python-format +msgid "Enter APN" +msgstr "" + +#: system/ui/widgets/network.py:201 +#, python-format +msgid "leave blank for automatic configuration" +msgstr "" + +#: system/ui/widgets/network.py:243 +#, python-format +msgid "Enter SSID" +msgstr "" + +#: system/ui/widgets/network.py:257 +#, python-format +msgid "Enter new tethering password" +msgstr "" + +#: system/ui/widgets/network.py:117 #, python-format msgid "Enable Tethering" msgstr "" -#: system/ui/widgets/network.py:123 system/ui/widgets/network.py:139 +#: system/ui/widgets/network.py:120 +#: system/ui/widgets/network.py:136 #, python-format msgid "EDIT" msgstr "" -#: system/ui/widgets/network.py:124 +#: system/ui/widgets/network.py:121 #, python-format msgid "Tethering Password" msgstr "" -#: system/ui/widgets/network.py:129 +#: system/ui/widgets/network.py:126 #, python-format msgid "Enable Roaming" msgstr "" -#: system/ui/widgets/network.py:134 +#: system/ui/widgets/network.py:131 #, python-format msgid "Cellular Metered" msgstr "" -#: system/ui/widgets/network.py:135 +#: system/ui/widgets/network.py:136 +#, python-format +msgid "APN Setting" +msgstr "" + +#: system/ui/widgets/network.py:141 +#, python-format +msgid "Wi-Fi Network Metered" +msgstr "" + +#: system/ui/widgets/network.py:238 +#: system/ui/widgets/network.py:320 +#, python-format +msgid "Enter password" +msgstr "" + +#: system/ui/widgets/network.py:316 +#, python-format +msgid "Scanning Wi-Fi networks..." +msgstr "" + +#: system/ui/widgets/network.py:376 +#, python-format +msgid "CONNECTING..." +msgstr "" + +#: system/ui/widgets/network.py:458 +#: system/ui/widgets/network.py:326 +#, python-format +msgid "Forget" +msgstr "" + +#: system/ui/widgets/network.py:132 #, python-format msgid "Prevent large data uploads when on a metered cellular connection" msgstr "" #: system/ui/widgets/network.py:139 #, python-format -msgid "APN Setting" -msgstr "" - -#: system/ui/widgets/network.py:142 -#, python-format msgid "default" msgstr "" -#: system/ui/widgets/network.py:142 +#: system/ui/widgets/network.py:139 #, python-format msgid "metered" msgstr "" -#: system/ui/widgets/network.py:142 +#: system/ui/widgets/network.py:139 #, python-format msgid "unmetered" msgstr "" -#: system/ui/widgets/network.py:144 -#, python-format -msgid "Wi-Fi Network Metered" -msgstr "" - -#: system/ui/widgets/network.py:144 +#: system/ui/widgets/network.py:141 #, python-format msgid "Prevent large data uploads when on a metered Wi-Fi connection" msgstr "" -#: system/ui/widgets/network.py:150 +#: system/ui/widgets/network.py:147 #, python-format msgid "IP Address" msgstr "" -#: system/ui/widgets/network.py:155 +#: system/ui/widgets/network.py:152 #, python-format msgid "Hidden Network" msgstr "" -#: system/ui/widgets/network.py:155 selfdrive/ui/layouts/sidebar.py:73 -#: selfdrive/ui/layouts/sidebar.py:134 selfdrive/ui/layouts/sidebar.py:136 -#: selfdrive/ui/layouts/sidebar.py:138 +#: system/ui/widgets/network.py:152 +#: openpilot/selfdrive/ui/layouts/sidebar.py:73 +#: openpilot/selfdrive/ui/layouts/sidebar.py:134 +#: openpilot/selfdrive/ui/layouts/sidebar.py:136 +#: openpilot/selfdrive/ui/layouts/sidebar.py:138 #, python-format msgid "CONNECT" msgstr "" -#: system/ui/widgets/network.py:204 -#, python-format -msgid "Enter APN" -msgstr "" - -#: system/ui/widgets/network.py:204 -#, python-format -msgid "leave blank for automatic configuration" -msgstr "" - -#: system/ui/widgets/network.py:237 system/ui/widgets/network.py:314 -#, python-format -msgid "Enter password" -msgstr "" - -#: system/ui/widgets/network.py:237 system/ui/widgets/network.py:314 -#, python-format -msgid "for \"{}\"" -msgstr "" - -#: system/ui/widgets/network.py:241 -#, python-format -msgid "Enter SSID" -msgstr "" - -#: system/ui/widgets/network.py:254 -#, python-format -msgid "Enter new tethering password" -msgstr "" - -#: system/ui/widgets/network.py:310 -#, python-format -msgid "Scanning Wi-Fi networks..." -msgstr "" - -#: system/ui/widgets/network.py:314 +#: system/ui/widgets/network.py:320 #, python-format msgid "Wrong password" msgstr "" -#: system/ui/widgets/network.py:318 system/ui/widgets/network.py:451 +#: system/ui/widgets/network.py:326 +#: system/ui/widgets/confirm_dialog.py:24 +#: system/ui/widgets/option_dialog.py:36 +#: system/ui/widgets/keyboard.py:83 #, python-format -msgid "Forget" +msgid "Cancel" msgstr "" -#: system/ui/widgets/network.py:319 -#, python-format -msgid "Forget Wi-Fi Network \"{}\"?" -msgstr "" - -#: system/ui/widgets/network.py:369 -#, python-format -msgid "CONNECTING..." -msgstr "" - -#: system/ui/widgets/network.py:373 +#: system/ui/widgets/network.py:380 #, python-format msgid "FORGETTING..." msgstr "" -#: system/ui/widgets/list_view.py:123 system/ui/widgets/list_view.py:160 +#: system/ui/widgets/network.py:238 +#: system/ui/widgets/network.py:321 +#, python-format +msgid "for \"{}\"" +msgstr "" + +#: system/ui/widgets/network.py:327 +#, python-format +msgid "Forget Wi-Fi Network \"{}\"?" +msgstr "" + +#: system/ui/widgets/confirm_dialog.py:93 +#: system/ui/widgets/html_render.py:263 +#: openpilot/selfdrive/ui/layouts/sidebar.py:127 +#, python-format +msgid "OK" +msgstr "" + +#: system/ui/widgets/option_dialog.py:37 +#, python-format +msgid "Select" +msgstr "" + +#: system/ui/widgets/list_view.py:123 +#: system/ui/widgets/list_view.py:160 #, python-format msgid "Error" msgstr "" -#: selfdrive/ui/widgets/pairing_dialog.py:103 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:92 #, python-format msgid "Pair your device to your comma account" msgstr "" -#: selfdrive/ui/widgets/pairing_dialog.py:128 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:117 #, python-format msgid "Go to https://connect.comma.ai on your phone" msgstr "" -#: selfdrive/ui/widgets/pairing_dialog.py:129 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:118 #, python-format msgid "Click \"add new device\" and scan the QR code on the right" msgstr "" -#: selfdrive/ui/widgets/pairing_dialog.py:130 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:119 #, python-format msgid "Bookmark connect.comma.ai to your home screen to use it like an app" msgstr "" -#: selfdrive/ui/widgets/pairing_dialog.py:161 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:150 #, python-format msgid "QR Code Error" msgstr "" -#: selfdrive/ui/widgets/ssh_key.py:29 -msgid "LOADING" -msgstr "" - -#: selfdrive/ui/widgets/ssh_key.py:30 -msgid "ADD" -msgstr "" - -#: selfdrive/ui/widgets/ssh_key.py:31 -msgid "REMOVE" -msgstr "" - -#: selfdrive/ui/widgets/ssh_key.py:89 +#: openpilot/selfdrive/ui/widgets/setup.py:47 +#: openpilot/selfdrive/ui/layouts/settings/device.py:23 #, python-format -msgid "Enter your GitHub username" +msgid "Pair your device with comma connect (connect.comma.ai) and claim your comma prime offer." msgstr "" -#: selfdrive/ui/widgets/ssh_key.py:114 +#: openpilot/selfdrive/ui/widgets/setup.py:74 #, python-format -msgid "No SSH keys found" +msgid "Maximize your training data uploads to improve openpilot's driving models." msgstr "" -#: selfdrive/ui/widgets/ssh_key.py:123 -#, python-format -msgid "Request timed out" -msgstr "" - -#: selfdrive/ui/widgets/ssh_key.py:126 -#, python-format -msgid "No SSH keys found for user '{}'" -msgstr "" - -#: selfdrive/ui/widgets/prime.py:33 -#, python-format -msgid "Upgrade Now" -msgstr "" - -#: selfdrive/ui/widgets/prime.py:38 -#, python-format -msgid "Become a comma prime member at connect.comma.ai" -msgstr "" - -#: selfdrive/ui/widgets/prime.py:44 -#, python-format -msgid "PRIME FEATURES:" -msgstr "" - -#: selfdrive/ui/widgets/prime.py:47 -#, python-format -msgid "Remote access" -msgstr "" - -#: selfdrive/ui/widgets/prime.py:47 -#, python-format -msgid "24/7 LTE connectivity" -msgstr "" - -#: selfdrive/ui/widgets/prime.py:47 -#, python-format -msgid "1 year of drive storage" -msgstr "" - -#: selfdrive/ui/widgets/prime.py:47 -#, python-format -msgid "Remote snapshots" -msgstr "" - -#: selfdrive/ui/widgets/prime.py:62 -#, python-format -msgid "✓ SUBSCRIBED" -msgstr "" - -#: selfdrive/ui/widgets/prime.py:63 -#, python-format -msgid "comma prime" -msgstr "" - -#: selfdrive/ui/widgets/exp_mode_button.py:50 -#, python-format -msgid "EXPERIMENTAL MODE ON" -msgstr "" - -#: selfdrive/ui/widgets/exp_mode_button.py:50 -#, python-format -msgid "CHILL MODE ON" -msgstr "" - -#: selfdrive/ui/widgets/offroad_alerts.py:104 -#, python-format -msgid "Close" -msgstr "" - -#: selfdrive/ui/widgets/offroad_alerts.py:106 -#, python-format -msgid "Snooze Update" -msgstr "" - -#: selfdrive/ui/widgets/offroad_alerts.py:109 -#, python-format -msgid "Acknowledge Excessive Actuation" -msgstr "" - -#: selfdrive/ui/widgets/offroad_alerts.py:112 -#, python-format -msgid "Reboot and Update" -msgstr "" - -#: selfdrive/ui/widgets/offroad_alerts.py:320 -#, python-format -msgid "No release notes available." -msgstr "" - -#: selfdrive/ui/widgets/setup.py:19 -#, python-format -msgid "Pair device" -msgstr "" - -#: selfdrive/ui/widgets/setup.py:20 -#, python-format -msgid "Open" -msgstr "" - -#: selfdrive/ui/widgets/setup.py:22 -#, python-format -msgid "🔥 Firehose Mode 🔥" -msgstr "" - -#: selfdrive/ui/widgets/setup.py:44 +#: openpilot/selfdrive/ui/widgets/setup.py:43 #, python-format msgid "Finish Setup" msgstr "" -#: selfdrive/ui/widgets/setup.py:48 selfdrive/ui/layouts/settings/device.py:24 +#: openpilot/selfdrive/ui/widgets/setup.py:18 #, python-format -msgid "" -"Pair your device with comma connect (connect.comma.ai) and claim your comma " -"prime offer." +msgid "Pair device" msgstr "" -#: selfdrive/ui/widgets/setup.py:75 +#: openpilot/selfdrive/ui/widgets/setup.py:19 #, python-format -msgid "" -"Maximize your training data uploads to improve openpilot's driving models." +msgid "Open" msgstr "" -#: selfdrive/ui/widgets/setup.py:91 +#: openpilot/selfdrive/ui/widgets/setup.py:21 +#, python-format +msgid "🔥 Firehose Mode 🔥" +msgstr "" + +#: openpilot/selfdrive/ui/widgets/setup.py:91 #, python-format msgid "Please connect to Wi-Fi to complete initial pairing" msgstr "" -#: selfdrive/ui/layouts/home.py:155 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:29 +msgid "LOADING" +msgstr "" + +#: openpilot/selfdrive/ui/widgets/ssh_key.py:30 +msgid "ADD" +msgstr "" + +#: openpilot/selfdrive/ui/widgets/ssh_key.py:31 +msgid "REMOVE" +msgstr "" + +#: openpilot/selfdrive/ui/widgets/ssh_key.py:89 +#, python-format +msgid "Enter your GitHub username" +msgstr "" + +#: openpilot/selfdrive/ui/widgets/ssh_key.py:124 +#, python-format +msgid "Request timed out" +msgstr "" + +#: openpilot/selfdrive/ui/widgets/ssh_key.py:115 +#, python-format +msgid "No SSH keys found" +msgstr "" + +#: openpilot/selfdrive/ui/widgets/ssh_key.py:127 +#, python-format +msgid "No SSH keys found for user '{}'" +msgstr "" + +#: openpilot/selfdrive/ui/widgets/prime.py:33 +#, python-format +msgid "Upgrade Now" +msgstr "" + +#: openpilot/selfdrive/ui/widgets/prime.py:44 +#, python-format +msgid "PRIME FEATURES:" +msgstr "" + +#: openpilot/selfdrive/ui/widgets/prime.py:47 +#, python-format +msgid "Remote access" +msgstr "" + +#: openpilot/selfdrive/ui/widgets/prime.py:47 +#, python-format +msgid "24/7 LTE connectivity" +msgstr "" + +#: openpilot/selfdrive/ui/widgets/prime.py:47 +#, python-format +msgid "1 year of drive storage" +msgstr "" + +#: openpilot/selfdrive/ui/widgets/prime.py:47 +#, python-format +msgid "Remote snapshots" +msgstr "" + +#: openpilot/selfdrive/ui/widgets/prime.py:62 +#, python-format +msgid "✓ SUBSCRIBED" +msgstr "" + +#: openpilot/selfdrive/ui/widgets/prime.py:63 +#, python-format +msgid "comma prime" +msgstr "" + +#: openpilot/selfdrive/ui/widgets/prime.py:38 +#, python-format +msgid "Become a comma prime member at connect.comma.ai" +msgstr "" + +#: openpilot/selfdrive/ui/widgets/exp_mode_button.py:51 +#, python-format +msgid "EXPERIMENTAL MODE ON" +msgstr "" + +#: openpilot/selfdrive/ui/widgets/exp_mode_button.py:51 +#, python-format +msgid "CHILL MODE ON" +msgstr "" + +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:104 +#, python-format +msgid "Close" +msgstr "" + +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:106 +#, python-format +msgid "Snooze Update" +msgstr "" + +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:109 +#, python-format +msgid "Acknowledge Excessive Actuation" +msgstr "" + +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:112 +#, python-format +msgid "Reboot and Update" +msgstr "" + +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:321 +#, python-format +msgid "No release notes available." +msgstr "" + +#: openpilot/selfdrive/ui/layouts/sidebar.py:43 +msgid "--" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/sidebar.py:44 +msgid "Wi-Fi" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/sidebar.py:45 +msgid "ETH" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/sidebar.py:46 +msgid "2G" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/sidebar.py:47 +msgid "3G" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/sidebar.py:48 +msgid "LTE" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/sidebar.py:49 +msgid "5G" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/sidebar.py:71 +#: openpilot/selfdrive/ui/layouts/sidebar.py:125 +#: openpilot/selfdrive/ui/layouts/sidebar.py:127 +#: openpilot/selfdrive/ui/layouts/sidebar.py:129 +msgid "TEMP" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/sidebar.py:71 +#: openpilot/selfdrive/ui/layouts/sidebar.py:125 +msgid "GOOD" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/sidebar.py:72 +#: openpilot/selfdrive/ui/layouts/sidebar.py:144 +msgid "VEHICLE" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/sidebar.py:72 +#: openpilot/selfdrive/ui/layouts/sidebar.py:144 +#: openpilot/selfdrive/ui/layouts/sidebar.py:136 +msgid "ONLINE" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/sidebar.py:73 +#: openpilot/selfdrive/ui/layouts/sidebar.py:134 +msgid "OFFLINE" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/sidebar.py:117 +msgid "Unknown" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/sidebar.py:142 +msgid "NO" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/sidebar.py:142 +msgid "PANDA" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/sidebar.py:129 +msgid "HIGH" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/sidebar.py:138 +msgid "ERROR" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/home.py:155 #, python-format msgid "UPDATE" msgstr "" -#: selfdrive/ui/layouts/home.py:169 +#: openpilot/selfdrive/ui/layouts/home.py:169 #, python-format msgid "{} ALERT" msgid_plural "{} ALERTS" msgstr[0] "" msgstr[1] "" -#: selfdrive/ui/layouts/sidebar.py:43 -msgid "--" -msgstr "" - -#: selfdrive/ui/layouts/sidebar.py:44 -msgid "Wi-Fi" -msgstr "" - -#: selfdrive/ui/layouts/sidebar.py:45 -msgid "ETH" -msgstr "" - -#: selfdrive/ui/layouts/sidebar.py:46 -msgid "2G" -msgstr "" - -#: selfdrive/ui/layouts/sidebar.py:47 -msgid "3G" -msgstr "" - -#: selfdrive/ui/layouts/sidebar.py:48 -msgid "LTE" -msgstr "" - -#: selfdrive/ui/layouts/sidebar.py:49 -msgid "5G" -msgstr "" - -#: selfdrive/ui/layouts/sidebar.py:71 selfdrive/ui/layouts/sidebar.py:125 -#: selfdrive/ui/layouts/sidebar.py:127 selfdrive/ui/layouts/sidebar.py:129 -msgid "TEMP" -msgstr "" - -#: selfdrive/ui/layouts/sidebar.py:71 selfdrive/ui/layouts/sidebar.py:125 -msgid "GOOD" -msgstr "" - -#: selfdrive/ui/layouts/sidebar.py:72 selfdrive/ui/layouts/sidebar.py:144 -msgid "VEHICLE" -msgstr "" - -#: selfdrive/ui/layouts/sidebar.py:72 selfdrive/ui/layouts/sidebar.py:136 -#: selfdrive/ui/layouts/sidebar.py:144 -msgid "ONLINE" -msgstr "" - -#: selfdrive/ui/layouts/sidebar.py:73 selfdrive/ui/layouts/sidebar.py:134 -msgid "OFFLINE" -msgstr "" - -#: selfdrive/ui/layouts/sidebar.py:117 -msgid "Unknown" -msgstr "" - -#: selfdrive/ui/layouts/sidebar.py:129 -msgid "HIGH" -msgstr "" - -#: selfdrive/ui/layouts/sidebar.py:138 -msgid "ERROR" -msgstr "" - -#: selfdrive/ui/layouts/sidebar.py:142 -msgid "NO" -msgstr "" - -#: selfdrive/ui/layouts/sidebar.py:142 -msgid "PANDA" -msgstr "" - -#: selfdrive/ui/layouts/onboarding.py:111 +#: openpilot/selfdrive/ui/layouts/onboarding.py:115 #, python-format msgid "Welcome to openpilot" msgstr "" -#: selfdrive/ui/layouts/onboarding.py:112 +#: openpilot/selfdrive/ui/layouts/onboarding.py:116 #, python-format -msgid "" -"You must accept the Terms and Conditions to use openpilot. Read the latest " -"terms at https://comma.ai/terms before continuing." +msgid "You must accept the Terms and Conditions to use openpilot. Read the latest terms at https://comma.ai/terms before continuing." msgstr "" -#: selfdrive/ui/layouts/onboarding.py:115 +#: openpilot/selfdrive/ui/layouts/onboarding.py:119 #, python-format msgid "Decline" msgstr "" -#: selfdrive/ui/layouts/onboarding.py:116 +#: openpilot/selfdrive/ui/layouts/onboarding.py:120 #, python-format msgid "Agree" msgstr "" -#: selfdrive/ui/layouts/onboarding.py:145 +#: openpilot/selfdrive/ui/layouts/onboarding.py:149 #, python-format msgid "You must accept the Terms and Conditions in order to use openpilot." msgstr "" -#: selfdrive/ui/layouts/onboarding.py:148 +#: openpilot/selfdrive/ui/layouts/onboarding.py:152 #, python-format msgid "Decline, uninstall openpilot" msgstr "" -#: selfdrive/ui/layouts/settings/firehose.py:18 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:20 +msgid "When enabled, pressing the accelerator pedal will disengage openpilot." +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:30 +msgid "Enable driver monitoring even when openpilot is not engaged." +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:31 +msgid "Upload data from the driver facing camera and help improve the driver monitoring algorithm." +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:32 +msgid "Display speed in km/h instead of mph." +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:33 +msgid "Record and store microphone audio while driving. The audio will be included in the dashcam video in comma connect." +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:96 +#, python-format +msgid "Driving Personality" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:125 +#, python-format +msgid "Changing this setting will restart openpilot if the car is powered on." +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:182 +#, python-format +msgid "Experimental mode is currently unavailable on this car since the car's stock ACC is used for longitudinal control." +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:229 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:180 +#, python-format +msgid "Enable" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:46 +#, python-format +msgid "Enable openpilot" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:52 +#, python-format +msgid "Experimental Mode" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:58 +#, python-format +msgid "Disengage on Accelerator Pedal" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:64 +#, python-format +msgid "Enable Lane Departure Warnings" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:70 +#, python-format +msgid "Always-On Driver Monitoring" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:76 +#, python-format +msgid "Record and Upload Driver Camera" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:82 +#, python-format +msgid "Record and Upload Microphone Audio" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:88 +#, python-format +msgid "Use Metric System" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:184 +#, python-format +msgid "openpilot longitudinal control may come in a future update." +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:98 +#, python-format +msgid "Aggressive" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:98 +#, python-format +msgid "Standard" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:98 +#, python-format +msgid "Relaxed" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:190 +#, python-format +msgid "Enable the openpilot longitudinal control (alpha) toggle to allow Experimental mode." +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/settings.py:59 +msgid "Device" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/settings.py:60 +msgid "Network" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/settings.py:61 +msgid "Toggles" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/settings.py:62 +msgid "Software" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/settings.py:63 +msgid "Firehose" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/settings.py:64 +msgid "Developer" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/device.py:24 +msgid "Preview the driver facing camera to ensure that driver monitoring has good visibility. (vehicle must be off)" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/device.py:25 +msgid "openpilot requires the device to be mounted within 4° left or right and within 5° up or 9° down." +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/device.py:26 +msgid "Review the rules, features, and limitations of openpilot" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/device.py:89 +#, python-format +msgid "Select a language" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/device.py:111 +#, python-format +msgid "Are you sure you want to reset calibration?" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/device.py:111 +#, python-format +msgid "Reset" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/device.py:140 +#, python-format +msgid "

Steering lag calibration is complete." +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/device.py:171 +#, python-format +msgid "Are you sure you want to reboot?" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/device.py:171 +#: openpilot/selfdrive/ui/layouts/settings/device.py:53 +#, python-format +msgid "Reboot" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/device.py:183 +#, python-format +msgid "Are you sure you want to power off?" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/device.py:183 +#: openpilot/selfdrive/ui/layouts/settings/device.py:53 +#, python-format +msgid "Power Off" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/device.py:45 +#, python-format +msgid "Pair Device" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/device.py:45 +#, python-format +msgid "PAIR" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/device.py:49 +#, python-format +msgid "Reset Calibration" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/device.py:49 +#, python-format +msgid "RESET" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/device.py:57 +#, python-format +msgid "Dongle ID" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/device.py:58 +#, python-format +msgid "Serial" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/device.py:60 +#, python-format +msgid "Driver Camera" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/device.py:60 +#, python-format +msgid "PREVIEW" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/device.py:63 +#, python-format +msgid "Review Training Guide" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/device.py:63 +#, python-format +msgid "REVIEW" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/device.py:65 +#, python-format +msgid "Regulatory" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/device.py:65 +#, python-format +msgid "VIEW" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/device.py:66 +#, python-format +msgid "Change Language" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/device.py:66 +#, python-format +msgid "CHANGE" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/device.py:95 +#, python-format +msgid "Disengage to Reset Calibration" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/device.py:138 +#, python-format +msgid "

Steering lag calibration is {}% complete." +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/device.py:164 +#, python-format +msgid "Disengage to Reboot" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/device.py:176 +#, python-format +msgid "Disengage to Power Off" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/device.py:57 +#: openpilot/selfdrive/ui/layouts/settings/device.py:58 +#, python-format +msgid "N/A" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/device.py:152 +#, python-format +msgid " Steering torque response calibration is complete." +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/device.py:125 +#, python-format +msgid " Your device is pointed {:.1f}° {} and {:.1f}° {}." +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/device.py:125 +#, python-format +msgid "down" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/device.py:125 +#, python-format +msgid "up" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/device.py:126 +#, python-format +msgid "left" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/device.py:126 +#, python-format +msgid "right" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/device.py:150 +#, python-format +msgid " Steering torque response calibration is {}% complete." +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/firehose.py:10 msgid "Firehose Mode" msgstr "" -#: selfdrive/ui/layouts/settings/firehose.py:20 -msgid "" -"openpilot learns to drive by watching humans, like you, drive.\n" -"\n" -"Firehose Mode allows you to maximize your training data uploads to improve " -"openpilot's driving models. More data means bigger models, which means " -"better Experimental Mode." -msgstr "" - -#: selfdrive/ui/layouts/settings/firehose.py:25 -msgid "" -"For maximum effectiveness, bring your device inside and connect to a good " -"USB-C adapter and Wi-Fi weekly.\n" -"\n" -"Firehose Mode can also work while you're driving if connected to a hotspot " -"or unlimited SIM card.\n" -"\n" -"\n" -"Frequently Asked Questions\n" -"\n" -"Does it matter how or where I drive? Nope, just drive as you normally " -"would.\n" -"\n" -"Do all of my segments get pulled in Firehose Mode? No, we selectively pull a " -"subset of your segments.\n" -"\n" -"What's a good USB-C adapter? Any fast phone or laptop charger should be " -"fine.\n" -"\n" -"Does it matter which software I run? Yes, only upstream openpilot (and " -"particular forks) are able to be used for training." -msgstr "" - -#: selfdrive/ui/layouts/settings/firehose.py:111 +#: openpilot/selfdrive/ui/layouts/settings/firehose.py:70 #, python-format msgid "{} segment of your driving is in the training dataset so far." msgid_plural "{} segments of your driving is in the training dataset so far." msgstr[0] "" msgstr[1] "" -#: selfdrive/ui/layouts/settings/firehose.py:138 +#: openpilot/selfdrive/ui/layouts/settings/software.py:19 #, python-format -msgid "ACTIVE" +msgid "checking..." msgstr "" -#: selfdrive/ui/layouts/settings/firehose.py:140 +#: openpilot/selfdrive/ui/layouts/settings/software.py:20 #, python-format -msgid "INACTIVE: connect to an unmetered network" +msgid "downloading..." msgstr "" -#: selfdrive/ui/layouts/settings/developer.py:15 -msgid "" -"ADB (Android Debug Bridge) allows connecting to your device over USB or over " -"the network. See https://docs.comma.ai/how-to/connect-to-comma for more info." -msgstr "" - -#: selfdrive/ui/layouts/settings/developer.py:19 -msgid "" -"Warning: This grants SSH access to all public keys in your GitHub settings. " -"Never enter a GitHub username other than your own. A comma employee will " -"NEVER ask you to add their GitHub username." -msgstr "" - -#: selfdrive/ui/layouts/settings/developer.py:23 -msgid "" -"WARNING: openpilot longitudinal control is in alpha for this car and will " -"disable Automatic Emergency Braking (AEB).

On this car, openpilot " -"defaults to the car's built-in ACC instead of openpilot's longitudinal " -"control. Enable this to switch to openpilot longitudinal control. Enabling " -"Experimental mode is recommended when enabling openpilot longitudinal " -"control alpha. Changing this setting will restart openpilot if the car is " -"powered on." -msgstr "" - -#: selfdrive/ui/layouts/settings/developer.py:39 +#: openpilot/selfdrive/ui/layouts/settings/software.py:21 #, python-format -msgid "Enable ADB" +msgid "finalizing update..." msgstr "" -#: selfdrive/ui/layouts/settings/developer.py:48 -#, python-format -msgid "Enable SSH" -msgstr "" - -#: selfdrive/ui/layouts/settings/developer.py:53 -#, python-format -msgid "SSH Keys" -msgstr "" - -#: selfdrive/ui/layouts/settings/developer.py:56 -#, python-format -msgid "Joystick Debug Mode" -msgstr "" - -#: selfdrive/ui/layouts/settings/developer.py:64 -#, python-format -msgid "Longitudinal Maneuver Mode" -msgstr "" - -#: selfdrive/ui/layouts/settings/developer.py:71 -#, python-format -msgid "openpilot Longitudinal Control (Alpha)" -msgstr "" - -#: selfdrive/ui/layouts/settings/developer.py:166 -#: selfdrive/ui/layouts/settings/toggles.py:228 -#, python-format -msgid "Enable" -msgstr "" - -#: selfdrive/ui/layouts/settings/software.py:20 +#: openpilot/selfdrive/ui/layouts/settings/software.py:27 #, python-format msgid "never" msgstr "" -#: selfdrive/ui/layouts/settings/software.py:31 +#: openpilot/selfdrive/ui/layouts/settings/software.py:38 #, python-format msgid "now" msgstr "" -#: selfdrive/ui/layouts/settings/software.py:34 +#: openpilot/selfdrive/ui/layouts/settings/software.py:157 +#: openpilot/selfdrive/ui/layouts/settings/software.py:57 +#: openpilot/selfdrive/ui/layouts/settings/software.py:117 +#: openpilot/selfdrive/ui/layouts/settings/software.py:128 +#, python-format +msgid "CHECK" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/software.py:173 +#, python-format +msgid "Are you sure you want to uninstall?" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/software.py:173 +#: openpilot/selfdrive/ui/layouts/settings/software.py:79 +#, python-format +msgid "Uninstall" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/software.py:203 +#, python-format +msgid "Select a branch" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/software.py:41 #, python-format msgid "{} minute ago" msgid_plural "{} minutes ago" msgstr[0] "" msgstr[1] "" -#: selfdrive/ui/layouts/settings/software.py:37 +#: openpilot/selfdrive/ui/layouts/settings/software.py:44 #, python-format msgid "{} hour ago" msgid_plural "{} hours ago" msgstr[0] "" msgstr[1] "" -#: selfdrive/ui/layouts/settings/software.py:40 +#: openpilot/selfdrive/ui/layouts/settings/software.py:47 #, python-format msgid "{} day ago" msgid_plural "{} days ago" msgstr[0] "" msgstr[1] "" -#: selfdrive/ui/layouts/settings/software.py:48 +#: openpilot/selfdrive/ui/layouts/settings/software.py:55 #, python-format msgid "Updates are only downloaded while the car is off." msgstr "" -#: selfdrive/ui/layouts/settings/software.py:49 +#: openpilot/selfdrive/ui/layouts/settings/software.py:56 #, python-format msgid "Current Version" msgstr "" -#: selfdrive/ui/layouts/settings/software.py:50 +#: openpilot/selfdrive/ui/layouts/settings/software.py:57 #, python-format msgid "Download" msgstr "" -#: selfdrive/ui/layouts/settings/software.py:50 -#: selfdrive/ui/layouts/settings/software.py:107 -#: selfdrive/ui/layouts/settings/software.py:118 -#: selfdrive/ui/layouts/settings/software.py:147 -#, python-format -msgid "CHECK" -msgstr "" - -#: selfdrive/ui/layouts/settings/software.py:53 +#: openpilot/selfdrive/ui/layouts/settings/software.py:60 #, python-format msgid "Install Update" msgstr "" -#: selfdrive/ui/layouts/settings/software.py:53 -#: selfdrive/ui/layouts/settings/software.py:136 +#: openpilot/selfdrive/ui/layouts/settings/software.py:60 +#: openpilot/selfdrive/ui/layouts/settings/software.py:146 #, python-format msgid "INSTALL" msgstr "" -#: selfdrive/ui/layouts/settings/software.py:61 +#: openpilot/selfdrive/ui/layouts/settings/software.py:68 #, python-format msgid "Target Branch" msgstr "" -#: selfdrive/ui/layouts/settings/software.py:61 +#: openpilot/selfdrive/ui/layouts/settings/software.py:68 #, python-format msgid "SELECT" msgstr "" -#: selfdrive/ui/layouts/settings/software.py:72 -#: selfdrive/ui/layouts/settings/software.py:163 -#, python-format -msgid "Uninstall" -msgstr "" - -#: selfdrive/ui/layouts/settings/software.py:72 -#, python-format -msgid "UNINSTALL" -msgstr "" - -#: selfdrive/ui/layouts/settings/software.py:106 +#: openpilot/selfdrive/ui/layouts/settings/software.py:116 #, python-format msgid "failed to check for update" msgstr "" -#: selfdrive/ui/layouts/settings/software.py:109 +#: openpilot/selfdrive/ui/layouts/settings/software.py:79 +#, python-format +msgid "UNINSTALL" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/software.py:119 #, python-format msgid "update available" msgstr "" -#: selfdrive/ui/layouts/settings/software.py:110 +#: openpilot/selfdrive/ui/layouts/settings/software.py:120 #, python-format msgid "DOWNLOAD" msgstr "" -#: selfdrive/ui/layouts/settings/software.py:115 -#, python-format -msgid "up to date, last checked {}" -msgstr "" - -#: selfdrive/ui/layouts/settings/software.py:117 +#: openpilot/selfdrive/ui/layouts/settings/software.py:127 #, python-format msgid "up to date, last checked never" msgstr "" -#: selfdrive/ui/layouts/settings/software.py:163 +#: openpilot/selfdrive/ui/layouts/settings/software.py:125 #, python-format -msgid "Are you sure you want to uninstall?" +msgid "up to date, last checked {}" msgstr "" -#: selfdrive/ui/layouts/settings/software.py:183 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:39 #, python-format -msgid "Select a branch" +msgid "Enable ADB" msgstr "" -#: selfdrive/ui/layouts/settings/device.py:25 -msgid "" -"Preview the driver facing camera to ensure that driver monitoring has good " -"visibility. (vehicle must be off)" -msgstr "" - -#: selfdrive/ui/layouts/settings/device.py:26 -msgid "" -"openpilot requires the device to be mounted within 4° left or right and " -"within 5° up or 9° down." -msgstr "" - -#: selfdrive/ui/layouts/settings/device.py:27 -msgid "Review the rules, features, and limitations of openpilot" -msgstr "" - -#: selfdrive/ui/layouts/settings/device.py:48 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:48 #, python-format -msgid "Pair Device" +msgid "Enable SSH" msgstr "" -#: selfdrive/ui/layouts/settings/device.py:48 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:53 #, python-format -msgid "PAIR" +msgid "SSH Keys" msgstr "" -#: selfdrive/ui/layouts/settings/device.py:51 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:56 #, python-format -msgid "Reset Calibration" +msgid "Joystick Debug Mode" msgstr "" -#: selfdrive/ui/layouts/settings/device.py:51 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:64 #, python-format -msgid "RESET" +msgid "Longitudinal Maneuver Mode" msgstr "" -#: selfdrive/ui/layouts/settings/device.py:55 -#: selfdrive/ui/layouts/settings/device.py:175 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:71 #, python-format -msgid "Reboot" +msgid "openpilot Longitudinal Control (Alpha)" msgstr "" -#: selfdrive/ui/layouts/settings/device.py:55 -#: selfdrive/ui/layouts/settings/device.py:187 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:79 #, python-format -msgid "Power Off" +msgid "UI Debug Mode" msgstr "" -#: selfdrive/ui/layouts/settings/device.py:59 -#, python-format -msgid "Dongle ID" -msgstr "" - -#: selfdrive/ui/layouts/settings/device.py:59 -#: selfdrive/ui/layouts/settings/device.py:60 -#, python-format -msgid "N/A" -msgstr "" - -#: selfdrive/ui/layouts/settings/device.py:60 -#, python-format -msgid "Serial" -msgstr "" - -#: selfdrive/ui/layouts/settings/device.py:62 -#, python-format -msgid "Driver Camera" -msgstr "" - -#: selfdrive/ui/layouts/settings/device.py:62 -#, python-format -msgid "PREVIEW" -msgstr "" - -#: selfdrive/ui/layouts/settings/device.py:65 -#, python-format -msgid "Review Training Guide" -msgstr "" - -#: selfdrive/ui/layouts/settings/device.py:65 -#, python-format -msgid "REVIEW" -msgstr "" - -#: selfdrive/ui/layouts/settings/device.py:67 -#, python-format -msgid "Regulatory" -msgstr "" - -#: selfdrive/ui/layouts/settings/device.py:67 -#, python-format -msgid "VIEW" -msgstr "" - -#: selfdrive/ui/layouts/settings/device.py:68 -#, python-format -msgid "Change Language" -msgstr "" - -#: selfdrive/ui/layouts/settings/device.py:68 -#, python-format -msgid "CHANGE" -msgstr "" - -#: selfdrive/ui/layouts/settings/device.py:91 -#, python-format -msgid "Select a language" -msgstr "" - -#: selfdrive/ui/layouts/settings/device.py:103 -#, python-format -msgid "Disengage to Reset Calibration" -msgstr "" - -#: selfdrive/ui/layouts/settings/device.py:119 -#, python-format -msgid "Are you sure you want to reset calibration?" -msgstr "" - -#: selfdrive/ui/layouts/settings/device.py:119 -#, python-format -msgid "Reset" -msgstr "" - -#: selfdrive/ui/layouts/settings/device.py:133 -#, python-format -msgid " Your device is pointed {:.1f}° {} and {:.1f}° {}." -msgstr "" - -#: selfdrive/ui/layouts/settings/device.py:133 -#, python-format -msgid "down" -msgstr "" - -#: selfdrive/ui/layouts/settings/device.py:133 -#, python-format -msgid "up" -msgstr "" - -#: selfdrive/ui/layouts/settings/device.py:134 -#, python-format -msgid "left" -msgstr "" - -#: selfdrive/ui/layouts/settings/device.py:134 -#, python-format -msgid "right" -msgstr "" - -#: selfdrive/ui/layouts/settings/device.py:146 -#, python-format -msgid "

Steering lag calibration is {}% complete." -msgstr "" - -#: selfdrive/ui/layouts/settings/device.py:148 -#, python-format -msgid "

Steering lag calibration is complete." -msgstr "" - -#: selfdrive/ui/layouts/settings/device.py:158 -#, python-format -msgid " Steering torque response calibration is {}% complete." -msgstr "" - -#: selfdrive/ui/layouts/settings/device.py:160 -#, python-format -msgid " Steering torque response calibration is complete." -msgstr "" - -#: selfdrive/ui/layouts/settings/device.py:165 -#, python-format -msgid "" -"openpilot is continuously calibrating, resetting is rarely required. " -"Resetting calibration will restart openpilot if the car is powered on." -msgstr "" - -#: selfdrive/ui/layouts/settings/device.py:172 -#, python-format -msgid "Disengage to Reboot" -msgstr "" - -#: selfdrive/ui/layouts/settings/device.py:175 -#, python-format -msgid "Are you sure you want to reboot?" -msgstr "" - -#: selfdrive/ui/layouts/settings/device.py:184 -#, python-format -msgid "Disengage to Power Off" -msgstr "" - -#: selfdrive/ui/layouts/settings/device.py:187 -#, python-format -msgid "Are you sure you want to power off?" -msgstr "" - -#: selfdrive/ui/layouts/settings/settings.py:62 -msgid "Device" -msgstr "" - -#: selfdrive/ui/layouts/settings/settings.py:63 -msgid "Network" -msgstr "" - -#: selfdrive/ui/layouts/settings/settings.py:64 -msgid "Toggles" -msgstr "" - -#: selfdrive/ui/layouts/settings/settings.py:65 -msgid "Software" -msgstr "" - -#: selfdrive/ui/layouts/settings/settings.py:66 -msgid "Firehose" -msgstr "" - -#: selfdrive/ui/layouts/settings/settings.py:67 -msgid "Developer" -msgstr "" - -#: selfdrive/ui/layouts/settings/toggles.py:17 -msgid "" -"Use the openpilot system for adaptive cruise control and lane keep driver " -"assistance. Your attention is required at all times to use this feature." -msgstr "" - -#: selfdrive/ui/layouts/settings/toggles.py:20 -msgid "When enabled, pressing the accelerator pedal will disengage openpilot." -msgstr "" - -#: selfdrive/ui/layouts/settings/toggles.py:22 -msgid "" -"Standard is recommended. In aggressive mode, openpilot will follow lead cars " -"closer and be more aggressive with the gas and brake. In relaxed mode " -"openpilot will stay further away from lead cars. On supported cars, you can " -"cycle through these personalities with your steering wheel distance button." -msgstr "" - -#: selfdrive/ui/layouts/settings/toggles.py:27 -msgid "" -"Receive alerts to steer back into the lane when your vehicle drifts over a " -"detected lane line without a turn signal activated while driving over 31 mph " -"(50 km/h)." -msgstr "" - -#: selfdrive/ui/layouts/settings/toggles.py:30 -msgid "Enable driver monitoring even when openpilot is not engaged." -msgstr "" - -#: selfdrive/ui/layouts/settings/toggles.py:31 -msgid "" -"Upload data from the driver facing camera and help improve the driver " -"monitoring algorithm." -msgstr "" - -#: selfdrive/ui/layouts/settings/toggles.py:32 -msgid "Display speed in km/h instead of mph." -msgstr "" - -#: selfdrive/ui/layouts/settings/toggles.py:33 -msgid "" -"Record and store microphone audio while driving. The audio will be included " -"in the dashcam video in comma connect." -msgstr "" - -#: selfdrive/ui/layouts/settings/toggles.py:46 -#, python-format -msgid "Enable openpilot" -msgstr "" - -#: selfdrive/ui/layouts/settings/toggles.py:52 -#, python-format -msgid "Experimental Mode" -msgstr "" - -#: selfdrive/ui/layouts/settings/toggles.py:58 -#, python-format -msgid "Disengage on Accelerator Pedal" -msgstr "" - -#: selfdrive/ui/layouts/settings/toggles.py:64 -#, python-format -msgid "Enable Lane Departure Warnings" -msgstr "" - -#: selfdrive/ui/layouts/settings/toggles.py:70 -#, python-format -msgid "Always-On Driver Monitoring" -msgstr "" - -#: selfdrive/ui/layouts/settings/toggles.py:76 -#, python-format -msgid "Record and Upload Driver Camera" -msgstr "" - -#: selfdrive/ui/layouts/settings/toggles.py:82 -#, python-format -msgid "Record and Upload Microphone Audio" -msgstr "" - -#: selfdrive/ui/layouts/settings/toggles.py:88 -#, python-format -msgid "Use Metric System" -msgstr "" - -#: selfdrive/ui/layouts/settings/toggles.py:96 -#, python-format -msgid "Driving Personality" -msgstr "" - -#: selfdrive/ui/layouts/settings/toggles.py:98 -#, python-format -msgid "Aggressive" -msgstr "" - -#: selfdrive/ui/layouts/settings/toggles.py:98 -#, python-format -msgid "Standard" -msgstr "" - -#: selfdrive/ui/layouts/settings/toggles.py:98 -#, python-format -msgid "Relaxed" -msgstr "" - -#: selfdrive/ui/layouts/settings/toggles.py:125 -#, python-format -msgid "Changing this setting will restart openpilot if the car is powered on." -msgstr "" - -#: selfdrive/ui/layouts/settings/toggles.py:158 -#, python-format -msgid "" -"openpilot defaults to driving in chill mode. Experimental mode enables alpha-" -"level features that aren't ready for chill mode. Experimental features are " -"listed below:

End-to-End Longitudinal Control


Let the driving " -"model control the gas and brakes. openpilot will drive as it thinks a human " -"would, including stopping for red lights and stop signs. Since the driving " -"model decides the speed to drive, the set speed will only act as an upper " -"bound. This is an alpha quality feature; mistakes should be expected." -"

New Driving Visualization


The driving visualization will " -"transition to the road-facing wide-angle camera at low speeds to better show " -"some turns. The Experimental mode logo will also be shown in the top right " -"corner." -msgstr "" - -#: selfdrive/ui/layouts/settings/toggles.py:181 -#, python-format -msgid "" -"Experimental mode is currently unavailable on this car since the car's stock " -"ACC is used for longitudinal control." -msgstr "" - -#: selfdrive/ui/layouts/settings/toggles.py:183 -#, python-format -msgid "openpilot longitudinal control may come in a future update." -msgstr "" - -#: selfdrive/ui/layouts/settings/toggles.py:186 -#, python-format -msgid "" -"An alpha version of openpilot longitudinal control can be tested, along with " -"Experimental mode, on non-release branches." -msgstr "" - -#: selfdrive/ui/layouts/settings/toggles.py:189 -#, python-format -msgid "" -"Enable the openpilot longitudinal control (alpha) toggle to allow " -"Experimental mode." -msgstr "" - -#: selfdrive/ui/onroad/hud_renderer.py:148 -#, python-format -msgid "MAX" -msgstr "" - -#: selfdrive/ui/onroad/hud_renderer.py:177 -#, python-format -msgid "km/h" -msgstr "" - -#: selfdrive/ui/onroad/hud_renderer.py:177 -#, python-format -msgid "mph" -msgstr "" - -#: selfdrive/ui/onroad/driver_camera_dialog.py:34 -#, python-format -msgid "camera starting" -msgstr "" - -#: selfdrive/ui/onroad/alert_renderer.py:51 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:51 #, python-format msgid "openpilot Unavailable" msgstr "" -#: selfdrive/ui/onroad/alert_renderer.py:52 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:52 #, python-format msgid "Waiting to start" msgstr "" -#: selfdrive/ui/onroad/alert_renderer.py:58 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:58 #, python-format msgid "TAKE CONTROL IMMEDIATELY" msgstr "" -#: selfdrive/ui/onroad/alert_renderer.py:59 -#: selfdrive/ui/onroad/alert_renderer.py:65 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:59 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:65 #, python-format msgid "System Unresponsive" msgstr "" -#: selfdrive/ui/onroad/alert_renderer.py:66 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:66 #, python-format msgid "Reboot Device" msgstr "" + +#: openpilot/selfdrive/ui/onroad/driver_camera_dialog.py:38 +#, python-format +msgid "camera starting" +msgstr "" + +#: openpilot/selfdrive/ui/onroad/hud_renderer.py:148 +#, python-format +msgid "MAX" +msgstr "" + +#: openpilot/selfdrive/ui/onroad/hud_renderer.py:177 +#, python-format +msgid "km/h" +msgstr "" + +#: openpilot/selfdrive/ui/onroad/hud_renderer.py:177 +#, python-format +msgid "mph" +msgstr "" + diff --git a/selfdrive/ui/translations/app_de.po b/selfdrive/ui/translations/app_de.po index f32c27a9ef..9888bb718e 100644 --- a/selfdrive/ui/translations/app_de.po +++ b/selfdrive/ui/translations/app_de.po @@ -17,1205 +17,1018 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -#: selfdrive/ui/layouts/settings/device.py:160 +#: openpilot/selfdrive/ui/layouts/settings/device.py:152 #, python-format msgid " Steering torque response calibration is complete." msgstr " Die Lenkmoment-Reaktionskalibrierung ist abgeschlossen." -#: selfdrive/ui/layouts/settings/device.py:158 +#: openpilot/selfdrive/ui/layouts/settings/device.py:150 #, python-format msgid " Steering torque response calibration is {}% complete." msgstr " Die Lenkmoment-Reaktionskalibrierung ist zu {}% abgeschlossen." -#: selfdrive/ui/layouts/settings/device.py:133 +#: openpilot/selfdrive/ui/layouts/settings/device.py:125 #, python-format msgid " Your device is pointed {:.1f}° {} and {:.1f}° {}." msgstr " Ihr Gerät ist um {:.1f}° {} und {:.1f}° {} ausgerichtet." -#: selfdrive/ui/layouts/sidebar.py:43 +#: openpilot/selfdrive/ui/layouts/sidebar.py:43 msgid "--" msgstr "--" -#: selfdrive/ui/widgets/prime.py:47 +#: openpilot/selfdrive/ui/widgets/prime.py:47 #, python-format msgid "1 year of drive storage" msgstr "1 Jahr Fahrtdatenspeicherung" -#: selfdrive/ui/widgets/prime.py:47 +#: openpilot/selfdrive/ui/widgets/prime.py:47 #, python-format msgid "24/7 LTE connectivity" msgstr "24/7 LTE‑Verbindung" -#: selfdrive/ui/layouts/sidebar.py:46 +#: openpilot/selfdrive/ui/layouts/sidebar.py:46 msgid "2G" msgstr "2G" -#: selfdrive/ui/layouts/sidebar.py:47 +#: openpilot/selfdrive/ui/layouts/sidebar.py:47 msgid "3G" msgstr "3G" -#: selfdrive/ui/layouts/sidebar.py:49 +#: openpilot/selfdrive/ui/layouts/sidebar.py:49 msgid "5G" msgstr "5G" -#: selfdrive/ui/layouts/settings/developer.py:23 -msgid "" -"WARNING: openpilot longitudinal control is in alpha for this car and will " -"disable Automatic Emergency Braking (AEB).

On this car, openpilot " -"defaults to the car's built-in ACC instead of openpilot's longitudinal " -"control. Enable this to switch to openpilot longitudinal control. Enabling " -"Experimental mode is recommended when enabling openpilot longitudinal " -"control alpha. Changing this setting will restart openpilot if the car is " -"powered on." -msgstr "" -"WARNUNG: Die Längsregelung von openpilot befindet sich für dieses " -"Fahrzeug in der Alpha-Phase und deaktiviert das automatische Notbremssystem " -"(AEB).

Auf diesem Fahrzeug verwendet openpilot standardmäßig den " -"integrierten ACC statt der openpilot-Längsregelung. Aktivieren Sie dies, um " -"auf die openpilot-Längsregelung umzuschalten. Das Aktivieren des " -"Experimentalmodus wird empfohlen, wenn Sie die openpilot-Längsregelung " -"(Alpha) aktivieren." - -#: selfdrive/ui/layouts/settings/device.py:148 +#: openpilot/selfdrive/ui/layouts/settings/device.py:140 #, python-format msgid "

Steering lag calibration is complete." msgstr "

Kalibrierung der Lenkverzögerung abgeschlossen." -#: selfdrive/ui/layouts/settings/device.py:146 +#: openpilot/selfdrive/ui/layouts/settings/device.py:138 #, python-format msgid "

Steering lag calibration is {}% complete." msgstr "

Kalibrierung der Lenkverzögerung zu {}% abgeschlossen." -#: selfdrive/ui/layouts/settings/firehose.py:138 -#, python-format -msgid "ACTIVE" -msgstr "AKTIV" - -#: selfdrive/ui/layouts/settings/developer.py:15 -msgid "" -"ADB (Android Debug Bridge) allows connecting to your device over USB or over " -"the network. See https://docs.comma.ai/how-to/connect-to-comma for more info." -msgstr "" -"ADB (Android Debug Bridge) ermöglicht die Verbindung mit Ihrem Gerät über " -"USB oder über das Netzwerk. Siehe https://docs.comma.ai/how-to/connect-to-" -"comma für weitere Informationen." - -#: selfdrive/ui/widgets/ssh_key.py:30 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:30 msgid "ADD" msgstr "HINZUFÜGEN" -#: system/ui/widgets/network.py:139 +#: system/ui/widgets/network.py:136 #, python-format msgid "APN Setting" msgstr "APN‑Einstellung" -#: selfdrive/ui/widgets/offroad_alerts.py:109 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:109 #, python-format msgid "Acknowledge Excessive Actuation" msgstr "Übermäßige Betätigung bestätigen" -#: system/ui/widgets/network.py:74 system/ui/widgets/network.py:95 +#: system/ui/widgets/network.py:92 +#: system/ui/widgets/network.py:74 #, python-format msgid "Advanced" msgstr "Erweitert" -#: selfdrive/ui/layouts/settings/toggles.py:98 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:98 #, python-format msgid "Aggressive" msgstr "Aggressiv" -#: selfdrive/ui/layouts/onboarding.py:116 +#: openpilot/selfdrive/ui/layouts/onboarding.py:120 #, python-format msgid "Agree" msgstr "Zustimmen" -#: selfdrive/ui/layouts/settings/toggles.py:70 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:70 #, python-format msgid "Always-On Driver Monitoring" msgstr "Immer aktive Fahrerüberwachung" -#: selfdrive/ui/layouts/settings/toggles.py:186 -#, python-format -msgid "" -"An alpha version of openpilot longitudinal control can be tested, along with " -"Experimental mode, on non-release branches." -msgstr "" -"Eine Alpha-Version der openpilot-Längsregelung kann zusammen mit dem " -"Experimentalmodus auf Nicht-Release-Zweigen getestet werden." - -#: selfdrive/ui/layouts/settings/device.py:187 +#: openpilot/selfdrive/ui/layouts/settings/device.py:183 #, python-format msgid "Are you sure you want to power off?" msgstr "Sind Sie sicher, dass Sie ausschalten möchten?" -#: selfdrive/ui/layouts/settings/device.py:175 +#: openpilot/selfdrive/ui/layouts/settings/device.py:171 #, python-format msgid "Are you sure you want to reboot?" msgstr "Sind Sie sicher, dass Sie neu starten möchten?" -#: selfdrive/ui/layouts/settings/device.py:119 +#: openpilot/selfdrive/ui/layouts/settings/device.py:111 #, python-format msgid "Are you sure you want to reset calibration?" msgstr "Sind Sie sicher, dass Sie die Kalibrierung zurücksetzen möchten?" -#: selfdrive/ui/layouts/settings/software.py:163 +#: openpilot/selfdrive/ui/layouts/settings/software.py:173 #, python-format msgid "Are you sure you want to uninstall?" msgstr "Sind Sie sicher, dass Sie deinstallieren möchten?" -#: system/ui/widgets/network.py:99 selfdrive/ui/layouts/onboarding.py:147 +#: system/ui/widgets/network.py:96 +#: openpilot/selfdrive/ui/layouts/onboarding.py:151 #, python-format msgid "Back" msgstr "Zurück" -#: selfdrive/ui/widgets/prime.py:38 +#: openpilot/selfdrive/ui/widgets/prime.py:38 #, python-format msgid "Become a comma prime member at connect.comma.ai" msgstr "Werden Sie comma prime Mitglied auf connect.comma.ai" -#: selfdrive/ui/widgets/pairing_dialog.py:130 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:119 #, python-format msgid "Bookmark connect.comma.ai to your home screen to use it like an app" -msgstr "" -"Fügen Sie connect.comma.ai Ihrem Startbildschirm hinzu, um es wie eine App " -"zu verwenden" +msgstr "Fügen Sie connect.comma.ai Ihrem Startbildschirm hinzu, um es wie eine App zu verwenden" -#: selfdrive/ui/layouts/settings/device.py:68 +#: openpilot/selfdrive/ui/layouts/settings/device.py:66 #, python-format msgid "CHANGE" msgstr "ÄNDERN" -#: selfdrive/ui/layouts/settings/software.py:50 -#: selfdrive/ui/layouts/settings/software.py:107 -#: selfdrive/ui/layouts/settings/software.py:118 -#: selfdrive/ui/layouts/settings/software.py:147 +#: openpilot/selfdrive/ui/layouts/settings/software.py:157 +#: openpilot/selfdrive/ui/layouts/settings/software.py:57 +#: openpilot/selfdrive/ui/layouts/settings/software.py:117 +#: openpilot/selfdrive/ui/layouts/settings/software.py:128 #, python-format msgid "CHECK" msgstr "PRÜFEN" -#: selfdrive/ui/widgets/exp_mode_button.py:50 +#: openpilot/selfdrive/ui/widgets/exp_mode_button.py:51 #, python-format msgid "CHILL MODE ON" msgstr "CHILL‑MODUS AKTIV" -#: system/ui/widgets/network.py:155 selfdrive/ui/layouts/sidebar.py:73 -#: selfdrive/ui/layouts/sidebar.py:134 selfdrive/ui/layouts/sidebar.py:136 -#: selfdrive/ui/layouts/sidebar.py:138 +#: system/ui/widgets/network.py:152 +#: openpilot/selfdrive/ui/layouts/sidebar.py:73 +#: openpilot/selfdrive/ui/layouts/sidebar.py:134 +#: openpilot/selfdrive/ui/layouts/sidebar.py:136 +#: openpilot/selfdrive/ui/layouts/sidebar.py:138 #, python-format msgid "CONNECT" msgstr "VERBINDUNG" -#: system/ui/widgets/network.py:369 +#: system/ui/widgets/network.py:376 #, python-format msgid "CONNECTING..." msgstr "VERBINDUNG" -#: system/ui/widgets/confirm_dialog.py:23 system/ui/widgets/option_dialog.py:35 -#: system/ui/widgets/keyboard.py:81 system/ui/widgets/network.py:318 +#: system/ui/widgets/network.py:326 +#: system/ui/widgets/confirm_dialog.py:24 +#: system/ui/widgets/option_dialog.py:36 +#: system/ui/widgets/keyboard.py:83 #, python-format msgid "Cancel" msgstr "Abbrechen" -#: system/ui/widgets/network.py:134 +#: system/ui/widgets/network.py:131 #, python-format msgid "Cellular Metered" msgstr "Getaktete Mobilfunkverbindung" -#: selfdrive/ui/layouts/settings/device.py:68 +#: openpilot/selfdrive/ui/layouts/settings/device.py:66 #, python-format msgid "Change Language" msgstr "Sprache ändern" -#: selfdrive/ui/layouts/settings/toggles.py:125 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:125 #, python-format msgid "Changing this setting will restart openpilot if the car is powered on." -msgstr "" -" Durch Ändern dieser Einstellung wird openpilot neu gestartet, wenn das Auto " -"eingeschaltet ist." +msgstr " Durch Ändern dieser Einstellung wird openpilot neu gestartet, wenn das Auto eingeschaltet ist." -#: selfdrive/ui/widgets/pairing_dialog.py:129 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:118 #, python-format msgid "Click \"add new device\" and scan the QR code on the right" msgstr "Klicken Sie auf \"add new device\" und scannen Sie den QR‑Code rechts" -#: selfdrive/ui/widgets/offroad_alerts.py:104 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:104 #, python-format msgid "Close" msgstr "Schließen" -#: selfdrive/ui/layouts/settings/software.py:49 +#: openpilot/selfdrive/ui/layouts/settings/software.py:56 #, python-format msgid "Current Version" msgstr "Aktuelle Version" -#: selfdrive/ui/layouts/settings/software.py:110 +#: openpilot/selfdrive/ui/layouts/settings/software.py:120 #, python-format msgid "DOWNLOAD" msgstr "HERUNTERLADEN" -#: selfdrive/ui/layouts/onboarding.py:115 +#: openpilot/selfdrive/ui/layouts/onboarding.py:119 #, python-format msgid "Decline" msgstr "Ablehnen" -#: selfdrive/ui/layouts/onboarding.py:148 +#: openpilot/selfdrive/ui/layouts/onboarding.py:152 #, python-format msgid "Decline, uninstall openpilot" msgstr "Ablehnen, openpilot deinstallieren" -#: selfdrive/ui/layouts/settings/settings.py:67 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:64 msgid "Developer" msgstr "Entwickler" -#: selfdrive/ui/layouts/settings/settings.py:62 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:59 msgid "Device" msgstr "Gerät" -#: selfdrive/ui/layouts/settings/toggles.py:58 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:58 #, python-format msgid "Disengage on Accelerator Pedal" msgstr "Beim Gaspedal deaktivieren" -#: selfdrive/ui/layouts/settings/device.py:184 +#: openpilot/selfdrive/ui/layouts/settings/device.py:176 #, python-format msgid "Disengage to Power Off" msgstr "Zum Ausschalten deaktivieren" -#: selfdrive/ui/layouts/settings/device.py:172 +#: openpilot/selfdrive/ui/layouts/settings/device.py:164 #, python-format msgid "Disengage to Reboot" msgstr "Zum Neustart deaktivieren" -#: selfdrive/ui/layouts/settings/device.py:103 +#: openpilot/selfdrive/ui/layouts/settings/device.py:95 #, python-format msgid "Disengage to Reset Calibration" msgstr "Zum Zurücksetzen der Kalibrierung deaktivieren" -#: selfdrive/ui/layouts/settings/toggles.py:32 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:32 msgid "Display speed in km/h instead of mph." msgstr "Geschwindigkeit in km/h statt mph anzeigen." -#: selfdrive/ui/layouts/settings/device.py:59 +#: openpilot/selfdrive/ui/layouts/settings/device.py:57 #, python-format msgid "Dongle ID" msgstr "Dongle-ID" -#: selfdrive/ui/layouts/settings/software.py:50 +#: openpilot/selfdrive/ui/layouts/settings/software.py:57 #, python-format msgid "Download" msgstr "Herunterladen" -#: selfdrive/ui/layouts/settings/device.py:62 +#: openpilot/selfdrive/ui/layouts/settings/device.py:60 #, python-format msgid "Driver Camera" msgstr "Fahrerkamera" -#: selfdrive/ui/layouts/settings/toggles.py:96 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:96 #, python-format msgid "Driving Personality" msgstr "Fahrstil" -#: system/ui/widgets/network.py:123 system/ui/widgets/network.py:139 +#: system/ui/widgets/network.py:120 +#: system/ui/widgets/network.py:136 #, python-format msgid "EDIT" msgstr "BEARBEITEN" -#: selfdrive/ui/layouts/sidebar.py:138 +#: openpilot/selfdrive/ui/layouts/sidebar.py:138 msgid "ERROR" msgstr "FEHLER" -#: selfdrive/ui/layouts/sidebar.py:45 +#: openpilot/selfdrive/ui/layouts/sidebar.py:45 msgid "ETH" msgstr "ETH" -#: selfdrive/ui/widgets/exp_mode_button.py:50 +#: openpilot/selfdrive/ui/widgets/exp_mode_button.py:51 #, python-format msgid "EXPERIMENTAL MODE ON" msgstr "EXPERIMENTALMODUS AKTIV" -#: selfdrive/ui/layouts/settings/developer.py:166 -#: selfdrive/ui/layouts/settings/toggles.py:228 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:229 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:180 #, python-format msgid "Enable" msgstr "Aktivieren" -#: selfdrive/ui/layouts/settings/developer.py:39 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:39 #, python-format msgid "Enable ADB" msgstr "ADB aktivieren" -#: selfdrive/ui/layouts/settings/toggles.py:64 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:64 #, python-format msgid "Enable Lane Departure Warnings" msgstr "Spurverlassenswarnungen aktivieren" -#: system/ui/widgets/network.py:129 +#: system/ui/widgets/network.py:126 #, python-format msgid "Enable Roaming" msgstr "openpilot aktivieren" -#: selfdrive/ui/layouts/settings/developer.py:48 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:48 #, python-format msgid "Enable SSH" msgstr "SSH aktivieren" -#: system/ui/widgets/network.py:120 +#: system/ui/widgets/network.py:117 #, python-format msgid "Enable Tethering" msgstr "Spurverlassenswarnungen aktivieren" -#: selfdrive/ui/layouts/settings/toggles.py:30 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:30 msgid "Enable driver monitoring even when openpilot is not engaged." msgstr "Fahrerüberwachung auch aktivieren, wenn openpilot nicht aktiv ist." -#: selfdrive/ui/layouts/settings/toggles.py:46 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:46 #, python-format msgid "Enable openpilot" msgstr "openpilot aktivieren" -#: selfdrive/ui/layouts/settings/toggles.py:189 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:190 #, python-format -msgid "" -"Enable the openpilot longitudinal control (alpha) toggle to allow " -"Experimental mode." -msgstr "" -"Den Schalter für die openpilot-Längsregelung (Alpha) aktivieren, um den " -"Experimentalmodus zu erlauben." +msgid "Enable the openpilot longitudinal control (alpha) toggle to allow Experimental mode." +msgstr "Den Schalter für die openpilot-Längsregelung (Alpha) aktivieren, um den Experimentalmodus zu erlauben." -#: system/ui/widgets/network.py:204 +#: system/ui/widgets/network.py:201 #, python-format msgid "Enter APN" msgstr "APN eingeben" -#: system/ui/widgets/network.py:241 +#: system/ui/widgets/network.py:243 #, python-format msgid "Enter SSID" msgstr "SSID eingeben" -#: system/ui/widgets/network.py:254 +#: system/ui/widgets/network.py:257 #, python-format msgid "Enter new tethering password" msgstr "Neues Tethering‑Passwort eingeben" -#: system/ui/widgets/network.py:237 system/ui/widgets/network.py:314 +#: system/ui/widgets/network.py:238 +#: system/ui/widgets/network.py:320 #, python-format msgid "Enter password" msgstr "Passwort eingeben" -#: selfdrive/ui/widgets/ssh_key.py:89 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:89 #, python-format msgid "Enter your GitHub username" msgstr "Geben Sie Ihren GitHub‑Benutzernamen ein" -#: system/ui/widgets/list_view.py:123 system/ui/widgets/list_view.py:160 +#: system/ui/widgets/list_view.py:123 +#: system/ui/widgets/list_view.py:160 #, python-format msgid "Error" msgstr "Fehler" -#: selfdrive/ui/layouts/settings/toggles.py:52 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:52 #, python-format msgid "Experimental Mode" msgstr "Experimentalmodus" -#: selfdrive/ui/layouts/settings/toggles.py:181 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:182 #, python-format -msgid "" -"Experimental mode is currently unavailable on this car since the car's stock " -"ACC is used for longitudinal control." -msgstr "" -"Der Experimentalmodus ist derzeit auf diesem Fahrzeug nicht verfügbar, da " -"der serienmäßige ACC für die Längsregelung verwendet wird." +msgid "Experimental mode is currently unavailable on this car since the car's stock ACC is used for longitudinal control." +msgstr "Der Experimentalmodus ist derzeit auf diesem Fahrzeug nicht verfügbar, da der serienmäßige ACC für die Längsregelung verwendet wird." -#: system/ui/widgets/network.py:373 +#: system/ui/widgets/network.py:380 #, python-format msgid "FORGETTING..." msgstr "WIRD VERGESSEN..." -#: selfdrive/ui/widgets/setup.py:44 +#: openpilot/selfdrive/ui/widgets/setup.py:43 #, python-format msgid "Finish Setup" msgstr "Einrichtung abschließen" -#: selfdrive/ui/layouts/settings/settings.py:66 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:63 msgid "Firehose" msgstr "Firehose" -#: selfdrive/ui/layouts/settings/firehose.py:18 +#: openpilot/selfdrive/ui/layouts/settings/firehose.py:10 msgid "Firehose Mode" msgstr "Firehose‑Modus" -#: selfdrive/ui/layouts/settings/firehose.py:25 -msgid "" -"For maximum effectiveness, bring your device inside and connect to a good " -"USB-C adapter and Wi-Fi weekly.\n" -"\n" -"Firehose Mode can also work while you're driving if connected to a hotspot " -"or unlimited SIM card.\n" -"\n" -"\n" -"Frequently Asked Questions\n" -"\n" -"Does it matter how or where I drive? Nope, just drive as you normally " -"would.\n" -"\n" -"Do all of my segments get pulled in Firehose Mode? No, we selectively pull a " -"subset of your segments.\n" -"\n" -"What's a good USB-C adapter? Any fast phone or laptop charger should be " -"fine.\n" -"\n" -"Does it matter which software I run? Yes, only upstream openpilot (and " -"particular forks) are able to be used for training." -msgstr "" -"Für maximale Wirksamkeit bringen Sie Ihr Gerät regelmäßig ins Haus und " -"verbinden es wöchentlich mit einem guten USB‑C‑Adapter und WLAN.\n" -"\n" -"Der Firehose‑Modus kann auch während der Fahrt funktionieren, wenn eine " -"Verbindung zu einem Hotspot oder einer unbegrenzten SIM besteht.\n" -"\n" -"\n" -"Häufig gestellte Fragen\n" -"\n" -"Spielt es eine Rolle, wie oder wo ich fahre? Nein, fahren Sie einfach wie " -"gewöhnlich.\n" -"\n" -"Werden alle meine Segmente im Firehose‑Modus abgeholt? Nein, wir ziehen " -"selektiv eine Teilmenge Ihrer Segmente.\n" -"\n" -"Was ist ein guter USB‑C‑Adapter? Jeder schnelle Telefon‑ oder Laptoplader " -"sollte ausreichen.\n" -"\n" -"Spielt es eine Rolle, welche Software ich verwende? Ja, nur " -"Upstream‑openpilot (und bestimmte Forks) können für das Training verwendet " -"werden." - -#: system/ui/widgets/network.py:318 system/ui/widgets/network.py:451 +#: system/ui/widgets/network.py:458 +#: system/ui/widgets/network.py:326 #, python-format msgid "Forget" msgstr "Vergessen" -#: system/ui/widgets/network.py:319 +#: system/ui/widgets/network.py:327 #, python-format msgid "Forget Wi-Fi Network \"{}\"?" msgstr "WLAN‑Netz „{}“ vergessen?" -#: selfdrive/ui/layouts/sidebar.py:71 selfdrive/ui/layouts/sidebar.py:125 +#: openpilot/selfdrive/ui/layouts/sidebar.py:71 +#: openpilot/selfdrive/ui/layouts/sidebar.py:125 msgid "GOOD" msgstr "GUT" -#: selfdrive/ui/widgets/pairing_dialog.py:128 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:117 #, python-format msgid "Go to https://connect.comma.ai on your phone" msgstr "Gehen Sie auf Ihrem Telefon zu https://connect.comma.ai" -#: selfdrive/ui/layouts/sidebar.py:129 +#: openpilot/selfdrive/ui/layouts/sidebar.py:129 msgid "HIGH" msgstr "HOCH" -#: system/ui/widgets/network.py:155 +#: system/ui/widgets/network.py:152 #, python-format msgid "Hidden Network" msgstr "Netzwerk" -#: selfdrive/ui/layouts/settings/firehose.py:140 -#, python-format -msgid "INACTIVE: connect to an unmetered network" -msgstr "INAKTIV: Mit einem unlimitierten Netzwerk verbinden" - -#: selfdrive/ui/layouts/settings/software.py:53 -#: selfdrive/ui/layouts/settings/software.py:136 +#: openpilot/selfdrive/ui/layouts/settings/software.py:60 +#: openpilot/selfdrive/ui/layouts/settings/software.py:146 #, python-format msgid "INSTALL" msgstr "INSTALLIEREN" -#: system/ui/widgets/network.py:150 +#: system/ui/widgets/network.py:147 #, python-format msgid "IP Address" msgstr "IP‑Adresse" -#: selfdrive/ui/layouts/settings/software.py:53 +#: openpilot/selfdrive/ui/layouts/settings/software.py:60 #, python-format msgid "Install Update" msgstr "Update installieren" -#: selfdrive/ui/layouts/settings/developer.py:56 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:56 #, python-format msgid "Joystick Debug Mode" msgstr "Joystick‑Debugmodus" -#: selfdrive/ui/widgets/ssh_key.py:29 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:29 msgid "LOADING" msgstr "LADEN" -#: selfdrive/ui/layouts/sidebar.py:48 +#: openpilot/selfdrive/ui/layouts/sidebar.py:48 msgid "LTE" msgstr "LTE" -#: selfdrive/ui/layouts/settings/developer.py:64 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:64 #, python-format msgid "Longitudinal Maneuver Mode" msgstr "Längsmanövermodus" -#: selfdrive/ui/onroad/hud_renderer.py:148 +#: openpilot/selfdrive/ui/onroad/hud_renderer.py:148 #, python-format msgid "MAX" msgstr "MAX" -#: selfdrive/ui/widgets/setup.py:75 +#: openpilot/selfdrive/ui/widgets/setup.py:74 #, python-format -msgid "" -"Maximize your training data uploads to improve openpilot's driving models." -msgstr "" -"Maximieren Sie Ihre Trainingsdaten‑Uploads, um die Fahrmodelle von openpilot " -"zu verbessern." +msgid "Maximize your training data uploads to improve openpilot's driving models." +msgstr "Maximieren Sie Ihre Trainingsdaten‑Uploads, um die Fahrmodelle von openpilot zu verbessern." -#: selfdrive/ui/layouts/settings/device.py:59 -#: selfdrive/ui/layouts/settings/device.py:60 +#: openpilot/selfdrive/ui/layouts/settings/device.py:57 +#: openpilot/selfdrive/ui/layouts/settings/device.py:58 #, python-format msgid "N/A" msgstr "k. A." -#: selfdrive/ui/layouts/sidebar.py:142 +#: openpilot/selfdrive/ui/layouts/sidebar.py:142 msgid "NO" msgstr "KEIN" -#: selfdrive/ui/layouts/settings/settings.py:63 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:60 msgid "Network" msgstr "Netzwerk" -#: selfdrive/ui/widgets/ssh_key.py:114 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:115 #, python-format msgid "No SSH keys found" msgstr "Keine SSH‑Schlüssel gefunden" -#: selfdrive/ui/widgets/ssh_key.py:126 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:127 #, python-format msgid "No SSH keys found for user '{}'" msgstr "Keine SSH‑Schlüssel für Benutzer '{username}' gefunden" -#: selfdrive/ui/widgets/offroad_alerts.py:320 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:321 #, python-format msgid "No release notes available." msgstr "Keine Versionshinweise verfügbar." -#: selfdrive/ui/layouts/sidebar.py:73 selfdrive/ui/layouts/sidebar.py:134 +#: openpilot/selfdrive/ui/layouts/sidebar.py:73 +#: openpilot/selfdrive/ui/layouts/sidebar.py:134 msgid "OFFLINE" msgstr "OFFLINE" -#: system/ui/widgets/html_render.py:263 system/ui/widgets/confirm_dialog.py:93 -#: selfdrive/ui/layouts/sidebar.py:127 +#: system/ui/widgets/confirm_dialog.py:93 +#: system/ui/widgets/html_render.py:263 +#: openpilot/selfdrive/ui/layouts/sidebar.py:127 #, python-format msgid "OK" msgstr "OK" -#: selfdrive/ui/layouts/sidebar.py:72 selfdrive/ui/layouts/sidebar.py:136 -#: selfdrive/ui/layouts/sidebar.py:144 +#: openpilot/selfdrive/ui/layouts/sidebar.py:72 +#: openpilot/selfdrive/ui/layouts/sidebar.py:144 +#: openpilot/selfdrive/ui/layouts/sidebar.py:136 msgid "ONLINE" msgstr "ONLINE" -#: selfdrive/ui/widgets/setup.py:20 +#: openpilot/selfdrive/ui/widgets/setup.py:19 #, python-format msgid "Open" msgstr "Öffnen" -#: selfdrive/ui/layouts/settings/device.py:48 +#: openpilot/selfdrive/ui/layouts/settings/device.py:45 #, python-format msgid "PAIR" msgstr "KOPPELN" -#: selfdrive/ui/layouts/sidebar.py:142 +#: openpilot/selfdrive/ui/layouts/sidebar.py:142 msgid "PANDA" msgstr "PANDA" -#: selfdrive/ui/layouts/settings/device.py:62 +#: openpilot/selfdrive/ui/layouts/settings/device.py:60 #, python-format msgid "PREVIEW" msgstr "VORSCHAU" -#: selfdrive/ui/widgets/prime.py:44 +#: openpilot/selfdrive/ui/widgets/prime.py:44 #, python-format msgid "PRIME FEATURES:" msgstr "PRIME‑FUNKTIONEN:" -#: selfdrive/ui/layouts/settings/device.py:48 +#: openpilot/selfdrive/ui/layouts/settings/device.py:45 #, python-format msgid "Pair Device" msgstr "Gerät koppeln" -#: selfdrive/ui/widgets/setup.py:19 +#: openpilot/selfdrive/ui/widgets/setup.py:18 #, python-format msgid "Pair device" msgstr "Gerät koppeln" -#: selfdrive/ui/widgets/pairing_dialog.py:103 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:92 #, python-format msgid "Pair your device to your comma account" msgstr "Koppeln Sie Ihr Gerät mit Ihrem comma‑Konto" -#: selfdrive/ui/widgets/setup.py:48 selfdrive/ui/layouts/settings/device.py:24 +#: openpilot/selfdrive/ui/widgets/setup.py:47 +#: openpilot/selfdrive/ui/layouts/settings/device.py:23 #, python-format -msgid "" -"Pair your device with comma connect (connect.comma.ai) and claim your comma " -"prime offer." -msgstr "" -"Koppeln Sie Ihr Gerät mit comma connect (connect.comma.ai) und lösen Sie Ihr " -"comma‑prime‑Angebot ein." +msgid "Pair your device with comma connect (connect.comma.ai) and claim your comma prime offer." +msgstr "Koppeln Sie Ihr Gerät mit comma connect (connect.comma.ai) und lösen Sie Ihr comma‑prime‑Angebot ein." -#: selfdrive/ui/widgets/setup.py:91 +#: openpilot/selfdrive/ui/widgets/setup.py:91 #, python-format msgid "Please connect to Wi-Fi to complete initial pairing" msgstr "Bitte mit WLAN verbinden, um das erste Koppeln abzuschließen" -#: selfdrive/ui/layouts/settings/device.py:55 -#: selfdrive/ui/layouts/settings/device.py:187 +#: openpilot/selfdrive/ui/layouts/settings/device.py:183 +#: openpilot/selfdrive/ui/layouts/settings/device.py:53 #, python-format msgid "Power Off" msgstr "Ausschalten" -#: system/ui/widgets/network.py:144 +#: system/ui/widgets/network.py:141 #, python-format msgid "Prevent large data uploads when on a metered Wi-Fi connection" msgstr "" -#: system/ui/widgets/network.py:135 +#: system/ui/widgets/network.py:132 #, python-format msgid "Prevent large data uploads when on a metered cellular connection" msgstr "" -#: selfdrive/ui/layouts/settings/device.py:25 -msgid "" -"Preview the driver facing camera to ensure that driver monitoring has good " -"visibility. (vehicle must be off)" -msgstr "" -"Vorschau der Fahrer‑Kamera, um sicherzustellen, dass die Fahrerüberwachung " -"gute Sicht hat. (Fahrzeug muss ausgeschaltet sein)" +#: openpilot/selfdrive/ui/layouts/settings/device.py:24 +msgid "Preview the driver facing camera to ensure that driver monitoring has good visibility. (vehicle must be off)" +msgstr "Vorschau der Fahrer‑Kamera, um sicherzustellen, dass die Fahrerüberwachung gute Sicht hat. (Fahrzeug muss ausgeschaltet sein)" -#: selfdrive/ui/widgets/pairing_dialog.py:161 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:150 #, python-format msgid "QR Code Error" msgstr "QR‑Code‑Fehler" -#: selfdrive/ui/widgets/ssh_key.py:31 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:31 msgid "REMOVE" msgstr "ENTFERNEN" -#: selfdrive/ui/layouts/settings/device.py:51 +#: openpilot/selfdrive/ui/layouts/settings/device.py:49 #, python-format msgid "RESET" msgstr "ZURÜCKSETZEN" -#: selfdrive/ui/layouts/settings/device.py:65 +#: openpilot/selfdrive/ui/layouts/settings/device.py:63 #, python-format msgid "REVIEW" msgstr "ANSEHEN" -#: selfdrive/ui/layouts/settings/device.py:55 -#: selfdrive/ui/layouts/settings/device.py:175 +#: openpilot/selfdrive/ui/layouts/settings/device.py:171 +#: openpilot/selfdrive/ui/layouts/settings/device.py:53 #, python-format msgid "Reboot" msgstr "Neustart" -#: selfdrive/ui/onroad/alert_renderer.py:66 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:66 #, python-format msgid "Reboot Device" msgstr "Gerät neu starten" -#: selfdrive/ui/widgets/offroad_alerts.py:112 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:112 #, python-format msgid "Reboot and Update" msgstr "Neustarten und aktualisieren" -#: selfdrive/ui/layouts/settings/toggles.py:27 -msgid "" -"Receive alerts to steer back into the lane when your vehicle drifts over a " -"detected lane line without a turn signal activated while driving over 31 mph " -"(50 km/h)." -msgstr "" -"Erhalten Sie Warnungen, um zurück in die Spur zu lenken, wenn Ihr Fahrzeug " -"ohne Blinker über eine erkannte Spurlinie driftet und über 31 mph (50 km/h) " -"fährt." - -#: selfdrive/ui/layouts/settings/toggles.py:76 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:76 #, python-format msgid "Record and Upload Driver Camera" msgstr "Fahrerkamera aufzeichnen und hochladen" -#: selfdrive/ui/layouts/settings/toggles.py:82 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:82 #, python-format msgid "Record and Upload Microphone Audio" msgstr "Mikrofonton aufzeichnen und hochladen" -#: selfdrive/ui/layouts/settings/toggles.py:33 -msgid "" -"Record and store microphone audio while driving. The audio will be included " -"in the dashcam video in comma connect." -msgstr "" -"Mikrofonton während der Fahrt aufzeichnen und speichern. Die Audiospur wird " -"im Dashcam‑Video in comma connect enthalten sein." +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:33 +msgid "Record and store microphone audio while driving. The audio will be included in the dashcam video in comma connect." +msgstr "Mikrofonton während der Fahrt aufzeichnen und speichern. Die Audiospur wird im Dashcam‑Video in comma connect enthalten sein." -#: selfdrive/ui/layouts/settings/device.py:67 +#: openpilot/selfdrive/ui/layouts/settings/device.py:65 #, python-format msgid "Regulatory" msgstr "Vorschriften" -#: selfdrive/ui/layouts/settings/toggles.py:98 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:98 #, python-format msgid "Relaxed" msgstr "Entspannt" -#: selfdrive/ui/widgets/prime.py:47 +#: openpilot/selfdrive/ui/widgets/prime.py:47 #, python-format msgid "Remote access" msgstr "Fernzugriff" -#: selfdrive/ui/widgets/prime.py:47 +#: openpilot/selfdrive/ui/widgets/prime.py:47 #, python-format msgid "Remote snapshots" msgstr "Remote‑Schnappschüsse" -#: selfdrive/ui/widgets/ssh_key.py:123 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:124 #, python-format msgid "Request timed out" msgstr "Zeitüberschreitung bei der Anfrage" -#: selfdrive/ui/layouts/settings/device.py:119 +#: openpilot/selfdrive/ui/layouts/settings/device.py:111 #, python-format msgid "Reset" msgstr "Zurücksetzen" -#: selfdrive/ui/layouts/settings/device.py:51 +#: openpilot/selfdrive/ui/layouts/settings/device.py:49 #, python-format msgid "Reset Calibration" msgstr "Kalibrierung zurücksetzen" -#: selfdrive/ui/layouts/settings/device.py:65 +#: openpilot/selfdrive/ui/layouts/settings/device.py:63 #, python-format msgid "Review Training Guide" msgstr "Trainingsanleitung ansehen" -#: selfdrive/ui/layouts/settings/device.py:27 +#: openpilot/selfdrive/ui/layouts/settings/device.py:26 msgid "Review the rules, features, and limitations of openpilot" -msgstr "" -"Überprüfen Sie die Regeln, Funktionen und Einschränkungen von openpilot" +msgstr "Überprüfen Sie die Regeln, Funktionen und Einschränkungen von openpilot" -#: selfdrive/ui/layouts/settings/software.py:61 +#: openpilot/selfdrive/ui/layouts/settings/software.py:68 #, python-format msgid "SELECT" msgstr "" -#: selfdrive/ui/layouts/settings/developer.py:53 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:53 #, python-format msgid "SSH Keys" msgstr "SSH‑Schlüssel" -#: system/ui/widgets/network.py:310 +#: system/ui/widgets/network.py:316 #, python-format msgid "Scanning Wi-Fi networks..." msgstr "WLAN‑Netzwerke werden gesucht..." -#: system/ui/widgets/option_dialog.py:36 +#: system/ui/widgets/option_dialog.py:37 #, python-format msgid "Select" msgstr "Auswählen" -#: selfdrive/ui/layouts/settings/software.py:183 +#: openpilot/selfdrive/ui/layouts/settings/software.py:203 #, python-format msgid "Select a branch" msgstr "" -#: selfdrive/ui/layouts/settings/device.py:91 +#: openpilot/selfdrive/ui/layouts/settings/device.py:89 #, python-format msgid "Select a language" msgstr "Sprache auswählen" -#: selfdrive/ui/layouts/settings/device.py:60 +#: openpilot/selfdrive/ui/layouts/settings/device.py:58 #, python-format msgid "Serial" msgstr "Seriennummer" -#: selfdrive/ui/widgets/offroad_alerts.py:106 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:106 #, python-format msgid "Snooze Update" msgstr "Update verschieben" -#: selfdrive/ui/layouts/settings/settings.py:65 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:62 msgid "Software" msgstr "Software" -#: selfdrive/ui/layouts/settings/toggles.py:98 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:98 #, python-format msgid "Standard" msgstr "Standard" -#: selfdrive/ui/layouts/settings/toggles.py:22 -msgid "" -"Standard is recommended. In aggressive mode, openpilot will follow lead cars " -"closer and be more aggressive with the gas and brake. In relaxed mode " -"openpilot will stay further away from lead cars. On supported cars, you can " -"cycle through these personalities with your steering wheel distance button." -msgstr "" -"Standard wird empfohlen. Im aggressiven Modus folgt openpilot " -"vorausfahrenden Fahrzeugen näher und ist beim Gasgeben und Bremsen " -"aggressiver. Im entspannten Modus bleibt openpilot weiter entfernt. Bei " -"unterstützten Fahrzeugen können Sie mit der Abstandstaste am Lenkrad " -"zwischen diesen Profilen wechseln." - -#: selfdrive/ui/onroad/alert_renderer.py:59 -#: selfdrive/ui/onroad/alert_renderer.py:65 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:59 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:65 #, python-format msgid "System Unresponsive" msgstr "System reagiert nicht" -#: selfdrive/ui/onroad/alert_renderer.py:58 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:58 #, python-format msgid "TAKE CONTROL IMMEDIATELY" msgstr "SOFORT DIE KONTROLLE ÜBERNEHMEN" -#: selfdrive/ui/layouts/sidebar.py:71 selfdrive/ui/layouts/sidebar.py:125 -#: selfdrive/ui/layouts/sidebar.py:127 selfdrive/ui/layouts/sidebar.py:129 +#: openpilot/selfdrive/ui/layouts/sidebar.py:71 +#: openpilot/selfdrive/ui/layouts/sidebar.py:125 +#: openpilot/selfdrive/ui/layouts/sidebar.py:127 +#: openpilot/selfdrive/ui/layouts/sidebar.py:129 msgid "TEMP" msgstr "TEMP" -#: selfdrive/ui/layouts/settings/software.py:61 +#: openpilot/selfdrive/ui/layouts/settings/software.py:68 #, python-format msgid "Target Branch" msgstr "" -#: system/ui/widgets/network.py:124 +#: system/ui/widgets/network.py:121 #, python-format msgid "Tethering Password" msgstr "Tethering‑Passwort" -#: selfdrive/ui/layouts/settings/settings.py:64 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:61 msgid "Toggles" msgstr "Schalter" -#: selfdrive/ui/layouts/settings/software.py:72 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:79 +#, python-format +msgid "UI Debug Mode" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/software.py:79 #, python-format msgid "UNINSTALL" msgstr "DEINSTALLIEREN" -#: selfdrive/ui/layouts/home.py:155 +#: openpilot/selfdrive/ui/layouts/home.py:155 #, python-format msgid "UPDATE" msgstr "UPDATE" -#: selfdrive/ui/layouts/settings/software.py:72 -#: selfdrive/ui/layouts/settings/software.py:163 +#: openpilot/selfdrive/ui/layouts/settings/software.py:173 +#: openpilot/selfdrive/ui/layouts/settings/software.py:79 #, python-format msgid "Uninstall" msgstr "Deinstallieren" -#: selfdrive/ui/layouts/sidebar.py:117 +#: openpilot/selfdrive/ui/layouts/sidebar.py:117 msgid "Unknown" msgstr "Unbekannt" -#: selfdrive/ui/layouts/settings/software.py:48 +#: openpilot/selfdrive/ui/layouts/settings/software.py:55 #, python-format msgid "Updates are only downloaded while the car is off." msgstr "Updates werden nur heruntergeladen, wenn das Auto aus ist." -#: selfdrive/ui/widgets/prime.py:33 +#: openpilot/selfdrive/ui/widgets/prime.py:33 #, python-format msgid "Upgrade Now" msgstr "Jetzt abonnieren" -#: selfdrive/ui/layouts/settings/toggles.py:31 -msgid "" -"Upload data from the driver facing camera and help improve the driver " -"monitoring algorithm." -msgstr "" -"Daten von der Fahrer‑Kamera hochladen und den Fahrerüberwachungs‑Algorithmus " -"verbessern." +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:31 +msgid "Upload data from the driver facing camera and help improve the driver monitoring algorithm." +msgstr "Daten von der Fahrer‑Kamera hochladen und den Fahrerüberwachungs‑Algorithmus verbessern." -#: selfdrive/ui/layouts/settings/toggles.py:88 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:88 #, python-format msgid "Use Metric System" msgstr "Metersystem verwenden" -#: selfdrive/ui/layouts/settings/toggles.py:17 -msgid "" -"Use the openpilot system for adaptive cruise control and lane keep driver " -"assistance. Your attention is required at all times to use this feature." -msgstr "" -"Verwenden Sie openpilot für adaptive Geschwindigkeitsregelung und " -"Spurhalteassistenz. Ihre Aufmerksamkeit ist jederzeit erforderlich, um diese " -"Funktion zu nutzen." - -#: selfdrive/ui/layouts/sidebar.py:72 selfdrive/ui/layouts/sidebar.py:144 +#: openpilot/selfdrive/ui/layouts/sidebar.py:72 +#: openpilot/selfdrive/ui/layouts/sidebar.py:144 msgid "VEHICLE" msgstr "FAHRZEUG" -#: selfdrive/ui/layouts/settings/device.py:67 +#: openpilot/selfdrive/ui/layouts/settings/device.py:65 #, python-format msgid "VIEW" msgstr "ANSEHEN" -#: selfdrive/ui/onroad/alert_renderer.py:52 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:52 #, python-format msgid "Waiting to start" msgstr "Warten auf Start" -#: selfdrive/ui/layouts/settings/developer.py:19 -msgid "" -"Warning: This grants SSH access to all public keys in your GitHub settings. " -"Never enter a GitHub username other than your own. A comma employee will " -"NEVER ask you to add their GitHub username." -msgstr "" -"Warnung: Dies gewährt SSH‑Zugriff auf alle öffentlichen Schlüssel in Ihren " -"GitHub‑Einstellungen. Geben Sie niemals einen anderen GitHub‑Benutzernamen " -"als Ihren eigenen ein. Ein comma‑Mitarbeiter wird Sie NIEMALS bitten, seinen " -"GitHub‑Benutzernamen hinzuzufügen." - -#: selfdrive/ui/layouts/onboarding.py:111 +#: openpilot/selfdrive/ui/layouts/onboarding.py:115 #, python-format msgid "Welcome to openpilot" msgstr "Willkommen bei openpilot" -#: selfdrive/ui/layouts/settings/toggles.py:20 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:20 msgid "When enabled, pressing the accelerator pedal will disengage openpilot." msgstr "Wenn aktiviert, deaktiviert das Drücken des Gaspedals openpilot." -#: selfdrive/ui/layouts/sidebar.py:44 +#: openpilot/selfdrive/ui/layouts/sidebar.py:44 msgid "Wi-Fi" msgstr "WLAN" -#: system/ui/widgets/network.py:144 +#: system/ui/widgets/network.py:141 #, python-format msgid "Wi-Fi Network Metered" msgstr "Getaktetes WLAN‑Netzwerk" -#: system/ui/widgets/network.py:314 +#: system/ui/widgets/network.py:320 #, python-format msgid "Wrong password" msgstr "Falsches Passwort" -#: selfdrive/ui/layouts/onboarding.py:145 +#: openpilot/selfdrive/ui/layouts/onboarding.py:149 #, python-format msgid "You must accept the Terms and Conditions in order to use openpilot." -msgstr "" -"Sie müssen die Nutzungsbedingungen akzeptieren, um openpilot zu verwenden." +msgstr "Sie müssen die Nutzungsbedingungen akzeptieren, um openpilot zu verwenden." -#: selfdrive/ui/layouts/onboarding.py:112 +#: openpilot/selfdrive/ui/layouts/onboarding.py:116 #, python-format -msgid "" -"You must accept the Terms and Conditions to use openpilot. Read the latest " -"terms at https://comma.ai/terms before continuing." -msgstr "" -"Sie müssen die Nutzungsbedingungen akzeptieren, um openpilot zu verwenden. " -"Lesen Sie die aktuellen Bedingungen unter https://comma.ai/terms, bevor Sie " -"fortfahren." +msgid "You must accept the Terms and Conditions to use openpilot. Read the latest terms at https://comma.ai/terms before continuing." +msgstr "Sie müssen die Nutzungsbedingungen akzeptieren, um openpilot zu verwenden. Lesen Sie die aktuellen Bedingungen unter https://comma.ai/terms, bevor Sie fortfahren." -#: selfdrive/ui/onroad/driver_camera_dialog.py:34 +#: openpilot/selfdrive/ui/onroad/driver_camera_dialog.py:38 #, python-format msgid "camera starting" msgstr "Kamera startet" -#: selfdrive/ui/widgets/prime.py:63 +#: openpilot/selfdrive/ui/layouts/settings/software.py:19 +#, python-format +msgid "checking..." +msgstr "" + +#: openpilot/selfdrive/ui/widgets/prime.py:63 #, python-format msgid "comma prime" msgstr "comma prime" -#: system/ui/widgets/network.py:142 +#: system/ui/widgets/network.py:139 #, python-format msgid "default" msgstr "Standard" -#: selfdrive/ui/layouts/settings/device.py:133 +#: openpilot/selfdrive/ui/layouts/settings/device.py:125 #, python-format msgid "down" msgstr "unten" -#: selfdrive/ui/layouts/settings/software.py:106 +#: openpilot/selfdrive/ui/layouts/settings/software.py:20 +#, python-format +msgid "downloading..." +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/software.py:116 #, python-format msgid "failed to check for update" msgstr "Überprüfung auf Updates fehlgeschlagen" -#: system/ui/widgets/network.py:237 system/ui/widgets/network.py:314 +#: openpilot/selfdrive/ui/layouts/settings/software.py:21 +#, python-format +msgid "finalizing update..." +msgstr "" + +#: system/ui/widgets/network.py:238 +#: system/ui/widgets/network.py:321 #, python-format msgid "for \"{}\"" msgstr "für „{}“" -#: selfdrive/ui/onroad/hud_renderer.py:177 +#: openpilot/selfdrive/ui/onroad/hud_renderer.py:177 #, python-format msgid "km/h" msgstr "km/h" -#: system/ui/widgets/network.py:204 +#: system/ui/widgets/network.py:201 #, python-format msgid "leave blank for automatic configuration" msgstr "für automatische Konfiguration leer lassen" -#: selfdrive/ui/layouts/settings/device.py:134 +#: openpilot/selfdrive/ui/layouts/settings/device.py:126 #, python-format msgid "left" msgstr "links" -#: system/ui/widgets/network.py:142 +#: system/ui/widgets/network.py:139 #, python-format msgid "metered" msgstr "getaktet" -#: selfdrive/ui/onroad/hud_renderer.py:177 +#: openpilot/selfdrive/ui/onroad/hud_renderer.py:177 #, python-format msgid "mph" msgstr "mph" -#: selfdrive/ui/layouts/settings/software.py:20 +#: openpilot/selfdrive/ui/layouts/settings/software.py:27 #, python-format msgid "never" msgstr "nie" -#: selfdrive/ui/layouts/settings/software.py:31 +#: openpilot/selfdrive/ui/layouts/settings/software.py:38 #, python-format msgid "now" msgstr "jetzt" -#: selfdrive/ui/layouts/settings/developer.py:71 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:71 #, python-format msgid "openpilot Longitudinal Control (Alpha)" msgstr "openpilot Längsregelung (Alpha)" -#: selfdrive/ui/onroad/alert_renderer.py:51 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:51 #, python-format msgid "openpilot Unavailable" msgstr "openpilot nicht verfügbar" -#: selfdrive/ui/layouts/settings/toggles.py:158 -#, python-format -msgid "" -"openpilot defaults to driving in chill mode. Experimental mode enables alpha-" -"level features that aren't ready for chill mode. Experimental features are " -"listed below:

End-to-End Longitudinal Control


Let the driving " -"model control the gas and brakes. openpilot will drive as it thinks a human " -"would, including stopping for red lights and stop signs. Since the driving " -"model decides the speed to drive, the set speed will only act as an upper " -"bound. This is an alpha quality feature; mistakes should be expected." -"

New Driving Visualization


The driving visualization will " -"transition to the road-facing wide-angle camera at low speeds to better show " -"some turns. The Experimental mode logo will also be shown in the top right " -"corner." -msgstr "" -"openpilot fährt standardmäßig im Chill‑Modus. Der Experimentalmodus " -"aktiviert Funktionen im Alpha‑Status, die für den Chill‑Modus noch nicht " -"bereit sind. Die experimentellen Funktionen sind unten aufgeführt:" -"

End-to‑End‑Längsregelung


Das Fahrmodell steuert Gas und " -"Bremse. openpilot fährt so, wie es einen Menschen einschätzt, einschließlich " -"Anhalten an roten Ampeln und Stoppschildern. Da das Modell die " -"Geschwindigkeit bestimmt, dient die eingestellte Geschwindigkeit nur als " -"Obergrenze. Dies ist eine Alpha‑Funktion; Fehler sind zu erwarten." -"

Neue Fahrvisualisierung


Die Visualisierung wechselt bei " -"niedriger Geschwindigkeit auf die nach vorn gerichtete Weitwinkelkamera, um " -"manche Kurven besser zu zeigen. Das Experimentalmodus‑Logo wird außerdem " -"oben rechts angezeigt." - -#: selfdrive/ui/layouts/settings/device.py:165 -#, python-format -msgid "" -"openpilot is continuously calibrating, resetting is rarely required. " -"Resetting calibration will restart openpilot if the car is powered on." -msgstr "" -" Durch Ändern dieser Einstellung wird openpilot neu gestartet, wenn das Auto " -"eingeschaltet ist." - -#: selfdrive/ui/layouts/settings/firehose.py:20 -msgid "" -"openpilot learns to drive by watching humans, like you, drive.\n" -"\n" -"Firehose Mode allows you to maximize your training data uploads to improve " -"openpilot's driving models. More data means bigger models, which means " -"better Experimental Mode." -msgstr "" -"openpilot lernt das Fahren, indem es Menschen wie Sie beobachtet.\n" -"\n" -"Der Firehose‑Modus ermöglicht es Ihnen, Ihre Trainingsdaten‑Uploads zu " -"maximieren, um die Fahrmodelle von openpilot zu verbessern. Mehr Daten " -"bedeuten größere Modelle – und damit einen besseren Experimentalmodus." - -#: selfdrive/ui/layouts/settings/toggles.py:183 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:184 #, python-format msgid "openpilot longitudinal control may come in a future update." msgstr "Die openpilot‑Längsregelung könnte in einem zukünftigen Update kommen." -#: selfdrive/ui/layouts/settings/device.py:26 -msgid "" -"openpilot requires the device to be mounted within 4° left or right and " -"within 5° up or 9° down." -msgstr "" -"openpilot erfordert, dass das Gerät innerhalb von 4° nach links oder rechts " -"und innerhalb von 5° nach oben oder 9° nach unten montiert ist." +#: openpilot/selfdrive/ui/layouts/settings/device.py:25 +msgid "openpilot requires the device to be mounted within 4° left or right and within 5° up or 9° down." +msgstr "openpilot erfordert, dass das Gerät innerhalb von 4° nach links oder rechts und innerhalb von 5° nach oben oder 9° nach unten montiert ist." -#: selfdrive/ui/layouts/settings/device.py:134 +#: openpilot/selfdrive/ui/layouts/settings/device.py:126 #, python-format msgid "right" msgstr "rechts" -#: system/ui/widgets/network.py:142 +#: system/ui/widgets/network.py:139 #, python-format msgid "unmetered" msgstr "unbegrenzt" -#: selfdrive/ui/layouts/settings/device.py:133 +#: openpilot/selfdrive/ui/layouts/settings/device.py:125 #, python-format msgid "up" msgstr "oben" -#: selfdrive/ui/layouts/settings/software.py:117 +#: openpilot/selfdrive/ui/layouts/settings/software.py:127 #, python-format msgid "up to date, last checked never" msgstr "Aktuell, zuletzt geprüft: nie" -#: selfdrive/ui/layouts/settings/software.py:115 +#: openpilot/selfdrive/ui/layouts/settings/software.py:125 #, python-format msgid "up to date, last checked {}" msgstr "Aktuell, zuletzt geprüft: {}" -#: selfdrive/ui/layouts/settings/software.py:109 +#: openpilot/selfdrive/ui/layouts/settings/software.py:119 #, python-format msgid "update available" msgstr "Update verfügbar" -#: selfdrive/ui/layouts/home.py:169 +#: openpilot/selfdrive/ui/layouts/home.py:169 #, python-format msgid "{} ALERT" msgid_plural "{} ALERTS" msgstr[0] "{} WARNUNG" msgstr[1] "{} WARNUNGEN" -#: selfdrive/ui/layouts/settings/software.py:40 +#: openpilot/selfdrive/ui/layouts/settings/software.py:47 #, python-format msgid "{} day ago" msgid_plural "{} days ago" msgstr[0] "vor {} Tag" msgstr[1] "vor {} Tagen" -#: selfdrive/ui/layouts/settings/software.py:37 +#: openpilot/selfdrive/ui/layouts/settings/software.py:44 #, python-format msgid "{} hour ago" msgid_plural "{} hours ago" msgstr[0] "vor {} Stunde" msgstr[1] "vor {} Stunden" -#: selfdrive/ui/layouts/settings/software.py:34 +#: openpilot/selfdrive/ui/layouts/settings/software.py:41 #, python-format msgid "{} minute ago" msgid_plural "{} minutes ago" msgstr[0] "vor {} Minute" msgstr[1] "vor {} Minuten" -#: selfdrive/ui/layouts/settings/firehose.py:111 +#: openpilot/selfdrive/ui/layouts/settings/firehose.py:70 #, python-format msgid "{} segment of your driving is in the training dataset so far." msgid_plural "{} segments of your driving is in the training dataset so far." msgstr[0] "{} Segment Ihrer Fahrten ist bisher im Trainingsdatensatz." msgstr[1] "{} Segmente Ihrer Fahrten sind bisher im Trainingsdatensatz." -#: selfdrive/ui/widgets/prime.py:62 +#: openpilot/selfdrive/ui/widgets/prime.py:62 #, python-format msgid "✓ SUBSCRIBED" msgstr "✓ ABONNIERT" -#: selfdrive/ui/widgets/setup.py:22 +#: openpilot/selfdrive/ui/widgets/setup.py:21 #, python-format msgid "🔥 Firehose Mode 🔥" msgstr "🔥 Firehose‑Modus 🔥" + diff --git a/selfdrive/ui/translations/app_en.po b/selfdrive/ui/translations/app_en.po index 6fbb537aff..3744096226 100644 --- a/selfdrive/ui/translations/app_en.po +++ b/selfdrive/ui/translations/app_en.po @@ -17,1191 +17,1018 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -#: selfdrive/ui/layouts/settings/device.py:160 +#: openpilot/selfdrive/ui/layouts/settings/device.py:152 #, python-format msgid " Steering torque response calibration is complete." msgstr " Steering torque response calibration is complete." -#: selfdrive/ui/layouts/settings/device.py:158 +#: openpilot/selfdrive/ui/layouts/settings/device.py:150 #, python-format msgid " Steering torque response calibration is {}% complete." msgstr " Steering torque response calibration is {}% complete." -#: selfdrive/ui/layouts/settings/device.py:133 +#: openpilot/selfdrive/ui/layouts/settings/device.py:125 #, python-format msgid " Your device is pointed {:.1f}° {} and {:.1f}° {}." msgstr " Your device is pointed {:.1f}° {} and {:.1f}° {}." -#: selfdrive/ui/layouts/sidebar.py:43 +#: openpilot/selfdrive/ui/layouts/sidebar.py:43 msgid "--" msgstr "--" -#: selfdrive/ui/widgets/prime.py:47 +#: openpilot/selfdrive/ui/widgets/prime.py:47 #, python-format msgid "1 year of drive storage" msgstr "1 year of drive storage" -#: selfdrive/ui/widgets/prime.py:47 +#: openpilot/selfdrive/ui/widgets/prime.py:47 #, python-format msgid "24/7 LTE connectivity" msgstr "24/7 LTE connectivity" -#: selfdrive/ui/layouts/sidebar.py:46 +#: openpilot/selfdrive/ui/layouts/sidebar.py:46 msgid "2G" msgstr "2G" -#: selfdrive/ui/layouts/sidebar.py:47 +#: openpilot/selfdrive/ui/layouts/sidebar.py:47 msgid "3G" msgstr "3G" -#: selfdrive/ui/layouts/sidebar.py:49 +#: openpilot/selfdrive/ui/layouts/sidebar.py:49 msgid "5G" msgstr "5G" -#: selfdrive/ui/layouts/settings/developer.py:23 -msgid "" -"WARNING: openpilot longitudinal control is in alpha for this car and will " -"disable Automatic Emergency Braking (AEB).

On this car, openpilot " -"defaults to the car's built-in ACC instead of openpilot's longitudinal " -"control. Enable this to switch to openpilot longitudinal control. Enabling " -"Experimental mode is recommended when enabling openpilot longitudinal " -"control alpha. Changing this setting will restart openpilot if the car is " -"powered on." -msgstr "" -"WARNING: openpilot longitudinal control is in alpha for this car and will " -"disable Automatic Emergency Braking (AEB).

On this car, openpilot " -"defaults to the car's built-in ACC instead of openpilot's longitudinal " -"control. Enable this to switch to openpilot longitudinal control. Enabling " -"Experimental mode is recommended when enabling openpilot longitudinal " -"control alpha. Changing this setting will restart openpilot if the car is " -"powered on." - -#: selfdrive/ui/layouts/settings/device.py:148 +#: openpilot/selfdrive/ui/layouts/settings/device.py:140 #, python-format msgid "

Steering lag calibration is complete." msgstr "

Steering lag calibration is complete." -#: selfdrive/ui/layouts/settings/device.py:146 +#: openpilot/selfdrive/ui/layouts/settings/device.py:138 #, python-format msgid "

Steering lag calibration is {}% complete." msgstr "

Steering lag calibration is {}% complete." -#: selfdrive/ui/layouts/settings/firehose.py:138 -#, python-format -msgid "ACTIVE" -msgstr "ACTIVE" - -#: selfdrive/ui/layouts/settings/developer.py:15 -msgid "" -"ADB (Android Debug Bridge) allows connecting to your device over USB or over " -"the network. See https://docs.comma.ai/how-to/connect-to-comma for more info." -msgstr "" -"ADB (Android Debug Bridge) allows connecting to your device over USB or over " -"the network. See https://docs.comma.ai/how-to/connect-to-comma for more info." - -#: selfdrive/ui/widgets/ssh_key.py:30 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:30 msgid "ADD" msgstr "ADD" -#: system/ui/widgets/network.py:139 +#: system/ui/widgets/network.py:136 #, python-format msgid "APN Setting" msgstr "APN Setting" -#: selfdrive/ui/widgets/offroad_alerts.py:109 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:109 #, python-format msgid "Acknowledge Excessive Actuation" msgstr "Acknowledge Excessive Actuation" -#: system/ui/widgets/network.py:74 system/ui/widgets/network.py:95 +#: system/ui/widgets/network.py:92 +#: system/ui/widgets/network.py:74 #, python-format msgid "Advanced" msgstr "Advanced" -#: selfdrive/ui/layouts/settings/toggles.py:98 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:98 #, python-format msgid "Aggressive" msgstr "Aggressive" -#: selfdrive/ui/layouts/onboarding.py:116 +#: openpilot/selfdrive/ui/layouts/onboarding.py:120 #, python-format msgid "Agree" msgstr "Agree" -#: selfdrive/ui/layouts/settings/toggles.py:70 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:70 #, python-format msgid "Always-On Driver Monitoring" msgstr "Always-On Driver Monitoring" -#: selfdrive/ui/layouts/settings/toggles.py:186 -#, python-format -msgid "" -"An alpha version of openpilot longitudinal control can be tested, along with " -"Experimental mode, on non-release branches." -msgstr "" -"An alpha version of openpilot longitudinal control can be tested, along with " -"Experimental mode, on non-release branches." - -#: selfdrive/ui/layouts/settings/device.py:187 +#: openpilot/selfdrive/ui/layouts/settings/device.py:183 #, python-format msgid "Are you sure you want to power off?" msgstr "Are you sure you want to power off?" -#: selfdrive/ui/layouts/settings/device.py:175 +#: openpilot/selfdrive/ui/layouts/settings/device.py:171 #, python-format msgid "Are you sure you want to reboot?" msgstr "Are you sure you want to reboot?" -#: selfdrive/ui/layouts/settings/device.py:119 +#: openpilot/selfdrive/ui/layouts/settings/device.py:111 #, python-format msgid "Are you sure you want to reset calibration?" msgstr "Are you sure you want to reset calibration?" -#: selfdrive/ui/layouts/settings/software.py:163 +#: openpilot/selfdrive/ui/layouts/settings/software.py:173 #, python-format msgid "Are you sure you want to uninstall?" msgstr "Are you sure you want to uninstall?" -#: system/ui/widgets/network.py:99 selfdrive/ui/layouts/onboarding.py:147 +#: system/ui/widgets/network.py:96 +#: openpilot/selfdrive/ui/layouts/onboarding.py:151 #, python-format msgid "Back" msgstr "Back" -#: selfdrive/ui/widgets/prime.py:38 +#: openpilot/selfdrive/ui/widgets/prime.py:38 #, python-format msgid "Become a comma prime member at connect.comma.ai" msgstr "Become a comma prime member at connect.comma.ai" -#: selfdrive/ui/widgets/pairing_dialog.py:130 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:119 #, python-format msgid "Bookmark connect.comma.ai to your home screen to use it like an app" msgstr "Bookmark connect.comma.ai to your home screen to use it like an app" -#: selfdrive/ui/layouts/settings/device.py:68 +#: openpilot/selfdrive/ui/layouts/settings/device.py:66 #, python-format msgid "CHANGE" msgstr "CHANGE" -#: selfdrive/ui/layouts/settings/software.py:50 -#: selfdrive/ui/layouts/settings/software.py:107 -#: selfdrive/ui/layouts/settings/software.py:118 -#: selfdrive/ui/layouts/settings/software.py:147 +#: openpilot/selfdrive/ui/layouts/settings/software.py:157 +#: openpilot/selfdrive/ui/layouts/settings/software.py:57 +#: openpilot/selfdrive/ui/layouts/settings/software.py:117 +#: openpilot/selfdrive/ui/layouts/settings/software.py:128 #, python-format msgid "CHECK" msgstr "CHECK" -#: selfdrive/ui/widgets/exp_mode_button.py:50 +#: openpilot/selfdrive/ui/widgets/exp_mode_button.py:51 #, python-format msgid "CHILL MODE ON" msgstr "CHILL MODE ON" -#: system/ui/widgets/network.py:155 selfdrive/ui/layouts/sidebar.py:73 -#: selfdrive/ui/layouts/sidebar.py:134 selfdrive/ui/layouts/sidebar.py:136 -#: selfdrive/ui/layouts/sidebar.py:138 +#: system/ui/widgets/network.py:152 +#: openpilot/selfdrive/ui/layouts/sidebar.py:73 +#: openpilot/selfdrive/ui/layouts/sidebar.py:134 +#: openpilot/selfdrive/ui/layouts/sidebar.py:136 +#: openpilot/selfdrive/ui/layouts/sidebar.py:138 #, python-format msgid "CONNECT" msgstr "CONNECT" -#: system/ui/widgets/network.py:369 +#: system/ui/widgets/network.py:376 #, python-format msgid "CONNECTING..." msgstr "CONNECTING..." -#: system/ui/widgets/confirm_dialog.py:23 system/ui/widgets/option_dialog.py:35 -#: system/ui/widgets/keyboard.py:81 system/ui/widgets/network.py:318 +#: system/ui/widgets/network.py:326 +#: system/ui/widgets/confirm_dialog.py:24 +#: system/ui/widgets/option_dialog.py:36 +#: system/ui/widgets/keyboard.py:83 #, python-format msgid "Cancel" msgstr "Cancel" -#: system/ui/widgets/network.py:134 +#: system/ui/widgets/network.py:131 #, python-format msgid "Cellular Metered" msgstr "Cellular Metered" -#: selfdrive/ui/layouts/settings/device.py:68 +#: openpilot/selfdrive/ui/layouts/settings/device.py:66 #, python-format msgid "Change Language" msgstr "Change Language" -#: selfdrive/ui/layouts/settings/toggles.py:125 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:125 #, python-format msgid "Changing this setting will restart openpilot if the car is powered on." msgstr "Changing this setting will restart openpilot if the car is powered on." -#: selfdrive/ui/widgets/pairing_dialog.py:129 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:118 #, python-format msgid "Click \"add new device\" and scan the QR code on the right" msgstr "Click \"add new device\" and scan the QR code on the right" -#: selfdrive/ui/widgets/offroad_alerts.py:104 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:104 #, python-format msgid "Close" msgstr "Close" -#: selfdrive/ui/layouts/settings/software.py:49 +#: openpilot/selfdrive/ui/layouts/settings/software.py:56 #, python-format msgid "Current Version" msgstr "Current Version" -#: selfdrive/ui/layouts/settings/software.py:110 +#: openpilot/selfdrive/ui/layouts/settings/software.py:120 #, python-format msgid "DOWNLOAD" msgstr "DOWNLOAD" -#: selfdrive/ui/layouts/onboarding.py:115 +#: openpilot/selfdrive/ui/layouts/onboarding.py:119 #, python-format msgid "Decline" msgstr "Decline" -#: selfdrive/ui/layouts/onboarding.py:148 +#: openpilot/selfdrive/ui/layouts/onboarding.py:152 #, python-format msgid "Decline, uninstall openpilot" msgstr "Decline, uninstall openpilot" -#: selfdrive/ui/layouts/settings/settings.py:67 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:64 msgid "Developer" msgstr "Developer" -#: selfdrive/ui/layouts/settings/settings.py:62 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:59 msgid "Device" msgstr "Device" -#: selfdrive/ui/layouts/settings/toggles.py:58 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:58 #, python-format msgid "Disengage on Accelerator Pedal" msgstr "Disengage on Accelerator Pedal" -#: selfdrive/ui/layouts/settings/device.py:184 +#: openpilot/selfdrive/ui/layouts/settings/device.py:176 #, python-format msgid "Disengage to Power Off" msgstr "Disengage to Power Off" -#: selfdrive/ui/layouts/settings/device.py:172 +#: openpilot/selfdrive/ui/layouts/settings/device.py:164 #, python-format msgid "Disengage to Reboot" msgstr "Disengage to Reboot" -#: selfdrive/ui/layouts/settings/device.py:103 +#: openpilot/selfdrive/ui/layouts/settings/device.py:95 #, python-format msgid "Disengage to Reset Calibration" msgstr "Disengage to Reset Calibration" -#: selfdrive/ui/layouts/settings/toggles.py:32 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:32 msgid "Display speed in km/h instead of mph." msgstr "Display speed in km/h instead of mph." -#: selfdrive/ui/layouts/settings/device.py:59 +#: openpilot/selfdrive/ui/layouts/settings/device.py:57 #, python-format msgid "Dongle ID" msgstr "Dongle ID" -#: selfdrive/ui/layouts/settings/software.py:50 +#: openpilot/selfdrive/ui/layouts/settings/software.py:57 #, python-format msgid "Download" msgstr "Download" -#: selfdrive/ui/layouts/settings/device.py:62 +#: openpilot/selfdrive/ui/layouts/settings/device.py:60 #, python-format msgid "Driver Camera" msgstr "Driver Camera" -#: selfdrive/ui/layouts/settings/toggles.py:96 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:96 #, python-format msgid "Driving Personality" msgstr "Driving Personality" -#: system/ui/widgets/network.py:123 system/ui/widgets/network.py:139 +#: system/ui/widgets/network.py:120 +#: system/ui/widgets/network.py:136 #, python-format msgid "EDIT" msgstr "EDIT" -#: selfdrive/ui/layouts/sidebar.py:138 +#: openpilot/selfdrive/ui/layouts/sidebar.py:138 msgid "ERROR" msgstr "ERROR" -#: selfdrive/ui/layouts/sidebar.py:45 +#: openpilot/selfdrive/ui/layouts/sidebar.py:45 msgid "ETH" msgstr "ETH" -#: selfdrive/ui/widgets/exp_mode_button.py:50 +#: openpilot/selfdrive/ui/widgets/exp_mode_button.py:51 #, python-format msgid "EXPERIMENTAL MODE ON" msgstr "EXPERIMENTAL MODE ON" -#: selfdrive/ui/layouts/settings/developer.py:166 -#: selfdrive/ui/layouts/settings/toggles.py:228 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:229 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:180 #, python-format msgid "Enable" msgstr "Enable" -#: selfdrive/ui/layouts/settings/developer.py:39 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:39 #, python-format msgid "Enable ADB" msgstr "Enable ADB" -#: selfdrive/ui/layouts/settings/toggles.py:64 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:64 #, python-format msgid "Enable Lane Departure Warnings" msgstr "Enable Lane Departure Warnings" -#: system/ui/widgets/network.py:129 +#: system/ui/widgets/network.py:126 #, python-format msgid "Enable Roaming" msgstr "Enable Roaming" -#: selfdrive/ui/layouts/settings/developer.py:48 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:48 #, python-format msgid "Enable SSH" msgstr "Enable SSH" -#: system/ui/widgets/network.py:120 +#: system/ui/widgets/network.py:117 #, python-format msgid "Enable Tethering" msgstr "Enable Tethering" -#: selfdrive/ui/layouts/settings/toggles.py:30 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:30 msgid "Enable driver monitoring even when openpilot is not engaged." msgstr "Enable driver monitoring even when openpilot is not engaged." -#: selfdrive/ui/layouts/settings/toggles.py:46 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:46 #, python-format msgid "Enable openpilot" msgstr "Enable openpilot" -#: selfdrive/ui/layouts/settings/toggles.py:189 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:190 #, python-format -msgid "" -"Enable the openpilot longitudinal control (alpha) toggle to allow " -"Experimental mode." -msgstr "" -"Enable the openpilot longitudinal control (alpha) toggle to allow " -"Experimental mode." +msgid "Enable the openpilot longitudinal control (alpha) toggle to allow Experimental mode." +msgstr "Enable the openpilot longitudinal control (alpha) toggle to allow Experimental mode." -#: system/ui/widgets/network.py:204 +#: system/ui/widgets/network.py:201 #, python-format msgid "Enter APN" msgstr "Enter APN" -#: system/ui/widgets/network.py:241 +#: system/ui/widgets/network.py:243 #, python-format msgid "Enter SSID" msgstr "Enter SSID" -#: system/ui/widgets/network.py:254 +#: system/ui/widgets/network.py:257 #, python-format msgid "Enter new tethering password" msgstr "Enter new tethering password" -#: system/ui/widgets/network.py:237 system/ui/widgets/network.py:314 +#: system/ui/widgets/network.py:238 +#: system/ui/widgets/network.py:320 #, python-format msgid "Enter password" msgstr "Enter password" -#: selfdrive/ui/widgets/ssh_key.py:89 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:89 #, python-format msgid "Enter your GitHub username" msgstr "Enter your GitHub username" -#: system/ui/widgets/list_view.py:123 system/ui/widgets/list_view.py:160 +#: system/ui/widgets/list_view.py:123 +#: system/ui/widgets/list_view.py:160 #, python-format msgid "Error" msgstr "Error" -#: selfdrive/ui/layouts/settings/toggles.py:52 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:52 #, python-format msgid "Experimental Mode" msgstr "Experimental Mode" -#: selfdrive/ui/layouts/settings/toggles.py:181 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:182 #, python-format -msgid "" -"Experimental mode is currently unavailable on this car since the car's stock " -"ACC is used for longitudinal control." -msgstr "" -"Experimental mode is currently unavailable on this car since the car's stock " -"ACC is used for longitudinal control." +msgid "Experimental mode is currently unavailable on this car since the car's stock ACC is used for longitudinal control." +msgstr "Experimental mode is currently unavailable on this car since the car's stock ACC is used for longitudinal control." -#: system/ui/widgets/network.py:373 +#: system/ui/widgets/network.py:380 #, python-format msgid "FORGETTING..." msgstr "FORGETTING..." -#: selfdrive/ui/widgets/setup.py:44 +#: openpilot/selfdrive/ui/widgets/setup.py:43 #, python-format msgid "Finish Setup" msgstr "Finish Setup" -#: selfdrive/ui/layouts/settings/settings.py:66 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:63 msgid "Firehose" msgstr "Firehose" -#: selfdrive/ui/layouts/settings/firehose.py:18 +#: openpilot/selfdrive/ui/layouts/settings/firehose.py:10 msgid "Firehose Mode" msgstr "Firehose Mode" -#: selfdrive/ui/layouts/settings/firehose.py:25 -msgid "" -"For maximum effectiveness, bring your device inside and connect to a good " -"USB-C adapter and Wi-Fi weekly.\n" -"\n" -"Firehose Mode can also work while you're driving if connected to a hotspot " -"or unlimited SIM card.\n" -"\n" -"\n" -"Frequently Asked Questions\n" -"\n" -"Does it matter how or where I drive? Nope, just drive as you normally " -"would.\n" -"\n" -"Do all of my segments get pulled in Firehose Mode? No, we selectively pull a " -"subset of your segments.\n" -"\n" -"What's a good USB-C adapter? Any fast phone or laptop charger should be " -"fine.\n" -"\n" -"Does it matter which software I run? Yes, only upstream openpilot (and " -"particular forks) are able to be used for training." -msgstr "" -"For maximum effectiveness, bring your device inside and connect to a good " -"USB-C adapter and Wi-Fi weekly.\n" -"\n" -"Firehose Mode can also work while you're driving if connected to a hotspot " -"or unlimited SIM card.\n" -"\n" -"\n" -"Frequently Asked Questions\n" -"\n" -"Does it matter how or where I drive? Nope, just drive as you normally " -"would.\n" -"\n" -"Do all of my segments get pulled in Firehose Mode? No, we selectively pull a " -"subset of your segments.\n" -"\n" -"What's a good USB-C adapter? Any fast phone or laptop charger should be " -"fine.\n" -"\n" -"Does it matter which software I run? Yes, only upstream openpilot (and " -"particular forks) are able to be used for training." - -#: system/ui/widgets/network.py:318 system/ui/widgets/network.py:451 +#: system/ui/widgets/network.py:458 +#: system/ui/widgets/network.py:326 #, python-format msgid "Forget" msgstr "Forget" -#: system/ui/widgets/network.py:319 +#: system/ui/widgets/network.py:327 #, python-format msgid "Forget Wi-Fi Network \"{}\"?" msgstr "Forget Wi-Fi Network \"{}\"?" -#: selfdrive/ui/layouts/sidebar.py:71 selfdrive/ui/layouts/sidebar.py:125 +#: openpilot/selfdrive/ui/layouts/sidebar.py:71 +#: openpilot/selfdrive/ui/layouts/sidebar.py:125 msgid "GOOD" msgstr "GOOD" -#: selfdrive/ui/widgets/pairing_dialog.py:128 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:117 #, python-format msgid "Go to https://connect.comma.ai on your phone" msgstr "Go to https://connect.comma.ai on your phone" -#: selfdrive/ui/layouts/sidebar.py:129 +#: openpilot/selfdrive/ui/layouts/sidebar.py:129 msgid "HIGH" msgstr "HIGH" -#: system/ui/widgets/network.py:155 +#: system/ui/widgets/network.py:152 #, python-format msgid "Hidden Network" msgstr "Hidden Network" -#: selfdrive/ui/layouts/settings/firehose.py:140 -#, python-format -msgid "INACTIVE: connect to an unmetered network" -msgstr "INACTIVE: connect to an unmetered network" - -#: selfdrive/ui/layouts/settings/software.py:53 -#: selfdrive/ui/layouts/settings/software.py:136 +#: openpilot/selfdrive/ui/layouts/settings/software.py:60 +#: openpilot/selfdrive/ui/layouts/settings/software.py:146 #, python-format msgid "INSTALL" msgstr "INSTALL" -#: system/ui/widgets/network.py:150 +#: system/ui/widgets/network.py:147 #, python-format msgid "IP Address" msgstr "IP Address" -#: selfdrive/ui/layouts/settings/software.py:53 +#: openpilot/selfdrive/ui/layouts/settings/software.py:60 #, python-format msgid "Install Update" msgstr "Install Update" -#: selfdrive/ui/layouts/settings/developer.py:56 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:56 #, python-format msgid "Joystick Debug Mode" msgstr "Joystick Debug Mode" -#: selfdrive/ui/widgets/ssh_key.py:29 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:29 msgid "LOADING" msgstr "LOADING" -#: selfdrive/ui/layouts/sidebar.py:48 +#: openpilot/selfdrive/ui/layouts/sidebar.py:48 msgid "LTE" msgstr "LTE" -#: selfdrive/ui/layouts/settings/developer.py:64 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:64 #, python-format msgid "Longitudinal Maneuver Mode" msgstr "Longitudinal Maneuver Mode" -#: selfdrive/ui/onroad/hud_renderer.py:148 +#: openpilot/selfdrive/ui/onroad/hud_renderer.py:148 #, python-format msgid "MAX" msgstr "MAX" -#: selfdrive/ui/widgets/setup.py:75 +#: openpilot/selfdrive/ui/widgets/setup.py:74 #, python-format -msgid "" -"Maximize your training data uploads to improve openpilot's driving models." -msgstr "" -"Maximize your training data uploads to improve openpilot's driving models." +msgid "Maximize your training data uploads to improve openpilot's driving models." +msgstr "Maximize your training data uploads to improve openpilot's driving models." -#: selfdrive/ui/layouts/settings/device.py:59 -#: selfdrive/ui/layouts/settings/device.py:60 +#: openpilot/selfdrive/ui/layouts/settings/device.py:57 +#: openpilot/selfdrive/ui/layouts/settings/device.py:58 #, python-format msgid "N/A" msgstr "N/A" -#: selfdrive/ui/layouts/sidebar.py:142 +#: openpilot/selfdrive/ui/layouts/sidebar.py:142 msgid "NO" msgstr "NO" -#: selfdrive/ui/layouts/settings/settings.py:63 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:60 msgid "Network" msgstr "Network" -#: selfdrive/ui/widgets/ssh_key.py:114 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:115 #, python-format msgid "No SSH keys found" msgstr "No SSH keys found" -#: selfdrive/ui/widgets/ssh_key.py:126 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:127 #, python-format msgid "No SSH keys found for user '{}'" msgstr "No SSH keys found for user '{}'" -#: selfdrive/ui/widgets/offroad_alerts.py:320 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:321 #, python-format msgid "No release notes available." msgstr "No release notes available." -#: selfdrive/ui/layouts/sidebar.py:73 selfdrive/ui/layouts/sidebar.py:134 +#: openpilot/selfdrive/ui/layouts/sidebar.py:73 +#: openpilot/selfdrive/ui/layouts/sidebar.py:134 msgid "OFFLINE" msgstr "OFFLINE" -#: system/ui/widgets/html_render.py:263 system/ui/widgets/confirm_dialog.py:93 -#: selfdrive/ui/layouts/sidebar.py:127 +#: system/ui/widgets/confirm_dialog.py:93 +#: system/ui/widgets/html_render.py:263 +#: openpilot/selfdrive/ui/layouts/sidebar.py:127 #, python-format msgid "OK" msgstr "OK" -#: selfdrive/ui/layouts/sidebar.py:72 selfdrive/ui/layouts/sidebar.py:136 -#: selfdrive/ui/layouts/sidebar.py:144 +#: openpilot/selfdrive/ui/layouts/sidebar.py:72 +#: openpilot/selfdrive/ui/layouts/sidebar.py:144 +#: openpilot/selfdrive/ui/layouts/sidebar.py:136 msgid "ONLINE" msgstr "ONLINE" -#: selfdrive/ui/widgets/setup.py:20 +#: openpilot/selfdrive/ui/widgets/setup.py:19 #, python-format msgid "Open" msgstr "Open" -#: selfdrive/ui/layouts/settings/device.py:48 +#: openpilot/selfdrive/ui/layouts/settings/device.py:45 #, python-format msgid "PAIR" msgstr "PAIR" -#: selfdrive/ui/layouts/sidebar.py:142 +#: openpilot/selfdrive/ui/layouts/sidebar.py:142 msgid "PANDA" msgstr "PANDA" -#: selfdrive/ui/layouts/settings/device.py:62 +#: openpilot/selfdrive/ui/layouts/settings/device.py:60 #, python-format msgid "PREVIEW" msgstr "PREVIEW" -#: selfdrive/ui/widgets/prime.py:44 +#: openpilot/selfdrive/ui/widgets/prime.py:44 #, python-format msgid "PRIME FEATURES:" msgstr "PRIME FEATURES:" -#: selfdrive/ui/layouts/settings/device.py:48 +#: openpilot/selfdrive/ui/layouts/settings/device.py:45 #, python-format msgid "Pair Device" msgstr "Pair Device" -#: selfdrive/ui/widgets/setup.py:19 +#: openpilot/selfdrive/ui/widgets/setup.py:18 #, python-format msgid "Pair device" msgstr "Pair device" -#: selfdrive/ui/widgets/pairing_dialog.py:103 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:92 #, python-format msgid "Pair your device to your comma account" msgstr "Pair your device to your comma account" -#: selfdrive/ui/widgets/setup.py:48 selfdrive/ui/layouts/settings/device.py:24 +#: openpilot/selfdrive/ui/widgets/setup.py:47 +#: openpilot/selfdrive/ui/layouts/settings/device.py:23 #, python-format -msgid "" -"Pair your device with comma connect (connect.comma.ai) and claim your comma " -"prime offer." -msgstr "" -"Pair your device with comma connect (connect.comma.ai) and claim your comma " -"prime offer." +msgid "Pair your device with comma connect (connect.comma.ai) and claim your comma prime offer." +msgstr "Pair your device with comma connect (connect.comma.ai) and claim your comma prime offer." -#: selfdrive/ui/widgets/setup.py:91 +#: openpilot/selfdrive/ui/widgets/setup.py:91 #, python-format msgid "Please connect to Wi-Fi to complete initial pairing" msgstr "Please connect to Wi-Fi to complete initial pairing" -#: selfdrive/ui/layouts/settings/device.py:55 -#: selfdrive/ui/layouts/settings/device.py:187 +#: openpilot/selfdrive/ui/layouts/settings/device.py:183 +#: openpilot/selfdrive/ui/layouts/settings/device.py:53 #, python-format msgid "Power Off" msgstr "Power Off" -#: system/ui/widgets/network.py:144 +#: system/ui/widgets/network.py:141 #, python-format msgid "Prevent large data uploads when on a metered Wi-Fi connection" msgstr "Prevent large data uploads when on a metered Wi-Fi connection" -#: system/ui/widgets/network.py:135 +#: system/ui/widgets/network.py:132 #, python-format msgid "Prevent large data uploads when on a metered cellular connection" msgstr "Prevent large data uploads when on a metered cellular connection" -#: selfdrive/ui/layouts/settings/device.py:25 -msgid "" -"Preview the driver facing camera to ensure that driver monitoring has good " -"visibility. (vehicle must be off)" -msgstr "" -"Preview the driver facing camera to ensure that driver monitoring has good " -"visibility. (vehicle must be off)" +#: openpilot/selfdrive/ui/layouts/settings/device.py:24 +msgid "Preview the driver facing camera to ensure that driver monitoring has good visibility. (vehicle must be off)" +msgstr "Preview the driver facing camera to ensure that driver monitoring has good visibility. (vehicle must be off)" -#: selfdrive/ui/widgets/pairing_dialog.py:161 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:150 #, python-format msgid "QR Code Error" msgstr "QR Code Error" -#: selfdrive/ui/widgets/ssh_key.py:31 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:31 msgid "REMOVE" msgstr "REMOVE" -#: selfdrive/ui/layouts/settings/device.py:51 +#: openpilot/selfdrive/ui/layouts/settings/device.py:49 #, python-format msgid "RESET" msgstr "RESET" -#: selfdrive/ui/layouts/settings/device.py:65 +#: openpilot/selfdrive/ui/layouts/settings/device.py:63 #, python-format msgid "REVIEW" msgstr "REVIEW" -#: selfdrive/ui/layouts/settings/device.py:55 -#: selfdrive/ui/layouts/settings/device.py:175 +#: openpilot/selfdrive/ui/layouts/settings/device.py:171 +#: openpilot/selfdrive/ui/layouts/settings/device.py:53 #, python-format msgid "Reboot" msgstr "Reboot" -#: selfdrive/ui/onroad/alert_renderer.py:66 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:66 #, python-format msgid "Reboot Device" msgstr "Reboot Device" -#: selfdrive/ui/widgets/offroad_alerts.py:112 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:112 #, python-format msgid "Reboot and Update" msgstr "Reboot and Update" -#: selfdrive/ui/layouts/settings/toggles.py:27 -msgid "" -"Receive alerts to steer back into the lane when your vehicle drifts over a " -"detected lane line without a turn signal activated while driving over 31 mph " -"(50 km/h)." -msgstr "" -"Receive alerts to steer back into the lane when your vehicle drifts over a " -"detected lane line without a turn signal activated while driving over 31 mph " -"(50 km/h)." - -#: selfdrive/ui/layouts/settings/toggles.py:76 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:76 #, python-format msgid "Record and Upload Driver Camera" msgstr "Record and Upload Driver Camera" -#: selfdrive/ui/layouts/settings/toggles.py:82 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:82 #, python-format msgid "Record and Upload Microphone Audio" msgstr "Record and Upload Microphone Audio" -#: selfdrive/ui/layouts/settings/toggles.py:33 -msgid "" -"Record and store microphone audio while driving. The audio will be included " -"in the dashcam video in comma connect." -msgstr "" -"Record and store microphone audio while driving. The audio will be included " -"in the dashcam video in comma connect." +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:33 +msgid "Record and store microphone audio while driving. The audio will be included in the dashcam video in comma connect." +msgstr "Record and store microphone audio while driving. The audio will be included in the dashcam video in comma connect." -#: selfdrive/ui/layouts/settings/device.py:67 +#: openpilot/selfdrive/ui/layouts/settings/device.py:65 #, python-format msgid "Regulatory" msgstr "Regulatory" -#: selfdrive/ui/layouts/settings/toggles.py:98 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:98 #, python-format msgid "Relaxed" msgstr "Relaxed" -#: selfdrive/ui/widgets/prime.py:47 +#: openpilot/selfdrive/ui/widgets/prime.py:47 #, python-format msgid "Remote access" msgstr "Remote access" -#: selfdrive/ui/widgets/prime.py:47 +#: openpilot/selfdrive/ui/widgets/prime.py:47 #, python-format msgid "Remote snapshots" msgstr "Remote snapshots" -#: selfdrive/ui/widgets/ssh_key.py:123 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:124 #, python-format msgid "Request timed out" msgstr "Request timed out" -#: selfdrive/ui/layouts/settings/device.py:119 +#: openpilot/selfdrive/ui/layouts/settings/device.py:111 #, python-format msgid "Reset" msgstr "Reset" -#: selfdrive/ui/layouts/settings/device.py:51 +#: openpilot/selfdrive/ui/layouts/settings/device.py:49 #, python-format msgid "Reset Calibration" msgstr "Reset Calibration" -#: selfdrive/ui/layouts/settings/device.py:65 +#: openpilot/selfdrive/ui/layouts/settings/device.py:63 #, python-format msgid "Review Training Guide" msgstr "Review Training Guide" -#: selfdrive/ui/layouts/settings/device.py:27 +#: openpilot/selfdrive/ui/layouts/settings/device.py:26 msgid "Review the rules, features, and limitations of openpilot" msgstr "Review the rules, features, and limitations of openpilot" -#: selfdrive/ui/layouts/settings/software.py:61 +#: openpilot/selfdrive/ui/layouts/settings/software.py:68 #, python-format msgid "SELECT" msgstr "" -#: selfdrive/ui/layouts/settings/developer.py:53 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:53 #, python-format msgid "SSH Keys" msgstr "SSH Keys" -#: system/ui/widgets/network.py:310 +#: system/ui/widgets/network.py:316 #, python-format msgid "Scanning Wi-Fi networks..." msgstr "Scanning Wi-Fi networks..." -#: system/ui/widgets/option_dialog.py:36 +#: system/ui/widgets/option_dialog.py:37 #, python-format msgid "Select" msgstr "Select" -#: selfdrive/ui/layouts/settings/software.py:183 +#: openpilot/selfdrive/ui/layouts/settings/software.py:203 #, python-format msgid "Select a branch" msgstr "" -#: selfdrive/ui/layouts/settings/device.py:91 +#: openpilot/selfdrive/ui/layouts/settings/device.py:89 #, python-format msgid "Select a language" msgstr "Select a language" -#: selfdrive/ui/layouts/settings/device.py:60 +#: openpilot/selfdrive/ui/layouts/settings/device.py:58 #, python-format msgid "Serial" msgstr "Serial" -#: selfdrive/ui/widgets/offroad_alerts.py:106 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:106 #, python-format msgid "Snooze Update" msgstr "Snooze Update" -#: selfdrive/ui/layouts/settings/settings.py:65 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:62 msgid "Software" msgstr "Software" -#: selfdrive/ui/layouts/settings/toggles.py:98 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:98 #, python-format msgid "Standard" msgstr "Standard" -#: selfdrive/ui/layouts/settings/toggles.py:22 -msgid "" -"Standard is recommended. In aggressive mode, openpilot will follow lead cars " -"closer and be more aggressive with the gas and brake. In relaxed mode " -"openpilot will stay further away from lead cars. On supported cars, you can " -"cycle through these personalities with your steering wheel distance button." -msgstr "" -"Standard is recommended. In aggressive mode, openpilot will follow lead cars " -"closer and be more aggressive with the gas and brake. In relaxed mode " -"openpilot will stay further away from lead cars. On supported cars, you can " -"cycle through these personalities with your steering wheel distance button." - -#: selfdrive/ui/onroad/alert_renderer.py:59 -#: selfdrive/ui/onroad/alert_renderer.py:65 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:59 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:65 #, python-format msgid "System Unresponsive" msgstr "System Unresponsive" -#: selfdrive/ui/onroad/alert_renderer.py:58 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:58 #, python-format msgid "TAKE CONTROL IMMEDIATELY" msgstr "TAKE CONTROL IMMEDIATELY" -#: selfdrive/ui/layouts/sidebar.py:71 selfdrive/ui/layouts/sidebar.py:125 -#: selfdrive/ui/layouts/sidebar.py:127 selfdrive/ui/layouts/sidebar.py:129 +#: openpilot/selfdrive/ui/layouts/sidebar.py:71 +#: openpilot/selfdrive/ui/layouts/sidebar.py:125 +#: openpilot/selfdrive/ui/layouts/sidebar.py:127 +#: openpilot/selfdrive/ui/layouts/sidebar.py:129 msgid "TEMP" msgstr "TEMP" -#: selfdrive/ui/layouts/settings/software.py:61 +#: openpilot/selfdrive/ui/layouts/settings/software.py:68 #, python-format msgid "Target Branch" msgstr "" -#: system/ui/widgets/network.py:124 +#: system/ui/widgets/network.py:121 #, python-format msgid "Tethering Password" msgstr "Tethering Password" -#: selfdrive/ui/layouts/settings/settings.py:64 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:61 msgid "Toggles" msgstr "Toggles" -#: selfdrive/ui/layouts/settings/software.py:72 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:79 +#, python-format +msgid "UI Debug Mode" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/software.py:79 #, python-format msgid "UNINSTALL" msgstr "UNINSTALL" -#: selfdrive/ui/layouts/home.py:155 +#: openpilot/selfdrive/ui/layouts/home.py:155 #, python-format msgid "UPDATE" msgstr "UPDATE" -#: selfdrive/ui/layouts/settings/software.py:72 -#: selfdrive/ui/layouts/settings/software.py:163 +#: openpilot/selfdrive/ui/layouts/settings/software.py:173 +#: openpilot/selfdrive/ui/layouts/settings/software.py:79 #, python-format msgid "Uninstall" msgstr "Uninstall" -#: selfdrive/ui/layouts/sidebar.py:117 +#: openpilot/selfdrive/ui/layouts/sidebar.py:117 msgid "Unknown" msgstr "Unknown" -#: selfdrive/ui/layouts/settings/software.py:48 +#: openpilot/selfdrive/ui/layouts/settings/software.py:55 #, python-format msgid "Updates are only downloaded while the car is off." msgstr "Updates are only downloaded while the car is off." -#: selfdrive/ui/widgets/prime.py:33 +#: openpilot/selfdrive/ui/widgets/prime.py:33 #, python-format msgid "Upgrade Now" msgstr "Upgrade Now" -#: selfdrive/ui/layouts/settings/toggles.py:31 -msgid "" -"Upload data from the driver facing camera and help improve the driver " -"monitoring algorithm." -msgstr "" -"Upload data from the driver facing camera and help improve the driver " -"monitoring algorithm." +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:31 +msgid "Upload data from the driver facing camera and help improve the driver monitoring algorithm." +msgstr "Upload data from the driver facing camera and help improve the driver monitoring algorithm." -#: selfdrive/ui/layouts/settings/toggles.py:88 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:88 #, python-format msgid "Use Metric System" msgstr "Use Metric System" -#: selfdrive/ui/layouts/settings/toggles.py:17 -msgid "" -"Use the openpilot system for adaptive cruise control and lane keep driver " -"assistance. Your attention is required at all times to use this feature." -msgstr "" -"Use the openpilot system for adaptive cruise control and lane keep driver " -"assistance. Your attention is required at all times to use this feature." - -#: selfdrive/ui/layouts/sidebar.py:72 selfdrive/ui/layouts/sidebar.py:144 +#: openpilot/selfdrive/ui/layouts/sidebar.py:72 +#: openpilot/selfdrive/ui/layouts/sidebar.py:144 msgid "VEHICLE" msgstr "VEHICLE" -#: selfdrive/ui/layouts/settings/device.py:67 +#: openpilot/selfdrive/ui/layouts/settings/device.py:65 #, python-format msgid "VIEW" msgstr "VIEW" -#: selfdrive/ui/onroad/alert_renderer.py:52 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:52 #, python-format msgid "Waiting to start" msgstr "Waiting to start" -#: selfdrive/ui/layouts/settings/developer.py:19 -msgid "" -"Warning: This grants SSH access to all public keys in your GitHub settings. " -"Never enter a GitHub username other than your own. A comma employee will " -"NEVER ask you to add their GitHub username." -msgstr "" -"Warning: This grants SSH access to all public keys in your GitHub settings. " -"Never enter a GitHub username other than your own. A comma employee will " -"NEVER ask you to add their GitHub username." - -#: selfdrive/ui/layouts/onboarding.py:111 +#: openpilot/selfdrive/ui/layouts/onboarding.py:115 #, python-format msgid "Welcome to openpilot" msgstr "Welcome to openpilot" -#: selfdrive/ui/layouts/settings/toggles.py:20 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:20 msgid "When enabled, pressing the accelerator pedal will disengage openpilot." msgstr "When enabled, pressing the accelerator pedal will disengage openpilot." -#: selfdrive/ui/layouts/sidebar.py:44 +#: openpilot/selfdrive/ui/layouts/sidebar.py:44 msgid "Wi-Fi" msgstr "Wi-Fi" -#: system/ui/widgets/network.py:144 +#: system/ui/widgets/network.py:141 #, python-format msgid "Wi-Fi Network Metered" msgstr "Wi-Fi Network Metered" -#: system/ui/widgets/network.py:314 +#: system/ui/widgets/network.py:320 #, python-format msgid "Wrong password" msgstr "Wrong password" -#: selfdrive/ui/layouts/onboarding.py:145 +#: openpilot/selfdrive/ui/layouts/onboarding.py:149 #, python-format msgid "You must accept the Terms and Conditions in order to use openpilot." msgstr "You must accept the Terms and Conditions in order to use openpilot." -#: selfdrive/ui/layouts/onboarding.py:112 +#: openpilot/selfdrive/ui/layouts/onboarding.py:116 #, python-format -msgid "" -"You must accept the Terms and Conditions to use openpilot. Read the latest " -"terms at https://comma.ai/terms before continuing." -msgstr "" -"You must accept the Terms and Conditions to use openpilot. Read the latest " -"terms at https://comma.ai/terms before continuing." +msgid "You must accept the Terms and Conditions to use openpilot. Read the latest terms at https://comma.ai/terms before continuing." +msgstr "You must accept the Terms and Conditions to use openpilot. Read the latest terms at https://comma.ai/terms before continuing." -#: selfdrive/ui/onroad/driver_camera_dialog.py:34 +#: openpilot/selfdrive/ui/onroad/driver_camera_dialog.py:38 #, python-format msgid "camera starting" msgstr "camera starting" -#: selfdrive/ui/widgets/prime.py:63 +#: openpilot/selfdrive/ui/layouts/settings/software.py:19 +#, python-format +msgid "checking..." +msgstr "" + +#: openpilot/selfdrive/ui/widgets/prime.py:63 #, python-format msgid "comma prime" msgstr "comma prime" -#: system/ui/widgets/network.py:142 +#: system/ui/widgets/network.py:139 #, python-format msgid "default" msgstr "default" -#: selfdrive/ui/layouts/settings/device.py:133 +#: openpilot/selfdrive/ui/layouts/settings/device.py:125 #, python-format msgid "down" msgstr "down" -#: selfdrive/ui/layouts/settings/software.py:106 +#: openpilot/selfdrive/ui/layouts/settings/software.py:20 +#, python-format +msgid "downloading..." +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/software.py:116 #, python-format msgid "failed to check for update" msgstr "failed to check for update" -#: system/ui/widgets/network.py:237 system/ui/widgets/network.py:314 +#: openpilot/selfdrive/ui/layouts/settings/software.py:21 +#, python-format +msgid "finalizing update..." +msgstr "" + +#: system/ui/widgets/network.py:238 +#: system/ui/widgets/network.py:321 #, python-format msgid "for \"{}\"" msgstr "for \"{}\"" -#: selfdrive/ui/onroad/hud_renderer.py:177 +#: openpilot/selfdrive/ui/onroad/hud_renderer.py:177 #, python-format msgid "km/h" msgstr "km/h" -#: system/ui/widgets/network.py:204 +#: system/ui/widgets/network.py:201 #, python-format msgid "leave blank for automatic configuration" msgstr "leave blank for automatic configuration" -#: selfdrive/ui/layouts/settings/device.py:134 +#: openpilot/selfdrive/ui/layouts/settings/device.py:126 #, python-format msgid "left" msgstr "left" -#: system/ui/widgets/network.py:142 +#: system/ui/widgets/network.py:139 #, python-format msgid "metered" msgstr "metered" -#: selfdrive/ui/onroad/hud_renderer.py:177 +#: openpilot/selfdrive/ui/onroad/hud_renderer.py:177 #, python-format msgid "mph" msgstr "mph" -#: selfdrive/ui/layouts/settings/software.py:20 +#: openpilot/selfdrive/ui/layouts/settings/software.py:27 #, python-format msgid "never" msgstr "never" -#: selfdrive/ui/layouts/settings/software.py:31 +#: openpilot/selfdrive/ui/layouts/settings/software.py:38 #, python-format msgid "now" msgstr "now" -#: selfdrive/ui/layouts/settings/developer.py:71 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:71 #, python-format msgid "openpilot Longitudinal Control (Alpha)" msgstr "openpilot Longitudinal Control (Alpha)" -#: selfdrive/ui/onroad/alert_renderer.py:51 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:51 #, python-format msgid "openpilot Unavailable" msgstr "openpilot Unavailable" -#: selfdrive/ui/layouts/settings/toggles.py:158 -#, python-format -msgid "" -"openpilot defaults to driving in chill mode. Experimental mode enables alpha-" -"level features that aren't ready for chill mode. Experimental features are " -"listed below:

End-to-End Longitudinal Control


Let the driving " -"model control the gas and brakes. openpilot will drive as it thinks a human " -"would, including stopping for red lights and stop signs. Since the driving " -"model decides the speed to drive, the set speed will only act as an upper " -"bound. This is an alpha quality feature; mistakes should be expected." -"

New Driving Visualization


The driving visualization will " -"transition to the road-facing wide-angle camera at low speeds to better show " -"some turns. The Experimental mode logo will also be shown in the top right " -"corner." -msgstr "" -"openpilot defaults to driving in chill mode. Experimental mode enables alpha-" -"level features that aren't ready for chill mode. Experimental features are " -"listed below:

End-to-End Longitudinal Control


Let the driving " -"model control the gas and brakes. openpilot will drive as it thinks a human " -"would, including stopping for red lights and stop signs. Since the driving " -"model decides the speed to drive, the set speed will only act as an upper " -"bound. This is an alpha quality feature; mistakes should be expected." -"

New Driving Visualization


The driving visualization will " -"transition to the road-facing wide-angle camera at low speeds to better show " -"some turns. The Experimental mode logo will also be shown in the top right " -"corner." - -#: selfdrive/ui/layouts/settings/device.py:165 -#, python-format -msgid "" -"openpilot is continuously calibrating, resetting is rarely required. " -"Resetting calibration will restart openpilot if the car is powered on." -msgstr "" -"openpilot is continuously calibrating, resetting is rarely required. " -"Resetting calibration will restart openpilot if the car is powered on." - -#: selfdrive/ui/layouts/settings/firehose.py:20 -msgid "" -"openpilot learns to drive by watching humans, like you, drive.\n" -"\n" -"Firehose Mode allows you to maximize your training data uploads to improve " -"openpilot's driving models. More data means bigger models, which means " -"better Experimental Mode." -msgstr "" -"openpilot learns to drive by watching humans, like you, drive.\n" -"\n" -"Firehose Mode allows you to maximize your training data uploads to improve " -"openpilot's driving models. More data means bigger models, which means " -"better Experimental Mode." - -#: selfdrive/ui/layouts/settings/toggles.py:183 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:184 #, python-format msgid "openpilot longitudinal control may come in a future update." msgstr "openpilot longitudinal control may come in a future update." -#: selfdrive/ui/layouts/settings/device.py:26 -msgid "" -"openpilot requires the device to be mounted within 4° left or right and " -"within 5° up or 9° down." -msgstr "" -"openpilot requires the device to be mounted within 4° left or right and " -"within 5° up or 9° down." +#: openpilot/selfdrive/ui/layouts/settings/device.py:25 +msgid "openpilot requires the device to be mounted within 4° left or right and within 5° up or 9° down." +msgstr "openpilot requires the device to be mounted within 4° left or right and within 5° up or 9° down." -#: selfdrive/ui/layouts/settings/device.py:134 +#: openpilot/selfdrive/ui/layouts/settings/device.py:126 #, python-format msgid "right" msgstr "right" -#: system/ui/widgets/network.py:142 +#: system/ui/widgets/network.py:139 #, python-format msgid "unmetered" msgstr "unmetered" -#: selfdrive/ui/layouts/settings/device.py:133 +#: openpilot/selfdrive/ui/layouts/settings/device.py:125 #, python-format msgid "up" msgstr "up" -#: selfdrive/ui/layouts/settings/software.py:117 +#: openpilot/selfdrive/ui/layouts/settings/software.py:127 #, python-format msgid "up to date, last checked never" msgstr "up to date, last checked never" -#: selfdrive/ui/layouts/settings/software.py:115 +#: openpilot/selfdrive/ui/layouts/settings/software.py:125 #, python-format msgid "up to date, last checked {}" msgstr "up to date, last checked {}" -#: selfdrive/ui/layouts/settings/software.py:109 +#: openpilot/selfdrive/ui/layouts/settings/software.py:119 #, python-format msgid "update available" msgstr "update available" -#: selfdrive/ui/layouts/home.py:169 +#: openpilot/selfdrive/ui/layouts/home.py:169 #, python-format msgid "{} ALERT" msgid_plural "{} ALERTS" msgstr[0] "{} ALERT" msgstr[1] "{} ALERTS" -#: selfdrive/ui/layouts/settings/software.py:40 +#: openpilot/selfdrive/ui/layouts/settings/software.py:47 #, python-format msgid "{} day ago" msgid_plural "{} days ago" msgstr[0] "{} day ago" msgstr[1] "{} days ago" -#: selfdrive/ui/layouts/settings/software.py:37 +#: openpilot/selfdrive/ui/layouts/settings/software.py:44 #, python-format msgid "{} hour ago" msgid_plural "{} hours ago" msgstr[0] "{} hour ago" msgstr[1] "{} hours ago" -#: selfdrive/ui/layouts/settings/software.py:34 +#: openpilot/selfdrive/ui/layouts/settings/software.py:41 #, python-format msgid "{} minute ago" msgid_plural "{} minutes ago" msgstr[0] "{} minute ago" msgstr[1] "{} minutes ago" -#: selfdrive/ui/layouts/settings/firehose.py:111 +#: openpilot/selfdrive/ui/layouts/settings/firehose.py:70 #, python-format msgid "{} segment of your driving is in the training dataset so far." msgid_plural "{} segments of your driving is in the training dataset so far." msgstr[0] "{} segment of your driving is in the training dataset so far." msgstr[1] "{} segments of your driving is in the training dataset so far." -#: selfdrive/ui/widgets/prime.py:62 +#: openpilot/selfdrive/ui/widgets/prime.py:62 #, python-format msgid "✓ SUBSCRIBED" msgstr "✓ SUBSCRIBED" -#: selfdrive/ui/widgets/setup.py:22 +#: openpilot/selfdrive/ui/widgets/setup.py:21 #, python-format msgid "🔥 Firehose Mode 🔥" msgstr "🔥 Firehose Mode 🔥" + diff --git a/selfdrive/ui/translations/app_es.po b/selfdrive/ui/translations/app_es.po index 59b9e6dfdb..35188fe2fc 100644 --- a/selfdrive/ui/translations/app_es.po +++ b/selfdrive/ui/translations/app_es.po @@ -17,1209 +17,1018 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -#: selfdrive/ui/layouts/settings/device.py:160 +#: openpilot/selfdrive/ui/layouts/settings/device.py:152 #, python-format msgid " Steering torque response calibration is complete." msgstr " La calibración de respuesta de par de dirección está completa." -#: selfdrive/ui/layouts/settings/device.py:158 +#: openpilot/selfdrive/ui/layouts/settings/device.py:150 #, python-format msgid " Steering torque response calibration is {}% complete." msgstr " La calibración de respuesta de par de dirección está {}% completa." -#: selfdrive/ui/layouts/settings/device.py:133 +#: openpilot/selfdrive/ui/layouts/settings/device.py:125 #, python-format msgid " Your device is pointed {:.1f}° {} and {:.1f}° {}." msgstr " Tu dispositivo está orientado {:.1f}° {} y {:.1f}° {}." -#: selfdrive/ui/layouts/sidebar.py:43 +#: openpilot/selfdrive/ui/layouts/sidebar.py:43 msgid "--" msgstr "--" -#: selfdrive/ui/widgets/prime.py:47 +#: openpilot/selfdrive/ui/widgets/prime.py:47 #, python-format msgid "1 year of drive storage" msgstr "1 año de almacenamiento de conducción" -#: selfdrive/ui/widgets/prime.py:47 +#: openpilot/selfdrive/ui/widgets/prime.py:47 #, python-format msgid "24/7 LTE connectivity" msgstr "Conectividad LTE 24/7" -#: selfdrive/ui/layouts/sidebar.py:46 +#: openpilot/selfdrive/ui/layouts/sidebar.py:46 msgid "2G" msgstr "2G" -#: selfdrive/ui/layouts/sidebar.py:47 +#: openpilot/selfdrive/ui/layouts/sidebar.py:47 msgid "3G" msgstr "3G" -#: selfdrive/ui/layouts/sidebar.py:49 +#: openpilot/selfdrive/ui/layouts/sidebar.py:49 msgid "5G" msgstr "5G" -#: selfdrive/ui/layouts/settings/developer.py:23 -msgid "" -"WARNING: openpilot longitudinal control is in alpha for this car and will " -"disable Automatic Emergency Braking (AEB).

On this car, openpilot " -"defaults to the car's built-in ACC instead of openpilot's longitudinal " -"control. Enable this to switch to openpilot longitudinal control. Enabling " -"Experimental mode is recommended when enabling openpilot longitudinal " -"control alpha. Changing this setting will restart openpilot if the car is " -"powered on." -msgstr "" -"ADVERTENCIA: el control longitudinal de openpilot está en alpha para este " -"coche y deshabilitará el Frenado Automático de Emergencia (AEB).

En este coche, openpilot usa por defecto el ACC integrado del " -"coche en lugar del control longitudinal de openpilot. Activa esto para " -"cambiar al control longitudinal de openpilot. Se recomienda activar el modo " -"Experimental al habilitar el control longitudinal de openpilot (alpha)." - -#: selfdrive/ui/layouts/settings/device.py:148 +#: openpilot/selfdrive/ui/layouts/settings/device.py:140 #, python-format msgid "

Steering lag calibration is complete." msgstr "" -#: selfdrive/ui/layouts/settings/device.py:146 +#: openpilot/selfdrive/ui/layouts/settings/device.py:138 #, python-format msgid "

Steering lag calibration is {}% complete." msgstr "" -#: selfdrive/ui/layouts/settings/firehose.py:138 -#, python-format -msgid "ACTIVE" -msgstr "ACTIVO" - -#: selfdrive/ui/layouts/settings/developer.py:15 -msgid "" -"ADB (Android Debug Bridge) allows connecting to your device over USB or over " -"the network. See https://docs.comma.ai/how-to/connect-to-comma for more info." -msgstr "" -"ADB (Android Debug Bridge) permite conectar tu dispositivo por USB o por la " -"red. Consulta https://docs.comma.ai/how-to/connect-to-comma para más " -"información." - -#: selfdrive/ui/widgets/ssh_key.py:30 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:30 msgid "ADD" msgstr "AÑADIR" -#: system/ui/widgets/network.py:139 +#: system/ui/widgets/network.py:136 #, python-format msgid "APN Setting" msgstr "" -#: selfdrive/ui/widgets/offroad_alerts.py:109 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:109 #, python-format msgid "Acknowledge Excessive Actuation" msgstr "Reconocer actuación excesiva" -#: system/ui/widgets/network.py:74 system/ui/widgets/network.py:95 +#: system/ui/widgets/network.py:92 +#: system/ui/widgets/network.py:74 #, python-format msgid "Advanced" msgstr "" -#: selfdrive/ui/layouts/settings/toggles.py:98 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:98 #, python-format msgid "Aggressive" msgstr "Agresivo" -#: selfdrive/ui/layouts/onboarding.py:116 +#: openpilot/selfdrive/ui/layouts/onboarding.py:120 #, python-format msgid "Agree" msgstr "Aceptar" -#: selfdrive/ui/layouts/settings/toggles.py:70 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:70 #, python-format msgid "Always-On Driver Monitoring" msgstr "Supervisión del conductor siempre activa" -#: selfdrive/ui/layouts/settings/toggles.py:186 -#, python-format -msgid "" -"An alpha version of openpilot longitudinal control can be tested, along with " -"Experimental mode, on non-release branches." -msgstr "" -"Se puede probar una versión alpha del control longitudinal de openpilot, " -"junto con el modo Experimental, en ramas que no son de lanzamiento." - -#: selfdrive/ui/layouts/settings/device.py:187 +#: openpilot/selfdrive/ui/layouts/settings/device.py:183 #, python-format msgid "Are you sure you want to power off?" msgstr "¿Seguro que quieres apagar?" -#: selfdrive/ui/layouts/settings/device.py:175 +#: openpilot/selfdrive/ui/layouts/settings/device.py:171 #, python-format msgid "Are you sure you want to reboot?" msgstr "¿Seguro que quieres reiniciar?" -#: selfdrive/ui/layouts/settings/device.py:119 +#: openpilot/selfdrive/ui/layouts/settings/device.py:111 #, python-format msgid "Are you sure you want to reset calibration?" msgstr "¿Seguro que quieres restablecer la calibración?" -#: selfdrive/ui/layouts/settings/software.py:163 +#: openpilot/selfdrive/ui/layouts/settings/software.py:173 #, python-format msgid "Are you sure you want to uninstall?" msgstr "¿Seguro que quieres desinstalar?" -#: system/ui/widgets/network.py:99 selfdrive/ui/layouts/onboarding.py:147 +#: system/ui/widgets/network.py:96 +#: openpilot/selfdrive/ui/layouts/onboarding.py:151 #, python-format msgid "Back" msgstr "Atrás" -#: selfdrive/ui/widgets/prime.py:38 +#: openpilot/selfdrive/ui/widgets/prime.py:38 #, python-format msgid "Become a comma prime member at connect.comma.ai" msgstr "Hazte miembro de comma prime en connect.comma.ai" -#: selfdrive/ui/widgets/pairing_dialog.py:130 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:119 #, python-format msgid "Bookmark connect.comma.ai to your home screen to use it like an app" -msgstr "" -"Añade connect.comma.ai a tu pantalla de inicio para usarlo como una app" +msgstr "Añade connect.comma.ai a tu pantalla de inicio para usarlo como una app" -#: selfdrive/ui/layouts/settings/device.py:68 +#: openpilot/selfdrive/ui/layouts/settings/device.py:66 #, python-format msgid "CHANGE" msgstr "CAMBIAR" -#: selfdrive/ui/layouts/settings/software.py:50 -#: selfdrive/ui/layouts/settings/software.py:107 -#: selfdrive/ui/layouts/settings/software.py:118 -#: selfdrive/ui/layouts/settings/software.py:147 +#: openpilot/selfdrive/ui/layouts/settings/software.py:157 +#: openpilot/selfdrive/ui/layouts/settings/software.py:57 +#: openpilot/selfdrive/ui/layouts/settings/software.py:117 +#: openpilot/selfdrive/ui/layouts/settings/software.py:128 #, python-format msgid "CHECK" msgstr "COMPROBAR" -#: selfdrive/ui/widgets/exp_mode_button.py:50 +#: openpilot/selfdrive/ui/widgets/exp_mode_button.py:51 #, python-format msgid "CHILL MODE ON" msgstr "MODO CHILL ACTIVADO" -#: system/ui/widgets/network.py:155 selfdrive/ui/layouts/sidebar.py:73 -#: selfdrive/ui/layouts/sidebar.py:134 selfdrive/ui/layouts/sidebar.py:136 -#: selfdrive/ui/layouts/sidebar.py:138 +#: system/ui/widgets/network.py:152 +#: openpilot/selfdrive/ui/layouts/sidebar.py:73 +#: openpilot/selfdrive/ui/layouts/sidebar.py:134 +#: openpilot/selfdrive/ui/layouts/sidebar.py:136 +#: openpilot/selfdrive/ui/layouts/sidebar.py:138 #, python-format msgid "CONNECT" msgstr "CONECTAR" -#: system/ui/widgets/network.py:369 +#: system/ui/widgets/network.py:376 #, python-format msgid "CONNECTING..." msgstr "CONECTAR" -#: system/ui/widgets/confirm_dialog.py:23 system/ui/widgets/option_dialog.py:35 -#: system/ui/widgets/keyboard.py:81 system/ui/widgets/network.py:318 +#: system/ui/widgets/network.py:326 +#: system/ui/widgets/confirm_dialog.py:24 +#: system/ui/widgets/option_dialog.py:36 +#: system/ui/widgets/keyboard.py:83 #, python-format msgid "Cancel" msgstr "" -#: system/ui/widgets/network.py:134 +#: system/ui/widgets/network.py:131 #, python-format msgid "Cellular Metered" msgstr "" -#: selfdrive/ui/layouts/settings/device.py:68 +#: openpilot/selfdrive/ui/layouts/settings/device.py:66 #, python-format msgid "Change Language" msgstr "Cambiar idioma" -#: selfdrive/ui/layouts/settings/toggles.py:125 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:125 #, python-format msgid "Changing this setting will restart openpilot if the car is powered on." -msgstr "" -" Cambiar esta configuración reiniciará openpilot si el coche está encendido." +msgstr " Cambiar esta configuración reiniciará openpilot si el coche está encendido." -#: selfdrive/ui/widgets/pairing_dialog.py:129 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:118 #, python-format msgid "Click \"add new device\" and scan the QR code on the right" -msgstr "" -"Haz clic en \"añadir nuevo dispositivo\" y escanea el código QR de la derecha" +msgstr "Haz clic en \"añadir nuevo dispositivo\" y escanea el código QR de la derecha" -#: selfdrive/ui/widgets/offroad_alerts.py:104 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:104 #, python-format msgid "Close" msgstr "Cerrar" -#: selfdrive/ui/layouts/settings/software.py:49 +#: openpilot/selfdrive/ui/layouts/settings/software.py:56 #, python-format msgid "Current Version" msgstr "Versión actual" -#: selfdrive/ui/layouts/settings/software.py:110 +#: openpilot/selfdrive/ui/layouts/settings/software.py:120 #, python-format msgid "DOWNLOAD" msgstr "DESCARGAR" -#: selfdrive/ui/layouts/onboarding.py:115 +#: openpilot/selfdrive/ui/layouts/onboarding.py:119 #, python-format msgid "Decline" msgstr "Rechazar" -#: selfdrive/ui/layouts/onboarding.py:148 +#: openpilot/selfdrive/ui/layouts/onboarding.py:152 #, python-format msgid "Decline, uninstall openpilot" msgstr "Rechazar, desinstalar openpilot" -#: selfdrive/ui/layouts/settings/settings.py:67 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:64 msgid "Developer" msgstr "Desarrollador" -#: selfdrive/ui/layouts/settings/settings.py:62 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:59 msgid "Device" msgstr "Dispositivo" -#: selfdrive/ui/layouts/settings/toggles.py:58 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:58 #, python-format msgid "Disengage on Accelerator Pedal" msgstr "Desactivar con el pedal del acelerador" -#: selfdrive/ui/layouts/settings/device.py:184 +#: openpilot/selfdrive/ui/layouts/settings/device.py:176 #, python-format msgid "Disengage to Power Off" msgstr "Desactivar para apagar" -#: selfdrive/ui/layouts/settings/device.py:172 +#: openpilot/selfdrive/ui/layouts/settings/device.py:164 #, python-format msgid "Disengage to Reboot" msgstr "Desactivar para reiniciar" -#: selfdrive/ui/layouts/settings/device.py:103 +#: openpilot/selfdrive/ui/layouts/settings/device.py:95 #, python-format msgid "Disengage to Reset Calibration" msgstr "Desactivar para restablecer la calibración" -#: selfdrive/ui/layouts/settings/toggles.py:32 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:32 msgid "Display speed in km/h instead of mph." msgstr "Mostrar la velocidad en km/h en lugar de mph." -#: selfdrive/ui/layouts/settings/device.py:59 +#: openpilot/selfdrive/ui/layouts/settings/device.py:57 #, python-format msgid "Dongle ID" msgstr "ID del dongle" -#: selfdrive/ui/layouts/settings/software.py:50 +#: openpilot/selfdrive/ui/layouts/settings/software.py:57 #, python-format msgid "Download" msgstr "Descargar" -#: selfdrive/ui/layouts/settings/device.py:62 +#: openpilot/selfdrive/ui/layouts/settings/device.py:60 #, python-format msgid "Driver Camera" msgstr "Cámara del conductor" -#: selfdrive/ui/layouts/settings/toggles.py:96 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:96 #, python-format msgid "Driving Personality" msgstr "Estilo de conducción" -#: system/ui/widgets/network.py:123 system/ui/widgets/network.py:139 +#: system/ui/widgets/network.py:120 +#: system/ui/widgets/network.py:136 #, python-format msgid "EDIT" msgstr "" -#: selfdrive/ui/layouts/sidebar.py:138 +#: openpilot/selfdrive/ui/layouts/sidebar.py:138 msgid "ERROR" msgstr "ERROR" -#: selfdrive/ui/layouts/sidebar.py:45 +#: openpilot/selfdrive/ui/layouts/sidebar.py:45 msgid "ETH" msgstr "ETH" -#: selfdrive/ui/widgets/exp_mode_button.py:50 +#: openpilot/selfdrive/ui/widgets/exp_mode_button.py:51 #, python-format msgid "EXPERIMENTAL MODE ON" msgstr "MODO EXPERIMENTAL ACTIVADO" -#: selfdrive/ui/layouts/settings/developer.py:166 -#: selfdrive/ui/layouts/settings/toggles.py:228 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:229 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:180 #, python-format msgid "Enable" msgstr "Activar" -#: selfdrive/ui/layouts/settings/developer.py:39 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:39 #, python-format msgid "Enable ADB" msgstr "Activar ADB" -#: selfdrive/ui/layouts/settings/toggles.py:64 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:64 #, python-format msgid "Enable Lane Departure Warnings" msgstr "Activar advertencias de salida de carril" -#: system/ui/widgets/network.py:129 +#: system/ui/widgets/network.py:126 #, python-format msgid "Enable Roaming" msgstr "Activar openpilot" -#: selfdrive/ui/layouts/settings/developer.py:48 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:48 #, python-format msgid "Enable SSH" msgstr "Activar SSH" -#: system/ui/widgets/network.py:120 +#: system/ui/widgets/network.py:117 #, python-format msgid "Enable Tethering" msgstr "Activar advertencias de salida de carril" -#: selfdrive/ui/layouts/settings/toggles.py:30 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:30 msgid "Enable driver monitoring even when openpilot is not engaged." -msgstr "" -"Activar la supervisión del conductor incluso cuando openpilot no esté " -"activado." +msgstr "Activar la supervisión del conductor incluso cuando openpilot no esté activado." -#: selfdrive/ui/layouts/settings/toggles.py:46 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:46 #, python-format msgid "Enable openpilot" msgstr "Activar openpilot" -#: selfdrive/ui/layouts/settings/toggles.py:189 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:190 #, python-format -msgid "" -"Enable the openpilot longitudinal control (alpha) toggle to allow " -"Experimental mode." -msgstr "" -"Activa el interruptor de control longitudinal de openpilot (alpha) para " -"permitir el modo Experimental." +msgid "Enable the openpilot longitudinal control (alpha) toggle to allow Experimental mode." +msgstr "Activa el interruptor de control longitudinal de openpilot (alpha) para permitir el modo Experimental." -#: system/ui/widgets/network.py:204 +#: system/ui/widgets/network.py:201 #, python-format msgid "Enter APN" msgstr "" -#: system/ui/widgets/network.py:241 +#: system/ui/widgets/network.py:243 #, python-format msgid "Enter SSID" msgstr "" -#: system/ui/widgets/network.py:254 +#: system/ui/widgets/network.py:257 #, python-format msgid "Enter new tethering password" msgstr "" -#: system/ui/widgets/network.py:237 system/ui/widgets/network.py:314 +#: system/ui/widgets/network.py:238 +#: system/ui/widgets/network.py:320 #, python-format msgid "Enter password" msgstr "" -#: selfdrive/ui/widgets/ssh_key.py:89 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:89 #, python-format msgid "Enter your GitHub username" msgstr "Introduce tu nombre de usuario de GitHub" -#: system/ui/widgets/list_view.py:123 system/ui/widgets/list_view.py:160 +#: system/ui/widgets/list_view.py:123 +#: system/ui/widgets/list_view.py:160 #, python-format msgid "Error" msgstr "" -#: selfdrive/ui/layouts/settings/toggles.py:52 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:52 #, python-format msgid "Experimental Mode" msgstr "Modo experimental" -#: selfdrive/ui/layouts/settings/toggles.py:181 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:182 #, python-format -msgid "" -"Experimental mode is currently unavailable on this car since the car's stock " -"ACC is used for longitudinal control." -msgstr "" -"El modo experimental no está disponible actualmente en este coche, ya que se " -"usa el ACC de fábrica para el control longitudinal." +msgid "Experimental mode is currently unavailable on this car since the car's stock ACC is used for longitudinal control." +msgstr "El modo experimental no está disponible actualmente en este coche, ya que se usa el ACC de fábrica para el control longitudinal." -#: system/ui/widgets/network.py:373 +#: system/ui/widgets/network.py:380 #, python-format msgid "FORGETTING..." msgstr "" -#: selfdrive/ui/widgets/setup.py:44 +#: openpilot/selfdrive/ui/widgets/setup.py:43 #, python-format msgid "Finish Setup" msgstr "Finalizar configuración" -#: selfdrive/ui/layouts/settings/settings.py:66 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:63 msgid "Firehose" msgstr "Firehose" -#: selfdrive/ui/layouts/settings/firehose.py:18 +#: openpilot/selfdrive/ui/layouts/settings/firehose.py:10 msgid "Firehose Mode" msgstr "Modo Firehose" -#: selfdrive/ui/layouts/settings/firehose.py:25 -msgid "" -"For maximum effectiveness, bring your device inside and connect to a good " -"USB-C adapter and Wi-Fi weekly.\n" -"\n" -"Firehose Mode can also work while you're driving if connected to a hotspot " -"or unlimited SIM card.\n" -"\n" -"\n" -"Frequently Asked Questions\n" -"\n" -"Does it matter how or where I drive? Nope, just drive as you normally " -"would.\n" -"\n" -"Do all of my segments get pulled in Firehose Mode? No, we selectively pull a " -"subset of your segments.\n" -"\n" -"What's a good USB-C adapter? Any fast phone or laptop charger should be " -"fine.\n" -"\n" -"Does it matter which software I run? Yes, only upstream openpilot (and " -"particular forks) are able to be used for training." -msgstr "" -"Para la máxima efectividad, lleva tu dispositivo al interior y conéctalo " -"semanalmente a un buen adaptador USB‑C y Wi‑Fi.\n" -"\n" -"El Modo Firehose también puede funcionar mientras conduces si está conectado " -"a un hotspot o a una SIM ilimitada.\n" -"\n" -"\n" -"Preguntas frecuentes\n" -"\n" -"¿Importa cómo o dónde conduzco? No, conduce como normalmente lo harías.\n" -"\n" -"¿Se suben todos mis segmentos en el Modo Firehose? No, seleccionamos un " -"subconjunto de tus segmentos.\n" -"\n" -"¿Qué es un buen adaptador USB‑C? Cualquier cargador rápido de teléfono o " -"laptop sirve.\n" -"\n" -"¿Importa qué software ejecuto? Sí, solo openpilot upstream (y forks " -"particulares) pueden usarse para entrenamiento." - -#: system/ui/widgets/network.py:318 system/ui/widgets/network.py:451 +#: system/ui/widgets/network.py:458 +#: system/ui/widgets/network.py:326 #, python-format msgid "Forget" msgstr "" -#: system/ui/widgets/network.py:319 +#: system/ui/widgets/network.py:327 #, python-format msgid "Forget Wi-Fi Network \"{}\"?" msgstr "" -#: selfdrive/ui/layouts/sidebar.py:71 selfdrive/ui/layouts/sidebar.py:125 +#: openpilot/selfdrive/ui/layouts/sidebar.py:71 +#: openpilot/selfdrive/ui/layouts/sidebar.py:125 msgid "GOOD" msgstr "BUENO" -#: selfdrive/ui/widgets/pairing_dialog.py:128 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:117 #, python-format msgid "Go to https://connect.comma.ai on your phone" msgstr "Ve a https://connect.comma.ai en tu teléfono" -#: selfdrive/ui/layouts/sidebar.py:129 +#: openpilot/selfdrive/ui/layouts/sidebar.py:129 msgid "HIGH" msgstr "ALTO" -#: system/ui/widgets/network.py:155 +#: system/ui/widgets/network.py:152 #, python-format msgid "Hidden Network" msgstr "Red" -#: selfdrive/ui/layouts/settings/firehose.py:140 -#, python-format -msgid "INACTIVE: connect to an unmetered network" -msgstr "INACTIVO: conéctate a una red sin límites" - -#: selfdrive/ui/layouts/settings/software.py:53 -#: selfdrive/ui/layouts/settings/software.py:136 +#: openpilot/selfdrive/ui/layouts/settings/software.py:60 +#: openpilot/selfdrive/ui/layouts/settings/software.py:146 #, python-format msgid "INSTALL" msgstr "INSTALAR" -#: system/ui/widgets/network.py:150 +#: system/ui/widgets/network.py:147 #, python-format msgid "IP Address" msgstr "" -#: selfdrive/ui/layouts/settings/software.py:53 +#: openpilot/selfdrive/ui/layouts/settings/software.py:60 #, python-format msgid "Install Update" msgstr "Instalar actualización" -#: selfdrive/ui/layouts/settings/developer.py:56 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:56 #, python-format msgid "Joystick Debug Mode" msgstr "Modo de depuración de joystick" -#: selfdrive/ui/widgets/ssh_key.py:29 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:29 msgid "LOADING" msgstr "CARGANDO" -#: selfdrive/ui/layouts/sidebar.py:48 +#: openpilot/selfdrive/ui/layouts/sidebar.py:48 msgid "LTE" msgstr "LTE" -#: selfdrive/ui/layouts/settings/developer.py:64 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:64 #, python-format msgid "Longitudinal Maneuver Mode" msgstr "Modo de maniobra longitudinal" -#: selfdrive/ui/onroad/hud_renderer.py:148 +#: openpilot/selfdrive/ui/onroad/hud_renderer.py:148 #, python-format msgid "MAX" msgstr "MÁX" -#: selfdrive/ui/widgets/setup.py:75 +#: openpilot/selfdrive/ui/widgets/setup.py:74 #, python-format -msgid "" -"Maximize your training data uploads to improve openpilot's driving models." -msgstr "" -"Maximiza tus cargas de datos de entrenamiento para mejorar los modelos de " -"conducción de openpilot." +msgid "Maximize your training data uploads to improve openpilot's driving models." +msgstr "Maximiza tus cargas de datos de entrenamiento para mejorar los modelos de conducción de openpilot." -#: selfdrive/ui/layouts/settings/device.py:59 -#: selfdrive/ui/layouts/settings/device.py:60 +#: openpilot/selfdrive/ui/layouts/settings/device.py:57 +#: openpilot/selfdrive/ui/layouts/settings/device.py:58 #, python-format msgid "N/A" msgstr "" -#: selfdrive/ui/layouts/sidebar.py:142 +#: openpilot/selfdrive/ui/layouts/sidebar.py:142 msgid "NO" msgstr "NO" -#: selfdrive/ui/layouts/settings/settings.py:63 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:60 msgid "Network" msgstr "Red" -#: selfdrive/ui/widgets/ssh_key.py:114 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:115 #, python-format msgid "No SSH keys found" msgstr "No se encontraron claves SSH" -#: selfdrive/ui/widgets/ssh_key.py:126 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:127 #, python-format msgid "No SSH keys found for user '{}'" msgstr "No se encontraron claves SSH para el usuario '{username}'" -#: selfdrive/ui/widgets/offroad_alerts.py:320 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:321 #, python-format msgid "No release notes available." msgstr "No hay notas de versión disponibles." -#: selfdrive/ui/layouts/sidebar.py:73 selfdrive/ui/layouts/sidebar.py:134 +#: openpilot/selfdrive/ui/layouts/sidebar.py:73 +#: openpilot/selfdrive/ui/layouts/sidebar.py:134 msgid "OFFLINE" msgstr "SIN CONEXIÓN" -#: system/ui/widgets/html_render.py:263 system/ui/widgets/confirm_dialog.py:93 -#: selfdrive/ui/layouts/sidebar.py:127 +#: system/ui/widgets/confirm_dialog.py:93 +#: system/ui/widgets/html_render.py:263 +#: openpilot/selfdrive/ui/layouts/sidebar.py:127 #, python-format msgid "OK" msgstr "OK" -#: selfdrive/ui/layouts/sidebar.py:72 selfdrive/ui/layouts/sidebar.py:136 -#: selfdrive/ui/layouts/sidebar.py:144 +#: openpilot/selfdrive/ui/layouts/sidebar.py:72 +#: openpilot/selfdrive/ui/layouts/sidebar.py:144 +#: openpilot/selfdrive/ui/layouts/sidebar.py:136 msgid "ONLINE" msgstr "EN LÍNEA" -#: selfdrive/ui/widgets/setup.py:20 +#: openpilot/selfdrive/ui/widgets/setup.py:19 #, python-format msgid "Open" msgstr "Abrir" -#: selfdrive/ui/layouts/settings/device.py:48 +#: openpilot/selfdrive/ui/layouts/settings/device.py:45 #, python-format msgid "PAIR" msgstr "EMPAREJAR" -#: selfdrive/ui/layouts/sidebar.py:142 +#: openpilot/selfdrive/ui/layouts/sidebar.py:142 msgid "PANDA" msgstr "PANDA" -#: selfdrive/ui/layouts/settings/device.py:62 +#: openpilot/selfdrive/ui/layouts/settings/device.py:60 #, python-format msgid "PREVIEW" msgstr "VISTA PREVIA" -#: selfdrive/ui/widgets/prime.py:44 +#: openpilot/selfdrive/ui/widgets/prime.py:44 #, python-format msgid "PRIME FEATURES:" msgstr "FUNCIONES PRIME:" -#: selfdrive/ui/layouts/settings/device.py:48 +#: openpilot/selfdrive/ui/layouts/settings/device.py:45 #, python-format msgid "Pair Device" msgstr "Emparejar dispositivo" -#: selfdrive/ui/widgets/setup.py:19 +#: openpilot/selfdrive/ui/widgets/setup.py:18 #, python-format msgid "Pair device" msgstr "Emparejar dispositivo" -#: selfdrive/ui/widgets/pairing_dialog.py:103 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:92 #, python-format msgid "Pair your device to your comma account" msgstr "Empareja tu dispositivo con tu cuenta de comma" -#: selfdrive/ui/widgets/setup.py:48 selfdrive/ui/layouts/settings/device.py:24 +#: openpilot/selfdrive/ui/widgets/setup.py:47 +#: openpilot/selfdrive/ui/layouts/settings/device.py:23 #, python-format -msgid "" -"Pair your device with comma connect (connect.comma.ai) and claim your comma " -"prime offer." -msgstr "" -"Empareja tu dispositivo con comma connect (connect.comma.ai) y reclama tu " -"oferta de comma prime." +msgid "Pair your device with comma connect (connect.comma.ai) and claim your comma prime offer." +msgstr "Empareja tu dispositivo con comma connect (connect.comma.ai) y reclama tu oferta de comma prime." -#: selfdrive/ui/widgets/setup.py:91 +#: openpilot/selfdrive/ui/widgets/setup.py:91 #, python-format msgid "Please connect to Wi-Fi to complete initial pairing" msgstr "Conéctate a Wi‑Fi para completar el emparejamiento inicial" -#: selfdrive/ui/layouts/settings/device.py:55 -#: selfdrive/ui/layouts/settings/device.py:187 +#: openpilot/selfdrive/ui/layouts/settings/device.py:183 +#: openpilot/selfdrive/ui/layouts/settings/device.py:53 #, python-format msgid "Power Off" msgstr "Apagar" -#: system/ui/widgets/network.py:144 +#: system/ui/widgets/network.py:141 #, python-format msgid "Prevent large data uploads when on a metered Wi-Fi connection" msgstr "" -#: system/ui/widgets/network.py:135 +#: system/ui/widgets/network.py:132 #, python-format msgid "Prevent large data uploads when on a metered cellular connection" msgstr "" -#: selfdrive/ui/layouts/settings/device.py:25 -msgid "" -"Preview the driver facing camera to ensure that driver monitoring has good " -"visibility. (vehicle must be off)" -msgstr "" -"Previsualiza la cámara hacia el conductor para asegurarte de que la " -"supervisión del conductor tenga buena visibilidad. (el vehículo debe estar " -"apagado)" +#: openpilot/selfdrive/ui/layouts/settings/device.py:24 +msgid "Preview the driver facing camera to ensure that driver monitoring has good visibility. (vehicle must be off)" +msgstr "Previsualiza la cámara hacia el conductor para asegurarte de que la supervisión del conductor tenga buena visibilidad. (el vehículo debe estar apagado)" -#: selfdrive/ui/widgets/pairing_dialog.py:161 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:150 #, python-format msgid "QR Code Error" msgstr "Error de código QR" -#: selfdrive/ui/widgets/ssh_key.py:31 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:31 msgid "REMOVE" msgstr "ELIMINAR" -#: selfdrive/ui/layouts/settings/device.py:51 +#: openpilot/selfdrive/ui/layouts/settings/device.py:49 #, python-format msgid "RESET" msgstr "RESTABLECER" -#: selfdrive/ui/layouts/settings/device.py:65 +#: openpilot/selfdrive/ui/layouts/settings/device.py:63 #, python-format msgid "REVIEW" msgstr "REVISAR" -#: selfdrive/ui/layouts/settings/device.py:55 -#: selfdrive/ui/layouts/settings/device.py:175 +#: openpilot/selfdrive/ui/layouts/settings/device.py:171 +#: openpilot/selfdrive/ui/layouts/settings/device.py:53 #, python-format msgid "Reboot" msgstr "Reiniciar" -#: selfdrive/ui/onroad/alert_renderer.py:66 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:66 #, python-format msgid "Reboot Device" msgstr "Reiniciar dispositivo" -#: selfdrive/ui/widgets/offroad_alerts.py:112 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:112 #, python-format msgid "Reboot and Update" msgstr "Reiniciar y actualizar" -#: selfdrive/ui/layouts/settings/toggles.py:27 -msgid "" -"Receive alerts to steer back into the lane when your vehicle drifts over a " -"detected lane line without a turn signal activated while driving over 31 mph " -"(50 km/h)." -msgstr "" -"Recibe alertas para volver al carril cuando tu vehículo se desvíe sobre una " -"línea de carril detectada sin la direccional activada mientras conduces a " -"más de 31 mph (50 km/h)." - -#: selfdrive/ui/layouts/settings/toggles.py:76 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:76 #, python-format msgid "Record and Upload Driver Camera" msgstr "Grabar y subir cámara del conductor" -#: selfdrive/ui/layouts/settings/toggles.py:82 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:82 #, python-format msgid "Record and Upload Microphone Audio" msgstr "Grabar y subir audio del micrófono" -#: selfdrive/ui/layouts/settings/toggles.py:33 -msgid "" -"Record and store microphone audio while driving. The audio will be included " -"in the dashcam video in comma connect." -msgstr "" -"Grabar y almacenar audio del micrófono mientras conduces. El audio se " -"incluirá en el video de la dashcam en comma connect." +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:33 +msgid "Record and store microphone audio while driving. The audio will be included in the dashcam video in comma connect." +msgstr "Grabar y almacenar audio del micrófono mientras conduces. El audio se incluirá en el video de la dashcam en comma connect." -#: selfdrive/ui/layouts/settings/device.py:67 +#: openpilot/selfdrive/ui/layouts/settings/device.py:65 #, python-format msgid "Regulatory" msgstr "Reglamentario" -#: selfdrive/ui/layouts/settings/toggles.py:98 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:98 #, python-format msgid "Relaxed" msgstr "Relajado" -#: selfdrive/ui/widgets/prime.py:47 +#: openpilot/selfdrive/ui/widgets/prime.py:47 #, python-format msgid "Remote access" msgstr "Acceso remoto" -#: selfdrive/ui/widgets/prime.py:47 +#: openpilot/selfdrive/ui/widgets/prime.py:47 #, python-format msgid "Remote snapshots" msgstr "Capturas remotas" -#: selfdrive/ui/widgets/ssh_key.py:123 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:124 #, python-format msgid "Request timed out" msgstr "Se agotó el tiempo de espera de la solicitud" -#: selfdrive/ui/layouts/settings/device.py:119 +#: openpilot/selfdrive/ui/layouts/settings/device.py:111 #, python-format msgid "Reset" msgstr "Restablecer" -#: selfdrive/ui/layouts/settings/device.py:51 +#: openpilot/selfdrive/ui/layouts/settings/device.py:49 #, python-format msgid "Reset Calibration" msgstr "Restablecer calibración" -#: selfdrive/ui/layouts/settings/device.py:65 +#: openpilot/selfdrive/ui/layouts/settings/device.py:63 #, python-format msgid "Review Training Guide" msgstr "Revisar guía de entrenamiento" -#: selfdrive/ui/layouts/settings/device.py:27 +#: openpilot/selfdrive/ui/layouts/settings/device.py:26 msgid "Review the rules, features, and limitations of openpilot" msgstr "Revisa las reglas, funciones y limitaciones de openpilot" -#: selfdrive/ui/layouts/settings/software.py:61 +#: openpilot/selfdrive/ui/layouts/settings/software.py:68 #, python-format msgid "SELECT" msgstr "" -#: selfdrive/ui/layouts/settings/developer.py:53 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:53 #, python-format msgid "SSH Keys" msgstr "" -#: system/ui/widgets/network.py:310 +#: system/ui/widgets/network.py:316 #, python-format msgid "Scanning Wi-Fi networks..." msgstr "" -#: system/ui/widgets/option_dialog.py:36 +#: system/ui/widgets/option_dialog.py:37 #, python-format msgid "Select" msgstr "" -#: selfdrive/ui/layouts/settings/software.py:183 +#: openpilot/selfdrive/ui/layouts/settings/software.py:203 #, python-format msgid "Select a branch" msgstr "" -#: selfdrive/ui/layouts/settings/device.py:91 +#: openpilot/selfdrive/ui/layouts/settings/device.py:89 #, python-format msgid "Select a language" msgstr "Selecciona un idioma" -#: selfdrive/ui/layouts/settings/device.py:60 +#: openpilot/selfdrive/ui/layouts/settings/device.py:58 #, python-format msgid "Serial" msgstr "Número de serie" -#: selfdrive/ui/widgets/offroad_alerts.py:106 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:106 #, python-format msgid "Snooze Update" msgstr "Posponer actualización" -#: selfdrive/ui/layouts/settings/settings.py:65 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:62 msgid "Software" msgstr "Software" -#: selfdrive/ui/layouts/settings/toggles.py:98 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:98 #, python-format msgid "Standard" msgstr "Estándar" -#: selfdrive/ui/layouts/settings/toggles.py:22 -msgid "" -"Standard is recommended. In aggressive mode, openpilot will follow lead cars " -"closer and be more aggressive with the gas and brake. In relaxed mode " -"openpilot will stay further away from lead cars. On supported cars, you can " -"cycle through these personalities with your steering wheel distance button." -msgstr "" -"Se recomienda Estándar. En modo agresivo, openpilot seguirá más de cerca a " -"los coches delanteros y será más agresivo con el acelerador y el freno. En " -"modo relajado, openpilot se mantendrá más lejos de los coches delanteros. En " -"coches compatibles, puedes cambiar entre estas personalidades con el botón " -"de distancia del volante." - -#: selfdrive/ui/onroad/alert_renderer.py:59 -#: selfdrive/ui/onroad/alert_renderer.py:65 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:59 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:65 #, python-format msgid "System Unresponsive" msgstr "Sistema sin respuesta" -#: selfdrive/ui/onroad/alert_renderer.py:58 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:58 #, python-format msgid "TAKE CONTROL IMMEDIATELY" msgstr "TOME EL CONTROL INMEDIATAMENTE" -#: selfdrive/ui/layouts/sidebar.py:71 selfdrive/ui/layouts/sidebar.py:125 -#: selfdrive/ui/layouts/sidebar.py:127 selfdrive/ui/layouts/sidebar.py:129 +#: openpilot/selfdrive/ui/layouts/sidebar.py:71 +#: openpilot/selfdrive/ui/layouts/sidebar.py:125 +#: openpilot/selfdrive/ui/layouts/sidebar.py:127 +#: openpilot/selfdrive/ui/layouts/sidebar.py:129 msgid "TEMP" msgstr "TEMP" -#: selfdrive/ui/layouts/settings/software.py:61 +#: openpilot/selfdrive/ui/layouts/settings/software.py:68 #, python-format msgid "Target Branch" msgstr "" -#: system/ui/widgets/network.py:124 +#: system/ui/widgets/network.py:121 #, python-format msgid "Tethering Password" msgstr "" -#: selfdrive/ui/layouts/settings/settings.py:64 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:61 msgid "Toggles" msgstr "Interruptores" -#: selfdrive/ui/layouts/settings/software.py:72 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:79 +#, python-format +msgid "UI Debug Mode" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/software.py:79 #, python-format msgid "UNINSTALL" msgstr "DESINSTALAR" -#: selfdrive/ui/layouts/home.py:155 +#: openpilot/selfdrive/ui/layouts/home.py:155 #, python-format msgid "UPDATE" msgstr "ACTUALIZAR" -#: selfdrive/ui/layouts/settings/software.py:72 -#: selfdrive/ui/layouts/settings/software.py:163 +#: openpilot/selfdrive/ui/layouts/settings/software.py:173 +#: openpilot/selfdrive/ui/layouts/settings/software.py:79 #, python-format msgid "Uninstall" msgstr "Desinstalar" -#: selfdrive/ui/layouts/sidebar.py:117 +#: openpilot/selfdrive/ui/layouts/sidebar.py:117 msgid "Unknown" msgstr "Desconocido" -#: selfdrive/ui/layouts/settings/software.py:48 +#: openpilot/selfdrive/ui/layouts/settings/software.py:55 #, python-format msgid "Updates are only downloaded while the car is off." msgstr "Las actualizaciones solo se descargan cuando el coche está apagado." -#: selfdrive/ui/widgets/prime.py:33 +#: openpilot/selfdrive/ui/widgets/prime.py:33 #, python-format msgid "Upgrade Now" msgstr "Mejorar ahora" -#: selfdrive/ui/layouts/settings/toggles.py:31 -msgid "" -"Upload data from the driver facing camera and help improve the driver " -"monitoring algorithm." -msgstr "" -"Sube datos de la cámara orientada al conductor y ayuda a mejorar el " -"algoritmo de supervisión del conductor." +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:31 +msgid "Upload data from the driver facing camera and help improve the driver monitoring algorithm." +msgstr "Sube datos de la cámara orientada al conductor y ayuda a mejorar el algoritmo de supervisión del conductor." -#: selfdrive/ui/layouts/settings/toggles.py:88 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:88 #, python-format msgid "Use Metric System" msgstr "Usar sistema métrico" -#: selfdrive/ui/layouts/settings/toggles.py:17 -msgid "" -"Use the openpilot system for adaptive cruise control and lane keep driver " -"assistance. Your attention is required at all times to use this feature." -msgstr "" -"Usa el sistema openpilot para control de crucero adaptativo y asistencia de " -"mantenimiento de carril. Tu atención se requiere en todo momento para usar " -"esta función." - -#: selfdrive/ui/layouts/sidebar.py:72 selfdrive/ui/layouts/sidebar.py:144 +#: openpilot/selfdrive/ui/layouts/sidebar.py:72 +#: openpilot/selfdrive/ui/layouts/sidebar.py:144 msgid "VEHICLE" msgstr "VEHÍCULO" -#: selfdrive/ui/layouts/settings/device.py:67 +#: openpilot/selfdrive/ui/layouts/settings/device.py:65 #, python-format msgid "VIEW" msgstr "VER" -#: selfdrive/ui/onroad/alert_renderer.py:52 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:52 #, python-format msgid "Waiting to start" msgstr "Esperando para iniciar" -#: selfdrive/ui/layouts/settings/developer.py:19 -msgid "" -"Warning: This grants SSH access to all public keys in your GitHub settings. " -"Never enter a GitHub username other than your own. A comma employee will " -"NEVER ask you to add their GitHub username." -msgstr "" -"Advertencia: Esto otorga acceso SSH a todas las claves públicas en tu " -"configuración de GitHub. Nunca introduzcas un nombre de usuario de GitHub " -"que no sea el tuyo. Un empleado de comma NUNCA te pedirá que agregues su " -"nombre de usuario de GitHub." - -#: selfdrive/ui/layouts/onboarding.py:111 +#: openpilot/selfdrive/ui/layouts/onboarding.py:115 #, python-format msgid "Welcome to openpilot" msgstr "Bienvenido a openpilot" -#: selfdrive/ui/layouts/settings/toggles.py:20 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:20 msgid "When enabled, pressing the accelerator pedal will disengage openpilot." -msgstr "" -"Cuando está activado, al presionar el pedal del acelerador se desactivará " -"openpilot." +msgstr "Cuando está activado, al presionar el pedal del acelerador se desactivará openpilot." -#: selfdrive/ui/layouts/sidebar.py:44 +#: openpilot/selfdrive/ui/layouts/sidebar.py:44 msgid "Wi-Fi" msgstr "Wi‑Fi" -#: system/ui/widgets/network.py:144 +#: system/ui/widgets/network.py:141 #, python-format msgid "Wi-Fi Network Metered" msgstr "" -#: system/ui/widgets/network.py:314 +#: system/ui/widgets/network.py:320 #, python-format msgid "Wrong password" msgstr "" -#: selfdrive/ui/layouts/onboarding.py:145 +#: openpilot/selfdrive/ui/layouts/onboarding.py:149 #, python-format msgid "You must accept the Terms and Conditions in order to use openpilot." msgstr "Debes aceptar los Términos y Condiciones para poder usar openpilot." -#: selfdrive/ui/layouts/onboarding.py:112 +#: openpilot/selfdrive/ui/layouts/onboarding.py:116 #, python-format -msgid "" -"You must accept the Terms and Conditions to use openpilot. Read the latest " -"terms at https://comma.ai/terms before continuing." -msgstr "" -"Debes aceptar los Términos y Condiciones para usar openpilot. Lee los " -"términos más recientes en https://comma.ai/terms antes de continuar." +msgid "You must accept the Terms and Conditions to use openpilot. Read the latest terms at https://comma.ai/terms before continuing." +msgstr "Debes aceptar los Términos y Condiciones para usar openpilot. Lee los términos más recientes en https://comma.ai/terms antes de continuar." -#: selfdrive/ui/onroad/driver_camera_dialog.py:34 +#: openpilot/selfdrive/ui/onroad/driver_camera_dialog.py:38 #, python-format msgid "camera starting" msgstr "iniciando cámara" -#: selfdrive/ui/widgets/prime.py:63 +#: openpilot/selfdrive/ui/layouts/settings/software.py:19 +#, python-format +msgid "checking..." +msgstr "" + +#: openpilot/selfdrive/ui/widgets/prime.py:63 #, python-format msgid "comma prime" msgstr "comma prime" -#: system/ui/widgets/network.py:142 +#: system/ui/widgets/network.py:139 #, python-format msgid "default" msgstr "" -#: selfdrive/ui/layouts/settings/device.py:133 +#: openpilot/selfdrive/ui/layouts/settings/device.py:125 #, python-format msgid "down" msgstr "abajo" -#: selfdrive/ui/layouts/settings/software.py:106 +#: openpilot/selfdrive/ui/layouts/settings/software.py:20 +#, python-format +msgid "downloading..." +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/software.py:116 #, python-format msgid "failed to check for update" msgstr "Error al buscar actualizaciones" -#: system/ui/widgets/network.py:237 system/ui/widgets/network.py:314 +#: openpilot/selfdrive/ui/layouts/settings/software.py:21 +#, python-format +msgid "finalizing update..." +msgstr "" + +#: system/ui/widgets/network.py:238 +#: system/ui/widgets/network.py:321 #, python-format msgid "for \"{}\"" msgstr "" -#: selfdrive/ui/onroad/hud_renderer.py:177 +#: openpilot/selfdrive/ui/onroad/hud_renderer.py:177 #, python-format msgid "km/h" msgstr "km/h" -#: system/ui/widgets/network.py:204 +#: system/ui/widgets/network.py:201 #, python-format msgid "leave blank for automatic configuration" msgstr "" -#: selfdrive/ui/layouts/settings/device.py:134 +#: openpilot/selfdrive/ui/layouts/settings/device.py:126 #, python-format msgid "left" msgstr "izquierda" -#: system/ui/widgets/network.py:142 +#: system/ui/widgets/network.py:139 #, python-format msgid "metered" msgstr "" -#: selfdrive/ui/onroad/hud_renderer.py:177 +#: openpilot/selfdrive/ui/onroad/hud_renderer.py:177 #, python-format msgid "mph" msgstr "mph" -#: selfdrive/ui/layouts/settings/software.py:20 +#: openpilot/selfdrive/ui/layouts/settings/software.py:27 #, python-format msgid "never" msgstr "nunca" -#: selfdrive/ui/layouts/settings/software.py:31 +#: openpilot/selfdrive/ui/layouts/settings/software.py:38 #, python-format msgid "now" msgstr "ahora" -#: selfdrive/ui/layouts/settings/developer.py:71 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:71 #, python-format msgid "openpilot Longitudinal Control (Alpha)" msgstr "Control longitudinal de openpilot (Alpha)" -#: selfdrive/ui/onroad/alert_renderer.py:51 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:51 #, python-format msgid "openpilot Unavailable" msgstr "openpilot no disponible" -#: selfdrive/ui/layouts/settings/toggles.py:158 -#, python-format -msgid "" -"openpilot defaults to driving in chill mode. Experimental mode enables alpha-" -"level features that aren't ready for chill mode. Experimental features are " -"listed below:

End-to-End Longitudinal Control


Let the driving " -"model control the gas and brakes. openpilot will drive as it thinks a human " -"would, including stopping for red lights and stop signs. Since the driving " -"model decides the speed to drive, the set speed will only act as an upper " -"bound. This is an alpha quality feature; mistakes should be expected." -"

New Driving Visualization


The driving visualization will " -"transition to the road-facing wide-angle camera at low speeds to better show " -"some turns. The Experimental mode logo will also be shown in the top right " -"corner." -msgstr "" -"openpilot conduce por defecto en modo chill. El modo Experimental habilita " -"funciones de nivel alpha que no están listas para el modo chill. Las " -"funciones experimentales se enumeran a continuación:

Control " -"longitudinal de extremo a extremo


Deja que el modelo de conducción " -"controle el acelerador y los frenos. openpilot conducirá como piensa que lo " -"haría un humano, incluyendo detenerse en luces rojas y señales de alto. Dado " -"que el modelo decide la velocidad a la que conducir, la velocidad " -"establecida solo actuará como límite superior. Esta es una función de " -"calidad alpha; se deben esperar errores.

Nueva visualización de " -"conducción


La visualización de conducción hará la transición a la " -"cámara gran angular orientada a la carretera a bajas velocidades para " -"mostrar mejor algunos giros. El logotipo del modo Experimental también se " -"mostrará en la esquina superior derecha." - -#: selfdrive/ui/layouts/settings/device.py:165 -#, python-format -msgid "" -"openpilot is continuously calibrating, resetting is rarely required. " -"Resetting calibration will restart openpilot if the car is powered on." -msgstr "" -" Cambiar esta configuración reiniciará openpilot si el coche está encendido." - -#: selfdrive/ui/layouts/settings/firehose.py:20 -msgid "" -"openpilot learns to drive by watching humans, like you, drive.\n" -"\n" -"Firehose Mode allows you to maximize your training data uploads to improve " -"openpilot's driving models. More data means bigger models, which means " -"better Experimental Mode." -msgstr "" -"openpilot aprende a conducir observando a humanos, como tú, conducir.\n" -"\n" -"El Modo Firehose te permite maximizar tus cargas de datos de entrenamiento " -"para mejorar los modelos de conducción de openpilot. Más datos significan " -"modelos más grandes, lo que significa un mejor Modo Experimental." - -#: selfdrive/ui/layouts/settings/toggles.py:183 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:184 #, python-format msgid "openpilot longitudinal control may come in a future update." -msgstr "" -"El control longitudinal de openpilot podría llegar en una actualización " -"futura." +msgstr "El control longitudinal de openpilot podría llegar en una actualización futura." -#: selfdrive/ui/layouts/settings/device.py:26 -msgid "" -"openpilot requires the device to be mounted within 4° left or right and " -"within 5° up or 9° down." -msgstr "" -"openpilot requiere que el dispositivo esté montado dentro de 4° a izquierda " -"o derecha y dentro de 5° hacia arriba o 9° hacia abajo." +#: openpilot/selfdrive/ui/layouts/settings/device.py:25 +msgid "openpilot requires the device to be mounted within 4° left or right and within 5° up or 9° down." +msgstr "openpilot requiere que el dispositivo esté montado dentro de 4° a izquierda o derecha y dentro de 5° hacia arriba o 9° hacia abajo." -#: selfdrive/ui/layouts/settings/device.py:134 +#: openpilot/selfdrive/ui/layouts/settings/device.py:126 #, python-format msgid "right" msgstr "derecha" -#: system/ui/widgets/network.py:142 +#: system/ui/widgets/network.py:139 #, python-format msgid "unmetered" msgstr "" -#: selfdrive/ui/layouts/settings/device.py:133 +#: openpilot/selfdrive/ui/layouts/settings/device.py:125 #, python-format msgid "up" msgstr "arriba" -#: selfdrive/ui/layouts/settings/software.py:117 +#: openpilot/selfdrive/ui/layouts/settings/software.py:127 #, python-format msgid "up to date, last checked never" msgstr "actualizado, última comprobación: nunca" -#: selfdrive/ui/layouts/settings/software.py:115 +#: openpilot/selfdrive/ui/layouts/settings/software.py:125 #, python-format msgid "up to date, last checked {}" msgstr "actualizado, última comprobación: {}" -#: selfdrive/ui/layouts/settings/software.py:109 +#: openpilot/selfdrive/ui/layouts/settings/software.py:119 #, python-format msgid "update available" msgstr "actualización disponible" -#: selfdrive/ui/layouts/home.py:169 +#: openpilot/selfdrive/ui/layouts/home.py:169 #, python-format msgid "{} ALERT" msgid_plural "{} ALERTS" msgstr[0] "{} ALERTA" msgstr[1] "{} ALERTAS" -#: selfdrive/ui/layouts/settings/software.py:40 +#: openpilot/selfdrive/ui/layouts/settings/software.py:47 #, python-format msgid "{} day ago" msgid_plural "{} days ago" msgstr[0] "hace {} día" msgstr[1] "hace {} días" -#: selfdrive/ui/layouts/settings/software.py:37 +#: openpilot/selfdrive/ui/layouts/settings/software.py:44 #, python-format msgid "{} hour ago" msgid_plural "{} hours ago" msgstr[0] "hace {} hora" msgstr[1] "hace {} horas" -#: selfdrive/ui/layouts/settings/software.py:34 +#: openpilot/selfdrive/ui/layouts/settings/software.py:41 #, python-format msgid "{} minute ago" msgid_plural "{} minutes ago" msgstr[0] "hace {} minuto" msgstr[1] "hace {} minutos" -#: selfdrive/ui/layouts/settings/firehose.py:111 +#: openpilot/selfdrive/ui/layouts/settings/firehose.py:70 #, python-format msgid "{} segment of your driving is in the training dataset so far." msgid_plural "{} segments of your driving is in the training dataset so far." -msgstr[0] "" -"{} segmento de tu conducción está en el conjunto de entrenamiento hasta " -"ahora." -msgstr[1] "" -"{} segmentos de tu conducción están en el conjunto de entrenamiento hasta " -"ahora." +msgstr[0] "{} segmento de tu conducción está en el conjunto de entrenamiento hasta ahora." +msgstr[1] "{} segmentos de tu conducción están en el conjunto de entrenamiento hasta ahora." -#: selfdrive/ui/widgets/prime.py:62 +#: openpilot/selfdrive/ui/widgets/prime.py:62 #, python-format msgid "✓ SUBSCRIBED" msgstr "✓ SUSCRITO" -#: selfdrive/ui/widgets/setup.py:22 +#: openpilot/selfdrive/ui/widgets/setup.py:21 #, python-format msgid "🔥 Firehose Mode 🔥" msgstr "🔥 Modo Firehose 🔥" + diff --git a/selfdrive/ui/translations/app_fr.po b/selfdrive/ui/translations/app_fr.po index 409761588e..d3ce386bdc 100644 --- a/selfdrive/ui/translations/app_fr.po +++ b/selfdrive/ui/translations/app_fr.po @@ -18,1219 +18,1018 @@ msgstr "" "Plural-Forms: nplurals=2; plural=(n > 1);\n" "X-Generator: Poedit 3.8\n" -#: selfdrive/ui/layouts/settings/device.py:160 +#: openpilot/selfdrive/ui/layouts/settings/device.py:152 #, python-format msgid " Steering torque response calibration is complete." msgstr " L'étalonnage de la réponse du couple de direction est terminé." -#: selfdrive/ui/layouts/settings/device.py:158 +#: openpilot/selfdrive/ui/layouts/settings/device.py:150 #, python-format msgid " Steering torque response calibration is {}% complete." msgstr " L'étalonnage de la réponse du couple de direction est terminé à {}%." -#: selfdrive/ui/layouts/settings/device.py:133 +#: openpilot/selfdrive/ui/layouts/settings/device.py:125 #, python-format msgid " Your device is pointed {:.1f}° {} and {:.1f}° {}." msgstr " Votre appareil est orienté {:.1f}° {} et {:.1f}° {}." -#: selfdrive/ui/layouts/sidebar.py:43 +#: openpilot/selfdrive/ui/layouts/sidebar.py:43 msgid "--" msgstr "--" -#: selfdrive/ui/widgets/prime.py:47 +#: openpilot/selfdrive/ui/widgets/prime.py:47 #, python-format msgid "1 year of drive storage" msgstr "1 an de stockage de trajets" -#: selfdrive/ui/widgets/prime.py:47 +#: openpilot/selfdrive/ui/widgets/prime.py:47 #, python-format msgid "24/7 LTE connectivity" msgstr "Connexion LTE 24/7" -#: selfdrive/ui/layouts/sidebar.py:46 +#: openpilot/selfdrive/ui/layouts/sidebar.py:46 msgid "2G" msgstr "2G" -#: selfdrive/ui/layouts/sidebar.py:47 +#: openpilot/selfdrive/ui/layouts/sidebar.py:47 msgid "3G" msgstr "3G" -#: selfdrive/ui/layouts/sidebar.py:49 +#: openpilot/selfdrive/ui/layouts/sidebar.py:49 msgid "5G" msgstr "5G" -#: selfdrive/ui/layouts/settings/developer.py:23 -msgid "" -"WARNING: openpilot longitudinal control is in alpha for this car and will " -"disable Automatic Emergency Braking (AEB).

On this car, openpilot " -"defaults to the car's built-in ACC instead of openpilot's longitudinal " -"control. Enable this to switch to openpilot longitudinal control. Enabling " -"Experimental mode is recommended when enabling openpilot longitudinal " -"control alpha. Changing this setting will restart openpilot if the car is " -"powered on." -msgstr "" -"ATTENTION : le contrôle longitudinal openpilot est en alpha pour cette " -"voiture et désactivera le freinage d'urgence automatique (AEB).

Sur cette voiture, openpilot utilise par défaut le régulateur de " -"vitesse adaptatif intégré au véhicule plutôt que le contrôle longitudinal " -"d'openpilot. Activez ceci pour passer au contrôle longitudinal openpilot. Il " -"est recommandé d'activer le mode expérimental lors de l'activation du " -"contrôle longitudinal openpilot alpha." - -#: selfdrive/ui/layouts/settings/device.py:148 +#: openpilot/selfdrive/ui/layouts/settings/device.py:140 #, python-format msgid "

Steering lag calibration is complete." msgstr "

L'étalonnage du délai de réponse de la direction est terminé." -#: selfdrive/ui/layouts/settings/device.py:146 +#: openpilot/selfdrive/ui/layouts/settings/device.py:138 #, python-format msgid "

Steering lag calibration is {}% complete." -msgstr "" -"

L'étalonnage du délai de réponse de la direction est terminé à {}%." +msgstr "

L'étalonnage du délai de réponse de la direction est terminé à {}%." -#: selfdrive/ui/layouts/settings/firehose.py:138 -#, python-format -msgid "ACTIVE" -msgstr "ACTIF" - -#: selfdrive/ui/layouts/settings/developer.py:15 -msgid "" -"ADB (Android Debug Bridge) allows connecting to your device over USB or over " -"the network. See https://docs.comma.ai/how-to/connect-to-comma for more info." -msgstr "" -"ADB (Android Debug Bridge) permet de connecter votre appareil via USB ou via " -"le réseau. Voir https://docs.comma.ai/how-to/connect-to-comma pour plus " -"d'informations." - -#: selfdrive/ui/widgets/ssh_key.py:30 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:30 msgid "ADD" msgstr "AJOUTER" -#: system/ui/widgets/network.py:139 +#: system/ui/widgets/network.py:136 #, python-format msgid "APN Setting" msgstr "Paramètres APN" -#: selfdrive/ui/widgets/offroad_alerts.py:109 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:109 #, python-format msgid "Acknowledge Excessive Actuation" msgstr "Accuser réception d'actionnement excessif" -#: system/ui/widgets/network.py:74 system/ui/widgets/network.py:95 +#: system/ui/widgets/network.py:92 +#: system/ui/widgets/network.py:74 #, python-format msgid "Advanced" msgstr "Avancé" -#: selfdrive/ui/layouts/settings/toggles.py:98 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:98 #, python-format msgid "Aggressive" msgstr "Agressif" -#: selfdrive/ui/layouts/onboarding.py:116 +#: openpilot/selfdrive/ui/layouts/onboarding.py:120 #, python-format msgid "Agree" msgstr "Accepter" -#: selfdrive/ui/layouts/settings/toggles.py:70 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:70 #, python-format msgid "Always-On Driver Monitoring" msgstr "Surveillance continue du conducteur" -#: selfdrive/ui/layouts/settings/toggles.py:186 -#, python-format -msgid "" -"An alpha version of openpilot longitudinal control can be tested, along with " -"Experimental mode, on non-release branches." -msgstr "" -"Une version alpha du contrôle longitudinal openpilot peut être testée, avec " -"le mode expérimental, sur des branches non publiées." - -#: selfdrive/ui/layouts/settings/device.py:187 +#: openpilot/selfdrive/ui/layouts/settings/device.py:183 #, python-format msgid "Are you sure you want to power off?" msgstr "Êtes-vous sûr de vouloir éteindre ?" -#: selfdrive/ui/layouts/settings/device.py:175 +#: openpilot/selfdrive/ui/layouts/settings/device.py:171 #, python-format msgid "Are you sure you want to reboot?" msgstr "Êtes-vous sûr de vouloir redémarrer ?" -#: selfdrive/ui/layouts/settings/device.py:119 +#: openpilot/selfdrive/ui/layouts/settings/device.py:111 #, python-format msgid "Are you sure you want to reset calibration?" msgstr "Êtes-vous sûr de vouloir réinitialiser la calibration ?" -#: selfdrive/ui/layouts/settings/software.py:163 +#: openpilot/selfdrive/ui/layouts/settings/software.py:173 #, python-format msgid "Are you sure you want to uninstall?" msgstr "Êtes-vous sûr de vouloir désinstaller ?" -#: system/ui/widgets/network.py:99 selfdrive/ui/layouts/onboarding.py:147 +#: system/ui/widgets/network.py:96 +#: openpilot/selfdrive/ui/layouts/onboarding.py:151 #, python-format msgid "Back" msgstr "Retour" -#: selfdrive/ui/widgets/prime.py:38 +#: openpilot/selfdrive/ui/widgets/prime.py:38 #, python-format msgid "Become a comma prime member at connect.comma.ai" msgstr "Devenez membre comma prime sur connect.comma.ai" -#: selfdrive/ui/widgets/pairing_dialog.py:130 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:119 #, python-format msgid "Bookmark connect.comma.ai to your home screen to use it like an app" -msgstr "" -"Ajoutez connect.comma.ai à votre écran d'accueil pour l'utiliser comme une " -"application" +msgstr "Ajoutez connect.comma.ai à votre écran d'accueil pour l'utiliser comme une application" -#: selfdrive/ui/layouts/settings/device.py:68 +#: openpilot/selfdrive/ui/layouts/settings/device.py:66 #, python-format msgid "CHANGE" msgstr "CHANGER" -#: selfdrive/ui/layouts/settings/software.py:50 -#: selfdrive/ui/layouts/settings/software.py:107 -#: selfdrive/ui/layouts/settings/software.py:118 -#: selfdrive/ui/layouts/settings/software.py:147 +#: openpilot/selfdrive/ui/layouts/settings/software.py:157 +#: openpilot/selfdrive/ui/layouts/settings/software.py:57 +#: openpilot/selfdrive/ui/layouts/settings/software.py:117 +#: openpilot/selfdrive/ui/layouts/settings/software.py:128 #, python-format msgid "CHECK" msgstr "VÉRIFIER" -#: selfdrive/ui/widgets/exp_mode_button.py:50 +#: openpilot/selfdrive/ui/widgets/exp_mode_button.py:51 #, python-format msgid "CHILL MODE ON" msgstr "MODE CHILL ACTIVÉ" -#: system/ui/widgets/network.py:155 selfdrive/ui/layouts/sidebar.py:73 -#: selfdrive/ui/layouts/sidebar.py:134 selfdrive/ui/layouts/sidebar.py:136 -#: selfdrive/ui/layouts/sidebar.py:138 +#: system/ui/widgets/network.py:152 +#: openpilot/selfdrive/ui/layouts/sidebar.py:73 +#: openpilot/selfdrive/ui/layouts/sidebar.py:134 +#: openpilot/selfdrive/ui/layouts/sidebar.py:136 +#: openpilot/selfdrive/ui/layouts/sidebar.py:138 #, python-format msgid "CONNECT" msgstr "CONNECTER" -#: system/ui/widgets/network.py:369 +#: system/ui/widgets/network.py:376 #, python-format msgid "CONNECTING..." msgstr "CONNECTER..." -#: system/ui/widgets/confirm_dialog.py:23 system/ui/widgets/option_dialog.py:35 -#: system/ui/widgets/keyboard.py:81 system/ui/widgets/network.py:318 +#: system/ui/widgets/network.py:326 +#: system/ui/widgets/confirm_dialog.py:24 +#: system/ui/widgets/option_dialog.py:36 +#: system/ui/widgets/keyboard.py:83 #, python-format msgid "Cancel" msgstr "Annuler" -#: system/ui/widgets/network.py:134 +#: system/ui/widgets/network.py:131 #, python-format msgid "Cellular Metered" msgstr "Données cellulaire limitées" -#: selfdrive/ui/layouts/settings/device.py:68 +#: openpilot/selfdrive/ui/layouts/settings/device.py:66 #, python-format msgid "Change Language" msgstr "Changer la langue" -#: selfdrive/ui/layouts/settings/toggles.py:125 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:125 #, python-format msgid "Changing this setting will restart openpilot if the car is powered on." -msgstr "" -"La modification de ce réglage redémarrera openpilot si la voiture est sous " -"tension." +msgstr "La modification de ce réglage redémarrera openpilot si la voiture est sous tension." -#: selfdrive/ui/widgets/pairing_dialog.py:129 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:118 #, python-format msgid "Click \"add new device\" and scan the QR code on the right" msgstr "Cliquez sur \"add new device\" et scannez le code QR à droite" -#: selfdrive/ui/widgets/offroad_alerts.py:104 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:104 #, python-format msgid "Close" msgstr "Fermer" -#: selfdrive/ui/layouts/settings/software.py:49 +#: openpilot/selfdrive/ui/layouts/settings/software.py:56 #, python-format msgid "Current Version" msgstr "Version actuelle" -#: selfdrive/ui/layouts/settings/software.py:110 +#: openpilot/selfdrive/ui/layouts/settings/software.py:120 #, python-format msgid "DOWNLOAD" msgstr "TÉLÉCHARGER" -#: selfdrive/ui/layouts/onboarding.py:115 +#: openpilot/selfdrive/ui/layouts/onboarding.py:119 #, python-format msgid "Decline" msgstr "Refuser" -#: selfdrive/ui/layouts/onboarding.py:148 +#: openpilot/selfdrive/ui/layouts/onboarding.py:152 #, python-format msgid "Decline, uninstall openpilot" msgstr "Refuser, désinstaller openpilot" -#: selfdrive/ui/layouts/settings/settings.py:67 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:64 msgid "Developer" msgstr "Développeur" -#: selfdrive/ui/layouts/settings/settings.py:62 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:59 msgid "Device" msgstr "Appareil" -#: selfdrive/ui/layouts/settings/toggles.py:58 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:58 #, python-format msgid "Disengage on Accelerator Pedal" msgstr "Désengager à l'appui sur l'accélérateur" -#: selfdrive/ui/layouts/settings/device.py:184 +#: openpilot/selfdrive/ui/layouts/settings/device.py:176 #, python-format msgid "Disengage to Power Off" msgstr "Désengager pour éteindre" -#: selfdrive/ui/layouts/settings/device.py:172 +#: openpilot/selfdrive/ui/layouts/settings/device.py:164 #, python-format msgid "Disengage to Reboot" msgstr "Désengager pour redémarrer" -#: selfdrive/ui/layouts/settings/device.py:103 +#: openpilot/selfdrive/ui/layouts/settings/device.py:95 #, python-format msgid "Disengage to Reset Calibration" msgstr "Désengager pour réinitialiser la calibration" -#: selfdrive/ui/layouts/settings/toggles.py:32 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:32 msgid "Display speed in km/h instead of mph." msgstr "Afficher la vitesse en km/h au lieu de mph." -#: selfdrive/ui/layouts/settings/device.py:59 +#: openpilot/selfdrive/ui/layouts/settings/device.py:57 #, python-format msgid "Dongle ID" msgstr "ID du dongle" -#: selfdrive/ui/layouts/settings/software.py:50 +#: openpilot/selfdrive/ui/layouts/settings/software.py:57 #, python-format msgid "Download" msgstr "Télécharger" -#: selfdrive/ui/layouts/settings/device.py:62 +#: openpilot/selfdrive/ui/layouts/settings/device.py:60 #, python-format msgid "Driver Camera" msgstr "Caméra conducteur" -#: selfdrive/ui/layouts/settings/toggles.py:96 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:96 #, python-format msgid "Driving Personality" msgstr "Personnalité de conduite" -#: system/ui/widgets/network.py:123 system/ui/widgets/network.py:139 +#: system/ui/widgets/network.py:120 +#: system/ui/widgets/network.py:136 #, python-format msgid "EDIT" msgstr "EDITER" -#: selfdrive/ui/layouts/sidebar.py:138 +#: openpilot/selfdrive/ui/layouts/sidebar.py:138 msgid "ERROR" msgstr "ERREUR" -#: selfdrive/ui/layouts/sidebar.py:45 +#: openpilot/selfdrive/ui/layouts/sidebar.py:45 msgid "ETH" msgstr "ETH" -#: selfdrive/ui/widgets/exp_mode_button.py:50 +#: openpilot/selfdrive/ui/widgets/exp_mode_button.py:51 #, python-format msgid "EXPERIMENTAL MODE ON" msgstr "MODE EXPÉRIMENTAL ACTIVÉ" -#: selfdrive/ui/layouts/settings/developer.py:166 -#: selfdrive/ui/layouts/settings/toggles.py:228 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:229 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:180 #, python-format msgid "Enable" msgstr "Activer" -#: selfdrive/ui/layouts/settings/developer.py:39 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:39 #, python-format msgid "Enable ADB" msgstr "Activer ADB" -#: selfdrive/ui/layouts/settings/toggles.py:64 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:64 #, python-format msgid "Enable Lane Departure Warnings" msgstr "Activer les alertes de sortie de voie" -#: system/ui/widgets/network.py:129 +#: system/ui/widgets/network.py:126 #, python-format msgid "Enable Roaming" msgstr "Activer openpilot" -#: selfdrive/ui/layouts/settings/developer.py:48 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:48 #, python-format msgid "Enable SSH" msgstr "Activer SSH" -#: system/ui/widgets/network.py:120 +#: system/ui/widgets/network.py:117 #, python-format msgid "Enable Tethering" msgstr "Activer les alertes de sortie de voie" -#: selfdrive/ui/layouts/settings/toggles.py:30 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:30 msgid "Enable driver monitoring even when openpilot is not engaged." -msgstr "" -"Activer la surveillance du conducteur même lorsque openpilot n'est pas " -"engagé." +msgstr "Activer la surveillance du conducteur même lorsque openpilot n'est pas engagé." -#: selfdrive/ui/layouts/settings/toggles.py:46 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:46 #, python-format msgid "Enable openpilot" msgstr "Activer openpilot" -#: selfdrive/ui/layouts/settings/toggles.py:189 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:190 #, python-format -msgid "" -"Enable the openpilot longitudinal control (alpha) toggle to allow " -"Experimental mode." -msgstr "" -"Activez l'option de contrôle longitudinal openpilot (alpha) pour autoriser " -"le mode expérimental." +msgid "Enable the openpilot longitudinal control (alpha) toggle to allow Experimental mode." +msgstr "Activez l'option de contrôle longitudinal openpilot (alpha) pour autoriser le mode expérimental." -#: system/ui/widgets/network.py:204 +#: system/ui/widgets/network.py:201 #, python-format msgid "Enter APN" msgstr "Saisir l'APN" -#: system/ui/widgets/network.py:241 +#: system/ui/widgets/network.py:243 #, python-format msgid "Enter SSID" msgstr "Entrer le SSID" -#: system/ui/widgets/network.py:254 +#: system/ui/widgets/network.py:257 #, python-format msgid "Enter new tethering password" msgstr "Saisir le mot de passe du partage de connexion" -#: system/ui/widgets/network.py:237 system/ui/widgets/network.py:314 +#: system/ui/widgets/network.py:238 +#: system/ui/widgets/network.py:320 #, python-format msgid "Enter password" msgstr "Saisir le mot de passe" -#: selfdrive/ui/widgets/ssh_key.py:89 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:89 #, python-format msgid "Enter your GitHub username" msgstr "Entrez votre nom d'utilisateur GitHub" -#: system/ui/widgets/list_view.py:123 system/ui/widgets/list_view.py:160 +#: system/ui/widgets/list_view.py:123 +#: system/ui/widgets/list_view.py:160 #, python-format msgid "Error" msgstr "Erreur" -#: selfdrive/ui/layouts/settings/toggles.py:52 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:52 #, python-format msgid "Experimental Mode" msgstr "Mode expérimental" -#: selfdrive/ui/layouts/settings/toggles.py:181 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:182 #, python-format -msgid "" -"Experimental mode is currently unavailable on this car since the car's stock " -"ACC is used for longitudinal control." -msgstr "" -"Le mode expérimental est actuellement indisponible sur cette voiture car " -"l'ACC d'origine est utilisé pour le contrôle longitudinal." +msgid "Experimental mode is currently unavailable on this car since the car's stock ACC is used for longitudinal control." +msgstr "Le mode expérimental est actuellement indisponible sur cette voiture car l'ACC d'origine est utilisé pour le contrôle longitudinal." -#: system/ui/widgets/network.py:373 +#: system/ui/widgets/network.py:380 #, python-format msgid "FORGETTING..." msgstr "OUBLIER..." -#: selfdrive/ui/widgets/setup.py:44 +#: openpilot/selfdrive/ui/widgets/setup.py:43 #, python-format msgid "Finish Setup" msgstr "Terminer la configuration" -#: selfdrive/ui/layouts/settings/settings.py:66 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:63 msgid "Firehose" msgstr "Firehose" -#: selfdrive/ui/layouts/settings/firehose.py:18 +#: openpilot/selfdrive/ui/layouts/settings/firehose.py:10 msgid "Firehose Mode" msgstr "Mode Firehose" -#: selfdrive/ui/layouts/settings/firehose.py:25 -msgid "" -"For maximum effectiveness, bring your device inside and connect to a good " -"USB-C adapter and Wi-Fi weekly.\n" -"\n" -"Firehose Mode can also work while you're driving if connected to a hotspot " -"or unlimited SIM card.\n" -"\n" -"\n" -"Frequently Asked Questions\n" -"\n" -"Does it matter how or where I drive? Nope, just drive as you normally " -"would.\n" -"\n" -"Do all of my segments get pulled in Firehose Mode? No, we selectively pull a " -"subset of your segments.\n" -"\n" -"What's a good USB-C adapter? Any fast phone or laptop charger should be " -"fine.\n" -"\n" -"Does it matter which software I run? Yes, only upstream openpilot (and " -"particular forks) are able to be used for training." -msgstr "" -"Pour une efficacité maximale, rentrez votre appareil et connectez-le chaque " -"semaine à un bon adaptateur USB-C et au Wi‑Fi.\n" -"\n" -"Le Mode Firehose peut aussi fonctionner pendant que vous conduisez si vous " -"êtes connecté à un hotspot ou à une carte SIM illimitée.\n" -"\n" -"\n" -"Foire aux questions\n" -"\n" -"Est-ce que la manière ou l'endroit où je conduis compte ? Non, conduisez " -"normalement.\n" -"\n" -"Tous mes segments sont-ils récupérés en Mode Firehose ? Non, nous récupérons " -"de façon sélective un sous-ensemble de vos segments.\n" -"\n" -"Quel est un bon adaptateur USB-C ? Tout chargeur rapide de téléphone ou " -"d'ordinateur portable convient.\n" -"\n" -"Le logiciel utilisé importe-t-il ? Oui, seul openpilot amont (et certains " -"forks) peut être utilisé pour l'entraînement." - -#: system/ui/widgets/network.py:318 system/ui/widgets/network.py:451 +#: system/ui/widgets/network.py:458 +#: system/ui/widgets/network.py:326 #, python-format msgid "Forget" msgstr "Oublier" -#: system/ui/widgets/network.py:319 +#: system/ui/widgets/network.py:327 #, python-format msgid "Forget Wi-Fi Network \"{}\"?" msgstr "Oublier le réseau Wi-Fi \"{}\" ?" -#: selfdrive/ui/layouts/sidebar.py:71 selfdrive/ui/layouts/sidebar.py:125 +#: openpilot/selfdrive/ui/layouts/sidebar.py:71 +#: openpilot/selfdrive/ui/layouts/sidebar.py:125 msgid "GOOD" msgstr "BON" -#: selfdrive/ui/widgets/pairing_dialog.py:128 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:117 #, python-format msgid "Go to https://connect.comma.ai on your phone" msgstr "Allez sur https://connect.comma.ai sur votre téléphone" -#: selfdrive/ui/layouts/sidebar.py:129 +#: openpilot/selfdrive/ui/layouts/sidebar.py:129 msgid "HIGH" msgstr "ÉLEVÉ" -#: system/ui/widgets/network.py:155 +#: system/ui/widgets/network.py:152 #, python-format msgid "Hidden Network" msgstr "Réseau" -#: selfdrive/ui/layouts/settings/firehose.py:140 -#, python-format -msgid "INACTIVE: connect to an unmetered network" -msgstr "INACTIF : connectez-vous à un réseau non limité" - -#: selfdrive/ui/layouts/settings/software.py:53 -#: selfdrive/ui/layouts/settings/software.py:136 +#: openpilot/selfdrive/ui/layouts/settings/software.py:60 +#: openpilot/selfdrive/ui/layouts/settings/software.py:146 #, python-format msgid "INSTALL" msgstr "INSTALLER" -#: system/ui/widgets/network.py:150 +#: system/ui/widgets/network.py:147 #, python-format msgid "IP Address" msgstr "Adresse IP" -#: selfdrive/ui/layouts/settings/software.py:53 +#: openpilot/selfdrive/ui/layouts/settings/software.py:60 #, python-format msgid "Install Update" msgstr "Installer la mise à jour" -#: selfdrive/ui/layouts/settings/developer.py:56 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:56 #, python-format msgid "Joystick Debug Mode" msgstr "Mode débogage joystick" -#: selfdrive/ui/widgets/ssh_key.py:29 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:29 msgid "LOADING" msgstr "CHARGEMENT" -#: selfdrive/ui/layouts/sidebar.py:48 +#: openpilot/selfdrive/ui/layouts/sidebar.py:48 msgid "LTE" msgstr "LTE" -#: selfdrive/ui/layouts/settings/developer.py:64 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:64 #, python-format msgid "Longitudinal Maneuver Mode" msgstr "Mode de manœuvre longitudinale" -#: selfdrive/ui/onroad/hud_renderer.py:148 +#: openpilot/selfdrive/ui/onroad/hud_renderer.py:148 #, python-format msgid "MAX" msgstr "MAX" -#: selfdrive/ui/widgets/setup.py:75 +#: openpilot/selfdrive/ui/widgets/setup.py:74 #, python-format -msgid "" -"Maximize your training data uploads to improve openpilot's driving models." -msgstr "" -"Maximisez vos envois de données d'entraînement pour améliorer les modèles de " -"conduite d'openpilot." +msgid "Maximize your training data uploads to improve openpilot's driving models." +msgstr "Maximisez vos envois de données d'entraînement pour améliorer les modèles de conduite d'openpilot." -#: selfdrive/ui/layouts/settings/device.py:59 -#: selfdrive/ui/layouts/settings/device.py:60 +#: openpilot/selfdrive/ui/layouts/settings/device.py:57 +#: openpilot/selfdrive/ui/layouts/settings/device.py:58 #, python-format msgid "N/A" msgstr "NC" -#: selfdrive/ui/layouts/sidebar.py:142 +#: openpilot/selfdrive/ui/layouts/sidebar.py:142 msgid "NO" msgstr "NON" -#: selfdrive/ui/layouts/settings/settings.py:63 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:60 msgid "Network" msgstr "Réseau" -#: selfdrive/ui/widgets/ssh_key.py:114 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:115 #, python-format msgid "No SSH keys found" msgstr "Aucune clé SSH trouvée" -#: selfdrive/ui/widgets/ssh_key.py:126 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:127 #, python-format msgid "No SSH keys found for user '{}'" msgstr "Aucune clé SSH trouvée pour l'utilisateur '{}'" -#: selfdrive/ui/widgets/offroad_alerts.py:320 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:321 #, python-format msgid "No release notes available." msgstr "Aucune note de version disponible." -#: selfdrive/ui/layouts/sidebar.py:73 selfdrive/ui/layouts/sidebar.py:134 +#: openpilot/selfdrive/ui/layouts/sidebar.py:73 +#: openpilot/selfdrive/ui/layouts/sidebar.py:134 msgid "OFFLINE" msgstr "HORS LIGNE" -#: system/ui/widgets/html_render.py:263 system/ui/widgets/confirm_dialog.py:93 -#: selfdrive/ui/layouts/sidebar.py:127 +#: system/ui/widgets/confirm_dialog.py:93 +#: system/ui/widgets/html_render.py:263 +#: openpilot/selfdrive/ui/layouts/sidebar.py:127 #, python-format msgid "OK" msgstr "OK" -#: selfdrive/ui/layouts/sidebar.py:72 selfdrive/ui/layouts/sidebar.py:136 -#: selfdrive/ui/layouts/sidebar.py:144 +#: openpilot/selfdrive/ui/layouts/sidebar.py:72 +#: openpilot/selfdrive/ui/layouts/sidebar.py:144 +#: openpilot/selfdrive/ui/layouts/sidebar.py:136 msgid "ONLINE" msgstr "EN LIGNE" -#: selfdrive/ui/widgets/setup.py:20 +#: openpilot/selfdrive/ui/widgets/setup.py:19 #, python-format msgid "Open" msgstr "Ouvrir" -#: selfdrive/ui/layouts/settings/device.py:48 +#: openpilot/selfdrive/ui/layouts/settings/device.py:45 #, python-format msgid "PAIR" msgstr "ASSOCIER" -#: selfdrive/ui/layouts/sidebar.py:142 +#: openpilot/selfdrive/ui/layouts/sidebar.py:142 msgid "PANDA" msgstr "PANDA" -#: selfdrive/ui/layouts/settings/device.py:62 +#: openpilot/selfdrive/ui/layouts/settings/device.py:60 #, python-format msgid "PREVIEW" msgstr "APERÇU" -#: selfdrive/ui/widgets/prime.py:44 +#: openpilot/selfdrive/ui/widgets/prime.py:44 #, python-format msgid "PRIME FEATURES:" msgstr "FONCTIONNALITÉS PRIME :" -#: selfdrive/ui/layouts/settings/device.py:48 +#: openpilot/selfdrive/ui/layouts/settings/device.py:45 #, python-format msgid "Pair Device" msgstr "Associer l'appareil" -#: selfdrive/ui/widgets/setup.py:19 +#: openpilot/selfdrive/ui/widgets/setup.py:18 #, python-format msgid "Pair device" msgstr "Associer l'appareil" -#: selfdrive/ui/widgets/pairing_dialog.py:103 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:92 #, python-format msgid "Pair your device to your comma account" msgstr "Associez votre appareil à votre compte comma" -#: selfdrive/ui/widgets/setup.py:48 selfdrive/ui/layouts/settings/device.py:24 +#: openpilot/selfdrive/ui/widgets/setup.py:47 +#: openpilot/selfdrive/ui/layouts/settings/device.py:23 #, python-format -msgid "" -"Pair your device with comma connect (connect.comma.ai) and claim your comma " -"prime offer." -msgstr "" -"Associez votre appareil à comma connect (connect.comma.ai) et réclamez votre " -"offre comma prime." +msgid "Pair your device with comma connect (connect.comma.ai) and claim your comma prime offer." +msgstr "Associez votre appareil à comma connect (connect.comma.ai) et réclamez votre offre comma prime." -#: selfdrive/ui/widgets/setup.py:91 +#: openpilot/selfdrive/ui/widgets/setup.py:91 #, python-format msgid "Please connect to Wi-Fi to complete initial pairing" msgstr "Veuillez vous connecter au Wi‑Fi pour terminer l'association initiale" -#: selfdrive/ui/layouts/settings/device.py:55 -#: selfdrive/ui/layouts/settings/device.py:187 +#: openpilot/selfdrive/ui/layouts/settings/device.py:183 +#: openpilot/selfdrive/ui/layouts/settings/device.py:53 #, python-format msgid "Power Off" msgstr "Éteindre" -#: system/ui/widgets/network.py:144 +#: system/ui/widgets/network.py:141 #, python-format msgid "Prevent large data uploads when on a metered Wi-Fi connection" -msgstr "" -"Eviter les transferts de données volumineux lorsque vous êtes connecté à un " -"réseau Wi-Fi limité" +msgstr "Eviter les transferts de données volumineux lorsque vous êtes connecté à un réseau Wi-Fi limité" -#: system/ui/widgets/network.py:135 +#: system/ui/widgets/network.py:132 #, python-format msgid "Prevent large data uploads when on a metered cellular connection" -msgstr "" -"Eviter les transferts de données volumineux lors d'une connexion à un réseau " -"cellulaire limité" +msgstr "Eviter les transferts de données volumineux lors d'une connexion à un réseau cellulaire limité" -#: selfdrive/ui/layouts/settings/device.py:25 -msgid "" -"Preview the driver facing camera to ensure that driver monitoring has good " -"visibility. (vehicle must be off)" -msgstr "" -"Prévisualisez la caméra orientée conducteur pour vous assurer que la " -"surveillance du conducteur a une bonne visibilité. (le véhicule doit être " -"éteint)" +#: openpilot/selfdrive/ui/layouts/settings/device.py:24 +msgid "Preview the driver facing camera to ensure that driver monitoring has good visibility. (vehicle must be off)" +msgstr "Prévisualisez la caméra orientée conducteur pour vous assurer que la surveillance du conducteur a une bonne visibilité. (le véhicule doit être éteint)" -#: selfdrive/ui/widgets/pairing_dialog.py:161 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:150 #, python-format msgid "QR Code Error" msgstr "Erreur de code QR" -#: selfdrive/ui/widgets/ssh_key.py:31 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:31 msgid "REMOVE" msgstr "SUPPRIMER" -#: selfdrive/ui/layouts/settings/device.py:51 +#: openpilot/selfdrive/ui/layouts/settings/device.py:49 #, python-format msgid "RESET" msgstr "RÉINITIALISER" -#: selfdrive/ui/layouts/settings/device.py:65 +#: openpilot/selfdrive/ui/layouts/settings/device.py:63 #, python-format msgid "REVIEW" msgstr "CONSULTER" -#: selfdrive/ui/layouts/settings/device.py:55 -#: selfdrive/ui/layouts/settings/device.py:175 +#: openpilot/selfdrive/ui/layouts/settings/device.py:171 +#: openpilot/selfdrive/ui/layouts/settings/device.py:53 #, python-format msgid "Reboot" msgstr "Redémarrer" -#: selfdrive/ui/onroad/alert_renderer.py:66 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:66 #, python-format msgid "Reboot Device" msgstr "Redémarrer l'appareil" -#: selfdrive/ui/widgets/offroad_alerts.py:112 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:112 #, python-format msgid "Reboot and Update" msgstr "Redémarrer et mettre à jour" -#: selfdrive/ui/layouts/settings/toggles.py:27 -msgid "" -"Receive alerts to steer back into the lane when your vehicle drifts over a " -"detected lane line without a turn signal activated while driving over 31 mph " -"(50 km/h)." -msgstr "" -"Recevez des alertes pour revenir dans la voie lorsque votre véhicule dépasse " -"une ligne de voie détectée sans clignotant activé en roulant au-delà de 31 " -"mph (50 km/h)." - -#: selfdrive/ui/layouts/settings/toggles.py:76 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:76 #, python-format msgid "Record and Upload Driver Camera" msgstr "Enregistrer et téléverser la caméra conducteur" -#: selfdrive/ui/layouts/settings/toggles.py:82 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:82 #, python-format msgid "Record and Upload Microphone Audio" msgstr "Enregistrer et téléverser l'audio du microphone" -#: selfdrive/ui/layouts/settings/toggles.py:33 -msgid "" -"Record and store microphone audio while driving. The audio will be included " -"in the dashcam video in comma connect." -msgstr "" -"Enregistrer et stocker l'audio du microphone pendant la conduite. L'audio " -"sera inclus dans la vidéo dashcam dans comma connect." +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:33 +msgid "Record and store microphone audio while driving. The audio will be included in the dashcam video in comma connect." +msgstr "Enregistrer et stocker l'audio du microphone pendant la conduite. L'audio sera inclus dans la vidéo dashcam dans comma connect." -#: selfdrive/ui/layouts/settings/device.py:67 +#: openpilot/selfdrive/ui/layouts/settings/device.py:65 #, python-format msgid "Regulatory" msgstr "Réglementaire" -#: selfdrive/ui/layouts/settings/toggles.py:98 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:98 #, python-format msgid "Relaxed" msgstr "Détendu" -#: selfdrive/ui/widgets/prime.py:47 +#: openpilot/selfdrive/ui/widgets/prime.py:47 #, python-format msgid "Remote access" msgstr "Accès à distance" -#: selfdrive/ui/widgets/prime.py:47 +#: openpilot/selfdrive/ui/widgets/prime.py:47 #, python-format msgid "Remote snapshots" msgstr "Captures à distance" -#: selfdrive/ui/widgets/ssh_key.py:123 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:124 #, python-format msgid "Request timed out" msgstr "Délai de la requête dépassé" -#: selfdrive/ui/layouts/settings/device.py:119 +#: openpilot/selfdrive/ui/layouts/settings/device.py:111 #, python-format msgid "Reset" msgstr "Réinitialiser" -#: selfdrive/ui/layouts/settings/device.py:51 +#: openpilot/selfdrive/ui/layouts/settings/device.py:49 #, python-format msgid "Reset Calibration" msgstr "Réinitialiser la calibration" -#: selfdrive/ui/layouts/settings/device.py:65 +#: openpilot/selfdrive/ui/layouts/settings/device.py:63 #, python-format msgid "Review Training Guide" msgstr "Consulter le guide d'entraînement" -#: selfdrive/ui/layouts/settings/device.py:27 +#: openpilot/selfdrive/ui/layouts/settings/device.py:26 msgid "Review the rules, features, and limitations of openpilot" msgstr "Consultez les règles, fonctionnalités et limitations d'openpilot" -#: selfdrive/ui/layouts/settings/software.py:61 +#: openpilot/selfdrive/ui/layouts/settings/software.py:68 #, python-format msgid "SELECT" msgstr "SELECTIONNER" -#: selfdrive/ui/layouts/settings/developer.py:53 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:53 #, python-format msgid "SSH Keys" msgstr "Clefs SSH" -#: system/ui/widgets/network.py:310 +#: system/ui/widgets/network.py:316 #, python-format msgid "Scanning Wi-Fi networks..." msgstr "Analyse des réseaux Wi-Fi..." -#: system/ui/widgets/option_dialog.py:36 +#: system/ui/widgets/option_dialog.py:37 #, python-format msgid "Select" msgstr "Sélectionner" -#: selfdrive/ui/layouts/settings/software.py:183 +#: openpilot/selfdrive/ui/layouts/settings/software.py:203 #, python-format msgid "Select a branch" msgstr "Sélectionner une branche" -#: selfdrive/ui/layouts/settings/device.py:91 +#: openpilot/selfdrive/ui/layouts/settings/device.py:89 #, python-format msgid "Select a language" msgstr "Sélectionner un langage" -#: selfdrive/ui/layouts/settings/device.py:60 +#: openpilot/selfdrive/ui/layouts/settings/device.py:58 #, python-format msgid "Serial" msgstr "Numéro de série" -#: selfdrive/ui/widgets/offroad_alerts.py:106 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:106 #, python-format msgid "Snooze Update" msgstr "Reporter la mise à jour" -#: selfdrive/ui/layouts/settings/settings.py:65 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:62 msgid "Software" msgstr "Logiciel" -#: selfdrive/ui/layouts/settings/toggles.py:98 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:98 #, python-format msgid "Standard" msgstr "Standard" -#: selfdrive/ui/layouts/settings/toggles.py:22 -msgid "" -"Standard is recommended. In aggressive mode, openpilot will follow lead cars " -"closer and be more aggressive with the gas and brake. In relaxed mode " -"openpilot will stay further away from lead cars. On supported cars, you can " -"cycle through these personalities with your steering wheel distance button." -msgstr "" -"Le mode standard est recommandé. En mode agressif, openpilot suivra les " -"véhicules de tête de plus près et sera plus agressif avec l'accélérateur et " -"le frein. En mode détendu, openpilot restera plus éloigné des véhicules de " -"tête. Sur les voitures compatibles, vous pouvez parcourir ces personnalités " -"avec le bouton de distance du volant." - -#: selfdrive/ui/onroad/alert_renderer.py:59 -#: selfdrive/ui/onroad/alert_renderer.py:65 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:59 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:65 #, python-format msgid "System Unresponsive" msgstr "Système non réactif" -#: selfdrive/ui/onroad/alert_renderer.py:58 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:58 #, python-format msgid "TAKE CONTROL IMMEDIATELY" msgstr "REPRENEZ IMMÉDIATEMENT LE CONTRÔLE" -#: selfdrive/ui/layouts/sidebar.py:71 selfdrive/ui/layouts/sidebar.py:125 -#: selfdrive/ui/layouts/sidebar.py:127 selfdrive/ui/layouts/sidebar.py:129 +#: openpilot/selfdrive/ui/layouts/sidebar.py:71 +#: openpilot/selfdrive/ui/layouts/sidebar.py:125 +#: openpilot/selfdrive/ui/layouts/sidebar.py:127 +#: openpilot/selfdrive/ui/layouts/sidebar.py:129 msgid "TEMP" msgstr "TEMPÉRATURE" -#: selfdrive/ui/layouts/settings/software.py:61 +#: openpilot/selfdrive/ui/layouts/settings/software.py:68 #, python-format msgid "Target Branch" msgstr "Branche cible" -#: system/ui/widgets/network.py:124 +#: system/ui/widgets/network.py:121 #, python-format msgid "Tethering Password" msgstr "Mot de passe du partage de connexion" -#: selfdrive/ui/layouts/settings/settings.py:64 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:61 msgid "Toggles" msgstr "Options" -#: selfdrive/ui/layouts/settings/software.py:72 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:79 +#, python-format +msgid "UI Debug Mode" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/software.py:79 #, python-format msgid "UNINSTALL" msgstr "DÉSINSTALLER" -#: selfdrive/ui/layouts/home.py:155 +#: openpilot/selfdrive/ui/layouts/home.py:155 #, python-format msgid "UPDATE" msgstr "METTRE À JOUR" -#: selfdrive/ui/layouts/settings/software.py:72 -#: selfdrive/ui/layouts/settings/software.py:163 +#: openpilot/selfdrive/ui/layouts/settings/software.py:173 +#: openpilot/selfdrive/ui/layouts/settings/software.py:79 #, python-format msgid "Uninstall" msgstr "Désinstaller" -#: selfdrive/ui/layouts/sidebar.py:117 +#: openpilot/selfdrive/ui/layouts/sidebar.py:117 msgid "Unknown" msgstr "Inconnu" -#: selfdrive/ui/layouts/settings/software.py:48 +#: openpilot/selfdrive/ui/layouts/settings/software.py:55 #, python-format msgid "Updates are only downloaded while the car is off." -msgstr "" -"Les mises à jour ne sont téléchargées que lorsque la voiture est éteinte." +msgstr "Les mises à jour ne sont téléchargées que lorsque la voiture est éteinte." -#: selfdrive/ui/widgets/prime.py:33 +#: openpilot/selfdrive/ui/widgets/prime.py:33 #, python-format msgid "Upgrade Now" msgstr "Mettre à niveau maintenant" -#: selfdrive/ui/layouts/settings/toggles.py:31 -msgid "" -"Upload data from the driver facing camera and help improve the driver " -"monitoring algorithm." -msgstr "" -"Téléverser les données de la caméra orientée conducteur et aider à améliorer " -"l'algorithme de surveillance du conducteur." +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:31 +msgid "Upload data from the driver facing camera and help improve the driver monitoring algorithm." +msgstr "Téléverser les données de la caméra orientée conducteur et aider à améliorer l'algorithme de surveillance du conducteur." -#: selfdrive/ui/layouts/settings/toggles.py:88 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:88 #, python-format msgid "Use Metric System" msgstr "Utiliser le système métrique" -#: selfdrive/ui/layouts/settings/toggles.py:17 -msgid "" -"Use the openpilot system for adaptive cruise control and lane keep driver " -"assistance. Your attention is required at all times to use this feature." -msgstr "" -"Utilisez le système openpilot pour l'ACC et l'assistance au maintien de " -"voie. Votre attention est requise en permanence pour utiliser cette " -"fonctionnalité." - -#: selfdrive/ui/layouts/sidebar.py:72 selfdrive/ui/layouts/sidebar.py:144 +#: openpilot/selfdrive/ui/layouts/sidebar.py:72 +#: openpilot/selfdrive/ui/layouts/sidebar.py:144 msgid "VEHICLE" msgstr "VÉHICULE" -#: selfdrive/ui/layouts/settings/device.py:67 +#: openpilot/selfdrive/ui/layouts/settings/device.py:65 #, python-format msgid "VIEW" msgstr "VOIR" -#: selfdrive/ui/onroad/alert_renderer.py:52 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:52 #, python-format msgid "Waiting to start" msgstr "En attente de démarrage" -#: selfdrive/ui/layouts/settings/developer.py:19 -msgid "" -"Warning: This grants SSH access to all public keys in your GitHub settings. " -"Never enter a GitHub username other than your own. A comma employee will " -"NEVER ask you to add their GitHub username." -msgstr "" -"Avertissement : Ceci accorde un accès SSH à toutes les clés publiques dans " -"vos paramètres GitHub. N'entrez jamais un nom d'utilisateur GitHub autre que " -"le vôtre. Un employé comma ne vous demandera JAMAIS d'ajouter son nom " -"d'utilisateur GitHub." - -#: selfdrive/ui/layouts/onboarding.py:111 +#: openpilot/selfdrive/ui/layouts/onboarding.py:115 #, python-format msgid "Welcome to openpilot" msgstr "Bienvenue sur openpilot" -#: selfdrive/ui/layouts/settings/toggles.py:20 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:20 msgid "When enabled, pressing the accelerator pedal will disengage openpilot." -msgstr "" -"Lorsque activé, appuyer sur la pédale d'accélérateur désengagera openpilot." +msgstr "Lorsque activé, appuyer sur la pédale d'accélérateur désengagera openpilot." -#: selfdrive/ui/layouts/sidebar.py:44 +#: openpilot/selfdrive/ui/layouts/sidebar.py:44 msgid "Wi-Fi" msgstr "Wi‑Fi" -#: system/ui/widgets/network.py:144 +#: system/ui/widgets/network.py:141 #, python-format msgid "Wi-Fi Network Metered" msgstr "Réseau Wi-Fi limité" -#: system/ui/widgets/network.py:314 +#: system/ui/widgets/network.py:320 #, python-format msgid "Wrong password" msgstr "Mauvais mot de passe" -#: selfdrive/ui/layouts/onboarding.py:145 +#: openpilot/selfdrive/ui/layouts/onboarding.py:149 #, python-format msgid "You must accept the Terms and Conditions in order to use openpilot." msgstr "Vous devez accepter les conditions générales pour utiliser openpilot." -#: selfdrive/ui/layouts/onboarding.py:112 +#: openpilot/selfdrive/ui/layouts/onboarding.py:116 #, python-format -msgid "" -"You must accept the Terms and Conditions to use openpilot. Read the latest " -"terms at https://comma.ai/terms before continuing." -msgstr "" -"Vous devez accepter les conditions générales pour utiliser openpilot. Lisez " -"les dernières conditions sur https://comma.ai/terms avant de continuer." +msgid "You must accept the Terms and Conditions to use openpilot. Read the latest terms at https://comma.ai/terms before continuing." +msgstr "Vous devez accepter les conditions générales pour utiliser openpilot. Lisez les dernières conditions sur https://comma.ai/terms avant de continuer." -#: selfdrive/ui/onroad/driver_camera_dialog.py:34 +#: openpilot/selfdrive/ui/onroad/driver_camera_dialog.py:38 #, python-format msgid "camera starting" msgstr "démarrage de la caméra" -#: selfdrive/ui/widgets/prime.py:63 +#: openpilot/selfdrive/ui/layouts/settings/software.py:19 +#, python-format +msgid "checking..." +msgstr "" + +#: openpilot/selfdrive/ui/widgets/prime.py:63 #, python-format msgid "comma prime" msgstr "comma prime" -#: system/ui/widgets/network.py:142 +#: system/ui/widgets/network.py:139 #, python-format msgid "default" msgstr "défaut" -#: selfdrive/ui/layouts/settings/device.py:133 +#: openpilot/selfdrive/ui/layouts/settings/device.py:125 #, python-format msgid "down" msgstr "bas" -#: selfdrive/ui/layouts/settings/software.py:106 +#: openpilot/selfdrive/ui/layouts/settings/software.py:20 +#, python-format +msgid "downloading..." +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/software.py:116 #, python-format msgid "failed to check for update" msgstr "échec de la vérification de mise à jour" -#: system/ui/widgets/network.py:237 system/ui/widgets/network.py:314 +#: openpilot/selfdrive/ui/layouts/settings/software.py:21 +#, python-format +msgid "finalizing update..." +msgstr "" + +#: system/ui/widgets/network.py:238 +#: system/ui/widgets/network.py:321 #, python-format msgid "for \"{}\"" msgstr "pour \"{}\"" -#: selfdrive/ui/onroad/hud_renderer.py:177 +#: openpilot/selfdrive/ui/onroad/hud_renderer.py:177 #, python-format msgid "km/h" msgstr "km/h" -#: system/ui/widgets/network.py:204 +#: system/ui/widgets/network.py:201 #, python-format msgid "leave blank for automatic configuration" msgstr "ne pas remplir pour une configuration automatique" -#: selfdrive/ui/layouts/settings/device.py:134 +#: openpilot/selfdrive/ui/layouts/settings/device.py:126 #, python-format msgid "left" msgstr "gauche" -#: system/ui/widgets/network.py:142 +#: system/ui/widgets/network.py:139 #, python-format msgid "metered" msgstr "limité" -#: selfdrive/ui/onroad/hud_renderer.py:177 +#: openpilot/selfdrive/ui/onroad/hud_renderer.py:177 #, python-format msgid "mph" msgstr "mph" -#: selfdrive/ui/layouts/settings/software.py:20 +#: openpilot/selfdrive/ui/layouts/settings/software.py:27 #, python-format msgid "never" msgstr "jamais" -#: selfdrive/ui/layouts/settings/software.py:31 +#: openpilot/selfdrive/ui/layouts/settings/software.py:38 #, python-format msgid "now" msgstr "maintenant" -#: selfdrive/ui/layouts/settings/developer.py:71 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:71 #, python-format msgid "openpilot Longitudinal Control (Alpha)" msgstr "Contrôle longitudinal openpilot (Alpha)" -#: selfdrive/ui/onroad/alert_renderer.py:51 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:51 #, python-format msgid "openpilot Unavailable" msgstr "openpilot indisponible" -#: selfdrive/ui/layouts/settings/toggles.py:158 -#, python-format -msgid "" -"openpilot defaults to driving in chill mode. Experimental mode enables alpha-" -"level features that aren't ready for chill mode. Experimental features are " -"listed below:

End-to-End Longitudinal Control


Let the driving " -"model control the gas and brakes. openpilot will drive as it thinks a human " -"would, including stopping for red lights and stop signs. Since the driving " -"model decides the speed to drive, the set speed will only act as an upper " -"bound. This is an alpha quality feature; mistakes should be expected." -"

New Driving Visualization


The driving visualization will " -"transition to the road-facing wide-angle camera at low speeds to better show " -"some turns. The Experimental mode logo will also be shown in the top right " -"corner." -msgstr "" -"openpilot roule par défaut en mode chill. Le mode expérimental active des " -"fonctionnalités de niveau alpha qui ne sont pas prêtes pour le mode chill. " -"Les fonctionnalités expérimentales sont listées ci‑dessous:

Contrôle " -"longitudinal de bout en bout


Laissez le modèle de conduite contrôler " -"l'accélérateur et les freins. openpilot conduira comme il pense qu'un humain " -"le ferait, y compris s'arrêter aux feux rouges et aux panneaux stop. Comme " -"le modèle décide de la vitesse à adopter, la vitesse réglée n'agira que " -"comme une limite supérieure. C'est une fonctionnalité de qualité alpha ; des " -"erreurs sont à prévoir.

Nouvelle visualisation de conduite


La " -"visualisation passera à la caméra grand angle orientée route à basse vitesse " -"pour mieux montrer certains virages. Le logo du mode expérimental sera " -"également affiché en haut à droite." - -#: selfdrive/ui/layouts/settings/device.py:165 -#, python-format -msgid "" -"openpilot is continuously calibrating, resetting is rarely required. " -"Resetting calibration will restart openpilot if the car is powered on." -msgstr "" -"La modification de ce réglage redémarrera openpilot si la voiture est sous " -"tension." - -#: selfdrive/ui/layouts/settings/firehose.py:20 -msgid "" -"openpilot learns to drive by watching humans, like you, drive.\n" -"\n" -"Firehose Mode allows you to maximize your training data uploads to improve " -"openpilot's driving models. More data means bigger models, which means " -"better Experimental Mode." -msgstr "" -"openpilot apprend à conduire en regardant des humains, comme vous, " -"conduire.\n" -"\n" -"Le Mode Firehose vous permet de maximiser vos envois de données " -"d'entraînement pour améliorer les modèles de conduite d'openpilot. Plus de " -"données signifie des modèles plus grands, ce qui signifie un meilleur Mode " -"expérimental." - -#: selfdrive/ui/layouts/settings/toggles.py:183 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:184 #, python-format msgid "openpilot longitudinal control may come in a future update." -msgstr "" -"Le contrôle longitudinal openpilot pourra arriver dans une future mise à " -"jour." +msgstr "Le contrôle longitudinal openpilot pourra arriver dans une future mise à jour." -#: selfdrive/ui/layouts/settings/device.py:26 -msgid "" -"openpilot requires the device to be mounted within 4° left or right and " -"within 5° up or 9° down." -msgstr "" -"openpilot exige que l'appareil soit monté à moins de 4° à gauche ou à droite " -"et à moins de 5° vers le haut ou 9° vers le bas." +#: openpilot/selfdrive/ui/layouts/settings/device.py:25 +msgid "openpilot requires the device to be mounted within 4° left or right and within 5° up or 9° down." +msgstr "openpilot exige que l'appareil soit monté à moins de 4° à gauche ou à droite et à moins de 5° vers le haut ou 9° vers le bas." -#: selfdrive/ui/layouts/settings/device.py:134 +#: openpilot/selfdrive/ui/layouts/settings/device.py:126 #, python-format msgid "right" msgstr "droite" -#: system/ui/widgets/network.py:142 +#: system/ui/widgets/network.py:139 #, python-format msgid "unmetered" msgstr "non limité" -#: selfdrive/ui/layouts/settings/device.py:133 +#: openpilot/selfdrive/ui/layouts/settings/device.py:125 #, python-format msgid "up" msgstr "haut" -#: selfdrive/ui/layouts/settings/software.py:117 +#: openpilot/selfdrive/ui/layouts/settings/software.py:127 #, python-format msgid "up to date, last checked never" msgstr "à jour, dernière vérification jamais" -#: selfdrive/ui/layouts/settings/software.py:115 +#: openpilot/selfdrive/ui/layouts/settings/software.py:125 #, python-format msgid "up to date, last checked {}" msgstr "à jour, dernière vérification {}" -#: selfdrive/ui/layouts/settings/software.py:109 +#: openpilot/selfdrive/ui/layouts/settings/software.py:119 #, python-format msgid "update available" msgstr "mise à jour disponible" -#: selfdrive/ui/layouts/home.py:169 +#: openpilot/selfdrive/ui/layouts/home.py:169 #, python-format msgid "{} ALERT" msgid_plural "{} ALERTS" msgstr[0] "{} ALERTE" msgstr[1] "{} ALERTES" -#: selfdrive/ui/layouts/settings/software.py:40 +#: openpilot/selfdrive/ui/layouts/settings/software.py:47 #, python-format msgid "{} day ago" msgid_plural "{} days ago" msgstr[0] "il y a {} jour" msgstr[1] "il y a {} jours" -#: selfdrive/ui/layouts/settings/software.py:37 +#: openpilot/selfdrive/ui/layouts/settings/software.py:44 #, python-format msgid "{} hour ago" msgid_plural "{} hours ago" msgstr[0] "il y a {} heure" msgstr[1] "il y a {} heures" -#: selfdrive/ui/layouts/settings/software.py:34 +#: openpilot/selfdrive/ui/layouts/settings/software.py:41 #, python-format msgid "{} minute ago" msgid_plural "{} minutes ago" msgstr[0] "il y a {} minute" msgstr[1] "il y a {} minutes" -#: selfdrive/ui/layouts/settings/firehose.py:111 +#: openpilot/selfdrive/ui/layouts/settings/firehose.py:70 #, python-format msgid "{} segment of your driving is in the training dataset so far." msgid_plural "{} segments of your driving is in the training dataset so far." -msgstr[0] "" -"{} segment de votre conduite est dans l'ensemble d'entraînement jusqu'à " -"présent." -msgstr[1] "" -"{} segments de votre conduite sont dans l'ensemble d'entraînement jusqu'à " -"présent." +msgstr[0] "{} segment de votre conduite est dans l'ensemble d'entraînement jusqu'à présent." +msgstr[1] "{} segments de votre conduite sont dans l'ensemble d'entraînement jusqu'à présent." -#: selfdrive/ui/widgets/prime.py:62 +#: openpilot/selfdrive/ui/widgets/prime.py:62 #, python-format msgid "✓ SUBSCRIBED" msgstr "✓ ABONNÉ" -#: selfdrive/ui/widgets/setup.py:22 +#: openpilot/selfdrive/ui/widgets/setup.py:21 #, python-format msgid "🔥 Firehose Mode 🔥" msgstr "🔥 Mode Firehose 🔥" + diff --git a/selfdrive/ui/translations/app_ja.po b/selfdrive/ui/translations/app_ja.po index ca8aac1515..41eb91dd58 100644 --- a/selfdrive/ui/translations/app_ja.po +++ b/selfdrive/ui/translations/app_ja.po @@ -17,1181 +17,1013 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=1; plural=0;\n" -#: selfdrive/ui/layouts/settings/device.py:160 +#: openpilot/selfdrive/ui/layouts/settings/device.py:152 #, python-format msgid " Steering torque response calibration is complete." msgstr " ステアリングトルク応答のキャリブレーションが完了しました。" -#: selfdrive/ui/layouts/settings/device.py:158 +#: openpilot/selfdrive/ui/layouts/settings/device.py:150 #, python-format msgid " Steering torque response calibration is {}% complete." msgstr " ステアリングトルク応答のキャリブレーションは{}%完了しました。" -#: selfdrive/ui/layouts/settings/device.py:133 +#: openpilot/selfdrive/ui/layouts/settings/device.py:125 #, python-format msgid " Your device is pointed {:.1f}° {} and {:.1f}° {}." msgstr " デバイスは{:.1f}°{}、{:.1f}°{}の向きです。" -#: selfdrive/ui/layouts/sidebar.py:43 +#: openpilot/selfdrive/ui/layouts/sidebar.py:43 msgid "--" msgstr "--" -#: selfdrive/ui/widgets/prime.py:47 +#: openpilot/selfdrive/ui/widgets/prime.py:47 #, python-format msgid "1 year of drive storage" msgstr "走行データを1年間保存" -#: selfdrive/ui/widgets/prime.py:47 +#: openpilot/selfdrive/ui/widgets/prime.py:47 #, python-format msgid "24/7 LTE connectivity" msgstr "24時間365日のLTE接続" -#: selfdrive/ui/layouts/sidebar.py:46 +#: openpilot/selfdrive/ui/layouts/sidebar.py:46 msgid "2G" msgstr "2G" -#: selfdrive/ui/layouts/sidebar.py:47 +#: openpilot/selfdrive/ui/layouts/sidebar.py:47 msgid "3G" msgstr "3G" -#: selfdrive/ui/layouts/sidebar.py:49 +#: openpilot/selfdrive/ui/layouts/sidebar.py:49 msgid "5G" msgstr "5G" -#: selfdrive/ui/layouts/settings/developer.py:23 -msgid "" -"WARNING: openpilot longitudinal control is in alpha for this car and will " -"disable Automatic Emergency Braking (AEB).

On this car, openpilot " -"defaults to the car's built-in ACC instead of openpilot's longitudinal " -"control. Enable this to switch to openpilot longitudinal control. Enabling " -"Experimental mode is recommended when enabling openpilot longitudinal " -"control alpha. Changing this setting will restart openpilot if the car is " -"powered on." -msgstr "" -"警告: この車におけるopenpilotの縦制御はアルファ版であり、自動緊急ブレーキ" -"(AEB)を無効にします。

この車では、openpilotは縦制御として" -"openpilotではなく車両の内蔵ACCを既定で使用します。openpilotの縦制御に切り替え" -"るにはこの設定を有効にしてください。openpilot縦制御アルファを有効にする場合は" -"実験モードの有効化を推奨します。この設定を変更すると、車が起動中の場合は" -"openpilotが再起動します。" - -#: selfdrive/ui/layouts/settings/device.py:148 +#: openpilot/selfdrive/ui/layouts/settings/device.py:140 #, python-format msgid "

Steering lag calibration is complete." msgstr "

ステアリング遅延のキャリブレーションが完了しました。" -#: selfdrive/ui/layouts/settings/device.py:146 +#: openpilot/selfdrive/ui/layouts/settings/device.py:138 #, python-format msgid "

Steering lag calibration is {}% complete." msgstr "

ステアリング遅延のキャリブレーションは{}%完了しました。" -#: selfdrive/ui/layouts/settings/firehose.py:138 -#, python-format -msgid "ACTIVE" -msgstr "アクティブ" - -#: selfdrive/ui/layouts/settings/developer.py:15 -msgid "" -"ADB (Android Debug Bridge) allows connecting to your device over USB or over " -"the network. See https://docs.comma.ai/how-to/connect-to-comma for more info." -msgstr "" -"ADB(Android Debug Bridge)を使用すると、USBまたはネットワーク経由でデバイス" -"に接続できます。詳しくは https://docs.comma.ai/how-to/connect-to-comma を参照" -"してください。" - -#: selfdrive/ui/widgets/ssh_key.py:30 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:30 msgid "ADD" msgstr "追加" -#: system/ui/widgets/network.py:139 +#: system/ui/widgets/network.py:136 #, python-format msgid "APN Setting" msgstr "APN設定" -#: selfdrive/ui/widgets/offroad_alerts.py:109 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:109 #, python-format msgid "Acknowledge Excessive Actuation" msgstr "過度な作動を承認" -#: system/ui/widgets/network.py:74 system/ui/widgets/network.py:95 +#: system/ui/widgets/network.py:92 +#: system/ui/widgets/network.py:74 #, python-format msgid "Advanced" msgstr "詳細設定" -#: selfdrive/ui/layouts/settings/toggles.py:98 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:98 #, python-format msgid "Aggressive" msgstr "アグレッシブ" -#: selfdrive/ui/layouts/onboarding.py:116 +#: openpilot/selfdrive/ui/layouts/onboarding.py:120 #, python-format msgid "Agree" msgstr "同意する" -#: selfdrive/ui/layouts/settings/toggles.py:70 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:70 #, python-format msgid "Always-On Driver Monitoring" msgstr "常時ドライバーモニタリング" -#: selfdrive/ui/layouts/settings/toggles.py:186 -#, python-format -msgid "" -"An alpha version of openpilot longitudinal control can be tested, along with " -"Experimental mode, on non-release branches." -msgstr "" -"openpilotの縦制御アルファ版は、実験モードと併せて非リリースブランチでテストで" -"きます。" - -#: selfdrive/ui/layouts/settings/device.py:187 +#: openpilot/selfdrive/ui/layouts/settings/device.py:183 #, python-format msgid "Are you sure you want to power off?" msgstr "本当に電源をオフにしますか?" -#: selfdrive/ui/layouts/settings/device.py:175 +#: openpilot/selfdrive/ui/layouts/settings/device.py:171 #, python-format msgid "Are you sure you want to reboot?" msgstr "本当に再起動しますか?" -#: selfdrive/ui/layouts/settings/device.py:119 +#: openpilot/selfdrive/ui/layouts/settings/device.py:111 #, python-format msgid "Are you sure you want to reset calibration?" msgstr "本当にキャリブレーションをリセットしますか?" -#: selfdrive/ui/layouts/settings/software.py:163 +#: openpilot/selfdrive/ui/layouts/settings/software.py:173 #, python-format msgid "Are you sure you want to uninstall?" msgstr "本当にアンインストールしますか?" -#: system/ui/widgets/network.py:99 selfdrive/ui/layouts/onboarding.py:147 +#: system/ui/widgets/network.py:96 +#: openpilot/selfdrive/ui/layouts/onboarding.py:151 #, python-format msgid "Back" msgstr "戻る" -#: selfdrive/ui/widgets/prime.py:38 +#: openpilot/selfdrive/ui/widgets/prime.py:38 #, python-format msgid "Become a comma prime member at connect.comma.ai" msgstr "connect.comma.aiで comma prime に加入" -#: selfdrive/ui/widgets/pairing_dialog.py:130 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:119 #, python-format msgid "Bookmark connect.comma.ai to your home screen to use it like an app" msgstr "connect.comma.aiをホーム画面に追加してアプリのように使いましょう" -#: selfdrive/ui/layouts/settings/device.py:68 +#: openpilot/selfdrive/ui/layouts/settings/device.py:66 #, python-format msgid "CHANGE" msgstr "変更" -#: selfdrive/ui/layouts/settings/software.py:50 -#: selfdrive/ui/layouts/settings/software.py:107 -#: selfdrive/ui/layouts/settings/software.py:118 -#: selfdrive/ui/layouts/settings/software.py:147 +#: openpilot/selfdrive/ui/layouts/settings/software.py:157 +#: openpilot/selfdrive/ui/layouts/settings/software.py:57 +#: openpilot/selfdrive/ui/layouts/settings/software.py:117 +#: openpilot/selfdrive/ui/layouts/settings/software.py:128 #, python-format msgid "CHECK" msgstr "確認" -#: selfdrive/ui/widgets/exp_mode_button.py:50 +#: openpilot/selfdrive/ui/widgets/exp_mode_button.py:51 #, python-format msgid "CHILL MODE ON" msgstr "チルモードON" -#: system/ui/widgets/network.py:155 selfdrive/ui/layouts/sidebar.py:73 -#: selfdrive/ui/layouts/sidebar.py:134 selfdrive/ui/layouts/sidebar.py:136 -#: selfdrive/ui/layouts/sidebar.py:138 +#: system/ui/widgets/network.py:152 +#: openpilot/selfdrive/ui/layouts/sidebar.py:73 +#: openpilot/selfdrive/ui/layouts/sidebar.py:134 +#: openpilot/selfdrive/ui/layouts/sidebar.py:136 +#: openpilot/selfdrive/ui/layouts/sidebar.py:138 #, python-format msgid "CONNECT" msgstr "接続" -#: system/ui/widgets/network.py:369 +#: system/ui/widgets/network.py:376 #, python-format msgid "CONNECTING..." msgstr "接続中..." -#: system/ui/widgets/confirm_dialog.py:23 system/ui/widgets/option_dialog.py:35 -#: system/ui/widgets/keyboard.py:81 system/ui/widgets/network.py:318 +#: system/ui/widgets/network.py:326 +#: system/ui/widgets/confirm_dialog.py:24 +#: system/ui/widgets/option_dialog.py:36 +#: system/ui/widgets/keyboard.py:83 #, python-format msgid "Cancel" msgstr "キャンセル" -#: system/ui/widgets/network.py:134 +#: system/ui/widgets/network.py:131 #, python-format msgid "Cellular Metered" msgstr "従量課金の携帯回線" -#: selfdrive/ui/layouts/settings/device.py:68 +#: openpilot/selfdrive/ui/layouts/settings/device.py:66 #, python-format msgid "Change Language" msgstr "言語を変更" -#: selfdrive/ui/layouts/settings/toggles.py:125 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:125 #, python-format msgid "Changing this setting will restart openpilot if the car is powered on." msgstr "車が起動中の場合、この設定を変更するとopenpilotが再起動します。" -#: selfdrive/ui/widgets/pairing_dialog.py:129 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:118 #, python-format msgid "Click \"add new device\" and scan the QR code on the right" msgstr "\"add new device\"を押して右側のQRコードをスキャン" -#: selfdrive/ui/widgets/offroad_alerts.py:104 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:104 #, python-format msgid "Close" msgstr "閉じる" -#: selfdrive/ui/layouts/settings/software.py:49 +#: openpilot/selfdrive/ui/layouts/settings/software.py:56 #, python-format msgid "Current Version" msgstr "現在のバージョン" -#: selfdrive/ui/layouts/settings/software.py:110 +#: openpilot/selfdrive/ui/layouts/settings/software.py:120 #, python-format msgid "DOWNLOAD" msgstr "ダウンロード" -#: selfdrive/ui/layouts/onboarding.py:115 +#: openpilot/selfdrive/ui/layouts/onboarding.py:119 #, python-format msgid "Decline" msgstr "拒否する" -#: selfdrive/ui/layouts/onboarding.py:148 +#: openpilot/selfdrive/ui/layouts/onboarding.py:152 #, python-format msgid "Decline, uninstall openpilot" msgstr "拒否してopenpilotをアンインストール" -#: selfdrive/ui/layouts/settings/settings.py:67 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:64 msgid "Developer" msgstr "開発者" -#: selfdrive/ui/layouts/settings/settings.py:62 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:59 msgid "Device" msgstr "デバイス" -#: selfdrive/ui/layouts/settings/toggles.py:58 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:58 #, python-format msgid "Disengage on Accelerator Pedal" msgstr "アクセルで解除" -#: selfdrive/ui/layouts/settings/device.py:184 +#: openpilot/selfdrive/ui/layouts/settings/device.py:176 #, python-format msgid "Disengage to Power Off" msgstr "解除して電源オフ" -#: selfdrive/ui/layouts/settings/device.py:172 +#: openpilot/selfdrive/ui/layouts/settings/device.py:164 #, python-format msgid "Disengage to Reboot" msgstr "解除して再起動" -#: selfdrive/ui/layouts/settings/device.py:103 +#: openpilot/selfdrive/ui/layouts/settings/device.py:95 #, python-format msgid "Disengage to Reset Calibration" msgstr "解除してキャリブレーションをリセット" -#: selfdrive/ui/layouts/settings/toggles.py:32 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:32 msgid "Display speed in km/h instead of mph." msgstr "速度をmphではなくkm/hで表示します。" -#: selfdrive/ui/layouts/settings/device.py:59 +#: openpilot/selfdrive/ui/layouts/settings/device.py:57 #, python-format msgid "Dongle ID" msgstr "ドングルID" -#: selfdrive/ui/layouts/settings/software.py:50 +#: openpilot/selfdrive/ui/layouts/settings/software.py:57 #, python-format msgid "Download" msgstr "ダウンロード" -#: selfdrive/ui/layouts/settings/device.py:62 +#: openpilot/selfdrive/ui/layouts/settings/device.py:60 #, python-format msgid "Driver Camera" msgstr "ドライバーカメラ" -#: selfdrive/ui/layouts/settings/toggles.py:96 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:96 #, python-format msgid "Driving Personality" msgstr "走行性格" -#: system/ui/widgets/network.py:123 system/ui/widgets/network.py:139 +#: system/ui/widgets/network.py:120 +#: system/ui/widgets/network.py:136 #, python-format msgid "EDIT" msgstr "編集" -#: selfdrive/ui/layouts/sidebar.py:138 +#: openpilot/selfdrive/ui/layouts/sidebar.py:138 msgid "ERROR" msgstr "エラー" -#: selfdrive/ui/layouts/sidebar.py:45 +#: openpilot/selfdrive/ui/layouts/sidebar.py:45 msgid "ETH" msgstr "ETH" -#: selfdrive/ui/widgets/exp_mode_button.py:50 +#: openpilot/selfdrive/ui/widgets/exp_mode_button.py:51 #, python-format msgid "EXPERIMENTAL MODE ON" msgstr "実験モードON" -#: selfdrive/ui/layouts/settings/developer.py:166 -#: selfdrive/ui/layouts/settings/toggles.py:228 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:229 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:180 #, python-format msgid "Enable" msgstr "有効化" -#: selfdrive/ui/layouts/settings/developer.py:39 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:39 #, python-format msgid "Enable ADB" msgstr "ADBを有効化" -#: selfdrive/ui/layouts/settings/toggles.py:64 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:64 #, python-format msgid "Enable Lane Departure Warnings" msgstr "車線逸脱警報を有効化" -#: system/ui/widgets/network.py:129 +#: system/ui/widgets/network.py:126 #, python-format msgid "Enable Roaming" msgstr "ローミングを有効化" -#: selfdrive/ui/layouts/settings/developer.py:48 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:48 #, python-format msgid "Enable SSH" msgstr "SSHを有効化" -#: system/ui/widgets/network.py:120 +#: system/ui/widgets/network.py:117 #, python-format msgid "Enable Tethering" msgstr "テザリングを有効化" -#: selfdrive/ui/layouts/settings/toggles.py:30 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:30 msgid "Enable driver monitoring even when openpilot is not engaged." msgstr "openpilotが未作動でもドライバーモニタリングを有効にします。" -#: selfdrive/ui/layouts/settings/toggles.py:46 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:46 #, python-format msgid "Enable openpilot" msgstr "openpilotを有効化" -#: selfdrive/ui/layouts/settings/toggles.py:189 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:190 #, python-format -msgid "" -"Enable the openpilot longitudinal control (alpha) toggle to allow " -"Experimental mode." -msgstr "" -"openpilot縦制御(アルファ)のトグルを有効にすると実験モードが使用できます。" +msgid "Enable the openpilot longitudinal control (alpha) toggle to allow Experimental mode." +msgstr "openpilot縦制御(アルファ)のトグルを有効にすると実験モードが使用できます。" -#: system/ui/widgets/network.py:204 +#: system/ui/widgets/network.py:201 #, python-format msgid "Enter APN" msgstr "APNを入力" -#: system/ui/widgets/network.py:241 +#: system/ui/widgets/network.py:243 #, python-format msgid "Enter SSID" msgstr "SSIDを入力" -#: system/ui/widgets/network.py:254 +#: system/ui/widgets/network.py:257 #, python-format msgid "Enter new tethering password" msgstr "新しいテザリングのパスワードを入力" -#: system/ui/widgets/network.py:237 system/ui/widgets/network.py:314 +#: system/ui/widgets/network.py:238 +#: system/ui/widgets/network.py:320 #, python-format msgid "Enter password" msgstr "パスワードを入力" -#: selfdrive/ui/widgets/ssh_key.py:89 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:89 #, python-format msgid "Enter your GitHub username" msgstr "GitHubユーザー名を入力" -#: system/ui/widgets/list_view.py:123 system/ui/widgets/list_view.py:160 +#: system/ui/widgets/list_view.py:123 +#: system/ui/widgets/list_view.py:160 #, python-format msgid "Error" msgstr "エラー" -#: selfdrive/ui/layouts/settings/toggles.py:52 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:52 #, python-format msgid "Experimental Mode" msgstr "実験モード" -#: selfdrive/ui/layouts/settings/toggles.py:181 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:182 #, python-format -msgid "" -"Experimental mode is currently unavailable on this car since the car's stock " -"ACC is used for longitudinal control." -msgstr "" -"この車では縦制御に純正ACCを使用するため、現在実験モードは利用できません。" +msgid "Experimental mode is currently unavailable on this car since the car's stock ACC is used for longitudinal control." +msgstr "この車では縦制御に純正ACCを使用するため、現在実験モードは利用できません。" -#: system/ui/widgets/network.py:373 +#: system/ui/widgets/network.py:380 #, python-format msgid "FORGETTING..." msgstr "削除中..." -#: selfdrive/ui/widgets/setup.py:44 +#: openpilot/selfdrive/ui/widgets/setup.py:43 #, python-format msgid "Finish Setup" msgstr "セットアップを完了" -#: selfdrive/ui/layouts/settings/settings.py:66 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:63 msgid "Firehose" msgstr "Firehose" -#: selfdrive/ui/layouts/settings/firehose.py:18 +#: openpilot/selfdrive/ui/layouts/settings/firehose.py:10 msgid "Firehose Mode" msgstr "Firehoseモード" -#: selfdrive/ui/layouts/settings/firehose.py:25 -msgid "" -"For maximum effectiveness, bring your device inside and connect to a good " -"USB-C adapter and Wi-Fi weekly.\n" -"\n" -"Firehose Mode can also work while you're driving if connected to a hotspot " -"or unlimited SIM card.\n" -"\n" -"\n" -"Frequently Asked Questions\n" -"\n" -"Does it matter how or where I drive? Nope, just drive as you normally " -"would.\n" -"\n" -"Do all of my segments get pulled in Firehose Mode? No, we selectively pull a " -"subset of your segments.\n" -"\n" -"What's a good USB-C adapter? Any fast phone or laptop charger should be " -"fine.\n" -"\n" -"Does it matter which software I run? Yes, only upstream openpilot (and " -"particular forks) are able to be used for training." -msgstr "" -"最大限の効果を得るため、デバイスを屋内に持ち込み、週に一度は品質の良いUSB-Cア" -"ダプターとWi‑Fiに接続してください。\n" -"\n" -"Firehoseモードは、ホットスポットや無制限SIMに接続していれば走行中でも動作しま" -"す。\n" -"\n" -"\n" -"よくある質問\n" -"\n" -"運転の仕方や場所は関係ありますか? いいえ。普段どおりに運転してください。\n" -"\n" -"Firehoseモードではすべてのセグメントが取得されますか? いいえ。セグメントの一" -"部を選択的に取得します。\n" -"\n" -"良いUSB‑Cアダプターとは? 高速なスマホまたはノートPC用充電器で問題ありませ" -"ん。\n" -"\n" -"どのソフトウェアを使うかは重要ですか? はい。学習に使えるのは上流のopenpilot" -"(および特定のフォーク)のみです。" - -#: system/ui/widgets/network.py:318 system/ui/widgets/network.py:451 +#: system/ui/widgets/network.py:458 +#: system/ui/widgets/network.py:326 #, python-format msgid "Forget" msgstr "削除" -#: system/ui/widgets/network.py:319 +#: system/ui/widgets/network.py:327 #, python-format msgid "Forget Wi-Fi Network \"{}\"?" msgstr "Wi‑Fiネットワーク「{}」を削除しますか?" -#: selfdrive/ui/layouts/sidebar.py:71 selfdrive/ui/layouts/sidebar.py:125 +#: openpilot/selfdrive/ui/layouts/sidebar.py:71 +#: openpilot/selfdrive/ui/layouts/sidebar.py:125 msgid "GOOD" msgstr "良好" -#: selfdrive/ui/widgets/pairing_dialog.py:128 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:117 #, python-format msgid "Go to https://connect.comma.ai on your phone" msgstr "スマートフォンで https://connect.comma.ai にアクセス" -#: selfdrive/ui/layouts/sidebar.py:129 +#: openpilot/selfdrive/ui/layouts/sidebar.py:129 msgid "HIGH" msgstr "高温" -#: system/ui/widgets/network.py:155 +#: system/ui/widgets/network.py:152 #, python-format msgid "Hidden Network" msgstr "非公開ネットワーク" -#: selfdrive/ui/layouts/settings/firehose.py:140 -#, python-format -msgid "INACTIVE: connect to an unmetered network" -msgstr "非アクティブ:非従量のネットワークに接続してください" - -#: selfdrive/ui/layouts/settings/software.py:53 -#: selfdrive/ui/layouts/settings/software.py:136 +#: openpilot/selfdrive/ui/layouts/settings/software.py:60 +#: openpilot/selfdrive/ui/layouts/settings/software.py:146 #, python-format msgid "INSTALL" msgstr "インストール" -#: system/ui/widgets/network.py:150 +#: system/ui/widgets/network.py:147 #, python-format msgid "IP Address" msgstr "IPアドレス" -#: selfdrive/ui/layouts/settings/software.py:53 +#: openpilot/selfdrive/ui/layouts/settings/software.py:60 #, python-format msgid "Install Update" msgstr "アップデートをインストール" -#: selfdrive/ui/layouts/settings/developer.py:56 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:56 #, python-format msgid "Joystick Debug Mode" msgstr "ジョイスティックデバッグモード" -#: selfdrive/ui/widgets/ssh_key.py:29 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:29 msgid "LOADING" msgstr "読み込み中" -#: selfdrive/ui/layouts/sidebar.py:48 +#: openpilot/selfdrive/ui/layouts/sidebar.py:48 msgid "LTE" msgstr "LTE" -#: selfdrive/ui/layouts/settings/developer.py:64 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:64 #, python-format msgid "Longitudinal Maneuver Mode" msgstr "縦制御マヌーバーモード" -#: selfdrive/ui/onroad/hud_renderer.py:148 +#: openpilot/selfdrive/ui/onroad/hud_renderer.py:148 #, python-format msgid "MAX" msgstr "最大" -#: selfdrive/ui/widgets/setup.py:75 +#: openpilot/selfdrive/ui/widgets/setup.py:74 #, python-format -msgid "" -"Maximize your training data uploads to improve openpilot's driving models." -msgstr "" -"学習データのアップロードを最大化してopenpilotの運転モデルを改善しましょう。" +msgid "Maximize your training data uploads to improve openpilot's driving models." +msgstr "学習データのアップロードを最大化してopenpilotの運転モデルを改善しましょう。" -#: selfdrive/ui/layouts/settings/device.py:59 -#: selfdrive/ui/layouts/settings/device.py:60 +#: openpilot/selfdrive/ui/layouts/settings/device.py:57 +#: openpilot/selfdrive/ui/layouts/settings/device.py:58 #, python-format msgid "N/A" msgstr "該当なし" -#: selfdrive/ui/layouts/sidebar.py:142 +#: openpilot/selfdrive/ui/layouts/sidebar.py:142 msgid "NO" msgstr "いいえ" -#: selfdrive/ui/layouts/settings/settings.py:63 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:60 msgid "Network" msgstr "ネットワーク" -#: selfdrive/ui/widgets/ssh_key.py:114 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:115 #, python-format msgid "No SSH keys found" msgstr "SSH鍵が見つかりません" -#: selfdrive/ui/widgets/ssh_key.py:126 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:127 #, python-format msgid "No SSH keys found for user '{}'" msgstr "ユーザー'{}'のSSH鍵が見つかりません" -#: selfdrive/ui/widgets/offroad_alerts.py:320 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:321 #, python-format msgid "No release notes available." msgstr "リリースノートはありません。" -#: selfdrive/ui/layouts/sidebar.py:73 selfdrive/ui/layouts/sidebar.py:134 +#: openpilot/selfdrive/ui/layouts/sidebar.py:73 +#: openpilot/selfdrive/ui/layouts/sidebar.py:134 msgid "OFFLINE" msgstr "オフライン" -#: system/ui/widgets/html_render.py:263 system/ui/widgets/confirm_dialog.py:93 -#: selfdrive/ui/layouts/sidebar.py:127 +#: system/ui/widgets/confirm_dialog.py:93 +#: system/ui/widgets/html_render.py:263 +#: openpilot/selfdrive/ui/layouts/sidebar.py:127 #, python-format msgid "OK" msgstr "OK" -#: selfdrive/ui/layouts/sidebar.py:72 selfdrive/ui/layouts/sidebar.py:136 -#: selfdrive/ui/layouts/sidebar.py:144 +#: openpilot/selfdrive/ui/layouts/sidebar.py:72 +#: openpilot/selfdrive/ui/layouts/sidebar.py:144 +#: openpilot/selfdrive/ui/layouts/sidebar.py:136 msgid "ONLINE" msgstr "オンライン" -#: selfdrive/ui/widgets/setup.py:20 +#: openpilot/selfdrive/ui/widgets/setup.py:19 #, python-format msgid "Open" msgstr "開く" -#: selfdrive/ui/layouts/settings/device.py:48 +#: openpilot/selfdrive/ui/layouts/settings/device.py:45 #, python-format msgid "PAIR" msgstr "ペアリング" -#: selfdrive/ui/layouts/sidebar.py:142 +#: openpilot/selfdrive/ui/layouts/sidebar.py:142 msgid "PANDA" msgstr "PANDA" -#: selfdrive/ui/layouts/settings/device.py:62 +#: openpilot/selfdrive/ui/layouts/settings/device.py:60 #, python-format msgid "PREVIEW" msgstr "プレビュー" -#: selfdrive/ui/widgets/prime.py:44 +#: openpilot/selfdrive/ui/widgets/prime.py:44 #, python-format msgid "PRIME FEATURES:" msgstr "prime の特典:" -#: selfdrive/ui/layouts/settings/device.py:48 +#: openpilot/selfdrive/ui/layouts/settings/device.py:45 #, python-format msgid "Pair Device" msgstr "デバイスをペアリング" -#: selfdrive/ui/widgets/setup.py:19 +#: openpilot/selfdrive/ui/widgets/setup.py:18 #, python-format msgid "Pair device" msgstr "デバイスをペアリング" -#: selfdrive/ui/widgets/pairing_dialog.py:103 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:92 #, python-format msgid "Pair your device to your comma account" msgstr "デバイスをあなたの comma アカウントにペアリング" -#: selfdrive/ui/widgets/setup.py:48 selfdrive/ui/layouts/settings/device.py:24 +#: openpilot/selfdrive/ui/widgets/setup.py:47 +#: openpilot/selfdrive/ui/layouts/settings/device.py:23 #, python-format -msgid "" -"Pair your device with comma connect (connect.comma.ai) and claim your comma " -"prime offer." -msgstr "" -"デバイスを comma connect(connect.comma.ai)とペアリングして、comma prime 特" -"典を受け取りましょう。" +msgid "Pair your device with comma connect (connect.comma.ai) and claim your comma prime offer." +msgstr "デバイスを comma connect(connect.comma.ai)とペアリングして、comma prime 特典を受け取りましょう。" -#: selfdrive/ui/widgets/setup.py:91 +#: openpilot/selfdrive/ui/widgets/setup.py:91 #, python-format msgid "Please connect to Wi-Fi to complete initial pairing" msgstr "初回ペアリングを完了するにはWi‑Fiに接続してください" -#: selfdrive/ui/layouts/settings/device.py:55 -#: selfdrive/ui/layouts/settings/device.py:187 +#: openpilot/selfdrive/ui/layouts/settings/device.py:183 +#: openpilot/selfdrive/ui/layouts/settings/device.py:53 #, python-format msgid "Power Off" msgstr "電源オフ" -#: system/ui/widgets/network.py:144 +#: system/ui/widgets/network.py:141 #, python-format msgid "Prevent large data uploads when on a metered Wi-Fi connection" msgstr "従量課金のWi‑Fi接続時は大きなデータのアップロードを抑制" -#: system/ui/widgets/network.py:135 +#: system/ui/widgets/network.py:132 #, python-format msgid "Prevent large data uploads when on a metered cellular connection" msgstr "従量課金の携帯回線接続時は大きなデータのアップロードを抑制" -#: selfdrive/ui/layouts/settings/device.py:25 -msgid "" -"Preview the driver facing camera to ensure that driver monitoring has good " -"visibility. (vehicle must be off)" -msgstr "" -"ドライバー向きカメラのプレビューでモニタリングの視界を確認します。(車両は停" -"止状態である必要があります)" +#: openpilot/selfdrive/ui/layouts/settings/device.py:24 +msgid "Preview the driver facing camera to ensure that driver monitoring has good visibility. (vehicle must be off)" +msgstr "ドライバー向きカメラのプレビューでモニタリングの視界を確認します。(車両は停止状態である必要があります)" -#: selfdrive/ui/widgets/pairing_dialog.py:161 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:150 #, python-format msgid "QR Code Error" msgstr "QRコードエラー" -#: selfdrive/ui/widgets/ssh_key.py:31 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:31 msgid "REMOVE" msgstr "削除" -#: selfdrive/ui/layouts/settings/device.py:51 +#: openpilot/selfdrive/ui/layouts/settings/device.py:49 #, python-format msgid "RESET" msgstr "リセット" -#: selfdrive/ui/layouts/settings/device.py:65 +#: openpilot/selfdrive/ui/layouts/settings/device.py:63 #, python-format msgid "REVIEW" msgstr "確認" -#: selfdrive/ui/layouts/settings/device.py:55 -#: selfdrive/ui/layouts/settings/device.py:175 +#: openpilot/selfdrive/ui/layouts/settings/device.py:171 +#: openpilot/selfdrive/ui/layouts/settings/device.py:53 #, python-format msgid "Reboot" msgstr "再起動" -#: selfdrive/ui/onroad/alert_renderer.py:66 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:66 #, python-format msgid "Reboot Device" msgstr "デバイスを再起動" -#: selfdrive/ui/widgets/offroad_alerts.py:112 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:112 #, python-format msgid "Reboot and Update" msgstr "再起動して更新" -#: selfdrive/ui/layouts/settings/toggles.py:27 -msgid "" -"Receive alerts to steer back into the lane when your vehicle drifts over a " -"detected lane line without a turn signal activated while driving over 31 mph " -"(50 km/h)." -msgstr "" -"時速31mph(50km/h)を超えて走行中にウインカーを出さず検出された車線を外れた場" -"合、車線内に戻るよう警告を受け取ります。" - -#: selfdrive/ui/layouts/settings/toggles.py:76 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:76 #, python-format msgid "Record and Upload Driver Camera" msgstr "ドライバーカメラを記録してアップロード" -#: selfdrive/ui/layouts/settings/toggles.py:82 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:82 #, python-format msgid "Record and Upload Microphone Audio" msgstr "マイク音声を記録してアップロード" -#: selfdrive/ui/layouts/settings/toggles.py:33 -msgid "" -"Record and store microphone audio while driving. The audio will be included " -"in the dashcam video in comma connect." -msgstr "" -"走行中にマイク音声を記録・保存します。音声は comma connect のドライブレコー" -"ダー動画に含まれます。" +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:33 +msgid "Record and store microphone audio while driving. The audio will be included in the dashcam video in comma connect." +msgstr "走行中にマイク音声を記録・保存します。音声は comma connect のドライブレコーダー動画に含まれます。" -#: selfdrive/ui/layouts/settings/device.py:67 +#: openpilot/selfdrive/ui/layouts/settings/device.py:65 #, python-format msgid "Regulatory" msgstr "規制情報" -#: selfdrive/ui/layouts/settings/toggles.py:98 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:98 #, python-format msgid "Relaxed" msgstr "リラックス" -#: selfdrive/ui/widgets/prime.py:47 +#: openpilot/selfdrive/ui/widgets/prime.py:47 #, python-format msgid "Remote access" msgstr "リモートアクセス" -#: selfdrive/ui/widgets/prime.py:47 +#: openpilot/selfdrive/ui/widgets/prime.py:47 #, python-format msgid "Remote snapshots" msgstr "リモートスナップショット" -#: selfdrive/ui/widgets/ssh_key.py:123 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:124 #, python-format msgid "Request timed out" msgstr "リクエストがタイムアウトしました" -#: selfdrive/ui/layouts/settings/device.py:119 +#: openpilot/selfdrive/ui/layouts/settings/device.py:111 #, python-format msgid "Reset" msgstr "リセット" -#: selfdrive/ui/layouts/settings/device.py:51 +#: openpilot/selfdrive/ui/layouts/settings/device.py:49 #, python-format msgid "Reset Calibration" msgstr "キャリブレーションをリセット" -#: selfdrive/ui/layouts/settings/device.py:65 +#: openpilot/selfdrive/ui/layouts/settings/device.py:63 #, python-format msgid "Review Training Guide" msgstr "トレーニングガイドを確認" -#: selfdrive/ui/layouts/settings/device.py:27 +#: openpilot/selfdrive/ui/layouts/settings/device.py:26 msgid "Review the rules, features, and limitations of openpilot" msgstr "openpilotのルール、機能、制限を確認" -#: selfdrive/ui/layouts/settings/software.py:61 +#: openpilot/selfdrive/ui/layouts/settings/software.py:68 #, python-format msgid "SELECT" msgstr "選択" -#: selfdrive/ui/layouts/settings/developer.py:53 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:53 #, python-format msgid "SSH Keys" msgstr "SSH鍵" -#: system/ui/widgets/network.py:310 +#: system/ui/widgets/network.py:316 #, python-format msgid "Scanning Wi-Fi networks..." msgstr "Wi‑Fiネットワークを検索中..." -#: system/ui/widgets/option_dialog.py:36 +#: system/ui/widgets/option_dialog.py:37 #, python-format msgid "Select" msgstr "選択" -#: selfdrive/ui/layouts/settings/software.py:183 +#: openpilot/selfdrive/ui/layouts/settings/software.py:203 #, python-format msgid "Select a branch" msgstr "ブランチを選択" -#: selfdrive/ui/layouts/settings/device.py:91 +#: openpilot/selfdrive/ui/layouts/settings/device.py:89 #, python-format msgid "Select a language" msgstr "言語を選択" -#: selfdrive/ui/layouts/settings/device.py:60 +#: openpilot/selfdrive/ui/layouts/settings/device.py:58 #, python-format msgid "Serial" msgstr "シリアル" -#: selfdrive/ui/widgets/offroad_alerts.py:106 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:106 #, python-format msgid "Snooze Update" msgstr "更新を後で通知" -#: selfdrive/ui/layouts/settings/settings.py:65 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:62 msgid "Software" msgstr "ソフトウェア" -#: selfdrive/ui/layouts/settings/toggles.py:98 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:98 #, python-format msgid "Standard" msgstr "スタンダード" -#: selfdrive/ui/layouts/settings/toggles.py:22 -msgid "" -"Standard is recommended. In aggressive mode, openpilot will follow lead cars " -"closer and be more aggressive with the gas and brake. In relaxed mode " -"openpilot will stay further away from lead cars. On supported cars, you can " -"cycle through these personalities with your steering wheel distance button." -msgstr "" -"標準を推奨します。アグレッシブでは前走車に近づき、加減速も積極的になります。" -"リラックスでは前走車との距離を保ちます。対応車種ではステアリングの車間ボタン" -"でこれらの性格を切り替えられます。" - -#: selfdrive/ui/onroad/alert_renderer.py:59 -#: selfdrive/ui/onroad/alert_renderer.py:65 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:59 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:65 #, python-format msgid "System Unresponsive" msgstr "システムが応答しません" -#: selfdrive/ui/onroad/alert_renderer.py:58 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:58 #, python-format msgid "TAKE CONTROL IMMEDIATELY" msgstr "すぐに手動介入してください" -#: selfdrive/ui/layouts/sidebar.py:71 selfdrive/ui/layouts/sidebar.py:125 -#: selfdrive/ui/layouts/sidebar.py:127 selfdrive/ui/layouts/sidebar.py:129 +#: openpilot/selfdrive/ui/layouts/sidebar.py:71 +#: openpilot/selfdrive/ui/layouts/sidebar.py:125 +#: openpilot/selfdrive/ui/layouts/sidebar.py:127 +#: openpilot/selfdrive/ui/layouts/sidebar.py:129 msgid "TEMP" msgstr "温度" -#: selfdrive/ui/layouts/settings/software.py:61 +#: openpilot/selfdrive/ui/layouts/settings/software.py:68 #, python-format msgid "Target Branch" msgstr "対象ブランチ" -#: system/ui/widgets/network.py:124 +#: system/ui/widgets/network.py:121 #, python-format msgid "Tethering Password" msgstr "テザリングのパスワード" -#: selfdrive/ui/layouts/settings/settings.py:64 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:61 msgid "Toggles" msgstr "トグル" -#: selfdrive/ui/layouts/settings/software.py:72 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:79 +#, python-format +msgid "UI Debug Mode" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/software.py:79 #, python-format msgid "UNINSTALL" msgstr "アンインストール" -#: selfdrive/ui/layouts/home.py:155 +#: openpilot/selfdrive/ui/layouts/home.py:155 #, python-format msgid "UPDATE" msgstr "更新" -#: selfdrive/ui/layouts/settings/software.py:72 -#: selfdrive/ui/layouts/settings/software.py:163 +#: openpilot/selfdrive/ui/layouts/settings/software.py:173 +#: openpilot/selfdrive/ui/layouts/settings/software.py:79 #, python-format msgid "Uninstall" msgstr "アンインストール" -#: selfdrive/ui/layouts/sidebar.py:117 +#: openpilot/selfdrive/ui/layouts/sidebar.py:117 msgid "Unknown" msgstr "不明" -#: selfdrive/ui/layouts/settings/software.py:48 +#: openpilot/selfdrive/ui/layouts/settings/software.py:55 #, python-format msgid "Updates are only downloaded while the car is off." msgstr "アップデートは車両の電源が切れている間のみダウンロードされます。" -#: selfdrive/ui/widgets/prime.py:33 +#: openpilot/selfdrive/ui/widgets/prime.py:33 #, python-format msgid "Upgrade Now" msgstr "今すぐアップグレード" -#: selfdrive/ui/layouts/settings/toggles.py:31 -msgid "" -"Upload data from the driver facing camera and help improve the driver " -"monitoring algorithm." -msgstr "" -"ドライバー向きカメラのデータをアップロードしてモニタリングアルゴリズムの改善" -"に協力してください。" +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:31 +msgid "Upload data from the driver facing camera and help improve the driver monitoring algorithm." +msgstr "ドライバー向きカメラのデータをアップロードしてモニタリングアルゴリズムの改善に協力してください。" -#: selfdrive/ui/layouts/settings/toggles.py:88 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:88 #, python-format msgid "Use Metric System" msgstr "メートル法を使用" -#: selfdrive/ui/layouts/settings/toggles.py:17 -msgid "" -"Use the openpilot system for adaptive cruise control and lane keep driver " -"assistance. Your attention is required at all times to use this feature." -msgstr "" -"ACCと車線維持支援にopenpilotを使用します。本機能の使用中は常に注意が必要で" -"す。" - -#: selfdrive/ui/layouts/sidebar.py:72 selfdrive/ui/layouts/sidebar.py:144 +#: openpilot/selfdrive/ui/layouts/sidebar.py:72 +#: openpilot/selfdrive/ui/layouts/sidebar.py:144 msgid "VEHICLE" msgstr "車両" -#: selfdrive/ui/layouts/settings/device.py:67 +#: openpilot/selfdrive/ui/layouts/settings/device.py:65 #, python-format msgid "VIEW" msgstr "表示" -#: selfdrive/ui/onroad/alert_renderer.py:52 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:52 #, python-format msgid "Waiting to start" msgstr "開始待機中" -#: selfdrive/ui/layouts/settings/developer.py:19 -msgid "" -"Warning: This grants SSH access to all public keys in your GitHub settings. " -"Never enter a GitHub username other than your own. A comma employee will " -"NEVER ask you to add their GitHub username." -msgstr "" -"警告: これはGitHub設定内のすべての公開鍵にSSHアクセスを与えます。自分以外の" -"GitHubユーザー名を絶対に入力しないでください。comma の従業員が自分のGitHub" -"ユーザー名を追加するよう求めることは決してありません。" - -#: selfdrive/ui/layouts/onboarding.py:111 +#: openpilot/selfdrive/ui/layouts/onboarding.py:115 #, python-format msgid "Welcome to openpilot" msgstr "openpilotへようこそ" -#: selfdrive/ui/layouts/settings/toggles.py:20 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:20 msgid "When enabled, pressing the accelerator pedal will disengage openpilot." msgstr "有効にすると、アクセルを踏むとopenpilotが解除されます。" -#: selfdrive/ui/layouts/sidebar.py:44 +#: openpilot/selfdrive/ui/layouts/sidebar.py:44 msgid "Wi-Fi" msgstr "Wi‑Fi" -#: system/ui/widgets/network.py:144 +#: system/ui/widgets/network.py:141 #, python-format msgid "Wi-Fi Network Metered" msgstr "Wi‑Fiネットワーク(従量課金)" -#: system/ui/widgets/network.py:314 +#: system/ui/widgets/network.py:320 #, python-format msgid "Wrong password" msgstr "パスワードが違います" -#: selfdrive/ui/layouts/onboarding.py:145 +#: openpilot/selfdrive/ui/layouts/onboarding.py:149 #, python-format msgid "You must accept the Terms and Conditions in order to use openpilot." msgstr "openpilotを使用するには、利用規約に同意する必要があります。" -#: selfdrive/ui/layouts/onboarding.py:112 +#: openpilot/selfdrive/ui/layouts/onboarding.py:116 #, python-format -msgid "" -"You must accept the Terms and Conditions to use openpilot. Read the latest " -"terms at https://comma.ai/terms before continuing." -msgstr "" -"openpilotを使用するには利用規約に同意する必要があります。続行する前に " -"https://comma.ai/terms の最新の規約をお読みください。" +msgid "You must accept the Terms and Conditions to use openpilot. Read the latest terms at https://comma.ai/terms before continuing." +msgstr "openpilotを使用するには利用規約に同意する必要があります。続行する前に https://comma.ai/terms の最新の規約をお読みください。" -#: selfdrive/ui/onroad/driver_camera_dialog.py:34 +#: openpilot/selfdrive/ui/onroad/driver_camera_dialog.py:38 #, python-format msgid "camera starting" msgstr "カメラを起動中" -#: selfdrive/ui/widgets/prime.py:63 +#: openpilot/selfdrive/ui/layouts/settings/software.py:19 +#, python-format +msgid "checking..." +msgstr "" + +#: openpilot/selfdrive/ui/widgets/prime.py:63 #, python-format msgid "comma prime" msgstr "comma prime" -#: system/ui/widgets/network.py:142 +#: system/ui/widgets/network.py:139 #, python-format msgid "default" msgstr "既定" -#: selfdrive/ui/layouts/settings/device.py:133 +#: openpilot/selfdrive/ui/layouts/settings/device.py:125 #, python-format msgid "down" msgstr "下" -#: selfdrive/ui/layouts/settings/software.py:106 +#: openpilot/selfdrive/ui/layouts/settings/software.py:20 +#, python-format +msgid "downloading..." +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/software.py:116 #, python-format msgid "failed to check for update" msgstr "アップデートの確認に失敗しました" -#: system/ui/widgets/network.py:237 system/ui/widgets/network.py:314 +#: openpilot/selfdrive/ui/layouts/settings/software.py:21 +#, python-format +msgid "finalizing update..." +msgstr "" + +#: system/ui/widgets/network.py:238 +#: system/ui/widgets/network.py:321 #, python-format msgid "for \"{}\"" msgstr "「{}」向け" -#: selfdrive/ui/onroad/hud_renderer.py:177 +#: openpilot/selfdrive/ui/onroad/hud_renderer.py:177 #, python-format msgid "km/h" msgstr "km/h" -#: system/ui/widgets/network.py:204 +#: system/ui/widgets/network.py:201 #, python-format msgid "leave blank for automatic configuration" msgstr "自動設定の場合は空欄のままにしてください" -#: selfdrive/ui/layouts/settings/device.py:134 +#: openpilot/selfdrive/ui/layouts/settings/device.py:126 #, python-format msgid "left" msgstr "左" -#: system/ui/widgets/network.py:142 +#: system/ui/widgets/network.py:139 #, python-format msgid "metered" msgstr "従量" -#: selfdrive/ui/onroad/hud_renderer.py:177 +#: openpilot/selfdrive/ui/onroad/hud_renderer.py:177 #, python-format msgid "mph" msgstr "mph" -#: selfdrive/ui/layouts/settings/software.py:20 +#: openpilot/selfdrive/ui/layouts/settings/software.py:27 #, python-format msgid "never" msgstr "なし" -#: selfdrive/ui/layouts/settings/software.py:31 +#: openpilot/selfdrive/ui/layouts/settings/software.py:38 #, python-format msgid "now" msgstr "今" -#: selfdrive/ui/layouts/settings/developer.py:71 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:71 #, python-format msgid "openpilot Longitudinal Control (Alpha)" msgstr "openpilot 縦制御(アルファ)" -#: selfdrive/ui/onroad/alert_renderer.py:51 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:51 #, python-format msgid "openpilot Unavailable" msgstr "openpilotは利用できません" -#: selfdrive/ui/layouts/settings/toggles.py:158 -#, python-format -msgid "" -"openpilot defaults to driving in chill mode. Experimental mode enables alpha-" -"level features that aren't ready for chill mode. Experimental features are " -"listed below:

End-to-End Longitudinal Control


Let the driving " -"model control the gas and brakes. openpilot will drive as it thinks a human " -"would, including stopping for red lights and stop signs. Since the driving " -"model decides the speed to drive, the set speed will only act as an upper " -"bound. This is an alpha quality feature; mistakes should be expected." -"

New Driving Visualization


The driving visualization will " -"transition to the road-facing wide-angle camera at low speeds to better show " -"some turns. The Experimental mode logo will also be shown in the top right " -"corner." -msgstr "" -"openpilotは既定でチルモードで走行します。実験モードでは、チルモードにはまだ準" -"備ができていないアルファレベルの機能が有効になります。実験的な機能は以下のと" -"おりです:

エンドツーエンド縦制御


運転モデルがアクセルとブレー" -"キを制御します。openpilotは人間のように走行し、赤信号や一時停止でも停止しま" -"す。走行速度は運転モデルが決めるため、設定速度は上限としてのみ機能します。こ" -"れはアルファ品質の機能であり、誤動作が発生する可能性があります。

新し" -"い運転ビジュアライゼーション


低速時には道路向きの広角カメラに切り替わ" -"り、一部の曲がりをより良く表示します。画面右上には実験モードのロゴも表示され" -"ます。" - -#: selfdrive/ui/layouts/settings/device.py:165 -#, python-format -msgid "" -"openpilot is continuously calibrating, resetting is rarely required. " -"Resetting calibration will restart openpilot if the car is powered on." -msgstr "" -"openpilotは継続的にキャリブレーションを行っており、リセットが必要になることは" -"稀です。車が起動中にキャリブレーションをリセットするとopenpilotが再起動しま" -"す。" - -#: selfdrive/ui/layouts/settings/firehose.py:20 -msgid "" -"openpilot learns to drive by watching humans, like you, drive.\n" -"\n" -"Firehose Mode allows you to maximize your training data uploads to improve " -"openpilot's driving models. More data means bigger models, which means " -"better Experimental Mode." -msgstr "" -"openpilotは、あなたのような人間の運転を見て運転を学習します。\n" -"\n" -"Firehoseモードを使うと、学習データのアップロードを最大化してopenpilotの運転モ" -"デルを改善できます。データが増えるほどモデルが大きくなり、実験モードがより良" -"くなります。" - -#: selfdrive/ui/layouts/settings/toggles.py:183 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:184 #, python-format msgid "openpilot longitudinal control may come in a future update." msgstr "openpilotの縦制御は将来のアップデートで提供される可能性があります。" -#: selfdrive/ui/layouts/settings/device.py:26 -msgid "" -"openpilot requires the device to be mounted within 4° left or right and " -"within 5° up or 9° down." -msgstr "" -"openpilotでは、デバイスの取り付け角度が左右±4°、上方向5°以内、下方向9°以内で" -"ある必要があります。" +#: openpilot/selfdrive/ui/layouts/settings/device.py:25 +msgid "openpilot requires the device to be mounted within 4° left or right and within 5° up or 9° down." +msgstr "openpilotでは、デバイスの取り付け角度が左右±4°、上方向5°以内、下方向9°以内である必要があります。" -#: selfdrive/ui/layouts/settings/device.py:134 +#: openpilot/selfdrive/ui/layouts/settings/device.py:126 #, python-format msgid "right" msgstr "右" -#: system/ui/widgets/network.py:142 +#: system/ui/widgets/network.py:139 #, python-format msgid "unmetered" msgstr "非従量" -#: selfdrive/ui/layouts/settings/device.py:133 +#: openpilot/selfdrive/ui/layouts/settings/device.py:125 #, python-format msgid "up" msgstr "上" -#: selfdrive/ui/layouts/settings/software.py:117 +#: openpilot/selfdrive/ui/layouts/settings/software.py:127 #, python-format msgid "up to date, last checked never" msgstr "最新です。最終確認: なし" -#: selfdrive/ui/layouts/settings/software.py:115 +#: openpilot/selfdrive/ui/layouts/settings/software.py:125 #, python-format msgid "up to date, last checked {}" msgstr "最新です。最終確認: {}" -#: selfdrive/ui/layouts/settings/software.py:109 +#: openpilot/selfdrive/ui/layouts/settings/software.py:119 #, python-format msgid "update available" msgstr "更新があります" -#: selfdrive/ui/layouts/home.py:169 +#: openpilot/selfdrive/ui/layouts/home.py:169 #, python-format msgid "{} ALERT" msgid_plural "{} ALERTS" msgstr[0] "{}件のアラート" -#: selfdrive/ui/layouts/settings/software.py:40 +#: openpilot/selfdrive/ui/layouts/settings/software.py:47 #, python-format msgid "{} day ago" msgid_plural "{} days ago" msgstr[0] "{}日前" -#: selfdrive/ui/layouts/settings/software.py:37 +#: openpilot/selfdrive/ui/layouts/settings/software.py:44 #, python-format msgid "{} hour ago" msgid_plural "{} hours ago" msgstr[0] "{}時間前" -#: selfdrive/ui/layouts/settings/software.py:34 +#: openpilot/selfdrive/ui/layouts/settings/software.py:41 #, python-format msgid "{} minute ago" msgid_plural "{} minutes ago" msgstr[0] "{}分前" -#: selfdrive/ui/layouts/settings/firehose.py:111 +#: openpilot/selfdrive/ui/layouts/settings/firehose.py:70 #, python-format msgid "{} segment of your driving is in the training dataset so far." msgid_plural "{} segments of your driving is in the training dataset so far." -msgstr[0] "" -"これまでにあなたの走行の{}セグメントが学習データセットに含まれています。" +msgstr[0] "これまでにあなたの走行の{}セグメントが学習データセットに含まれています。" -#: selfdrive/ui/widgets/prime.py:62 +#: openpilot/selfdrive/ui/widgets/prime.py:62 #, python-format msgid "✓ SUBSCRIBED" msgstr "✓ 登録済み" -#: selfdrive/ui/widgets/setup.py:22 +#: openpilot/selfdrive/ui/widgets/setup.py:21 #, python-format msgid "🔥 Firehose Mode 🔥" msgstr "🔥 Firehoseモード 🔥" + diff --git a/selfdrive/ui/translations/app_ko.po b/selfdrive/ui/translations/app_ko.po index f12aebaeb3..9b73a22387 100644 --- a/selfdrive/ui/translations/app_ko.po +++ b/selfdrive/ui/translations/app_ko.po @@ -17,1174 +17,1013 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=1; plural=0;\n" -#: selfdrive/ui/layouts/settings/device.py:160 +#: openpilot/selfdrive/ui/layouts/settings/device.py:152 #, python-format msgid " Steering torque response calibration is complete." msgstr " 스티어링 토크 응답 보정이 완료되었습니다." -#: selfdrive/ui/layouts/settings/device.py:158 +#: openpilot/selfdrive/ui/layouts/settings/device.py:150 #, python-format msgid " Steering torque response calibration is {}% complete." msgstr " 스티어링 토크 응답 보정이 {}% 완료되었습니다." -#: selfdrive/ui/layouts/settings/device.py:133 +#: openpilot/selfdrive/ui/layouts/settings/device.py:125 #, python-format msgid " Your device is pointed {:.1f}° {} and {:.1f}° {}." msgstr " 장치는 {:.1f}° {} 및 {:.1f}° {} 방향을 가리키고 있습니다." -#: selfdrive/ui/layouts/sidebar.py:43 +#: openpilot/selfdrive/ui/layouts/sidebar.py:43 msgid "--" msgstr "--" -#: selfdrive/ui/widgets/prime.py:47 +#: openpilot/selfdrive/ui/widgets/prime.py:47 #, python-format msgid "1 year of drive storage" msgstr "주행 데이터 1년 보관" -#: selfdrive/ui/widgets/prime.py:47 +#: openpilot/selfdrive/ui/widgets/prime.py:47 #, python-format msgid "24/7 LTE connectivity" msgstr "연중무휴 LTE 연결" -#: selfdrive/ui/layouts/sidebar.py:46 +#: openpilot/selfdrive/ui/layouts/sidebar.py:46 msgid "2G" msgstr "2G" -#: selfdrive/ui/layouts/sidebar.py:47 +#: openpilot/selfdrive/ui/layouts/sidebar.py:47 msgid "3G" msgstr "3G" -#: selfdrive/ui/layouts/sidebar.py:49 +#: openpilot/selfdrive/ui/layouts/sidebar.py:49 msgid "5G" msgstr "5G" -#: selfdrive/ui/layouts/settings/developer.py:23 -msgid "" -"WARNING: openpilot longitudinal control is in alpha for this car and will " -"disable Automatic Emergency Braking (AEB).

On this car, openpilot " -"defaults to the car's built-in ACC instead of openpilot's longitudinal " -"control. Enable this to switch to openpilot longitudinal control. Enabling " -"Experimental mode is recommended when enabling openpilot longitudinal " -"control alpha. Changing this setting will restart openpilot if the car is " -"powered on." -msgstr "" -"경고: 이 차량에서 openpilot의 롱컨 제어는 알파 버전이며 자동 긴급 제동" -"(AEB)을 비활성화합니다.

이 차량에서는 openpilot 롱컨 제어 대신 " -"차량 내장 ACC가 기본으로 사용됩니다. openpilot 롱컨 제어로 전환하려면 이 설" -"정을 켜세요. 롱컨 제어 알파를 켤 때는 실험 모드 사용을 권장합니다. 차량 전" -"원이 켜져 있는 경우 이 설정을 변경하면 openpilot이 재시작됩니다." - -#: selfdrive/ui/layouts/settings/device.py:148 +#: openpilot/selfdrive/ui/layouts/settings/device.py:140 #, python-format msgid "

Steering lag calibration is complete." msgstr "

스티어링 지연 보정이 완료되었습니다." -#: selfdrive/ui/layouts/settings/device.py:146 +#: openpilot/selfdrive/ui/layouts/settings/device.py:138 #, python-format msgid "

Steering lag calibration is {}% complete." msgstr "

스티어링 지연 보정이 {}% 완료되었습니다." -#: selfdrive/ui/layouts/settings/firehose.py:138 -#, python-format -msgid "ACTIVE" -msgstr "활성" - -#: selfdrive/ui/layouts/settings/developer.py:15 -msgid "" -"ADB (Android Debug Bridge) allows connecting to your device over USB or over " -"the network. See https://docs.comma.ai/how-to/connect-to-comma for more info." -msgstr "" -"ADB(Android Debug Bridge)를 사용하면 USB 또는 네트워크로 장치에 연결할 수 있" -"습니다. 자세한 내용은 https://docs.comma.ai/how-to/connect-to-comma 를 참고하" -"세요." - -#: selfdrive/ui/widgets/ssh_key.py:30 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:30 msgid "ADD" msgstr "추가" -#: system/ui/widgets/network.py:139 +#: system/ui/widgets/network.py:136 #, python-format msgid "APN Setting" msgstr "APN 설정" -#: selfdrive/ui/widgets/offroad_alerts.py:109 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:109 #, python-format msgid "Acknowledge Excessive Actuation" msgstr "과도한 작동을 확인" -#: system/ui/widgets/network.py:74 system/ui/widgets/network.py:95 +#: system/ui/widgets/network.py:92 +#: system/ui/widgets/network.py:74 #, python-format msgid "Advanced" msgstr "고급" -#: selfdrive/ui/layouts/settings/toggles.py:98 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:98 #, python-format msgid "Aggressive" msgstr "공격적" -#: selfdrive/ui/layouts/onboarding.py:116 +#: openpilot/selfdrive/ui/layouts/onboarding.py:120 #, python-format msgid "Agree" msgstr "동의" -#: selfdrive/ui/layouts/settings/toggles.py:70 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:70 #, python-format msgid "Always-On Driver Monitoring" msgstr "운전자 모니터링 항상 켜짐" -#: selfdrive/ui/layouts/settings/toggles.py:186 -#, python-format -msgid "" -"An alpha version of openpilot longitudinal control can be tested, along with " -"Experimental mode, on non-release branches." -msgstr "" -"openpilot 롱컨 제어 알파 버전은 실험 모드와 함께 비릴리스 브랜치에서 테스트" -"할 수 있습니다." - -#: selfdrive/ui/layouts/settings/device.py:187 +#: openpilot/selfdrive/ui/layouts/settings/device.py:183 #, python-format msgid "Are you sure you want to power off?" msgstr "정말 전원을 끄시겠습니까?" -#: selfdrive/ui/layouts/settings/device.py:175 +#: openpilot/selfdrive/ui/layouts/settings/device.py:171 #, python-format msgid "Are you sure you want to reboot?" msgstr "정말 재시작하시겠습니까?" -#: selfdrive/ui/layouts/settings/device.py:119 +#: openpilot/selfdrive/ui/layouts/settings/device.py:111 #, python-format msgid "Are you sure you want to reset calibration?" msgstr "정말 보정을 재설정하시겠습니까?" -#: selfdrive/ui/layouts/settings/software.py:163 +#: openpilot/selfdrive/ui/layouts/settings/software.py:173 #, python-format msgid "Are you sure you want to uninstall?" msgstr "정말 제거하시겠습니까?" -#: system/ui/widgets/network.py:99 selfdrive/ui/layouts/onboarding.py:147 +#: system/ui/widgets/network.py:96 +#: openpilot/selfdrive/ui/layouts/onboarding.py:151 #, python-format msgid "Back" msgstr "뒤로" -#: selfdrive/ui/widgets/prime.py:38 +#: openpilot/selfdrive/ui/widgets/prime.py:38 #, python-format msgid "Become a comma prime member at connect.comma.ai" msgstr "connect.comma.ai에서 comma prime 회원이 되세요" -#: selfdrive/ui/widgets/pairing_dialog.py:130 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:119 #, python-format msgid "Bookmark connect.comma.ai to your home screen to use it like an app" msgstr "connect.comma.ai를 홈 화면에 추가하여 앱처럼 사용하세요" -#: selfdrive/ui/layouts/settings/device.py:68 +#: openpilot/selfdrive/ui/layouts/settings/device.py:66 #, python-format msgid "CHANGE" msgstr "변경" -#: selfdrive/ui/layouts/settings/software.py:50 -#: selfdrive/ui/layouts/settings/software.py:107 -#: selfdrive/ui/layouts/settings/software.py:118 -#: selfdrive/ui/layouts/settings/software.py:147 +#: openpilot/selfdrive/ui/layouts/settings/software.py:157 +#: openpilot/selfdrive/ui/layouts/settings/software.py:57 +#: openpilot/selfdrive/ui/layouts/settings/software.py:117 +#: openpilot/selfdrive/ui/layouts/settings/software.py:128 #, python-format msgid "CHECK" msgstr "확인" -#: selfdrive/ui/widgets/exp_mode_button.py:50 +#: openpilot/selfdrive/ui/widgets/exp_mode_button.py:51 #, python-format msgid "CHILL MODE ON" msgstr "안정적 모드 켜짐" -#: system/ui/widgets/network.py:155 selfdrive/ui/layouts/sidebar.py:73 -#: selfdrive/ui/layouts/sidebar.py:134 selfdrive/ui/layouts/sidebar.py:136 -#: selfdrive/ui/layouts/sidebar.py:138 +#: system/ui/widgets/network.py:152 +#: openpilot/selfdrive/ui/layouts/sidebar.py:73 +#: openpilot/selfdrive/ui/layouts/sidebar.py:134 +#: openpilot/selfdrive/ui/layouts/sidebar.py:136 +#: openpilot/selfdrive/ui/layouts/sidebar.py:138 #, python-format msgid "CONNECT" msgstr "연결" -#: system/ui/widgets/network.py:369 +#: system/ui/widgets/network.py:376 #, python-format msgid "CONNECTING..." msgstr "연결 중..." -#: system/ui/widgets/confirm_dialog.py:23 system/ui/widgets/option_dialog.py:35 -#: system/ui/widgets/keyboard.py:81 system/ui/widgets/network.py:318 +#: system/ui/widgets/network.py:326 +#: system/ui/widgets/confirm_dialog.py:24 +#: system/ui/widgets/option_dialog.py:36 +#: system/ui/widgets/keyboard.py:83 #, python-format msgid "Cancel" msgstr "취소" -#: system/ui/widgets/network.py:134 +#: system/ui/widgets/network.py:131 #, python-format msgid "Cellular Metered" msgstr "종량제 셀룰러" -#: selfdrive/ui/layouts/settings/device.py:68 +#: openpilot/selfdrive/ui/layouts/settings/device.py:66 #, python-format msgid "Change Language" msgstr "언어 변경" -#: selfdrive/ui/layouts/settings/toggles.py:125 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:125 #, python-format msgid "Changing this setting will restart openpilot if the car is powered on." msgstr "차량 전원이 켜져 있으면 이 설정을 변경할 때 openpilot이 재시작됩니다." -#: selfdrive/ui/widgets/pairing_dialog.py:129 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:118 #, python-format msgid "Click \"add new device\" and scan the QR code on the right" msgstr "\"add new device\"를 눌러 오른쪽의 QR 코드를 스캔하세요" -#: selfdrive/ui/widgets/offroad_alerts.py:104 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:104 #, python-format msgid "Close" msgstr "닫기" -#: selfdrive/ui/layouts/settings/software.py:49 +#: openpilot/selfdrive/ui/layouts/settings/software.py:56 #, python-format msgid "Current Version" msgstr "현재 버전" -#: selfdrive/ui/layouts/settings/software.py:110 +#: openpilot/selfdrive/ui/layouts/settings/software.py:120 #, python-format msgid "DOWNLOAD" msgstr "다운로드" -#: selfdrive/ui/layouts/onboarding.py:115 +#: openpilot/selfdrive/ui/layouts/onboarding.py:119 #, python-format msgid "Decline" msgstr "거부" -#: selfdrive/ui/layouts/onboarding.py:148 +#: openpilot/selfdrive/ui/layouts/onboarding.py:152 #, python-format msgid "Decline, uninstall openpilot" msgstr "거부하고 openpilot 제거" -#: selfdrive/ui/layouts/settings/settings.py:67 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:64 msgid "Developer" msgstr "개발자" -#: selfdrive/ui/layouts/settings/settings.py:62 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:59 msgid "Device" msgstr "장치" -#: selfdrive/ui/layouts/settings/toggles.py:58 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:58 #, python-format msgid "Disengage on Accelerator Pedal" msgstr "가속 페달로 해제" -#: selfdrive/ui/layouts/settings/device.py:184 +#: openpilot/selfdrive/ui/layouts/settings/device.py:176 #, python-format msgid "Disengage to Power Off" msgstr "해제 후 전원 끄기" -#: selfdrive/ui/layouts/settings/device.py:172 +#: openpilot/selfdrive/ui/layouts/settings/device.py:164 #, python-format msgid "Disengage to Reboot" msgstr "해제 후 재시작" -#: selfdrive/ui/layouts/settings/device.py:103 +#: openpilot/selfdrive/ui/layouts/settings/device.py:95 #, python-format msgid "Disengage to Reset Calibration" msgstr "해제 후 캘리브레이션 재설정" -#: selfdrive/ui/layouts/settings/toggles.py:32 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:32 msgid "Display speed in km/h instead of mph." msgstr "속도를 mph 대신 km/h로 표시합니다." -#: selfdrive/ui/layouts/settings/device.py:59 +#: openpilot/selfdrive/ui/layouts/settings/device.py:57 #, python-format msgid "Dongle ID" msgstr "동글 ID" -#: selfdrive/ui/layouts/settings/software.py:50 +#: openpilot/selfdrive/ui/layouts/settings/software.py:57 #, python-format msgid "Download" msgstr "다운로드" -#: selfdrive/ui/layouts/settings/device.py:62 +#: openpilot/selfdrive/ui/layouts/settings/device.py:60 #, python-format msgid "Driver Camera" msgstr "운전자 카메라" -#: selfdrive/ui/layouts/settings/toggles.py:96 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:96 #, python-format msgid "Driving Personality" msgstr "주행 성향" -#: system/ui/widgets/network.py:123 system/ui/widgets/network.py:139 +#: system/ui/widgets/network.py:120 +#: system/ui/widgets/network.py:136 #, python-format msgid "EDIT" msgstr "편집" -#: selfdrive/ui/layouts/sidebar.py:138 +#: openpilot/selfdrive/ui/layouts/sidebar.py:138 msgid "ERROR" msgstr "오류" -#: selfdrive/ui/layouts/sidebar.py:45 +#: openpilot/selfdrive/ui/layouts/sidebar.py:45 msgid "ETH" msgstr "ETH" -#: selfdrive/ui/widgets/exp_mode_button.py:50 +#: openpilot/selfdrive/ui/widgets/exp_mode_button.py:51 #, python-format msgid "EXPERIMENTAL MODE ON" msgstr "실험 모드 켜짐" -#: selfdrive/ui/layouts/settings/developer.py:166 -#: selfdrive/ui/layouts/settings/toggles.py:228 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:229 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:180 #, python-format msgid "Enable" msgstr "사용" -#: selfdrive/ui/layouts/settings/developer.py:39 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:39 #, python-format msgid "Enable ADB" msgstr "ADB 사용" -#: selfdrive/ui/layouts/settings/toggles.py:64 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:64 #, python-format msgid "Enable Lane Departure Warnings" msgstr "차선 이탈 경고 사용" -#: system/ui/widgets/network.py:129 +#: system/ui/widgets/network.py:126 #, python-format msgid "Enable Roaming" msgstr "로밍 사용" -#: selfdrive/ui/layouts/settings/developer.py:48 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:48 #, python-format msgid "Enable SSH" msgstr "SSH 사용" -#: system/ui/widgets/network.py:120 +#: system/ui/widgets/network.py:117 #, python-format msgid "Enable Tethering" msgstr "테더링 사용" -#: selfdrive/ui/layouts/settings/toggles.py:30 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:30 msgid "Enable driver monitoring even when openpilot is not engaged." msgstr "openpilot이 작동 중이 아닐 때도 운전자 모니터링을 사용합니다." -#: selfdrive/ui/layouts/settings/toggles.py:46 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:46 #, python-format msgid "Enable openpilot" msgstr "openpilot 사용" -#: selfdrive/ui/layouts/settings/toggles.py:189 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:190 #, python-format -msgid "" -"Enable the openpilot longitudinal control (alpha) toggle to allow " -"Experimental mode." +msgid "Enable the openpilot longitudinal control (alpha) toggle to allow Experimental mode." msgstr "실험 모드를 사용하려면 openpilot 롱컨 제어(알파) 토글을 켜세요." -#: system/ui/widgets/network.py:204 +#: system/ui/widgets/network.py:201 #, python-format msgid "Enter APN" msgstr "APN 입력" -#: system/ui/widgets/network.py:241 +#: system/ui/widgets/network.py:243 #, python-format msgid "Enter SSID" msgstr "SSID 입력" -#: system/ui/widgets/network.py:254 +#: system/ui/widgets/network.py:257 #, python-format msgid "Enter new tethering password" msgstr "새 테더링 비밀번호 입력" -#: system/ui/widgets/network.py:237 system/ui/widgets/network.py:314 +#: system/ui/widgets/network.py:238 +#: system/ui/widgets/network.py:320 #, python-format msgid "Enter password" msgstr "비밀번호 입력" -#: selfdrive/ui/widgets/ssh_key.py:89 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:89 #, python-format msgid "Enter your GitHub username" msgstr "GitHub 사용자 이름 입력" -#: system/ui/widgets/list_view.py:123 system/ui/widgets/list_view.py:160 +#: system/ui/widgets/list_view.py:123 +#: system/ui/widgets/list_view.py:160 #, python-format msgid "Error" msgstr "오류" -#: selfdrive/ui/layouts/settings/toggles.py:52 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:52 #, python-format msgid "Experimental Mode" msgstr "실험 모드" -#: selfdrive/ui/layouts/settings/toggles.py:181 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:182 #, python-format -msgid "" -"Experimental mode is currently unavailable on this car since the car's stock " -"ACC is used for longitudinal control." -msgstr "" -"이 차량은 롱컨 제어에 순정 ACC를 사용하므로 현재 실험 모드를 사용할 수 없습" -"니다." +msgid "Experimental mode is currently unavailable on this car since the car's stock ACC is used for longitudinal control." +msgstr "이 차량은 롱컨 제어에 순정 ACC를 사용하므로 현재 실험 모드를 사용할 수 없습니다." -#: system/ui/widgets/network.py:373 +#: system/ui/widgets/network.py:380 #, python-format msgid "FORGETTING..." msgstr "삭제 중..." -#: selfdrive/ui/widgets/setup.py:44 +#: openpilot/selfdrive/ui/widgets/setup.py:43 #, python-format msgid "Finish Setup" msgstr "설정 완료" -#: selfdrive/ui/layouts/settings/settings.py:66 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:63 msgid "Firehose" msgstr "파이어호스" -#: selfdrive/ui/layouts/settings/firehose.py:18 +#: openpilot/selfdrive/ui/layouts/settings/firehose.py:10 msgid "Firehose Mode" msgstr "파이어호스 모드" -#: selfdrive/ui/layouts/settings/firehose.py:25 -msgid "" -"For maximum effectiveness, bring your device inside and connect to a good " -"USB-C adapter and Wi-Fi weekly.\n" -"\n" -"Firehose Mode can also work while you're driving if connected to a hotspot " -"or unlimited SIM card.\n" -"\n" -"\n" -"Frequently Asked Questions\n" -"\n" -"Does it matter how or where I drive? Nope, just drive as you normally " -"would.\n" -"\n" -"Do all of my segments get pulled in Firehose Mode? No, we selectively pull a " -"subset of your segments.\n" -"\n" -"What's a good USB-C adapter? Any fast phone or laptop charger should be " -"fine.\n" -"\n" -"Does it matter which software I run? Yes, only upstream openpilot (and " -"particular forks) are able to be used for training." -msgstr "" -"최대의 효과를 위해 주 1회는 장치를 실내로 가져와 품질 좋은 USB‑C 어댑터와 " -"Wi‑Fi에 연결하세요.\n" -"\n" -"핫스팟이나 무제한 SIM에 연결되어 있다면 주행 중에도 파이어호스 모드가 동작합니" -"다.\n" -"\n" -"\n" -"자주 묻는 질문\n" -"\n" -"어떻게, 어디서 운전하는지가 중요한가요? 아니요. 평소처럼 운전하세요.\n" -"\n" -"파이어호스 모드에서 모든 구간을 가져가지나요? 아니요. 일부 구간만 선택" -"적으로 가져갑니다.\n" -"\n" -"좋은 USB‑C 어댑터는 무엇인가요? 빠른 휴대폰 또는 노트북 충전기면 충분합니" -"다.\n" -"\n" -"어떤 소프트웨어를 실행하는지가 중요한가요? 예. 학습에는 업스트림 " -"openpilot(및 일부 포크)만 사용할 수 있습니다." - -#: system/ui/widgets/network.py:318 system/ui/widgets/network.py:451 +#: system/ui/widgets/network.py:458 +#: system/ui/widgets/network.py:326 #, python-format msgid "Forget" msgstr "삭제" -#: system/ui/widgets/network.py:319 +#: system/ui/widgets/network.py:327 #, python-format msgid "Forget Wi-Fi Network \"{}\"?" msgstr "Wi‑Fi 네트워크 \"{}\"를 삭제하시겠습니까?" -#: selfdrive/ui/layouts/sidebar.py:71 selfdrive/ui/layouts/sidebar.py:125 +#: openpilot/selfdrive/ui/layouts/sidebar.py:71 +#: openpilot/selfdrive/ui/layouts/sidebar.py:125 msgid "GOOD" msgstr "양호" -#: selfdrive/ui/widgets/pairing_dialog.py:128 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:117 #, python-format msgid "Go to https://connect.comma.ai on your phone" msgstr "휴대폰에서 https://connect.comma.ai 에 접속하세요" -#: selfdrive/ui/layouts/sidebar.py:129 +#: openpilot/selfdrive/ui/layouts/sidebar.py:129 msgid "HIGH" msgstr "높음" -#: system/ui/widgets/network.py:155 +#: system/ui/widgets/network.py:152 #, python-format msgid "Hidden Network" msgstr "숨겨진 네트워크" -#: selfdrive/ui/layouts/settings/firehose.py:140 -#, python-format -msgid "INACTIVE: connect to an unmetered network" -msgstr "비활성: 비종량제 네트워크에 연결하세요" - -#: selfdrive/ui/layouts/settings/software.py:53 -#: selfdrive/ui/layouts/settings/software.py:136 +#: openpilot/selfdrive/ui/layouts/settings/software.py:60 +#: openpilot/selfdrive/ui/layouts/settings/software.py:146 #, python-format msgid "INSTALL" msgstr "설치" -#: system/ui/widgets/network.py:150 +#: system/ui/widgets/network.py:147 #, python-format msgid "IP Address" msgstr "IP 주소" -#: selfdrive/ui/layouts/settings/software.py:53 +#: openpilot/selfdrive/ui/layouts/settings/software.py:60 #, python-format msgid "Install Update" msgstr "업데이트 설치" -#: selfdrive/ui/layouts/settings/developer.py:56 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:56 #, python-format msgid "Joystick Debug Mode" msgstr "조이스틱 디버그 모드" -#: selfdrive/ui/widgets/ssh_key.py:29 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:29 msgid "LOADING" msgstr "로딩 중" -#: selfdrive/ui/layouts/sidebar.py:48 +#: openpilot/selfdrive/ui/layouts/sidebar.py:48 msgid "LTE" msgstr "LTE" -#: selfdrive/ui/layouts/settings/developer.py:64 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:64 #, python-format msgid "Longitudinal Maneuver Mode" msgstr "롱컨 기동 모드" -#: selfdrive/ui/onroad/hud_renderer.py:148 +#: openpilot/selfdrive/ui/onroad/hud_renderer.py:148 #, python-format msgid "MAX" msgstr "최대" -#: selfdrive/ui/widgets/setup.py:75 +#: openpilot/selfdrive/ui/widgets/setup.py:74 #, python-format -msgid "" -"Maximize your training data uploads to improve openpilot's driving models." +msgid "Maximize your training data uploads to improve openpilot's driving models." msgstr "학습 데이터 업로드를 최대화하여 openpilot의 주행 모델을 개선하세요." -#: selfdrive/ui/layouts/settings/device.py:59 -#: selfdrive/ui/layouts/settings/device.py:60 +#: openpilot/selfdrive/ui/layouts/settings/device.py:57 +#: openpilot/selfdrive/ui/layouts/settings/device.py:58 #, python-format msgid "N/A" msgstr "해당 없음" -#: selfdrive/ui/layouts/sidebar.py:142 +#: openpilot/selfdrive/ui/layouts/sidebar.py:142 msgid "NO" msgstr "아니오" -#: selfdrive/ui/layouts/settings/settings.py:63 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:60 msgid "Network" msgstr "네트워크" -#: selfdrive/ui/widgets/ssh_key.py:114 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:115 #, python-format msgid "No SSH keys found" msgstr "SSH 키를 찾을 수 없습니다" -#: selfdrive/ui/widgets/ssh_key.py:126 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:127 #, python-format msgid "No SSH keys found for user '{}'" msgstr "사용자 '{}'의 SSH 키를 찾을 수 없습니다" -#: selfdrive/ui/widgets/offroad_alerts.py:320 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:321 #, python-format msgid "No release notes available." msgstr "릴리스 노트가 없습니다." -#: selfdrive/ui/layouts/sidebar.py:73 selfdrive/ui/layouts/sidebar.py:134 +#: openpilot/selfdrive/ui/layouts/sidebar.py:73 +#: openpilot/selfdrive/ui/layouts/sidebar.py:134 msgid "OFFLINE" msgstr "오프라인" -#: system/ui/widgets/html_render.py:263 system/ui/widgets/confirm_dialog.py:93 -#: selfdrive/ui/layouts/sidebar.py:127 +#: system/ui/widgets/confirm_dialog.py:93 +#: system/ui/widgets/html_render.py:263 +#: openpilot/selfdrive/ui/layouts/sidebar.py:127 #, python-format msgid "OK" msgstr "확인" -#: selfdrive/ui/layouts/sidebar.py:72 selfdrive/ui/layouts/sidebar.py:136 -#: selfdrive/ui/layouts/sidebar.py:144 +#: openpilot/selfdrive/ui/layouts/sidebar.py:72 +#: openpilot/selfdrive/ui/layouts/sidebar.py:144 +#: openpilot/selfdrive/ui/layouts/sidebar.py:136 msgid "ONLINE" msgstr "온라인" -#: selfdrive/ui/widgets/setup.py:20 +#: openpilot/selfdrive/ui/widgets/setup.py:19 #, python-format msgid "Open" msgstr "열기" -#: selfdrive/ui/layouts/settings/device.py:48 +#: openpilot/selfdrive/ui/layouts/settings/device.py:45 #, python-format msgid "PAIR" msgstr "페어링" -#: selfdrive/ui/layouts/sidebar.py:142 +#: openpilot/selfdrive/ui/layouts/sidebar.py:142 msgid "PANDA" msgstr "PANDA" -#: selfdrive/ui/layouts/settings/device.py:62 +#: openpilot/selfdrive/ui/layouts/settings/device.py:60 #, python-format msgid "PREVIEW" msgstr "미리보기" -#: selfdrive/ui/widgets/prime.py:44 +#: openpilot/selfdrive/ui/widgets/prime.py:44 #, python-format msgid "PRIME FEATURES:" msgstr "프라임 기능:" -#: selfdrive/ui/layouts/settings/device.py:48 +#: openpilot/selfdrive/ui/layouts/settings/device.py:45 #, python-format msgid "Pair Device" msgstr "장치 페어링" -#: selfdrive/ui/widgets/setup.py:19 +#: openpilot/selfdrive/ui/widgets/setup.py:18 #, python-format msgid "Pair device" msgstr "장치 페어링" -#: selfdrive/ui/widgets/pairing_dialog.py:103 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:92 #, python-format msgid "Pair your device to your comma account" msgstr "장치를 귀하의 comma 계정에 페어링하세요" -#: selfdrive/ui/widgets/setup.py:48 selfdrive/ui/layouts/settings/device.py:24 +#: openpilot/selfdrive/ui/widgets/setup.py:47 +#: openpilot/selfdrive/ui/layouts/settings/device.py:23 #, python-format -msgid "" -"Pair your device with comma connect (connect.comma.ai) and claim your comma " -"prime offer." -msgstr "" -"장치를 comma connect(connect.comma.ai)와 페어링하고 comma 프라임 혜택을 받으세" -"요." +msgid "Pair your device with comma connect (connect.comma.ai) and claim your comma prime offer." +msgstr "장치를 comma connect(connect.comma.ai)와 페어링하고 comma 프라임 혜택을 받으세요." -#: selfdrive/ui/widgets/setup.py:91 +#: openpilot/selfdrive/ui/widgets/setup.py:91 #, python-format msgid "Please connect to Wi-Fi to complete initial pairing" msgstr "초기 페어링을 완료하려면 Wi‑Fi에 연결하세요" -#: selfdrive/ui/layouts/settings/device.py:55 -#: selfdrive/ui/layouts/settings/device.py:187 +#: openpilot/selfdrive/ui/layouts/settings/device.py:183 +#: openpilot/selfdrive/ui/layouts/settings/device.py:53 #, python-format msgid "Power Off" msgstr "전원 끄기" -#: system/ui/widgets/network.py:144 +#: system/ui/widgets/network.py:141 #, python-format msgid "Prevent large data uploads when on a metered Wi-Fi connection" msgstr "종량제 Wi‑Fi 연결 시 대용량 업로드 방지" -#: system/ui/widgets/network.py:135 +#: system/ui/widgets/network.py:132 #, python-format msgid "Prevent large data uploads when on a metered cellular connection" msgstr "종량제 셀룰러 연결 시 대용량 업로드 방지" -#: selfdrive/ui/layouts/settings/device.py:25 -msgid "" -"Preview the driver facing camera to ensure that driver monitoring has good " -"visibility. (vehicle must be off)" -msgstr "" -"운전자 모니터링의 가시성을 확인하기 위해 운전자 카메라를 미리 봅니다. (차량" -"은 꺼져 있어야 합니다)" +#: openpilot/selfdrive/ui/layouts/settings/device.py:24 +msgid "Preview the driver facing camera to ensure that driver monitoring has good visibility. (vehicle must be off)" +msgstr "운전자 모니터링의 가시성을 확인하기 위해 운전자 카메라를 미리 봅니다. (차량은 꺼져 있어야 합니다)" -#: selfdrive/ui/widgets/pairing_dialog.py:161 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:150 #, python-format msgid "QR Code Error" msgstr "QR 코드 오류" -#: selfdrive/ui/widgets/ssh_key.py:31 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:31 msgid "REMOVE" msgstr "제거" -#: selfdrive/ui/layouts/settings/device.py:51 +#: openpilot/selfdrive/ui/layouts/settings/device.py:49 #, python-format msgid "RESET" msgstr "재설정" -#: selfdrive/ui/layouts/settings/device.py:65 +#: openpilot/selfdrive/ui/layouts/settings/device.py:63 #, python-format msgid "REVIEW" msgstr "검토" -#: selfdrive/ui/layouts/settings/device.py:55 -#: selfdrive/ui/layouts/settings/device.py:175 +#: openpilot/selfdrive/ui/layouts/settings/device.py:171 +#: openpilot/selfdrive/ui/layouts/settings/device.py:53 #, python-format msgid "Reboot" msgstr "재시작" -#: selfdrive/ui/onroad/alert_renderer.py:66 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:66 #, python-format msgid "Reboot Device" msgstr "장치 재시작" -#: selfdrive/ui/widgets/offroad_alerts.py:112 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:112 #, python-format msgid "Reboot and Update" msgstr "재시작 및 업데이트" -#: selfdrive/ui/layouts/settings/toggles.py:27 -msgid "" -"Receive alerts to steer back into the lane when your vehicle drifts over a " -"detected lane line without a turn signal activated while driving over 31 mph " -"(50 km/h)." -msgstr "" -"시속 31mph(50km/h) 이상에서 방향지시등 없이 감지된 차선 밖으로 벗어나면 차선" -"으로 복귀하라는 경고를 받습니다." - -#: selfdrive/ui/layouts/settings/toggles.py:76 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:76 #, python-format msgid "Record and Upload Driver Camera" msgstr "운전자 카메라 기록 및 업로드" -#: selfdrive/ui/layouts/settings/toggles.py:82 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:82 #, python-format msgid "Record and Upload Microphone Audio" msgstr "마이크 오디오 기록 및 업로드" -#: selfdrive/ui/layouts/settings/toggles.py:33 -msgid "" -"Record and store microphone audio while driving. The audio will be included " -"in the dashcam video in comma connect." -msgstr "" -"주행 중 마이크 오디오를 기록하고 저장합니다. 오디오는 comma connect의 대시캠 " -"영상에 포함됩니다." +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:33 +msgid "Record and store microphone audio while driving. The audio will be included in the dashcam video in comma connect." +msgstr "주행 중 마이크 오디오를 기록하고 저장합니다. 오디오는 comma connect의 대시캠 영상에 포함됩니다." -#: selfdrive/ui/layouts/settings/device.py:67 +#: openpilot/selfdrive/ui/layouts/settings/device.py:65 #, python-format msgid "Regulatory" msgstr "규제 정보" -#: selfdrive/ui/layouts/settings/toggles.py:98 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:98 #, python-format msgid "Relaxed" msgstr "편안한" -#: selfdrive/ui/widgets/prime.py:47 +#: openpilot/selfdrive/ui/widgets/prime.py:47 #, python-format msgid "Remote access" msgstr "원격 액세스" -#: selfdrive/ui/widgets/prime.py:47 +#: openpilot/selfdrive/ui/widgets/prime.py:47 #, python-format msgid "Remote snapshots" msgstr "원격 스냅샷" -#: selfdrive/ui/widgets/ssh_key.py:123 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:124 #, python-format msgid "Request timed out" msgstr "요청 시간이 초과되었습니다" -#: selfdrive/ui/layouts/settings/device.py:119 +#: openpilot/selfdrive/ui/layouts/settings/device.py:111 #, python-format msgid "Reset" msgstr "재설정" -#: selfdrive/ui/layouts/settings/device.py:51 +#: openpilot/selfdrive/ui/layouts/settings/device.py:49 #, python-format msgid "Reset Calibration" msgstr "캘리브레이션 재설정" -#: selfdrive/ui/layouts/settings/device.py:65 +#: openpilot/selfdrive/ui/layouts/settings/device.py:63 #, python-format msgid "Review Training Guide" msgstr "학습 가이드 검토" -#: selfdrive/ui/layouts/settings/device.py:27 +#: openpilot/selfdrive/ui/layouts/settings/device.py:26 msgid "Review the rules, features, and limitations of openpilot" msgstr "openpilot의 규칙, 기능 및 제한을 검토" -#: selfdrive/ui/layouts/settings/software.py:61 +#: openpilot/selfdrive/ui/layouts/settings/software.py:68 #, python-format msgid "SELECT" msgstr "선택" -#: selfdrive/ui/layouts/settings/developer.py:53 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:53 #, python-format msgid "SSH Keys" msgstr "SSH 키" -#: system/ui/widgets/network.py:310 +#: system/ui/widgets/network.py:316 #, python-format msgid "Scanning Wi-Fi networks..." msgstr "Wi‑Fi 네트워크 검색 중..." -#: system/ui/widgets/option_dialog.py:36 +#: system/ui/widgets/option_dialog.py:37 #, python-format msgid "Select" msgstr "선택" -#: selfdrive/ui/layouts/settings/software.py:183 +#: openpilot/selfdrive/ui/layouts/settings/software.py:203 #, python-format msgid "Select a branch" msgstr "브랜치 선택" -#: selfdrive/ui/layouts/settings/device.py:91 +#: openpilot/selfdrive/ui/layouts/settings/device.py:89 #, python-format msgid "Select a language" msgstr "언어 선택" -#: selfdrive/ui/layouts/settings/device.py:60 +#: openpilot/selfdrive/ui/layouts/settings/device.py:58 #, python-format msgid "Serial" msgstr "시리얼" -#: selfdrive/ui/widgets/offroad_alerts.py:106 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:106 #, python-format msgid "Snooze Update" msgstr "업데이트 나중에 알림" -#: selfdrive/ui/layouts/settings/settings.py:65 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:62 msgid "Software" msgstr "소프트웨어" -#: selfdrive/ui/layouts/settings/toggles.py:98 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:98 #, python-format msgid "Standard" msgstr "표준" -#: selfdrive/ui/layouts/settings/toggles.py:22 -msgid "" -"Standard is recommended. In aggressive mode, openpilot will follow lead cars " -"closer and be more aggressive with the gas and brake. In relaxed mode " -"openpilot will stay further away from lead cars. On supported cars, you can " -"cycle through these personalities with your steering wheel distance button." -msgstr "" -"표준을 권장합니다. 공격적 모드에서는 앞차를 더 가깝게 따라가고 가감속이 더 적" -"극적입니다. 편안한 모드에서는 앞차와 거리를 더 둡니다. 지원 차량에서는 스티어" -"링의 차간 버튼으로 이 성향들을 전환할 수 있습니다." - -#: selfdrive/ui/onroad/alert_renderer.py:59 -#: selfdrive/ui/onroad/alert_renderer.py:65 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:59 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:65 #, python-format msgid "System Unresponsive" msgstr "시스템 응답 없음" -#: selfdrive/ui/onroad/alert_renderer.py:58 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:58 #, python-format msgid "TAKE CONTROL IMMEDIATELY" msgstr "즉시 수동 조작하세요" -#: selfdrive/ui/layouts/sidebar.py:71 selfdrive/ui/layouts/sidebar.py:125 -#: selfdrive/ui/layouts/sidebar.py:127 selfdrive/ui/layouts/sidebar.py:129 +#: openpilot/selfdrive/ui/layouts/sidebar.py:71 +#: openpilot/selfdrive/ui/layouts/sidebar.py:125 +#: openpilot/selfdrive/ui/layouts/sidebar.py:127 +#: openpilot/selfdrive/ui/layouts/sidebar.py:129 msgid "TEMP" msgstr "온도" -#: selfdrive/ui/layouts/settings/software.py:61 +#: openpilot/selfdrive/ui/layouts/settings/software.py:68 #, python-format msgid "Target Branch" msgstr "대상 브랜치" -#: system/ui/widgets/network.py:124 +#: system/ui/widgets/network.py:121 #, python-format msgid "Tethering Password" msgstr "테더링 비밀번호" -#: selfdrive/ui/layouts/settings/settings.py:64 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:61 msgid "Toggles" msgstr "토글" -#: selfdrive/ui/layouts/settings/software.py:72 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:79 +#, python-format +msgid "UI Debug Mode" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/software.py:79 #, python-format msgid "UNINSTALL" msgstr "제거" -#: selfdrive/ui/layouts/home.py:155 +#: openpilot/selfdrive/ui/layouts/home.py:155 #, python-format msgid "UPDATE" msgstr "업데이트" -#: selfdrive/ui/layouts/settings/software.py:72 -#: selfdrive/ui/layouts/settings/software.py:163 +#: openpilot/selfdrive/ui/layouts/settings/software.py:173 +#: openpilot/selfdrive/ui/layouts/settings/software.py:79 #, python-format msgid "Uninstall" msgstr "제거" -#: selfdrive/ui/layouts/sidebar.py:117 +#: openpilot/selfdrive/ui/layouts/sidebar.py:117 msgid "Unknown" msgstr "알수없음" -#: selfdrive/ui/layouts/settings/software.py:48 +#: openpilot/selfdrive/ui/layouts/settings/software.py:55 #, python-format msgid "Updates are only downloaded while the car is off." msgstr "업데이트는 차량 전원이 꺼져 있을 때만 다운로드됩니다." -#: selfdrive/ui/widgets/prime.py:33 +#: openpilot/selfdrive/ui/widgets/prime.py:33 #, python-format msgid "Upgrade Now" msgstr "지금 업그레이드" -#: selfdrive/ui/layouts/settings/toggles.py:31 -msgid "" -"Upload data from the driver facing camera and help improve the driver " -"monitoring algorithm." -msgstr "" -"운전자 방향 카메라 데이터를 업로드하여 운전자 모니터링 알고리즘 개선에 도움" -"을 주세요." +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:31 +msgid "Upload data from the driver facing camera and help improve the driver monitoring algorithm." +msgstr "운전자 방향 카메라 데이터를 업로드하여 운전자 모니터링 알고리즘 개선에 도움을 주세요." -#: selfdrive/ui/layouts/settings/toggles.py:88 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:88 #, python-format msgid "Use Metric System" msgstr "미터법 사용" -#: selfdrive/ui/layouts/settings/toggles.py:17 -msgid "" -"Use the openpilot system for adaptive cruise control and lane keep driver " -"assistance. Your attention is required at all times to use this feature." -msgstr "" -"ACC 및 차선 유지 보조에 openpilot을 사용합니다. 이 기능을 사용할 때는 항상 주" -"의가 필요합니다." - -#: selfdrive/ui/layouts/sidebar.py:72 selfdrive/ui/layouts/sidebar.py:144 +#: openpilot/selfdrive/ui/layouts/sidebar.py:72 +#: openpilot/selfdrive/ui/layouts/sidebar.py:144 msgid "VEHICLE" msgstr "차량" -#: selfdrive/ui/layouts/settings/device.py:67 +#: openpilot/selfdrive/ui/layouts/settings/device.py:65 #, python-format msgid "VIEW" msgstr "보기" -#: selfdrive/ui/onroad/alert_renderer.py:52 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:52 #, python-format msgid "Waiting to start" msgstr "시작 대기 중" -#: selfdrive/ui/layouts/settings/developer.py:19 -msgid "" -"Warning: This grants SSH access to all public keys in your GitHub settings. " -"Never enter a GitHub username other than your own. A comma employee will " -"NEVER ask you to add their GitHub username." -msgstr "" -"경고: 이는 GitHub 설정의 모든 공개 키에 SSH 액세스를 부여합니다. 자신의 것이 " -"아닌 GitHub 사용자 이름을 절대 입력하지 마세요. comma 직원이 본인의 GitHub 사" -"용자 이름 추가를 요구하는 일은 결코 없습니다." - -#: selfdrive/ui/layouts/onboarding.py:111 +#: openpilot/selfdrive/ui/layouts/onboarding.py:115 #, python-format msgid "Welcome to openpilot" msgstr "openpilot에 오신 것을 환영합니다" -#: selfdrive/ui/layouts/settings/toggles.py:20 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:20 msgid "When enabled, pressing the accelerator pedal will disengage openpilot." msgstr "이 옵션을 켜면 가속 페달을 밟을 때 openpilot이 해제됩니다." -#: selfdrive/ui/layouts/sidebar.py:44 +#: openpilot/selfdrive/ui/layouts/sidebar.py:44 msgid "Wi-Fi" msgstr "Wi‑Fi" -#: system/ui/widgets/network.py:144 +#: system/ui/widgets/network.py:141 #, python-format msgid "Wi-Fi Network Metered" msgstr "Wi‑Fi 네트워크 종량제" -#: system/ui/widgets/network.py:314 +#: system/ui/widgets/network.py:320 #, python-format msgid "Wrong password" msgstr "비밀번호가 올바르지 않습니다" -#: selfdrive/ui/layouts/onboarding.py:145 +#: openpilot/selfdrive/ui/layouts/onboarding.py:149 #, python-format msgid "You must accept the Terms and Conditions in order to use openpilot." msgstr "openpilot을 사용하려면 약관에 동의해야 합니다." -#: selfdrive/ui/layouts/onboarding.py:112 +#: openpilot/selfdrive/ui/layouts/onboarding.py:116 #, python-format -msgid "" -"You must accept the Terms and Conditions to use openpilot. Read the latest " -"terms at https://comma.ai/terms before continuing." -msgstr "" -"openpilot을 사용하려면 약관에 동의해야 합니다. 계속하기 전에 https://comma." -"ai/terms 에서 최신 약관을 읽어주세요." +msgid "You must accept the Terms and Conditions to use openpilot. Read the latest terms at https://comma.ai/terms before continuing." +msgstr "openpilot을 사용하려면 약관에 동의해야 합니다. 계속하기 전에 https://comma.ai/terms 에서 최신 약관을 읽어주세요." -#: selfdrive/ui/onroad/driver_camera_dialog.py:34 +#: openpilot/selfdrive/ui/onroad/driver_camera_dialog.py:38 #, python-format msgid "camera starting" msgstr "카메라 시작 중" -#: selfdrive/ui/widgets/prime.py:63 +#: openpilot/selfdrive/ui/layouts/settings/software.py:19 +#, python-format +msgid "checking..." +msgstr "" + +#: openpilot/selfdrive/ui/widgets/prime.py:63 #, python-format msgid "comma prime" msgstr "comma 프라임" -#: system/ui/widgets/network.py:142 +#: system/ui/widgets/network.py:139 #, python-format msgid "default" msgstr "기본값" -#: selfdrive/ui/layouts/settings/device.py:133 +#: openpilot/selfdrive/ui/layouts/settings/device.py:125 #, python-format msgid "down" msgstr "아래" -#: selfdrive/ui/layouts/settings/software.py:106 +#: openpilot/selfdrive/ui/layouts/settings/software.py:20 +#, python-format +msgid "downloading..." +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/software.py:116 #, python-format msgid "failed to check for update" msgstr "업데이트 확인 실패" -#: system/ui/widgets/network.py:237 system/ui/widgets/network.py:314 +#: openpilot/selfdrive/ui/layouts/settings/software.py:21 +#, python-format +msgid "finalizing update..." +msgstr "" + +#: system/ui/widgets/network.py:238 +#: system/ui/widgets/network.py:321 #, python-format msgid "for \"{}\"" msgstr "\"{}\"용" -#: selfdrive/ui/onroad/hud_renderer.py:177 +#: openpilot/selfdrive/ui/onroad/hud_renderer.py:177 #, python-format msgid "km/h" msgstr "km/h" -#: system/ui/widgets/network.py:204 +#: system/ui/widgets/network.py:201 #, python-format msgid "leave blank for automatic configuration" msgstr "자동 구성을 사용하려면 비워 두세요" -#: selfdrive/ui/layouts/settings/device.py:134 +#: openpilot/selfdrive/ui/layouts/settings/device.py:126 #, python-format msgid "left" msgstr "왼쪽" -#: system/ui/widgets/network.py:142 +#: system/ui/widgets/network.py:139 #, python-format msgid "metered" msgstr "종량제" -#: selfdrive/ui/onroad/hud_renderer.py:177 +#: openpilot/selfdrive/ui/onroad/hud_renderer.py:177 #, python-format msgid "mph" msgstr "mph" -#: selfdrive/ui/layouts/settings/software.py:20 +#: openpilot/selfdrive/ui/layouts/settings/software.py:27 #, python-format msgid "never" msgstr "없음" -#: selfdrive/ui/layouts/settings/software.py:31 +#: openpilot/selfdrive/ui/layouts/settings/software.py:38 #, python-format msgid "now" msgstr "지금" -#: selfdrive/ui/layouts/settings/developer.py:71 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:71 #, python-format msgid "openpilot Longitudinal Control (Alpha)" msgstr "openpilot 롱컨 제어(알파)" -#: selfdrive/ui/onroad/alert_renderer.py:51 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:51 #, python-format msgid "openpilot Unavailable" msgstr "openpilot 사용 불가" -#: selfdrive/ui/layouts/settings/toggles.py:158 -#, python-format -msgid "" -"openpilot defaults to driving in chill mode. Experimental mode enables alpha-" -"level features that aren't ready for chill mode. Experimental features are " -"listed below:

End-to-End Longitudinal Control


Let the driving " -"model control the gas and brakes. openpilot will drive as it thinks a human " -"would, including stopping for red lights and stop signs. Since the driving " -"model decides the speed to drive, the set speed will only act as an upper " -"bound. This is an alpha quality feature; mistakes should be expected." -"

New Driving Visualization


The driving visualization will " -"transition to the road-facing wide-angle camera at low speeds to better show " -"some turns. The Experimental mode logo will also be shown in the top right " -"corner." -msgstr "" -"openpilot은 기본적으로 안정적 모드로 주행합니다. 실험 모드를 사용하면 안정적 모드에 " -"아직 준비되지 않은 알파 수준의 기능이 활성화됩니다. 실험 기능은 아래와 같습니" -"다:

엔드투엔드 롱컨 제어


주행 모델이 가속과 제동을 제어합니" -"다. openpilot은 빨간 신호 및 정지 표지에서의 정지를 포함해 사람이 운전한다고 " -"판단하는 방식으로 주행합니다. 주행 속도는 모델이 결정하므로 설정 속도는 상한" -"으로만 동작합니다. 알파 품질 기능이므로 오작동이 발생할 수 있습니다.

" -"새로운 주행 시각화


저속에서는 도로 방향의 광각 카메라로 전환되어 일" -"부 회전을 더 잘 보여줍니다. 화면 오른쪽 위에는 실험 모드 로고도 표시됩니다." - -#: selfdrive/ui/layouts/settings/device.py:165 -#, python-format -msgid "" -"openpilot is continuously calibrating, resetting is rarely required. " -"Resetting calibration will restart openpilot if the car is powered on." -msgstr "" -"openpilot은 지속적으로 보정을 진행하므로 재설정이 필요한 경우는 드뭅니다. 차" -"량 전원이 켜져 있을 때 보정을 재설정하면 openpilot이 재시작됩니다." - -#: selfdrive/ui/layouts/settings/firehose.py:20 -msgid "" -"openpilot learns to drive by watching humans, like you, drive.\n" -"\n" -"Firehose Mode allows you to maximize your training data uploads to improve " -"openpilot's driving models. More data means bigger models, which means " -"better Experimental Mode." -msgstr "" -"openpilot은 당신과 같은 사람의 운전을 보며 운전을 학습합니다.\n" -"\n" -"Firehose 모드는 학습 데이터 업로드를 최대화하여 openpilot의 주행 모델을 개선" -"할 수 있게 해줍니다. 데이터가 많을수록 모델은 커지고, 실험 모드는 더 좋아집니" -"다." - -#: selfdrive/ui/layouts/settings/toggles.py:183 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:184 #, python-format msgid "openpilot longitudinal control may come in a future update." msgstr "openpilot 롱컨 제어는 향후 업데이트에서 제공될 수 있습니다." -#: selfdrive/ui/layouts/settings/device.py:26 -msgid "" -"openpilot requires the device to be mounted within 4° left or right and " -"within 5° up or 9° down." +#: openpilot/selfdrive/ui/layouts/settings/device.py:25 +msgid "openpilot requires the device to be mounted within 4° left or right and within 5° up or 9° down." msgstr "openpilot은 장치를 좌우 4°, 위쪽 5°, 아래쪽 9° 이내로 장착해야 합니다." -#: selfdrive/ui/layouts/settings/device.py:134 +#: openpilot/selfdrive/ui/layouts/settings/device.py:126 #, python-format msgid "right" msgstr "오른쪽" -#: system/ui/widgets/network.py:142 +#: system/ui/widgets/network.py:139 #, python-format msgid "unmetered" msgstr "비종량제" -#: selfdrive/ui/layouts/settings/device.py:133 +#: openpilot/selfdrive/ui/layouts/settings/device.py:125 #, python-format msgid "up" msgstr "위" -#: selfdrive/ui/layouts/settings/software.py:117 +#: openpilot/selfdrive/ui/layouts/settings/software.py:127 #, python-format msgid "up to date, last checked never" msgstr "최신입니다. 마지막 확인: 없음" -#: selfdrive/ui/layouts/settings/software.py:115 +#: openpilot/selfdrive/ui/layouts/settings/software.py:125 #, python-format msgid "up to date, last checked {}" msgstr "최신입니다. 마지막 확인: {}" -#: selfdrive/ui/layouts/settings/software.py:109 +#: openpilot/selfdrive/ui/layouts/settings/software.py:119 #, python-format msgid "update available" msgstr "업데이트 가능" -#: selfdrive/ui/layouts/home.py:169 +#: openpilot/selfdrive/ui/layouts/home.py:169 #, python-format msgid "{} ALERT" msgid_plural "{} ALERTS" msgstr[0] "{}건의 알림" -#: selfdrive/ui/layouts/settings/software.py:40 +#: openpilot/selfdrive/ui/layouts/settings/software.py:47 #, python-format msgid "{} day ago" msgid_plural "{} days ago" msgstr[0] "{}일 전" -#: selfdrive/ui/layouts/settings/software.py:37 +#: openpilot/selfdrive/ui/layouts/settings/software.py:44 #, python-format msgid "{} hour ago" msgid_plural "{} hours ago" msgstr[0] "{}시간 전" -#: selfdrive/ui/layouts/settings/software.py:34 +#: openpilot/selfdrive/ui/layouts/settings/software.py:41 #, python-format msgid "{} minute ago" msgid_plural "{} minutes ago" msgstr[0] "{}분 전" -#: selfdrive/ui/layouts/settings/firehose.py:111 +#: openpilot/selfdrive/ui/layouts/settings/firehose.py:70 #, python-format msgid "{} segment of your driving is in the training dataset so far." msgid_plural "{} segments of your driving is in the training dataset so far." msgstr[0] "현재까지 귀하의 주행 {}구간이 학습 데이터셋에 포함되었습니다." -#: selfdrive/ui/widgets/prime.py:62 +#: openpilot/selfdrive/ui/widgets/prime.py:62 #, python-format msgid "✓ SUBSCRIBED" msgstr "✓ 구독됨" -#: selfdrive/ui/widgets/setup.py:22 +#: openpilot/selfdrive/ui/widgets/setup.py:21 #, python-format msgid "🔥 Firehose Mode 🔥" msgstr "🔥 파이어호스 모드 🔥" + diff --git a/selfdrive/ui/translations/app_pt-BR.po b/selfdrive/ui/translations/app_pt-BR.po index 84b53c6e8d..1adb797c88 100644 --- a/selfdrive/ui/translations/app_pt-BR.po +++ b/selfdrive/ui/translations/app_pt-BR.po @@ -19,1202 +19,1018 @@ msgstr "" "X-Language: pt_BR\n" "X-Source-Language: C\n" -#: selfdrive/ui/layouts/settings/device.py:160 +#: openpilot/selfdrive/ui/layouts/settings/device.py:152 #, python-format msgid " Steering torque response calibration is complete." msgstr " A calibração da resposta de torque da direção foi concluída." -#: selfdrive/ui/layouts/settings/device.py:158 +#: openpilot/selfdrive/ui/layouts/settings/device.py:150 #, python-format msgid " Steering torque response calibration is {}% complete." msgstr " A calibração da resposta de torque da direção está {}% concluída." -#: selfdrive/ui/layouts/settings/device.py:133 +#: openpilot/selfdrive/ui/layouts/settings/device.py:125 #, python-format msgid " Your device is pointed {:.1f}° {} and {:.1f}° {}." msgstr " Seu dispositivo está apontado {:.1f}° {} e {:.1f}° {}." -#: selfdrive/ui/layouts/sidebar.py:43 +#: openpilot/selfdrive/ui/layouts/sidebar.py:43 msgid "--" msgstr "--" -#: selfdrive/ui/widgets/prime.py:47 +#: openpilot/selfdrive/ui/widgets/prime.py:47 #, python-format msgid "1 year of drive storage" msgstr "1 ano de armazenamento de condução" -#: selfdrive/ui/widgets/prime.py:47 +#: openpilot/selfdrive/ui/widgets/prime.py:47 #, python-format msgid "24/7 LTE connectivity" msgstr "Conectividade LTE 24/7" -#: selfdrive/ui/layouts/sidebar.py:46 +#: openpilot/selfdrive/ui/layouts/sidebar.py:46 msgid "2G" msgstr "2G" -#: selfdrive/ui/layouts/sidebar.py:47 +#: openpilot/selfdrive/ui/layouts/sidebar.py:47 msgid "3G" msgstr "3G" -#: selfdrive/ui/layouts/sidebar.py:49 +#: openpilot/selfdrive/ui/layouts/sidebar.py:49 msgid "5G" msgstr "5G" -#: selfdrive/ui/layouts/settings/developer.py:23 -msgid "" -"WARNING: openpilot longitudinal control is in alpha for this car and will " -"disable Automatic Emergency Braking (AEB).

On this car, openpilot " -"defaults to the car's built-in ACC instead of openpilot's longitudinal " -"control. Enable this to switch to openpilot longitudinal control. Enabling " -"Experimental mode is recommended when enabling openpilot longitudinal " -"control alpha. Changing this setting will restart openpilot if the car is " -"powered on." -msgstr "" -"AVISO: o controle longitudinal do openpilot está em alpha para este carro " -"e desativará a Frenagem Automática de Emergência (AEB).

Neste " -"carro, o openpilot usa por padrão o ACC integrado do carro em vez do " -"controle longitudinal do openpilot. Ative isto para alternar para o controle " -"longitudinal do openpilot. Recomenda-se ativar o Modo Experimental ao ativar " -"o controle longitudinal do openpilot em alpha." - -#: selfdrive/ui/layouts/settings/device.py:148 +#: openpilot/selfdrive/ui/layouts/settings/device.py:140 #, python-format msgid "

Steering lag calibration is complete." msgstr "

A calibração da latência da direção está concluída." -#: selfdrive/ui/layouts/settings/device.py:146 +#: openpilot/selfdrive/ui/layouts/settings/device.py:138 #, python-format msgid "

Steering lag calibration is {}% complete." msgstr "

A calibração da latência da direção está {}% concluída." -#: selfdrive/ui/layouts/settings/firehose.py:138 -#, python-format -msgid "ACTIVE" -msgstr "ATIVO" - -#: selfdrive/ui/layouts/settings/developer.py:15 -msgid "" -"ADB (Android Debug Bridge) allows connecting to your device over USB or over " -"the network. See https://docs.comma.ai/how-to/connect-to-comma for more info." -msgstr "" -"ADB (Android Debug Bridge) permite conectar ao seu dispositivo via USB ou " -"pela rede. Veja https://docs.comma.ai/how-to/connect-to-comma para mais " -"informações." - -#: selfdrive/ui/widgets/ssh_key.py:30 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:30 msgid "ADD" msgstr "ADICIONAR" -#: system/ui/widgets/network.py:139 +#: system/ui/widgets/network.py:136 #, python-format msgid "APN Setting" msgstr "Configuração de APN" -#: selfdrive/ui/widgets/offroad_alerts.py:109 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:109 #, python-format msgid "Acknowledge Excessive Actuation" msgstr "Reconhecer Atuação Excessiva" -#: system/ui/widgets/network.py:74 system/ui/widgets/network.py:95 +#: system/ui/widgets/network.py:92 +#: system/ui/widgets/network.py:74 #, python-format msgid "Advanced" msgstr "Avançado" -#: selfdrive/ui/layouts/settings/toggles.py:98 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:98 #, python-format msgid "Aggressive" msgstr "Agressivo" -#: selfdrive/ui/layouts/onboarding.py:116 +#: openpilot/selfdrive/ui/layouts/onboarding.py:120 #, python-format msgid "Agree" msgstr "Concordo" -#: selfdrive/ui/layouts/settings/toggles.py:70 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:70 #, python-format msgid "Always-On Driver Monitoring" msgstr "Monitoramento de Motorista Sempre Ativo" -#: selfdrive/ui/layouts/settings/toggles.py:186 -#, python-format -msgid "" -"An alpha version of openpilot longitudinal control can be tested, along with " -"Experimental mode, on non-release branches." -msgstr "" -"Uma versão alpha do controle longitudinal do openpilot pode ser testada, " -"junto com o Modo Experimental, em ramificações fora de release." - -#: selfdrive/ui/layouts/settings/device.py:187 +#: openpilot/selfdrive/ui/layouts/settings/device.py:183 #, python-format msgid "Are you sure you want to power off?" msgstr "Tem certeza de que deseja desligar?" -#: selfdrive/ui/layouts/settings/device.py:175 +#: openpilot/selfdrive/ui/layouts/settings/device.py:171 #, python-format msgid "Are you sure you want to reboot?" msgstr "Tem certeza de que deseja reiniciar?" -#: selfdrive/ui/layouts/settings/device.py:119 +#: openpilot/selfdrive/ui/layouts/settings/device.py:111 #, python-format msgid "Are you sure you want to reset calibration?" msgstr "Tem certeza de que deseja redefinir a calibração?" -#: selfdrive/ui/layouts/settings/software.py:163 +#: openpilot/selfdrive/ui/layouts/settings/software.py:173 #, python-format msgid "Are you sure you want to uninstall?" msgstr "Tem certeza de que deseja desinstalar?" -#: system/ui/widgets/network.py:99 selfdrive/ui/layouts/onboarding.py:147 +#: system/ui/widgets/network.py:96 +#: openpilot/selfdrive/ui/layouts/onboarding.py:151 #, python-format msgid "Back" msgstr "Voltar" -#: selfdrive/ui/widgets/prime.py:38 +#: openpilot/selfdrive/ui/widgets/prime.py:38 #, python-format msgid "Become a comma prime member at connect.comma.ai" msgstr "Torne-se membro comma prime em connect.comma.ai" -#: selfdrive/ui/widgets/pairing_dialog.py:130 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:119 #, python-format msgid "Bookmark connect.comma.ai to your home screen to use it like an app" msgstr "Adicione connect.comma.ai à tela inicial para usá-lo como um app" -#: selfdrive/ui/layouts/settings/device.py:68 +#: openpilot/selfdrive/ui/layouts/settings/device.py:66 #, python-format msgid "CHANGE" msgstr "ALTERAR" -#: selfdrive/ui/layouts/settings/software.py:50 -#: selfdrive/ui/layouts/settings/software.py:107 -#: selfdrive/ui/layouts/settings/software.py:118 -#: selfdrive/ui/layouts/settings/software.py:147 +#: openpilot/selfdrive/ui/layouts/settings/software.py:157 +#: openpilot/selfdrive/ui/layouts/settings/software.py:57 +#: openpilot/selfdrive/ui/layouts/settings/software.py:117 +#: openpilot/selfdrive/ui/layouts/settings/software.py:128 #, python-format msgid "CHECK" msgstr "VERIFICAR" -#: selfdrive/ui/widgets/exp_mode_button.py:50 +#: openpilot/selfdrive/ui/widgets/exp_mode_button.py:51 #, python-format msgid "CHILL MODE ON" msgstr "MODO CHILL ATIVO" -#: system/ui/widgets/network.py:155 selfdrive/ui/layouts/sidebar.py:73 -#: selfdrive/ui/layouts/sidebar.py:134 selfdrive/ui/layouts/sidebar.py:136 -#: selfdrive/ui/layouts/sidebar.py:138 +#: system/ui/widgets/network.py:152 +#: openpilot/selfdrive/ui/layouts/sidebar.py:73 +#: openpilot/selfdrive/ui/layouts/sidebar.py:134 +#: openpilot/selfdrive/ui/layouts/sidebar.py:136 +#: openpilot/selfdrive/ui/layouts/sidebar.py:138 #, python-format msgid "CONNECT" msgstr "CONECTAR" -#: system/ui/widgets/network.py:369 +#: system/ui/widgets/network.py:376 #, python-format msgid "CONNECTING..." msgstr "CONECTANDO..." -#: system/ui/widgets/confirm_dialog.py:23 -#: system/ui/widgets/option_dialog.py:35 system/ui/widgets/keyboard.py:81 -#: system/ui/widgets/network.py:318 +#: system/ui/widgets/network.py:326 +#: system/ui/widgets/confirm_dialog.py:24 +#: system/ui/widgets/option_dialog.py:36 +#: system/ui/widgets/keyboard.py:83 #, python-format msgid "Cancel" msgstr "Cancelar" -#: system/ui/widgets/network.py:134 +#: system/ui/widgets/network.py:131 #, python-format msgid "Cellular Metered" msgstr "Dados móveis limitados" -#: selfdrive/ui/layouts/settings/device.py:68 +#: openpilot/selfdrive/ui/layouts/settings/device.py:66 #, python-format msgid "Change Language" msgstr "Alterar Idioma" -#: selfdrive/ui/layouts/settings/toggles.py:125 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:125 #, python-format msgid "Changing this setting will restart openpilot if the car is powered on." -msgstr "" -"Alterar esta configuração reiniciará o openpilot se o carro estiver ligado." +msgstr "Alterar esta configuração reiniciará o openpilot se o carro estiver ligado." -#: selfdrive/ui/widgets/pairing_dialog.py:129 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:118 #, python-format msgid "Click \"add new device\" and scan the QR code on the right" msgstr "Toque em \"adicionar novo dispositivo\" e escaneie o QR code à direita" -#: selfdrive/ui/widgets/offroad_alerts.py:104 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:104 #, python-format msgid "Close" msgstr "Fechar" -#: selfdrive/ui/layouts/settings/software.py:49 +#: openpilot/selfdrive/ui/layouts/settings/software.py:56 #, python-format msgid "Current Version" msgstr "Versão Atual" -#: selfdrive/ui/layouts/settings/software.py:110 +#: openpilot/selfdrive/ui/layouts/settings/software.py:120 #, python-format msgid "DOWNLOAD" msgstr "BAIXAR" -#: selfdrive/ui/layouts/onboarding.py:115 +#: openpilot/selfdrive/ui/layouts/onboarding.py:119 #, python-format msgid "Decline" msgstr "Recusar" -#: selfdrive/ui/layouts/onboarding.py:148 +#: openpilot/selfdrive/ui/layouts/onboarding.py:152 #, python-format msgid "Decline, uninstall openpilot" msgstr "Recusar, desinstalar o openpilot" -#: selfdrive/ui/layouts/settings/settings.py:67 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:64 msgid "Developer" msgstr "Desenvolv" -#: selfdrive/ui/layouts/settings/settings.py:62 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:59 msgid "Device" msgstr "Dispositivo" -#: selfdrive/ui/layouts/settings/toggles.py:58 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:58 #, python-format msgid "Disengage on Accelerator Pedal" msgstr "Desativar ao pressionar o acelerador" -#: selfdrive/ui/layouts/settings/device.py:184 +#: openpilot/selfdrive/ui/layouts/settings/device.py:176 #, python-format msgid "Disengage to Power Off" msgstr "Desativar para Desligar" -#: selfdrive/ui/layouts/settings/device.py:172 +#: openpilot/selfdrive/ui/layouts/settings/device.py:164 #, python-format msgid "Disengage to Reboot" msgstr "Desativar para Reiniciar" -#: selfdrive/ui/layouts/settings/device.py:103 +#: openpilot/selfdrive/ui/layouts/settings/device.py:95 #, python-format msgid "Disengage to Reset Calibration" msgstr "Desativar para Redefinir Calibração" -#: selfdrive/ui/layouts/settings/toggles.py:32 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:32 msgid "Display speed in km/h instead of mph." msgstr "Exibir velocidade em km/h em vez de mph." -#: selfdrive/ui/layouts/settings/device.py:59 +#: openpilot/selfdrive/ui/layouts/settings/device.py:57 #, python-format msgid "Dongle ID" msgstr "ID do Dongle" -#: selfdrive/ui/layouts/settings/software.py:50 +#: openpilot/selfdrive/ui/layouts/settings/software.py:57 #, python-format msgid "Download" msgstr "Baixar" -#: selfdrive/ui/layouts/settings/device.py:62 +#: openpilot/selfdrive/ui/layouts/settings/device.py:60 #, python-format msgid "Driver Camera" msgstr "Câmera do Motorista" -#: selfdrive/ui/layouts/settings/toggles.py:96 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:96 #, python-format msgid "Driving Personality" msgstr "Personalidade" -#: system/ui/widgets/network.py:123 system/ui/widgets/network.py:139 +#: system/ui/widgets/network.py:120 +#: system/ui/widgets/network.py:136 #, python-format msgid "EDIT" msgstr "EDITAR" -#: selfdrive/ui/layouts/sidebar.py:138 +#: openpilot/selfdrive/ui/layouts/sidebar.py:138 msgid "ERROR" msgstr "ERRO" -#: selfdrive/ui/layouts/sidebar.py:45 +#: openpilot/selfdrive/ui/layouts/sidebar.py:45 msgid "ETH" msgstr "ETH" -#: selfdrive/ui/widgets/exp_mode_button.py:50 +#: openpilot/selfdrive/ui/widgets/exp_mode_button.py:51 #, python-format msgid "EXPERIMENTAL MODE ON" msgstr "MODO EXPERIMENTAL ATIVO" -#: selfdrive/ui/layouts/settings/developer.py:166 -#: selfdrive/ui/layouts/settings/toggles.py:228 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:229 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:180 #, python-format msgid "Enable" msgstr "Ativar" -#: selfdrive/ui/layouts/settings/developer.py:39 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:39 #, python-format msgid "Enable ADB" msgstr "Ativar ADB" -#: selfdrive/ui/layouts/settings/toggles.py:64 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:64 #, python-format msgid "Enable Lane Departure Warnings" msgstr "Ativar alertas de saída de faixa" -#: system/ui/widgets/network.py:129 +#: system/ui/widgets/network.py:126 #, python-format msgid "Enable Roaming" msgstr "Ativar openpilot" -#: selfdrive/ui/layouts/settings/developer.py:48 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:48 #, python-format msgid "Enable SSH" msgstr "Ativar SSH" -#: system/ui/widgets/network.py:120 +#: system/ui/widgets/network.py:117 #, python-format msgid "Enable Tethering" msgstr "Ativar alertas de saída de faixa" -#: selfdrive/ui/layouts/settings/toggles.py:30 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:30 msgid "Enable driver monitoring even when openpilot is not engaged." -msgstr "" -"Ativar monitoramento do motorista mesmo quando o openpilot não está engajado." +msgstr "Ativar monitoramento do motorista mesmo quando o openpilot não está engajado." -#: selfdrive/ui/layouts/settings/toggles.py:46 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:46 #, python-format msgid "Enable openpilot" msgstr "Ativar openpilot" -#: selfdrive/ui/layouts/settings/toggles.py:189 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:190 #, python-format -msgid "" -"Enable the openpilot longitudinal control (alpha) toggle to allow " -"Experimental mode." -msgstr "" -"Ative a opção de controle longitudinal do openpilot (alpha) para permitir o " -"Modo Experimental." +msgid "Enable the openpilot longitudinal control (alpha) toggle to allow Experimental mode." +msgstr "Ative a opção de controle longitudinal do openpilot (alpha) para permitir o Modo Experimental." -#: system/ui/widgets/network.py:204 +#: system/ui/widgets/network.py:201 #, python-format msgid "Enter APN" msgstr "Digite APN" -#: system/ui/widgets/network.py:241 +#: system/ui/widgets/network.py:243 #, python-format msgid "Enter SSID" msgstr "Digite SSID" -#: system/ui/widgets/network.py:254 +#: system/ui/widgets/network.py:257 #, python-format msgid "Enter new tethering password" msgstr "Digite nova senha tethering" -#: system/ui/widgets/network.py:237 system/ui/widgets/network.py:314 +#: system/ui/widgets/network.py:238 +#: system/ui/widgets/network.py:320 #, python-format msgid "Enter password" msgstr "Digite a senha" -#: selfdrive/ui/widgets/ssh_key.py:89 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:89 #, python-format msgid "Enter your GitHub username" msgstr "Digite seu nome de usuário do GitHub" -#: system/ui/widgets/list_view.py:123 system/ui/widgets/list_view.py:160 +#: system/ui/widgets/list_view.py:123 +#: system/ui/widgets/list_view.py:160 #, python-format msgid "Error" msgstr "Erro" -#: selfdrive/ui/layouts/settings/toggles.py:52 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:52 #, python-format msgid "Experimental Mode" msgstr "Modo Experimental" -#: selfdrive/ui/layouts/settings/toggles.py:181 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:182 #, python-format -msgid "" -"Experimental mode is currently unavailable on this car since the car's stock " -"ACC is used for longitudinal control." -msgstr "" -"O Modo Experimental está indisponível neste carro pois o ACC original do " -"carro é usado para controle longitudinal." +msgid "Experimental mode is currently unavailable on this car since the car's stock ACC is used for longitudinal control." +msgstr "O Modo Experimental está indisponível neste carro pois o ACC original do carro é usado para controle longitudinal." -#: system/ui/widgets/network.py:373 +#: system/ui/widgets/network.py:380 #, python-format msgid "FORGETTING..." msgstr "ESQUECENDO..." -#: selfdrive/ui/widgets/setup.py:44 +#: openpilot/selfdrive/ui/widgets/setup.py:43 #, python-format msgid "Finish Setup" msgstr "Configure" -#: selfdrive/ui/layouts/settings/settings.py:66 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:63 msgid "Firehose" msgstr "Firehose" -#: selfdrive/ui/layouts/settings/firehose.py:18 +#: openpilot/selfdrive/ui/layouts/settings/firehose.py:10 msgid "Firehose Mode" msgstr "Modo Firehose" -#: selfdrive/ui/layouts/settings/firehose.py:25 -msgid "" -"For maximum effectiveness, bring your device inside and connect to a good " -"USB-C adapter and Wi-Fi weekly.\n" -"\n" -"Firehose Mode can also work while you're driving if connected to a hotspot " -"or unlimited SIM card.\n" -"\n" -"\n" -"Frequently Asked Questions\n" -"\n" -"Does it matter how or where I drive? Nope, just drive as you normally " -"would.\n" -"\n" -"Do all of my segments get pulled in Firehose Mode? No, we selectively pull a " -"subset of your segments.\n" -"\n" -"What's a good USB-C adapter? Any fast phone or laptop charger should be " -"fine.\n" -"\n" -"Does it matter which software I run? Yes, only upstream openpilot (and " -"particular forks) are able to be used for training." -msgstr "" -"Para máxima efetividade, leve seu dispositivo para dentro e conecte a um bom " -"adaptador USB-C e Wi‑Fi semanalmente.\n" -"\n" -"O Modo Firehose também pode funcionar enquanto você dirige se estiver " -"conectado a um hotspot ou a um SIM ilimitado.\n" -"\n" -"\n" -"Perguntas Frequentes\n" -"\n" -"Importa como ou onde eu dirijo? Não, apenas dirija como normalmente.\n" -"\n" -"Todos os meus segmentos são puxados no Modo Firehose? Não, puxamos " -"seletivamente um subconjunto dos seus segmentos.\n" -"\n" -"Qual é um bom adaptador USB‑C? Qualquer carregador rápido de telefone ou " -"laptop serve.\n" -"\n" -"Importa qual software eu executo? Sim, apenas o openpilot upstream (e forks " -"específicos) podem ser usados para treinamento." - -#: system/ui/widgets/network.py:318 system/ui/widgets/network.py:451 +#: system/ui/widgets/network.py:458 +#: system/ui/widgets/network.py:326 #, python-format msgid "Forget" msgstr "Esquecer" -#: system/ui/widgets/network.py:319 +#: system/ui/widgets/network.py:327 #, python-format msgid "Forget Wi-Fi Network \"{}\"?" msgstr "Esquecer rede Wi-Fi \"{}\"?" -#: selfdrive/ui/layouts/sidebar.py:71 selfdrive/ui/layouts/sidebar.py:125 +#: openpilot/selfdrive/ui/layouts/sidebar.py:71 +#: openpilot/selfdrive/ui/layouts/sidebar.py:125 msgid "GOOD" msgstr "BOM" -#: selfdrive/ui/widgets/pairing_dialog.py:128 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:117 #, python-format msgid "Go to https://connect.comma.ai on your phone" msgstr "Acesse https://connect.comma.ai no seu telefone" -#: selfdrive/ui/layouts/sidebar.py:129 +#: openpilot/selfdrive/ui/layouts/sidebar.py:129 msgid "HIGH" msgstr "ALTO" -#: system/ui/widgets/network.py:155 +#: system/ui/widgets/network.py:152 #, python-format msgid "Hidden Network" msgstr "Rede" -#: selfdrive/ui/layouts/settings/firehose.py:140 -#, python-format -msgid "INACTIVE: connect to an unmetered network" -msgstr "INATIVO: conecte a uma rede sem franquia" - -#: selfdrive/ui/layouts/settings/software.py:53 -#: selfdrive/ui/layouts/settings/software.py:136 +#: openpilot/selfdrive/ui/layouts/settings/software.py:60 +#: openpilot/selfdrive/ui/layouts/settings/software.py:146 #, python-format msgid "INSTALL" msgstr "INSTALAR" -#: system/ui/widgets/network.py:150 +#: system/ui/widgets/network.py:147 #, python-format msgid "IP Address" msgstr "Endereço IP" -#: selfdrive/ui/layouts/settings/software.py:53 +#: openpilot/selfdrive/ui/layouts/settings/software.py:60 #, python-format msgid "Install Update" msgstr "Instalar Atualização" -#: selfdrive/ui/layouts/settings/developer.py:56 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:56 #, python-format msgid "Joystick Debug Mode" msgstr "Modo de Depuração do Joystick" -#: selfdrive/ui/widgets/ssh_key.py:29 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:29 msgid "LOADING" msgstr "CARREGANDO" -#: selfdrive/ui/layouts/sidebar.py:48 +#: openpilot/selfdrive/ui/layouts/sidebar.py:48 msgid "LTE" msgstr "LTE" -#: selfdrive/ui/layouts/settings/developer.py:64 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:64 #, python-format msgid "Longitudinal Maneuver Mode" msgstr "Modo de Manobra Longitudinal" -#: selfdrive/ui/onroad/hud_renderer.py:148 +#: openpilot/selfdrive/ui/onroad/hud_renderer.py:148 #, python-format msgid "MAX" msgstr "MÁX" -#: selfdrive/ui/widgets/setup.py:75 +#: openpilot/selfdrive/ui/widgets/setup.py:74 #, python-format -msgid "" -"Maximize your training data uploads to improve openpilot's driving models." -msgstr "" -"Maximize seus envios de dados de treinamento para melhorar os modelos de " -"condução do openpilot." +msgid "Maximize your training data uploads to improve openpilot's driving models." +msgstr "Maximize seus envios de dados de treinamento para melhorar os modelos de condução do openpilot." -#: selfdrive/ui/layouts/settings/device.py:59 -#: selfdrive/ui/layouts/settings/device.py:60 +#: openpilot/selfdrive/ui/layouts/settings/device.py:57 +#: openpilot/selfdrive/ui/layouts/settings/device.py:58 #, python-format msgid "N/A" msgstr "N/A" -#: selfdrive/ui/layouts/sidebar.py:142 +#: openpilot/selfdrive/ui/layouts/sidebar.py:142 msgid "NO" msgstr "NÃO" -#: selfdrive/ui/layouts/settings/settings.py:63 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:60 msgid "Network" msgstr "Rede" -#: selfdrive/ui/widgets/ssh_key.py:114 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:115 #, python-format msgid "No SSH keys found" msgstr "Nenhuma chave SSH encontrada" -#: selfdrive/ui/widgets/ssh_key.py:126 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:127 #, python-format msgid "No SSH keys found for user '{}'" msgstr "Nenhuma chave SSH encontrada para o usuário '{username}'" -#: selfdrive/ui/widgets/offroad_alerts.py:320 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:321 #, python-format msgid "No release notes available." msgstr "Sem notas de versão disponíveis." -#: selfdrive/ui/layouts/sidebar.py:73 selfdrive/ui/layouts/sidebar.py:134 +#: openpilot/selfdrive/ui/layouts/sidebar.py:73 +#: openpilot/selfdrive/ui/layouts/sidebar.py:134 msgid "OFFLINE" msgstr "OFFLINE" -#: system/ui/widgets/html_render.py:263 system/ui/widgets/confirm_dialog.py:93 -#: selfdrive/ui/layouts/sidebar.py:127 +#: system/ui/widgets/confirm_dialog.py:93 +#: system/ui/widgets/html_render.py:263 +#: openpilot/selfdrive/ui/layouts/sidebar.py:127 #, python-format msgid "OK" msgstr "OK" -#: selfdrive/ui/layouts/sidebar.py:72 selfdrive/ui/layouts/sidebar.py:136 -#: selfdrive/ui/layouts/sidebar.py:144 +#: openpilot/selfdrive/ui/layouts/sidebar.py:72 +#: openpilot/selfdrive/ui/layouts/sidebar.py:144 +#: openpilot/selfdrive/ui/layouts/sidebar.py:136 msgid "ONLINE" msgstr "ONLINE" -#: selfdrive/ui/widgets/setup.py:20 +#: openpilot/selfdrive/ui/widgets/setup.py:19 #, python-format msgid "Open" msgstr "Abrir" -#: selfdrive/ui/layouts/settings/device.py:48 +#: openpilot/selfdrive/ui/layouts/settings/device.py:45 #, python-format msgid "PAIR" msgstr "EMPARELHAR" -#: selfdrive/ui/layouts/sidebar.py:142 +#: openpilot/selfdrive/ui/layouts/sidebar.py:142 msgid "PANDA" msgstr "PANDA" -#: selfdrive/ui/layouts/settings/device.py:62 +#: openpilot/selfdrive/ui/layouts/settings/device.py:60 #, python-format msgid "PREVIEW" msgstr "PRÉVIA" -#: selfdrive/ui/widgets/prime.py:44 +#: openpilot/selfdrive/ui/widgets/prime.py:44 #, python-format msgid "PRIME FEATURES:" msgstr "RECURSOS PRIME:" -#: selfdrive/ui/layouts/settings/device.py:48 +#: openpilot/selfdrive/ui/layouts/settings/device.py:45 #, python-format msgid "Pair Device" msgstr "Emparelhar Dispositivo" -#: selfdrive/ui/widgets/setup.py:19 +#: openpilot/selfdrive/ui/widgets/setup.py:18 #, python-format msgid "Pair device" msgstr "Emparelhar dispositivo" -#: selfdrive/ui/widgets/pairing_dialog.py:103 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:92 #, python-format msgid "Pair your device to your comma account" msgstr "Emparelhe seu dispositivo à sua conta comma" -#: selfdrive/ui/widgets/setup.py:48 selfdrive/ui/layouts/settings/device.py:24 +#: openpilot/selfdrive/ui/widgets/setup.py:47 +#: openpilot/selfdrive/ui/layouts/settings/device.py:23 #, python-format -msgid "" -"Pair your device with comma connect (connect.comma.ai) and claim your comma " -"prime offer." -msgstr "" -"Emparelhe seu dispositivo com o comma connect (connect.comma.ai) e resgate " -"sua oferta comma prime." +msgid "Pair your device with comma connect (connect.comma.ai) and claim your comma prime offer." +msgstr "Emparelhe seu dispositivo com o comma connect (connect.comma.ai) e resgate sua oferta comma prime." -#: selfdrive/ui/widgets/setup.py:91 +#: openpilot/selfdrive/ui/widgets/setup.py:91 #, python-format msgid "Please connect to Wi-Fi to complete initial pairing" msgstr "Conecte-se ao Wi‑Fi para concluir o emparelhamento inicial" -#: selfdrive/ui/layouts/settings/device.py:55 -#: selfdrive/ui/layouts/settings/device.py:187 +#: openpilot/selfdrive/ui/layouts/settings/device.py:183 +#: openpilot/selfdrive/ui/layouts/settings/device.py:53 #, python-format msgid "Power Off" msgstr "Desligar" -#: system/ui/widgets/network.py:144 +#: system/ui/widgets/network.py:141 #, python-format msgid "Prevent large data uploads when on a metered Wi-Fi connection" msgstr "Evitar uploads grandes de dados em conexões Wi-Fi limitadas" -#: system/ui/widgets/network.py:135 +#: system/ui/widgets/network.py:132 #, python-format msgid "Prevent large data uploads when on a metered cellular connection" msgstr "Evitar uploads grandes de dados em conexões móveis limitadas" -#: selfdrive/ui/layouts/settings/device.py:25 -msgid "" -"Preview the driver facing camera to ensure that driver monitoring has good " -"visibility. (vehicle must be off)" -msgstr "" -"Pré-visualize a câmera voltada para o motorista para garantir que o " -"monitoramento do motorista tenha boa visibilidade. (veículo deve estar " -"desligado)" +#: openpilot/selfdrive/ui/layouts/settings/device.py:24 +msgid "Preview the driver facing camera to ensure that driver monitoring has good visibility. (vehicle must be off)" +msgstr "Pré-visualize a câmera voltada para o motorista para garantir que o monitoramento do motorista tenha boa visibilidade. (veículo deve estar desligado)" -#: selfdrive/ui/widgets/pairing_dialog.py:161 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:150 #, python-format msgid "QR Code Error" msgstr "Erro no QR Code" -#: selfdrive/ui/widgets/ssh_key.py:31 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:31 msgid "REMOVE" msgstr "REMOVER" -#: selfdrive/ui/layouts/settings/device.py:51 +#: openpilot/selfdrive/ui/layouts/settings/device.py:49 #, python-format msgid "RESET" msgstr "REDEFINIR" -#: selfdrive/ui/layouts/settings/device.py:65 +#: openpilot/selfdrive/ui/layouts/settings/device.py:63 #, python-format msgid "REVIEW" msgstr "REVISAR" -#: selfdrive/ui/layouts/settings/device.py:55 -#: selfdrive/ui/layouts/settings/device.py:175 +#: openpilot/selfdrive/ui/layouts/settings/device.py:171 +#: openpilot/selfdrive/ui/layouts/settings/device.py:53 #, python-format msgid "Reboot" msgstr "Reiniciar" -#: selfdrive/ui/onroad/alert_renderer.py:66 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:66 #, python-format msgid "Reboot Device" msgstr "Reiniciar Dispositivo" -#: selfdrive/ui/widgets/offroad_alerts.py:112 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:112 #, python-format msgid "Reboot and Update" msgstr "Reiniciar e Atualizar" -#: selfdrive/ui/layouts/settings/toggles.py:27 -msgid "" -"Receive alerts to steer back into the lane when your vehicle drifts over a " -"detected lane line without a turn signal activated while driving over 31 mph " -"(50 km/h)." -msgstr "" -"Receba alertas para voltar à faixa quando seu veículo cruzar uma linha de " -"faixa detectada sem seta ativada ao dirigir acima de 31 mph (50 km/h)." - -#: selfdrive/ui/layouts/settings/toggles.py:76 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:76 #, python-format msgid "Record and Upload Driver Camera" msgstr "Gravar e Enviar Câmera do Motorista" -#: selfdrive/ui/layouts/settings/toggles.py:82 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:82 #, python-format msgid "Record and Upload Microphone Audio" msgstr "Gravar e Enviar Áudio do Microfone" -#: selfdrive/ui/layouts/settings/toggles.py:33 -msgid "" -"Record and store microphone audio while driving. The audio will be included " -"in the dashcam video in comma connect." -msgstr "" -"Grave e armazene o áudio do microfone enquanto dirige. O áudio será incluído " -"no vídeo da dashcam no comma connect." +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:33 +msgid "Record and store microphone audio while driving. The audio will be included in the dashcam video in comma connect." +msgstr "Grave e armazene o áudio do microfone enquanto dirige. O áudio será incluído no vídeo da dashcam no comma connect." -#: selfdrive/ui/layouts/settings/device.py:67 +#: openpilot/selfdrive/ui/layouts/settings/device.py:65 #, python-format msgid "Regulatory" msgstr "Regulatório" -#: selfdrive/ui/layouts/settings/toggles.py:98 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:98 #, python-format msgid "Relaxed" msgstr "Relaxado" -#: selfdrive/ui/widgets/prime.py:47 +#: openpilot/selfdrive/ui/widgets/prime.py:47 #, python-format msgid "Remote access" msgstr "Acesso remoto" -#: selfdrive/ui/widgets/prime.py:47 +#: openpilot/selfdrive/ui/widgets/prime.py:47 #, python-format msgid "Remote snapshots" msgstr "Capturas remotas" -#: selfdrive/ui/widgets/ssh_key.py:123 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:124 #, python-format msgid "Request timed out" msgstr "Tempo da solicitação esgotado" -#: selfdrive/ui/layouts/settings/device.py:119 +#: openpilot/selfdrive/ui/layouts/settings/device.py:111 #, python-format msgid "Reset" msgstr "Redefinir" -#: selfdrive/ui/layouts/settings/device.py:51 +#: openpilot/selfdrive/ui/layouts/settings/device.py:49 #, python-format msgid "Reset Calibration" msgstr "Redefinir Calibração" -#: selfdrive/ui/layouts/settings/device.py:65 +#: openpilot/selfdrive/ui/layouts/settings/device.py:63 #, python-format msgid "Review Training Guide" msgstr "Revisar Guia de Treinamento" -#: selfdrive/ui/layouts/settings/device.py:27 +#: openpilot/selfdrive/ui/layouts/settings/device.py:26 msgid "Review the rules, features, and limitations of openpilot" msgstr "Revise as regras, recursos e limitações do openpilot" -#: selfdrive/ui/layouts/settings/software.py:61 +#: openpilot/selfdrive/ui/layouts/settings/software.py:68 #, python-format msgid "SELECT" msgstr "SELECIONAR" -#: selfdrive/ui/layouts/settings/developer.py:53 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:53 #, python-format msgid "SSH Keys" msgstr "Chaves SSH" -#: system/ui/widgets/network.py:310 +#: system/ui/widgets/network.py:316 #, python-format msgid "Scanning Wi-Fi networks..." msgstr "Procurando redes Wi-Fi..." -#: system/ui/widgets/option_dialog.py:36 +#: system/ui/widgets/option_dialog.py:37 #, python-format msgid "Select" msgstr "Selecione" -#: selfdrive/ui/layouts/settings/software.py:183 +#: openpilot/selfdrive/ui/layouts/settings/software.py:203 #, python-format msgid "Select a branch" msgstr "Selecione uma branch" -#: selfdrive/ui/layouts/settings/device.py:91 +#: openpilot/selfdrive/ui/layouts/settings/device.py:89 #, python-format msgid "Select a language" msgstr "Selecione um idioma" -#: selfdrive/ui/layouts/settings/device.py:60 +#: openpilot/selfdrive/ui/layouts/settings/device.py:58 #, python-format msgid "Serial" msgstr "Serial" -#: selfdrive/ui/widgets/offroad_alerts.py:106 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:106 #, python-format msgid "Snooze Update" msgstr "Adiar Atualização" -#: selfdrive/ui/layouts/settings/settings.py:65 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:62 msgid "Software" msgstr "Software" -#: selfdrive/ui/layouts/settings/toggles.py:98 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:98 #, python-format msgid "Standard" msgstr "Padrão" -#: selfdrive/ui/layouts/settings/toggles.py:22 -msgid "" -"Standard is recommended. In aggressive mode, openpilot will follow lead cars " -"closer and be more aggressive with the gas and brake. In relaxed mode " -"openpilot will stay further away from lead cars. On supported cars, you can " -"cycle through these personalities with your steering wheel distance button." -msgstr "" -"Padrão é recomendado. No modo agressivo, o openpilot seguirá veículos à " -"frente mais de perto e será mais agressivo com acelerador e freio. No modo " -"relaxado, o openpilot ficará mais longe dos veículos à frente. Em carros " -"compatíveis, você pode alternar essas personalidades com o botão de " -"distância do volante." - -#: selfdrive/ui/onroad/alert_renderer.py:59 -#: selfdrive/ui/onroad/alert_renderer.py:65 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:59 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:65 #, python-format msgid "System Unresponsive" msgstr "Sistema sem resposta" -#: selfdrive/ui/onroad/alert_renderer.py:58 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:58 #, python-format msgid "TAKE CONTROL IMMEDIATELY" msgstr "ASSUMA O CONTROLE IMEDIATAMENTE" -#: selfdrive/ui/layouts/sidebar.py:71 selfdrive/ui/layouts/sidebar.py:125 -#: selfdrive/ui/layouts/sidebar.py:127 selfdrive/ui/layouts/sidebar.py:129 +#: openpilot/selfdrive/ui/layouts/sidebar.py:71 +#: openpilot/selfdrive/ui/layouts/sidebar.py:125 +#: openpilot/selfdrive/ui/layouts/sidebar.py:127 +#: openpilot/selfdrive/ui/layouts/sidebar.py:129 msgid "TEMP" msgstr "TEMP" -#: selfdrive/ui/layouts/settings/software.py:61 +#: openpilot/selfdrive/ui/layouts/settings/software.py:68 #, python-format msgid "Target Branch" msgstr "Branch Alvo" -#: system/ui/widgets/network.py:124 +#: system/ui/widgets/network.py:121 #, python-format msgid "Tethering Password" msgstr "Senha Tethering" -#: selfdrive/ui/layouts/settings/settings.py:64 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:61 msgid "Toggles" msgstr "Toggles" -#: selfdrive/ui/layouts/settings/software.py:72 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:79 +#, python-format +msgid "UI Debug Mode" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/software.py:79 #, python-format msgid "UNINSTALL" msgstr "DESINSTALAR" -#: selfdrive/ui/layouts/home.py:155 +#: openpilot/selfdrive/ui/layouts/home.py:155 #, python-format msgid "UPDATE" msgstr "ATUALIZAR" -#: selfdrive/ui/layouts/settings/software.py:72 -#: selfdrive/ui/layouts/settings/software.py:163 +#: openpilot/selfdrive/ui/layouts/settings/software.py:173 +#: openpilot/selfdrive/ui/layouts/settings/software.py:79 #, python-format msgid "Uninstall" msgstr "Desinstalar" -#: selfdrive/ui/layouts/sidebar.py:117 +#: openpilot/selfdrive/ui/layouts/sidebar.py:117 msgid "Unknown" msgstr "Desconhecido" -#: selfdrive/ui/layouts/settings/software.py:48 +#: openpilot/selfdrive/ui/layouts/settings/software.py:55 #, python-format msgid "Updates are only downloaded while the car is off." msgstr "Atualizações são baixadas apenas com o carro desligado." -#: selfdrive/ui/widgets/prime.py:33 +#: openpilot/selfdrive/ui/widgets/prime.py:33 #, python-format msgid "Upgrade Now" msgstr "Atualizar Agora" -#: selfdrive/ui/layouts/settings/toggles.py:31 -msgid "" -"Upload data from the driver facing camera and help improve the driver " -"monitoring algorithm." -msgstr "" -"Envie dados da câmera voltada para o motorista e ajude a melhorar o " -"algoritmo de monitoramento do motorista." +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:31 +msgid "Upload data from the driver facing camera and help improve the driver monitoring algorithm." +msgstr "Envie dados da câmera voltada para o motorista e ajude a melhorar o algoritmo de monitoramento do motorista." -#: selfdrive/ui/layouts/settings/toggles.py:88 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:88 #, python-format msgid "Use Metric System" msgstr "Usar Sistema Métrico" -#: selfdrive/ui/layouts/settings/toggles.py:17 -msgid "" -"Use the openpilot system for adaptive cruise control and lane keep driver " -"assistance. Your attention is required at all times to use this feature." -msgstr "" -"Use o sistema openpilot para controle de cruzeiro adaptativo e assistência " -"de permanência em faixa. Sua atenção é necessária o tempo todo para usar " -"este recurso." - -#: selfdrive/ui/layouts/sidebar.py:72 selfdrive/ui/layouts/sidebar.py:144 +#: openpilot/selfdrive/ui/layouts/sidebar.py:72 +#: openpilot/selfdrive/ui/layouts/sidebar.py:144 msgid "VEHICLE" msgstr "VEÍCULO" -#: selfdrive/ui/layouts/settings/device.py:67 +#: openpilot/selfdrive/ui/layouts/settings/device.py:65 #, python-format msgid "VIEW" msgstr "VER" -#: selfdrive/ui/onroad/alert_renderer.py:52 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:52 #, python-format msgid "Waiting to start" msgstr "Aguardando para iniciar" -#: selfdrive/ui/layouts/settings/developer.py:19 -msgid "" -"Warning: This grants SSH access to all public keys in your GitHub settings. " -"Never enter a GitHub username other than your own. A comma employee will " -"NEVER ask you to add their GitHub username." -msgstr "" -"Aviso: Isso concede acesso SSH a todas as chaves públicas nas suas " -"configurações do GitHub. Nunca informe um nome de usuário do GitHub que não " -"seja o seu. Um funcionário da comma NUNCA pedirá para você adicionar o nome " -"de usuário do GitHub dele." - -#: selfdrive/ui/layouts/onboarding.py:111 +#: openpilot/selfdrive/ui/layouts/onboarding.py:115 #, python-format msgid "Welcome to openpilot" msgstr "Bem-vindo ao openpilot" -#: selfdrive/ui/layouts/settings/toggles.py:20 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:20 msgid "When enabled, pressing the accelerator pedal will disengage openpilot." -msgstr "" -"Quando ativado, pressionar o pedal do acelerador desengajará o openpilot." +msgstr "Quando ativado, pressionar o pedal do acelerador desengajará o openpilot." -#: selfdrive/ui/layouts/sidebar.py:44 +#: openpilot/selfdrive/ui/layouts/sidebar.py:44 msgid "Wi-Fi" msgstr "Wi‑Fi" -#: system/ui/widgets/network.py:144 +#: system/ui/widgets/network.py:141 #, python-format msgid "Wi-Fi Network Metered" msgstr "Rede Wi-Fi limitada" -#: system/ui/widgets/network.py:314 +#: system/ui/widgets/network.py:320 #, python-format msgid "Wrong password" msgstr "Senha errada" -#: selfdrive/ui/layouts/onboarding.py:145 +#: openpilot/selfdrive/ui/layouts/onboarding.py:149 #, python-format msgid "You must accept the Terms and Conditions in order to use openpilot." msgstr "Você deve aceitar os Termos e Condições para usar o openpilot." -#: selfdrive/ui/layouts/onboarding.py:112 +#: openpilot/selfdrive/ui/layouts/onboarding.py:116 #, python-format -msgid "" -"You must accept the Terms and Conditions to use openpilot. Read the latest " -"terms at https://comma.ai/terms before continuing." -msgstr "" -"Você deve aceitar os Termos e Condições para usar o openpilot. Leia os " -"termos mais recentes em https://comma.ai/terms antes de continuar." +msgid "You must accept the Terms and Conditions to use openpilot. Read the latest terms at https://comma.ai/terms before continuing." +msgstr "Você deve aceitar os Termos e Condições para usar o openpilot. Leia os termos mais recentes em https://comma.ai/terms antes de continuar." -#: selfdrive/ui/onroad/driver_camera_dialog.py:34 +#: openpilot/selfdrive/ui/onroad/driver_camera_dialog.py:38 #, python-format msgid "camera starting" msgstr "câmera iniciando" -#: selfdrive/ui/widgets/prime.py:63 +#: openpilot/selfdrive/ui/layouts/settings/software.py:19 +#, python-format +msgid "checking..." +msgstr "" + +#: openpilot/selfdrive/ui/widgets/prime.py:63 #, python-format msgid "comma prime" msgstr "comma prime" -#: system/ui/widgets/network.py:142 +#: system/ui/widgets/network.py:139 #, python-format msgid "default" msgstr "default" -#: selfdrive/ui/layouts/settings/device.py:133 +#: openpilot/selfdrive/ui/layouts/settings/device.py:125 #, python-format msgid "down" msgstr "para baixo" -#: selfdrive/ui/layouts/settings/software.py:106 +#: openpilot/selfdrive/ui/layouts/settings/software.py:20 +#, python-format +msgid "downloading..." +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/software.py:116 #, python-format msgid "failed to check for update" msgstr "falha ao verificar atualização" -#: system/ui/widgets/network.py:237 system/ui/widgets/network.py:314 +#: openpilot/selfdrive/ui/layouts/settings/software.py:21 +#, python-format +msgid "finalizing update..." +msgstr "" + +#: system/ui/widgets/network.py:238 +#: system/ui/widgets/network.py:321 #, python-format msgid "for \"{}\"" msgstr "para \"{}\"" -#: selfdrive/ui/onroad/hud_renderer.py:177 +#: openpilot/selfdrive/ui/onroad/hud_renderer.py:177 #, python-format msgid "km/h" msgstr "km/h" -#: system/ui/widgets/network.py:204 +#: system/ui/widgets/network.py:201 #, python-format msgid "leave blank for automatic configuration" msgstr "deixe em branco para configuração automática" -#: selfdrive/ui/layouts/settings/device.py:134 +#: openpilot/selfdrive/ui/layouts/settings/device.py:126 #, python-format msgid "left" msgstr "à esquerda" -#: system/ui/widgets/network.py:142 +#: system/ui/widgets/network.py:139 #, python-format msgid "metered" msgstr "limitados" -#: selfdrive/ui/onroad/hud_renderer.py:177 +#: openpilot/selfdrive/ui/onroad/hud_renderer.py:177 #, python-format msgid "mph" msgstr "mph" -#: selfdrive/ui/layouts/settings/software.py:20 +#: openpilot/selfdrive/ui/layouts/settings/software.py:27 #, python-format msgid "never" msgstr "nunca" -#: selfdrive/ui/layouts/settings/software.py:31 +#: openpilot/selfdrive/ui/layouts/settings/software.py:38 #, python-format msgid "now" msgstr "agora" -#: selfdrive/ui/layouts/settings/developer.py:71 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:71 #, python-format msgid "openpilot Longitudinal Control (Alpha)" msgstr "Controle Longitudinal do openpilot (Alpha)" -#: selfdrive/ui/onroad/alert_renderer.py:51 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:51 #, python-format msgid "openpilot Unavailable" msgstr "openpilot Indisponível" -#: selfdrive/ui/layouts/settings/toggles.py:158 -#, python-format -msgid "" -"openpilot defaults to driving in chill mode. Experimental mode enables " -"alpha-level features that aren't ready for chill mode. Experimental features " -"are listed below:

End-to-End Longitudinal Control


Let the " -"driving model control the gas and brakes. openpilot will drive as it thinks " -"a human would, including stopping for red lights and stop signs. Since the " -"driving model decides the speed to drive, the set speed will only act as an " -"upper bound. This is an alpha quality feature; mistakes should be " -"expected.

New Driving Visualization


The driving visualization " -"will transition to the road-facing wide-angle camera at low speeds to better " -"show some turns. The Experimental mode logo will also be shown in the top " -"right corner." -msgstr "" -"o openpilot dirige por padrão no modo chill. O Modo Experimental habilita " -"recursos em nível alpha que não estão prontos para o modo chill. Os recursos " -"experimentais são listados abaixo:

Controle Longitudinal " -"End-to-End


Permita que o modelo de condução controle o acelerador e " -"os freios. O openpilot dirigirá como acha que um humano faria, incluindo " -"parar em sinais e semáforos vermelhos. Como o modelo decide a velocidade, a " -"velocidade definida atuará apenas como limite superior. Este é um recurso de " -"qualidade alpha; erros devem ser esperados.

Nova Visualização de " -"Condução


A visualização de condução mudará para a câmera " -"grande-angular voltada para a estrada em baixas velocidades para mostrar " -"melhor algumas curvas. O logotipo do Modo Experimental também será exibido " -"no canto superior direito." - -#: selfdrive/ui/layouts/settings/device.py:165 -#, python-format -msgid "" -"openpilot is continuously calibrating, resetting is rarely required. " -"Resetting calibration will restart openpilot if the car is powered on." -msgstr "" -"O openpilot está continuamente calibrando, resetar é raramente solicitado. " -"Alterar esta configuração reiniciará o openpilot se o carro estiver ligado." - -#: selfdrive/ui/layouts/settings/firehose.py:20 -msgid "" -"openpilot learns to drive by watching humans, like you, drive.\n" -"\n" -"Firehose Mode allows you to maximize your training data uploads to improve " -"openpilot's driving models. More data means bigger models, which means " -"better Experimental Mode." -msgstr "" -"o openpilot aprende a dirigir observando humanos, como você, dirigirem.\n" -"\n" -"O Modo Firehose permite maximizar seus envios de dados de treinamento para " -"melhorar os modelos de condução do openpilot. Mais dados significam modelos " -"maiores, o que significa um Modo Experimental melhor." - -#: selfdrive/ui/layouts/settings/toggles.py:183 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:184 #, python-format msgid "openpilot longitudinal control may come in a future update." -msgstr "" -"o controle longitudinal do openpilot pode vir em uma atualização futura." +msgstr "o controle longitudinal do openpilot pode vir em uma atualização futura." -#: selfdrive/ui/layouts/settings/device.py:26 -msgid "" -"openpilot requires the device to be mounted within 4° left or right and " -"within 5° up or 9° down." -msgstr "" -"o openpilot requer que o dispositivo seja montado dentro de 4° para a " -"esquerda ou direita e dentro de 5° para cima ou 9° para baixo." +#: openpilot/selfdrive/ui/layouts/settings/device.py:25 +msgid "openpilot requires the device to be mounted within 4° left or right and within 5° up or 9° down." +msgstr "o openpilot requer que o dispositivo seja montado dentro de 4° para a esquerda ou direita e dentro de 5° para cima ou 9° para baixo." -#: selfdrive/ui/layouts/settings/device.py:134 +#: openpilot/selfdrive/ui/layouts/settings/device.py:126 #, python-format msgid "right" msgstr "à direita" -#: system/ui/widgets/network.py:142 +#: system/ui/widgets/network.py:139 #, python-format msgid "unmetered" msgstr "ilimitados" -#: selfdrive/ui/layouts/settings/device.py:133 +#: openpilot/selfdrive/ui/layouts/settings/device.py:125 #, python-format msgid "up" msgstr "para cima" -#: selfdrive/ui/layouts/settings/software.py:117 +#: openpilot/selfdrive/ui/layouts/settings/software.py:127 #, python-format msgid "up to date, last checked never" msgstr "atualizado, última verificação: nunca" -#: selfdrive/ui/layouts/settings/software.py:115 +#: openpilot/selfdrive/ui/layouts/settings/software.py:125 #, python-format msgid "up to date, last checked {}" msgstr "atualizado, última verificação: {}" -#: selfdrive/ui/layouts/settings/software.py:109 +#: openpilot/selfdrive/ui/layouts/settings/software.py:119 #, python-format msgid "update available" msgstr "atualização disponível" -#: selfdrive/ui/layouts/home.py:169 +#: openpilot/selfdrive/ui/layouts/home.py:169 #, python-format msgid "{} ALERT" msgid_plural "{} ALERTS" msgstr[0] "{} ALERTA" msgstr[1] "{} ALERTAS" -#: selfdrive/ui/layouts/settings/software.py:40 +#: openpilot/selfdrive/ui/layouts/settings/software.py:47 #, python-format msgid "{} day ago" msgid_plural "{} days ago" msgstr[0] "{} dia atrás" msgstr[1] "{} dias atrás" -#: selfdrive/ui/layouts/settings/software.py:37 +#: openpilot/selfdrive/ui/layouts/settings/software.py:44 #, python-format msgid "{} hour ago" msgid_plural "{} hours ago" msgstr[0] "{} hora atrás" msgstr[1] "{} horas atrás" -#: selfdrive/ui/layouts/settings/software.py:34 +#: openpilot/selfdrive/ui/layouts/settings/software.py:41 #, python-format msgid "{} minute ago" msgid_plural "{} minutes ago" msgstr[0] "{} minuto atrás" msgstr[1] "{} minutos atrás" -#: selfdrive/ui/layouts/settings/firehose.py:111 +#: openpilot/selfdrive/ui/layouts/settings/firehose.py:70 #, python-format msgid "{} segment of your driving is in the training dataset so far." msgid_plural "{} segments of your driving is in the training dataset so far." -msgstr[0] "" -"{} segmento da sua condução está no conjunto de treinamento até agora." -msgstr[1] "" -"{} segmentos da sua condução estão no conjunto de treinamento até agora." +msgstr[0] "{} segmento da sua condução está no conjunto de treinamento até agora." +msgstr[1] "{} segmentos da sua condução estão no conjunto de treinamento até agora." -#: selfdrive/ui/widgets/prime.py:62 +#: openpilot/selfdrive/ui/widgets/prime.py:62 #, python-format msgid "✓ SUBSCRIBED" msgstr "✓ ASSINADO" -#: selfdrive/ui/widgets/setup.py:22 +#: openpilot/selfdrive/ui/widgets/setup.py:21 #, python-format msgid "🔥 Firehose Mode 🔥" msgstr "🔥 Modo Firehose 🔥" + diff --git a/selfdrive/ui/translations/app_th.po b/selfdrive/ui/translations/app_th.po index f2e56f2882..facf52d922 100644 --- a/selfdrive/ui/translations/app_th.po +++ b/selfdrive/ui/translations/app_th.po @@ -17,1113 +17,1018 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=1; plural=0;\n" -#: selfdrive/ui/layouts/settings/device.py:160 +#: openpilot/selfdrive/ui/layouts/settings/device.py:152 #, python-format msgid " Steering torque response calibration is complete." msgstr "" -#: selfdrive/ui/layouts/settings/device.py:158 +#: openpilot/selfdrive/ui/layouts/settings/device.py:150 #, python-format msgid " Steering torque response calibration is {}% complete." msgstr "" -#: selfdrive/ui/layouts/settings/device.py:133 +#: openpilot/selfdrive/ui/layouts/settings/device.py:125 #, python-format msgid " Your device is pointed {:.1f}° {} and {:.1f}° {}." msgstr "" -#: selfdrive/ui/layouts/sidebar.py:43 +#: openpilot/selfdrive/ui/layouts/sidebar.py:43 msgid "--" msgstr "" -#: selfdrive/ui/widgets/prime.py:47 +#: openpilot/selfdrive/ui/widgets/prime.py:47 #, python-format msgid "1 year of drive storage" msgstr "" -#: selfdrive/ui/widgets/prime.py:47 +#: openpilot/selfdrive/ui/widgets/prime.py:47 #, python-format msgid "24/7 LTE connectivity" msgstr "" -#: selfdrive/ui/layouts/sidebar.py:46 +#: openpilot/selfdrive/ui/layouts/sidebar.py:46 msgid "2G" msgstr "" -#: selfdrive/ui/layouts/sidebar.py:47 +#: openpilot/selfdrive/ui/layouts/sidebar.py:47 msgid "3G" msgstr "" -#: selfdrive/ui/layouts/sidebar.py:49 +#: openpilot/selfdrive/ui/layouts/sidebar.py:49 msgid "5G" msgstr "" -#: selfdrive/ui/layouts/settings/developer.py:23 -msgid "" -"WARNING: openpilot longitudinal control is in alpha for this car and will " -"disable Automatic Emergency Braking (AEB).

On this car, openpilot " -"defaults to the car's built-in ACC instead of openpilot's longitudinal " -"control. Enable this to switch to openpilot longitudinal control. Enabling " -"Experimental mode is recommended when enabling openpilot longitudinal " -"control alpha. Changing this setting will restart openpilot if the car is " -"powered on." -msgstr "" - -#: selfdrive/ui/layouts/settings/device.py:148 +#: openpilot/selfdrive/ui/layouts/settings/device.py:140 #, python-format msgid "

Steering lag calibration is complete." msgstr "" -#: selfdrive/ui/layouts/settings/device.py:146 +#: openpilot/selfdrive/ui/layouts/settings/device.py:138 #, python-format msgid "

Steering lag calibration is {}% complete." msgstr "" -#: selfdrive/ui/layouts/settings/firehose.py:138 -#, python-format -msgid "ACTIVE" -msgstr "" - -#: selfdrive/ui/layouts/settings/developer.py:15 -msgid "" -"ADB (Android Debug Bridge) allows connecting to your device over USB or over " -"the network. See https://docs.comma.ai/how-to/connect-to-comma for more info." -msgstr "" - -#: selfdrive/ui/widgets/ssh_key.py:30 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:30 msgid "ADD" msgstr "" -#: system/ui/widgets/network.py:139 +#: system/ui/widgets/network.py:136 #, python-format msgid "APN Setting" msgstr "" -#: selfdrive/ui/widgets/offroad_alerts.py:109 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:109 #, python-format msgid "Acknowledge Excessive Actuation" msgstr "" -#: system/ui/widgets/network.py:74 system/ui/widgets/network.py:95 +#: system/ui/widgets/network.py:92 +#: system/ui/widgets/network.py:74 #, python-format msgid "Advanced" msgstr "" -#: selfdrive/ui/layouts/settings/toggles.py:98 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:98 #, python-format msgid "Aggressive" msgstr "" -#: selfdrive/ui/layouts/onboarding.py:116 +#: openpilot/selfdrive/ui/layouts/onboarding.py:120 #, python-format msgid "Agree" msgstr "" -#: selfdrive/ui/layouts/settings/toggles.py:70 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:70 #, python-format msgid "Always-On Driver Monitoring" msgstr "" -#: selfdrive/ui/layouts/settings/toggles.py:186 -#, python-format -msgid "" -"An alpha version of openpilot longitudinal control can be tested, along with " -"Experimental mode, on non-release branches." -msgstr "" - -#: selfdrive/ui/layouts/settings/device.py:187 +#: openpilot/selfdrive/ui/layouts/settings/device.py:183 #, python-format msgid "Are you sure you want to power off?" msgstr "" -#: selfdrive/ui/layouts/settings/device.py:175 +#: openpilot/selfdrive/ui/layouts/settings/device.py:171 #, python-format msgid "Are you sure you want to reboot?" msgstr "" -#: selfdrive/ui/layouts/settings/device.py:119 +#: openpilot/selfdrive/ui/layouts/settings/device.py:111 #, python-format msgid "Are you sure you want to reset calibration?" msgstr "" -#: selfdrive/ui/layouts/settings/software.py:163 +#: openpilot/selfdrive/ui/layouts/settings/software.py:173 #, python-format msgid "Are you sure you want to uninstall?" msgstr "" -#: system/ui/widgets/network.py:99 selfdrive/ui/layouts/onboarding.py:147 +#: system/ui/widgets/network.py:96 +#: openpilot/selfdrive/ui/layouts/onboarding.py:151 #, python-format msgid "Back" msgstr "" -#: selfdrive/ui/widgets/prime.py:38 +#: openpilot/selfdrive/ui/widgets/prime.py:38 #, python-format msgid "Become a comma prime member at connect.comma.ai" msgstr "" -#: selfdrive/ui/widgets/pairing_dialog.py:130 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:119 #, python-format msgid "Bookmark connect.comma.ai to your home screen to use it like an app" msgstr "" -#: selfdrive/ui/layouts/settings/device.py:68 +#: openpilot/selfdrive/ui/layouts/settings/device.py:66 #, python-format msgid "CHANGE" msgstr "" -#: selfdrive/ui/layouts/settings/software.py:50 -#: selfdrive/ui/layouts/settings/software.py:107 -#: selfdrive/ui/layouts/settings/software.py:118 -#: selfdrive/ui/layouts/settings/software.py:147 +#: openpilot/selfdrive/ui/layouts/settings/software.py:157 +#: openpilot/selfdrive/ui/layouts/settings/software.py:57 +#: openpilot/selfdrive/ui/layouts/settings/software.py:117 +#: openpilot/selfdrive/ui/layouts/settings/software.py:128 #, python-format msgid "CHECK" msgstr "" -#: selfdrive/ui/widgets/exp_mode_button.py:50 +#: openpilot/selfdrive/ui/widgets/exp_mode_button.py:51 #, python-format msgid "CHILL MODE ON" msgstr "" -#: system/ui/widgets/network.py:155 selfdrive/ui/layouts/sidebar.py:73 -#: selfdrive/ui/layouts/sidebar.py:134 selfdrive/ui/layouts/sidebar.py:136 -#: selfdrive/ui/layouts/sidebar.py:138 +#: system/ui/widgets/network.py:152 +#: openpilot/selfdrive/ui/layouts/sidebar.py:73 +#: openpilot/selfdrive/ui/layouts/sidebar.py:134 +#: openpilot/selfdrive/ui/layouts/sidebar.py:136 +#: openpilot/selfdrive/ui/layouts/sidebar.py:138 #, python-format msgid "CONNECT" msgstr "" -#: system/ui/widgets/network.py:369 +#: system/ui/widgets/network.py:376 #, python-format msgid "CONNECTING..." msgstr "" -#: system/ui/widgets/confirm_dialog.py:23 system/ui/widgets/option_dialog.py:35 -#: system/ui/widgets/keyboard.py:81 system/ui/widgets/network.py:318 +#: system/ui/widgets/network.py:326 +#: system/ui/widgets/confirm_dialog.py:24 +#: system/ui/widgets/option_dialog.py:36 +#: system/ui/widgets/keyboard.py:83 #, python-format msgid "Cancel" msgstr "" -#: system/ui/widgets/network.py:134 +#: system/ui/widgets/network.py:131 #, python-format msgid "Cellular Metered" msgstr "" -#: selfdrive/ui/layouts/settings/device.py:68 +#: openpilot/selfdrive/ui/layouts/settings/device.py:66 #, python-format msgid "Change Language" msgstr "" -#: selfdrive/ui/layouts/settings/toggles.py:125 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:125 #, python-format msgid "Changing this setting will restart openpilot if the car is powered on." msgstr "" -#: selfdrive/ui/widgets/pairing_dialog.py:129 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:118 #, python-format msgid "Click \"add new device\" and scan the QR code on the right" msgstr "" -#: selfdrive/ui/widgets/offroad_alerts.py:104 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:104 #, python-format msgid "Close" msgstr "" -#: selfdrive/ui/layouts/settings/software.py:49 +#: openpilot/selfdrive/ui/layouts/settings/software.py:56 #, python-format msgid "Current Version" msgstr "" -#: selfdrive/ui/layouts/settings/software.py:110 +#: openpilot/selfdrive/ui/layouts/settings/software.py:120 #, python-format msgid "DOWNLOAD" msgstr "" -#: selfdrive/ui/layouts/onboarding.py:115 +#: openpilot/selfdrive/ui/layouts/onboarding.py:119 #, python-format msgid "Decline" msgstr "" -#: selfdrive/ui/layouts/onboarding.py:148 +#: openpilot/selfdrive/ui/layouts/onboarding.py:152 #, python-format msgid "Decline, uninstall openpilot" msgstr "" -#: selfdrive/ui/layouts/settings/settings.py:67 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:64 msgid "Developer" msgstr "" -#: selfdrive/ui/layouts/settings/settings.py:62 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:59 msgid "Device" msgstr "" -#: selfdrive/ui/layouts/settings/toggles.py:58 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:58 #, python-format msgid "Disengage on Accelerator Pedal" msgstr "" -#: selfdrive/ui/layouts/settings/device.py:184 +#: openpilot/selfdrive/ui/layouts/settings/device.py:176 #, python-format msgid "Disengage to Power Off" msgstr "" -#: selfdrive/ui/layouts/settings/device.py:172 +#: openpilot/selfdrive/ui/layouts/settings/device.py:164 #, python-format msgid "Disengage to Reboot" msgstr "" -#: selfdrive/ui/layouts/settings/device.py:103 +#: openpilot/selfdrive/ui/layouts/settings/device.py:95 #, python-format msgid "Disengage to Reset Calibration" msgstr "" -#: selfdrive/ui/layouts/settings/toggles.py:32 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:32 msgid "Display speed in km/h instead of mph." msgstr "" -#: selfdrive/ui/layouts/settings/device.py:59 +#: openpilot/selfdrive/ui/layouts/settings/device.py:57 #, python-format msgid "Dongle ID" msgstr "" -#: selfdrive/ui/layouts/settings/software.py:50 +#: openpilot/selfdrive/ui/layouts/settings/software.py:57 #, python-format msgid "Download" msgstr "" -#: selfdrive/ui/layouts/settings/device.py:62 +#: openpilot/selfdrive/ui/layouts/settings/device.py:60 #, python-format msgid "Driver Camera" msgstr "" -#: selfdrive/ui/layouts/settings/toggles.py:96 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:96 #, python-format msgid "Driving Personality" msgstr "" -#: system/ui/widgets/network.py:123 system/ui/widgets/network.py:139 +#: system/ui/widgets/network.py:120 +#: system/ui/widgets/network.py:136 #, python-format msgid "EDIT" msgstr "" -#: selfdrive/ui/layouts/sidebar.py:138 +#: openpilot/selfdrive/ui/layouts/sidebar.py:138 msgid "ERROR" msgstr "" -#: selfdrive/ui/layouts/sidebar.py:45 +#: openpilot/selfdrive/ui/layouts/sidebar.py:45 msgid "ETH" msgstr "" -#: selfdrive/ui/widgets/exp_mode_button.py:50 +#: openpilot/selfdrive/ui/widgets/exp_mode_button.py:51 #, python-format msgid "EXPERIMENTAL MODE ON" msgstr "" -#: selfdrive/ui/layouts/settings/developer.py:166 -#: selfdrive/ui/layouts/settings/toggles.py:228 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:229 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:180 #, python-format msgid "Enable" msgstr "" -#: selfdrive/ui/layouts/settings/developer.py:39 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:39 #, python-format msgid "Enable ADB" msgstr "" -#: selfdrive/ui/layouts/settings/toggles.py:64 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:64 #, python-format msgid "Enable Lane Departure Warnings" msgstr "" -#: system/ui/widgets/network.py:129 +#: system/ui/widgets/network.py:126 #, python-format msgid "Enable Roaming" msgstr "" -#: selfdrive/ui/layouts/settings/developer.py:48 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:48 #, python-format msgid "Enable SSH" msgstr "" -#: system/ui/widgets/network.py:120 +#: system/ui/widgets/network.py:117 #, python-format msgid "Enable Tethering" msgstr "" -#: selfdrive/ui/layouts/settings/toggles.py:30 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:30 msgid "Enable driver monitoring even when openpilot is not engaged." msgstr "" -#: selfdrive/ui/layouts/settings/toggles.py:46 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:46 #, python-format msgid "Enable openpilot" msgstr "" -#: selfdrive/ui/layouts/settings/toggles.py:189 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:190 #, python-format -msgid "" -"Enable the openpilot longitudinal control (alpha) toggle to allow " -"Experimental mode." +msgid "Enable the openpilot longitudinal control (alpha) toggle to allow Experimental mode." msgstr "" -#: system/ui/widgets/network.py:204 +#: system/ui/widgets/network.py:201 #, python-format msgid "Enter APN" msgstr "" -#: system/ui/widgets/network.py:241 +#: system/ui/widgets/network.py:243 #, python-format msgid "Enter SSID" msgstr "" -#: system/ui/widgets/network.py:254 +#: system/ui/widgets/network.py:257 #, python-format msgid "Enter new tethering password" msgstr "" -#: system/ui/widgets/network.py:237 system/ui/widgets/network.py:314 +#: system/ui/widgets/network.py:238 +#: system/ui/widgets/network.py:320 #, python-format msgid "Enter password" msgstr "" -#: selfdrive/ui/widgets/ssh_key.py:89 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:89 #, python-format msgid "Enter your GitHub username" msgstr "" -#: system/ui/widgets/list_view.py:123 system/ui/widgets/list_view.py:160 +#: system/ui/widgets/list_view.py:123 +#: system/ui/widgets/list_view.py:160 #, python-format msgid "Error" msgstr "" -#: selfdrive/ui/layouts/settings/toggles.py:52 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:52 #, python-format msgid "Experimental Mode" msgstr "" -#: selfdrive/ui/layouts/settings/toggles.py:181 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:182 #, python-format -msgid "" -"Experimental mode is currently unavailable on this car since the car's stock " -"ACC is used for longitudinal control." +msgid "Experimental mode is currently unavailable on this car since the car's stock ACC is used for longitudinal control." msgstr "" -#: system/ui/widgets/network.py:373 +#: system/ui/widgets/network.py:380 #, python-format msgid "FORGETTING..." msgstr "" -#: selfdrive/ui/widgets/setup.py:44 +#: openpilot/selfdrive/ui/widgets/setup.py:43 #, python-format msgid "Finish Setup" msgstr "" -#: selfdrive/ui/layouts/settings/settings.py:66 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:63 msgid "Firehose" msgstr "" -#: selfdrive/ui/layouts/settings/firehose.py:18 +#: openpilot/selfdrive/ui/layouts/settings/firehose.py:10 msgid "Firehose Mode" msgstr "" -#: selfdrive/ui/layouts/settings/firehose.py:25 -msgid "" -"For maximum effectiveness, bring your device inside and connect to a good " -"USB-C adapter and Wi-Fi weekly.\n" -"\n" -"Firehose Mode can also work while you're driving if connected to a hotspot " -"or unlimited SIM card.\n" -"\n" -"\n" -"Frequently Asked Questions\n" -"\n" -"Does it matter how or where I drive? Nope, just drive as you normally " -"would.\n" -"\n" -"Do all of my segments get pulled in Firehose Mode? No, we selectively pull a " -"subset of your segments.\n" -"\n" -"What's a good USB-C adapter? Any fast phone or laptop charger should be " -"fine.\n" -"\n" -"Does it matter which software I run? Yes, only upstream openpilot (and " -"particular forks) are able to be used for training." -msgstr "" - -#: system/ui/widgets/network.py:318 system/ui/widgets/network.py:451 +#: system/ui/widgets/network.py:458 +#: system/ui/widgets/network.py:326 #, python-format msgid "Forget" msgstr "" -#: system/ui/widgets/network.py:319 +#: system/ui/widgets/network.py:327 #, python-format msgid "Forget Wi-Fi Network \"{}\"?" msgstr "" -#: selfdrive/ui/layouts/sidebar.py:71 selfdrive/ui/layouts/sidebar.py:125 +#: openpilot/selfdrive/ui/layouts/sidebar.py:71 +#: openpilot/selfdrive/ui/layouts/sidebar.py:125 msgid "GOOD" msgstr "" -#: selfdrive/ui/widgets/pairing_dialog.py:128 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:117 #, python-format msgid "Go to https://connect.comma.ai on your phone" msgstr "" -#: selfdrive/ui/layouts/sidebar.py:129 +#: openpilot/selfdrive/ui/layouts/sidebar.py:129 msgid "HIGH" msgstr "" -#: system/ui/widgets/network.py:155 +#: system/ui/widgets/network.py:152 #, python-format msgid "Hidden Network" msgstr "" -#: selfdrive/ui/layouts/settings/firehose.py:140 -#, python-format -msgid "INACTIVE: connect to an unmetered network" -msgstr "" - -#: selfdrive/ui/layouts/settings/software.py:53 -#: selfdrive/ui/layouts/settings/software.py:136 +#: openpilot/selfdrive/ui/layouts/settings/software.py:60 +#: openpilot/selfdrive/ui/layouts/settings/software.py:146 #, python-format msgid "INSTALL" msgstr "" -#: system/ui/widgets/network.py:150 +#: system/ui/widgets/network.py:147 #, python-format msgid "IP Address" msgstr "" -#: selfdrive/ui/layouts/settings/software.py:53 +#: openpilot/selfdrive/ui/layouts/settings/software.py:60 #, python-format msgid "Install Update" msgstr "" -#: selfdrive/ui/layouts/settings/developer.py:56 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:56 #, python-format msgid "Joystick Debug Mode" msgstr "" -#: selfdrive/ui/widgets/ssh_key.py:29 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:29 msgid "LOADING" msgstr "" -#: selfdrive/ui/layouts/sidebar.py:48 +#: openpilot/selfdrive/ui/layouts/sidebar.py:48 msgid "LTE" msgstr "" -#: selfdrive/ui/layouts/settings/developer.py:64 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:64 #, python-format msgid "Longitudinal Maneuver Mode" msgstr "" -#: selfdrive/ui/onroad/hud_renderer.py:148 +#: openpilot/selfdrive/ui/onroad/hud_renderer.py:148 #, python-format msgid "MAX" msgstr "" -#: selfdrive/ui/widgets/setup.py:75 +#: openpilot/selfdrive/ui/widgets/setup.py:74 #, python-format -msgid "" -"Maximize your training data uploads to improve openpilot's driving models." +msgid "Maximize your training data uploads to improve openpilot's driving models." msgstr "" -#: selfdrive/ui/layouts/settings/device.py:59 -#: selfdrive/ui/layouts/settings/device.py:60 +#: openpilot/selfdrive/ui/layouts/settings/device.py:57 +#: openpilot/selfdrive/ui/layouts/settings/device.py:58 #, python-format msgid "N/A" msgstr "" -#: selfdrive/ui/layouts/sidebar.py:142 +#: openpilot/selfdrive/ui/layouts/sidebar.py:142 msgid "NO" msgstr "" -#: selfdrive/ui/layouts/settings/settings.py:63 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:60 msgid "Network" msgstr "" -#: selfdrive/ui/widgets/ssh_key.py:114 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:115 #, python-format msgid "No SSH keys found" msgstr "" -#: selfdrive/ui/widgets/ssh_key.py:126 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:127 #, python-format msgid "No SSH keys found for user '{}'" msgstr "" -#: selfdrive/ui/widgets/offroad_alerts.py:320 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:321 #, python-format msgid "No release notes available." msgstr "" -#: selfdrive/ui/layouts/sidebar.py:73 selfdrive/ui/layouts/sidebar.py:134 +#: openpilot/selfdrive/ui/layouts/sidebar.py:73 +#: openpilot/selfdrive/ui/layouts/sidebar.py:134 msgid "OFFLINE" msgstr "" -#: system/ui/widgets/html_render.py:263 system/ui/widgets/confirm_dialog.py:93 -#: selfdrive/ui/layouts/sidebar.py:127 +#: system/ui/widgets/confirm_dialog.py:93 +#: system/ui/widgets/html_render.py:263 +#: openpilot/selfdrive/ui/layouts/sidebar.py:127 #, python-format msgid "OK" msgstr "" -#: selfdrive/ui/layouts/sidebar.py:72 selfdrive/ui/layouts/sidebar.py:136 -#: selfdrive/ui/layouts/sidebar.py:144 +#: openpilot/selfdrive/ui/layouts/sidebar.py:72 +#: openpilot/selfdrive/ui/layouts/sidebar.py:144 +#: openpilot/selfdrive/ui/layouts/sidebar.py:136 msgid "ONLINE" msgstr "" -#: selfdrive/ui/widgets/setup.py:20 +#: openpilot/selfdrive/ui/widgets/setup.py:19 #, python-format msgid "Open" msgstr "" -#: selfdrive/ui/layouts/settings/device.py:48 +#: openpilot/selfdrive/ui/layouts/settings/device.py:45 #, python-format msgid "PAIR" msgstr "" -#: selfdrive/ui/layouts/sidebar.py:142 +#: openpilot/selfdrive/ui/layouts/sidebar.py:142 msgid "PANDA" msgstr "" -#: selfdrive/ui/layouts/settings/device.py:62 +#: openpilot/selfdrive/ui/layouts/settings/device.py:60 #, python-format msgid "PREVIEW" msgstr "" -#: selfdrive/ui/widgets/prime.py:44 +#: openpilot/selfdrive/ui/widgets/prime.py:44 #, python-format msgid "PRIME FEATURES:" msgstr "" -#: selfdrive/ui/layouts/settings/device.py:48 +#: openpilot/selfdrive/ui/layouts/settings/device.py:45 #, python-format msgid "Pair Device" msgstr "" -#: selfdrive/ui/widgets/setup.py:19 +#: openpilot/selfdrive/ui/widgets/setup.py:18 #, python-format msgid "Pair device" msgstr "" -#: selfdrive/ui/widgets/pairing_dialog.py:103 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:92 #, python-format msgid "Pair your device to your comma account" msgstr "" -#: selfdrive/ui/widgets/setup.py:48 selfdrive/ui/layouts/settings/device.py:24 +#: openpilot/selfdrive/ui/widgets/setup.py:47 +#: openpilot/selfdrive/ui/layouts/settings/device.py:23 #, python-format -msgid "" -"Pair your device with comma connect (connect.comma.ai) and claim your comma " -"prime offer." +msgid "Pair your device with comma connect (connect.comma.ai) and claim your comma prime offer." msgstr "" -#: selfdrive/ui/widgets/setup.py:91 +#: openpilot/selfdrive/ui/widgets/setup.py:91 #, python-format msgid "Please connect to Wi-Fi to complete initial pairing" msgstr "" -#: selfdrive/ui/layouts/settings/device.py:55 -#: selfdrive/ui/layouts/settings/device.py:187 +#: openpilot/selfdrive/ui/layouts/settings/device.py:183 +#: openpilot/selfdrive/ui/layouts/settings/device.py:53 #, python-format msgid "Power Off" msgstr "" -#: system/ui/widgets/network.py:144 +#: system/ui/widgets/network.py:141 #, python-format msgid "Prevent large data uploads when on a metered Wi-Fi connection" msgstr "" -#: system/ui/widgets/network.py:135 +#: system/ui/widgets/network.py:132 #, python-format msgid "Prevent large data uploads when on a metered cellular connection" msgstr "" -#: selfdrive/ui/layouts/settings/device.py:25 -msgid "" -"Preview the driver facing camera to ensure that driver monitoring has good " -"visibility. (vehicle must be off)" +#: openpilot/selfdrive/ui/layouts/settings/device.py:24 +msgid "Preview the driver facing camera to ensure that driver monitoring has good visibility. (vehicle must be off)" msgstr "" -#: selfdrive/ui/widgets/pairing_dialog.py:161 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:150 #, python-format msgid "QR Code Error" msgstr "" -#: selfdrive/ui/widgets/ssh_key.py:31 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:31 msgid "REMOVE" msgstr "" -#: selfdrive/ui/layouts/settings/device.py:51 +#: openpilot/selfdrive/ui/layouts/settings/device.py:49 #, python-format msgid "RESET" msgstr "" -#: selfdrive/ui/layouts/settings/device.py:65 +#: openpilot/selfdrive/ui/layouts/settings/device.py:63 #, python-format msgid "REVIEW" msgstr "" -#: selfdrive/ui/layouts/settings/device.py:55 -#: selfdrive/ui/layouts/settings/device.py:175 +#: openpilot/selfdrive/ui/layouts/settings/device.py:171 +#: openpilot/selfdrive/ui/layouts/settings/device.py:53 #, python-format msgid "Reboot" msgstr "" -#: selfdrive/ui/onroad/alert_renderer.py:66 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:66 #, python-format msgid "Reboot Device" msgstr "" -#: selfdrive/ui/widgets/offroad_alerts.py:112 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:112 #, python-format msgid "Reboot and Update" msgstr "" -#: selfdrive/ui/layouts/settings/toggles.py:27 -msgid "" -"Receive alerts to steer back into the lane when your vehicle drifts over a " -"detected lane line without a turn signal activated while driving over 31 mph " -"(50 km/h)." -msgstr "" - -#: selfdrive/ui/layouts/settings/toggles.py:76 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:76 #, python-format msgid "Record and Upload Driver Camera" msgstr "" -#: selfdrive/ui/layouts/settings/toggles.py:82 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:82 #, python-format msgid "Record and Upload Microphone Audio" msgstr "" -#: selfdrive/ui/layouts/settings/toggles.py:33 -msgid "" -"Record and store microphone audio while driving. The audio will be included " -"in the dashcam video in comma connect." +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:33 +msgid "Record and store microphone audio while driving. The audio will be included in the dashcam video in comma connect." msgstr "" -#: selfdrive/ui/layouts/settings/device.py:67 +#: openpilot/selfdrive/ui/layouts/settings/device.py:65 #, python-format msgid "Regulatory" msgstr "" -#: selfdrive/ui/layouts/settings/toggles.py:98 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:98 #, python-format msgid "Relaxed" msgstr "" -#: selfdrive/ui/widgets/prime.py:47 +#: openpilot/selfdrive/ui/widgets/prime.py:47 #, python-format msgid "Remote access" msgstr "" -#: selfdrive/ui/widgets/prime.py:47 +#: openpilot/selfdrive/ui/widgets/prime.py:47 #, python-format msgid "Remote snapshots" msgstr "" -#: selfdrive/ui/widgets/ssh_key.py:123 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:124 #, python-format msgid "Request timed out" msgstr "" -#: selfdrive/ui/layouts/settings/device.py:119 +#: openpilot/selfdrive/ui/layouts/settings/device.py:111 #, python-format msgid "Reset" msgstr "" -#: selfdrive/ui/layouts/settings/device.py:51 +#: openpilot/selfdrive/ui/layouts/settings/device.py:49 #, python-format msgid "Reset Calibration" msgstr "" -#: selfdrive/ui/layouts/settings/device.py:65 +#: openpilot/selfdrive/ui/layouts/settings/device.py:63 #, python-format msgid "Review Training Guide" msgstr "" -#: selfdrive/ui/layouts/settings/device.py:27 +#: openpilot/selfdrive/ui/layouts/settings/device.py:26 msgid "Review the rules, features, and limitations of openpilot" msgstr "" -#: selfdrive/ui/layouts/settings/software.py:61 +#: openpilot/selfdrive/ui/layouts/settings/software.py:68 #, python-format msgid "SELECT" msgstr "" -#: selfdrive/ui/layouts/settings/developer.py:53 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:53 #, python-format msgid "SSH Keys" msgstr "" -#: system/ui/widgets/network.py:310 +#: system/ui/widgets/network.py:316 #, python-format msgid "Scanning Wi-Fi networks..." msgstr "" -#: system/ui/widgets/option_dialog.py:36 +#: system/ui/widgets/option_dialog.py:37 #, python-format msgid "Select" msgstr "" -#: selfdrive/ui/layouts/settings/software.py:183 +#: openpilot/selfdrive/ui/layouts/settings/software.py:203 #, python-format msgid "Select a branch" msgstr "" -#: selfdrive/ui/layouts/settings/device.py:91 +#: openpilot/selfdrive/ui/layouts/settings/device.py:89 #, python-format msgid "Select a language" msgstr "" -#: selfdrive/ui/layouts/settings/device.py:60 +#: openpilot/selfdrive/ui/layouts/settings/device.py:58 #, python-format msgid "Serial" msgstr "" -#: selfdrive/ui/widgets/offroad_alerts.py:106 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:106 #, python-format msgid "Snooze Update" msgstr "" -#: selfdrive/ui/layouts/settings/settings.py:65 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:62 msgid "Software" msgstr "" -#: selfdrive/ui/layouts/settings/toggles.py:98 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:98 #, python-format msgid "Standard" msgstr "" -#: selfdrive/ui/layouts/settings/toggles.py:22 -msgid "" -"Standard is recommended. In aggressive mode, openpilot will follow lead cars " -"closer and be more aggressive with the gas and brake. In relaxed mode " -"openpilot will stay further away from lead cars. On supported cars, you can " -"cycle through these personalities with your steering wheel distance button." -msgstr "" - -#: selfdrive/ui/onroad/alert_renderer.py:59 -#: selfdrive/ui/onroad/alert_renderer.py:65 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:59 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:65 #, python-format msgid "System Unresponsive" msgstr "" -#: selfdrive/ui/onroad/alert_renderer.py:58 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:58 #, python-format msgid "TAKE CONTROL IMMEDIATELY" msgstr "" -#: selfdrive/ui/layouts/sidebar.py:71 selfdrive/ui/layouts/sidebar.py:125 -#: selfdrive/ui/layouts/sidebar.py:127 selfdrive/ui/layouts/sidebar.py:129 +#: openpilot/selfdrive/ui/layouts/sidebar.py:71 +#: openpilot/selfdrive/ui/layouts/sidebar.py:125 +#: openpilot/selfdrive/ui/layouts/sidebar.py:127 +#: openpilot/selfdrive/ui/layouts/sidebar.py:129 msgid "TEMP" msgstr "" -#: selfdrive/ui/layouts/settings/software.py:61 +#: openpilot/selfdrive/ui/layouts/settings/software.py:68 #, python-format msgid "Target Branch" msgstr "" -#: system/ui/widgets/network.py:124 +#: system/ui/widgets/network.py:121 #, python-format msgid "Tethering Password" msgstr "" -#: selfdrive/ui/layouts/settings/settings.py:64 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:61 msgid "Toggles" msgstr "" -#: selfdrive/ui/layouts/settings/software.py:72 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:79 +#, python-format +msgid "UI Debug Mode" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/software.py:79 #, python-format msgid "UNINSTALL" msgstr "" -#: selfdrive/ui/layouts/home.py:155 +#: openpilot/selfdrive/ui/layouts/home.py:155 #, python-format msgid "UPDATE" msgstr "" -#: selfdrive/ui/layouts/settings/software.py:72 -#: selfdrive/ui/layouts/settings/software.py:163 +#: openpilot/selfdrive/ui/layouts/settings/software.py:173 +#: openpilot/selfdrive/ui/layouts/settings/software.py:79 #, python-format msgid "Uninstall" msgstr "" -#: selfdrive/ui/layouts/sidebar.py:117 +#: openpilot/selfdrive/ui/layouts/sidebar.py:117 msgid "Unknown" msgstr "" -#: selfdrive/ui/layouts/settings/software.py:48 +#: openpilot/selfdrive/ui/layouts/settings/software.py:55 #, python-format msgid "Updates are only downloaded while the car is off." msgstr "" -#: selfdrive/ui/widgets/prime.py:33 +#: openpilot/selfdrive/ui/widgets/prime.py:33 #, python-format msgid "Upgrade Now" msgstr "" -#: selfdrive/ui/layouts/settings/toggles.py:31 -msgid "" -"Upload data from the driver facing camera and help improve the driver " -"monitoring algorithm." +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:31 +msgid "Upload data from the driver facing camera and help improve the driver monitoring algorithm." msgstr "" -#: selfdrive/ui/layouts/settings/toggles.py:88 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:88 #, python-format msgid "Use Metric System" msgstr "" -#: selfdrive/ui/layouts/settings/toggles.py:17 -msgid "" -"Use the openpilot system for adaptive cruise control and lane keep driver " -"assistance. Your attention is required at all times to use this feature." -msgstr "" - -#: selfdrive/ui/layouts/sidebar.py:72 selfdrive/ui/layouts/sidebar.py:144 +#: openpilot/selfdrive/ui/layouts/sidebar.py:72 +#: openpilot/selfdrive/ui/layouts/sidebar.py:144 msgid "VEHICLE" msgstr "" -#: selfdrive/ui/layouts/settings/device.py:67 +#: openpilot/selfdrive/ui/layouts/settings/device.py:65 #, python-format msgid "VIEW" msgstr "" -#: selfdrive/ui/onroad/alert_renderer.py:52 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:52 #, python-format msgid "Waiting to start" msgstr "" -#: selfdrive/ui/layouts/settings/developer.py:19 -msgid "" -"Warning: This grants SSH access to all public keys in your GitHub settings. " -"Never enter a GitHub username other than your own. A comma employee will " -"NEVER ask you to add their GitHub username." -msgstr "" - -#: selfdrive/ui/layouts/onboarding.py:111 +#: openpilot/selfdrive/ui/layouts/onboarding.py:115 #, python-format msgid "Welcome to openpilot" msgstr "" -#: selfdrive/ui/layouts/settings/toggles.py:20 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:20 msgid "When enabled, pressing the accelerator pedal will disengage openpilot." msgstr "" -#: selfdrive/ui/layouts/sidebar.py:44 +#: openpilot/selfdrive/ui/layouts/sidebar.py:44 msgid "Wi-Fi" msgstr "" -#: system/ui/widgets/network.py:144 +#: system/ui/widgets/network.py:141 #, python-format msgid "Wi-Fi Network Metered" msgstr "" -#: system/ui/widgets/network.py:314 +#: system/ui/widgets/network.py:320 #, python-format msgid "Wrong password" msgstr "" -#: selfdrive/ui/layouts/onboarding.py:145 +#: openpilot/selfdrive/ui/layouts/onboarding.py:149 #, python-format msgid "You must accept the Terms and Conditions in order to use openpilot." msgstr "" -#: selfdrive/ui/layouts/onboarding.py:112 +#: openpilot/selfdrive/ui/layouts/onboarding.py:116 #, python-format -msgid "" -"You must accept the Terms and Conditions to use openpilot. Read the latest " -"terms at https://comma.ai/terms before continuing." +msgid "You must accept the Terms and Conditions to use openpilot. Read the latest terms at https://comma.ai/terms before continuing." msgstr "" -#: selfdrive/ui/onroad/driver_camera_dialog.py:34 +#: openpilot/selfdrive/ui/onroad/driver_camera_dialog.py:38 #, python-format msgid "camera starting" msgstr "" -#: selfdrive/ui/widgets/prime.py:63 +#: openpilot/selfdrive/ui/layouts/settings/software.py:19 +#, python-format +msgid "checking..." +msgstr "" + +#: openpilot/selfdrive/ui/widgets/prime.py:63 #, python-format msgid "comma prime" msgstr "" -#: system/ui/widgets/network.py:142 +#: system/ui/widgets/network.py:139 #, python-format msgid "default" msgstr "" -#: selfdrive/ui/layouts/settings/device.py:133 +#: openpilot/selfdrive/ui/layouts/settings/device.py:125 #, python-format msgid "down" msgstr "" -#: selfdrive/ui/layouts/settings/software.py:106 +#: openpilot/selfdrive/ui/layouts/settings/software.py:20 +#, python-format +msgid "downloading..." +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/software.py:116 #, python-format msgid "failed to check for update" msgstr "" -#: system/ui/widgets/network.py:237 system/ui/widgets/network.py:314 +#: openpilot/selfdrive/ui/layouts/settings/software.py:21 +#, python-format +msgid "finalizing update..." +msgstr "" + +#: system/ui/widgets/network.py:238 +#: system/ui/widgets/network.py:321 #, python-format msgid "for \"{}\"" msgstr "" -#: selfdrive/ui/onroad/hud_renderer.py:177 +#: openpilot/selfdrive/ui/onroad/hud_renderer.py:177 #, python-format msgid "km/h" msgstr "" -#: system/ui/widgets/network.py:204 +#: system/ui/widgets/network.py:201 #, python-format msgid "leave blank for automatic configuration" msgstr "" -#: selfdrive/ui/layouts/settings/device.py:134 +#: openpilot/selfdrive/ui/layouts/settings/device.py:126 #, python-format msgid "left" msgstr "" -#: system/ui/widgets/network.py:142 +#: system/ui/widgets/network.py:139 #, python-format msgid "metered" msgstr "" -#: selfdrive/ui/onroad/hud_renderer.py:177 +#: openpilot/selfdrive/ui/onroad/hud_renderer.py:177 #, python-format msgid "mph" msgstr "" -#: selfdrive/ui/layouts/settings/software.py:20 +#: openpilot/selfdrive/ui/layouts/settings/software.py:27 #, python-format msgid "never" msgstr "" -#: selfdrive/ui/layouts/settings/software.py:31 +#: openpilot/selfdrive/ui/layouts/settings/software.py:38 #, python-format msgid "now" msgstr "" -#: selfdrive/ui/layouts/settings/developer.py:71 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:71 #, python-format msgid "openpilot Longitudinal Control (Alpha)" msgstr "" -#: selfdrive/ui/onroad/alert_renderer.py:51 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:51 #, python-format msgid "openpilot Unavailable" msgstr "" -#: selfdrive/ui/layouts/settings/toggles.py:158 -#, python-format -msgid "" -"openpilot defaults to driving in chill mode. Experimental mode enables alpha-" -"level features that aren't ready for chill mode. Experimental features are " -"listed below:

End-to-End Longitudinal Control


Let the driving " -"model control the gas and brakes. openpilot will drive as it thinks a human " -"would, including stopping for red lights and stop signs. Since the driving " -"model decides the speed to drive, the set speed will only act as an upper " -"bound. This is an alpha quality feature; mistakes should be expected." -"

New Driving Visualization


The driving visualization will " -"transition to the road-facing wide-angle camera at low speeds to better show " -"some turns. The Experimental mode logo will also be shown in the top right " -"corner." -msgstr "" - -#: selfdrive/ui/layouts/settings/device.py:165 -#, python-format -msgid "" -"openpilot is continuously calibrating, resetting is rarely required. " -"Resetting calibration will restart openpilot if the car is powered on." -msgstr "" - -#: selfdrive/ui/layouts/settings/firehose.py:20 -msgid "" -"openpilot learns to drive by watching humans, like you, drive.\n" -"\n" -"Firehose Mode allows you to maximize your training data uploads to improve " -"openpilot's driving models. More data means bigger models, which means " -"better Experimental Mode." -msgstr "" - -#: selfdrive/ui/layouts/settings/toggles.py:183 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:184 #, python-format msgid "openpilot longitudinal control may come in a future update." msgstr "" -#: selfdrive/ui/layouts/settings/device.py:26 -msgid "" -"openpilot requires the device to be mounted within 4° left or right and " -"within 5° up or 9° down." +#: openpilot/selfdrive/ui/layouts/settings/device.py:25 +msgid "openpilot requires the device to be mounted within 4° left or right and within 5° up or 9° down." msgstr "" -#: selfdrive/ui/layouts/settings/device.py:134 +#: openpilot/selfdrive/ui/layouts/settings/device.py:126 #, python-format msgid "right" msgstr "" -#: system/ui/widgets/network.py:142 +#: system/ui/widgets/network.py:139 #, python-format msgid "unmetered" msgstr "" -#: selfdrive/ui/layouts/settings/device.py:133 +#: openpilot/selfdrive/ui/layouts/settings/device.py:125 #, python-format msgid "up" msgstr "" -#: selfdrive/ui/layouts/settings/software.py:117 +#: openpilot/selfdrive/ui/layouts/settings/software.py:127 #, python-format msgid "up to date, last checked never" msgstr "" -#: selfdrive/ui/layouts/settings/software.py:115 +#: openpilot/selfdrive/ui/layouts/settings/software.py:125 #, python-format msgid "up to date, last checked {}" msgstr "" -#: selfdrive/ui/layouts/settings/software.py:109 +#: openpilot/selfdrive/ui/layouts/settings/software.py:119 #, python-format msgid "update available" msgstr "" -#: selfdrive/ui/layouts/home.py:169 +#: openpilot/selfdrive/ui/layouts/home.py:169 #, python-format msgid "{} ALERT" msgid_plural "{} ALERTS" msgstr[0] "" msgstr[1] "" -#: selfdrive/ui/layouts/settings/software.py:40 +#: openpilot/selfdrive/ui/layouts/settings/software.py:47 #, python-format msgid "{} day ago" msgid_plural "{} days ago" msgstr[0] "" msgstr[1] "" -#: selfdrive/ui/layouts/settings/software.py:37 +#: openpilot/selfdrive/ui/layouts/settings/software.py:44 #, python-format msgid "{} hour ago" msgid_plural "{} hours ago" msgstr[0] "" msgstr[1] "" -#: selfdrive/ui/layouts/settings/software.py:34 +#: openpilot/selfdrive/ui/layouts/settings/software.py:41 #, python-format msgid "{} minute ago" msgid_plural "{} minutes ago" msgstr[0] "" msgstr[1] "" -#: selfdrive/ui/layouts/settings/firehose.py:111 +#: openpilot/selfdrive/ui/layouts/settings/firehose.py:70 #, python-format msgid "{} segment of your driving is in the training dataset so far." msgid_plural "{} segments of your driving is in the training dataset so far." msgstr[0] "" msgstr[1] "" -#: selfdrive/ui/widgets/prime.py:62 +#: openpilot/selfdrive/ui/widgets/prime.py:62 #, python-format msgid "✓ SUBSCRIBED" msgstr "" -#: selfdrive/ui/widgets/setup.py:22 +#: openpilot/selfdrive/ui/widgets/setup.py:21 #, python-format msgid "🔥 Firehose Mode 🔥" msgstr "" + diff --git a/selfdrive/ui/translations/app_tr.po b/selfdrive/ui/translations/app_tr.po index 10191234a1..137d350c2a 100644 --- a/selfdrive/ui/translations/app_tr.po +++ b/selfdrive/ui/translations/app_tr.po @@ -17,1194 +17,1018 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -#: selfdrive/ui/layouts/settings/device.py:160 +#: openpilot/selfdrive/ui/layouts/settings/device.py:152 #, python-format msgid " Steering torque response calibration is complete." msgstr " Direksiyon tork tepkisi kalibrasyonu tamamlandı." -#: selfdrive/ui/layouts/settings/device.py:158 +#: openpilot/selfdrive/ui/layouts/settings/device.py:150 #, python-format msgid " Steering torque response calibration is {}% complete." msgstr " Direksiyon tork tepkisi kalibrasyonu {}% tamamlandı." -#: selfdrive/ui/layouts/settings/device.py:133 +#: openpilot/selfdrive/ui/layouts/settings/device.py:125 #, python-format msgid " Your device is pointed {:.1f}° {} and {:.1f}° {}." msgstr " Cihazınız {:.1f}° {} ve {:.1f}° {} yönünde konumlandırılmış." -#: selfdrive/ui/layouts/sidebar.py:43 +#: openpilot/selfdrive/ui/layouts/sidebar.py:43 msgid "--" msgstr "--" -#: selfdrive/ui/widgets/prime.py:47 +#: openpilot/selfdrive/ui/widgets/prime.py:47 #, python-format msgid "1 year of drive storage" msgstr "1 yıl sürüş depolaması" -#: selfdrive/ui/widgets/prime.py:47 +#: openpilot/selfdrive/ui/widgets/prime.py:47 #, python-format msgid "24/7 LTE connectivity" msgstr "7/24 LTE bağlantısı" -#: selfdrive/ui/layouts/sidebar.py:46 +#: openpilot/selfdrive/ui/layouts/sidebar.py:46 msgid "2G" msgstr "2G" -#: selfdrive/ui/layouts/sidebar.py:47 +#: openpilot/selfdrive/ui/layouts/sidebar.py:47 msgid "3G" msgstr "3G" -#: selfdrive/ui/layouts/sidebar.py:49 +#: openpilot/selfdrive/ui/layouts/sidebar.py:49 msgid "5G" msgstr "5G" -#: selfdrive/ui/layouts/settings/developer.py:23 -msgid "" -"WARNING: openpilot longitudinal control is in alpha for this car and will " -"disable Automatic Emergency Braking (AEB).

On this car, openpilot " -"defaults to the car's built-in ACC instead of openpilot's longitudinal " -"control. Enable this to switch to openpilot longitudinal control. Enabling " -"Experimental mode is recommended when enabling openpilot longitudinal " -"control alpha. Changing this setting will restart openpilot if the car is " -"powered on." -msgstr "" -"UYARI: Bu araç için openpilot boylamsal kontrolü alfa aşamasındadır ve " -"Otomatik Acil Frenlemeyi (AEB) devre dışı bırakacaktır.

Bu araçta " -"openpilot, openpilot'un boylamsal kontrolü yerine aracın yerleşik ACC'sini " -"varsayılan olarak kullanır. openpilot boylamsal kontrolüne geçmek için bunu " -"etkinleştirin. openpilot boylamsal kontrol alfayı etkinleştirirken Deneysel " -"modu etkinleştirmeniz önerilir." - -#: selfdrive/ui/layouts/settings/device.py:148 +#: openpilot/selfdrive/ui/layouts/settings/device.py:140 #, python-format msgid "

Steering lag calibration is complete." msgstr "" -#: selfdrive/ui/layouts/settings/device.py:146 +#: openpilot/selfdrive/ui/layouts/settings/device.py:138 #, python-format msgid "

Steering lag calibration is {}% complete." msgstr "" -#: selfdrive/ui/layouts/settings/firehose.py:138 -#, python-format -msgid "ACTIVE" -msgstr "AKTİF" - -#: selfdrive/ui/layouts/settings/developer.py:15 -msgid "" -"ADB (Android Debug Bridge) allows connecting to your device over USB or over " -"the network. See https://docs.comma.ai/how-to/connect-to-comma for more info." -msgstr "" -"ADB (Android Debug Bridge), cihazınıza USB veya ağ üzerinden bağlanmayı " -"sağlar. Daha fazla bilgi için https://docs.comma.ai/how-to/connect-to-comma " -"adresine bakın." - -#: selfdrive/ui/widgets/ssh_key.py:30 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:30 msgid "ADD" msgstr "EKLE" -#: system/ui/widgets/network.py:139 +#: system/ui/widgets/network.py:136 #, python-format msgid "APN Setting" msgstr "" -#: selfdrive/ui/widgets/offroad_alerts.py:109 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:109 #, python-format msgid "Acknowledge Excessive Actuation" msgstr "Aşırı Müdahaleyi Onayla" -#: system/ui/widgets/network.py:74 system/ui/widgets/network.py:95 +#: system/ui/widgets/network.py:92 +#: system/ui/widgets/network.py:74 #, python-format msgid "Advanced" msgstr "" -#: selfdrive/ui/layouts/settings/toggles.py:98 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:98 #, python-format msgid "Aggressive" msgstr "Agresif" -#: selfdrive/ui/layouts/onboarding.py:116 +#: openpilot/selfdrive/ui/layouts/onboarding.py:120 #, python-format msgid "Agree" msgstr "Kabul et" -#: selfdrive/ui/layouts/settings/toggles.py:70 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:70 #, python-format msgid "Always-On Driver Monitoring" msgstr "Sürekli Sürücü İzleme" -#: selfdrive/ui/layouts/settings/toggles.py:186 -#, python-format -msgid "" -"An alpha version of openpilot longitudinal control can be tested, along with " -"Experimental mode, on non-release branches." -msgstr "" -"openpilot boylamsal kontrolünün alfa sürümü, Deneysel mod ile birlikte, " -"yayın dışı dallarda test edilebilir." - -#: selfdrive/ui/layouts/settings/device.py:187 +#: openpilot/selfdrive/ui/layouts/settings/device.py:183 #, python-format msgid "Are you sure you want to power off?" msgstr "Kapatmak istediğinizden emin misiniz?" -#: selfdrive/ui/layouts/settings/device.py:175 +#: openpilot/selfdrive/ui/layouts/settings/device.py:171 #, python-format msgid "Are you sure you want to reboot?" msgstr "Yeniden başlatmak istediğinizden emin misiniz?" -#: selfdrive/ui/layouts/settings/device.py:119 +#: openpilot/selfdrive/ui/layouts/settings/device.py:111 #, python-format msgid "Are you sure you want to reset calibration?" msgstr "Kalibrasyonu sıfırlamak istediğinizden emin misiniz?" -#: selfdrive/ui/layouts/settings/software.py:163 +#: openpilot/selfdrive/ui/layouts/settings/software.py:173 #, python-format msgid "Are you sure you want to uninstall?" msgstr "Kaldırmak istediğinizden emin misiniz?" -#: system/ui/widgets/network.py:99 selfdrive/ui/layouts/onboarding.py:147 +#: system/ui/widgets/network.py:96 +#: openpilot/selfdrive/ui/layouts/onboarding.py:151 #, python-format msgid "Back" msgstr "Geri" -#: selfdrive/ui/widgets/prime.py:38 +#: openpilot/selfdrive/ui/widgets/prime.py:38 #, python-format msgid "Become a comma prime member at connect.comma.ai" msgstr "connect.comma.ai adresinde comma prime üyesi olun" -#: selfdrive/ui/widgets/pairing_dialog.py:130 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:119 #, python-format msgid "Bookmark connect.comma.ai to your home screen to use it like an app" -msgstr "" -"connect.comma.ai'yi ana ekranınıza ekleyerek bir uygulama gibi kullanın" +msgstr "connect.comma.ai'yi ana ekranınıza ekleyerek bir uygulama gibi kullanın" -#: selfdrive/ui/layouts/settings/device.py:68 +#: openpilot/selfdrive/ui/layouts/settings/device.py:66 #, python-format msgid "CHANGE" msgstr "DEĞİŞTİR" -#: selfdrive/ui/layouts/settings/software.py:50 -#: selfdrive/ui/layouts/settings/software.py:107 -#: selfdrive/ui/layouts/settings/software.py:118 -#: selfdrive/ui/layouts/settings/software.py:147 +#: openpilot/selfdrive/ui/layouts/settings/software.py:157 +#: openpilot/selfdrive/ui/layouts/settings/software.py:57 +#: openpilot/selfdrive/ui/layouts/settings/software.py:117 +#: openpilot/selfdrive/ui/layouts/settings/software.py:128 #, python-format msgid "CHECK" msgstr "KONTROL ET" -#: selfdrive/ui/widgets/exp_mode_button.py:50 +#: openpilot/selfdrive/ui/widgets/exp_mode_button.py:51 #, python-format msgid "CHILL MODE ON" msgstr "CHILL MODU AÇIK" -#: system/ui/widgets/network.py:155 selfdrive/ui/layouts/sidebar.py:73 -#: selfdrive/ui/layouts/sidebar.py:134 selfdrive/ui/layouts/sidebar.py:136 -#: selfdrive/ui/layouts/sidebar.py:138 +#: system/ui/widgets/network.py:152 +#: openpilot/selfdrive/ui/layouts/sidebar.py:73 +#: openpilot/selfdrive/ui/layouts/sidebar.py:134 +#: openpilot/selfdrive/ui/layouts/sidebar.py:136 +#: openpilot/selfdrive/ui/layouts/sidebar.py:138 #, python-format msgid "CONNECT" msgstr "BAĞLAN" -#: system/ui/widgets/network.py:369 +#: system/ui/widgets/network.py:376 #, python-format msgid "CONNECTING..." msgstr "BAĞLAN" -#: system/ui/widgets/confirm_dialog.py:23 system/ui/widgets/option_dialog.py:35 -#: system/ui/widgets/keyboard.py:81 system/ui/widgets/network.py:318 +#: system/ui/widgets/network.py:326 +#: system/ui/widgets/confirm_dialog.py:24 +#: system/ui/widgets/option_dialog.py:36 +#: system/ui/widgets/keyboard.py:83 #, python-format msgid "Cancel" msgstr "" -#: system/ui/widgets/network.py:134 +#: system/ui/widgets/network.py:131 #, python-format msgid "Cellular Metered" msgstr "" -#: selfdrive/ui/layouts/settings/device.py:68 +#: openpilot/selfdrive/ui/layouts/settings/device.py:66 #, python-format msgid "Change Language" msgstr "Dili Değiştir" -#: selfdrive/ui/layouts/settings/toggles.py:125 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:125 #, python-format msgid "Changing this setting will restart openpilot if the car is powered on." -msgstr "" -" Bu ayarı değiştirmek, araç çalışıyorsa openpilot'u yeniden başlatacaktır." +msgstr " Bu ayarı değiştirmek, araç çalışıyorsa openpilot'u yeniden başlatacaktır." -#: selfdrive/ui/widgets/pairing_dialog.py:129 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:118 #, python-format msgid "Click \"add new device\" and scan the QR code on the right" msgstr "\"yeni cihaz ekle\"ye tıklayın ve sağdaki QR kodunu tarayın" -#: selfdrive/ui/widgets/offroad_alerts.py:104 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:104 #, python-format msgid "Close" msgstr "Kapat" -#: selfdrive/ui/layouts/settings/software.py:49 +#: openpilot/selfdrive/ui/layouts/settings/software.py:56 #, python-format msgid "Current Version" msgstr "Geçerli Sürüm" -#: selfdrive/ui/layouts/settings/software.py:110 +#: openpilot/selfdrive/ui/layouts/settings/software.py:120 #, python-format msgid "DOWNLOAD" msgstr "İNDİR" -#: selfdrive/ui/layouts/onboarding.py:115 +#: openpilot/selfdrive/ui/layouts/onboarding.py:119 #, python-format msgid "Decline" msgstr "Reddet" -#: selfdrive/ui/layouts/onboarding.py:148 +#: openpilot/selfdrive/ui/layouts/onboarding.py:152 #, python-format msgid "Decline, uninstall openpilot" msgstr "Reddet, openpilot'u kaldır" -#: selfdrive/ui/layouts/settings/settings.py:67 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:64 msgid "Developer" msgstr "Geliştirici" -#: selfdrive/ui/layouts/settings/settings.py:62 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:59 msgid "Device" msgstr "Cihaz" -#: selfdrive/ui/layouts/settings/toggles.py:58 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:58 #, python-format msgid "Disengage on Accelerator Pedal" msgstr "Gaz Pedalında Devreden Çık" -#: selfdrive/ui/layouts/settings/device.py:184 +#: openpilot/selfdrive/ui/layouts/settings/device.py:176 #, python-format msgid "Disengage to Power Off" msgstr "Kapatmak için Devreden Çıkın" -#: selfdrive/ui/layouts/settings/device.py:172 +#: openpilot/selfdrive/ui/layouts/settings/device.py:164 #, python-format msgid "Disengage to Reboot" msgstr "Yeniden Başlatmak için Devreden Çıkın" -#: selfdrive/ui/layouts/settings/device.py:103 +#: openpilot/selfdrive/ui/layouts/settings/device.py:95 #, python-format msgid "Disengage to Reset Calibration" msgstr "Kalibrasyonu Sıfırlamak için Devreden Çıkın" -#: selfdrive/ui/layouts/settings/toggles.py:32 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:32 msgid "Display speed in km/h instead of mph." msgstr "Hızı mph yerine km/h olarak göster." -#: selfdrive/ui/layouts/settings/device.py:59 +#: openpilot/selfdrive/ui/layouts/settings/device.py:57 #, python-format msgid "Dongle ID" msgstr "Dongle ID" -#: selfdrive/ui/layouts/settings/software.py:50 +#: openpilot/selfdrive/ui/layouts/settings/software.py:57 #, python-format msgid "Download" msgstr "İndir" -#: selfdrive/ui/layouts/settings/device.py:62 +#: openpilot/selfdrive/ui/layouts/settings/device.py:60 #, python-format msgid "Driver Camera" msgstr "Sürücü Kamerası" -#: selfdrive/ui/layouts/settings/toggles.py:96 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:96 #, python-format msgid "Driving Personality" msgstr "Sürüş Kişiliği" -#: system/ui/widgets/network.py:123 system/ui/widgets/network.py:139 +#: system/ui/widgets/network.py:120 +#: system/ui/widgets/network.py:136 #, python-format msgid "EDIT" msgstr "" -#: selfdrive/ui/layouts/sidebar.py:138 +#: openpilot/selfdrive/ui/layouts/sidebar.py:138 msgid "ERROR" msgstr "HATA" -#: selfdrive/ui/layouts/sidebar.py:45 +#: openpilot/selfdrive/ui/layouts/sidebar.py:45 msgid "ETH" msgstr "ETH" -#: selfdrive/ui/widgets/exp_mode_button.py:50 +#: openpilot/selfdrive/ui/widgets/exp_mode_button.py:51 #, python-format msgid "EXPERIMENTAL MODE ON" msgstr "DENEYSEL MOD AÇIK" -#: selfdrive/ui/layouts/settings/developer.py:166 -#: selfdrive/ui/layouts/settings/toggles.py:228 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:229 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:180 #, python-format msgid "Enable" msgstr "Etkinleştir" -#: selfdrive/ui/layouts/settings/developer.py:39 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:39 #, python-format msgid "Enable ADB" msgstr "ADB'yi Etkinleştir" -#: selfdrive/ui/layouts/settings/toggles.py:64 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:64 #, python-format msgid "Enable Lane Departure Warnings" msgstr "Şerit Terk Uyarılarını Etkinleştir" -#: system/ui/widgets/network.py:129 +#: system/ui/widgets/network.py:126 #, python-format msgid "Enable Roaming" msgstr "openpilot'u etkinleştir" -#: selfdrive/ui/layouts/settings/developer.py:48 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:48 #, python-format msgid "Enable SSH" msgstr "SSH'yi Etkinleştir" -#: system/ui/widgets/network.py:120 +#: system/ui/widgets/network.py:117 #, python-format msgid "Enable Tethering" msgstr "Şerit Terk Uyarılarını Etkinleştir" -#: selfdrive/ui/layouts/settings/toggles.py:30 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:30 msgid "Enable driver monitoring even when openpilot is not engaged." msgstr "openpilot devrede değilken bile sürücü izlemesini etkinleştir." -#: selfdrive/ui/layouts/settings/toggles.py:46 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:46 #, python-format msgid "Enable openpilot" msgstr "openpilot'u etkinleştir" -#: selfdrive/ui/layouts/settings/toggles.py:189 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:190 #, python-format -msgid "" -"Enable the openpilot longitudinal control (alpha) toggle to allow " -"Experimental mode." -msgstr "" -"Deneysel modu etkinleştirmek için openpilot boylamsal kontrolünü (alfa) açın." +msgid "Enable the openpilot longitudinal control (alpha) toggle to allow Experimental mode." +msgstr "Deneysel modu etkinleştirmek için openpilot boylamsal kontrolünü (alfa) açın." -#: system/ui/widgets/network.py:204 +#: system/ui/widgets/network.py:201 #, python-format msgid "Enter APN" msgstr "" -#: system/ui/widgets/network.py:241 +#: system/ui/widgets/network.py:243 #, python-format msgid "Enter SSID" msgstr "" -#: system/ui/widgets/network.py:254 +#: system/ui/widgets/network.py:257 #, python-format msgid "Enter new tethering password" msgstr "" -#: system/ui/widgets/network.py:237 system/ui/widgets/network.py:314 +#: system/ui/widgets/network.py:238 +#: system/ui/widgets/network.py:320 #, python-format msgid "Enter password" msgstr "" -#: selfdrive/ui/widgets/ssh_key.py:89 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:89 #, python-format msgid "Enter your GitHub username" msgstr "GitHub kullanıcı adınızı girin" -#: system/ui/widgets/list_view.py:123 system/ui/widgets/list_view.py:160 +#: system/ui/widgets/list_view.py:123 +#: system/ui/widgets/list_view.py:160 #, python-format msgid "Error" msgstr "" -#: selfdrive/ui/layouts/settings/toggles.py:52 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:52 #, python-format msgid "Experimental Mode" msgstr "Deneysel Mod" -#: selfdrive/ui/layouts/settings/toggles.py:181 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:182 #, python-format -msgid "" -"Experimental mode is currently unavailable on this car since the car's stock " -"ACC is used for longitudinal control." -msgstr "" -"Bu araçta boylamsal kontrol için stok ACC kullanıldığından şu anda Deneysel " -"mod kullanılamıyor." +msgid "Experimental mode is currently unavailable on this car since the car's stock ACC is used for longitudinal control." +msgstr "Bu araçta boylamsal kontrol için stok ACC kullanıldığından şu anda Deneysel mod kullanılamıyor." -#: system/ui/widgets/network.py:373 +#: system/ui/widgets/network.py:380 #, python-format msgid "FORGETTING..." msgstr "" -#: selfdrive/ui/widgets/setup.py:44 +#: openpilot/selfdrive/ui/widgets/setup.py:43 #, python-format msgid "Finish Setup" msgstr "Kurulumu Bitir" -#: selfdrive/ui/layouts/settings/settings.py:66 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:63 msgid "Firehose" msgstr "Firehose" -#: selfdrive/ui/layouts/settings/firehose.py:18 +#: openpilot/selfdrive/ui/layouts/settings/firehose.py:10 msgid "Firehose Mode" msgstr "Firehose Modu" -#: selfdrive/ui/layouts/settings/firehose.py:25 -msgid "" -"For maximum effectiveness, bring your device inside and connect to a good " -"USB-C adapter and Wi-Fi weekly.\n" -"\n" -"Firehose Mode can also work while you're driving if connected to a hotspot " -"or unlimited SIM card.\n" -"\n" -"\n" -"Frequently Asked Questions\n" -"\n" -"Does it matter how or where I drive? Nope, just drive as you normally " -"would.\n" -"\n" -"Do all of my segments get pulled in Firehose Mode? No, we selectively pull a " -"subset of your segments.\n" -"\n" -"What's a good USB-C adapter? Any fast phone or laptop charger should be " -"fine.\n" -"\n" -"Does it matter which software I run? Yes, only upstream openpilot (and " -"particular forks) are able to be used for training." -msgstr "" -"Maksimum verim için cihazınızı içeri alın ve haftalık olarak iyi bir USB-C " -"adaptörüne ve Wi‑Fi'a bağlayın.\n" -"\n" -"Firehose Modu, bir hotspot'a veya sınırsız SIM karta bağlıyken sürüş " -"sırasında da çalışabilir.\n" -"\n" -"\n" -"Sıkça Sorulan Sorular\n" -"\n" -"Nasıl veya nerede sürdüğüm önemli mi? Hayır, normalde nasıl sürüyorsanız " -"öyle sürün.\n" -"\n" -"Firehose Modu'nda tüm segmentlerim çekiliyor mu? Hayır, segmentlerinizin bir " -"alt kümesini seçerek çekiyoruz.\n" -"\n" -"İyi bir USB‑C adaptörü nedir? Hızlı bir telefon veya dizüstü şarj cihazı " -"uygundur.\n" -"\n" -"Hangi yazılımı çalıştırdığım önemli mi? Evet, yalnızca upstream openpilot " -"(ve bazı fork'lar) eğitim için kullanılabilir." - -#: system/ui/widgets/network.py:318 system/ui/widgets/network.py:451 +#: system/ui/widgets/network.py:458 +#: system/ui/widgets/network.py:326 #, python-format msgid "Forget" msgstr "" -#: system/ui/widgets/network.py:319 +#: system/ui/widgets/network.py:327 #, python-format msgid "Forget Wi-Fi Network \"{}\"?" msgstr "" -#: selfdrive/ui/layouts/sidebar.py:71 selfdrive/ui/layouts/sidebar.py:125 +#: openpilot/selfdrive/ui/layouts/sidebar.py:71 +#: openpilot/selfdrive/ui/layouts/sidebar.py:125 msgid "GOOD" msgstr "İYİ" -#: selfdrive/ui/widgets/pairing_dialog.py:128 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:117 #, python-format msgid "Go to https://connect.comma.ai on your phone" msgstr "Telefonunuzda https://connect.comma.ai adresine gidin" -#: selfdrive/ui/layouts/sidebar.py:129 +#: openpilot/selfdrive/ui/layouts/sidebar.py:129 msgid "HIGH" msgstr "YÜKSEK" -#: system/ui/widgets/network.py:155 +#: system/ui/widgets/network.py:152 #, python-format msgid "Hidden Network" msgstr "Ağ" -#: selfdrive/ui/layouts/settings/firehose.py:140 -#, python-format -msgid "INACTIVE: connect to an unmetered network" -msgstr "PASİF: sınırsız bir ağa bağlanın" - -#: selfdrive/ui/layouts/settings/software.py:53 -#: selfdrive/ui/layouts/settings/software.py:136 +#: openpilot/selfdrive/ui/layouts/settings/software.py:60 +#: openpilot/selfdrive/ui/layouts/settings/software.py:146 #, python-format msgid "INSTALL" msgstr "YÜKLE" -#: system/ui/widgets/network.py:150 +#: system/ui/widgets/network.py:147 #, python-format msgid "IP Address" msgstr "" -#: selfdrive/ui/layouts/settings/software.py:53 +#: openpilot/selfdrive/ui/layouts/settings/software.py:60 #, python-format msgid "Install Update" msgstr "Güncellemeyi Yükle" -#: selfdrive/ui/layouts/settings/developer.py:56 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:56 #, python-format msgid "Joystick Debug Mode" msgstr "Joystick Hata Ayıklama Modu" -#: selfdrive/ui/widgets/ssh_key.py:29 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:29 msgid "LOADING" msgstr "YÜKLENİYOR" -#: selfdrive/ui/layouts/sidebar.py:48 +#: openpilot/selfdrive/ui/layouts/sidebar.py:48 msgid "LTE" msgstr "LTE" -#: selfdrive/ui/layouts/settings/developer.py:64 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:64 #, python-format msgid "Longitudinal Maneuver Mode" msgstr "Boylamsal Manevra Modu" -#: selfdrive/ui/onroad/hud_renderer.py:148 +#: openpilot/selfdrive/ui/onroad/hud_renderer.py:148 #, python-format msgid "MAX" msgstr "MAKS" -#: selfdrive/ui/widgets/setup.py:75 +#: openpilot/selfdrive/ui/widgets/setup.py:74 #, python-format -msgid "" -"Maximize your training data uploads to improve openpilot's driving models." -msgstr "" -"openpilot'un sürüş modellerini iyileştirmek için eğitim veri yüklemelerinizi " -"en üst düzeye çıkarın." +msgid "Maximize your training data uploads to improve openpilot's driving models." +msgstr "openpilot'un sürüş modellerini iyileştirmek için eğitim veri yüklemelerinizi en üst düzeye çıkarın." -#: selfdrive/ui/layouts/settings/device.py:59 -#: selfdrive/ui/layouts/settings/device.py:60 +#: openpilot/selfdrive/ui/layouts/settings/device.py:57 +#: openpilot/selfdrive/ui/layouts/settings/device.py:58 #, python-format msgid "N/A" msgstr "" -#: selfdrive/ui/layouts/sidebar.py:142 +#: openpilot/selfdrive/ui/layouts/sidebar.py:142 msgid "NO" msgstr "HAYIR" -#: selfdrive/ui/layouts/settings/settings.py:63 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:60 msgid "Network" msgstr "Ağ" -#: selfdrive/ui/widgets/ssh_key.py:114 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:115 #, python-format msgid "No SSH keys found" msgstr "SSH anahtarı bulunamadı" -#: selfdrive/ui/widgets/ssh_key.py:126 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:127 #, python-format msgid "No SSH keys found for user '{}'" msgstr "'{username}' için SSH anahtarı bulunamadı" -#: selfdrive/ui/widgets/offroad_alerts.py:320 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:321 #, python-format msgid "No release notes available." msgstr "Sürüm notu mevcut değil." -#: selfdrive/ui/layouts/sidebar.py:73 selfdrive/ui/layouts/sidebar.py:134 +#: openpilot/selfdrive/ui/layouts/sidebar.py:73 +#: openpilot/selfdrive/ui/layouts/sidebar.py:134 msgid "OFFLINE" msgstr "ÇEVRİMDIŞI" -#: system/ui/widgets/html_render.py:263 system/ui/widgets/confirm_dialog.py:93 -#: selfdrive/ui/layouts/sidebar.py:127 +#: system/ui/widgets/confirm_dialog.py:93 +#: system/ui/widgets/html_render.py:263 +#: openpilot/selfdrive/ui/layouts/sidebar.py:127 #, python-format msgid "OK" msgstr "OK" -#: selfdrive/ui/layouts/sidebar.py:72 selfdrive/ui/layouts/sidebar.py:136 -#: selfdrive/ui/layouts/sidebar.py:144 +#: openpilot/selfdrive/ui/layouts/sidebar.py:72 +#: openpilot/selfdrive/ui/layouts/sidebar.py:144 +#: openpilot/selfdrive/ui/layouts/sidebar.py:136 msgid "ONLINE" msgstr "ÇEVRİMİÇİ" -#: selfdrive/ui/widgets/setup.py:20 +#: openpilot/selfdrive/ui/widgets/setup.py:19 #, python-format msgid "Open" msgstr "Aç" -#: selfdrive/ui/layouts/settings/device.py:48 +#: openpilot/selfdrive/ui/layouts/settings/device.py:45 #, python-format msgid "PAIR" msgstr "EŞLE" -#: selfdrive/ui/layouts/sidebar.py:142 +#: openpilot/selfdrive/ui/layouts/sidebar.py:142 msgid "PANDA" msgstr "PANDA" -#: selfdrive/ui/layouts/settings/device.py:62 +#: openpilot/selfdrive/ui/layouts/settings/device.py:60 #, python-format msgid "PREVIEW" msgstr "ÖNİZLEME" -#: selfdrive/ui/widgets/prime.py:44 +#: openpilot/selfdrive/ui/widgets/prime.py:44 #, python-format msgid "PRIME FEATURES:" msgstr "PRIME ÖZELLİKLERİ:" -#: selfdrive/ui/layouts/settings/device.py:48 +#: openpilot/selfdrive/ui/layouts/settings/device.py:45 #, python-format msgid "Pair Device" msgstr "Cihazı Eşle" -#: selfdrive/ui/widgets/setup.py:19 +#: openpilot/selfdrive/ui/widgets/setup.py:18 #, python-format msgid "Pair device" msgstr "Cihazı eşle" -#: selfdrive/ui/widgets/pairing_dialog.py:103 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:92 #, python-format msgid "Pair your device to your comma account" msgstr "Cihazınızı comma hesabınızla eşleştirin" -#: selfdrive/ui/widgets/setup.py:48 selfdrive/ui/layouts/settings/device.py:24 +#: openpilot/selfdrive/ui/widgets/setup.py:47 +#: openpilot/selfdrive/ui/layouts/settings/device.py:23 #, python-format -msgid "" -"Pair your device with comma connect (connect.comma.ai) and claim your comma " -"prime offer." -msgstr "" -"Cihazınızı comma connect (connect.comma.ai) ile eşleştirin ve comma prime " -"teklifinizi alın." +msgid "Pair your device with comma connect (connect.comma.ai) and claim your comma prime offer." +msgstr "Cihazınızı comma connect (connect.comma.ai) ile eşleştirin ve comma prime teklifinizi alın." -#: selfdrive/ui/widgets/setup.py:91 +#: openpilot/selfdrive/ui/widgets/setup.py:91 #, python-format msgid "Please connect to Wi-Fi to complete initial pairing" msgstr "İlk eşleştirmeyi tamamlamak için lütfen Wi‑Fi'a bağlanın" -#: selfdrive/ui/layouts/settings/device.py:55 -#: selfdrive/ui/layouts/settings/device.py:187 +#: openpilot/selfdrive/ui/layouts/settings/device.py:183 +#: openpilot/selfdrive/ui/layouts/settings/device.py:53 #, python-format msgid "Power Off" msgstr "Kapat" -#: system/ui/widgets/network.py:144 +#: system/ui/widgets/network.py:141 #, python-format msgid "Prevent large data uploads when on a metered Wi-Fi connection" msgstr "" -#: system/ui/widgets/network.py:135 +#: system/ui/widgets/network.py:132 #, python-format msgid "Prevent large data uploads when on a metered cellular connection" msgstr "" -#: selfdrive/ui/layouts/settings/device.py:25 -msgid "" -"Preview the driver facing camera to ensure that driver monitoring has good " -"visibility. (vehicle must be off)" -msgstr "" -"Sürücü izleme görünürlüğünün iyi olduğundan emin olmak için sürücüye bakan " -"kamerayı önizleyin. (araç kapalı olmalıdır)" +#: openpilot/selfdrive/ui/layouts/settings/device.py:24 +msgid "Preview the driver facing camera to ensure that driver monitoring has good visibility. (vehicle must be off)" +msgstr "Sürücü izleme görünürlüğünün iyi olduğundan emin olmak için sürücüye bakan kamerayı önizleyin. (araç kapalı olmalıdır)" -#: selfdrive/ui/widgets/pairing_dialog.py:161 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:150 #, python-format msgid "QR Code Error" msgstr "QR Kod Hatası" -#: selfdrive/ui/widgets/ssh_key.py:31 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:31 msgid "REMOVE" msgstr "KALDIR" -#: selfdrive/ui/layouts/settings/device.py:51 +#: openpilot/selfdrive/ui/layouts/settings/device.py:49 #, python-format msgid "RESET" msgstr "SIFIRLA" -#: selfdrive/ui/layouts/settings/device.py:65 +#: openpilot/selfdrive/ui/layouts/settings/device.py:63 #, python-format msgid "REVIEW" msgstr "GÖZDEN GEÇİR" -#: selfdrive/ui/layouts/settings/device.py:55 -#: selfdrive/ui/layouts/settings/device.py:175 +#: openpilot/selfdrive/ui/layouts/settings/device.py:171 +#: openpilot/selfdrive/ui/layouts/settings/device.py:53 #, python-format msgid "Reboot" msgstr "Yeniden Başlat" -#: selfdrive/ui/onroad/alert_renderer.py:66 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:66 #, python-format msgid "Reboot Device" msgstr "Cihazı Yeniden Başlat" -#: selfdrive/ui/widgets/offroad_alerts.py:112 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:112 #, python-format msgid "Reboot and Update" msgstr "Yeniden Başlat ve Güncelle" -#: selfdrive/ui/layouts/settings/toggles.py:27 -msgid "" -"Receive alerts to steer back into the lane when your vehicle drifts over a " -"detected lane line without a turn signal activated while driving over 31 mph " -"(50 km/h)." -msgstr "" -"Araç 31 mph (50 km/h) üzerindeyken sinyal verilmeden algılanan şerit " -"çizgisini aştığınızda şeride geri dönmeniz için uyarılar alın." - -#: selfdrive/ui/layouts/settings/toggles.py:76 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:76 #, python-format msgid "Record and Upload Driver Camera" msgstr "Sürücü Kamerasını Kaydet ve Yükle" -#: selfdrive/ui/layouts/settings/toggles.py:82 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:82 #, python-format msgid "Record and Upload Microphone Audio" msgstr "Mikrofon Sesini Kaydet ve Yükle" -#: selfdrive/ui/layouts/settings/toggles.py:33 -msgid "" -"Record and store microphone audio while driving. The audio will be included " -"in the dashcam video in comma connect." -msgstr "" -"Sürüş sırasında mikrofon sesini kaydedip saklayın. Ses, comma connect'teki " -"ön kamera videosuna dahil edilecektir." +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:33 +msgid "Record and store microphone audio while driving. The audio will be included in the dashcam video in comma connect." +msgstr "Sürüş sırasında mikrofon sesini kaydedip saklayın. Ses, comma connect'teki ön kamera videosuna dahil edilecektir." -#: selfdrive/ui/layouts/settings/device.py:67 +#: openpilot/selfdrive/ui/layouts/settings/device.py:65 #, python-format msgid "Regulatory" msgstr "Mevzuat" -#: selfdrive/ui/layouts/settings/toggles.py:98 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:98 #, python-format msgid "Relaxed" msgstr "Rahat" -#: selfdrive/ui/widgets/prime.py:47 +#: openpilot/selfdrive/ui/widgets/prime.py:47 #, python-format msgid "Remote access" msgstr "Uzaktan erişim" -#: selfdrive/ui/widgets/prime.py:47 +#: openpilot/selfdrive/ui/widgets/prime.py:47 #, python-format msgid "Remote snapshots" msgstr "Uzaktan anlık görüntüler" -#: selfdrive/ui/widgets/ssh_key.py:123 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:124 #, python-format msgid "Request timed out" msgstr "İstek zaman aşımına uğradı" -#: selfdrive/ui/layouts/settings/device.py:119 +#: openpilot/selfdrive/ui/layouts/settings/device.py:111 #, python-format msgid "Reset" msgstr "Sıfırla" -#: selfdrive/ui/layouts/settings/device.py:51 +#: openpilot/selfdrive/ui/layouts/settings/device.py:49 #, python-format msgid "Reset Calibration" msgstr "Kalibrasyonu Sıfırla" -#: selfdrive/ui/layouts/settings/device.py:65 +#: openpilot/selfdrive/ui/layouts/settings/device.py:63 #, python-format msgid "Review Training Guide" msgstr "Eğitim Kılavuzunu İncele" -#: selfdrive/ui/layouts/settings/device.py:27 +#: openpilot/selfdrive/ui/layouts/settings/device.py:26 msgid "Review the rules, features, and limitations of openpilot" -msgstr "" -"openpilot'un kurallarını, özelliklerini ve sınırlamalarını gözden geçirin" +msgstr "openpilot'un kurallarını, özelliklerini ve sınırlamalarını gözden geçirin" -#: selfdrive/ui/layouts/settings/software.py:61 +#: openpilot/selfdrive/ui/layouts/settings/software.py:68 #, python-format msgid "SELECT" msgstr "" -#: selfdrive/ui/layouts/settings/developer.py:53 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:53 #, python-format msgid "SSH Keys" msgstr "" -#: system/ui/widgets/network.py:310 +#: system/ui/widgets/network.py:316 #, python-format msgid "Scanning Wi-Fi networks..." msgstr "" -#: system/ui/widgets/option_dialog.py:36 +#: system/ui/widgets/option_dialog.py:37 #, python-format msgid "Select" msgstr "" -#: selfdrive/ui/layouts/settings/software.py:183 +#: openpilot/selfdrive/ui/layouts/settings/software.py:203 #, python-format msgid "Select a branch" msgstr "" -#: selfdrive/ui/layouts/settings/device.py:91 +#: openpilot/selfdrive/ui/layouts/settings/device.py:89 #, python-format msgid "Select a language" msgstr "Bir dil seçin" -#: selfdrive/ui/layouts/settings/device.py:60 +#: openpilot/selfdrive/ui/layouts/settings/device.py:58 #, python-format msgid "Serial" msgstr "Seri" -#: selfdrive/ui/widgets/offroad_alerts.py:106 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:106 #, python-format msgid "Snooze Update" msgstr "Güncellemeyi Ertele" -#: selfdrive/ui/layouts/settings/settings.py:65 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:62 msgid "Software" msgstr "Yazılım" -#: selfdrive/ui/layouts/settings/toggles.py:98 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:98 #, python-format msgid "Standard" msgstr "Standart" -#: selfdrive/ui/layouts/settings/toggles.py:22 -msgid "" -"Standard is recommended. In aggressive mode, openpilot will follow lead cars " -"closer and be more aggressive with the gas and brake. In relaxed mode " -"openpilot will stay further away from lead cars. On supported cars, you can " -"cycle through these personalities with your steering wheel distance button." -msgstr "" -"Standart önerilir. Agresif modda openpilot öndeki aracı daha yakından takip " -"eder ve gaz/fren kullanımında daha ataktır. Rahat modda openpilot öndeki " -"araçlardan daha uzak durur. Desteklenen araçlarda bu kişilikler arasında " -"direksiyon mesafe düğmesiyle geçiş yapabilirsiniz." - -#: selfdrive/ui/onroad/alert_renderer.py:59 -#: selfdrive/ui/onroad/alert_renderer.py:65 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:59 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:65 #, python-format msgid "System Unresponsive" msgstr "Sistem Yanıt Vermiyor" -#: selfdrive/ui/onroad/alert_renderer.py:58 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:58 #, python-format msgid "TAKE CONTROL IMMEDIATELY" msgstr "HEMEN KONTROLÜ DEVRALIN" -#: selfdrive/ui/layouts/sidebar.py:71 selfdrive/ui/layouts/sidebar.py:125 -#: selfdrive/ui/layouts/sidebar.py:127 selfdrive/ui/layouts/sidebar.py:129 +#: openpilot/selfdrive/ui/layouts/sidebar.py:71 +#: openpilot/selfdrive/ui/layouts/sidebar.py:125 +#: openpilot/selfdrive/ui/layouts/sidebar.py:127 +#: openpilot/selfdrive/ui/layouts/sidebar.py:129 msgid "TEMP" msgstr "TEMP" -#: selfdrive/ui/layouts/settings/software.py:61 +#: openpilot/selfdrive/ui/layouts/settings/software.py:68 #, python-format msgid "Target Branch" msgstr "" -#: system/ui/widgets/network.py:124 +#: system/ui/widgets/network.py:121 #, python-format msgid "Tethering Password" msgstr "" -#: selfdrive/ui/layouts/settings/settings.py:64 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:61 msgid "Toggles" msgstr "Seçenekler" -#: selfdrive/ui/layouts/settings/software.py:72 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:79 +#, python-format +msgid "UI Debug Mode" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/software.py:79 #, python-format msgid "UNINSTALL" msgstr "KALDIR" -#: selfdrive/ui/layouts/home.py:155 +#: openpilot/selfdrive/ui/layouts/home.py:155 #, python-format msgid "UPDATE" msgstr "GÜNCELLE" -#: selfdrive/ui/layouts/settings/software.py:72 -#: selfdrive/ui/layouts/settings/software.py:163 +#: openpilot/selfdrive/ui/layouts/settings/software.py:173 +#: openpilot/selfdrive/ui/layouts/settings/software.py:79 #, python-format msgid "Uninstall" msgstr "Kaldır" -#: selfdrive/ui/layouts/sidebar.py:117 +#: openpilot/selfdrive/ui/layouts/sidebar.py:117 msgid "Unknown" msgstr "Bilinmiyor" -#: selfdrive/ui/layouts/settings/software.py:48 +#: openpilot/selfdrive/ui/layouts/settings/software.py:55 #, python-format msgid "Updates are only downloaded while the car is off." msgstr "Güncellemeler yalnızca araç kapalıyken indirilir." -#: selfdrive/ui/widgets/prime.py:33 +#: openpilot/selfdrive/ui/widgets/prime.py:33 #, python-format msgid "Upgrade Now" msgstr "Şimdi Yükselt" -#: selfdrive/ui/layouts/settings/toggles.py:31 -msgid "" -"Upload data from the driver facing camera and help improve the driver " -"monitoring algorithm." -msgstr "" -"Sürücüye bakan kameradan veri yükleyin ve sürücü izleme algoritmasını " -"geliştirmeye yardımcı olun." +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:31 +msgid "Upload data from the driver facing camera and help improve the driver monitoring algorithm." +msgstr "Sürücüye bakan kameradan veri yükleyin ve sürücü izleme algoritmasını geliştirmeye yardımcı olun." -#: selfdrive/ui/layouts/settings/toggles.py:88 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:88 #, python-format msgid "Use Metric System" msgstr "Metrik Sistemi Kullan" -#: selfdrive/ui/layouts/settings/toggles.py:17 -msgid "" -"Use the openpilot system for adaptive cruise control and lane keep driver " -"assistance. Your attention is required at all times to use this feature." -msgstr "" -"Uyarlanabilir hız sabitleyici ve şerit koruma sürücü yardımında openpilot " -"sistemini kullanın. Bu özelliği kullanırken her zaman dikkatli olmanız " -"gerekir." - -#: selfdrive/ui/layouts/sidebar.py:72 selfdrive/ui/layouts/sidebar.py:144 +#: openpilot/selfdrive/ui/layouts/sidebar.py:72 +#: openpilot/selfdrive/ui/layouts/sidebar.py:144 msgid "VEHICLE" msgstr "ARAÇ" -#: selfdrive/ui/layouts/settings/device.py:67 +#: openpilot/selfdrive/ui/layouts/settings/device.py:65 #, python-format msgid "VIEW" msgstr "GÖRÜNTÜLE" -#: selfdrive/ui/onroad/alert_renderer.py:52 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:52 #, python-format msgid "Waiting to start" msgstr "Başlatma bekleniyor" -#: selfdrive/ui/layouts/settings/developer.py:19 -msgid "" -"Warning: This grants SSH access to all public keys in your GitHub settings. " -"Never enter a GitHub username other than your own. A comma employee will " -"NEVER ask you to add their GitHub username." -msgstr "" -"Uyarı: Bu, GitHub ayarlarınızdaki tüm açık anahtarlara SSH erişimi verir. " -"Kendi adınız dışında asla bir GitHub kullanıcı adı girmeyin. Bir comma " -"çalışanı sizden asla GitHub kullanıcı adlarını eklemenizi İSTEMEZ." - -#: selfdrive/ui/layouts/onboarding.py:111 +#: openpilot/selfdrive/ui/layouts/onboarding.py:115 #, python-format msgid "Welcome to openpilot" msgstr "openpilot'a hoş geldiniz" -#: selfdrive/ui/layouts/settings/toggles.py:20 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:20 msgid "When enabled, pressing the accelerator pedal will disengage openpilot." -msgstr "" -"Etkinleştirildiğinde, gaz pedalına basmak openpilot'u devreden çıkarır." +msgstr "Etkinleştirildiğinde, gaz pedalına basmak openpilot'u devreden çıkarır." -#: selfdrive/ui/layouts/sidebar.py:44 +#: openpilot/selfdrive/ui/layouts/sidebar.py:44 msgid "Wi-Fi" msgstr "Wi‑Fi" -#: system/ui/widgets/network.py:144 +#: system/ui/widgets/network.py:141 #, python-format msgid "Wi-Fi Network Metered" msgstr "" -#: system/ui/widgets/network.py:314 +#: system/ui/widgets/network.py:320 #, python-format msgid "Wrong password" msgstr "" -#: selfdrive/ui/layouts/onboarding.py:145 +#: openpilot/selfdrive/ui/layouts/onboarding.py:149 #, python-format msgid "You must accept the Terms and Conditions in order to use openpilot." msgstr "openpilot'u kullanmak için Şartlar ve Koşulları kabul etmelisiniz." -#: selfdrive/ui/layouts/onboarding.py:112 +#: openpilot/selfdrive/ui/layouts/onboarding.py:116 #, python-format -msgid "" -"You must accept the Terms and Conditions to use openpilot. Read the latest " -"terms at https://comma.ai/terms before continuing." -msgstr "" -"openpilot'u kullanmak için Şartlar ve Koşulları kabul etmelisiniz. Devam " -"etmeden önce en güncel şartları https://comma.ai/terms adresinde okuyun." +msgid "You must accept the Terms and Conditions to use openpilot. Read the latest terms at https://comma.ai/terms before continuing." +msgstr "openpilot'u kullanmak için Şartlar ve Koşulları kabul etmelisiniz. Devam etmeden önce en güncel şartları https://comma.ai/terms adresinde okuyun." -#: selfdrive/ui/onroad/driver_camera_dialog.py:34 +#: openpilot/selfdrive/ui/onroad/driver_camera_dialog.py:38 #, python-format msgid "camera starting" msgstr "kamera başlatılıyor" -#: selfdrive/ui/widgets/prime.py:63 +#: openpilot/selfdrive/ui/layouts/settings/software.py:19 +#, python-format +msgid "checking..." +msgstr "" + +#: openpilot/selfdrive/ui/widgets/prime.py:63 #, python-format msgid "comma prime" msgstr "comma prime" -#: system/ui/widgets/network.py:142 +#: system/ui/widgets/network.py:139 #, python-format msgid "default" msgstr "" -#: selfdrive/ui/layouts/settings/device.py:133 +#: openpilot/selfdrive/ui/layouts/settings/device.py:125 #, python-format msgid "down" msgstr "aşağı" -#: selfdrive/ui/layouts/settings/software.py:106 +#: openpilot/selfdrive/ui/layouts/settings/software.py:20 +#, python-format +msgid "downloading..." +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/software.py:116 #, python-format msgid "failed to check for update" msgstr "güncelleme kontrolü başarısız" -#: system/ui/widgets/network.py:237 system/ui/widgets/network.py:314 +#: openpilot/selfdrive/ui/layouts/settings/software.py:21 +#, python-format +msgid "finalizing update..." +msgstr "" + +#: system/ui/widgets/network.py:238 +#: system/ui/widgets/network.py:321 #, python-format msgid "for \"{}\"" msgstr "" -#: selfdrive/ui/onroad/hud_renderer.py:177 +#: openpilot/selfdrive/ui/onroad/hud_renderer.py:177 #, python-format msgid "km/h" msgstr "km/h" -#: system/ui/widgets/network.py:204 +#: system/ui/widgets/network.py:201 #, python-format msgid "leave blank for automatic configuration" msgstr "" -#: selfdrive/ui/layouts/settings/device.py:134 +#: openpilot/selfdrive/ui/layouts/settings/device.py:126 #, python-format msgid "left" msgstr "sol" -#: system/ui/widgets/network.py:142 +#: system/ui/widgets/network.py:139 #, python-format msgid "metered" msgstr "" -#: selfdrive/ui/onroad/hud_renderer.py:177 +#: openpilot/selfdrive/ui/onroad/hud_renderer.py:177 #, python-format msgid "mph" msgstr "mph" -#: selfdrive/ui/layouts/settings/software.py:20 +#: openpilot/selfdrive/ui/layouts/settings/software.py:27 #, python-format msgid "never" msgstr "asla" -#: selfdrive/ui/layouts/settings/software.py:31 +#: openpilot/selfdrive/ui/layouts/settings/software.py:38 #, python-format msgid "now" msgstr "şimdi" -#: selfdrive/ui/layouts/settings/developer.py:71 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:71 #, python-format msgid "openpilot Longitudinal Control (Alpha)" msgstr "openpilot Boylamsal Kontrol (Alfa)" -#: selfdrive/ui/onroad/alert_renderer.py:51 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:51 #, python-format msgid "openpilot Unavailable" msgstr "openpilot Kullanılamıyor" -#: selfdrive/ui/layouts/settings/toggles.py:158 -#, python-format -msgid "" -"openpilot defaults to driving in chill mode. Experimental mode enables alpha-" -"level features that aren't ready for chill mode. Experimental features are " -"listed below:

End-to-End Longitudinal Control


Let the driving " -"model control the gas and brakes. openpilot will drive as it thinks a human " -"would, including stopping for red lights and stop signs. Since the driving " -"model decides the speed to drive, the set speed will only act as an upper " -"bound. This is an alpha quality feature; mistakes should be expected." -"

New Driving Visualization


The driving visualization will " -"transition to the road-facing wide-angle camera at low speeds to better show " -"some turns. The Experimental mode logo will also be shown in the top right " -"corner." -msgstr "" -"openpilot varsayılan olarak chill modunda sürer. Deneysel mod, chill moduna " -"hazır olmayan alfa seviyesindeki özellikleri etkinleştirir. Deneysel " -"özellikler aşağıda listelenmiştir:

Uçtan Uca Boylamsal Kontrol
Sürüş modelinin gaz ve frenleri kontrol etmesine izin verin. " -"openpilot, kırmızı ışıklarda ve dur işaretlerinde durmak dahil, bir insan " -"nasıl sürer diye düşündüğüne göre sürer. Hızı sürüş modeli belirlediğinden, " -"ayarlanan hız yalnızca üst sınır olarak işlev görür. Bu bir alfa kalitesinde " -"özelliktir; hatalar beklenmelidir.

Yeni Sürüş Görselleştirmesi
Sürüş görselleştirmesi, düşük hızlarda bazı dönüşleri daha iyi " -"göstermek için yola bakan geniş açılı kameraya geçer. Deneysel mod logosu " -"sağ üst köşede de gösterilecektir." - -#: selfdrive/ui/layouts/settings/device.py:165 -#, python-format -msgid "" -"openpilot is continuously calibrating, resetting is rarely required. " -"Resetting calibration will restart openpilot if the car is powered on." -msgstr "" -" Bu ayarı değiştirmek, araç çalışıyorsa openpilot'u yeniden başlatacaktır." - -#: selfdrive/ui/layouts/settings/firehose.py:20 -msgid "" -"openpilot learns to drive by watching humans, like you, drive.\n" -"\n" -"Firehose Mode allows you to maximize your training data uploads to improve " -"openpilot's driving models. More data means bigger models, which means " -"better Experimental Mode." -msgstr "" -"openpilot, sizin gibi insanların nasıl sürdüğünü izleyerek sürmeyi öğrenir.\n" -"\n" -"Firehose Modu, openpilot'un sürüş modellerini geliştirmek için eğitim veri " -"yüklemelerinizi en üst düzeye çıkarmanıza olanak tanır. Daha fazla veri, " -"daha büyük modeller demektir; bu da daha iyi Deneysel Mod anlamına gelir." - -#: selfdrive/ui/layouts/settings/toggles.py:183 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:184 #, python-format msgid "openpilot longitudinal control may come in a future update." msgstr "openpilot boylamsal kontrolü gelecekteki bir güncellemede gelebilir." -#: selfdrive/ui/layouts/settings/device.py:26 -msgid "" -"openpilot requires the device to be mounted within 4° left or right and " -"within 5° up or 9° down." -msgstr "" -"openpilot, cihazın sağa/sola 4° ve yukarı 5° veya aşağı 9° içinde monte " -"edilmesini gerektirir." +#: openpilot/selfdrive/ui/layouts/settings/device.py:25 +msgid "openpilot requires the device to be mounted within 4° left or right and within 5° up or 9° down." +msgstr "openpilot, cihazın sağa/sola 4° ve yukarı 5° veya aşağı 9° içinde monte edilmesini gerektirir." -#: selfdrive/ui/layouts/settings/device.py:134 +#: openpilot/selfdrive/ui/layouts/settings/device.py:126 #, python-format msgid "right" msgstr "sağ" -#: system/ui/widgets/network.py:142 +#: system/ui/widgets/network.py:139 #, python-format msgid "unmetered" msgstr "" -#: selfdrive/ui/layouts/settings/device.py:133 +#: openpilot/selfdrive/ui/layouts/settings/device.py:125 #, python-format msgid "up" msgstr "yukarı" -#: selfdrive/ui/layouts/settings/software.py:117 +#: openpilot/selfdrive/ui/layouts/settings/software.py:127 #, python-format msgid "up to date, last checked never" msgstr "güncel, son kontrol asla" -#: selfdrive/ui/layouts/settings/software.py:115 +#: openpilot/selfdrive/ui/layouts/settings/software.py:125 #, python-format msgid "up to date, last checked {}" msgstr "güncel, son kontrol {}" -#: selfdrive/ui/layouts/settings/software.py:109 +#: openpilot/selfdrive/ui/layouts/settings/software.py:119 #, python-format msgid "update available" msgstr "güncelleme mevcut" -#: selfdrive/ui/layouts/home.py:169 +#: openpilot/selfdrive/ui/layouts/home.py:169 #, python-format msgid "{} ALERT" msgid_plural "{} ALERTS" msgstr[0] "{} UYARI" msgstr[1] "{} UYARILAR" -#: selfdrive/ui/layouts/settings/software.py:40 +#: openpilot/selfdrive/ui/layouts/settings/software.py:47 #, python-format msgid "{} day ago" msgid_plural "{} days ago" msgstr[0] "{} gün önce" msgstr[1] "{} gün önce" -#: selfdrive/ui/layouts/settings/software.py:37 +#: openpilot/selfdrive/ui/layouts/settings/software.py:44 #, python-format msgid "{} hour ago" msgid_plural "{} hours ago" msgstr[0] "{} saat önce" msgstr[1] "{} saat önce" -#: selfdrive/ui/layouts/settings/software.py:34 +#: openpilot/selfdrive/ui/layouts/settings/software.py:41 #, python-format msgid "{} minute ago" msgid_plural "{} minutes ago" msgstr[0] "{} dakika önce" msgstr[1] "{} dakika önce" -#: selfdrive/ui/layouts/settings/firehose.py:111 +#: openpilot/selfdrive/ui/layouts/settings/firehose.py:70 #, python-format msgid "{} segment of your driving is in the training dataset so far." msgid_plural "{} segments of your driving is in the training dataset so far." msgstr[0] "{} segment sürüşünüz eğitim veri setinde." msgstr[1] "{} segment sürüşünüz eğitim veri setinde." -#: selfdrive/ui/widgets/prime.py:62 +#: openpilot/selfdrive/ui/widgets/prime.py:62 #, python-format msgid "✓ SUBSCRIBED" msgstr "✓ ABONE" -#: selfdrive/ui/widgets/setup.py:22 +#: openpilot/selfdrive/ui/widgets/setup.py:21 #, python-format msgid "🔥 Firehose Mode 🔥" msgstr "🔥 Firehose Modu 🔥" + diff --git a/selfdrive/ui/translations/app_uk.po b/selfdrive/ui/translations/app_uk.po index cf78fb5a33..e36ceac2ce 100644 --- a/selfdrive/ui/translations/app_uk.po +++ b/selfdrive/ui/translations/app_uk.po @@ -15,1193 +15,980 @@ msgstr "" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && " -"n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" +"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" "X-Generator: Poedit 3.8\n" -#: selfdrive/ui/layouts/settings/device.py:160 +#: openpilot/selfdrive/ui/layouts/settings/device.py:152 #, python-format msgid " Steering torque response calibration is complete." msgstr " Калібрування реакції крутного моменту керма завершено." -#: selfdrive/ui/layouts/settings/device.py:158 +#: openpilot/selfdrive/ui/layouts/settings/device.py:150 #, python-format msgid " Steering torque response calibration is {}% complete." msgstr "Калібрування реакції крутного моменту керма завершено на {}%." -#: selfdrive/ui/layouts/settings/device.py:133 +#: openpilot/selfdrive/ui/layouts/settings/device.py:125 #, python-format msgid " Your device is pointed {:.1f}° {} and {:.1f}° {}." msgstr " Ваш пристрій нахилено на {:.1f}° {} та {:.1f}° {}." -#: selfdrive/ui/layouts/sidebar.py:43 +#: openpilot/selfdrive/ui/layouts/sidebar.py:43 msgid "--" msgstr "--" -#: selfdrive/ui/widgets/prime.py:47 +#: openpilot/selfdrive/ui/widgets/prime.py:47 #, python-format msgid "1 year of drive storage" msgstr "1 рік зберігання поїздок" -#: selfdrive/ui/widgets/prime.py:47 +#: openpilot/selfdrive/ui/widgets/prime.py:47 #, python-format msgid "24/7 LTE connectivity" msgstr "Підключення LTE 24/7" -#: selfdrive/ui/layouts/sidebar.py:46 +#: openpilot/selfdrive/ui/layouts/sidebar.py:46 msgid "2G" msgstr "2G" -#: selfdrive/ui/layouts/sidebar.py:47 +#: openpilot/selfdrive/ui/layouts/sidebar.py:47 msgid "3G" msgstr "3G" -#: selfdrive/ui/layouts/sidebar.py:49 +#: openpilot/selfdrive/ui/layouts/sidebar.py:49 msgid "5G" msgstr "5G" -#: selfdrive/ui/layouts/settings/developer.py:23 -msgid "" -"WARNING: openpilot longitudinal control is in alpha for this car and will " -"disable Automatic Emergency Braking (AEB).

On this car, openpilot " -"defaults to the car's built-in ACC instead of openpilot's longitudinal " -"control. Enable this to switch to openpilot longitudinal control. Enabling " -"Experimental mode is recommended when enabling openpilot longitudinal " -"control alpha. Changing this setting will restart openpilot if the car is " -"powered on." -msgstr "" -"ПОПЕРЕДЖЕННЯ: поздовжнє керування openpilot для цього автомобіля знаходиться " -"в стадії альфа-тестування і вимкне автоматичне екстрене гальмування (AEB)." - -#: selfdrive/ui/layouts/settings/device.py:148 +#: openpilot/selfdrive/ui/layouts/settings/device.py:140 #, python-format msgid "

Steering lag calibration is complete." msgstr "

Калібрування затримки кермування завершено." -#: selfdrive/ui/layouts/settings/device.py:146 +#: openpilot/selfdrive/ui/layouts/settings/device.py:138 #, python-format msgid "

Steering lag calibration is {}% complete." msgstr "

Калібрування затримки кермування завершено на {}%." -#: selfdrive/ui/layouts/settings/firehose.py:138 -#, python-format -msgid "ACTIVE" -msgstr "АКТИВНИЙ" - -#: selfdrive/ui/layouts/settings/developer.py:15 -msgid "" -"ADB (Android Debug Bridge) allows connecting to your device over USB or over " -"the network. See https://docs.comma.ai/how-to/connect-to-comma for more info." -msgstr "" -"ADB (Android Debug Bridge) дозволяє підключатися до вашого пристрою через " -"USB або мережу. Дивіться https://docs.comma.ai/how-to/connect-to-comma для " -"отримання додаткової інформації." - -#: selfdrive/ui/widgets/ssh_key.py:30 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:30 msgid "ADD" msgstr "ДОДАТИ" -#: system/ui/widgets/network.py:139 +#: system/ui/widgets/network.py:136 #, python-format msgid "APN Setting" msgstr "Налаштування APN" -#: selfdrive/ui/widgets/offroad_alerts.py:109 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:109 #, python-format msgid "Acknowledge Excessive Actuation" msgstr "Визнайте надмірне спрацьовування" -#: system/ui/widgets/network.py:74 system/ui/widgets/network.py:95 +#: system/ui/widgets/network.py:92 +#: system/ui/widgets/network.py:74 #, python-format msgid "Advanced" msgstr "Розширені" -#: selfdrive/ui/layouts/settings/toggles.py:98 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:98 #, python-format msgid "Aggressive" msgstr "Агресивн." -#: selfdrive/ui/layouts/onboarding.py:116 +#: openpilot/selfdrive/ui/layouts/onboarding.py:120 #, python-format msgid "Agree" msgstr "Погодитися" -#: selfdrive/ui/layouts/settings/toggles.py:70 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:70 #, python-format msgid "Always-On Driver Monitoring" msgstr "Постійний моніторинг водія" -#: selfdrive/ui/layouts/settings/toggles.py:186 -#, python-format -msgid "" -"An alpha version of openpilot longitudinal control can be tested, along with " -"Experimental mode, on non-release branches." -msgstr "" -"Альфа-версію поздовжнього керування openpilot можна протестувати разом з " -"експериментальним режимом на нерелізних гілках." - -#: selfdrive/ui/layouts/settings/device.py:187 +#: openpilot/selfdrive/ui/layouts/settings/device.py:183 #, python-format msgid "Are you sure you want to power off?" msgstr "Ви впевнені, що хочете вимкнути?" -#: selfdrive/ui/layouts/settings/device.py:175 +#: openpilot/selfdrive/ui/layouts/settings/device.py:171 #, python-format msgid "Are you sure you want to reboot?" msgstr "Ви впевнені, що хочете перезавантажити?" -#: selfdrive/ui/layouts/settings/device.py:119 +#: openpilot/selfdrive/ui/layouts/settings/device.py:111 #, python-format msgid "Are you sure you want to reset calibration?" msgstr "Ви впевнені, що хочете скинути калібрування?" -#: selfdrive/ui/layouts/settings/software.py:171 +#: openpilot/selfdrive/ui/layouts/settings/software.py:173 #, python-format msgid "Are you sure you want to uninstall?" msgstr "Ви впевнені, що хочете видалити?" -#: system/ui/widgets/network.py:99 -#: selfdrive/ui/layouts/onboarding.py:147 +#: system/ui/widgets/network.py:96 +#: openpilot/selfdrive/ui/layouts/onboarding.py:151 #, python-format msgid "Back" msgstr "Назад" -#: selfdrive/ui/widgets/prime.py:38 +#: openpilot/selfdrive/ui/widgets/prime.py:38 #, python-format msgid "Become a comma prime member at connect.comma.ai" msgstr "Станьте членом comma prime на connect.comma.ai" -#: selfdrive/ui/widgets/pairing_dialog.py:119 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:119 #, python-format msgid "Bookmark connect.comma.ai to your home screen to use it like an app" -msgstr "" -"Додайте connect.comma.ai до головного екрану, щоб використовувати його як " -"додаток." +msgstr "Додайте connect.comma.ai до головного екрану, щоб використовувати його як додаток." -#: selfdrive/ui/layouts/settings/device.py:68 +#: openpilot/selfdrive/ui/layouts/settings/device.py:66 #, python-format msgid "CHANGE" msgstr "ЗМІНИТИ" -#: selfdrive/ui/layouts/settings/software.py:50 -#: selfdrive/ui/layouts/settings/software.py:115 -#: selfdrive/ui/layouts/settings/software.py:126 -#: selfdrive/ui/layouts/settings/software.py:155 +#: openpilot/selfdrive/ui/layouts/settings/software.py:157 +#: openpilot/selfdrive/ui/layouts/settings/software.py:57 +#: openpilot/selfdrive/ui/layouts/settings/software.py:117 +#: openpilot/selfdrive/ui/layouts/settings/software.py:128 #, python-format msgid "CHECK" msgstr "ПЕРЕВІРИТИ" -#: selfdrive/ui/widgets/exp_mode_button.py:50 +#: openpilot/selfdrive/ui/widgets/exp_mode_button.py:51 #, python-format msgid "CHILL MODE ON" msgstr "СПОКІЙНИЙ РЕЖИМ" -#: system/ui/widgets/network.py:155 -#: selfdrive/ui/layouts/sidebar.py:73 -#: selfdrive/ui/layouts/sidebar.py:134 -#: selfdrive/ui/layouts/sidebar.py:136 -#: selfdrive/ui/layouts/sidebar.py:138 +#: system/ui/widgets/network.py:152 +#: openpilot/selfdrive/ui/layouts/sidebar.py:73 +#: openpilot/selfdrive/ui/layouts/sidebar.py:134 +#: openpilot/selfdrive/ui/layouts/sidebar.py:136 +#: openpilot/selfdrive/ui/layouts/sidebar.py:138 #, python-format msgid "CONNECT" msgstr "CONNECT" -#: system/ui/widgets/network.py:369 +#: system/ui/widgets/network.py:376 #, python-format msgid "CONNECTING..." msgstr "ПІДКЛЮЧА..." -#: system/ui/widgets/confirm_dialog.py:23 system/ui/widgets/option_dialog.py:35 -#: system/ui/widgets/network.py:318 system/ui/widgets/keyboard.py:81 +#: system/ui/widgets/network.py:326 +#: system/ui/widgets/confirm_dialog.py:24 +#: system/ui/widgets/option_dialog.py:36 +#: system/ui/widgets/keyboard.py:83 #, python-format msgid "Cancel" msgstr "Скасувати" -#: system/ui/widgets/network.py:134 +#: system/ui/widgets/network.py:131 #, python-format msgid "Cellular Metered" msgstr "Лімітне стільникове з'єднання" -#: selfdrive/ui/layouts/settings/device.py:68 +#: openpilot/selfdrive/ui/layouts/settings/device.py:66 #, python-format msgid "Change Language" msgstr "Змінити мову" -#: selfdrive/ui/layouts/settings/toggles.py:125 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:125 #, python-format msgid "Changing this setting will restart openpilot if the car is powered on." -msgstr "" -"Зміна цього параметра призведе до перезапуску openpilot, якщо автомобіль " -"увімкнено." +msgstr "Зміна цього параметра призведе до перезапуску openpilot, якщо автомобіль увімкнено." -#: selfdrive/ui/widgets/pairing_dialog.py:118 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:118 #, python-format msgid "Click \"add new device\" and scan the QR code on the right" msgstr "Натисніть «додати новий пристрій» і відскануйте QR-код праворуч." -#: selfdrive/ui/widgets/offroad_alerts.py:104 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:104 #, python-format msgid "Close" msgstr "Закрити" -#: selfdrive/ui/layouts/settings/software.py:49 +#: openpilot/selfdrive/ui/layouts/settings/software.py:56 #, python-format msgid "Current Version" msgstr "Поточна версія" -#: selfdrive/ui/layouts/settings/software.py:118 +#: openpilot/selfdrive/ui/layouts/settings/software.py:120 #, python-format msgid "DOWNLOAD" msgstr "ВАНТАЖ" -#: selfdrive/ui/layouts/onboarding.py:115 +#: openpilot/selfdrive/ui/layouts/onboarding.py:119 #, python-format msgid "Decline" msgstr "Відхилити" -#: selfdrive/ui/layouts/onboarding.py:148 +#: openpilot/selfdrive/ui/layouts/onboarding.py:152 #, python-format msgid "Decline, uninstall openpilot" msgstr "Відхилити, видалити openpilot" -#: selfdrive/ui/layouts/settings/settings.py:64 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:64 msgid "Developer" msgstr "Розробник" -#: selfdrive/ui/layouts/settings/settings.py:59 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:59 msgid "Device" msgstr "Пристрій" -#: selfdrive/ui/layouts/settings/toggles.py:58 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:58 #, python-format msgid "Disengage on Accelerator Pedal" msgstr "Вимкнення при натисканні на педаль газу" -#: selfdrive/ui/layouts/settings/device.py:184 +#: openpilot/selfdrive/ui/layouts/settings/device.py:176 #, python-format msgid "Disengage to Power Off" msgstr "Вимкніть openpilot, щоб вимкнути пристрій" -#: selfdrive/ui/layouts/settings/device.py:172 +#: openpilot/selfdrive/ui/layouts/settings/device.py:164 #, python-format msgid "Disengage to Reboot" msgstr "Вимкніть openpilot, щоб перезавантажити" -#: selfdrive/ui/layouts/settings/device.py:103 +#: openpilot/selfdrive/ui/layouts/settings/device.py:95 #, python-format msgid "Disengage to Reset Calibration" msgstr "Деактивуйте для скидання калібрування" -#: selfdrive/ui/layouts/settings/toggles.py:32 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:32 msgid "Display speed in km/h instead of mph." msgstr "Відображати швидкість у км/год замість миль/год." -#: selfdrive/ui/layouts/settings/device.py:59 +#: openpilot/selfdrive/ui/layouts/settings/device.py:57 #, python-format msgid "Dongle ID" msgstr "ID ключа" -#: selfdrive/ui/layouts/settings/software.py:50 +#: openpilot/selfdrive/ui/layouts/settings/software.py:57 #, python-format msgid "Download" msgstr "Завантажити" -#: selfdrive/ui/layouts/settings/device.py:62 +#: openpilot/selfdrive/ui/layouts/settings/device.py:60 #, python-format msgid "Driver Camera" msgstr "Камера водія" -#: selfdrive/ui/layouts/settings/toggles.py:96 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:96 #, python-format msgid "Driving Personality" msgstr "Стиль водіння" -#: system/ui/widgets/network.py:123 system/ui/widgets/network.py:139 +#: system/ui/widgets/network.py:120 +#: system/ui/widgets/network.py:136 #, python-format msgid "EDIT" msgstr "РЕДАГ." -#: selfdrive/ui/layouts/sidebar.py:138 +#: openpilot/selfdrive/ui/layouts/sidebar.py:138 msgid "ERROR" msgstr "ПОМИЛКА" -#: selfdrive/ui/layouts/sidebar.py:45 +#: openpilot/selfdrive/ui/layouts/sidebar.py:45 msgid "ETH" msgstr "ETH" -#: selfdrive/ui/widgets/exp_mode_button.py:50 +#: openpilot/selfdrive/ui/widgets/exp_mode_button.py:51 #, python-format msgid "EXPERIMENTAL MODE ON" msgstr "ЕКСПЕРИМЕНТ. РЕЖИМ" -#: selfdrive/ui/layouts/settings/toggles.py:228 -#: selfdrive/ui/layouts/settings/developer.py:166 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:229 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:180 #, python-format msgid "Enable" msgstr "Увімкнути" -#: selfdrive/ui/layouts/settings/developer.py:39 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:39 #, python-format msgid "Enable ADB" msgstr "Увімкнути ADB" -#: selfdrive/ui/layouts/settings/toggles.py:64 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:64 #, python-format msgid "Enable Lane Departure Warnings" msgstr "Увімкнути попередження про виїзд зі смуги" -#: system/ui/widgets/network.py:129 +#: system/ui/widgets/network.py:126 #, python-format msgid "Enable Roaming" msgstr "Увімкнути роумінг" -#: selfdrive/ui/layouts/settings/developer.py:48 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:48 #, python-format msgid "Enable SSH" msgstr "Увімкнути SSH" -#: system/ui/widgets/network.py:120 +#: system/ui/widgets/network.py:117 #, python-format msgid "Enable Tethering" msgstr "Увімкнути точку доступу" -#: selfdrive/ui/layouts/settings/toggles.py:30 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:30 msgid "Enable driver monitoring even when openpilot is not engaged." msgstr "Увімкнути моніторинг водія, навіть коли openpilot не ввімкнено." -#: selfdrive/ui/layouts/settings/toggles.py:46 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:46 #, python-format msgid "Enable openpilot" msgstr "Увімкнути openpilot" -#: selfdrive/ui/layouts/settings/toggles.py:189 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:190 #, python-format -msgid "" -"Enable the openpilot longitudinal control (alpha) toggle to allow " -"Experimental mode." -msgstr "" -"Увімкніть перемикач поздовжнього керування openpilot (альфа), щоб увімкнути " -"експериментальний режим." +msgid "Enable the openpilot longitudinal control (alpha) toggle to allow Experimental mode." +msgstr "Увімкніть перемикач поздовжнього керування openpilot (альфа), щоб увімкнути експериментальний режим." -#: system/ui/widgets/network.py:204 +#: system/ui/widgets/network.py:201 #, python-format msgid "Enter APN" msgstr "Введіть APN" -#: system/ui/widgets/network.py:241 +#: system/ui/widgets/network.py:243 #, python-format msgid "Enter SSID" msgstr "Введіть SSID" -#: system/ui/widgets/network.py:254 +#: system/ui/widgets/network.py:257 #, python-format msgid "Enter new tethering password" msgstr "Введіть новий пароль для модему" -#: system/ui/widgets/network.py:237 system/ui/widgets/network.py:314 +#: system/ui/widgets/network.py:238 +#: system/ui/widgets/network.py:320 #, python-format msgid "Enter password" msgstr "Введіть пароль" -#: selfdrive/ui/widgets/ssh_key.py:89 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:89 #, python-format msgid "Enter your GitHub username" msgstr "Введіть ваш логін GitHub" -#: system/ui/widgets/list_view.py:123 system/ui/widgets/list_view.py:160 +#: system/ui/widgets/list_view.py:123 +#: system/ui/widgets/list_view.py:160 #, python-format msgid "Error" msgstr "Помилка" -#: selfdrive/ui/layouts/settings/toggles.py:52 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:52 #, python-format msgid "Experimental Mode" msgstr "Експериментальний режим" -#: selfdrive/ui/layouts/settings/toggles.py:181 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:182 #, python-format -msgid "" -"Experimental mode is currently unavailable on this car since the car's stock " -"ACC is used for longitudinal control." -msgstr "" -"Експериментальний режим наразі недоступний для цього автомобіля, оскільки " -"для поздовжнього керування використовується штатний адаптивний круїз-" -"контроль (ACC)." +msgid "Experimental mode is currently unavailable on this car since the car's stock ACC is used for longitudinal control." +msgstr "Експериментальний режим наразі недоступний для цього автомобіля, оскільки для поздовжнього керування використовується штатний адаптивний круїз-контроль (ACC)." -#: system/ui/widgets/network.py:373 +#: system/ui/widgets/network.py:380 #, python-format msgid "FORGETTING..." msgstr "ЗАБУВАЮ..." -#: selfdrive/ui/widgets/setup.py:44 +#: openpilot/selfdrive/ui/widgets/setup.py:43 #, python-format msgid "Finish Setup" msgstr "Завершити налаштування" -#: selfdrive/ui/layouts/settings/settings.py:63 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:63 msgid "Firehose" msgstr "Злива" -#: selfdrive/ui/layouts/settings/firehose.py:18 +#: openpilot/selfdrive/ui/layouts/settings/firehose.py:10 msgid "Firehose Mode" msgstr "Режим зливи" -#: selfdrive/ui/layouts/settings/firehose.py:25 -msgid "" -"For maximum effectiveness, bring your device inside and connect to a good " -"USB-C adapter and Wi-Fi weekly.\n" -"\n" -"Firehose Mode can also work while you're driving if connected to a hotspot " -"or unlimited SIM card.\n" -"\n" -"\n" -"Frequently Asked Questions\n" -"\n" -"Does it matter how or where I drive? Nope, just drive as you normally " -"would.\n" -"\n" -"Do all of my segments get pulled in Firehose Mode? No, we selectively pull a " -"subset of your segments.\n" -"\n" -"What's a good USB-C adapter? Any fast phone or laptop charger should be " -"fine.\n" -"\n" -"Does it matter which software I run? Yes, only upstream openpilot (and " -"particular forks) are able to be used for training." -msgstr "" -"Для максимальної ефективності щотижня заносьте пристрій у приміщення та " -"підключайте його до якісного адаптера USB-C і Wi-Fi.\n" -"\n" -"Режим Зливи також може працювати під час руху, якщо пристрій підключено до " -"точки доступу або SIM-картки з необмеженим трафіком.\n" -"\n" -"\n" -"Поширені запитання\n" -"\n" -"Чи має значення, як і де я їду? Ні, просто їдьте, як зазвичай.\n" -"\n" -"Чи всі мої сегменти потрапляють у режим Зливи? Ні, ми вибірково вибираємо " -"підмножину ваших сегментів.\n" -"\n" -"Що таке хороший адаптер USB-C? Будь-який швидкий зарядний пристрій для " -"телефону або ноутбука підійде.\n" -"\n" -"Чи має значення, яке програмне забезпечення я використовую? Так, для " -"навчання можна використовувати тільки upstream openpilot (і певні його " -"форки)." - -#: system/ui/widgets/network.py:318 system/ui/widgets/network.py:451 +#: system/ui/widgets/network.py:458 +#: system/ui/widgets/network.py:326 #, python-format msgid "Forget" msgstr "Заб-и" -#: system/ui/widgets/network.py:319 +#: system/ui/widgets/network.py:327 #, python-format msgid "Forget Wi-Fi Network \"{}\"?" msgstr "Забути мережу Wi-Fi \"{}\"?" -#: selfdrive/ui/layouts/sidebar.py:71 -#: selfdrive/ui/layouts/sidebar.py:125 +#: openpilot/selfdrive/ui/layouts/sidebar.py:71 +#: openpilot/selfdrive/ui/layouts/sidebar.py:125 msgid "GOOD" msgstr "ДОБРА" -#: selfdrive/ui/widgets/pairing_dialog.py:117 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:117 #, python-format msgid "Go to https://connect.comma.ai on your phone" msgstr "Перейдіть на сайт https://connect.comma.ai на своєму телефоні." -#: selfdrive/ui/layouts/sidebar.py:129 +#: openpilot/selfdrive/ui/layouts/sidebar.py:129 msgid "HIGH" msgstr "ВИСОКА" -#: system/ui/widgets/network.py:155 +#: system/ui/widgets/network.py:152 #, python-format msgid "Hidden Network" msgstr "Прихована мережа" -#: selfdrive/ui/layouts/settings/firehose.py:140 -#, python-format -msgid "INACTIVE: connect to an unmetered network" -msgstr "НЕАКТИВНО: підключення до мережі без ліміту трафіку" - -#: selfdrive/ui/layouts/settings/software.py:53 -#: selfdrive/ui/layouts/settings/software.py:144 +#: openpilot/selfdrive/ui/layouts/settings/software.py:60 +#: openpilot/selfdrive/ui/layouts/settings/software.py:146 #, python-format msgid "INSTALL" msgstr "ВСТАНОВ." -#: system/ui/widgets/network.py:150 +#: system/ui/widgets/network.py:147 #, python-format msgid "IP Address" msgstr "IP-адреса" -#: selfdrive/ui/layouts/settings/software.py:53 +#: openpilot/selfdrive/ui/layouts/settings/software.py:60 #, python-format msgid "Install Update" msgstr "Встановити оновлення" -#: selfdrive/ui/layouts/settings/developer.py:56 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:56 #, python-format msgid "Joystick Debug Mode" msgstr "Режим зневадження джойстика" -#: selfdrive/ui/widgets/ssh_key.py:29 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:29 msgid "LOADING" msgstr "ЗАВАНТАЖЕННЯ" -#: selfdrive/ui/layouts/sidebar.py:48 +#: openpilot/selfdrive/ui/layouts/sidebar.py:48 msgid "LTE" msgstr "LTE" -#: selfdrive/ui/layouts/settings/developer.py:64 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:64 #, python-format msgid "Longitudinal Maneuver Mode" msgstr "Режим поздовжнього маневрування" -#: selfdrive/ui/onroad/hud_renderer.py:148 +#: openpilot/selfdrive/ui/onroad/hud_renderer.py:148 #, python-format msgid "MAX" msgstr "МАКС" -#: selfdrive/ui/widgets/setup.py:75 +#: openpilot/selfdrive/ui/widgets/setup.py:74 #, python-format -msgid "" -"Maximize your training data uploads to improve openpilot's driving models." -msgstr "" -"Максимізуйте завантаження навчальних даних, щоб поліпшити моделі openpilot." +msgid "Maximize your training data uploads to improve openpilot's driving models." +msgstr "Максимізуйте завантаження навчальних даних, щоб поліпшити моделі openpilot." -#: selfdrive/ui/layouts/settings/device.py:59 -#: selfdrive/ui/layouts/settings/device.py:60 +#: openpilot/selfdrive/ui/layouts/settings/device.py:57 +#: openpilot/selfdrive/ui/layouts/settings/device.py:58 #, python-format msgid "N/A" msgstr "Н/Д" -#: selfdrive/ui/layouts/sidebar.py:142 +#: openpilot/selfdrive/ui/layouts/sidebar.py:142 msgid "NO" msgstr "НЕМАЄ" -#: selfdrive/ui/layouts/settings/settings.py:60 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:60 msgid "Network" msgstr "Мережа" -#: selfdrive/ui/widgets/ssh_key.py:114 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:115 #, python-format msgid "No SSH keys found" msgstr "Не знайдено ключів SSH" -#: selfdrive/ui/widgets/ssh_key.py:126 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:127 #, python-format msgid "No SSH keys found for user '{}'" msgstr "Користувач '{}' не має ключів на GitHub" -#: selfdrive/ui/widgets/offroad_alerts.py:320 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:321 #, python-format msgid "No release notes available." msgstr "Інформація про випуск відсутня." -#: selfdrive/ui/layouts/sidebar.py:73 -#: selfdrive/ui/layouts/sidebar.py:134 +#: openpilot/selfdrive/ui/layouts/sidebar.py:73 +#: openpilot/selfdrive/ui/layouts/sidebar.py:134 msgid "OFFLINE" msgstr "ОФЛАЙН" -#: system/ui/widgets/confirm_dialog.py:93 system/ui/widgets/html_render.py:263 -#: selfdrive/ui/layouts/sidebar.py:127 +#: system/ui/widgets/confirm_dialog.py:93 +#: system/ui/widgets/html_render.py:263 +#: openpilot/selfdrive/ui/layouts/sidebar.py:127 #, python-format msgid "OK" msgstr "OK" -#: selfdrive/ui/layouts/sidebar.py:72 -#: selfdrive/ui/layouts/sidebar.py:136 -#: selfdrive/ui/layouts/sidebar.py:144 +#: openpilot/selfdrive/ui/layouts/sidebar.py:72 +#: openpilot/selfdrive/ui/layouts/sidebar.py:144 +#: openpilot/selfdrive/ui/layouts/sidebar.py:136 msgid "ONLINE" msgstr "ОНЛАЙН" -#: selfdrive/ui/widgets/setup.py:20 +#: openpilot/selfdrive/ui/widgets/setup.py:19 #, python-format msgid "Open" msgstr "ВІДКРИТИ" -#: selfdrive/ui/layouts/settings/device.py:48 +#: openpilot/selfdrive/ui/layouts/settings/device.py:45 #, python-format msgid "PAIR" msgstr "ПІДКЛЮЧИТИ" -#: selfdrive/ui/layouts/sidebar.py:142 +#: openpilot/selfdrive/ui/layouts/sidebar.py:142 msgid "PANDA" msgstr "PANDA" -#: selfdrive/ui/layouts/settings/device.py:62 +#: openpilot/selfdrive/ui/layouts/settings/device.py:60 #, python-format msgid "PREVIEW" msgstr "ПОКАЖИ" -#: selfdrive/ui/widgets/prime.py:44 +#: openpilot/selfdrive/ui/widgets/prime.py:44 #, python-format msgid "PRIME FEATURES:" msgstr "XАРАКТЕРИСТИКИ PRIME:" -#: selfdrive/ui/layouts/settings/device.py:48 +#: openpilot/selfdrive/ui/layouts/settings/device.py:45 #, python-format msgid "Pair Device" msgstr "Підключити пристрій" -#: selfdrive/ui/widgets/setup.py:19 +#: openpilot/selfdrive/ui/widgets/setup.py:18 #, python-format msgid "Pair device" msgstr "Підключити пристрій" -#: selfdrive/ui/widgets/pairing_dialog.py:92 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:92 #, python-format msgid "Pair your device to your comma account" msgstr "Підключіть свій пристрій до обліковки comma connect" -#: selfdrive/ui/widgets/setup.py:48 -#: selfdrive/ui/layouts/settings/device.py:24 +#: openpilot/selfdrive/ui/widgets/setup.py:47 +#: openpilot/selfdrive/ui/layouts/settings/device.py:23 #, python-format -msgid "" -"Pair your device with comma connect (connect.comma.ai) and claim your comma " -"prime offer." -msgstr "" -"Підключіть свій пристрій до comma connect (connect.comma.ai) і отримайте " -"свою пропозицію comma prime." +msgid "Pair your device with comma connect (connect.comma.ai) and claim your comma prime offer." +msgstr "Підключіть свій пристрій до comma connect (connect.comma.ai) і отримайте свою пропозицію comma prime." -#: selfdrive/ui/widgets/setup.py:91 +#: openpilot/selfdrive/ui/widgets/setup.py:91 #, python-format msgid "Please connect to Wi-Fi to complete initial pairing" msgstr "Будь ласка, підключіться до Wi-Fi, щоб завершити початкове сполучення." -#: selfdrive/ui/layouts/settings/device.py:55 -#: selfdrive/ui/layouts/settings/device.py:187 +#: openpilot/selfdrive/ui/layouts/settings/device.py:183 +#: openpilot/selfdrive/ui/layouts/settings/device.py:53 #, python-format msgid "Power Off" msgstr "Вимкнути" -#: system/ui/widgets/network.py:144 +#: system/ui/widgets/network.py:141 #, python-format msgid "Prevent large data uploads when on a metered Wi-Fi connection" -msgstr "" -"Запобігайте завантаженню великих обсягів даних під час використання Wi-Fi-" -"з'єднання з обмеженим трафіком" +msgstr "Запобігайте завантаженню великих обсягів даних під час використання Wi-Fi-з'єднання з обмеженим трафіком" -#: system/ui/widgets/network.py:135 +#: system/ui/widgets/network.py:132 #, python-format msgid "Prevent large data uploads when on a metered cellular connection" -msgstr "" -"Запобігати великим завантаженням даних під час лімітного стільникового " -"з'єднання" +msgstr "Запобігати великим завантаженням даних під час лімітного стільникового з'єднання" -#: selfdrive/ui/layouts/settings/device.py:25 -msgid "" -"Preview the driver facing camera to ensure that driver monitoring has good " -"visibility. (vehicle must be off)" -msgstr "" -"Попередньо перегляньте камеру, спрямовану на водія, щоб переконатися, що " -"система моніторингу водія має добру видимість. (автомобіль повинен бути " -"вимкнений)" +#: openpilot/selfdrive/ui/layouts/settings/device.py:24 +msgid "Preview the driver facing camera to ensure that driver monitoring has good visibility. (vehicle must be off)" +msgstr "Попередньо перегляньте камеру, спрямовану на водія, щоб переконатися, що система моніторингу водія має добру видимість. (автомобіль повинен бути вимкнений)" -#: selfdrive/ui/widgets/pairing_dialog.py:150 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:150 #, python-format msgid "QR Code Error" msgstr "Помилка QR-коду" -#: selfdrive/ui/widgets/ssh_key.py:31 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:31 msgid "REMOVE" msgstr "ВИДАЛИТИ" -#: selfdrive/ui/layouts/settings/device.py:51 +#: openpilot/selfdrive/ui/layouts/settings/device.py:49 #, python-format msgid "RESET" msgstr "Скинути" -#: selfdrive/ui/layouts/settings/device.py:65 +#: openpilot/selfdrive/ui/layouts/settings/device.py:63 #, python-format msgid "REVIEW" msgstr "ДИВИТИСЬ" -#: selfdrive/ui/layouts/settings/device.py:55 -#: selfdrive/ui/layouts/settings/device.py:175 +#: openpilot/selfdrive/ui/layouts/settings/device.py:171 +#: openpilot/selfdrive/ui/layouts/settings/device.py:53 #, python-format msgid "Reboot" msgstr "Перезавантажити" -#: selfdrive/ui/onroad/alert_renderer.py:66 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:66 #, python-format msgid "Reboot Device" msgstr "Перезавантажте пристрій" -#: selfdrive/ui/widgets/offroad_alerts.py:112 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:112 #, python-format msgid "Reboot and Update" msgstr "Перезавантажити та оновити" -#: selfdrive/ui/layouts/settings/toggles.py:27 -msgid "" -"Receive alerts to steer back into the lane when your vehicle drifts over a " -"detected lane line without a turn signal activated while driving over 31 mph " -"(50 km/h)." -msgstr "" -"Отримувати попередження про необхідність повернутися в смугу, коли ваш " -"автомобіль перетинає виявлену лінію розмітки без увімкненого сигналу " -"повороту під час руху зі швидкістю понад 31 миль/год (50 км/год)." - -#: selfdrive/ui/layouts/settings/toggles.py:76 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:76 #, python-format msgid "Record and Upload Driver Camera" msgstr "Писати та вантажити відео з камери водія" -#: selfdrive/ui/layouts/settings/toggles.py:82 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:82 #, python-format msgid "Record and Upload Microphone Audio" msgstr "Запис та завантаження аудіо з мікрофона" -#: selfdrive/ui/layouts/settings/toggles.py:33 -msgid "" -"Record and store microphone audio while driving. The audio will be included " -"in the dashcam video in comma connect." -msgstr "" -"Записуйте та зберігайте аудіо з мікрофона під час руху. Аудіо буде включено " -"до відео з відеореєстратора в comma connect." +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:33 +msgid "Record and store microphone audio while driving. The audio will be included in the dashcam video in comma connect." +msgstr "Записуйте та зберігайте аудіо з мікрофона під час руху. Аудіо буде включено до відео з відеореєстратора в comma connect." -#: selfdrive/ui/layouts/settings/device.py:67 +#: openpilot/selfdrive/ui/layouts/settings/device.py:65 #, python-format msgid "Regulatory" msgstr "Нормативні документи" -#: selfdrive/ui/layouts/settings/toggles.py:98 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:98 #, python-format msgid "Relaxed" msgstr "Спокійний" -#: selfdrive/ui/widgets/prime.py:47 +#: openpilot/selfdrive/ui/widgets/prime.py:47 #, python-format msgid "Remote access" msgstr "Віддалений доступ" -#: selfdrive/ui/widgets/prime.py:47 +#: openpilot/selfdrive/ui/widgets/prime.py:47 #, python-format msgid "Remote snapshots" msgstr "Віддалені знімки" -#: selfdrive/ui/widgets/ssh_key.py:123 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:124 #, python-format msgid "Request timed out" msgstr "Час запиту вичерпано" -#: selfdrive/ui/layouts/settings/device.py:119 +#: openpilot/selfdrive/ui/layouts/settings/device.py:111 #, python-format msgid "Reset" msgstr "Скинути" -#: selfdrive/ui/layouts/settings/device.py:51 +#: openpilot/selfdrive/ui/layouts/settings/device.py:49 #, python-format msgid "Reset Calibration" msgstr "Скинути калібрування" -#: selfdrive/ui/layouts/settings/device.py:65 +#: openpilot/selfdrive/ui/layouts/settings/device.py:63 #, python-format msgid "Review Training Guide" msgstr "Переглянути посібник з навчання" -#: selfdrive/ui/layouts/settings/device.py:27 +#: openpilot/selfdrive/ui/layouts/settings/device.py:26 msgid "Review the rules, features, and limitations of openpilot" msgstr "Перегляньте правила, функції та обмеження openpilot" -#: selfdrive/ui/layouts/settings/software.py:61 +#: openpilot/selfdrive/ui/layouts/settings/software.py:68 #, python-format msgid "SELECT" msgstr "ВИБРАТИ" -#: selfdrive/ui/layouts/settings/developer.py:53 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:53 #, python-format msgid "SSH Keys" msgstr "SSH ключі" -#: system/ui/widgets/network.py:310 +#: system/ui/widgets/network.py:316 #, python-format msgid "Scanning Wi-Fi networks..." msgstr "Пошук мереж..." -#: system/ui/widgets/option_dialog.py:36 +#: system/ui/widgets/option_dialog.py:37 #, python-format msgid "Select" msgstr "Вибрати" -#: selfdrive/ui/layouts/settings/software.py:191 +#: openpilot/selfdrive/ui/layouts/settings/software.py:203 #, python-format msgid "Select a branch" msgstr "Виберіть гілку" -#: selfdrive/ui/layouts/settings/device.py:91 +#: openpilot/selfdrive/ui/layouts/settings/device.py:89 #, python-format msgid "Select a language" msgstr "Виберіть мову" -#: selfdrive/ui/layouts/settings/device.py:60 +#: openpilot/selfdrive/ui/layouts/settings/device.py:58 #, python-format msgid "Serial" msgstr "Серійний номер" -#: selfdrive/ui/widgets/offroad_alerts.py:106 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:106 #, python-format msgid "Snooze Update" msgstr "Відкласти оновлення" -#: selfdrive/ui/layouts/settings/settings.py:62 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:62 msgid "Software" msgstr "Програма" -#: selfdrive/ui/layouts/settings/toggles.py:98 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:98 #, python-format msgid "Standard" msgstr "Стандарт" -#: selfdrive/ui/layouts/settings/toggles.py:22 -msgid "" -"Standard is recommended. In aggressive mode, openpilot will follow lead cars " -"closer and be more aggressive with the gas and brake. In relaxed mode " -"openpilot will stay further away from lead cars. On supported cars, you can " -"cycle through these personalities with your steering wheel distance button." -msgstr "" -"Рекомендується стандартний режим. В агресивному режимі openpilot буде " -"триматися ближче до автомобілів попереду і більш агресивно використовувати " -"газ і гальма. У спокійному режимі openpilot буде триматися на більшій " -"відстані від автомобілів попереду. На підтримуваних автомобілях ви можете " -"перемикатися між цими режимами за допомогою кнопки дистанції на кермі." - -#: selfdrive/ui/onroad/alert_renderer.py:59 -#: selfdrive/ui/onroad/alert_renderer.py:65 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:59 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:65 #, python-format msgid "System Unresponsive" msgstr "Система не реагує" -#: selfdrive/ui/onroad/alert_renderer.py:58 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:58 #, python-format msgid "TAKE CONTROL IMMEDIATELY" msgstr "КЕРМУЙТЕ НЕГАЙНО" -#: selfdrive/ui/layouts/sidebar.py:71 -#: selfdrive/ui/layouts/sidebar.py:125 -#: selfdrive/ui/layouts/sidebar.py:127 -#: selfdrive/ui/layouts/sidebar.py:129 +#: openpilot/selfdrive/ui/layouts/sidebar.py:71 +#: openpilot/selfdrive/ui/layouts/sidebar.py:125 +#: openpilot/selfdrive/ui/layouts/sidebar.py:127 +#: openpilot/selfdrive/ui/layouts/sidebar.py:129 msgid "TEMP" msgstr "ТЕМП" -#: selfdrive/ui/layouts/settings/software.py:61 +#: openpilot/selfdrive/ui/layouts/settings/software.py:68 #, python-format msgid "Target Branch" msgstr "Цільова гілка" -#: system/ui/widgets/network.py:124 +#: system/ui/widgets/network.py:121 #, python-format msgid "Tethering Password" msgstr "Пароль для точки доступу" -#: selfdrive/ui/layouts/settings/settings.py:61 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:61 msgid "Toggles" msgstr "Перемикачі" -#: selfdrive/ui/layouts/settings/software.py:72 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:79 +#, python-format +msgid "UI Debug Mode" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/software.py:79 #, python-format msgid "UNINSTALL" msgstr "ВИДАЛИТИ" -#: selfdrive/ui/layouts/home.py:155 +#: openpilot/selfdrive/ui/layouts/home.py:155 #, python-format msgid "UPDATE" msgstr "ОНОВИТИ" -#: selfdrive/ui/layouts/settings/software.py:72 -#: selfdrive/ui/layouts/settings/software.py:171 +#: openpilot/selfdrive/ui/layouts/settings/software.py:173 +#: openpilot/selfdrive/ui/layouts/settings/software.py:79 #, python-format msgid "Uninstall" msgstr "Видалити" -#: selfdrive/ui/layouts/sidebar.py:117 +#: openpilot/selfdrive/ui/layouts/sidebar.py:117 msgid "Unknown" msgstr "Невідомо" -#: selfdrive/ui/layouts/settings/software.py:48 +#: openpilot/selfdrive/ui/layouts/settings/software.py:55 #, python-format msgid "Updates are only downloaded while the car is off." msgstr "Оновлення завантажуються лише тоді, коли автомобіль вимкнено." -#: selfdrive/ui/widgets/prime.py:33 +#: openpilot/selfdrive/ui/widgets/prime.py:33 #, python-format msgid "Upgrade Now" msgstr "Оновити зараз" -#: selfdrive/ui/layouts/settings/toggles.py:31 -msgid "" -"Upload data from the driver facing camera and help improve the driver " -"monitoring algorithm." -msgstr "" -"Завантажуйте дані з камери, спрямованої на водія, та допоможіть покращити " -"алгоритм моніторингу водія." +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:31 +msgid "Upload data from the driver facing camera and help improve the driver monitoring algorithm." +msgstr "Завантажуйте дані з камери, спрямованої на водія, та допоможіть покращити алгоритм моніторингу водія." -#: selfdrive/ui/layouts/settings/toggles.py:88 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:88 #, python-format msgid "Use Metric System" msgstr "Використовувати метричну систему" -#: selfdrive/ui/layouts/settings/toggles.py:17 -msgid "" -"Use the openpilot system for adaptive cruise control and lane keep driver " -"assistance. Your attention is required at all times to use this feature." -msgstr "" -"Використовуйте систему openpilot для адаптивного круїз-контролю та допомоги " -"в утриманні смуги руху. Ваша увага потрібна постійно при використанні цієї " -"функції. Зміна цього налаштування набуває чинності після вимкнення живлення " -"автомобіля." - -#: selfdrive/ui/layouts/sidebar.py:72 -#: selfdrive/ui/layouts/sidebar.py:144 +#: openpilot/selfdrive/ui/layouts/sidebar.py:72 +#: openpilot/selfdrive/ui/layouts/sidebar.py:144 msgid "VEHICLE" msgstr "АВТО" -#: selfdrive/ui/layouts/settings/device.py:67 +#: openpilot/selfdrive/ui/layouts/settings/device.py:65 #, python-format msgid "VIEW" msgstr "ДИВИСЬ" -#: selfdrive/ui/onroad/alert_renderer.py:52 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:52 #, python-format msgid "Waiting to start" msgstr "Очікування початку" -#: selfdrive/ui/layouts/settings/developer.py:19 -msgid "" -"Warning: This grants SSH access to all public keys in your GitHub settings. " -"Never enter a GitHub username other than your own. A comma employee will " -"NEVER ask you to add their GitHub username." -msgstr "" -"Попередження: це надає доступ по SSH до всіх публічних ключів у ваших " -"налаштуваннях GitHub. Ніколи не вводьте ім'я користувача GitHub, окрім " -"вашого власного. Співробітник comma НІКОЛИ не попросить вас додати його ім'я " -"користувача GitHub." - -#: selfdrive/ui/layouts/onboarding.py:111 +#: openpilot/selfdrive/ui/layouts/onboarding.py:115 #, python-format msgid "Welcome to openpilot" msgstr "Ласкаво просимо до openpilot" -#: selfdrive/ui/layouts/settings/toggles.py:20 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:20 msgid "When enabled, pressing the accelerator pedal will disengage openpilot." msgstr "Якщо увімкнено, натискання на педаль акселератора вимкне openpilot." -#: selfdrive/ui/layouts/sidebar.py:44 +#: openpilot/selfdrive/ui/layouts/sidebar.py:44 msgid "Wi-Fi" msgstr "Wi-Fi" -#: system/ui/widgets/network.py:144 +#: system/ui/widgets/network.py:141 #, python-format msgid "Wi-Fi Network Metered" msgstr "Трафік Wi-Fi" -#: system/ui/widgets/network.py:314 +#: system/ui/widgets/network.py:320 #, python-format msgid "Wrong password" msgstr "Невірний пароль" -#: selfdrive/ui/layouts/onboarding.py:145 +#: openpilot/selfdrive/ui/layouts/onboarding.py:149 #, python-format msgid "You must accept the Terms and Conditions in order to use openpilot." msgstr "Ви повинні прийняти Умови та положення, щоб користуватися openpilot." -#: selfdrive/ui/layouts/onboarding.py:112 +#: openpilot/selfdrive/ui/layouts/onboarding.py:116 #, python-format -msgid "" -"You must accept the Terms and Conditions to use openpilot. Read the latest " -"terms at https://comma.ai/terms before continuing." -msgstr "" -"Ви повинні прийняти Умови використання, щоб користуватися openpilot. Перед " -"тим, як продовжити, ознайомтеся з останніми умовами на сайті https://" -"comma.ai/terms." +msgid "You must accept the Terms and Conditions to use openpilot. Read the latest terms at https://comma.ai/terms before continuing." +msgstr "Ви повинні прийняти Умови використання, щоб користуватися openpilot. Перед тим, як продовжити, ознайомтеся з останніми умовами на сайті https://comma.ai/terms." -#: selfdrive/ui/onroad/driver_camera_dialog.py:34 +#: openpilot/selfdrive/ui/onroad/driver_camera_dialog.py:38 #, python-format msgid "camera starting" msgstr "запуск камери" -#: selfdrive/ui/layouts/settings/software.py:105 +#: openpilot/selfdrive/ui/layouts/settings/software.py:19 #, python-format msgid "checking..." msgstr "перевіряю..." -#: selfdrive/ui/widgets/prime.py:63 +#: openpilot/selfdrive/ui/widgets/prime.py:63 #, python-format msgid "comma prime" msgstr "comma prime" -#: system/ui/widgets/network.py:142 +#: system/ui/widgets/network.py:139 #, python-format msgid "default" msgstr "замовч." -#: selfdrive/ui/layouts/settings/device.py:133 +#: openpilot/selfdrive/ui/layouts/settings/device.py:125 #, python-format msgid "down" msgstr "вниз" -#: selfdrive/ui/layouts/settings/software.py:106 +#: openpilot/selfdrive/ui/layouts/settings/software.py:20 #, python-format msgid "downloading..." msgstr "завантажую..." -#: selfdrive/ui/layouts/settings/software.py:114 +#: openpilot/selfdrive/ui/layouts/settings/software.py:116 #, python-format msgid "failed to check for update" msgstr "не вдалося перевірити оновлення" -#: selfdrive/ui/layouts/settings/software.py:107 +#: openpilot/selfdrive/ui/layouts/settings/software.py:21 #, python-format msgid "finalizing update..." msgstr "завершую..." -#: system/ui/widgets/network.py:237 system/ui/widgets/network.py:314 +#: system/ui/widgets/network.py:238 +#: system/ui/widgets/network.py:321 #, python-format msgid "for \"{}\"" msgstr "для \"{}\"" -#: selfdrive/ui/onroad/hud_renderer.py:177 +#: openpilot/selfdrive/ui/onroad/hud_renderer.py:177 #, python-format msgid "km/h" msgstr "км/год" -#: system/ui/widgets/network.py:204 +#: system/ui/widgets/network.py:201 #, python-format msgid "leave blank for automatic configuration" msgstr "залиште порожнім для автоматичного налаштування" -#: selfdrive/ui/layouts/settings/device.py:134 +#: openpilot/selfdrive/ui/layouts/settings/device.py:126 #, python-format msgid "left" msgstr "вліво" -#: system/ui/widgets/network.py:142 +#: system/ui/widgets/network.py:139 #, python-format msgid "metered" msgstr "обмеж." -#: selfdrive/ui/onroad/hud_renderer.py:177 +#: openpilot/selfdrive/ui/onroad/hud_renderer.py:177 #, python-format msgid "mph" msgstr "миль/год" -#: selfdrive/ui/layouts/settings/software.py:20 +#: openpilot/selfdrive/ui/layouts/settings/software.py:27 #, python-format msgid "never" msgstr "ніколи" -#: selfdrive/ui/layouts/settings/software.py:31 +#: openpilot/selfdrive/ui/layouts/settings/software.py:38 #, python-format msgid "now" msgstr "зараз" -#: selfdrive/ui/layouts/settings/developer.py:71 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:71 #, python-format msgid "openpilot Longitudinal Control (Alpha)" msgstr "Поздовжнє керування openpilot (Альфа)" -#: selfdrive/ui/onroad/alert_renderer.py:51 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:51 #, python-format msgid "openpilot Unavailable" msgstr "openpilot Недоступний" -#: selfdrive/ui/layouts/settings/toggles.py:158 -#, python-format -msgid "" -"openpilot defaults to driving in chill mode. Experimental mode enables alpha-" -"level features that aren't ready for chill mode. Experimental features are " -"listed below:

End-to-End Longitudinal Control


Let the driving " -"model control the gas and brakes. openpilot will drive as it thinks a human " -"would, including stopping for red lights and stop signs. Since the driving " -"model decides the speed to drive, the set speed will only act as an upper " -"bound. This is an alpha quality feature; mistakes should be expected." -"

New Driving Visualization


The driving visualization will " -"transition to the road-facing wide-angle camera at low speeds to better show " -"some turns. The Experimental mode logo will also be shown in the top right " -"corner." -msgstr "" -"openpilot за замовчуванням працює в режимі спокій. Експериментальний режим " -"увімкне функції альфа-рівня, які ще не готові для режиму спокій. " -"Експериментальні функції перелічені нижче:

Кінцевий поздовжній " -"контроль


Дозвольте моделі водіння контролювати газ і гальма. " -"openpilot буде керувати автомобілем так, як це робив би людина, включаючи " -"зупинку на червоне світло і знаки зупинки. Оскільки модель водіння визначає " -"швидкість руху, задана швидкість буде діяти лише як верхня межа. Це функція " -"альфа-рівня; слід очікувати помилок.

Нова візуалізація водіння
Візуалізація водіння перейде на ширококутну камеру, спрямовану на " -"дорогу, при низьких швидкостях, щоб краще показувати деякі повороти. Логотип " -"експериментального режиму також буде показаний у верхньому правому куті." - -#: selfdrive/ui/layouts/settings/device.py:165 -#, python-format -msgid "" -"openpilot is continuously calibrating, resetting is rarely required. " -"Resetting calibration will restart openpilot if the car is powered on." -msgstr "" -"openpilot постійно калібрується, скидання рідко потрібне. Скидання " -"калібрування призведе до перезапуску openpilot, якщо автомобіль увімкнено." - -#: selfdrive/ui/layouts/settings/firehose.py:20 -msgid "" -"openpilot learns to drive by watching humans, like you, drive.\n" -"\n" -"Firehose Mode allows you to maximize your training data uploads to improve " -"openpilot's driving models. More data means bigger models, which means " -"better Experimental Mode." -msgstr "" -"openpilot вчиться керувати автомобілем, спостерігаючи за тим, як це роблять " -"люди, такі як ви.\n" -"\n" -"Режим зливи дозволяє максимально збільшити обсяг завантажуваних навчальних " -"даних, щоб поліпшити моделі керування автомобілем openpilot. Більше даних " -"означає більші моделі, а це означає кращий експериментальний режим." - -#: selfdrive/ui/layouts/settings/toggles.py:183 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:184 #, python-format msgid "openpilot longitudinal control may come in a future update." msgstr "Поздовжнє керування openpilot може з'явитися в майбутньому оновленні." -#: selfdrive/ui/layouts/settings/device.py:26 -msgid "" -"openpilot requires the device to be mounted within 4° left or right and " -"within 5° up or 9° down." -msgstr "" -"Для роботи openpilot потрібно, щоб пристрій був встановлений з нахилом не " -"більше 4° вліво або вправо та не більше 5° вгору або 9° вниз. openpilot " -"постійно калібрується, тому скидання калібрування потрібне рідко." +#: openpilot/selfdrive/ui/layouts/settings/device.py:25 +msgid "openpilot requires the device to be mounted within 4° left or right and within 5° up or 9° down." +msgstr "Для роботи openpilot потрібно, щоб пристрій був встановлений з нахилом не більше 4° вліво або вправо та не більше 5° вгору або 9° вниз. openpilot постійно калібрується, тому скидання калібрування потрібне рідко." -#: selfdrive/ui/layouts/settings/device.py:134 +#: openpilot/selfdrive/ui/layouts/settings/device.py:126 #, python-format msgid "right" msgstr "вправо" -#: system/ui/widgets/network.py:142 +#: system/ui/widgets/network.py:139 #, python-format msgid "unmetered" msgstr "необмеж." -#: selfdrive/ui/layouts/settings/device.py:133 +#: openpilot/selfdrive/ui/layouts/settings/device.py:125 #, python-format msgid "up" msgstr "вгору" -#: selfdrive/ui/layouts/settings/software.py:125 +#: openpilot/selfdrive/ui/layouts/settings/software.py:127 #, python-format msgid "up to date, last checked never" msgstr "оновлено, ніколи не перевірялось" -#: selfdrive/ui/layouts/settings/software.py:123 +#: openpilot/selfdrive/ui/layouts/settings/software.py:125 #, python-format msgid "up to date, last checked {}" msgstr "оновлено, перевірив {}" -#: selfdrive/ui/layouts/settings/software.py:117 +#: openpilot/selfdrive/ui/layouts/settings/software.py:119 #, python-format msgid "update available" msgstr "доступне оновлення" -#: selfdrive/ui/layouts/home.py:169 +#: openpilot/selfdrive/ui/layouts/home.py:169 #, python-format msgid "{} ALERT" msgid_plural "{} ALERTS" @@ -1209,7 +996,7 @@ msgstr[0] "{} СПОВІЩЕННЯ" msgstr[1] "{} СПОВІЩЕННЯ" msgstr[2] "{} СПОВІЩЕНЬ" -#: selfdrive/ui/layouts/settings/software.py:40 +#: openpilot/selfdrive/ui/layouts/settings/software.py:47 #, python-format msgid "{} day ago" msgid_plural "{} days ago" @@ -1217,7 +1004,7 @@ msgstr[0] "{} день тому" msgstr[1] "{} дні тому" msgstr[2] "{} днів тому" -#: selfdrive/ui/layouts/settings/software.py:37 +#: openpilot/selfdrive/ui/layouts/settings/software.py:44 #, python-format msgid "{} hour ago" msgid_plural "{} hours ago" @@ -1225,7 +1012,7 @@ msgstr[0] "{} година тому" msgstr[1] "{} години тому" msgstr[2] "{} годин тому" -#: selfdrive/ui/layouts/settings/software.py:34 +#: openpilot/selfdrive/ui/layouts/settings/software.py:41 #, python-format msgid "{} minute ago" msgid_plural "{} minutes ago" @@ -1233,26 +1020,21 @@ msgstr[0] "{} хвилина тому" msgstr[1] "{} хвилини тому" msgstr[2] "{} хвилин тому" -#: selfdrive/ui/layouts/settings/firehose.py:111 +#: openpilot/selfdrive/ui/layouts/settings/firehose.py:70 #, python-format msgid "{} segment of your driving is in the training dataset so far." msgid_plural "{} segments of your driving is in the training dataset so far." -msgstr[0] "" -"{} сегмент вашого водіння на даний момент містяться в тренувальному наборі " -"даних." -msgstr[1] "" -"{} сегменти вашого водіння на даний момент містяться в тренувальному наборі " -"даних." -msgstr[2] "" -"{} сегментів вашого водіння на даний момент містяться в тренувальному наборі " -"даних." +msgstr[0] "{} сегмент вашого водіння на даний момент містяться в тренувальному наборі даних." +msgstr[1] "{} сегменти вашого водіння на даний момент містяться в тренувальному наборі даних." +msgstr[2] "{} сегментів вашого водіння на даний момент містяться в тренувальному наборі даних." -#: selfdrive/ui/widgets/prime.py:62 +#: openpilot/selfdrive/ui/widgets/prime.py:62 #, python-format msgid "✓ SUBSCRIBED" msgstr "✓ ПІДПИСАНО" -#: selfdrive/ui/widgets/setup.py:22 +#: openpilot/selfdrive/ui/widgets/setup.py:21 #, python-format msgid "🔥 Firehose Mode 🔥" msgstr "🌧️ Режим зливи 🌧️" + diff --git a/selfdrive/ui/translations/app_zh-CHS.po b/selfdrive/ui/translations/app_zh-CHS.po index 2400b6f44a..4d9a5b78e2 100644 --- a/selfdrive/ui/translations/app_zh-CHS.po +++ b/selfdrive/ui/translations/app_zh-CHS.po @@ -17,1158 +17,1018 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=1; plural=0;\n" -#: selfdrive/ui/layouts/settings/device.py:160 +#: openpilot/selfdrive/ui/layouts/settings/device.py:152 #, python-format msgid " Steering torque response calibration is complete." msgstr " 转向扭矩响应校准完成。" -#: selfdrive/ui/layouts/settings/device.py:158 +#: openpilot/selfdrive/ui/layouts/settings/device.py:150 #, python-format msgid " Steering torque response calibration is {}% complete." msgstr " 转向扭矩响应校准已完成 {}%。" -#: selfdrive/ui/layouts/settings/device.py:133 +#: openpilot/selfdrive/ui/layouts/settings/device.py:125 #, python-format msgid " Your device is pointed {:.1f}° {} and {:.1f}° {}." msgstr " 您的设备朝向 {:.1f}° {} 与 {:.1f}° {}。" -#: selfdrive/ui/layouts/sidebar.py:43 +#: openpilot/selfdrive/ui/layouts/sidebar.py:43 msgid "--" msgstr "--" -#: selfdrive/ui/widgets/prime.py:47 +#: openpilot/selfdrive/ui/widgets/prime.py:47 #, python-format msgid "1 year of drive storage" msgstr "1 年行驶数据存储" -#: selfdrive/ui/widgets/prime.py:47 +#: openpilot/selfdrive/ui/widgets/prime.py:47 #, python-format msgid "24/7 LTE connectivity" msgstr "全天候 LTE 连接" -#: selfdrive/ui/layouts/sidebar.py:46 +#: openpilot/selfdrive/ui/layouts/sidebar.py:46 msgid "2G" msgstr "2G" -#: selfdrive/ui/layouts/sidebar.py:47 +#: openpilot/selfdrive/ui/layouts/sidebar.py:47 msgid "3G" msgstr "3G" -#: selfdrive/ui/layouts/sidebar.py:49 +#: openpilot/selfdrive/ui/layouts/sidebar.py:49 msgid "5G" msgstr "5G" -#: selfdrive/ui/layouts/settings/developer.py:23 -msgid "" -"WARNING: openpilot longitudinal control is in alpha for this car and will " -"disable Automatic Emergency Braking (AEB).

On this car, openpilot " -"defaults to the car's built-in ACC instead of openpilot's longitudinal " -"control. Enable this to switch to openpilot longitudinal control. Enabling " -"Experimental mode is recommended when enabling openpilot longitudinal " -"control alpha. Changing this setting will restart openpilot if the car is " -"powered on." -msgstr "" -"警告:此车型的 openpilot 纵向控制仍为 alpha,将会停用自动紧急制动 (AEB)。" -"

在此车型上,openpilot 默认使用车载 ACC,而非 openpilot 的纵向控" -"制。启用此选项可切换为 openpilot 纵向控制。建议同时启用实验模式。若车辆通电," -"更改此设置将会重启 openpilot。" - -#: selfdrive/ui/layouts/settings/device.py:148 +#: openpilot/selfdrive/ui/layouts/settings/device.py:140 #, python-format msgid "

Steering lag calibration is complete." msgstr "

转向延迟校准完成。" -#: selfdrive/ui/layouts/settings/device.py:146 +#: openpilot/selfdrive/ui/layouts/settings/device.py:138 #, python-format msgid "

Steering lag calibration is {}% complete." msgstr "

转向延迟校准已完成 {}%。" -#: selfdrive/ui/layouts/settings/firehose.py:138 -#, python-format -msgid "ACTIVE" -msgstr "已启用" - -#: selfdrive/ui/layouts/settings/developer.py:15 -msgid "" -"ADB (Android Debug Bridge) allows connecting to your device over USB or over " -"the network. See https://docs.comma.ai/how-to/connect-to-comma for more info." -msgstr "" -"ADB(Android 调试桥)可通过 USB 或网络连接到您的设备。详见 https://docs." -"comma.ai/how-to/connect-to-comma。" - -#: selfdrive/ui/widgets/ssh_key.py:30 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:30 msgid "ADD" msgstr "添加" -#: system/ui/widgets/network.py:139 +#: system/ui/widgets/network.py:136 #, python-format msgid "APN Setting" msgstr "APN 设置" -#: selfdrive/ui/widgets/offroad_alerts.py:109 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:109 #, python-format msgid "Acknowledge Excessive Actuation" msgstr "确认过度作动" -#: system/ui/widgets/network.py:74 system/ui/widgets/network.py:95 +#: system/ui/widgets/network.py:92 +#: system/ui/widgets/network.py:74 #, python-format msgid "Advanced" msgstr "高级" -#: selfdrive/ui/layouts/settings/toggles.py:98 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:98 #, python-format msgid "Aggressive" msgstr "激进" -#: selfdrive/ui/layouts/onboarding.py:116 +#: openpilot/selfdrive/ui/layouts/onboarding.py:120 #, python-format msgid "Agree" msgstr "同意" -#: selfdrive/ui/layouts/settings/toggles.py:70 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:70 #, python-format msgid "Always-On Driver Monitoring" msgstr "始终启用驾驶员监控" -#: selfdrive/ui/layouts/settings/toggles.py:186 -#, python-format -msgid "" -"An alpha version of openpilot longitudinal control can be tested, along with " -"Experimental mode, on non-release branches." -msgstr "openpilot 纵向控制的 alpha 版本可在非发布分支搭配实验模式进行测试。" - -#: selfdrive/ui/layouts/settings/device.py:187 +#: openpilot/selfdrive/ui/layouts/settings/device.py:183 #, python-format msgid "Are you sure you want to power off?" msgstr "确定要关机吗?" -#: selfdrive/ui/layouts/settings/device.py:175 +#: openpilot/selfdrive/ui/layouts/settings/device.py:171 #, python-format msgid "Are you sure you want to reboot?" msgstr "确定要重启吗?" -#: selfdrive/ui/layouts/settings/device.py:119 +#: openpilot/selfdrive/ui/layouts/settings/device.py:111 #, python-format msgid "Are you sure you want to reset calibration?" msgstr "确定要重置校准吗?" -#: selfdrive/ui/layouts/settings/software.py:163 +#: openpilot/selfdrive/ui/layouts/settings/software.py:173 #, python-format msgid "Are you sure you want to uninstall?" msgstr "确定要卸载吗?" -#: system/ui/widgets/network.py:99 selfdrive/ui/layouts/onboarding.py:147 +#: system/ui/widgets/network.py:96 +#: openpilot/selfdrive/ui/layouts/onboarding.py:151 #, python-format msgid "Back" msgstr "返回" -#: selfdrive/ui/widgets/prime.py:38 +#: openpilot/selfdrive/ui/widgets/prime.py:38 #, python-format msgid "Become a comma prime member at connect.comma.ai" msgstr "前往 connect.comma.ai 成为 comma prime 会员" -#: selfdrive/ui/widgets/pairing_dialog.py:130 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:119 #, python-format msgid "Bookmark connect.comma.ai to your home screen to use it like an app" msgstr "将 connect.comma.ai 添加到主屏幕,像应用一样使用" -#: selfdrive/ui/layouts/settings/device.py:68 +#: openpilot/selfdrive/ui/layouts/settings/device.py:66 #, python-format msgid "CHANGE" msgstr "更改" -#: selfdrive/ui/layouts/settings/software.py:50 -#: selfdrive/ui/layouts/settings/software.py:107 -#: selfdrive/ui/layouts/settings/software.py:118 -#: selfdrive/ui/layouts/settings/software.py:147 +#: openpilot/selfdrive/ui/layouts/settings/software.py:157 +#: openpilot/selfdrive/ui/layouts/settings/software.py:57 +#: openpilot/selfdrive/ui/layouts/settings/software.py:117 +#: openpilot/selfdrive/ui/layouts/settings/software.py:128 #, python-format msgid "CHECK" msgstr "检查" -#: selfdrive/ui/widgets/exp_mode_button.py:50 +#: openpilot/selfdrive/ui/widgets/exp_mode_button.py:51 #, python-format msgid "CHILL MODE ON" msgstr "安稳模式已开启" -#: system/ui/widgets/network.py:155 selfdrive/ui/layouts/sidebar.py:73 -#: selfdrive/ui/layouts/sidebar.py:134 selfdrive/ui/layouts/sidebar.py:136 -#: selfdrive/ui/layouts/sidebar.py:138 +#: system/ui/widgets/network.py:152 +#: openpilot/selfdrive/ui/layouts/sidebar.py:73 +#: openpilot/selfdrive/ui/layouts/sidebar.py:134 +#: openpilot/selfdrive/ui/layouts/sidebar.py:136 +#: openpilot/selfdrive/ui/layouts/sidebar.py:138 #, python-format msgid "CONNECT" msgstr "CONNECT" -#: system/ui/widgets/network.py:369 +#: system/ui/widgets/network.py:376 #, python-format msgid "CONNECTING..." msgstr "连接中..." -#: system/ui/widgets/confirm_dialog.py:23 system/ui/widgets/option_dialog.py:35 -#: system/ui/widgets/keyboard.py:81 system/ui/widgets/network.py:318 +#: system/ui/widgets/network.py:326 +#: system/ui/widgets/confirm_dialog.py:24 +#: system/ui/widgets/option_dialog.py:36 +#: system/ui/widgets/keyboard.py:83 #, python-format msgid "Cancel" msgstr "取消" -#: system/ui/widgets/network.py:134 +#: system/ui/widgets/network.py:131 #, python-format msgid "Cellular Metered" msgstr "蜂窝计量" -#: selfdrive/ui/layouts/settings/device.py:68 +#: openpilot/selfdrive/ui/layouts/settings/device.py:66 #, python-format msgid "Change Language" msgstr "更改语言" -#: selfdrive/ui/layouts/settings/toggles.py:125 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:125 #, python-format msgid "Changing this setting will restart openpilot if the car is powered on." msgstr "若车辆通电,更改此设置将重启 openpilot。" -#: selfdrive/ui/widgets/pairing_dialog.py:129 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:118 #, python-format msgid "Click \"add new device\" and scan the QR code on the right" msgstr "点击“添加新设备”,扫描右侧二维码" -#: selfdrive/ui/widgets/offroad_alerts.py:104 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:104 #, python-format msgid "Close" msgstr "关闭" -#: selfdrive/ui/layouts/settings/software.py:49 +#: openpilot/selfdrive/ui/layouts/settings/software.py:56 #, python-format msgid "Current Version" msgstr "当前版本" -#: selfdrive/ui/layouts/settings/software.py:110 +#: openpilot/selfdrive/ui/layouts/settings/software.py:120 #, python-format msgid "DOWNLOAD" msgstr "下载" -#: selfdrive/ui/layouts/onboarding.py:115 +#: openpilot/selfdrive/ui/layouts/onboarding.py:119 #, python-format msgid "Decline" msgstr "拒绝" -#: selfdrive/ui/layouts/onboarding.py:148 +#: openpilot/selfdrive/ui/layouts/onboarding.py:152 #, python-format msgid "Decline, uninstall openpilot" msgstr "拒绝并卸载 openpilot" -#: selfdrive/ui/layouts/settings/settings.py:67 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:64 msgid "Developer" msgstr "开发者" -#: selfdrive/ui/layouts/settings/settings.py:62 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:59 msgid "Device" msgstr "设备" -#: selfdrive/ui/layouts/settings/toggles.py:58 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:58 #, python-format msgid "Disengage on Accelerator Pedal" msgstr "踩下加速踏板时脱离" -#: selfdrive/ui/layouts/settings/device.py:184 +#: openpilot/selfdrive/ui/layouts/settings/device.py:176 #, python-format msgid "Disengage to Power Off" msgstr "脱离以关机" -#: selfdrive/ui/layouts/settings/device.py:172 +#: openpilot/selfdrive/ui/layouts/settings/device.py:164 #, python-format msgid "Disengage to Reboot" msgstr "脱离以重启" -#: selfdrive/ui/layouts/settings/device.py:103 +#: openpilot/selfdrive/ui/layouts/settings/device.py:95 #, python-format msgid "Disengage to Reset Calibration" msgstr "脱离以重置校准" -#: selfdrive/ui/layouts/settings/toggles.py:32 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:32 msgid "Display speed in km/h instead of mph." msgstr "以 km/h 显示速度(非 mph)。" -#: selfdrive/ui/layouts/settings/device.py:59 +#: openpilot/selfdrive/ui/layouts/settings/device.py:57 #, python-format msgid "Dongle ID" msgstr "Dongle ID" -#: selfdrive/ui/layouts/settings/software.py:50 +#: openpilot/selfdrive/ui/layouts/settings/software.py:57 #, python-format msgid "Download" msgstr "下载" -#: selfdrive/ui/layouts/settings/device.py:62 +#: openpilot/selfdrive/ui/layouts/settings/device.py:60 #, python-format msgid "Driver Camera" msgstr "车内摄像头" -#: selfdrive/ui/layouts/settings/toggles.py:96 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:96 #, python-format msgid "Driving Personality" msgstr "驾驶风格" -#: system/ui/widgets/network.py:123 system/ui/widgets/network.py:139 +#: system/ui/widgets/network.py:120 +#: system/ui/widgets/network.py:136 #, python-format msgid "EDIT" msgstr "编辑" -#: selfdrive/ui/layouts/sidebar.py:138 +#: openpilot/selfdrive/ui/layouts/sidebar.py:138 msgid "ERROR" msgstr "错误" -#: selfdrive/ui/layouts/sidebar.py:45 +#: openpilot/selfdrive/ui/layouts/sidebar.py:45 msgid "ETH" msgstr "ETH" -#: selfdrive/ui/widgets/exp_mode_button.py:50 +#: openpilot/selfdrive/ui/widgets/exp_mode_button.py:51 #, python-format msgid "EXPERIMENTAL MODE ON" msgstr "实验模式已开启" -#: selfdrive/ui/layouts/settings/developer.py:166 -#: selfdrive/ui/layouts/settings/toggles.py:228 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:229 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:180 #, python-format msgid "Enable" msgstr "启用" -#: selfdrive/ui/layouts/settings/developer.py:39 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:39 #, python-format msgid "Enable ADB" msgstr "启用 ADB" -#: selfdrive/ui/layouts/settings/toggles.py:64 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:64 #, python-format msgid "Enable Lane Departure Warnings" msgstr "启用车道偏离警示" -#: system/ui/widgets/network.py:129 +#: system/ui/widgets/network.py:126 #, python-format msgid "Enable Roaming" msgstr "启用漫游" -#: selfdrive/ui/layouts/settings/developer.py:48 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:48 #, python-format msgid "Enable SSH" msgstr "启用 SSH" -#: system/ui/widgets/network.py:120 +#: system/ui/widgets/network.py:117 #, python-format msgid "Enable Tethering" msgstr "启用网络共享" -#: selfdrive/ui/layouts/settings/toggles.py:30 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:30 msgid "Enable driver monitoring even when openpilot is not engaged." msgstr "即使未启用 openpilot 也启用驾驶员监控。" -#: selfdrive/ui/layouts/settings/toggles.py:46 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:46 #, python-format msgid "Enable openpilot" msgstr "启用 openpilot" -#: selfdrive/ui/layouts/settings/toggles.py:189 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:190 #, python-format -msgid "" -"Enable the openpilot longitudinal control (alpha) toggle to allow " -"Experimental mode." +msgid "Enable the openpilot longitudinal control (alpha) toggle to allow Experimental mode." msgstr "启用 openpilot 纵向控制(alpha)开关,以使用实验模式。" -#: system/ui/widgets/network.py:204 +#: system/ui/widgets/network.py:201 #, python-format msgid "Enter APN" msgstr "输入 APN" -#: system/ui/widgets/network.py:241 +#: system/ui/widgets/network.py:243 #, python-format msgid "Enter SSID" msgstr "输入 SSID" -#: system/ui/widgets/network.py:254 +#: system/ui/widgets/network.py:257 #, python-format msgid "Enter new tethering password" msgstr "输入新的网络共享密码" -#: system/ui/widgets/network.py:237 system/ui/widgets/network.py:314 +#: system/ui/widgets/network.py:238 +#: system/ui/widgets/network.py:320 #, python-format msgid "Enter password" msgstr "输入密码" -#: selfdrive/ui/widgets/ssh_key.py:89 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:89 #, python-format msgid "Enter your GitHub username" msgstr "输入您的 GitHub 用户名" -#: system/ui/widgets/list_view.py:123 system/ui/widgets/list_view.py:160 +#: system/ui/widgets/list_view.py:123 +#: system/ui/widgets/list_view.py:160 #, python-format msgid "Error" msgstr "错误" -#: selfdrive/ui/layouts/settings/toggles.py:52 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:52 #, python-format msgid "Experimental Mode" msgstr "实验模式" -#: selfdrive/ui/layouts/settings/toggles.py:181 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:182 #, python-format -msgid "" -"Experimental mode is currently unavailable on this car since the car's stock " -"ACC is used for longitudinal control." +msgid "Experimental mode is currently unavailable on this car since the car's stock ACC is used for longitudinal control." msgstr "此车型当前无法使用实验模式,因为纵向控制使用的是原厂 ACC。" -#: system/ui/widgets/network.py:373 +#: system/ui/widgets/network.py:380 #, python-format msgid "FORGETTING..." msgstr "正在遗忘..." -#: selfdrive/ui/widgets/setup.py:44 +#: openpilot/selfdrive/ui/widgets/setup.py:43 #, python-format msgid "Finish Setup" msgstr "完成设置" -#: selfdrive/ui/layouts/settings/settings.py:66 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:63 msgid "Firehose" msgstr "Firehose" -#: selfdrive/ui/layouts/settings/firehose.py:18 +#: openpilot/selfdrive/ui/layouts/settings/firehose.py:10 msgid "Firehose Mode" msgstr "Firehose 模式" -#: selfdrive/ui/layouts/settings/firehose.py:25 -msgid "" -"For maximum effectiveness, bring your device inside and connect to a good " -"USB-C adapter and Wi-Fi weekly.\n" -"\n" -"Firehose Mode can also work while you're driving if connected to a hotspot " -"or unlimited SIM card.\n" -"\n" -"\n" -"Frequently Asked Questions\n" -"\n" -"Does it matter how or where I drive? Nope, just drive as you normally " -"would.\n" -"\n" -"Do all of my segments get pulled in Firehose Mode? No, we selectively pull a " -"subset of your segments.\n" -"\n" -"What's a good USB-C adapter? Any fast phone or laptop charger should be " -"fine.\n" -"\n" -"Does it matter which software I run? Yes, only upstream openpilot (and " -"particular forks) are able to be used for training." -msgstr "" -"为达到最佳效果,请将设备带到室内,并每周连接优质 USB‑C 充电器与 Wi‑Fi。\n" -"\n" -"若连接热点或不限流量卡,行车中也可使用 Firehose 模式。\n" -"\n" -"\n" -"常见问题\n" -"\n" -"我怎么开、在哪开有区别吗?没有,平常怎么开就怎么开。\n" -"\n" -"Firehose 模式会拉取我所有片段吗?不会,我们会选择性拉取部分片段。\n" -"\n" -"什么是好的 USB‑C 充电器?任何快速的手机或笔电充电器都可以。\n" -"\n" -"我跑什么软件有区别吗?有,只有上游 openpilot(及特定分支)可用于训练。" - -#: system/ui/widgets/network.py:318 system/ui/widgets/network.py:451 +#: system/ui/widgets/network.py:458 +#: system/ui/widgets/network.py:326 #, python-format msgid "Forget" msgstr "忘记" -#: system/ui/widgets/network.py:319 +#: system/ui/widgets/network.py:327 #, python-format msgid "Forget Wi-Fi Network \"{}\"?" msgstr "要忘记 Wi‑Fi 网络“{}”吗?" -#: selfdrive/ui/layouts/sidebar.py:71 selfdrive/ui/layouts/sidebar.py:125 +#: openpilot/selfdrive/ui/layouts/sidebar.py:71 +#: openpilot/selfdrive/ui/layouts/sidebar.py:125 msgid "GOOD" msgstr "良好" -#: selfdrive/ui/widgets/pairing_dialog.py:128 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:117 #, python-format msgid "Go to https://connect.comma.ai on your phone" msgstr "在手机上前往 https://connect.comma.ai" -#: selfdrive/ui/layouts/sidebar.py:129 +#: openpilot/selfdrive/ui/layouts/sidebar.py:129 msgid "HIGH" msgstr "高" -#: system/ui/widgets/network.py:155 +#: system/ui/widgets/network.py:152 #, python-format msgid "Hidden Network" msgstr "隐藏网络" -#: selfdrive/ui/layouts/settings/firehose.py:140 -#, python-format -msgid "INACTIVE: connect to an unmetered network" -msgstr "未启用:请连接不限流量网络" - -#: selfdrive/ui/layouts/settings/software.py:53 -#: selfdrive/ui/layouts/settings/software.py:136 +#: openpilot/selfdrive/ui/layouts/settings/software.py:60 +#: openpilot/selfdrive/ui/layouts/settings/software.py:146 #, python-format msgid "INSTALL" msgstr "安装" -#: system/ui/widgets/network.py:150 +#: system/ui/widgets/network.py:147 #, python-format msgid "IP Address" msgstr "IP 地址" -#: selfdrive/ui/layouts/settings/software.py:53 +#: openpilot/selfdrive/ui/layouts/settings/software.py:60 #, python-format msgid "Install Update" msgstr "安装更新" -#: selfdrive/ui/layouts/settings/developer.py:56 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:56 #, python-format msgid "Joystick Debug Mode" msgstr "摇杆调试模式" -#: selfdrive/ui/widgets/ssh_key.py:29 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:29 msgid "LOADING" msgstr "加载中" -#: selfdrive/ui/layouts/sidebar.py:48 +#: openpilot/selfdrive/ui/layouts/sidebar.py:48 msgid "LTE" msgstr "LTE" -#: selfdrive/ui/layouts/settings/developer.py:64 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:64 #, python-format msgid "Longitudinal Maneuver Mode" msgstr "纵向操作模式" -#: selfdrive/ui/onroad/hud_renderer.py:148 +#: openpilot/selfdrive/ui/onroad/hud_renderer.py:148 #, python-format msgid "MAX" msgstr "最大" -#: selfdrive/ui/widgets/setup.py:75 +#: openpilot/selfdrive/ui/widgets/setup.py:74 #, python-format -msgid "" -"Maximize your training data uploads to improve openpilot's driving models." +msgid "Maximize your training data uploads to improve openpilot's driving models." msgstr "最大化上传训练数据,以改进 openpilot 的驾驶模型。" -#: selfdrive/ui/layouts/settings/device.py:59 -#: selfdrive/ui/layouts/settings/device.py:60 +#: openpilot/selfdrive/ui/layouts/settings/device.py:57 +#: openpilot/selfdrive/ui/layouts/settings/device.py:58 #, python-format msgid "N/A" msgstr "无" -#: selfdrive/ui/layouts/sidebar.py:142 +#: openpilot/selfdrive/ui/layouts/sidebar.py:142 msgid "NO" msgstr "否" -#: selfdrive/ui/layouts/settings/settings.py:63 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:60 msgid "Network" msgstr "网络" -#: selfdrive/ui/widgets/ssh_key.py:114 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:115 #, python-format msgid "No SSH keys found" msgstr "未找到 SSH 密钥" -#: selfdrive/ui/widgets/ssh_key.py:126 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:127 #, python-format msgid "No SSH keys found for user '{}'" msgstr "未找到用户“{}”的 SSH 密钥" -#: selfdrive/ui/widgets/offroad_alerts.py:320 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:321 #, python-format msgid "No release notes available." msgstr "暂无发行说明。" -#: selfdrive/ui/layouts/sidebar.py:73 selfdrive/ui/layouts/sidebar.py:134 +#: openpilot/selfdrive/ui/layouts/sidebar.py:73 +#: openpilot/selfdrive/ui/layouts/sidebar.py:134 msgid "OFFLINE" msgstr "离线" -#: system/ui/widgets/html_render.py:263 system/ui/widgets/confirm_dialog.py:93 -#: selfdrive/ui/layouts/sidebar.py:127 +#: system/ui/widgets/confirm_dialog.py:93 +#: system/ui/widgets/html_render.py:263 +#: openpilot/selfdrive/ui/layouts/sidebar.py:127 #, python-format msgid "OK" msgstr "确定" -#: selfdrive/ui/layouts/sidebar.py:72 selfdrive/ui/layouts/sidebar.py:136 -#: selfdrive/ui/layouts/sidebar.py:144 +#: openpilot/selfdrive/ui/layouts/sidebar.py:72 +#: openpilot/selfdrive/ui/layouts/sidebar.py:144 +#: openpilot/selfdrive/ui/layouts/sidebar.py:136 msgid "ONLINE" msgstr "在线" -#: selfdrive/ui/widgets/setup.py:20 +#: openpilot/selfdrive/ui/widgets/setup.py:19 #, python-format msgid "Open" msgstr "打开" -#: selfdrive/ui/layouts/settings/device.py:48 +#: openpilot/selfdrive/ui/layouts/settings/device.py:45 #, python-format msgid "PAIR" msgstr "配对" -#: selfdrive/ui/layouts/sidebar.py:142 +#: openpilot/selfdrive/ui/layouts/sidebar.py:142 msgid "PANDA" msgstr "PANDA" -#: selfdrive/ui/layouts/settings/device.py:62 +#: openpilot/selfdrive/ui/layouts/settings/device.py:60 #, python-format msgid "PREVIEW" msgstr "预览" -#: selfdrive/ui/widgets/prime.py:44 +#: openpilot/selfdrive/ui/widgets/prime.py:44 #, python-format msgid "PRIME FEATURES:" msgstr "PRIME 功能:" -#: selfdrive/ui/layouts/settings/device.py:48 +#: openpilot/selfdrive/ui/layouts/settings/device.py:45 #, python-format msgid "Pair Device" msgstr "配对设备" -#: selfdrive/ui/widgets/setup.py:19 +#: openpilot/selfdrive/ui/widgets/setup.py:18 #, python-format msgid "Pair device" msgstr "配对设备" -#: selfdrive/ui/widgets/pairing_dialog.py:103 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:92 #, python-format msgid "Pair your device to your comma account" msgstr "将设备配对到您的 comma 账号" -#: selfdrive/ui/widgets/setup.py:48 selfdrive/ui/layouts/settings/device.py:24 +#: openpilot/selfdrive/ui/widgets/setup.py:47 +#: openpilot/selfdrive/ui/layouts/settings/device.py:23 #, python-format -msgid "" -"Pair your device with comma connect (connect.comma.ai) and claim your comma " -"prime offer." -msgstr "" -"将设备与 comma connect(connect.comma.ai)配对,领取您的 comma prime 优惠。" +msgid "Pair your device with comma connect (connect.comma.ai) and claim your comma prime offer." +msgstr "将设备与 comma connect(connect.comma.ai)配对,领取您的 comma prime 优惠。" -#: selfdrive/ui/widgets/setup.py:91 +#: openpilot/selfdrive/ui/widgets/setup.py:91 #, python-format msgid "Please connect to Wi-Fi to complete initial pairing" msgstr "请连接 Wi‑Fi 以完成初始配对" -#: selfdrive/ui/layouts/settings/device.py:55 -#: selfdrive/ui/layouts/settings/device.py:187 +#: openpilot/selfdrive/ui/layouts/settings/device.py:183 +#: openpilot/selfdrive/ui/layouts/settings/device.py:53 #, python-format msgid "Power Off" msgstr "关机" -#: system/ui/widgets/network.py:144 +#: system/ui/widgets/network.py:141 #, python-format msgid "Prevent large data uploads when on a metered Wi-Fi connection" msgstr "在计量制 Wi‑Fi 连接时避免大量上传" -#: system/ui/widgets/network.py:135 +#: system/ui/widgets/network.py:132 #, python-format msgid "Prevent large data uploads when on a metered cellular connection" msgstr "在计量制蜂窝网络时避免大量上传" -#: selfdrive/ui/layouts/settings/device.py:25 -msgid "" -"Preview the driver facing camera to ensure that driver monitoring has good " -"visibility. (vehicle must be off)" +#: openpilot/selfdrive/ui/layouts/settings/device.py:24 +msgid "Preview the driver facing camera to ensure that driver monitoring has good visibility. (vehicle must be off)" msgstr "预览车内摄像头以确保驾驶员监控视野良好。(车辆必须熄火)" -#: selfdrive/ui/widgets/pairing_dialog.py:161 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:150 #, python-format msgid "QR Code Error" msgstr "二维码错误" -#: selfdrive/ui/widgets/ssh_key.py:31 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:31 msgid "REMOVE" msgstr "移除" -#: selfdrive/ui/layouts/settings/device.py:51 +#: openpilot/selfdrive/ui/layouts/settings/device.py:49 #, python-format msgid "RESET" msgstr "重置" -#: selfdrive/ui/layouts/settings/device.py:65 +#: openpilot/selfdrive/ui/layouts/settings/device.py:63 #, python-format msgid "REVIEW" msgstr "查看" -#: selfdrive/ui/layouts/settings/device.py:55 -#: selfdrive/ui/layouts/settings/device.py:175 +#: openpilot/selfdrive/ui/layouts/settings/device.py:171 +#: openpilot/selfdrive/ui/layouts/settings/device.py:53 #, python-format msgid "Reboot" msgstr "重启" -#: selfdrive/ui/onroad/alert_renderer.py:66 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:66 #, python-format msgid "Reboot Device" msgstr "重启设备" -#: selfdrive/ui/widgets/offroad_alerts.py:112 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:112 #, python-format msgid "Reboot and Update" msgstr "重启并更新" -#: selfdrive/ui/layouts/settings/toggles.py:27 -msgid "" -"Receive alerts to steer back into the lane when your vehicle drifts over a " -"detected lane line without a turn signal activated while driving over 31 mph " -"(50 km/h)." -msgstr "" -"当车辆以超过 31 mph(50 km/h)行驶且未打转向灯越过检测到的车道线时,接收引导" -"回车道的警报。" - -#: selfdrive/ui/layouts/settings/toggles.py:76 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:76 #, python-format msgid "Record and Upload Driver Camera" msgstr "录制并上传车内摄像头" -#: selfdrive/ui/layouts/settings/toggles.py:82 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:82 #, python-format msgid "Record and Upload Microphone Audio" msgstr "录制并上传麦克风音频" -#: selfdrive/ui/layouts/settings/toggles.py:33 -msgid "" -"Record and store microphone audio while driving. The audio will be included " -"in the dashcam video in comma connect." -msgstr "" -"行驶时录制并保存麦克风音频。音频将包含在 comma connect 的行车记录视频中。" +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:33 +msgid "Record and store microphone audio while driving. The audio will be included in the dashcam video in comma connect." +msgstr "行驶时录制并保存麦克风音频。音频将包含在 comma connect 的行车记录视频中。" -#: selfdrive/ui/layouts/settings/device.py:67 +#: openpilot/selfdrive/ui/layouts/settings/device.py:65 #, python-format msgid "Regulatory" msgstr "法规" -#: selfdrive/ui/layouts/settings/toggles.py:98 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:98 #, python-format msgid "Relaxed" msgstr "从容" -#: selfdrive/ui/widgets/prime.py:47 +#: openpilot/selfdrive/ui/widgets/prime.py:47 #, python-format msgid "Remote access" msgstr "远程访问" -#: selfdrive/ui/widgets/prime.py:47 +#: openpilot/selfdrive/ui/widgets/prime.py:47 #, python-format msgid "Remote snapshots" msgstr "远程快照" -#: selfdrive/ui/widgets/ssh_key.py:123 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:124 #, python-format msgid "Request timed out" msgstr "请求超时" -#: selfdrive/ui/layouts/settings/device.py:119 +#: openpilot/selfdrive/ui/layouts/settings/device.py:111 #, python-format msgid "Reset" msgstr "重置" -#: selfdrive/ui/layouts/settings/device.py:51 +#: openpilot/selfdrive/ui/layouts/settings/device.py:49 #, python-format msgid "Reset Calibration" msgstr "重置校准" -#: selfdrive/ui/layouts/settings/device.py:65 +#: openpilot/selfdrive/ui/layouts/settings/device.py:63 #, python-format msgid "Review Training Guide" msgstr "查看训练指南" -#: selfdrive/ui/layouts/settings/device.py:27 +#: openpilot/selfdrive/ui/layouts/settings/device.py:26 msgid "Review the rules, features, and limitations of openpilot" msgstr "查看 openpilot 的规则、功能与限制" -#: selfdrive/ui/layouts/settings/software.py:61 +#: openpilot/selfdrive/ui/layouts/settings/software.py:68 #, python-format msgid "SELECT" msgstr "选择" -#: selfdrive/ui/layouts/settings/developer.py:53 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:53 #, python-format msgid "SSH Keys" msgstr "SSH 密钥" -#: system/ui/widgets/network.py:310 +#: system/ui/widgets/network.py:316 #, python-format msgid "Scanning Wi-Fi networks..." msgstr "正在扫描 Wi‑Fi 网络…" -#: system/ui/widgets/option_dialog.py:36 +#: system/ui/widgets/option_dialog.py:37 #, python-format msgid "Select" msgstr "选择" -#: selfdrive/ui/layouts/settings/software.py:183 +#: openpilot/selfdrive/ui/layouts/settings/software.py:203 #, python-format msgid "Select a branch" msgstr "选择分支" -#: selfdrive/ui/layouts/settings/device.py:91 +#: openpilot/selfdrive/ui/layouts/settings/device.py:89 #, python-format msgid "Select a language" msgstr "选择语言" -#: selfdrive/ui/layouts/settings/device.py:60 +#: openpilot/selfdrive/ui/layouts/settings/device.py:58 #, python-format msgid "Serial" msgstr "序列号" -#: selfdrive/ui/widgets/offroad_alerts.py:106 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:106 #, python-format msgid "Snooze Update" msgstr "延后更新" -#: selfdrive/ui/layouts/settings/settings.py:65 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:62 msgid "Software" msgstr "软件" -#: selfdrive/ui/layouts/settings/toggles.py:98 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:98 #, python-format msgid "Standard" msgstr "标准" -#: selfdrive/ui/layouts/settings/toggles.py:22 -msgid "" -"Standard is recommended. In aggressive mode, openpilot will follow lead cars " -"closer and be more aggressive with the gas and brake. In relaxed mode " -"openpilot will stay further away from lead cars. On supported cars, you can " -"cycle through these personalities with your steering wheel distance button." -msgstr "" -"建议使用标准模式。激进模式下,openpilot 会更贴近前车,油门与刹车更为激进;从" -"容模式下,会与前车保持更远距离。在支持的车型上,可用方向盘距离按钮切换这些风" -"格。" - -#: selfdrive/ui/onroad/alert_renderer.py:59 -#: selfdrive/ui/onroad/alert_renderer.py:65 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:59 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:65 #, python-format msgid "System Unresponsive" msgstr "系统无响应" -#: selfdrive/ui/onroad/alert_renderer.py:58 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:58 #, python-format msgid "TAKE CONTROL IMMEDIATELY" msgstr "请立即接管控制" -#: selfdrive/ui/layouts/sidebar.py:71 selfdrive/ui/layouts/sidebar.py:125 -#: selfdrive/ui/layouts/sidebar.py:127 selfdrive/ui/layouts/sidebar.py:129 +#: openpilot/selfdrive/ui/layouts/sidebar.py:71 +#: openpilot/selfdrive/ui/layouts/sidebar.py:125 +#: openpilot/selfdrive/ui/layouts/sidebar.py:127 +#: openpilot/selfdrive/ui/layouts/sidebar.py:129 msgid "TEMP" msgstr "温度" -#: selfdrive/ui/layouts/settings/software.py:61 +#: openpilot/selfdrive/ui/layouts/settings/software.py:68 #, python-format msgid "Target Branch" msgstr "目标分支" -#: system/ui/widgets/network.py:124 +#: system/ui/widgets/network.py:121 #, python-format msgid "Tethering Password" msgstr "网络共享密码" -#: selfdrive/ui/layouts/settings/settings.py:64 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:61 msgid "Toggles" msgstr "切换" -#: selfdrive/ui/layouts/settings/software.py:72 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:79 +#, python-format +msgid "UI Debug Mode" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/software.py:79 #, python-format msgid "UNINSTALL" msgstr "卸载" -#: selfdrive/ui/layouts/home.py:155 +#: openpilot/selfdrive/ui/layouts/home.py:155 #, python-format msgid "UPDATE" msgstr "更新" -#: selfdrive/ui/layouts/settings/software.py:72 -#: selfdrive/ui/layouts/settings/software.py:163 +#: openpilot/selfdrive/ui/layouts/settings/software.py:173 +#: openpilot/selfdrive/ui/layouts/settings/software.py:79 #, python-format msgid "Uninstall" msgstr "卸载" -#: selfdrive/ui/layouts/sidebar.py:117 +#: openpilot/selfdrive/ui/layouts/sidebar.py:117 msgid "Unknown" msgstr "未知" -#: selfdrive/ui/layouts/settings/software.py:48 +#: openpilot/selfdrive/ui/layouts/settings/software.py:55 #, python-format msgid "Updates are only downloaded while the car is off." msgstr "仅在车辆熄火时下载更新。" -#: selfdrive/ui/widgets/prime.py:33 +#: openpilot/selfdrive/ui/widgets/prime.py:33 #, python-format msgid "Upgrade Now" msgstr "立即升级" -#: selfdrive/ui/layouts/settings/toggles.py:31 -msgid "" -"Upload data from the driver facing camera and help improve the driver " -"monitoring algorithm." +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:31 +msgid "Upload data from the driver facing camera and help improve the driver monitoring algorithm." msgstr "上传车内摄像头数据,帮助改进驾驶员监控算法。" -#: selfdrive/ui/layouts/settings/toggles.py:88 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:88 #, python-format msgid "Use Metric System" msgstr "使用公制" -#: selfdrive/ui/layouts/settings/toggles.py:17 -msgid "" -"Use the openpilot system for adaptive cruise control and lane keep driver " -"assistance. Your attention is required at all times to use this feature." -msgstr "" -"使用 openpilot 进行自适应巡航与车道保持辅助。使用此功能时,您必须始终保持专" -"注。" - -#: selfdrive/ui/layouts/sidebar.py:72 selfdrive/ui/layouts/sidebar.py:144 +#: openpilot/selfdrive/ui/layouts/sidebar.py:72 +#: openpilot/selfdrive/ui/layouts/sidebar.py:144 msgid "VEHICLE" msgstr "车辆" -#: selfdrive/ui/layouts/settings/device.py:67 +#: openpilot/selfdrive/ui/layouts/settings/device.py:65 #, python-format msgid "VIEW" msgstr "查看" -#: selfdrive/ui/onroad/alert_renderer.py:52 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:52 #, python-format msgid "Waiting to start" msgstr "等待开始" -#: selfdrive/ui/layouts/settings/developer.py:19 -msgid "" -"Warning: This grants SSH access to all public keys in your GitHub settings. " -"Never enter a GitHub username other than your own. A comma employee will " -"NEVER ask you to add their GitHub username." -msgstr "" -"警告:这将授予对您 GitHub 设置中所有公钥的 SSH 访问权限。请勿输入非您本人的 " -"GitHub 用户名。comma 员工绝不会要求您添加他们的用户名。" - -#: selfdrive/ui/layouts/onboarding.py:111 +#: openpilot/selfdrive/ui/layouts/onboarding.py:115 #, python-format msgid "Welcome to openpilot" msgstr "欢迎使用 openpilot" -#: selfdrive/ui/layouts/settings/toggles.py:20 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:20 msgid "When enabled, pressing the accelerator pedal will disengage openpilot." msgstr "启用后,踩下加速踏板将会脱离 openpilot。" -#: selfdrive/ui/layouts/sidebar.py:44 +#: openpilot/selfdrive/ui/layouts/sidebar.py:44 msgid "Wi-Fi" msgstr "Wi‑Fi" -#: system/ui/widgets/network.py:144 +#: system/ui/widgets/network.py:141 #, python-format msgid "Wi-Fi Network Metered" msgstr "Wi‑Fi 计量网络" -#: system/ui/widgets/network.py:314 +#: system/ui/widgets/network.py:320 #, python-format msgid "Wrong password" msgstr "密码错误" -#: selfdrive/ui/layouts/onboarding.py:145 +#: openpilot/selfdrive/ui/layouts/onboarding.py:149 #, python-format msgid "You must accept the Terms and Conditions in order to use openpilot." msgstr "您必须接受条款与条件才能使用 openpilot。" -#: selfdrive/ui/layouts/onboarding.py:112 +#: openpilot/selfdrive/ui/layouts/onboarding.py:116 #, python-format -msgid "" -"You must accept the Terms and Conditions to use openpilot. Read the latest " -"terms at https://comma.ai/terms before continuing." -msgstr "" -"您必须接受条款与条件才能使用 openpilot。继续前请阅读 https://comma.ai/terms " -"上的最新条款。" +msgid "You must accept the Terms and Conditions to use openpilot. Read the latest terms at https://comma.ai/terms before continuing." +msgstr "您必须接受条款与条件才能使用 openpilot。继续前请阅读 https://comma.ai/terms 上的最新条款。" -#: selfdrive/ui/onroad/driver_camera_dialog.py:34 +#: openpilot/selfdrive/ui/onroad/driver_camera_dialog.py:38 #, python-format msgid "camera starting" msgstr "相机启动中" -#: selfdrive/ui/widgets/prime.py:63 +#: openpilot/selfdrive/ui/layouts/settings/software.py:19 +#, python-format +msgid "checking..." +msgstr "" + +#: openpilot/selfdrive/ui/widgets/prime.py:63 #, python-format msgid "comma prime" msgstr "comma prime" -#: system/ui/widgets/network.py:142 +#: system/ui/widgets/network.py:139 #, python-format msgid "default" msgstr "默认" -#: selfdrive/ui/layouts/settings/device.py:133 +#: openpilot/selfdrive/ui/layouts/settings/device.py:125 #, python-format msgid "down" msgstr "下" -#: selfdrive/ui/layouts/settings/software.py:106 +#: openpilot/selfdrive/ui/layouts/settings/software.py:20 +#, python-format +msgid "downloading..." +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/software.py:116 #, python-format msgid "failed to check for update" msgstr "检查更新失败" -#: system/ui/widgets/network.py:237 system/ui/widgets/network.py:314 +#: openpilot/selfdrive/ui/layouts/settings/software.py:21 +#, python-format +msgid "finalizing update..." +msgstr "" + +#: system/ui/widgets/network.py:238 +#: system/ui/widgets/network.py:321 #, python-format msgid "for \"{}\"" msgstr "用于“{}”" -#: selfdrive/ui/onroad/hud_renderer.py:177 +#: openpilot/selfdrive/ui/onroad/hud_renderer.py:177 #, python-format msgid "km/h" msgstr "km/h" -#: system/ui/widgets/network.py:204 +#: system/ui/widgets/network.py:201 #, python-format msgid "leave blank for automatic configuration" msgstr "留空以自动配置" -#: selfdrive/ui/layouts/settings/device.py:134 +#: openpilot/selfdrive/ui/layouts/settings/device.py:126 #, python-format msgid "left" msgstr "左" -#: system/ui/widgets/network.py:142 +#: system/ui/widgets/network.py:139 #, python-format msgid "metered" msgstr "计量" -#: selfdrive/ui/onroad/hud_renderer.py:177 +#: openpilot/selfdrive/ui/onroad/hud_renderer.py:177 #, python-format msgid "mph" msgstr "mph" -#: selfdrive/ui/layouts/settings/software.py:20 +#: openpilot/selfdrive/ui/layouts/settings/software.py:27 #, python-format msgid "never" msgstr "从不" -#: selfdrive/ui/layouts/settings/software.py:31 +#: openpilot/selfdrive/ui/layouts/settings/software.py:38 #, python-format msgid "now" msgstr "现在" -#: selfdrive/ui/layouts/settings/developer.py:71 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:71 #, python-format msgid "openpilot Longitudinal Control (Alpha)" msgstr "openpilot 纵向控制(Alpha)" -#: selfdrive/ui/onroad/alert_renderer.py:51 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:51 #, python-format msgid "openpilot Unavailable" msgstr "openpilot 无法使用" -#: selfdrive/ui/layouts/settings/toggles.py:158 -#, python-format -msgid "" -"openpilot defaults to driving in chill mode. Experimental mode enables alpha-" -"level features that aren't ready for chill mode. Experimental features are " -"listed below:

End-to-End Longitudinal Control


Let the driving " -"model control the gas and brakes. openpilot will drive as it thinks a human " -"would, including stopping for red lights and stop signs. Since the driving " -"model decides the speed to drive, the set speed will only act as an upper " -"bound. This is an alpha quality feature; mistakes should be expected." -"

New Driving Visualization


The driving visualization will " -"transition to the road-facing wide-angle camera at low speeds to better show " -"some turns. The Experimental mode logo will also be shown in the top right " -"corner." -msgstr "" -"openpilot 默认以安稳模式行驶。实验模式会启用尚未准备好用于安稳模式的 Alpha 级" -"功能。实验功能如下:

端到端纵向控制


让驾驶模型控制油门与刹车。" -"openpilot 会像人类一样驾驶,包括在红灯与停牌前停车。由于驾驶模型决定行驶速" -"度,设定速度仅作为上限。这是 Alpha 质量功能;预期会有错误。

全新驾驶可" -"视化


在低速时,驾驶可视化将切换至面向道路的广角摄像头以更好显示部分转" -"弯。右上角也会显示实验模式图标。" - -#: selfdrive/ui/layouts/settings/device.py:165 -#, python-format -msgid "" -"openpilot is continuously calibrating, resetting is rarely required. " -"Resetting calibration will restart openpilot if the car is powered on." -msgstr "" -"openpilot 持续进行校准,通常无需重置。若车辆通电,重置校准将会重启 " -"openpilot。" - -#: selfdrive/ui/layouts/settings/firehose.py:20 -msgid "" -"openpilot learns to drive by watching humans, like you, drive.\n" -"\n" -"Firehose Mode allows you to maximize your training data uploads to improve " -"openpilot's driving models. More data means bigger models, which means " -"better Experimental Mode." -msgstr "" -"openpilot 通过观察人类(例如您)的驾驶来学习。\n" -"\n" -"Firehose 模式可让您最大化上传训练数据,以改进 openpilot 的驾驶模型。更多数据" -"意味着更大的模型,也意味着更好的实验模式。" - -#: selfdrive/ui/layouts/settings/toggles.py:183 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:184 #, python-format msgid "openpilot longitudinal control may come in a future update." msgstr "openpilot 纵向控制可能会在未来更新中提供。" -#: selfdrive/ui/layouts/settings/device.py:26 -msgid "" -"openpilot requires the device to be mounted within 4° left or right and " -"within 5° up or 9° down." +#: openpilot/selfdrive/ui/layouts/settings/device.py:25 +msgid "openpilot requires the device to be mounted within 4° left or right and within 5° up or 9° down." msgstr "openpilot 要求设备安装在左右 4°、上 5° 或下 9° 以内。" -#: selfdrive/ui/layouts/settings/device.py:134 +#: openpilot/selfdrive/ui/layouts/settings/device.py:126 #, python-format msgid "right" msgstr "右" -#: system/ui/widgets/network.py:142 +#: system/ui/widgets/network.py:139 #, python-format msgid "unmetered" msgstr "不限流量" -#: selfdrive/ui/layouts/settings/device.py:133 +#: openpilot/selfdrive/ui/layouts/settings/device.py:125 #, python-format msgid "up" msgstr "上" -#: selfdrive/ui/layouts/settings/software.py:117 +#: openpilot/selfdrive/ui/layouts/settings/software.py:127 #, python-format msgid "up to date, last checked never" msgstr "已是最新,最后检查:从未" -#: selfdrive/ui/layouts/settings/software.py:115 +#: openpilot/selfdrive/ui/layouts/settings/software.py:125 #, python-format msgid "up to date, last checked {}" msgstr "已是最新,最后检查:{}" -#: selfdrive/ui/layouts/settings/software.py:109 +#: openpilot/selfdrive/ui/layouts/settings/software.py:119 #, python-format msgid "update available" msgstr "有可用更新" -#: selfdrive/ui/layouts/home.py:169 +#: openpilot/selfdrive/ui/layouts/home.py:169 #, python-format msgid "{} ALERT" msgid_plural "{} ALERTS" msgstr[0] "{} 条警报" msgstr[1] "{} 条警报" -#: selfdrive/ui/layouts/settings/software.py:40 +#: openpilot/selfdrive/ui/layouts/settings/software.py:47 #, python-format msgid "{} day ago" msgid_plural "{} days ago" msgstr[0] "{} 天前" msgstr[1] "{} 天前" -#: selfdrive/ui/layouts/settings/software.py:37 +#: openpilot/selfdrive/ui/layouts/settings/software.py:44 #, python-format msgid "{} hour ago" msgid_plural "{} hours ago" msgstr[0] "{} 小时前" msgstr[1] "{} 小时前" -#: selfdrive/ui/layouts/settings/software.py:34 +#: openpilot/selfdrive/ui/layouts/settings/software.py:41 #, python-format msgid "{} minute ago" msgid_plural "{} minutes ago" msgstr[0] "{} 分钟前" msgstr[1] "{} 分钟前" -#: selfdrive/ui/layouts/settings/firehose.py:111 +#: openpilot/selfdrive/ui/layouts/settings/firehose.py:70 #, python-format msgid "{} segment of your driving is in the training dataset so far." msgid_plural "{} segments of your driving is in the training dataset so far." msgstr[0] "目前已有 {} 个您的驾驶片段被纳入训练数据集。" msgstr[1] "目前已有 {} 个您的驾驶片段被纳入训练数据集。" -#: selfdrive/ui/widgets/prime.py:62 +#: openpilot/selfdrive/ui/widgets/prime.py:62 #, python-format msgid "✓ SUBSCRIBED" msgstr "✓ 已订阅" -#: selfdrive/ui/widgets/setup.py:22 +#: openpilot/selfdrive/ui/widgets/setup.py:21 #, python-format msgid "🔥 Firehose Mode 🔥" msgstr "🔥 Firehose 模式 🔥" + diff --git a/selfdrive/ui/translations/app_zh-CHT.po b/selfdrive/ui/translations/app_zh-CHT.po index f4d5e0a4ed..1c2fd06563 100644 --- a/selfdrive/ui/translations/app_zh-CHT.po +++ b/selfdrive/ui/translations/app_zh-CHT.po @@ -17,1157 +17,1018 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=1; plural=0;\n" -#: selfdrive/ui/layouts/settings/device.py:160 +#: openpilot/selfdrive/ui/layouts/settings/device.py:152 #, python-format msgid " Steering torque response calibration is complete." msgstr " 轉向扭矩回應校正完成。" -#: selfdrive/ui/layouts/settings/device.py:158 +#: openpilot/selfdrive/ui/layouts/settings/device.py:150 #, python-format msgid " Steering torque response calibration is {}% complete." msgstr " 轉向扭矩回應校正已完成 {}%。" -#: selfdrive/ui/layouts/settings/device.py:133 +#: openpilot/selfdrive/ui/layouts/settings/device.py:125 #, python-format msgid " Your device is pointed {:.1f}° {} and {:.1f}° {}." msgstr " 您的裝置朝向 {:.1f}° {} 與 {:.1f}° {}。" -#: selfdrive/ui/layouts/sidebar.py:43 +#: openpilot/selfdrive/ui/layouts/sidebar.py:43 msgid "--" msgstr "--" -#: selfdrive/ui/widgets/prime.py:47 +#: openpilot/selfdrive/ui/widgets/prime.py:47 #, python-format msgid "1 year of drive storage" msgstr "1 年行駛資料儲存" -#: selfdrive/ui/widgets/prime.py:47 +#: openpilot/selfdrive/ui/widgets/prime.py:47 #, python-format msgid "24/7 LTE connectivity" msgstr "全年無休 LTE 連線" -#: selfdrive/ui/layouts/sidebar.py:46 +#: openpilot/selfdrive/ui/layouts/sidebar.py:46 msgid "2G" msgstr "2G" -#: selfdrive/ui/layouts/sidebar.py:47 +#: openpilot/selfdrive/ui/layouts/sidebar.py:47 msgid "3G" msgstr "3G" -#: selfdrive/ui/layouts/sidebar.py:49 +#: openpilot/selfdrive/ui/layouts/sidebar.py:49 msgid "5G" msgstr "5G" -#: selfdrive/ui/layouts/settings/developer.py:23 -msgid "" -"WARNING: openpilot longitudinal control is in alpha for this car and will " -"disable Automatic Emergency Braking (AEB).

On this car, openpilot " -"defaults to the car's built-in ACC instead of openpilot's longitudinal " -"control. Enable this to switch to openpilot longitudinal control. Enabling " -"Experimental mode is recommended when enabling openpilot longitudinal " -"control alpha. Changing this setting will restart openpilot if the car is " -"powered on." -msgstr "" -"警告:此車款的 openpilot 縱向控制仍為 alpha,將會停用自動緊急煞車 (AEB)。" -"

在此車款上,openpilot 預設使用車載 ACC,而非 openpilot 的縱向控" -"制。啟用此選項可切換為 openpilot 縱向控制。建議同時啟用實驗模式。若車輛通電," -"變更此設定將會重新啟動 openpilot。" - -#: selfdrive/ui/layouts/settings/device.py:148 +#: openpilot/selfdrive/ui/layouts/settings/device.py:140 #, python-format msgid "

Steering lag calibration is complete." msgstr "

轉向延遲校正完成。" -#: selfdrive/ui/layouts/settings/device.py:146 +#: openpilot/selfdrive/ui/layouts/settings/device.py:138 #, python-format msgid "

Steering lag calibration is {}% complete." msgstr "

轉向延遲校正已完成 {}%。" -#: selfdrive/ui/layouts/settings/firehose.py:138 -#, python-format -msgid "ACTIVE" -msgstr "啟用" - -#: selfdrive/ui/layouts/settings/developer.py:15 -msgid "" -"ADB (Android Debug Bridge) allows connecting to your device over USB or over " -"the network. See https://docs.comma.ai/how-to/connect-to-comma for more info." -msgstr "" -"ADB (Android Debug Bridge) 可透過 USB 或網路連線至您的裝置。詳見 https://" -"docs.comma.ai/how-to/connect-to-comma。" - -#: selfdrive/ui/widgets/ssh_key.py:30 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:30 msgid "ADD" msgstr "新增" -#: system/ui/widgets/network.py:139 +#: system/ui/widgets/network.py:136 #, python-format msgid "APN Setting" msgstr "APN 設定" -#: selfdrive/ui/widgets/offroad_alerts.py:109 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:109 #, python-format msgid "Acknowledge Excessive Actuation" msgstr "確認過度作動" -#: system/ui/widgets/network.py:74 system/ui/widgets/network.py:95 +#: system/ui/widgets/network.py:92 +#: system/ui/widgets/network.py:74 #, python-format msgid "Advanced" msgstr "進階" -#: selfdrive/ui/layouts/settings/toggles.py:98 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:98 #, python-format msgid "Aggressive" msgstr "積極" -#: selfdrive/ui/layouts/onboarding.py:116 +#: openpilot/selfdrive/ui/layouts/onboarding.py:120 #, python-format msgid "Agree" msgstr "同意" -#: selfdrive/ui/layouts/settings/toggles.py:70 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:70 #, python-format msgid "Always-On Driver Monitoring" msgstr "持續啟用駕駛監控" -#: selfdrive/ui/layouts/settings/toggles.py:186 -#, python-format -msgid "" -"An alpha version of openpilot longitudinal control can be tested, along with " -"Experimental mode, on non-release branches." -msgstr "openpilot 縱向控制的 alpha 版本可於非發行分支搭配實驗模式進行測試。" - -#: selfdrive/ui/layouts/settings/device.py:187 +#: openpilot/selfdrive/ui/layouts/settings/device.py:183 #, python-format msgid "Are you sure you want to power off?" msgstr "確定要關機嗎?" -#: selfdrive/ui/layouts/settings/device.py:175 +#: openpilot/selfdrive/ui/layouts/settings/device.py:171 #, python-format msgid "Are you sure you want to reboot?" msgstr "確定要重新啟動嗎?" -#: selfdrive/ui/layouts/settings/device.py:119 +#: openpilot/selfdrive/ui/layouts/settings/device.py:111 #, python-format msgid "Are you sure you want to reset calibration?" msgstr "確定要重設校正嗎?" -#: selfdrive/ui/layouts/settings/software.py:163 +#: openpilot/selfdrive/ui/layouts/settings/software.py:173 #, python-format msgid "Are you sure you want to uninstall?" msgstr "確定要解除安裝嗎?" -#: system/ui/widgets/network.py:99 selfdrive/ui/layouts/onboarding.py:147 +#: system/ui/widgets/network.py:96 +#: openpilot/selfdrive/ui/layouts/onboarding.py:151 #, python-format msgid "Back" msgstr "返回" -#: selfdrive/ui/widgets/prime.py:38 +#: openpilot/selfdrive/ui/widgets/prime.py:38 #, python-format msgid "Become a comma prime member at connect.comma.ai" msgstr "前往 connect.comma.ai 成為 comma prime 會員" -#: selfdrive/ui/widgets/pairing_dialog.py:130 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:119 #, python-format msgid "Bookmark connect.comma.ai to your home screen to use it like an app" msgstr "將 connect.comma.ai 加到主畫面,像 App 一樣使用" -#: selfdrive/ui/layouts/settings/device.py:68 +#: openpilot/selfdrive/ui/layouts/settings/device.py:66 #, python-format msgid "CHANGE" msgstr "變更" -#: selfdrive/ui/layouts/settings/software.py:50 -#: selfdrive/ui/layouts/settings/software.py:107 -#: selfdrive/ui/layouts/settings/software.py:118 -#: selfdrive/ui/layouts/settings/software.py:147 +#: openpilot/selfdrive/ui/layouts/settings/software.py:157 +#: openpilot/selfdrive/ui/layouts/settings/software.py:57 +#: openpilot/selfdrive/ui/layouts/settings/software.py:117 +#: openpilot/selfdrive/ui/layouts/settings/software.py:128 #, python-format msgid "CHECK" msgstr "檢查" -#: selfdrive/ui/widgets/exp_mode_button.py:50 +#: openpilot/selfdrive/ui/widgets/exp_mode_button.py:51 #, python-format msgid "CHILL MODE ON" msgstr "安穩模式已開啟" -#: system/ui/widgets/network.py:155 selfdrive/ui/layouts/sidebar.py:73 -#: selfdrive/ui/layouts/sidebar.py:134 selfdrive/ui/layouts/sidebar.py:136 -#: selfdrive/ui/layouts/sidebar.py:138 +#: system/ui/widgets/network.py:152 +#: openpilot/selfdrive/ui/layouts/sidebar.py:73 +#: openpilot/selfdrive/ui/layouts/sidebar.py:134 +#: openpilot/selfdrive/ui/layouts/sidebar.py:136 +#: openpilot/selfdrive/ui/layouts/sidebar.py:138 #, python-format msgid "CONNECT" msgstr "CONNECT" -#: system/ui/widgets/network.py:369 +#: system/ui/widgets/network.py:376 #, python-format msgid "CONNECTING..." msgstr "連線中..." -#: system/ui/widgets/confirm_dialog.py:23 system/ui/widgets/option_dialog.py:35 -#: system/ui/widgets/keyboard.py:81 system/ui/widgets/network.py:318 +#: system/ui/widgets/network.py:326 +#: system/ui/widgets/confirm_dialog.py:24 +#: system/ui/widgets/option_dialog.py:36 +#: system/ui/widgets/keyboard.py:83 #, python-format msgid "Cancel" msgstr "取消" -#: system/ui/widgets/network.py:134 +#: system/ui/widgets/network.py:131 #, python-format msgid "Cellular Metered" msgstr "行動網路計量" -#: selfdrive/ui/layouts/settings/device.py:68 +#: openpilot/selfdrive/ui/layouts/settings/device.py:66 #, python-format msgid "Change Language" msgstr "變更語言" -#: selfdrive/ui/layouts/settings/toggles.py:125 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:125 #, python-format msgid "Changing this setting will restart openpilot if the car is powered on." msgstr "若車輛通電,變更此設定將重新啟動 openpilot。" -#: selfdrive/ui/widgets/pairing_dialog.py:129 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:118 #, python-format msgid "Click \"add new device\" and scan the QR code on the right" msgstr "點選「新增裝置」,掃描右側 QR 碼" -#: selfdrive/ui/widgets/offroad_alerts.py:104 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:104 #, python-format msgid "Close" msgstr "關閉" -#: selfdrive/ui/layouts/settings/software.py:49 +#: openpilot/selfdrive/ui/layouts/settings/software.py:56 #, python-format msgid "Current Version" msgstr "目前版本" -#: selfdrive/ui/layouts/settings/software.py:110 +#: openpilot/selfdrive/ui/layouts/settings/software.py:120 #, python-format msgid "DOWNLOAD" msgstr "下載" -#: selfdrive/ui/layouts/onboarding.py:115 +#: openpilot/selfdrive/ui/layouts/onboarding.py:119 #, python-format msgid "Decline" msgstr "拒絕" -#: selfdrive/ui/layouts/onboarding.py:148 +#: openpilot/selfdrive/ui/layouts/onboarding.py:152 #, python-format msgid "Decline, uninstall openpilot" msgstr "拒絕並解除安裝 openpilot" -#: selfdrive/ui/layouts/settings/settings.py:67 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:64 msgid "Developer" msgstr "開發人員" -#: selfdrive/ui/layouts/settings/settings.py:62 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:59 msgid "Device" msgstr "裝置" -#: selfdrive/ui/layouts/settings/toggles.py:58 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:58 #, python-format msgid "Disengage on Accelerator Pedal" msgstr "踩下加速踏板時脫離" -#: selfdrive/ui/layouts/settings/device.py:184 +#: openpilot/selfdrive/ui/layouts/settings/device.py:176 #, python-format msgid "Disengage to Power Off" msgstr "脫離以關機" -#: selfdrive/ui/layouts/settings/device.py:172 +#: openpilot/selfdrive/ui/layouts/settings/device.py:164 #, python-format msgid "Disengage to Reboot" msgstr "脫離以重新啟動" -#: selfdrive/ui/layouts/settings/device.py:103 +#: openpilot/selfdrive/ui/layouts/settings/device.py:95 #, python-format msgid "Disengage to Reset Calibration" msgstr "脫離以重設校正" -#: selfdrive/ui/layouts/settings/toggles.py:32 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:32 msgid "Display speed in km/h instead of mph." msgstr "以 km/h 顯示速度(非 mph)。" -#: selfdrive/ui/layouts/settings/device.py:59 +#: openpilot/selfdrive/ui/layouts/settings/device.py:57 #, python-format msgid "Dongle ID" msgstr "Dongle ID" -#: selfdrive/ui/layouts/settings/software.py:50 +#: openpilot/selfdrive/ui/layouts/settings/software.py:57 #, python-format msgid "Download" msgstr "下載" -#: selfdrive/ui/layouts/settings/device.py:62 +#: openpilot/selfdrive/ui/layouts/settings/device.py:60 #, python-format msgid "Driver Camera" msgstr "車內鏡頭" -#: selfdrive/ui/layouts/settings/toggles.py:96 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:96 #, python-format msgid "Driving Personality" msgstr "駕駛風格" -#: system/ui/widgets/network.py:123 system/ui/widgets/network.py:139 +#: system/ui/widgets/network.py:120 +#: system/ui/widgets/network.py:136 #, python-format msgid "EDIT" msgstr "編輯" -#: selfdrive/ui/layouts/sidebar.py:138 +#: openpilot/selfdrive/ui/layouts/sidebar.py:138 msgid "ERROR" msgstr "錯誤" -#: selfdrive/ui/layouts/sidebar.py:45 +#: openpilot/selfdrive/ui/layouts/sidebar.py:45 msgid "ETH" msgstr "ETH" -#: selfdrive/ui/widgets/exp_mode_button.py:50 +#: openpilot/selfdrive/ui/widgets/exp_mode_button.py:51 #, python-format msgid "EXPERIMENTAL MODE ON" msgstr "實驗模式已開啟" -#: selfdrive/ui/layouts/settings/developer.py:166 -#: selfdrive/ui/layouts/settings/toggles.py:228 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:229 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:180 #, python-format msgid "Enable" msgstr "啟用" -#: selfdrive/ui/layouts/settings/developer.py:39 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:39 #, python-format msgid "Enable ADB" msgstr "啟用 ADB" -#: selfdrive/ui/layouts/settings/toggles.py:64 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:64 #, python-format msgid "Enable Lane Departure Warnings" msgstr "啟用偏離車道警示" -#: system/ui/widgets/network.py:129 +#: system/ui/widgets/network.py:126 #, python-format msgid "Enable Roaming" msgstr "啟用漫遊" -#: selfdrive/ui/layouts/settings/developer.py:48 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:48 #, python-format msgid "Enable SSH" msgstr "啟用 SSH" -#: system/ui/widgets/network.py:120 +#: system/ui/widgets/network.py:117 #, python-format msgid "Enable Tethering" msgstr "啟用網路共享" -#: selfdrive/ui/layouts/settings/toggles.py:30 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:30 msgid "Enable driver monitoring even when openpilot is not engaged." msgstr "即使未啟動 openpilot 亦啟用駕駛監控。" -#: selfdrive/ui/layouts/settings/toggles.py:46 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:46 #, python-format msgid "Enable openpilot" msgstr "啟用 openpilot" -#: selfdrive/ui/layouts/settings/toggles.py:189 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:190 #, python-format -msgid "" -"Enable the openpilot longitudinal control (alpha) toggle to allow " -"Experimental mode." +msgid "Enable the openpilot longitudinal control (alpha) toggle to allow Experimental mode." msgstr "啟用 openpilot 縱向控制(alpha)切換,以使用實驗模式。" -#: system/ui/widgets/network.py:204 +#: system/ui/widgets/network.py:201 #, python-format msgid "Enter APN" msgstr "輸入 APN" -#: system/ui/widgets/network.py:241 +#: system/ui/widgets/network.py:243 #, python-format msgid "Enter SSID" msgstr "輸入 SSID" -#: system/ui/widgets/network.py:254 +#: system/ui/widgets/network.py:257 #, python-format msgid "Enter new tethering password" msgstr "輸入新的網路共享密碼" -#: system/ui/widgets/network.py:237 system/ui/widgets/network.py:314 +#: system/ui/widgets/network.py:238 +#: system/ui/widgets/network.py:320 #, python-format msgid "Enter password" msgstr "輸入密碼" -#: selfdrive/ui/widgets/ssh_key.py:89 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:89 #, python-format msgid "Enter your GitHub username" msgstr "輸入您的 GitHub 使用者名稱" -#: system/ui/widgets/list_view.py:123 system/ui/widgets/list_view.py:160 +#: system/ui/widgets/list_view.py:123 +#: system/ui/widgets/list_view.py:160 #, python-format msgid "Error" msgstr "錯誤" -#: selfdrive/ui/layouts/settings/toggles.py:52 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:52 #, python-format msgid "Experimental Mode" msgstr "實驗模式" -#: selfdrive/ui/layouts/settings/toggles.py:181 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:182 #, python-format -msgid "" -"Experimental mode is currently unavailable on this car since the car's stock " -"ACC is used for longitudinal control." +msgid "Experimental mode is currently unavailable on this car since the car's stock ACC is used for longitudinal control." msgstr "此車款目前無法使用實驗模式,因為縱向控制使用的是原廠 ACC。" -#: system/ui/widgets/network.py:373 +#: system/ui/widgets/network.py:380 #, python-format msgid "FORGETTING..." msgstr "正在遺忘..." -#: selfdrive/ui/widgets/setup.py:44 +#: openpilot/selfdrive/ui/widgets/setup.py:43 #, python-format msgid "Finish Setup" msgstr "完成設定" -#: selfdrive/ui/layouts/settings/settings.py:66 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:63 msgid "Firehose" msgstr "Firehose" -#: selfdrive/ui/layouts/settings/firehose.py:18 +#: openpilot/selfdrive/ui/layouts/settings/firehose.py:10 msgid "Firehose Mode" msgstr "Firehose 模式" -#: selfdrive/ui/layouts/settings/firehose.py:25 -msgid "" -"For maximum effectiveness, bring your device inside and connect to a good " -"USB-C adapter and Wi-Fi weekly.\n" -"\n" -"Firehose Mode can also work while you're driving if connected to a hotspot " -"or unlimited SIM card.\n" -"\n" -"\n" -"Frequently Asked Questions\n" -"\n" -"Does it matter how or where I drive? Nope, just drive as you normally " -"would.\n" -"\n" -"Do all of my segments get pulled in Firehose Mode? No, we selectively pull a " -"subset of your segments.\n" -"\n" -"What's a good USB-C adapter? Any fast phone or laptop charger should be " -"fine.\n" -"\n" -"Does it matter which software I run? Yes, only upstream openpilot (and " -"particular forks) are able to be used for training." -msgstr "" -"為達最佳效果,請將裝置帶到室內,並每週連接優質 USB‑C 充電器與 Wi‑Fi。\n" -"\n" -"若連上熱點或吃到飽門號,行車中也可使用 Firehose 模式。\n" -"\n" -"\n" -"常見問題\n" -"\n" -"我怎麼開、在哪裡開有差嗎?沒有,平常怎麼開就怎麼開。\n" -"\n" -"Firehose 模式會拉取我所有片段嗎?不會,我們會選擇性拉取部分片段。\n" -"\n" -"什麼是好的 USB‑C 充電器?任何快速的手機或筆電充電器都可以。\n" -"\n" -"我跑什麼軟體有差嗎?有,只有上游 openpilot(及特定分支)可用於訓練。" - -#: system/ui/widgets/network.py:318 system/ui/widgets/network.py:451 +#: system/ui/widgets/network.py:458 +#: system/ui/widgets/network.py:326 #, python-format msgid "Forget" msgstr "忘記" -#: system/ui/widgets/network.py:319 +#: system/ui/widgets/network.py:327 #, python-format msgid "Forget Wi-Fi Network \"{}\"?" msgstr "要忘記 Wi‑Fi 網路「{}」嗎?" -#: selfdrive/ui/layouts/sidebar.py:71 selfdrive/ui/layouts/sidebar.py:125 +#: openpilot/selfdrive/ui/layouts/sidebar.py:71 +#: openpilot/selfdrive/ui/layouts/sidebar.py:125 msgid "GOOD" msgstr "良好" -#: selfdrive/ui/widgets/pairing_dialog.py:128 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:117 #, python-format msgid "Go to https://connect.comma.ai on your phone" msgstr "在手機上前往 https://connect.comma.ai" -#: selfdrive/ui/layouts/sidebar.py:129 +#: openpilot/selfdrive/ui/layouts/sidebar.py:129 msgid "HIGH" msgstr "高" -#: system/ui/widgets/network.py:155 +#: system/ui/widgets/network.py:152 #, python-format msgid "Hidden Network" msgstr "隱藏網路" -#: selfdrive/ui/layouts/settings/firehose.py:140 -#, python-format -msgid "INACTIVE: connect to an unmetered network" -msgstr "未啟用:請連接不限流量網路" - -#: selfdrive/ui/layouts/settings/software.py:53 -#: selfdrive/ui/layouts/settings/software.py:136 +#: openpilot/selfdrive/ui/layouts/settings/software.py:60 +#: openpilot/selfdrive/ui/layouts/settings/software.py:146 #, python-format msgid "INSTALL" msgstr "安裝" -#: system/ui/widgets/network.py:150 +#: system/ui/widgets/network.py:147 #, python-format msgid "IP Address" msgstr "IP 位址" -#: selfdrive/ui/layouts/settings/software.py:53 +#: openpilot/selfdrive/ui/layouts/settings/software.py:60 #, python-format msgid "Install Update" msgstr "安裝更新" -#: selfdrive/ui/layouts/settings/developer.py:56 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:56 #, python-format msgid "Joystick Debug Mode" msgstr "搖桿除錯模式" -#: selfdrive/ui/widgets/ssh_key.py:29 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:29 msgid "LOADING" msgstr "載入中" -#: selfdrive/ui/layouts/sidebar.py:48 +#: openpilot/selfdrive/ui/layouts/sidebar.py:48 msgid "LTE" msgstr "LTE" -#: selfdrive/ui/layouts/settings/developer.py:64 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:64 #, python-format msgid "Longitudinal Maneuver Mode" msgstr "縱向操作模式" -#: selfdrive/ui/onroad/hud_renderer.py:148 +#: openpilot/selfdrive/ui/onroad/hud_renderer.py:148 #, python-format msgid "MAX" msgstr "最大" -#: selfdrive/ui/widgets/setup.py:75 +#: openpilot/selfdrive/ui/widgets/setup.py:74 #, python-format -msgid "" -"Maximize your training data uploads to improve openpilot's driving models." +msgid "Maximize your training data uploads to improve openpilot's driving models." msgstr "最大化上傳訓練資料,以改進 openpilot 的駕駛模型。" -#: selfdrive/ui/layouts/settings/device.py:59 -#: selfdrive/ui/layouts/settings/device.py:60 +#: openpilot/selfdrive/ui/layouts/settings/device.py:57 +#: openpilot/selfdrive/ui/layouts/settings/device.py:58 #, python-format msgid "N/A" msgstr "無" -#: selfdrive/ui/layouts/sidebar.py:142 +#: openpilot/selfdrive/ui/layouts/sidebar.py:142 msgid "NO" msgstr "否" -#: selfdrive/ui/layouts/settings/settings.py:63 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:60 msgid "Network" msgstr "網路" -#: selfdrive/ui/widgets/ssh_key.py:114 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:115 #, python-format msgid "No SSH keys found" msgstr "找不到 SSH 金鑰" -#: selfdrive/ui/widgets/ssh_key.py:126 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:127 #, python-format msgid "No SSH keys found for user '{}'" msgstr "找不到使用者 '{}' 的 SSH 金鑰" -#: selfdrive/ui/widgets/offroad_alerts.py:320 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:321 #, python-format msgid "No release notes available." msgstr "無可用發行說明。" -#: selfdrive/ui/layouts/sidebar.py:73 selfdrive/ui/layouts/sidebar.py:134 +#: openpilot/selfdrive/ui/layouts/sidebar.py:73 +#: openpilot/selfdrive/ui/layouts/sidebar.py:134 msgid "OFFLINE" msgstr "離線" -#: system/ui/widgets/html_render.py:263 system/ui/widgets/confirm_dialog.py:93 -#: selfdrive/ui/layouts/sidebar.py:127 +#: system/ui/widgets/confirm_dialog.py:93 +#: system/ui/widgets/html_render.py:263 +#: openpilot/selfdrive/ui/layouts/sidebar.py:127 #, python-format msgid "OK" msgstr "確定" -#: selfdrive/ui/layouts/sidebar.py:72 selfdrive/ui/layouts/sidebar.py:136 -#: selfdrive/ui/layouts/sidebar.py:144 +#: openpilot/selfdrive/ui/layouts/sidebar.py:72 +#: openpilot/selfdrive/ui/layouts/sidebar.py:144 +#: openpilot/selfdrive/ui/layouts/sidebar.py:136 msgid "ONLINE" msgstr "線上" -#: selfdrive/ui/widgets/setup.py:20 +#: openpilot/selfdrive/ui/widgets/setup.py:19 #, python-format msgid "Open" msgstr "開啟" -#: selfdrive/ui/layouts/settings/device.py:48 +#: openpilot/selfdrive/ui/layouts/settings/device.py:45 #, python-format msgid "PAIR" msgstr "配對" -#: selfdrive/ui/layouts/sidebar.py:142 +#: openpilot/selfdrive/ui/layouts/sidebar.py:142 msgid "PANDA" msgstr "PANDA" -#: selfdrive/ui/layouts/settings/device.py:62 +#: openpilot/selfdrive/ui/layouts/settings/device.py:60 #, python-format msgid "PREVIEW" msgstr "預覽" -#: selfdrive/ui/widgets/prime.py:44 +#: openpilot/selfdrive/ui/widgets/prime.py:44 #, python-format msgid "PRIME FEATURES:" msgstr "PRIME 功能:" -#: selfdrive/ui/layouts/settings/device.py:48 +#: openpilot/selfdrive/ui/layouts/settings/device.py:45 #, python-format msgid "Pair Device" msgstr "配對裝置" -#: selfdrive/ui/widgets/setup.py:19 +#: openpilot/selfdrive/ui/widgets/setup.py:18 #, python-format msgid "Pair device" msgstr "配對裝置" -#: selfdrive/ui/widgets/pairing_dialog.py:103 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:92 #, python-format msgid "Pair your device to your comma account" msgstr "將裝置配對至您的 comma 帳號" -#: selfdrive/ui/widgets/setup.py:48 selfdrive/ui/layouts/settings/device.py:24 +#: openpilot/selfdrive/ui/widgets/setup.py:47 +#: openpilot/selfdrive/ui/layouts/settings/device.py:23 #, python-format -msgid "" -"Pair your device with comma connect (connect.comma.ai) and claim your comma " -"prime offer." -msgstr "" -"將裝置與 comma connect(connect.comma.ai)配對,領取您的 comma prime 優惠。" +msgid "Pair your device with comma connect (connect.comma.ai) and claim your comma prime offer." +msgstr "將裝置與 comma connect(connect.comma.ai)配對,領取您的 comma prime 優惠。" -#: selfdrive/ui/widgets/setup.py:91 +#: openpilot/selfdrive/ui/widgets/setup.py:91 #, python-format msgid "Please connect to Wi-Fi to complete initial pairing" msgstr "請連線至 Wi‑Fi 以完成初始化配對" -#: selfdrive/ui/layouts/settings/device.py:55 -#: selfdrive/ui/layouts/settings/device.py:187 +#: openpilot/selfdrive/ui/layouts/settings/device.py:183 +#: openpilot/selfdrive/ui/layouts/settings/device.py:53 #, python-format msgid "Power Off" msgstr "關機" -#: system/ui/widgets/network.py:144 +#: system/ui/widgets/network.py:141 #, python-format msgid "Prevent large data uploads when on a metered Wi-Fi connection" msgstr "在計量制 Wi‑Fi 連線時避免大量上傳" -#: system/ui/widgets/network.py:135 +#: system/ui/widgets/network.py:132 #, python-format msgid "Prevent large data uploads when on a metered cellular connection" msgstr "在計量制行動網路時避免大量上傳" -#: selfdrive/ui/layouts/settings/device.py:25 -msgid "" -"Preview the driver facing camera to ensure that driver monitoring has good " -"visibility. (vehicle must be off)" +#: openpilot/selfdrive/ui/layouts/settings/device.py:24 +msgid "Preview the driver facing camera to ensure that driver monitoring has good visibility. (vehicle must be off)" msgstr "預覽車內鏡頭以確保駕駛監控視野良好。(車輛須熄火)" -#: selfdrive/ui/widgets/pairing_dialog.py:161 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:150 #, python-format msgid "QR Code Error" msgstr "QR 碼錯誤" -#: selfdrive/ui/widgets/ssh_key.py:31 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:31 msgid "REMOVE" msgstr "移除" -#: selfdrive/ui/layouts/settings/device.py:51 +#: openpilot/selfdrive/ui/layouts/settings/device.py:49 #, python-format msgid "RESET" msgstr "重設" -#: selfdrive/ui/layouts/settings/device.py:65 +#: openpilot/selfdrive/ui/layouts/settings/device.py:63 #, python-format msgid "REVIEW" msgstr "檢視" -#: selfdrive/ui/layouts/settings/device.py:55 -#: selfdrive/ui/layouts/settings/device.py:175 +#: openpilot/selfdrive/ui/layouts/settings/device.py:171 +#: openpilot/selfdrive/ui/layouts/settings/device.py:53 #, python-format msgid "Reboot" msgstr "重新啟動" -#: selfdrive/ui/onroad/alert_renderer.py:66 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:66 #, python-format msgid "Reboot Device" msgstr "重新啟動裝置" -#: selfdrive/ui/widgets/offroad_alerts.py:112 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:112 #, python-format msgid "Reboot and Update" msgstr "重新啟動並更新" -#: selfdrive/ui/layouts/settings/toggles.py:27 -msgid "" -"Receive alerts to steer back into the lane when your vehicle drifts over a " -"detected lane line without a turn signal activated while driving over 31 mph " -"(50 km/h)." -msgstr "" -"當車輛以超過 31 mph(50 km/h)行駛且未打方向燈越過偵測到的車道線時,接收轉向" -"回車道的警示。" - -#: selfdrive/ui/layouts/settings/toggles.py:76 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:76 #, python-format msgid "Record and Upload Driver Camera" msgstr "錄製並上傳車內鏡頭" -#: selfdrive/ui/layouts/settings/toggles.py:82 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:82 #, python-format msgid "Record and Upload Microphone Audio" msgstr "錄製並上傳麥克風音訊" -#: selfdrive/ui/layouts/settings/toggles.py:33 -msgid "" -"Record and store microphone audio while driving. The audio will be included " -"in the dashcam video in comma connect." -msgstr "" -"行車時錄製並儲存麥克風音訊。音訊將包含在 comma connect 的行車紀錄影片中。" +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:33 +msgid "Record and store microphone audio while driving. The audio will be included in the dashcam video in comma connect." +msgstr "行車時錄製並儲存麥克風音訊。音訊將包含在 comma connect 的行車紀錄影片中。" -#: selfdrive/ui/layouts/settings/device.py:67 +#: openpilot/selfdrive/ui/layouts/settings/device.py:65 #, python-format msgid "Regulatory" msgstr "法規" -#: selfdrive/ui/layouts/settings/toggles.py:98 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:98 #, python-format msgid "Relaxed" msgstr "從容" -#: selfdrive/ui/widgets/prime.py:47 +#: openpilot/selfdrive/ui/widgets/prime.py:47 #, python-format msgid "Remote access" msgstr "遠端存取" -#: selfdrive/ui/widgets/prime.py:47 +#: openpilot/selfdrive/ui/widgets/prime.py:47 #, python-format msgid "Remote snapshots" msgstr "遠端擷圖" -#: selfdrive/ui/widgets/ssh_key.py:123 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:124 #, python-format msgid "Request timed out" msgstr "要求逾時" -#: selfdrive/ui/layouts/settings/device.py:119 +#: openpilot/selfdrive/ui/layouts/settings/device.py:111 #, python-format msgid "Reset" msgstr "重設" -#: selfdrive/ui/layouts/settings/device.py:51 +#: openpilot/selfdrive/ui/layouts/settings/device.py:49 #, python-format msgid "Reset Calibration" msgstr "重設校正" -#: selfdrive/ui/layouts/settings/device.py:65 +#: openpilot/selfdrive/ui/layouts/settings/device.py:63 #, python-format msgid "Review Training Guide" msgstr "檢視訓練指南" -#: selfdrive/ui/layouts/settings/device.py:27 +#: openpilot/selfdrive/ui/layouts/settings/device.py:26 msgid "Review the rules, features, and limitations of openpilot" msgstr "檢視 openpilot 的規則、功能與限制" -#: selfdrive/ui/layouts/settings/software.py:61 +#: openpilot/selfdrive/ui/layouts/settings/software.py:68 #, python-format msgid "SELECT" msgstr "選取" -#: selfdrive/ui/layouts/settings/developer.py:53 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:53 #, python-format msgid "SSH Keys" msgstr "SSH 金鑰" -#: system/ui/widgets/network.py:310 +#: system/ui/widgets/network.py:316 #, python-format msgid "Scanning Wi-Fi networks..." msgstr "正在掃描 Wi‑Fi 網路…" -#: system/ui/widgets/option_dialog.py:36 +#: system/ui/widgets/option_dialog.py:37 #, python-format msgid "Select" msgstr "選取" -#: selfdrive/ui/layouts/settings/software.py:183 +#: openpilot/selfdrive/ui/layouts/settings/software.py:203 #, python-format msgid "Select a branch" msgstr "選取分支" -#: selfdrive/ui/layouts/settings/device.py:91 +#: openpilot/selfdrive/ui/layouts/settings/device.py:89 #, python-format msgid "Select a language" msgstr "選取語言" -#: selfdrive/ui/layouts/settings/device.py:60 +#: openpilot/selfdrive/ui/layouts/settings/device.py:58 #, python-format msgid "Serial" msgstr "序號" -#: selfdrive/ui/widgets/offroad_alerts.py:106 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:106 #, python-format msgid "Snooze Update" msgstr "延後更新" -#: selfdrive/ui/layouts/settings/settings.py:65 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:62 msgid "Software" msgstr "軟體" -#: selfdrive/ui/layouts/settings/toggles.py:98 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:98 #, python-format msgid "Standard" msgstr "標準" -#: selfdrive/ui/layouts/settings/toggles.py:22 -msgid "" -"Standard is recommended. In aggressive mode, openpilot will follow lead cars " -"closer and be more aggressive with the gas and brake. In relaxed mode " -"openpilot will stay further away from lead cars. On supported cars, you can " -"cycle through these personalities with your steering wheel distance button." -msgstr "" -"建議使用標準模式。積極模式下,openpilot 會更貼近前車,油門與煞車反應更積極;" -"從容模式下,會與前車保持更遠距離。於支援車款,可用方向盤距離按鈕切換這些風" -"格。" - -#: selfdrive/ui/onroad/alert_renderer.py:59 -#: selfdrive/ui/onroad/alert_renderer.py:65 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:59 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:65 #, python-format msgid "System Unresponsive" msgstr "系統無回應" -#: selfdrive/ui/onroad/alert_renderer.py:58 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:58 #, python-format msgid "TAKE CONTROL IMMEDIATELY" msgstr "請立刻接手控制" -#: selfdrive/ui/layouts/sidebar.py:71 selfdrive/ui/layouts/sidebar.py:125 -#: selfdrive/ui/layouts/sidebar.py:127 selfdrive/ui/layouts/sidebar.py:129 +#: openpilot/selfdrive/ui/layouts/sidebar.py:71 +#: openpilot/selfdrive/ui/layouts/sidebar.py:125 +#: openpilot/selfdrive/ui/layouts/sidebar.py:127 +#: openpilot/selfdrive/ui/layouts/sidebar.py:129 msgid "TEMP" msgstr "溫度" -#: selfdrive/ui/layouts/settings/software.py:61 +#: openpilot/selfdrive/ui/layouts/settings/software.py:68 #, python-format msgid "Target Branch" msgstr "目標分支" -#: system/ui/widgets/network.py:124 +#: system/ui/widgets/network.py:121 #, python-format msgid "Tethering Password" msgstr "網路共享密碼" -#: selfdrive/ui/layouts/settings/settings.py:64 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:61 msgid "Toggles" msgstr "切換" -#: selfdrive/ui/layouts/settings/software.py:72 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:79 +#, python-format +msgid "UI Debug Mode" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/software.py:79 #, python-format msgid "UNINSTALL" msgstr "解除安裝" -#: selfdrive/ui/layouts/home.py:155 +#: openpilot/selfdrive/ui/layouts/home.py:155 #, python-format msgid "UPDATE" msgstr "更新" -#: selfdrive/ui/layouts/settings/software.py:72 -#: selfdrive/ui/layouts/settings/software.py:163 +#: openpilot/selfdrive/ui/layouts/settings/software.py:173 +#: openpilot/selfdrive/ui/layouts/settings/software.py:79 #, python-format msgid "Uninstall" msgstr "解除安裝" -#: selfdrive/ui/layouts/sidebar.py:117 +#: openpilot/selfdrive/ui/layouts/sidebar.py:117 msgid "Unknown" msgstr "未知" -#: selfdrive/ui/layouts/settings/software.py:48 +#: openpilot/selfdrive/ui/layouts/settings/software.py:55 #, python-format msgid "Updates are only downloaded while the car is off." msgstr "僅在車輛熄火時下載更新。" -#: selfdrive/ui/widgets/prime.py:33 +#: openpilot/selfdrive/ui/widgets/prime.py:33 #, python-format msgid "Upgrade Now" msgstr "立即升級" -#: selfdrive/ui/layouts/settings/toggles.py:31 -msgid "" -"Upload data from the driver facing camera and help improve the driver " -"monitoring algorithm." +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:31 +msgid "Upload data from the driver facing camera and help improve the driver monitoring algorithm." msgstr "上傳車內鏡頭資料,協助改善駕駛監控演算法。" -#: selfdrive/ui/layouts/settings/toggles.py:88 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:88 #, python-format msgid "Use Metric System" msgstr "使用公制" -#: selfdrive/ui/layouts/settings/toggles.py:17 -msgid "" -"Use the openpilot system for adaptive cruise control and lane keep driver " -"assistance. Your attention is required at all times to use this feature." -msgstr "" -"使用 openpilot 進行 ACC 與車道維持輔助。使用此功能時,您必須始終保持專注。" - -#: selfdrive/ui/layouts/sidebar.py:72 selfdrive/ui/layouts/sidebar.py:144 +#: openpilot/selfdrive/ui/layouts/sidebar.py:72 +#: openpilot/selfdrive/ui/layouts/sidebar.py:144 msgid "VEHICLE" msgstr "車輛" -#: selfdrive/ui/layouts/settings/device.py:67 +#: openpilot/selfdrive/ui/layouts/settings/device.py:65 #, python-format msgid "VIEW" msgstr "檢視" -#: selfdrive/ui/onroad/alert_renderer.py:52 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:52 #, python-format msgid "Waiting to start" msgstr "等待開始" -#: selfdrive/ui/layouts/settings/developer.py:19 -msgid "" -"Warning: This grants SSH access to all public keys in your GitHub settings. " -"Never enter a GitHub username other than your own. A comma employee will " -"NEVER ask you to add their GitHub username." -msgstr "" -"警告:這將授予對您 GitHub 設定中所有公開金鑰的 SSH 存取權。請勿輸入非您本人" -"的 GitHub 帳號。comma 員工絕不會要求您新增他們的帳號。" - -#: selfdrive/ui/layouts/onboarding.py:111 +#: openpilot/selfdrive/ui/layouts/onboarding.py:115 #, python-format msgid "Welcome to openpilot" msgstr "歡迎使用 openpilot" -#: selfdrive/ui/layouts/settings/toggles.py:20 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:20 msgid "When enabled, pressing the accelerator pedal will disengage openpilot." msgstr "啟用後,踩下加速踏板將會脫離 openpilot。" -#: selfdrive/ui/layouts/sidebar.py:44 +#: openpilot/selfdrive/ui/layouts/sidebar.py:44 msgid "Wi-Fi" msgstr "Wi‑Fi" -#: system/ui/widgets/network.py:144 +#: system/ui/widgets/network.py:141 #, python-format msgid "Wi-Fi Network Metered" msgstr "Wi‑Fi 計量網路" -#: system/ui/widgets/network.py:314 +#: system/ui/widgets/network.py:320 #, python-format msgid "Wrong password" msgstr "密碼錯誤" -#: selfdrive/ui/layouts/onboarding.py:145 +#: openpilot/selfdrive/ui/layouts/onboarding.py:149 #, python-format msgid "You must accept the Terms and Conditions in order to use openpilot." msgstr "您必須接受條款與細則才能使用 openpilot。" -#: selfdrive/ui/layouts/onboarding.py:112 +#: openpilot/selfdrive/ui/layouts/onboarding.py:116 #, python-format -msgid "" -"You must accept the Terms and Conditions to use openpilot. Read the latest " -"terms at https://comma.ai/terms before continuing." -msgstr "" -"您必須接受條款與細則才能使用 openpilot。繼續前請閱讀 https://comma.ai/terms " -"上的最新條款。" +msgid "You must accept the Terms and Conditions to use openpilot. Read the latest terms at https://comma.ai/terms before continuing." +msgstr "您必須接受條款與細則才能使用 openpilot。繼續前請閱讀 https://comma.ai/terms 上的最新條款。" -#: selfdrive/ui/onroad/driver_camera_dialog.py:34 +#: openpilot/selfdrive/ui/onroad/driver_camera_dialog.py:38 #, python-format msgid "camera starting" msgstr "相機啟動中" -#: selfdrive/ui/widgets/prime.py:63 +#: openpilot/selfdrive/ui/layouts/settings/software.py:19 +#, python-format +msgid "checking..." +msgstr "" + +#: openpilot/selfdrive/ui/widgets/prime.py:63 #, python-format msgid "comma prime" msgstr "comma prime" -#: system/ui/widgets/network.py:142 +#: system/ui/widgets/network.py:139 #, python-format msgid "default" msgstr "預設" -#: selfdrive/ui/layouts/settings/device.py:133 +#: openpilot/selfdrive/ui/layouts/settings/device.py:125 #, python-format msgid "down" msgstr "下" -#: selfdrive/ui/layouts/settings/software.py:106 +#: openpilot/selfdrive/ui/layouts/settings/software.py:20 +#, python-format +msgid "downloading..." +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/software.py:116 #, python-format msgid "failed to check for update" msgstr "檢查更新失敗" -#: system/ui/widgets/network.py:237 system/ui/widgets/network.py:314 +#: openpilot/selfdrive/ui/layouts/settings/software.py:21 +#, python-format +msgid "finalizing update..." +msgstr "" + +#: system/ui/widgets/network.py:238 +#: system/ui/widgets/network.py:321 #, python-format msgid "for \"{}\"" msgstr "適用於「{}」" -#: selfdrive/ui/onroad/hud_renderer.py:177 +#: openpilot/selfdrive/ui/onroad/hud_renderer.py:177 #, python-format msgid "km/h" msgstr "km/h" -#: system/ui/widgets/network.py:204 +#: system/ui/widgets/network.py:201 #, python-format msgid "leave blank for automatic configuration" msgstr "留空以自動設定" -#: selfdrive/ui/layouts/settings/device.py:134 +#: openpilot/selfdrive/ui/layouts/settings/device.py:126 #, python-format msgid "left" msgstr "左" -#: system/ui/widgets/network.py:142 +#: system/ui/widgets/network.py:139 #, python-format msgid "metered" msgstr "計量" -#: selfdrive/ui/onroad/hud_renderer.py:177 +#: openpilot/selfdrive/ui/onroad/hud_renderer.py:177 #, python-format msgid "mph" msgstr "mph" -#: selfdrive/ui/layouts/settings/software.py:20 +#: openpilot/selfdrive/ui/layouts/settings/software.py:27 #, python-format msgid "never" msgstr "從不" -#: selfdrive/ui/layouts/settings/software.py:31 +#: openpilot/selfdrive/ui/layouts/settings/software.py:38 #, python-format msgid "now" msgstr "現在" -#: selfdrive/ui/layouts/settings/developer.py:71 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:71 #, python-format msgid "openpilot Longitudinal Control (Alpha)" msgstr "openpilot 縱向控制(Alpha)" -#: selfdrive/ui/onroad/alert_renderer.py:51 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:51 #, python-format msgid "openpilot Unavailable" msgstr "openpilot 無法使用" -#: selfdrive/ui/layouts/settings/toggles.py:158 -#, python-format -msgid "" -"openpilot defaults to driving in chill mode. Experimental mode enables alpha-" -"level features that aren't ready for chill mode. Experimental features are " -"listed below:

End-to-End Longitudinal Control


Let the driving " -"model control the gas and brakes. openpilot will drive as it thinks a human " -"would, including stopping for red lights and stop signs. Since the driving " -"model decides the speed to drive, the set speed will only act as an upper " -"bound. This is an alpha quality feature; mistakes should be expected." -"

New Driving Visualization


The driving visualization will " -"transition to the road-facing wide-angle camera at low speeds to better show " -"some turns. The Experimental mode logo will also be shown in the top right " -"corner." -msgstr "" -"openpilot 預設以安穩模式行駛。實驗模式啟用尚未準備好進入安穩模式的 Alpha 等級" -"功能。實驗功能如下:

端到端縱向控制


讓駕駛模型控制油門與煞車。" -"openpilot 會如同人類駕駛般行駛,包括在紅燈與停車標誌前停車。由於駕駛模型決定" -"行駛速度,設定速度僅作為上限。此為 Alpha 品質功能;預期會有失誤。

全新" -"駕駛視覺化


在低速時,駕駛視覺化將切換至面向道路的廣角鏡頭以更好呈現部" -"分轉彎。右上角亦會顯示實驗模式圖示。" - -#: selfdrive/ui/layouts/settings/device.py:165 -#, python-format -msgid "" -"openpilot is continuously calibrating, resetting is rarely required. " -"Resetting calibration will restart openpilot if the car is powered on." -msgstr "" -"openpilot 會持續校正,通常不需重設。若車輛通電,重設校正將重新啟動 " -"openpilot。" - -#: selfdrive/ui/layouts/settings/firehose.py:20 -msgid "" -"openpilot learns to drive by watching humans, like you, drive.\n" -"\n" -"Firehose Mode allows you to maximize your training data uploads to improve " -"openpilot's driving models. More data means bigger models, which means " -"better Experimental Mode." -msgstr "" -"openpilot 透過觀察人類(也就是您)的駕駛方式來學習。\n" -"\n" -"Firehose 模式可讓您最大化上傳訓練資料,以改進 openpilot 的駕駛模型。更多資料" -"代表更大的模型,也就代表更好的實驗模式。" - -#: selfdrive/ui/layouts/settings/toggles.py:183 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:184 #, python-format msgid "openpilot longitudinal control may come in a future update." msgstr "openpilot 縱向控制可能於未來更新提供。" -#: selfdrive/ui/layouts/settings/device.py:26 -msgid "" -"openpilot requires the device to be mounted within 4° left or right and " -"within 5° up or 9° down." +#: openpilot/selfdrive/ui/layouts/settings/device.py:25 +msgid "openpilot requires the device to be mounted within 4° left or right and within 5° up or 9° down." msgstr "openpilot 要求裝置安裝在左右 4°、上 5° 或下 9° 以內。" -#: selfdrive/ui/layouts/settings/device.py:134 +#: openpilot/selfdrive/ui/layouts/settings/device.py:126 #, python-format msgid "right" msgstr "右" -#: system/ui/widgets/network.py:142 +#: system/ui/widgets/network.py:139 #, python-format msgid "unmetered" msgstr "不限流量" -#: selfdrive/ui/layouts/settings/device.py:133 +#: openpilot/selfdrive/ui/layouts/settings/device.py:125 #, python-format msgid "up" msgstr "上" -#: selfdrive/ui/layouts/settings/software.py:117 +#: openpilot/selfdrive/ui/layouts/settings/software.py:127 #, python-format msgid "up to date, last checked never" msgstr "已為最新,最後檢查:從未" -#: selfdrive/ui/layouts/settings/software.py:115 +#: openpilot/selfdrive/ui/layouts/settings/software.py:125 #, python-format msgid "up to date, last checked {}" msgstr "已為最新,最後檢查:{}" -#: selfdrive/ui/layouts/settings/software.py:109 +#: openpilot/selfdrive/ui/layouts/settings/software.py:119 #, python-format msgid "update available" msgstr "有可用更新" -#: selfdrive/ui/layouts/home.py:169 +#: openpilot/selfdrive/ui/layouts/home.py:169 #, python-format msgid "{} ALERT" msgid_plural "{} ALERTS" msgstr[0] "{} 則警示" msgstr[1] "{} 則警示" -#: selfdrive/ui/layouts/settings/software.py:40 +#: openpilot/selfdrive/ui/layouts/settings/software.py:47 #, python-format msgid "{} day ago" msgid_plural "{} days ago" msgstr[0] "{} 天前" msgstr[1] "{} 天前" -#: selfdrive/ui/layouts/settings/software.py:37 +#: openpilot/selfdrive/ui/layouts/settings/software.py:44 #, python-format msgid "{} hour ago" msgid_plural "{} hours ago" msgstr[0] "{} 小時前" msgstr[1] "{} 小時前" -#: selfdrive/ui/layouts/settings/software.py:34 +#: openpilot/selfdrive/ui/layouts/settings/software.py:41 #, python-format msgid "{} minute ago" msgid_plural "{} minutes ago" msgstr[0] "{} 分鐘前" msgstr[1] "{} 分鐘前" -#: selfdrive/ui/layouts/settings/firehose.py:111 +#: openpilot/selfdrive/ui/layouts/settings/firehose.py:70 #, python-format msgid "{} segment of your driving is in the training dataset so far." msgid_plural "{} segments of your driving is in the training dataset so far." msgstr[0] "目前已有 {} 個您的駕駛片段納入訓練資料集。" msgstr[1] "目前已有 {} 個您的駕駛片段納入訓練資料集。" -#: selfdrive/ui/widgets/prime.py:62 +#: openpilot/selfdrive/ui/widgets/prime.py:62 #, python-format msgid "✓ SUBSCRIBED" msgstr "✓ 已訂閱" -#: selfdrive/ui/widgets/setup.py:22 +#: openpilot/selfdrive/ui/widgets/setup.py:21 #, python-format msgid "🔥 Firehose Mode 🔥" msgstr "🔥 Firehose 模式 🔥" + From 1dbae159a8aff24d7325c91bb875ed90472c4313 Mon Sep 17 00:00:00 2001 From: Armand du Parc Locmaria Date: Mon, 9 Mar 2026 14:02:03 -0700 Subject: [PATCH 086/141] op switch: sync submodules (#37618) --- tools/op.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/op.sh b/tools/op.sh index 7c20403a27..29f0b63020 100755 --- a/tools/op.sh +++ b/tools/op.sh @@ -405,6 +405,7 @@ function op_switch() { git submodule deinit --all --force git reset --hard "${REMOTE}/${BRANCH}" git clean -df + git submodule sync --recursive git submodule update --init --recursive git submodule foreach git reset --hard git submodule foreach git clean -df From 56d196162528b053d8da3441edc83608931dc66a Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Mon, 9 Mar 2026 14:09:13 -0700 Subject: [PATCH 087/141] Revert "setup & reset tuneups" (#37619) Revert "setup & reset tuneups (#37611)" This reverts commit 9510e05dc0012757f6156f012fc37f87f6340d06. --- system/ui/mici_reset.py | 2 +- system/ui/mici_setup.py | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/system/ui/mici_reset.py b/system/ui/mici_reset.py index c547d9185d..2f89b6d62a 100755 --- a/system/ui/mici_reset.py +++ b/system/ui/mici_reset.py @@ -77,7 +77,7 @@ class Reset(Scroller): self._reset_failed_page = ResetFailedPage() self._reset_button = BigConfirmationCircleButton("reset &\nerase", gui_app.texture("icons_mici/settings/device/uninstall.png", 70, 70), - self._start_reset, exit_on_confirm=False, red=True) + self._start_reset, red=True) self._cancel_button = BigConfirmationCircleButton("cancel", gui_app.texture("icons_mici/setup/cancel.png", 64, 64), gui_app.request_close, exit_on_confirm=False) self._reboot_button = BigConfirmationCircleButton("reboot\ndevice", gui_app.texture("icons_mici/settings/device/reboot.png", 64, 70), diff --git a/system/ui/mici_setup.py b/system/ui/mici_setup.py index 4e340335b9..33dfe98f37 100755 --- a/system/ui/mici_setup.py +++ b/system/ui/mici_setup.py @@ -144,7 +144,7 @@ class SoftwareSelectionPage(NavWidget): self._openpilot_slider = LargerSlider("slide to install\nopenpilot", use_openpilot_callback) self._openpilot_slider.set_enabled(lambda: self.enabled and not self.is_dismissing) - self._custom_software_slider = LargerSlider("slide to install\ncustom software", use_custom_software_callback, green=False) + self._custom_software_slider = LargerSlider("slide to install\nother software", use_custom_software_callback, green=False) self._custom_software_slider.set_enabled(lambda: self.enabled and not self.is_dismissing) def show_event(self): @@ -190,11 +190,11 @@ class CustomSoftwareWarningPage(NavScroller): self._continue_button.set_click_callback(continue_callback) self._scroller.add_widgets([ - GreyBigButton("caution: installing\n3rd party software", "swipe down to go back", + GreyBigButton("use caution", "when installing\n3rd party software", gui_app.texture("icons_mici/setup/warning.png", 64, 58)), - GreyBigButton("", "• It has not been tested by comma."), - GreyBigButton("", "• It may not comply with safety standards."), - GreyBigButton("", "• It may damage your device and/or vehicle."), + GreyBigButton("", "• It has not been tested by comma"), + GreyBigButton("", "• It may not comply with relevant safety standards."), + GreyBigButton("", "• It may cause damage to your device and/or vehicle."), GreyBigButton("how to restore to a\nfactory state later", "https://flash.comma.ai", gui_app.texture("icons_mici/setup/restore.png", 64, 64)), self._continue_button, @@ -546,7 +546,7 @@ class Setup(Widget): def _push_network_setup(self, custom_software: bool = False): # to fire the correct continue callback later self._network_setup_page.set_custom_software(custom_software) - gui_app.push_widget(self._network_setup_page) + gui_app.pop_widgets_to(self._software_selection_page, lambda: gui_app.push_widget(self._network_setup_page)) def _network_setup_continue_callback(self, custom_software: bool): if not custom_software: From d6c85abcd3f80e27ebe56c526a5aef3515bbc314 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Mon, 9 Mar 2026 14:11:01 -0700 Subject: [PATCH 088/141] setup: copy changes from https://github.com/commaai/openpilot/pull/37611 --- system/ui/mici_setup.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/system/ui/mici_setup.py b/system/ui/mici_setup.py index 33dfe98f37..7571139361 100755 --- a/system/ui/mici_setup.py +++ b/system/ui/mici_setup.py @@ -144,7 +144,7 @@ class SoftwareSelectionPage(NavWidget): self._openpilot_slider = LargerSlider("slide to install\nopenpilot", use_openpilot_callback) self._openpilot_slider.set_enabled(lambda: self.enabled and not self.is_dismissing) - self._custom_software_slider = LargerSlider("slide to install\nother software", use_custom_software_callback, green=False) + self._custom_software_slider = LargerSlider("slide to install\ncustom software", use_custom_software_callback, green=False) self._custom_software_slider.set_enabled(lambda: self.enabled and not self.is_dismissing) def show_event(self): @@ -190,11 +190,11 @@ class CustomSoftwareWarningPage(NavScroller): self._continue_button.set_click_callback(continue_callback) self._scroller.add_widgets([ - GreyBigButton("use caution", "when installing\n3rd party software", + GreyBigButton("caution: installing\n3rd party software", "swipe down to go back", gui_app.texture("icons_mici/setup/warning.png", 64, 58)), - GreyBigButton("", "• It has not been tested by comma"), - GreyBigButton("", "• It may not comply with relevant safety standards."), - GreyBigButton("", "• It may cause damage to your device and/or vehicle."), + GreyBigButton("", "• It has not been tested by comma."), + GreyBigButton("", "• It may not comply with safety standards."), + GreyBigButton("", "• It may damage your device and/or vehicle."), GreyBigButton("how to restore to a\nfactory state later", "https://flash.comma.ai", gui_app.texture("icons_mici/setup/restore.png", 64, 64)), self._continue_button, From dd8aa4a21ef5cab0dec44ee642293d52eb87ca28 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Mon, 9 Mar 2026 14:20:16 -0700 Subject: [PATCH 089/141] setup: don't swipe down custom fork screen --- system/ui/mici_setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system/ui/mici_setup.py b/system/ui/mici_setup.py index 7571139361..4e340335b9 100755 --- a/system/ui/mici_setup.py +++ b/system/ui/mici_setup.py @@ -546,7 +546,7 @@ class Setup(Widget): def _push_network_setup(self, custom_software: bool = False): # to fire the correct continue callback later self._network_setup_page.set_custom_software(custom_software) - gui_app.pop_widgets_to(self._software_selection_page, lambda: gui_app.push_widget(self._network_setup_page)) + gui_app.push_widget(self._network_setup_page) def _network_setup_continue_callback(self, custom_software: bool): if not custom_software: From 0208d26845db2dabb0f7a49bd69f694fb4a6c771 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Mon, 9 Mar 2026 15:39:06 -0700 Subject: [PATCH 090/141] reset: don't swipe down confirm slider (#37620) * test and broke * fix * clean up --- system/ui/mici_reset.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/system/ui/mici_reset.py b/system/ui/mici_reset.py index 2f89b6d62a..2eed4db11e 100755 --- a/system/ui/mici_reset.py +++ b/system/ui/mici_reset.py @@ -77,7 +77,7 @@ class Reset(Scroller): self._reset_failed_page = ResetFailedPage() self._reset_button = BigConfirmationCircleButton("reset &\nerase", gui_app.texture("icons_mici/settings/device/uninstall.png", 70, 70), - self._start_reset, red=True) + self._start_reset, exit_on_confirm=False, red=True) self._cancel_button = BigConfirmationCircleButton("cancel", gui_app.texture("icons_mici/setup/cancel.png", 64, 64), gui_app.request_close, exit_on_confirm=False) self._reboot_button = BigConfirmationCircleButton("reboot\ndevice", gui_app.texture("icons_mici/settings/device/reboot.png", 64, 70), @@ -105,6 +105,8 @@ class Reset(Scroller): self._reset_button, ]) + gui_app.add_nav_stack_tick(self._nav_stack_tick) + def _do_erase(self): if PC: return @@ -123,9 +125,7 @@ class Reset(Scroller): self._resetting_page.set_shown_callback(self._do_erase) gui_app.push_widget(self._resetting_page) - def _update_state(self): - super()._update_state() - + def _nav_stack_tick(self): if self._reset_failed: self._reset_failed = False gui_app.pop_widgets_to(self, lambda: gui_app.push_widget(self._reset_failed_page)) From acace97ef89c3893322f690bc621118c05a1b258 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Mon, 9 Mar 2026 17:18:40 -0700 Subject: [PATCH 091/141] add warning to pack.py (#37624) * start * works! * can't check ls-files because we need built files too >:( * add print --- release/pack.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/release/pack.py b/release/pack.py index 8979e0e618..a617a88350 100755 --- a/release/pack.py +++ b/release/pack.py @@ -28,6 +28,8 @@ if __name__ == '__main__': parser.add_argument('module', help="the module to target, e.g. 'openpilot.system.ui.spinner'") args = parser.parse_args() + print('WARNING: copying all files! make sure to run scons and git tree is clean') + if not args.output: args.output = args.module From a17a8daad56f99fccf6e366e96bb693ac943b47c Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Mon, 9 Mar 2026 17:32:33 -0700 Subject: [PATCH 092/141] pack.py: exclude large unused folderrs --- release/pack.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/release/pack.py b/release/pack.py index a617a88350..8831a0b34d 100755 --- a/release/pack.py +++ b/release/pack.py @@ -13,11 +13,12 @@ from openpilot.common.basedir import BASEDIR DIRS = ['cereal', 'openpilot'] EXTS = ['.png', '.py', '.ttf', '.capnp', '.json', '.fnt', '.mo', '.po'] +EXCLUDE = ['selfdrive/assets/training', 'third_party/raylib/raylib_repo/examples'] INTERPRETER = '/usr/bin/env python3' def copy(src, dest): - if any(src.endswith(ext) for ext in EXTS): + if any(src.endswith(ext) for ext in EXTS) and not any(exc in src for exc in EXCLUDE): shutil.copy2(src, dest, follow_symlinks=True) From 2ca6f893df17bc98b9e4dca0e854c26b95ce8a5c Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Mon, 9 Mar 2026 17:34:16 -0700 Subject: [PATCH 093/141] New updater_magic --- system/hardware/tici/updater_magic | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/system/hardware/tici/updater_magic b/system/hardware/tici/updater_magic index 8bf150744a..f0689d5072 100755 --- a/system/hardware/tici/updater_magic +++ b/system/hardware/tici/updater_magic @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3236a08d318c26022e536fa76627ca15c0517f050f4c4e91a1aafc5bfefb44d0 -size 71240680 +oid sha256:238d750f12c8b6758fd47998891d0cfa4bc52d72662801553c7d03952f8166b2 +size 24744419 From 095d96fbe07cb28b81bde9102208a0ff1f3cba77 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Mon, 9 Mar 2026 18:43:42 -0700 Subject: [PATCH 094/141] reset: erase in thread (#37627) erase in thread --- system/ui/mici_reset.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/system/ui/mici_reset.py b/system/ui/mici_reset.py index 2eed4db11e..af9d866d54 100755 --- a/system/ui/mici_reset.py +++ b/system/ui/mici_reset.py @@ -2,6 +2,7 @@ import os import sys import time +import threading from enum import IntEnum import pyray as rl @@ -122,7 +123,10 @@ class Reset(Scroller): self._reset_failed = True def _start_reset(self): - self._resetting_page.set_shown_callback(self._do_erase) + def do_erase_thread(): + threading.Thread(target=self._do_erase, daemon=True).start() + + self._resetting_page.set_shown_callback(do_erase_thread) gui_app.push_widget(self._resetting_page) def _nav_stack_tick(self): From 1777d548bf0023297d8a685ff8dc6917a1e1db44 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Mon, 9 Mar 2026 20:11:26 -0700 Subject: [PATCH 095/141] stagger driver camera SOF (#37628) --- panda | 2 +- selfdrive/test/test_onroad.py | 7 ++++++- system/camerad/cameras/hw.h | 4 ++++ system/camerad/cameras/spectra.cc | 20 ++++++++++++++------ system/camerad/cameras/spectra.h | 3 ++- system/camerad/test/test_camerad.py | 12 +++++++++--- 6 files changed, 36 insertions(+), 12 deletions(-) diff --git a/panda b/panda index d1410f7f7b..c10b82f8ff 160000 --- a/panda +++ b/panda @@ -1 +1 @@ -Subproject commit d1410f7f7b061171c3702d84d975a3da3afce109 +Subproject commit c10b82f8ff03c7d677a9420c21c0c50126a5071e diff --git a/selfdrive/test/test_onroad.py b/selfdrive/test/test_onroad.py index 008b8ebe7f..1129a1a2ff 100644 --- a/selfdrive/test/test_onroad.py +++ b/selfdrive/test/test_onroad.py @@ -342,10 +342,15 @@ class TestOnroad: start, end = min(first_fid), min(last_fid) for i in range(end-start): - ts = {c: round(self.ts[c]['timestampSof'][i]/1e6, 1) for c in cams} + # road and wide cameras (first two) should be synced within 2ms + ts = {c: round(self.ts[c]['timestampSof'][i]/1e6, 1) for c in cams[:2]} diff = (max(ts.values()) - min(ts.values())) assert diff < 2, f"Cameras not synced properly: frame_id={start+i}, {diff=:.1f}ms, {ts=}" + # driver camera should be staggered ~25ms from road camera + offset_ms = abs(self.ts[cams[2]]['timestampSof'][i] - self.ts[cams[0]]['timestampSof'][i]) / 1e6 + assert 20 < offset_ms < 30, f"driver camera stagger out of range at frame {start+i}: {offset_ms:.1f}ms" + def test_camera_encoder_matches(self, subtests): # sanity check that the frame metadata is consistent with the encoded frames pairs = [('roadCameraState', 'roadEncodeIdx'), diff --git a/system/camerad/cameras/hw.h b/system/camerad/cameras/hw.h index f20a1b3ade..defe878e89 100644 --- a/system/camerad/cameras/hw.h +++ b/system/camerad/cameras/hw.h @@ -25,6 +25,7 @@ struct CameraConfig { uint32_t phy; bool vignetting_correction; SpectraOutputType output_type; + bool staggered_sof; // SOF is staggered (half-period offset) from other cameras }; // NOTE: to be able to disable road and wide road, we still have to configure the sensor over i2c @@ -39,6 +40,7 @@ const CameraConfig WIDE_ROAD_CAMERA_CONFIG = { .phy = CAM_ISP_IFE_IN_RES_PHY_0, .vignetting_correction = false, .output_type = ISP_IFE_PROCESSED, + .staggered_sof = false, }; const CameraConfig ROAD_CAMERA_CONFIG = { @@ -51,6 +53,7 @@ const CameraConfig ROAD_CAMERA_CONFIG = { .phy = CAM_ISP_IFE_IN_RES_PHY_1, .vignetting_correction = true, .output_type = ISP_IFE_PROCESSED, + .staggered_sof = false, }; const CameraConfig DRIVER_CAMERA_CONFIG = { @@ -63,6 +66,7 @@ const CameraConfig DRIVER_CAMERA_CONFIG = { .phy = CAM_ISP_IFE_IN_RES_PHY_2, .vignetting_correction = false, .output_type = ISP_BPS_PROCESSED, + .staggered_sof = true, }; const CameraConfig ALL_CAMERA_CONFIGS[] = {WIDE_ROAD_CAMERA_CONFIG, ROAD_CAMERA_CONFIG, DRIVER_CAMERA_CONFIG}; diff --git a/system/camerad/cameras/spectra.cc b/system/camerad/cameras/spectra.cc index 73e0a78da3..ab9d8e069a 100644 --- a/system/camerad/cameras/spectra.cc +++ b/system/camerad/cameras/spectra.cc @@ -1436,7 +1436,7 @@ bool SpectraCamera::waitForFrameReady(uint64_t request_id) { } bool SpectraCamera::processFrame(int buf_idx, uint64_t request_id, uint64_t frame_id_raw, uint64_t timestamp) { - if (!syncFirstFrame(cc.camera_num, request_id, frame_id_raw, timestamp)) { + if (!syncFirstFrame(cc.camera_num, request_id, frame_id_raw, timestamp, cc.staggered_sof)) { return false; } @@ -1455,23 +1455,31 @@ bool SpectraCamera::processFrame(int buf_idx, uint64_t request_id, uint64_t fram return true; } -bool SpectraCamera::syncFirstFrame(int camera_id, uint64_t request_id, uint64_t raw_id, uint64_t timestamp) { +bool SpectraCamera::syncFirstFrame(int camera_id, uint64_t request_id, uint64_t raw_id, uint64_t timestamp, bool staggered) { if (first_frame_synced) return true; // Store the frame data for this camera - camera_sync_data[camera_id] = SyncData{timestamp, raw_id + 1}; + camera_sync_data[camera_id] = SyncData{timestamp, raw_id + 1, staggered}; // Ensure all cameras are up int enabled_camera_count = std::count_if(std::begin(ALL_CAMERA_CONFIGS), std::end(ALL_CAMERA_CONFIGS), [](const auto &config) { return config.enabled; }); bool all_cams_up = camera_sync_data.size() == enabled_camera_count; - // Wait until the timestamps line up + // Check that camera timestamps are properly aligned: + // - non-staggered cameras should be within 0.2ms of each other + // - staggered cameras should be within 0.2ms of a 25ms offset from non-staggered cameras + const uint64_t half_period_ns = 25 * 1000000ULL; // 25ms + const uint64_t tolerance_ns = 200000ULL; // 0.2ms bool all_cams_synced = true; - for (const auto &[_, sync_data] : camera_sync_data) { + for (const auto &[cam, sync_data] : camera_sync_data) { + if (cam == camera_id) continue; uint64_t diff = std::max(timestamp, sync_data.timestamp) - std::min(timestamp, sync_data.timestamp); - if (diff > 0.2*1e6) { // milliseconds + bool pair_staggered = staggered != sync_data.staggered; + uint64_t expected_offset = pair_staggered ? half_period_ns : 0; + uint64_t error = (diff > expected_offset) ? diff - expected_offset : expected_offset - diff; + if (error > tolerance_ns) { all_cams_synced = false; } } diff --git a/system/camerad/cameras/spectra.h b/system/camerad/cameras/spectra.h index a02b8a6cac..7dd1135254 100644 --- a/system/camerad/cameras/spectra.h +++ b/system/camerad/cameras/spectra.h @@ -194,10 +194,11 @@ private: bool validateEvent(uint64_t request_id, uint64_t frame_id_raw); bool waitForFrameReady(uint64_t request_id); bool processFrame(int buf_idx, uint64_t request_id, uint64_t frame_id_raw, uint64_t timestamp); - static bool syncFirstFrame(int camera_id, uint64_t request_id, uint64_t raw_id, uint64_t timestamp); + static bool syncFirstFrame(int camera_id, uint64_t request_id, uint64_t raw_id, uint64_t timestamp, bool staggered); struct SyncData { uint64_t timestamp; uint64_t frame_id_offset = 0; + bool staggered = false; }; inline static std::map camera_sync_data; inline static bool first_frame_synced = false; diff --git a/system/camerad/test/test_camerad.py b/system/camerad/test/test_camerad.py index 5f8de86899..3abe4db654 100644 --- a/system/camerad/test/test_camerad.py +++ b/system/camerad/test/test_camerad.py @@ -105,17 +105,23 @@ class TestCamerad: assert set(np.diff(logs[c]['frameId'])) == {1, }, f"{c} has frame skips" def test_frame_sync(self, logs): + SYNCED_CAMS = ('roadCameraState', 'wideRoadCameraState') n = range(len(logs['roadCameraState']['t'][:-10])) frame_ids = {i: [logs[cam]['frameId'][i] for cam in CAMERAS] for i in n} assert all(len(set(v)) == 1 for v in frame_ids.values()), "frame IDs not aligned" - frame_times = {i: [logs[cam]['timestampSof'][i] for cam in CAMERAS] for i in n} - diffs = {i: (max(ts) - min(ts))/1e6 for i, ts in frame_times.items()} - + # road and wide cameras should be synced within 1.1ms + synced_times = {i: [logs[cam]['timestampSof'][i] for cam in SYNCED_CAMS] for i in n} + diffs = {i: (max(ts) - min(ts))/1e6 for i, ts in synced_times.items()} laggy_frames = {k: v for k, v in diffs.items() if v > 1.1} assert len(laggy_frames) == 0, f"Frames not synced properly: {laggy_frames=}" + # driver camera should be staggered ~25ms from road camera + for i in n: + offset_ms = abs(logs['driverCameraState']['timestampSof'][i] - logs['roadCameraState']['timestampSof'][i]) / 1e6 + assert 20 < offset_ms < 30, f"driver camera stagger out of range at frame {i}: {offset_ms:.1f}ms (expected ~25ms)" + def test_sanity_checks(self, logs): self._sanity_checks(logs) From bd5fbbabda6c4328e2efe144be10f8184807b03b Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Mon, 9 Mar 2026 22:25:49 -0700 Subject: [PATCH 096/141] setup: simplify cache branch (#37630) * this wasn't atomic! * start mici * always require internet to download installer * this made it never use cached fetch! * this skipped installer when it wrote it raced trying to run * entirely remove * clean up mici * fix tici setup * inline * works --- system/ui/mici_setup.py | 36 +++++------------------------------- system/ui/tici_setup.py | 34 +++------------------------------- 2 files changed, 8 insertions(+), 62 deletions(-) diff --git a/system/ui/mici_setup.py b/system/ui/mici_setup.py index 4e340335b9..616991b560 100755 --- a/system/ui/mici_setup.py +++ b/system/ui/mici_setup.py @@ -7,7 +7,6 @@ import time import urllib.request import urllib.error from urllib.parse import urlparse -import shutil from collections.abc import Callable import pyray as rl @@ -36,20 +35,9 @@ NetworkType = log.DeviceState.NetworkType OPENPILOT_URL = "https://openpilot.comma.ai" USER_AGENT = f"AGNOSSetup-{HARDWARE.get_os_version()}" -CONTINUE_PATH = "/data/continue.sh" -TMP_CONTINUE_PATH = "/data/continue.sh.new" -INSTALL_PATH = "/data/openpilot" -VALID_CACHE_PATH = "/data/.openpilot_cache" -INSTALLER_SOURCE_PATH = "/usr/comma/installer" INSTALLER_DESTINATION_PATH = "/tmp/installer" INSTALLER_URL_PATH = "/tmp/installer_url" -CONTINUE = """#!/usr/bin/env bash - -cd /data/openpilot -exec ./launch_openpilot.sh -""" - class NetworkConnectivityMonitor: def __init__(self, should_check: Callable[[], bool] | None = None): @@ -499,7 +487,7 @@ class Setup(Widget): self._network_setup_page = NetworkSetupPage(self._network_monitor, self._network_setup_continue_callback, self._pop_to_software_selection) - self._software_selection_page = SoftwareSelectionPage(self._use_openpilot, lambda: gui_app.push_widget(self._custom_software_warning_page)) + self._software_selection_page = SoftwareSelectionPage(self._push_network_setup, lambda: gui_app.push_widget(self._custom_software_warning_page)) self._download_failed_page = FailedPage(self._pop_to_software_selection, icon="icons_mici/setup/red_warning.png") @@ -528,21 +516,6 @@ class Setup(Widget): # reset sliders after dismiss completes gui_app.pop_widgets_to(self._software_selection_page, self._software_selection_page.reset) - def _use_openpilot(self): - if os.path.isdir(INSTALL_PATH) and os.path.isfile(VALID_CACHE_PATH): - os.remove(VALID_CACHE_PATH) - with open(TMP_CONTINUE_PATH, "w") as f: - f.write(CONTINUE) - run_cmd(["chmod", "+x", TMP_CONTINUE_PATH]) - shutil.move(TMP_CONTINUE_PATH, CONTINUE_PATH) - shutil.copyfile(INSTALLER_SOURCE_PATH, INSTALLER_DESTINATION_PATH) - - # give time for installer UI to take over - time.sleep(0.1) - gui_app.request_close() - else: - self._push_network_setup() - def _push_network_setup(self, custom_software: bool = False): # to fire the correct continue callback later self._network_setup_page.set_custom_software(custom_software) @@ -612,14 +585,15 @@ class Setup(Widget): self._download_failed_reason = "No custom software found at this URL: " + self.download_url.replace("https://", "", 1) return + # NOTE: currently unused, for future logging + with open(INSTALLER_URL_PATH, "w") as f: + f.write(self.download_url) + # AGNOS might try to execute the installer before this process exits. # Therefore, important to close the fd before renaming the installer. os.close(fd) os.rename(tmpfile, INSTALLER_DESTINATION_PATH) - with open(INSTALLER_URL_PATH, "w") as f: - f.write(self.download_url) - # give time for installer UI to take over time.sleep(0.1) gui_app.request_close() diff --git a/system/ui/tici_setup.py b/system/ui/tici_setup.py index 8098e9ea27..f98ab5ffaa 100755 --- a/system/ui/tici_setup.py +++ b/system/ui/tici_setup.py @@ -7,12 +7,10 @@ import urllib.request import urllib.error from urllib.parse import urlparse from enum import IntEnum -import shutil import pyray as rl from cereal import log -from openpilot.common.utils import run_cmd from openpilot.system.hardware import HARDWARE from openpilot.system.ui.lib.scroll_panel import GuiScrollPanel from openpilot.system.ui.lib.application import gui_app, FontWeight, FONT_SCALE @@ -35,20 +33,9 @@ BUTTON_SPACING = 50 OPENPILOT_URL = "https://openpilot.comma.ai" USER_AGENT = f"AGNOSSetup-{HARDWARE.get_os_version()}" -CONTINUE_PATH = "/data/continue.sh" -TMP_CONTINUE_PATH = "/data/continue.sh.new" -INSTALL_PATH = "/data/openpilot" -VALID_CACHE_PATH = "/data/.openpilot_cache" -INSTALLER_SOURCE_PATH = "/usr/comma/installer" INSTALLER_DESTINATION_PATH = "/tmp/installer" INSTALLER_URL_PATH = "/tmp/installer_url" -CONTINUE = """#!/usr/bin/env bash - -cd /data/openpilot -exec ./launch_openpilot.sh -""" - class SetupState(IntEnum): LOW_VOLTAGE = 0 @@ -176,7 +163,9 @@ class Setup(Widget): def _software_selection_continue_button_callback(self): if self._software_selection_openpilot_button.selected: - self.use_openpilot() + self.state = SetupState.NETWORK_SETUP + self.stop_network_check_thread.clear() + self.start_network_check() else: self.state = SetupState.CUSTOM_SOFTWARE_WARNING @@ -342,23 +331,6 @@ class Setup(Widget): self.keyboard.set_callback(handle_keyboard_result) gui_app.push_widget(self.keyboard) - def use_openpilot(self): - if os.path.isdir(INSTALL_PATH) and os.path.isfile(VALID_CACHE_PATH): - os.remove(VALID_CACHE_PATH) - with open(TMP_CONTINUE_PATH, "w") as f: - f.write(CONTINUE) - run_cmd(["chmod", "+x", TMP_CONTINUE_PATH]) - shutil.move(TMP_CONTINUE_PATH, CONTINUE_PATH) - shutil.copyfile(INSTALLER_SOURCE_PATH, INSTALLER_DESTINATION_PATH) - - # give time for installer UI to take over - time.sleep(0.1) - gui_app.request_close() - else: - self.state = SetupState.NETWORK_SETUP - self.stop_network_check_thread.clear() - self.start_network_check() - def download(self, url: str): # autocomplete incomplete URLs if re.match("^([^/.]+)/([^/]+)$", url): From 4acf0438c8a15488cf86416e2ec602dd9ba126e7 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 10 Mar 2026 03:17:18 -0700 Subject: [PATCH 097/141] AGNOS 17.1 (#37631) * agnos 17.1 * bump version --- launch_env.sh | 2 +- system/hardware/tici/agnos.json | 20 ++++++------- system/hardware/tici/all-partitions.json | 36 ++++++++++++------------ 3 files changed, 29 insertions(+), 29 deletions(-) diff --git a/launch_env.sh b/launch_env.sh index 097f5fbe94..efdb4bf0e7 100755 --- a/launch_env.sh +++ b/launch_env.sh @@ -16,7 +16,7 @@ export VECLIB_MAXIMUM_THREADS=1 export QCOM_PRIORITY=12 if [ -z "$AGNOS_VERSION" ]; then - export AGNOS_VERSION="17" + export AGNOS_VERSION="17.1" fi export STAGING_ROOT="/data/safe_staging" diff --git a/system/hardware/tici/agnos.json b/system/hardware/tici/agnos.json index ec5f43fa7b..58162fc311 100644 --- a/system/hardware/tici/agnos.json +++ b/system/hardware/tici/agnos.json @@ -56,28 +56,28 @@ }, { "name": "boot", - "url": "https://commadist.azureedge.net/agnosupdate/boot-1bce912831b41cc65d651acbc07ca68a1d528298c22ebae7c060db5f524f4ee0.img.xz", - "hash": "1bce912831b41cc65d651acbc07ca68a1d528298c22ebae7c060db5f524f4ee0", - "hash_raw": "1bce912831b41cc65d651acbc07ca68a1d528298c22ebae7c060db5f524f4ee0", + "url": "https://commadist.azureedge.net/agnosupdate/boot-d726315cf98a43e1090e5b49297404cf3d084cfbd42ad8bb7d8afb68136b9f51.img.xz", + "hash": "d726315cf98a43e1090e5b49297404cf3d084cfbd42ad8bb7d8afb68136b9f51", + "hash_raw": "d726315cf98a43e1090e5b49297404cf3d084cfbd42ad8bb7d8afb68136b9f51", "size": 17500160, "sparse": false, "full_check": true, "has_ab": true, - "ondevice_hash": "a64fc06e2508dbb2af4fa20808c10009bb5a31517ff39b0fb1dad882c9bec808" + "ondevice_hash": "2454108de1161289bc4a75449ad6421f1772b13b3e5cba68a84fca7530557699" }, { "name": "system", - "url": "https://commadist.azureedge.net/agnosupdate/system-1a64e41bf8d9f63c1a4d5e3e0a5a4c6c60c7dfb9d1e677c03adc2c668bc3fcb6.img.xz", - "hash": "ef7ad7d290d74285e9e73f30442d2adc8ff3a616e1d7ddcceee6ba5420aa2fb7", - "hash_raw": "1a64e41bf8d9f63c1a4d5e3e0a5a4c6c60c7dfb9d1e677c03adc2c668bc3fcb6", + "url": "https://commadist.azureedge.net/agnosupdate/system-8014b6aab8ea2d76f7ea90e76efb9f94504495d29a37d281f0df903b3bdb630f.img.xz", + "hash": "5d49176870f05328c9b4a934863644c2a9b59c993df97a9134ff63f7a4e1d81d", + "hash_raw": "8014b6aab8ea2d76f7ea90e76efb9f94504495d29a37d281f0df903b3bdb630f", "size": 4718592000, "sparse": true, "full_check": false, "has_ab": true, - "ondevice_hash": "bfe5187bb754fd0df5069d0b2b12f79f5938588f97bb87063532da5d6e7a1cfb", + "ondevice_hash": "ce18addaec7dd87a511fb7cb068500a1de5720fd76fceddf2944084b7d6fdf15", "alt": { - "hash": "1a64e41bf8d9f63c1a4d5e3e0a5a4c6c60c7dfb9d1e677c03adc2c668bc3fcb6", - "url": "https://commadist.azureedge.net/agnosupdate/system-1a64e41bf8d9f63c1a4d5e3e0a5a4c6c60c7dfb9d1e677c03adc2c668bc3fcb6.img", + "hash": "8014b6aab8ea2d76f7ea90e76efb9f94504495d29a37d281f0df903b3bdb630f", + "url": "https://commadist.azureedge.net/agnosupdate/system-8014b6aab8ea2d76f7ea90e76efb9f94504495d29a37d281f0df903b3bdb630f.img", "size": 4718592000 } } diff --git a/system/hardware/tici/all-partitions.json b/system/hardware/tici/all-partitions.json index 8b4c0d4312..468142be60 100644 --- a/system/hardware/tici/all-partitions.json +++ b/system/hardware/tici/all-partitions.json @@ -339,51 +339,51 @@ }, { "name": "boot", - "url": "https://commadist.azureedge.net/agnosupdate/boot-1bce912831b41cc65d651acbc07ca68a1d528298c22ebae7c060db5f524f4ee0.img.xz", - "hash": "1bce912831b41cc65d651acbc07ca68a1d528298c22ebae7c060db5f524f4ee0", - "hash_raw": "1bce912831b41cc65d651acbc07ca68a1d528298c22ebae7c060db5f524f4ee0", + "url": "https://commadist.azureedge.net/agnosupdate/boot-d726315cf98a43e1090e5b49297404cf3d084cfbd42ad8bb7d8afb68136b9f51.img.xz", + "hash": "d726315cf98a43e1090e5b49297404cf3d084cfbd42ad8bb7d8afb68136b9f51", + "hash_raw": "d726315cf98a43e1090e5b49297404cf3d084cfbd42ad8bb7d8afb68136b9f51", "size": 17500160, "sparse": false, "full_check": true, "has_ab": true, - "ondevice_hash": "a64fc06e2508dbb2af4fa20808c10009bb5a31517ff39b0fb1dad882c9bec808" + "ondevice_hash": "2454108de1161289bc4a75449ad6421f1772b13b3e5cba68a84fca7530557699" }, { "name": "system", - "url": "https://commadist.azureedge.net/agnosupdate/system-1a64e41bf8d9f63c1a4d5e3e0a5a4c6c60c7dfb9d1e677c03adc2c668bc3fcb6.img.xz", - "hash": "ef7ad7d290d74285e9e73f30442d2adc8ff3a616e1d7ddcceee6ba5420aa2fb7", - "hash_raw": "1a64e41bf8d9f63c1a4d5e3e0a5a4c6c60c7dfb9d1e677c03adc2c668bc3fcb6", + "url": "https://commadist.azureedge.net/agnosupdate/system-8014b6aab8ea2d76f7ea90e76efb9f94504495d29a37d281f0df903b3bdb630f.img.xz", + "hash": "5d49176870f05328c9b4a934863644c2a9b59c993df97a9134ff63f7a4e1d81d", + "hash_raw": "8014b6aab8ea2d76f7ea90e76efb9f94504495d29a37d281f0df903b3bdb630f", "size": 4718592000, "sparse": true, "full_check": false, "has_ab": true, - "ondevice_hash": "bfe5187bb754fd0df5069d0b2b12f79f5938588f97bb87063532da5d6e7a1cfb", + "ondevice_hash": "ce18addaec7dd87a511fb7cb068500a1de5720fd76fceddf2944084b7d6fdf15", "alt": { - "hash": "1a64e41bf8d9f63c1a4d5e3e0a5a4c6c60c7dfb9d1e677c03adc2c668bc3fcb6", - "url": "https://commadist.azureedge.net/agnosupdate/system-1a64e41bf8d9f63c1a4d5e3e0a5a4c6c60c7dfb9d1e677c03adc2c668bc3fcb6.img", + "hash": "8014b6aab8ea2d76f7ea90e76efb9f94504495d29a37d281f0df903b3bdb630f", + "url": "https://commadist.azureedge.net/agnosupdate/system-8014b6aab8ea2d76f7ea90e76efb9f94504495d29a37d281f0df903b3bdb630f.img", "size": 4718592000 } }, { "name": "userdata_90", - "url": "https://commadist.azureedge.net/agnosupdate/userdata_90-3711c021ee7a6512c93452857893325a6ed845e4cfad52398b531eaede22f913.img.xz", - "hash": "67e0066cc5d2b7173f6280a7a8836d6681e2cd55b0f4a79eafac5b9fdd4fd7c8", - "hash_raw": "3711c021ee7a6512c93452857893325a6ed845e4cfad52398b531eaede22f913", + "url": "https://commadist.azureedge.net/agnosupdate/userdata_90-24f985765b91593e468fa76333a994420a9f460b9a66368a8dbdcac6ee04d8f5.img.xz", + "hash": "f24ee158408d4b41a95c79361f442b5a3304c46e0872e387afddbdbb56d934ac", + "hash_raw": "24f985765b91593e468fa76333a994420a9f460b9a66368a8dbdcac6ee04d8f5", "size": 96636764160, "sparse": true, "full_check": true, "has_ab": false, - "ondevice_hash": "31fe9e6e1b4ae1fe267868daff1b56660c9b4345e6a6272fce161b30ba70ed4c" + "ondevice_hash": "9793fa1de3a22cedace28a43f001fbdc4fb73c81c949dc4ec4f5e60ce9eef0b6" }, { "name": "userdata_89", - "url": "https://commadist.azureedge.net/agnosupdate/userdata_89-8fcd15c164625c81199d7d3cca11009c862efa3ae19112f57fb10b4202474363.img.xz", - "hash": "ccb3bde6f31b340816d8499a5a205ba0e9984c189451807501a76f05820c3f4c", - "hash_raw": "8fcd15c164625c81199d7d3cca11009c862efa3ae19112f57fb10b4202474363", + "url": "https://commadist.azureedge.net/agnosupdate/userdata_89-01336e05d3f85a398f2255ecbd4ebbc14b7f688a33295390891ad90234db8f0b.img.xz", + "hash": "ec13a458352c8d3a974a97acc1aa5d7f1118ab59989d375e00d81c7091b75e8e", + "hash_raw": "01336e05d3f85a398f2255ecbd4ebbc14b7f688a33295390891ad90234db8f0b", "size": 95563022336, "sparse": true, "full_check": true, "has_ab": false, - "ondevice_hash": "4167c69304e8038050793dbde70ff230614146836d9d31e5eb83aa03b6ed787f" + "ondevice_hash": "963c071664375b531bb867d83ffc9e2de7883408ca1481330a1ee4db1a964e68" } ] \ No newline at end of file From ba19527181a0ce3f8b98ea78398c06c9ca384469 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Tue, 10 Mar 2026 10:19:20 -0700 Subject: [PATCH 098/141] 0.11.1: a nice DM focused release --- RELEASES.md | 5 +++++ common/version.h | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/RELEASES.md b/RELEASES.md index bf4e0c75aa..a3a95ae6a8 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -1,3 +1,8 @@ +Version 0.11.1 (2026-04-08) +======================== +* New driver monitoring model +* Improved image processing pipeline for driver camera + Version 0.11.0 (2026-03-17) ======================== * New driving model #36798 diff --git a/common/version.h b/common/version.h index 78264e074c..41440556c5 100644 --- a/common/version.h +++ b/common/version.h @@ -1 +1 @@ -#define COMMA_VERSION "0.11.0" +#define COMMA_VERSION "0.11.1" From ac3dcbe62fc7b60d7285bc056b0210a7aaf7cf9a Mon Sep 17 00:00:00 2001 From: Armand du Parc Locmaria Date: Tue, 10 Mar 2026 10:55:17 -0700 Subject: [PATCH 099/141] Revert "op switch: sync submodules" (#37632) Revert "op switch: sync submodules (#37618)" This reverts commit 1dbae159a8aff24d7325c91bb875ed90472c4313. --- tools/op.sh | 1 - 1 file changed, 1 deletion(-) diff --git a/tools/op.sh b/tools/op.sh index 29f0b63020..7c20403a27 100755 --- a/tools/op.sh +++ b/tools/op.sh @@ -405,7 +405,6 @@ function op_switch() { git submodule deinit --all --force git reset --hard "${REMOTE}/${BRANCH}" git clean -df - git submodule sync --recursive git submodule update --init --recursive git submodule foreach git reset --hard git submodule foreach git clean -df From 9164148d487d7004142b28900e1cd496ce6a8d1d Mon Sep 17 00:00:00 2001 From: Trey Moen <50057480+greatgitsby@users.noreply.github.com> Date: Tue, 10 Mar 2026 10:58:21 -0700 Subject: [PATCH 100/141] feat: uv manages python (#37535) --- SConstruct | 3 +-- pyproject.toml | 4 +++- tools/op.sh | 27 +-------------------------- uv.lock | 7 ------- 4 files changed, 5 insertions(+), 36 deletions(-) diff --git a/SConstruct b/SConstruct index 3f974f09c1..c6c8632560 100644 --- a/SConstruct +++ b/SConstruct @@ -41,7 +41,6 @@ assert arch in [ pkg_names = ['bzip2', 'capnproto', 'eigen', 'ffmpeg', 'libjpeg', 'libyuv', 'ncurses', 'zeromq', 'zstd'] pkgs = [importlib.import_module(name) for name in pkg_names] -py_include = importlib.import_module('python3_dev').INCLUDE_DIR env = Environment( ENV={ @@ -163,7 +162,7 @@ if os.environ.get('SCONS_PROGRESS'): # ********** Cython build environment ********** envCython = env.Clone() -envCython["CPPPATH"] += [py_include, np.get_include()] +envCython["CPPPATH"] += [sysconfig.get_paths()['include'], np.get_include()] envCython["CCFLAGS"] += ["-Wno-#warnings", "-Wno-cpp", "-Wno-shadow", "-Wno-deprecated-declarations"] envCython["CCFLAGS"].remove("-Werror") diff --git a/pyproject.toml b/pyproject.toml index 5b77eb76d8..408513600f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -32,7 +32,6 @@ dependencies = [ "ffmpeg @ git+https://github.com/commaai/dependencies.git@releases#subdirectory=ffmpeg", "libjpeg @ git+https://github.com/commaai/dependencies.git@releases#subdirectory=libjpeg", "libyuv @ git+https://github.com/commaai/dependencies.git@releases#subdirectory=libyuv", - "python3-dev @ git+https://github.com/commaai/dependencies.git@releases#subdirectory=python3-dev", "zstd @ git+https://github.com/commaai/dependencies.git@releases#subdirectory=zstd", "ncurses @ git+https://github.com/commaai/dependencies.git@releases#subdirectory=ncurses", "zeromq @ git+https://github.com/commaai/dependencies.git@releases#subdirectory=zeromq", @@ -248,3 +247,6 @@ unsupported-operator = "ignore" # Ignore not-subscriptable - false positives from dynamic types not-subscriptable = "ignore" # not-iterable errors are now fixed + +[tool.uv] +python-preference = "only-managed" diff --git a/tools/op.sh b/tools/op.sh index 7c20403a27..f21a285d17 100755 --- a/tools/op.sh +++ b/tools/op.sh @@ -167,29 +167,6 @@ function op_check_os() { fi } -function op_check_python() { - echo "Checking for compatible python version..." - REQUIRED_PYTHON_VERSION=$(grep "requires-python" $OPENPILOT_ROOT/pyproject.toml) - INSTALLED_PYTHON_VERSION=$(python3 --version 2> /dev/null || true) - - if [[ -z $INSTALLED_PYTHON_VERSION ]]; then - echo -e " ↳ [${RED}✗${NC}] python3 not found on your system. You need python version satisfying $(echo $REQUIRED_PYTHON_VERSION | cut -d '=' -f2-) to continue!" - loge "ERROR_PYTHON_NOT_FOUND" - return 1 - else - LB=$(echo $REQUIRED_PYTHON_VERSION | tr -d '",' | awk '{ split($4, v, "."); printf "%d%02d%02d", v[1], v[2], v[3] }') - UB=$(echo $REQUIRED_PYTHON_VERSION | tr -d '",' | awk '{ split($6, v, "."); printf "%d%02d%02d", v[1], v[2], v[3] }') - VERSION=$(echo $INSTALLED_PYTHON_VERSION | awk '{ split($2, v, "."); printf "%d%02d%02d", v[1], v[2], v[3] }') - if [[ $VERSION -ge LB && $VERSION -lt UB ]]; then - echo -e " ↳ [${GREEN}✔${NC}] $INSTALLED_PYTHON_VERSION detected." - else - echo -e " ↳ [${RED}✗${NC}] You need a python version satisfying $(echo $REQUIRED_PYTHON_VERSION | cut -d '=' -f2-) to continue!" - loge "ERROR_PYTHON_VERSION" "$INSTALLED_PYTHON_VERSION" - return 1 - fi - fi -} - function op_check_venv() { echo "Checking for venv..." if [[ -f $OPENPILOT_ROOT/.venv/bin/activate ]]; then @@ -214,8 +191,6 @@ function op_before_cmd() { op_activate_venv - result="${result}\n$(( op_check_python ) 2>&1)" || (echo -e "$result" && return 1) - if [[ -z $VERBOSE ]]; then echo -e "${BOLD}Checking system →${NC} [${GREEN}✔${NC}]" else @@ -436,7 +411,7 @@ function op_default() { echo "" echo -e "${BOLD}${UNDERLINE}Commands [System]:${NC}" echo -e " ${BOLD}auth${NC} Authenticate yourself for API use" - echo -e " ${BOLD}check${NC} Check the development environment (git, os, python) to start using openpilot" + echo -e " ${BOLD}check${NC} Check the development environment (git, os) to start using openpilot" echo -e " ${BOLD}esim${NC} Manage eSIM profiles on your comma device" echo -e " ${BOLD}venv${NC} Activate the python virtual environment" echo -e " ${BOLD}setup${NC} Install openpilot dependencies" diff --git a/uv.lock b/uv.lock index 743521fb2f..578b19f646 100644 --- a/uv.lock +++ b/uv.lock @@ -806,7 +806,6 @@ dependencies = [ { name = "pycryptodome" }, { name = "pyjwt" }, { name = "pyserial" }, - { name = "python3-dev" }, { name = "pyzmq" }, { name = "qrcode" }, { name = "raylib" }, @@ -897,7 +896,6 @@ requires-dist = [ { name = "pytest-mock", marker = "extra == 'testing'" }, { name = "pytest-subtests", marker = "extra == 'testing'" }, { name = "pytest-xdist", marker = "extra == 'testing'", git = "https://github.com/sshane/pytest-xdist?rev=2b4372bd62699fb412c4fe2f95bf9f01bd2018da" }, - { name = "python3-dev", git = "https://github.com/commaai/dependencies.git?subdirectory=python3-dev&rev=releases" }, { name = "pyzmq" }, { name = "qrcode" }, { name = "raylib", specifier = ">5.5.0.3" }, @@ -1286,11 +1284,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" }, ] -[[package]] -name = "python3-dev" -version = "3.12.8" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=python3-dev&rev=releases#9777ee38aa5ca9439843125392af38ed1262e500" } - [[package]] name = "pyyaml" version = "6.0.3" From bf4bf0e5b7faa98212f62786bdd13863feeb85a4 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Tue, 10 Mar 2026 11:44:25 -0700 Subject: [PATCH 101/141] qcomgpsd, timed: reject invalid GPS timestamps (#37633) --- common/time_helpers.py | 3 ++- system/qcomgpsd/qcomgpsd.py | 3 +++ system/timed.py | 4 ++-- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/common/time_helpers.py b/common/time_helpers.py index 8564e270c2..c709182d45 100644 --- a/common/time_helpers.py +++ b/common/time_helpers.py @@ -2,6 +2,7 @@ import datetime from pathlib import Path MIN_DATE = datetime.datetime(year=2025, month=2, day=21) +MAX_DATE = datetime.datetime(year=2035, month=1, day=1) def min_date(): # on systemd systems, the default time is the systemd build time @@ -12,4 +13,4 @@ def min_date(): return MIN_DATE def system_time_valid(): - return datetime.datetime.now() > min_date() + return min_date() < datetime.datetime.now() < MAX_DATE diff --git a/system/qcomgpsd/qcomgpsd.py b/system/qcomgpsd/qcomgpsd.py index 82b9ea927f..47d64ff4e9 100755 --- a/system/qcomgpsd/qcomgpsd.py +++ b/system/qcomgpsd/qcomgpsd.py @@ -357,6 +357,9 @@ def main() -> NoReturn: report = unpack_position(log_payload) if report["u_PosSource"] != 2: continue + # uint16_t max is an invalid sentinel value from the modem + if report['w_GpsWeekNumber'] >= 0xFFFF: + continue vNED = [report["q_FltVelEnuMps[1]"], report["q_FltVelEnuMps[0]"], -report["q_FltVelEnuMps[2]"]] vNEDsigma = [report["q_FltVelSigmaMps[1]"], report["q_FltVelSigmaMps[0]"], -report["q_FltVelSigmaMps[2]"]] diff --git a/system/timed.py b/system/timed.py index b7131b04c0..c74ba51da5 100755 --- a/system/timed.py +++ b/system/timed.py @@ -5,7 +5,7 @@ import time from typing import NoReturn import cereal.messaging as messaging -from openpilot.common.time_helpers import min_date, system_time_valid +from openpilot.common.time_helpers import min_date, MAX_DATE, system_time_valid from openpilot.common.swaglog import cloudlog from openpilot.common.params import Params from openpilot.common.gps import get_gps_location_service @@ -52,7 +52,7 @@ def main() -> NoReturn: continue if not gps.hasFix: continue - if gps_time < min_date(): + if gps_time < min_date() or gps_time > MAX_DATE: continue set_time(gps_time) From dd89bc30fa131d7a859f5538f10c99509228dbbf Mon Sep 17 00:00:00 2001 From: Trey Moen <50057480+greatgitsby@users.noreply.github.com> Date: Tue, 10 Mar 2026 15:08:56 -0700 Subject: [PATCH 102/141] set preference for python 3.12.13 (#37637) --- .python-version | 1 + 1 file changed, 1 insertion(+) create mode 100644 .python-version diff --git a/.python-version b/.python-version new file mode 100644 index 0000000000..28d9a01b1f --- /dev/null +++ b/.python-version @@ -0,0 +1 @@ +3.12.13 From 592731678813565fd4cd4f25fad8a9d45f844abd Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Tue, 10 Mar 2026 15:57:26 -0700 Subject: [PATCH 103/141] ci: revert first-interaction to v1 (#37639) * ci: revert first-interaction to v1 * ci: retrigger PR review on synchronize --- .github/workflows/auto_pr_review.yaml | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/.github/workflows/auto_pr_review.yaml b/.github/workflows/auto_pr_review.yaml index 63cb062ebe..6052ec0712 100644 --- a/.github/workflows/auto_pr_review.yaml +++ b/.github/workflows/auto_pr_review.yaml @@ -36,12 +36,11 @@ jobs: # Welcome comment - name: "First timers PR" - uses: actions/first-interaction@v3 + uses: actions/first-interaction@v1 if: github.event.pull_request.head.repo.full_name != 'commaai/openpilot' with: - repo_token: ${{ secrets.GITHUB_TOKEN }} - issue_message: "" - pr_message: | + repo-token: ${{ secrets.GITHUB_TOKEN }} + pr-message: | Thanks for contributing to openpilot! In order for us to review your PR as quickly as possible, check the following: * Convert your PR to a draft unless it's ready to review From 40b61a82122d5ec41cecdbe5cfaff8415c5726c2 Mon Sep 17 00:00:00 2001 From: David <49467229+TheSecurityDev@users.noreply.github.com> Date: Tue, 10 Mar 2026 18:01:31 -0500 Subject: [PATCH 104/141] clip: load metadata params within OpenpilotPrefix (#37634) fix: move metadata loading inside OpenpilotPrefix context --- tools/clip/run.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/clip/run.py b/tools/clip/run.py index a5587f239b..2deb95dede 100755 --- a/tools/clip/run.py +++ b/tools/clip/run.py @@ -302,11 +302,11 @@ def clip(route: Route, output: str, start: int, end: int, headless: bool = True, logger.error("No messages to render") sys.exit(1) - metadata = load_route_metadata(route) if show_metadata else None if headless: rl.set_config_flags(rl.ConfigFlags.FLAG_WINDOW_HIDDEN) with OpenpilotPrefix(shared_download_cache=True): + metadata = load_route_metadata(route) if show_metadata else None camera_paths = route.qcamera_paths() if use_qcam else route.camera_paths() frame_queue = FrameQueue(camera_paths, start, end, fps=FRAMERATE, use_qcam=use_qcam) From b750229e7087c896a0cbd1af3b263156cfcf96f4 Mon Sep 17 00:00:00 2001 From: David <49467229+TheSecurityDev@users.noreply.github.com> Date: Tue, 10 Mar 2026 18:02:02 -0500 Subject: [PATCH 105/141] fix(sim): remove alpha channel for improved performance (#37602) fix: update RGB image processing in CopyRamRGBCamera --- tools/sim/bridge/metadrive/metadrive_common.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/tools/sim/bridge/metadrive/metadrive_common.py b/tools/sim/bridge/metadrive/metadrive_common.py index 079d933d1a..0106579b20 100644 --- a/tools/sim/bridge/metadrive/metadrive_common.py +++ b/tools/sim/bridge/metadrive/metadrive_common.py @@ -13,11 +13,9 @@ class CopyRamRGBCamera(RGBCamera): def get_rgb_array_cpu(self): origin_img = self.cpu_texture - img = np.frombuffer(origin_img.getRamImageAs("RGBA").getData(), dtype=np.uint8) - img = img.reshape((origin_img.getYSize(), origin_img.getXSize(), 4)) - img = img[:, :, :3] - # img = np.swapaxes(img, 1, 0) - img = img[::-1] # Flip on vertical axis + img = np.frombuffer(origin_img.getRamImageAs("RGB").getData(), dtype=np.uint8) + img = img.reshape((origin_img.getYSize(), origin_img.getXSize(), 3)) + img = img[::-1] # Flip on vertical axis return img From f85b3473a22ba406d25d762e0afc9d8f6515bff7 Mon Sep 17 00:00:00 2001 From: David <49467229+TheSecurityDev@users.noreply.github.com> Date: Tue, 10 Mar 2026 18:02:55 -0500 Subject: [PATCH 106/141] ui replay: Improve big (tizi) replay coverage (#37468) * fix pairing qr code * test pair device * merge and pick from explore-more * key * fast click * again * add branch selection test * click uninstall * test prime states * view regulatory * test expand calibration desc * override interactive timeout * reorder * remove todo * update * clarify * test reset calibration * update * add calibration params test * comments * reorganize * clarify * add click through training guide --- selfdrive/ui/tests/diff/replay.py | 14 ++- selfdrive/ui/tests/diff/replay_script.py | 148 ++++++++++++++++++----- 2 files changed, 133 insertions(+), 29 deletions(-) diff --git a/selfdrive/ui/tests/diff/replay.py b/selfdrive/ui/tests/diff/replay.py index 9860969efb..49e17a6c2a 100755 --- a/selfdrive/ui/tests/diff/replay.py +++ b/selfdrive/ui/tests/diff/replay.py @@ -7,9 +7,12 @@ import pyray as rl from typing import Literal from collections.abc import Callable from cereal.messaging import PubMaster +from openpilot.common.api import Api +from openpilot.common.basedir import BASEDIR from openpilot.common.params import Params from openpilot.common.prefix import OpenpilotPrefix from openpilot.selfdrive.ui.tests.diff.diff import DIFF_OUT_DIR +from openpilot.system.updated.updated import parse_release_notes from openpilot.system.version import terms_version, training_version LayoutVariant = Literal["mici", "tizi"] @@ -25,13 +28,16 @@ def setup_state(): params.put("DongleId", "test123456789") # Combined description for layouts that still use it (BIG home, settings/software) params.put("UpdaterCurrentDescription", "0.10.1 / test-branch / abc1234 / Nov 30") - + params.put("UpdaterCurrentReleaseNotes", parse_release_notes(BASEDIR)) # Params for mici home params.put("Version", "0.10.1") params.put("GitBranch", "test-branch") params.put("GitCommit", "abc12340ff9131237ba23a1d0fbd8edf9c80e87") params.put("GitCommitDate", "'1732924800 2024-11-30 00:00:00 +0000'") + # Patch Api.get_token to return a static token so the pairing QR code is deterministic across runs + Api.get_token = lambda self, payload_extra=None, expiry_hours=0: "test_token" + def run_replay(variant: LayoutVariant) -> None: if HEADLESS: @@ -41,7 +47,7 @@ def run_replay(variant: LayoutVariant) -> None: setup_state() os.makedirs(DIFF_OUT_DIR, exist_ok=True) - from openpilot.selfdrive.ui.ui_state import ui_state # Import within OpenpilotPrefix context so param values are setup correctly + from openpilot.selfdrive.ui.ui_state import ui_state, device # Import within OpenpilotPrefix context so param values are setup correctly from openpilot.system.ui.lib.application import gui_app # Import here for accurate coverage from openpilot.selfdrive.ui.tests.diff.replay_script import build_script @@ -54,6 +60,10 @@ def run_replay(variant: LayoutVariant) -> None: from openpilot.selfdrive.ui.layouts.main import MainLayout main_layout = MainLayout() + # Disable interactive timeout — replay clicks use left_down=False so they never reset the timer, + # and after 30s of real wall-clock time the settings panel would close automatically. + device.set_override_interactive_timeout(99999) + pm = PubMaster(["deviceState", "pandaStates", "driverStateV2", "selfdriveState"]) script = build_script(pm, main_layout, variant) script_index = 0 diff --git a/selfdrive/ui/tests/diff/replay_script.py b/selfdrive/ui/tests/diff/replay_script.py index 9f2104ec49..c43442a33d 100644 --- a/selfdrive/ui/tests/diff/replay_script.py +++ b/selfdrive/ui/tests/diff/replay_script.py @@ -3,15 +3,20 @@ from typing import TYPE_CHECKING from collections.abc import Callable from dataclasses import dataclass +import math + from cereal import car, log, messaging from cereal.messaging import PubMaster from openpilot.common.basedir import BASEDIR from openpilot.common.params import Params from openpilot.selfdrive.selfdrived.alertmanager import set_offroad_alert +from openpilot.selfdrive.ui.lib.prime_state import PrimeType from openpilot.selfdrive.ui.tests.diff.replay import FPS, LayoutVariant from openpilot.system.updated.updated import parse_release_notes -WAIT = int(FPS * 0.5) # Default frames to wait after events +# Default frames to wait after events +WAIT = FPS // 2 +FAST_CLICK = FPS // 6 AlertSize = log.SelfdriveState.AlertSize AlertStatus = log.SelfdriveState.AlertStatus @@ -79,26 +84,48 @@ class Script: # --- Setup functions --- -def put_update_params(params: Params | None = None) -> None: - if params is None: - params = Params() - params.put("UpdaterCurrentReleaseNotes", parse_release_notes(BASEDIR)) - params.put("UpdaterNewReleaseNotes", parse_release_notes(BASEDIR)) - params.put("UpdaterTargetBranch", BRANCH_NAME) + +def set_prime_state(prime_type: PrimeType) -> None: + from openpilot.selfdrive.ui.ui_state import ui_state + ui_state.prime_state.set_type(prime_type) def setup_offroad_alerts() -> None: - put_update_params(Params()) set_offroad_alert("Offroad_TemperatureTooHigh", True, extra_text='99C') set_offroad_alert("Offroad_ExcessiveActuation", True, extra_text='longitudinal') set_offroad_alert("Offroad_IsTakingSnapshot", True) -def setup_update_available() -> None: +def setup_update_available(available: bool = True) -> None: params = Params() - params.put_bool("UpdateAvailable", True) - params.put("UpdaterNewDescription", f"0.10.2 / {BRANCH_NAME} / 0a1b2c3 / Jan 01") - put_update_params(params) + params.put_bool("UpdateAvailable", available) + params.put("UpdaterAvailableBranches", ",".join(["test-branch", "test-branch-2", BRANCH_NAME])) + if available: + params.put("UpdaterNewDescription", f"0.10.2 / {BRANCH_NAME} / 0a1b2c3 / Jan 01") + params.put("UpdaterNewReleaseNotes", parse_release_notes(BASEDIR)) + params.put("UpdaterTargetBranch", BRANCH_NAME) + else: + params.remove("UpdaterNewDescription") + params.remove("UpdaterNewReleaseNotes") + params.remove("UpdaterTargetBranch") + + +def setup_calibration_params() -> None: + params = Params() + # live calibration + calib = messaging.new_message('liveCalibration') + calib.liveCalibration.calStatus = log.LiveCalibrationData.Status.calibrated + calib.liveCalibration.rpyCalib = [0.0, math.radians(2.5), math.radians(-1.2)] + params.put("CalibrationParams", calib.to_bytes()) + # live delay + delay = messaging.new_message('liveDelay') + delay.liveDelay.calPerc = 75 + params.put("LiveDelay", delay.to_bytes()) + # live torque parameters + torque = messaging.new_message('liveTorqueParameters') + torque.liveTorqueParameters.useParams = True + torque.liveTorqueParameters.calPerc = 60 + params.put("LiveTorqueParameters", torque.to_bytes()) def setup_developer_params() -> None: @@ -132,7 +159,6 @@ def make_network_state_setup(pm: PubMaster, network_type) -> Callable: def make_alert_setup(pm: PubMaster, size, text1, text2, status) -> Callable: def _send() -> None: - send_onroad(pm) alert = messaging.new_message('selfdriveState') ss = alert.selfdriveState ss.alertSize = size @@ -171,34 +197,104 @@ def build_tizi_script(pm: PubMaster, main_layout, script: Script) -> None: return setup + def add_prime_state_setup(prime_type: PrimeType) -> None: + script.set_send(lambda: set_prime_state(prime_type)) + + def do_onboarding() -> None: + """Click through the training guide and close.""" + from openpilot.selfdrive.ui.layouts.onboarding import STEP_RECTS + step = 0 + for step_rect in STEP_RECTS: + if step < len(STEP_RECTS) - 1: + script.click(int(step_rect.x), int(step_rect.y), wait_after=FAST_CLICK) + else: + script.click(950, 900) # On the last step, click Finish instead of restart + step += 1 + + def type_keyboard() -> None: + """Types 8 characters using the big keyboard to test different layouts and interactions.""" + KEY = (150, 430) # e.g. 'Q' key + SHIFT = (150, 750) # also symbols key in number mode + NUMBERS = (150, 950) + SPACE = (1060, 950) + BACKSPACE = (2000, 780) + for key in [ + SHIFT, KEY, KEY, SHIFT, SHIFT, KEY, KEY, # test casing (upper, lower, caps lock) + SPACE, SPACE, BACKSPACE, BACKSPACE, # test multiple space and backspace + NUMBERS, KEY, KEY, SHIFT, KEY, KEY # test numbers and symbols + ]: + script.click(*key, wait_after=FAST_CLICK) + # TODO: Better way of organizing the events # === Homescreen === script.set_send(make_network_state_setup(pm, log.DeviceState.NetworkType.wifi)) - - # === Offroad Alerts (auto-transitions via HomeLayout refresh) === - script.setup(make_home_refresh_setup(setup_offroad_alerts)) + # Go through different prime state layouts + add_prime_state_setup(PrimeType.LITE) + add_prime_state_setup(PrimeType.NONE) + add_prime_state_setup(PrimeType.UNPAIRED) # === Update Available (auto-transitions via HomeLayout refresh) === script.setup(make_home_refresh_setup(setup_update_available)) - # === Settings - Device (click sidebar settings button) === + # === Offroad Alerts (auto-transitions via HomeLayout refresh, overrides update) === + script.setup(make_home_refresh_setup(setup_offroad_alerts)) + script.click(620, 950) # close alerts + + # === Settings (click sidebar settings button) === script.click(150, 90) - script.click(1985, 790) # reset calibration confirmation - script.click(650, 750) # cancel + + # === Settings - Device === + # pair device + script.click(2000, 450) # pair device + script.click(110, 110) # close pairing dialog + add_prime_state_setup(PrimeType.NONE) # changed from unpaired to hide pair device button + # calibration + script.setup(setup_calibration_params, wait_after=0) + script.click(1000, 620) # expand calibration description + script.click(2000, 620) # reset calibration confirmation + script.click(1500, 750) # confirm reset + script.click(1000, 620) # collapse calibration description + # training guide + script.click(2000, 800) # open training guide + do_onboarding() + # regulatory info + script.click(2000, 970) # regulatory button + script.click(2000, 970) # OK # === Settings - Network === script.click(278, 450) + # TODO: mock networks script.click(1880, 100) # advanced network settings - script.click(630, 80) # back + + # Keyboard (tethering password) + script.click(2000, 420, wait_after=FAST_CLICK) # open tether password keyboard + script.click(2000, 950, wait_after=FAST_CLICK) # click confirm (disabled, should not close) + script.click(2000, 115) # cancel (close without typing) + script.click(2000, 420, wait_after=FAST_CLICK) # open keyboard again + type_keyboard() # test various keyboard layouts and interactions + script.click(2050, 250, wait_after=FAST_CLICK) # toggle show/hide password + script.click(2000, 950) # confirm (close keyboard) + + script.click(630, 80) # back from advanced network # === Settings - Toggles === script.click(278, 600) - script.click(1200, 280) # experimental mode description + script.click(1200, 280) # expand experimental mode description # === Settings - Software === - script.setup(put_update_params, wait_after=0) - script.click(278, 720) + script.setup(lambda: setup_update_available(False), wait_after=0) # start with no update available + script.click(278, 720) # software + for _ in range(2): + script.click(720, 120) # toggle current release notes + script.setup(setup_update_available) # set update available + for _ in range(2): + script.click(720, 450) # toggle new release notes + script.click(2000, 630) # open select branch dialog + script.click(1000, 300) # select 1st option + script.click(1600, 900) # confirm selection + script.click(2000, 800) # uninstall + script.click(650, 750) # cancel uninstall # === Settings - Firehose === script.click(278, 845) @@ -206,13 +302,11 @@ def build_tizi_script(pm: PubMaster, main_layout, script: Script) -> None: # === Settings - Developer (set CarParamsPersistent first) === script.setup(setup_developer_params, wait_after=0) script.click(278, 950) + script.click(1930, 470) # SSH keys (keyboard) + script.click(1930, 115) # click cancel on keyboard script.click(2000, 960) # toggle alpha long script.click(1500, 875) # confirm - # === Keyboard modal (SSH keys button in developer panel) === - script.click(1930, 470) # click SSH keys - script.click(1930, 115) # click cancel on keyboard - # === Close settings === script.click(250, 160) From d55ccba5fe7174c52ff3cbaa4b7c067f445da54e Mon Sep 17 00:00:00 2001 From: David <49467229+TheSecurityDev@users.noreply.github.com> Date: Tue, 10 Mar 2026 18:42:38 -0500 Subject: [PATCH 107/141] clip: only fast rendering when headless (#37635) only set offscreen when headless --- tools/clip/run.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/tools/clip/run.py b/tools/clip/run.py index 2deb95dede..ed2a5075b4 100755 --- a/tools/clip/run.py +++ b/tools/clip/run.py @@ -63,8 +63,10 @@ def parse_args(): return args -def setup_env(output_path: str, big: bool = False, speed: int = 1, target_mb: float = 0, duration: int = 0): - os.environ.update({"RECORD": "1", "OFFSCREEN": "1", "RECORD_OUTPUT": str(Path(output_path).with_suffix(".mp4"))}) +def setup_env(output_path: str, big: bool = False, speed: int = 1, target_mb: float = 0, duration: int = 0, headless: bool = True): + os.environ.update({"RECORD": "1", "RECORD_OUTPUT": str(Path(output_path).with_suffix(".mp4"))}) + if headless: + os.environ["OFFSCREEN"] = "1" if speed > 1: os.environ["RECORD_SPEED"] = str(speed) if target_mb > 0 and duration > 0: @@ -349,8 +351,9 @@ def main(): logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s\t%(message)s") args = parse_args() - setup_env(args.output, big=args.big, speed=args.speed, target_mb=args.file_size, duration=args.end - args.start) - clip(Route(args.route, data_dir=args.data_dir), args.output, args.start, args.end, not args.windowed, + headless = not args.windowed + setup_env(args.output, big=args.big, speed=args.speed, target_mb=args.file_size, duration=args.end - args.start, headless=headless) + clip(Route(args.route, data_dir=args.data_dir), args.output, args.start, args.end, headless, args.big, args.title, not args.no_metadata, not args.no_time_overlay, args.qcam) From 0ce679f687985bec6d10c57d8baa65ce9e8f6ee4 Mon Sep 17 00:00:00 2001 From: David <49467229+TheSecurityDev@users.noreply.github.com> Date: Tue, 10 Mar 2026 18:42:56 -0500 Subject: [PATCH 108/141] ui replay: Add progress bar (#37471) * add replay progress bar * simplify * use frames instead * update * disable in CI * +1 --- selfdrive/ui/tests/diff/replay.py | 49 ++++++++++++++++--------------- 1 file changed, 26 insertions(+), 23 deletions(-) diff --git a/selfdrive/ui/tests/diff/replay.py b/selfdrive/ui/tests/diff/replay.py index 49e17a6c2a..b38026048d 100755 --- a/selfdrive/ui/tests/diff/replay.py +++ b/selfdrive/ui/tests/diff/replay.py @@ -4,6 +4,7 @@ import argparse import coverage import pyray as rl +from tqdm import tqdm from typing import Literal from collections.abc import Callable from cereal.messaging import PubMaster @@ -75,33 +76,35 @@ def run_replay(variant: LayoutVariant) -> None: rl.get_time = lambda: frame / FPS # Main loop to replay events and render frames - for _ in gui_app.render(): - # Handle all events for the current frame - while script_index < len(script) and script[script_index][0] == frame: - _, event = script[script_index] - # Call setup function, if any - if event.setup: - event.setup() - # Send mouse events to the application - if event.mouse_events: - with gui_app._mouse._lock: - gui_app._mouse._events.extend(event.mouse_events) - # Update persistent send function - if event.send_fn is not None: - send_fn = event.send_fn - # Move to next script event - script_index += 1 + with tqdm(total=script[-1][0] + 1, desc="Replaying", unit="frame", disable=bool(os.getenv("CI"))) as pbar: + for _ in gui_app.render(): + # Handle all events for the current frame + while script_index < len(script) and script[script_index][0] == frame: + _, event = script[script_index] + # Call setup function, if any + if event.setup: + event.setup() + # Send mouse events to the application + if event.mouse_events: + with gui_app._mouse._lock: + gui_app._mouse._events.extend(event.mouse_events) + # Update persistent send function + if event.send_fn is not None: + send_fn = event.send_fn + # Move to next script event + script_index += 1 - # Keep sending cereal messages for persistent states (onroad, alerts) - if send_fn: - send_fn() + # Keep sending cereal messages for persistent states (onroad, alerts) + if send_fn: + send_fn() - ui_state.update() + ui_state.update() - frame += 1 + frame += 1 + pbar.update(1) - if script_index >= len(script): - break + if script_index >= len(script): + break gui_app.close() From d3bcc80d284fd5c72e40ed157758b0625010e550 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Tue, 10 Mar 2026 17:01:23 -0700 Subject: [PATCH 109/141] jenkins: push mici and tizi builds together --- Jenkinsfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 5c785f1d98..39175d89e3 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -167,7 +167,7 @@ node { env.GIT_COMMIT = checkout(scm).GIT_COMMIT def excludeBranches = ['__nightly', 'devel', 'devel-staging', 'release3', 'release3-staging', - 'release-tici', 'release-tizi', 'release-tizi-staging', 'testing-closet*', 'hotfix-*'] + 'release-tici', 'release-tizi', 'release-tizi-staging', 'release-mici-staging', 'testing-closet*', 'hotfix-*'] def excludeRegex = excludeBranches.join('|').replaceAll('\\*', '.*') if (env.BRANCH_NAME != 'master' && !env.BRANCH_NAME.contains('__jenkins_loop_')) { @@ -179,7 +179,7 @@ node { try { if (env.BRANCH_NAME == 'devel-staging') { deviceStage("build release-tizi-staging", "tizi-needs-can", [], [ - step("build release-tizi-staging", "RELEASE_BRANCH=release-tizi-staging $SOURCE_DIR/release/build_release.sh"), + step("build release-tizi-staging", "RELEASE_BRANCH=release-tizi-staging $SOURCE_DIR/release/build_release.sh && git push -f origin release-tizi-staging:release-mici-staging"), ]) } From 2e82908c0788e5613d8a7cea943934edcb32eeab Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Tue, 10 Mar 2026 20:30:25 -0400 Subject: [PATCH 110/141] pandad: always prioritize internal panda (#1759) * pandad: filter out external panda * fix * internal panda * move it even higher * this * should be this still * anoter * more * 1 more time * bruh * try this out * revert * gotta do this after * filter --- selfdrive/pandad/pandad.py | 26 +++++++++---------- .../selfdrive/pandad/rivian_long_flasher.py | 4 +++ 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/selfdrive/pandad/pandad.py b/selfdrive/pandad/pandad.py index a8300de321..f65c64259f 100755 --- a/selfdrive/pandad/pandad.py +++ b/selfdrive/pandad/pandad.py @@ -68,21 +68,20 @@ def flash_panda(panda_serial: str) -> Panda: return panda -def check_panda_support(panda_serials: list[str]) -> bool: - unsupported = [] +def check_panda_support(panda_serials: list[str]) -> list[str]: + spi_serials = set(Panda.spi_list()) + for serial in panda_serials: + if serial in spi_serials: + return [serial] + for serial in panda_serials: panda = Panda(serial) - hw_type = panda.get_type() + is_internal = panda.is_internal() panda.close() - if hw_type in Panda.SUPPORTED_DEVICES: - return True + if is_internal: + return [serial] - unsupported.append((serial, hw_type)) - - for serial, hw_type in unsupported: - cloudlog.warning(f"Panda {serial} is not supported (hw_type: {hw_type}), skipping...") - - return False + return [] def main() -> None: @@ -137,8 +136,9 @@ def main() -> None: # custom flasher for xnor's Rivian Longitudinal Upgrade Kit flash_rivian_long(panda_serials) - # skip flashing and health check if no supported panda is detected - if not check_panda_support(panda_serials): + # find the internal supported panda (e.g. skip external Black Panda) + panda_serials = check_panda_support(panda_serials) + if len(panda_serials) == 0: continue # Flash the first panda diff --git a/sunnypilot/selfdrive/pandad/rivian_long_flasher.py b/sunnypilot/selfdrive/pandad/rivian_long_flasher.py index 70ddb44358..305b994c78 100755 --- a/sunnypilot/selfdrive/pandad/rivian_long_flasher.py +++ b/sunnypilot/selfdrive/pandad/rivian_long_flasher.py @@ -84,7 +84,11 @@ def flash_rivian_long(panda_serials: list[str]) -> None: cloudlog.info("Not a Rivian, skipping longitudinal upgrade...") return + # only check USB connected pandas, internal panda uses SPI and is never an external panda + usb_serials = set(Panda.usb_list()) for serial in panda_serials: + if serial not in usb_serials: + continue panda = Panda(serial) # only flash external black pandas (HW_TYPE_BLACK = 0x03) if panda.get_type() == b'\x03' and not panda.is_internal(): From 3584523a93991f23f10e3795a2c25dcd814552ee Mon Sep 17 00:00:00 2001 From: Daniel Koepping Date: Tue, 10 Mar 2026 20:27:40 -0700 Subject: [PATCH 111/141] fix process replay race on push (#37643) --- .github/workflows/tests.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 8add04c465..f4be0ad5a6 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -164,7 +164,7 @@ jobs: echo "${{ github.sha }}" > ref_commit git add . git commit -m "process-replay refs for ${{ github.repository }}@${{ github.sha }}" || echo "No changes to commit" - git push origin process-replay + git push origin process-replay --force - name: Run regen if: false timeout-minutes: 4 From bea040095c75a1292541beff1e8e58df936d3996 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 10 Mar 2026 20:44:56 -0700 Subject: [PATCH 112/141] Make sliders children --- selfdrive/ui/mici/widgets/dialog.py | 4 ++-- system/ui/mici_setup.py | 5 ++--- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/selfdrive/ui/mici/widgets/dialog.py b/selfdrive/ui/mici/widgets/dialog.py index 94091b9269..1f67b987f1 100644 --- a/selfdrive/ui/mici/widgets/dialog.py +++ b/selfdrive/ui/mici/widgets/dialog.py @@ -72,9 +72,9 @@ class BigConfirmationDialog(BigDialogBase): self._slider: BigSlider | RedBigSlider if red: - self._slider = RedBigSlider(title, icon, confirm_callback=self._on_confirm) + self._slider = self._child(RedBigSlider(title, icon, confirm_callback=self._on_confirm)) else: - self._slider = BigSlider(title, icon, confirm_callback=self._on_confirm) + self._slider = self._child(BigSlider(title, icon, confirm_callback=self._on_confirm)) self._slider.set_enabled(lambda: self.enabled and not self.is_dismissing) # for nav stack + NavWidget def _on_confirm(self): diff --git a/system/ui/mici_setup.py b/system/ui/mici_setup.py index 616991b560..f2e18cde71 100755 --- a/system/ui/mici_setup.py +++ b/system/ui/mici_setup.py @@ -130,9 +130,9 @@ class SoftwareSelectionPage(NavWidget): use_custom_software_callback: Callable): super().__init__() - self._openpilot_slider = LargerSlider("slide to install\nopenpilot", use_openpilot_callback) + self._openpilot_slider = self._child(LargerSlider("slide to install\nopenpilot", use_openpilot_callback)) self._openpilot_slider.set_enabled(lambda: self.enabled and not self.is_dismissing) - self._custom_software_slider = LargerSlider("slide to install\ncustom software", use_custom_software_callback, green=False) + self._custom_software_slider = self._child(LargerSlider("slide to install\ncustom software", use_custom_software_callback, green=False)) self._custom_software_slider.set_enabled(lambda: self.enabled and not self.is_dismissing) def show_event(self): @@ -478,7 +478,6 @@ class Setup(Widget): self._network_monitor.start() def getting_started_button_callback(): - self._software_selection_page.reset() gui_app.push_widget(self._software_selection_page) self._start_page = StartPage() From 50f0cf25a61c4b3db8ece03b808def4ae359e0a0 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 10 Mar 2026 20:53:17 -0700 Subject: [PATCH 113/141] ui: slider shimmer sans shader (#37640) * actually epic * use child * inside label * revert other stuff * no reset_shimmer: bool * try 2 char * not worth dynamic chunking * bring back * rm * no emoji support on shimmer --- system/ui/widgets/label.py | 70 +++++++++++++++++++++++++++++++++++-- system/ui/widgets/slider.py | 31 +++++++++------- 2 files changed, 86 insertions(+), 15 deletions(-) diff --git a/system/ui/widgets/label.py b/system/ui/widgets/label.py index 0b2444ec65..7fe25ab51d 100644 --- a/system/ui/widgets/label.py +++ b/system/ui/widgets/label.py @@ -1,3 +1,4 @@ +import math from enum import IntEnum from collections.abc import Callable from itertools import zip_longest @@ -241,6 +242,13 @@ class UnifiedLabel(Widget): - Proper multiline vertical alignment - Height calculation for layout purposes """ + # Shimmer constants + SHIMMER_BAND_WIDTH = 0.3 # shimmer width as fraction of text width + SHIMMER_BLUR_RADIUS = 0.12 # gaussian blur as fraction of text width + SHIMMER_CYCLE_PERIOD = 2.5 # seconds per full shimmer cycle + SHIMMER_SWEEP_FRACTION = 0.9 # fraction of cycle spent sweeping (rest is pause) + SHIMMER_LOW_OPACITY = 0.65 # text opacity at rest, shimmer brings to 1.0 + def __init__(self, text: str | Callable[[], str], font_size: int = DEFAULT_TEXT_SIZE, @@ -254,7 +262,8 @@ class UnifiedLabel(Widget): wrap_text: bool = True, scroll: bool = False, line_height: float = 1.0, - letter_spacing: float = 0.0): + letter_spacing: float = 0.0, + shimmer: bool = False): super().__init__() self._text = text self._font_size = font_size @@ -272,6 +281,10 @@ class UnifiedLabel(Widget): self._letter_spacing = letter_spacing # 0.1 = 10% self._spacing_pixels = font_size * letter_spacing + # Shimmer state + self._shimmer = shimmer + self._shimmer_start_time = 0.0 + # Scroll state self._scroll = scroll self._needs_scroll = False @@ -365,6 +378,15 @@ class UnifiedLabel(Widget): self._scroll_pause_t = None self._scroll_state = ScrollState.STARTING + def show_event(self): + super().show_event() + if self._shimmer: + self.reset_shimmer() + + def reset_shimmer(self, offset: float = 0.0): + """Reset shimmer animation timing.""" + self._shimmer_start_time = rl.get_time() + offset + def set_max_width(self, max_width: int | None): """Set the maximum width constraint for wrapping/eliding.""" if self._max_width != max_width: @@ -618,6 +640,24 @@ class UnifiedLabel(Widget): rl.end_scissor_mode() + def _shimmer_alpha(self, char_x: float, shimmer_left: float, shimmer_width: float) -> float: + """Compute shimmer opacity multiplier for a character at the given x position.""" + sigma = shimmer_width * self.SHIMMER_BLUR_RADIUS + if sigma <= 0: + return self.SHIMMER_LOW_OPACITY + + elapsed = rl.get_time() - self._shimmer_start_time + t_raw = (elapsed % self.SHIMMER_CYCLE_PERIOD) / self.SHIMMER_CYCLE_PERIOD + t_clamped = max(0.0, min(t_raw / self.SHIMMER_SWEEP_FRACTION, 1.0)) + t = t_clamped * t_clamped * (3.0 - 2.0 * t_clamped) # smoothstep + + margin = shimmer_width * self.SHIMMER_BAND_WIDTH + center = shimmer_left + shimmer_width + margin - t * (shimmer_width + 2.0 * margin) + + d = char_x - center + shimmer = math.exp(-0.5 * d * d / (sigma * sigma)) + return self.SHIMMER_LOW_OPACITY + (1.0 - self.SHIMMER_LOW_OPACITY) * shimmer + def _render_line(self, line, size, emojis, current_y, x_offset=0.0): # Calculate horizontal position if self._alignment == rl.GuiTextAlignment.TEXT_ALIGN_LEFT: @@ -630,7 +670,13 @@ class UnifiedLabel(Widget): line_x = self._rect.x + self._text_padding line_x += self._scroll_offset + x_offset - # Render line with emojis + if self._shimmer: + self._render_line_shimmer(line, line_x, current_y) + else: + # Render line with emojis + self._render_line_normal(line, emojis, line_x, current_y) + + def _render_line_normal(self, line, emojis, line_x, current_y): line_pos = rl.Vector2(line_x, current_y) prev_index = 0 @@ -654,3 +700,23 @@ class UnifiedLabel(Widget): text_after = line[prev_index:] if text_after: rl.draw_text_ex(self._font, text_after, line_pos, self._font_size, self._spacing_pixels, self._text_color) + + def _render_line_shimmer(self, line, line_x, current_y): + # Shimmer range based on widest line so sweep is even across all lines + max_width = self.text_width + if self._alignment == rl.GuiTextAlignment.TEXT_ALIGN_RIGHT: + shimmer_left = self._rect.x + self._rect.width - self._text_padding - max_width + elif self._alignment == rl.GuiTextAlignment.TEXT_ALIGN_CENTER: + shimmer_left = self._rect.x + (self._rect.width - max_width) / 2 + else: + shimmer_left = self._rect.x + self._text_padding + + base_a = self._text_color.a / 255.0 + cursor_x = line_x + for ch in line: + char_width = measure_text_cached(self._font, ch, self._font_size, self._spacing_pixels).x + char_center_x = cursor_x + char_width / 2.0 + alpha = int(255 * self._shimmer_alpha(char_center_x, shimmer_left, max_width) * base_a) + color = rl.Color(self._text_color.r, self._text_color.g, self._text_color.b, alpha) + rl.draw_text_ex(self._font, ch, rl.Vector2(cursor_x, current_y), self._font_size, 0, color) + cursor_x += char_width + self._spacing_pixels diff --git a/system/ui/widgets/slider.py b/system/ui/widgets/slider.py index 7a10548acc..bf965954f2 100644 --- a/system/ui/widgets/slider.py +++ b/system/ui/widgets/slider.py @@ -19,9 +19,10 @@ class SliderBase(Widget, abc.ABC): _circle_bg_pressed_txt: rl.Texture _circle_arrow_txt: rl.Texture - def __init__(self, title: str, confirm_callback: Callable | None = None): + def __init__(self, title: str, confirm_callback: Callable | None = None, shimmer_offset: float = 0.0): super().__init__() self._confirm_callback = confirm_callback + self._shimmer_offset = shimmer_offset self._load_assets() @@ -39,9 +40,9 @@ class SliderBase(Widget, abc.ABC): self._is_dragging_circle = False - self._label = UnifiedLabel(title, font_size=36, font_weight=FontWeight.SEMI_BOLD, text_color=rl.Color(255, 255, 255, int(255 * 0.65)), - alignment=rl.GuiTextAlignment.TEXT_ALIGN_RIGHT, - alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_MIDDLE, line_height=0.9) + self._label = self._child(UnifiedLabel(title, font_size=36, font_weight=FontWeight.SEMI_BOLD, text_color=rl.WHITE, + alignment=rl.GuiTextAlignment.TEXT_ALIGN_RIGHT, + alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_MIDDLE, line_height=0.9, shimmer=True)) @abc.abstractmethod def _load_assets(self): @@ -51,12 +52,17 @@ class SliderBase(Widget, abc.ABC): def confirmed(self) -> bool: return self._confirmed_time > 0.0 + def show_event(self): + super().show_event() + self.reset() + def reset(self): # reset all slider state self._is_dragging_circle = False self._circle_press_time = None self._confirmed_time = 0.0 self._confirm_callback_called = False + self._label.reset_shimmer(self._shimmer_offset) def set_opacity(self, opacity: float, smooth: bool = False): if smooth: @@ -123,8 +129,6 @@ class SliderBase(Widget, abc.ABC): self._scroll_x_circle_filter.x = self._scroll_x_circle def _render(self, _): - # TODO: iOS text shimmering animation - white = rl.Color(255, 255, 255, int(255 * self._opacity_filter.x)) bg_txt_x = self._rect.x + (self._rect.width - self._bg_txt.width) / 2 @@ -134,8 +138,9 @@ class SliderBase(Widget, abc.ABC): btn_x = bg_txt_x + self._bg_txt.width - self._circle_bg_txt.width + self._scroll_x_circle_filter.x btn_y = self._rect.y + (self._rect.height - self._circle_bg_txt.height) / 2 - if not self.confirmed: - self._label.set_text_color(rl.Color(255, 255, 255, int(255 * 0.65 * (1.0 - self.slider_percentage) * self._opacity_filter.x))) + label_alpha = int(255 * (1.0 - self.slider_percentage) * self._opacity_filter.x) + if label_alpha > 0: + self._label.set_text_color(rl.Color(255, 255, 255, label_alpha)) label_rect = rl.Rectangle( self._rect.x + 20, self._rect.y, @@ -158,9 +163,9 @@ class SliderBase(Widget, abc.ABC): class LargerSlider(SliderBase): - def __init__(self, title: str, confirm_callback: Callable | None = None, green: bool = True): + def __init__(self, title: str, confirm_callback: Callable | None = None, green: bool = True, shimmer_offset: float = 0.0): self._green = green - super().__init__(title, confirm_callback=confirm_callback) + super().__init__(title, confirm_callback=confirm_callback, shimmer_offset=shimmer_offset) def _load_assets(self): self.set_rect(rl.Rectangle(0, 0, 520 + self.HORIZONTAL_PADDING * 2, 115)) @@ -176,9 +181,9 @@ class BigSlider(SliderBase): def __init__(self, title: str, icon: rl.Texture, confirm_callback: Callable | None = None): self._icon = icon super().__init__(title, confirm_callback=confirm_callback) - self._label = UnifiedLabel(title, font_size=48, font_weight=FontWeight.DISPLAY, text_color=rl.Color(255, 255, 255, int(255 * 0.65)), - alignment=rl.GuiTextAlignment.TEXT_ALIGN_RIGHT, alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_MIDDLE, - line_height=0.875) + self._label.set_font_size(48) + self._label.set_font_weight(FontWeight.DISPLAY) + self._label.set_line_height(0.875) def _load_assets(self): self.set_rect(rl.Rectangle(0, 0, 520 + self.HORIZONTAL_PADDING * 2, 180)) From 18da21e65be6a1ea5619fd7c3a035a2cb268790e Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 10 Mar 2026 23:26:39 -0700 Subject: [PATCH 114/141] Add shimmer offset for custom software --- system/ui/mici_setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system/ui/mici_setup.py b/system/ui/mici_setup.py index f2e18cde71..62d9dcd827 100755 --- a/system/ui/mici_setup.py +++ b/system/ui/mici_setup.py @@ -132,7 +132,7 @@ class SoftwareSelectionPage(NavWidget): self._openpilot_slider = self._child(LargerSlider("slide to install\nopenpilot", use_openpilot_callback)) self._openpilot_slider.set_enabled(lambda: self.enabled and not self.is_dismissing) - self._custom_software_slider = self._child(LargerSlider("slide to install\ncustom software", use_custom_software_callback, green=False)) + self._custom_software_slider = self._child(LargerSlider("slide to install\ncustom software", use_custom_software_callback, green=False, shimmer_offset=0.4)) self._custom_software_slider.set_enabled(lambda: self.enabled and not self.is_dismissing) def show_event(self): From 3469d9aadbc16eb854bad3d851bd580d2b8f132e Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 11 Mar 2026 00:05:04 -0700 Subject: [PATCH 115/141] AGNOS 17.2 (#37644) * 17.2 * 17.2 * new updater * shimmer offset --- launch_env.sh | 2 +- system/hardware/tici/agnos.json | 12 +++++----- system/hardware/tici/all-partitions.json | 28 ++++++++++++------------ system/hardware/tici/updater_magic | 4 ++-- 4 files changed, 23 insertions(+), 23 deletions(-) diff --git a/launch_env.sh b/launch_env.sh index efdb4bf0e7..e409a80dd4 100755 --- a/launch_env.sh +++ b/launch_env.sh @@ -16,7 +16,7 @@ export VECLIB_MAXIMUM_THREADS=1 export QCOM_PRIORITY=12 if [ -z "$AGNOS_VERSION" ]; then - export AGNOS_VERSION="17.1" + export AGNOS_VERSION="17.2" fi export STAGING_ROOT="/data/safe_staging" diff --git a/system/hardware/tici/agnos.json b/system/hardware/tici/agnos.json index 58162fc311..295b0279d9 100644 --- a/system/hardware/tici/agnos.json +++ b/system/hardware/tici/agnos.json @@ -67,17 +67,17 @@ }, { "name": "system", - "url": "https://commadist.azureedge.net/agnosupdate/system-8014b6aab8ea2d76f7ea90e76efb9f94504495d29a37d281f0df903b3bdb630f.img.xz", - "hash": "5d49176870f05328c9b4a934863644c2a9b59c993df97a9134ff63f7a4e1d81d", - "hash_raw": "8014b6aab8ea2d76f7ea90e76efb9f94504495d29a37d281f0df903b3bdb630f", + "url": "https://commadist.azureedge.net/agnosupdate/system-dcdea6bd675d0276a63c25151727829620794baf42ada2e5e19a3f77b3f583a5.img.xz", + "hash": "5f319030ad05942267b77f1a4686c4ca24cc09b2c2a4688e57342ffc9720fd49", + "hash_raw": "dcdea6bd675d0276a63c25151727829620794baf42ada2e5e19a3f77b3f583a5", "size": 4718592000, "sparse": true, "full_check": false, "has_ab": true, - "ondevice_hash": "ce18addaec7dd87a511fb7cb068500a1de5720fd76fceddf2944084b7d6fdf15", + "ondevice_hash": "c12f1b7d790a418aea17424accf4cd59c575e5745cad82bdc9452f384483648c", "alt": { - "hash": "8014b6aab8ea2d76f7ea90e76efb9f94504495d29a37d281f0df903b3bdb630f", - "url": "https://commadist.azureedge.net/agnosupdate/system-8014b6aab8ea2d76f7ea90e76efb9f94504495d29a37d281f0df903b3bdb630f.img", + "hash": "dcdea6bd675d0276a63c25151727829620794baf42ada2e5e19a3f77b3f583a5", + "url": "https://commadist.azureedge.net/agnosupdate/system-dcdea6bd675d0276a63c25151727829620794baf42ada2e5e19a3f77b3f583a5.img", "size": 4718592000 } } diff --git a/system/hardware/tici/all-partitions.json b/system/hardware/tici/all-partitions.json index 468142be60..0801907a2d 100644 --- a/system/hardware/tici/all-partitions.json +++ b/system/hardware/tici/all-partitions.json @@ -350,40 +350,40 @@ }, { "name": "system", - "url": "https://commadist.azureedge.net/agnosupdate/system-8014b6aab8ea2d76f7ea90e76efb9f94504495d29a37d281f0df903b3bdb630f.img.xz", - "hash": "5d49176870f05328c9b4a934863644c2a9b59c993df97a9134ff63f7a4e1d81d", - "hash_raw": "8014b6aab8ea2d76f7ea90e76efb9f94504495d29a37d281f0df903b3bdb630f", + "url": "https://commadist.azureedge.net/agnosupdate/system-dcdea6bd675d0276a63c25151727829620794baf42ada2e5e19a3f77b3f583a5.img.xz", + "hash": "5f319030ad05942267b77f1a4686c4ca24cc09b2c2a4688e57342ffc9720fd49", + "hash_raw": "dcdea6bd675d0276a63c25151727829620794baf42ada2e5e19a3f77b3f583a5", "size": 4718592000, "sparse": true, "full_check": false, "has_ab": true, - "ondevice_hash": "ce18addaec7dd87a511fb7cb068500a1de5720fd76fceddf2944084b7d6fdf15", + "ondevice_hash": "c12f1b7d790a418aea17424accf4cd59c575e5745cad82bdc9452f384483648c", "alt": { - "hash": "8014b6aab8ea2d76f7ea90e76efb9f94504495d29a37d281f0df903b3bdb630f", - "url": "https://commadist.azureedge.net/agnosupdate/system-8014b6aab8ea2d76f7ea90e76efb9f94504495d29a37d281f0df903b3bdb630f.img", + "hash": "dcdea6bd675d0276a63c25151727829620794baf42ada2e5e19a3f77b3f583a5", + "url": "https://commadist.azureedge.net/agnosupdate/system-dcdea6bd675d0276a63c25151727829620794baf42ada2e5e19a3f77b3f583a5.img", "size": 4718592000 } }, { "name": "userdata_90", - "url": "https://commadist.azureedge.net/agnosupdate/userdata_90-24f985765b91593e468fa76333a994420a9f460b9a66368a8dbdcac6ee04d8f5.img.xz", - "hash": "f24ee158408d4b41a95c79361f442b5a3304c46e0872e387afddbdbb56d934ac", - "hash_raw": "24f985765b91593e468fa76333a994420a9f460b9a66368a8dbdcac6ee04d8f5", + "url": "https://commadist.azureedge.net/agnosupdate/userdata_90-a7b25ea29255f4fd3a2da99e037f40b4ca10bd4afd57dd96563353b8dfb0f634.img.xz", + "hash": "7ea9d7d4685ec36bbfdf06afe0b51650d567416c3092fef96bd97158ed322742", + "hash_raw": "a7b25ea29255f4fd3a2da99e037f40b4ca10bd4afd57dd96563353b8dfb0f634", "size": 96636764160, "sparse": true, "full_check": true, "has_ab": false, - "ondevice_hash": "9793fa1de3a22cedace28a43f001fbdc4fb73c81c949dc4ec4f5e60ce9eef0b6" + "ondevice_hash": "79ed653c1679d84b13ee23083a511b0e668454e4af9b0db99a3279072ed041c1" }, { "name": "userdata_89", - "url": "https://commadist.azureedge.net/agnosupdate/userdata_89-01336e05d3f85a398f2255ecbd4ebbc14b7f688a33295390891ad90234db8f0b.img.xz", - "hash": "ec13a458352c8d3a974a97acc1aa5d7f1118ab59989d375e00d81c7091b75e8e", - "hash_raw": "01336e05d3f85a398f2255ecbd4ebbc14b7f688a33295390891ad90234db8f0b", + "url": "https://commadist.azureedge.net/agnosupdate/userdata_89-8e428632c967aa609cac184bff938a90240e53ffd3b4fca40bc94c33c81202ba.img.xz", + "hash": "7104cdb0384e4ecb1ebfa6136a2330251bc8aa829b9ec48c4b740f656252d382", + "hash_raw": "8e428632c967aa609cac184bff938a90240e53ffd3b4fca40bc94c33c81202ba", "size": 95563022336, "sparse": true, "full_check": true, "has_ab": false, - "ondevice_hash": "963c071664375b531bb867d83ffc9e2de7883408ca1481330a1ee4db1a964e68" + "ondevice_hash": "fbede3b0831dbc4a4edd336e5f547f4978902b9421fb1484e86c416192c59165" } ] \ No newline at end of file diff --git a/system/hardware/tici/updater_magic b/system/hardware/tici/updater_magic index f0689d5072..44b82d0c54 100755 --- a/system/hardware/tici/updater_magic +++ b/system/hardware/tici/updater_magic @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:238d750f12c8b6758fd47998891d0cfa4bc52d72662801553c7d03952f8166b2 -size 24744419 +oid sha256:3a94ab8395f20d20a9d5a2a2bacca0694f072df8421cf13adca6250d28065bdc +size 24709205 From 4e239dbc22a22e425cd02162ee33f4eceea44c4e Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Wed, 11 Mar 2026 11:35:32 -0700 Subject: [PATCH 116/141] bump opendbc: in-memory DBC generation, drop scons build (#37646) --- .github/workflows/repo-maintenance.yaml | 1 - SConstruct | 1 - opendbc_repo | 2 +- 3 files changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/repo-maintenance.yaml b/.github/workflows/repo-maintenance.yaml index 2c5d049e4c..1ac6efb4f0 100644 --- a/.github/workflows/repo-maintenance.yaml +++ b/.github/workflows/repo-maintenance.yaml @@ -68,7 +68,6 @@ jobs: git add . - name: update car docs run: | - scons -j$(nproc) --minimal opendbc_repo python selfdrive/car/docs.py git add docs/CARS.md - name: Create Pull Request diff --git a/SConstruct b/SConstruct index c6c8632560..6ec98b429e 100644 --- a/SConstruct +++ b/SConstruct @@ -195,7 +195,6 @@ Export('common') env_swaglog = env.Clone() env_swaglog['CXXFLAGS'].append('-DSWAGLOG="\\"common/swaglog.h\\""') SConscript(['msgq_repo/SConscript'], exports={'env': env_swaglog}) -SConscript(['opendbc_repo/SConscript'], exports={'env': env_swaglog}) SConscript(['cereal/SConscript']) diff --git a/opendbc_repo b/opendbc_repo index ffe10c0c80..ddeba888a3 160000 --- a/opendbc_repo +++ b/opendbc_repo @@ -1 +1 @@ -Subproject commit ffe10c0c809cc48726292c832b109a14fee603be +Subproject commit ddeba888a3d03b32269f0c780490ad8578917527 From 58d6211bc27cc88066ca65ede50812e621f8aae9 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 11 Mar 2026 18:58:51 -0700 Subject: [PATCH 117/141] ui: no int textures (#37649) * no int textures * round qr code * unround firehose * ignore here --- pyproject.toml | 1 + selfdrive/ui/layouts/onboarding.py | 2 +- selfdrive/ui/layouts/sidebar.py | 8 ++--- selfdrive/ui/mici/layouts/home.py | 2 +- selfdrive/ui/mici/layouts/offroad_alerts.py | 4 +-- .../ui/mici/layouts/settings/firehose.py | 12 +++---- .../mici/layouts/settings/network/wifi_ui.py | 2 +- selfdrive/ui/mici/onroad/alert_renderer.py | 4 +-- .../ui/mici/onroad/augmented_road_view.py | 2 +- selfdrive/ui/mici/onroad/driver_state.py | 17 +++++----- selfdrive/ui/mici/onroad/hud_renderer.py | 2 +- selfdrive/ui/mici/widgets/dialog.py | 34 +++++++++---------- selfdrive/ui/mici/widgets/pairing_dialog.py | 2 +- selfdrive/ui/onroad/exp_button.py | 2 +- system/ui/tici_setup.py | 2 +- system/ui/widgets/button.py | 6 ++-- system/ui/widgets/layouts.py | 2 +- system/ui/widgets/list_view.py | 2 +- system/ui/widgets/mici_keyboard.py | 2 +- system/ui/widgets/nav_widget.py | 8 ++--- system/ui/widgets/scroller.py | 4 +-- tools/replay/ui.py | 8 ++--- 22 files changed, 64 insertions(+), 64 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 408513600f..bf84161317 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -204,6 +204,7 @@ lint.flake8-implicit-str-concat.allow-multiline = false "pyray.is_mouse_button_pressed".msg = "This can miss events. Use Widget._handle_mouse_press" "pyray.is_mouse_button_released".msg = "This can miss events. Use Widget._handle_mouse_release" "pyray.draw_text".msg = "Use a function (such as rl.draw_font_ex) that takes font as an argument" +"pyray.draw_texture".msg = "Use rl.draw_texture_ex for float position support" [tool.ruff.format] quote-style = "preserve" diff --git a/selfdrive/ui/layouts/onboarding.py b/selfdrive/ui/layouts/onboarding.py index 25294511d4..37205b0e26 100644 --- a/selfdrive/ui/layouts/onboarding.py +++ b/selfdrive/ui/layouts/onboarding.py @@ -91,7 +91,7 @@ class TrainingGuide(Widget): def _render(self, _): # Safeguard against fast tapping step = min(self._step, len(self._textures) - 1) - rl.draw_texture(self._textures[step], 0, 0, rl.WHITE) + rl.draw_texture_ex(self._textures[step], rl.Vector2(0, 0), 0.0, 1.0, rl.WHITE) # progress bar if 0 < step < len(STEP_RECTS) - 1: diff --git a/selfdrive/ui/layouts/sidebar.py b/selfdrive/ui/layouts/sidebar.py index 050cd795bf..a7f3a46279 100644 --- a/selfdrive/ui/layouts/sidebar.py +++ b/selfdrive/ui/layouts/sidebar.py @@ -161,14 +161,14 @@ class Sidebar(Widget): # Settings button settings_down = mouse_down and rl.check_collision_point_rec(mouse_pos, SETTINGS_BTN) tint = Colors.BUTTON_PRESSED if settings_down else Colors.BUTTON_NORMAL - rl.draw_texture(self._settings_img, int(SETTINGS_BTN.x), int(SETTINGS_BTN.y), tint) + rl.draw_texture_ex(self._settings_img, rl.Vector2(SETTINGS_BTN.x, SETTINGS_BTN.y), 0.0, 1.0, tint) # Home/Flag button flag_pressed = mouse_down and rl.check_collision_point_rec(mouse_pos, HOME_BTN) button_img = self._flag_img if ui_state.started else self._home_img tint = Colors.BUTTON_PRESSED if (ui_state.started and flag_pressed) else Colors.BUTTON_NORMAL - rl.draw_texture(button_img, int(HOME_BTN.x), int(HOME_BTN.y), tint) + rl.draw_texture_ex(button_img, rl.Vector2(HOME_BTN.x, HOME_BTN.y), 0.0, 1.0, tint) # Microphone button if self._recording_audio: @@ -178,8 +178,8 @@ class Sidebar(Widget): bg_color = rl.Color(Colors.DANGER.r, Colors.DANGER.g, Colors.DANGER.b, int(255 * 0.65)) if mic_pressed else Colors.DANGER rl.draw_rectangle_rounded(self._mic_indicator_rect, 1, 10, bg_color) - rl.draw_texture(self._mic_img, int(self._mic_indicator_rect.x + (self._mic_indicator_rect.width - self._mic_img.width) / 2), - int(self._mic_indicator_rect.y + (self._mic_indicator_rect.height - self._mic_img.height) / 2), Colors.WHITE) + rl.draw_texture_ex(self._mic_img, rl.Vector2(self._mic_indicator_rect.x + (self._mic_indicator_rect.width - self._mic_img.width) / 2, + self._mic_indicator_rect.y + (self._mic_indicator_rect.height - self._mic_img.height) / 2), 0.0, 1.0, Colors.WHITE) def _draw_network_indicator(self, rect: rl.Rectangle): # Signal strength dots diff --git a/selfdrive/ui/mici/layouts/home.py b/selfdrive/ui/mici/layouts/home.py index da5b0ac5e0..77b665f5d8 100644 --- a/selfdrive/ui/mici/layouts/home.py +++ b/selfdrive/ui/mici/layouts/home.py @@ -77,7 +77,7 @@ class NetworkIcon(Widget): # Offset by difference in height between slashless and slash icons to make center align match draw_y -= (self._wifi_slash_txt.height - self._wifi_none_txt.height) / 2 - rl.draw_texture(draw_net_txt, int(draw_x), int(draw_y), rl.Color(255, 255, 255, int(255 * 0.9))) + rl.draw_texture_ex(draw_net_txt, rl.Vector2(draw_x, draw_y), 0.0, 1.0, rl.Color(255, 255, 255, int(255 * 0.9))) class MiciHomeLayout(Widget): diff --git a/selfdrive/ui/mici/layouts/offroad_alerts.py b/selfdrive/ui/mici/layouts/offroad_alerts.py index 5ccb815da6..0dae5d2075 100644 --- a/selfdrive/ui/mici/layouts/offroad_alerts.py +++ b/selfdrive/ui/mici/layouts/offroad_alerts.py @@ -144,7 +144,7 @@ class AlertItem(Widget): bg_texture = self._bg_small_pressed if self.is_pressed else self._bg_small # Draw background - rl.draw_texture(bg_texture, int(self._rect.x), int(self._rect.y), rl.WHITE) + rl.draw_texture_ex(bg_texture, rl.Vector2(self._rect.x, self._rect.y), 0.0, 1.0, rl.WHITE) # Calculate text area (left side, avoiding icon on right) title_width = self.ALERT_WIDTH - (self.ALERT_PADDING * 2) - self.ICON_SIZE - self.ICON_MARGIN @@ -183,7 +183,7 @@ class AlertItem(Widget): icon_texture = self._icon_orange icon_x = self._rect.x + self.ALERT_WIDTH - self.ALERT_PADDING - self.ICON_SIZE icon_y = self._rect.y + self.ALERT_PADDING - rl.draw_texture(icon_texture, int(icon_x), int(icon_y), rl.WHITE) + rl.draw_texture_ex(icon_texture, rl.Vector2(icon_x, icon_y), 0.0, 1.0, rl.WHITE) class MiciOffroadAlerts(Scroller): diff --git a/selfdrive/ui/mici/layouts/settings/firehose.py b/selfdrive/ui/mici/layouts/settings/firehose.py index e5b6301acf..4c27a909f9 100644 --- a/selfdrive/ui/mici/layouts/settings/firehose.py +++ b/selfdrive/ui/mici/layouts/settings/firehose.py @@ -81,12 +81,12 @@ class FirehoseLayoutBase(Widget): def _render(self, rect: rl.Rectangle): # compute total content height for scrolling content_height = self._measure_content_height(rect) - scroll_offset = round(self._scroll_panel.update(rect, content_height)) + scroll_offset = self._scroll_panel.update(rect, content_height) # start drawing with offset - x = int(rect.x + 40) - y = int(rect.y + 40 + scroll_offset) - w = int(rect.width - 80) + x = rect.x + 40 + y = rect.y + 40 + scroll_offset + w = rect.width - 80 # Title title_text = tr(TITLE) @@ -100,7 +100,7 @@ class FirehoseLayoutBase(Widget): y += 20 # Separator - rl.draw_rectangle(x, y, w, 2, self.GRAY) + rl.draw_rectangle_rec(rl.Rectangle(x, y, w, 2), self.GRAY) y += 20 # Status @@ -116,7 +116,7 @@ class FirehoseLayoutBase(Widget): y += 20 # Separator - rl.draw_rectangle(x, y, w, 2, self.GRAY) + rl.draw_rectangle_rec(rl.Rectangle(x, y, w, 2), self.GRAY) y += 20 # Instructions intro diff --git a/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py b/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py index 10d828fbd8..006027e258 100644 --- a/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py +++ b/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py @@ -165,7 +165,7 @@ class WifiButton(BigButton): if self._is_connected and not self._network_forgetting: check_y = int(label_y - sub_label_height + (sub_label_height - self._check_txt.height) / 2) - rl.draw_texture(self._check_txt, int(sub_label_x), check_y, rl.Color(255, 255, 255, int(255 * 0.9 * 0.65))) + rl.draw_texture_ex(self._check_txt, rl.Vector2(sub_label_x, check_y), 0.0, 1.0, rl.Color(255, 255, 255, int(255 * 0.9 * 0.65))) sub_label_x += self._check_txt.width + 14 sub_label_rect = rl.Rectangle(sub_label_x, label_y - sub_label_height, sub_label_w, sub_label_height) diff --git a/selfdrive/ui/mici/onroad/alert_renderer.py b/selfdrive/ui/mici/onroad/alert_renderer.py index 1c1fd8404f..7b006aaaea 100644 --- a/selfdrive/ui/mici/onroad/alert_renderer.py +++ b/selfdrive/ui/mici/onroad/alert_renderer.py @@ -258,8 +258,8 @@ class AlertRenderer(Widget): else: icon_alpha = int(min(self._turn_signal_alpha_filter.x, 255)) - rl.draw_texture(alert_layout.icon.texture, pos_x, int(self._rect.y + alert_layout.icon.margin_y), - rl.Color(255, 255, 255, int(icon_alpha * self._alpha_filter.x))) + rl.draw_texture_ex(alert_layout.icon.texture, rl.Vector2(pos_x, self._rect.y + alert_layout.icon.margin_y), 0.0, 1.0, + rl.Color(255, 255, 255, int(icon_alpha * self._alpha_filter.x))) def _draw_background(self, alert: Alert) -> None: # draw top gradient for alert text at top diff --git a/selfdrive/ui/mici/onroad/augmented_road_view.py b/selfdrive/ui/mici/onroad/augmented_road_view.py index 2c083e84b9..09d5d57fbb 100644 --- a/selfdrive/ui/mici/onroad/augmented_road_view.py +++ b/selfdrive/ui/mici/onroad/augmented_road_view.py @@ -126,7 +126,7 @@ class BookmarkIcon(Widget): if self._offset_filter.x > 0: icon_x = self.rect.x + self.rect.width - round(self._offset_filter.x) icon_y = self.rect.y + (self.rect.height - self._icon.height) / 2 # Vertically centered - rl.draw_texture(self._icon, int(icon_x), int(icon_y), rl.WHITE) + rl.draw_texture_ex(self._icon, rl.Vector2(icon_x, icon_y), 0.0, 1.0, rl.WHITE) class AugmentedRoadView(CameraView): diff --git a/selfdrive/ui/mici/onroad/driver_state.py b/selfdrive/ui/mici/onroad/driver_state.py index 356d7ac832..92ff07c1e9 100644 --- a/selfdrive/ui/mici/onroad/driver_state.py +++ b/selfdrive/ui/mici/onroad/driver_state.py @@ -61,7 +61,7 @@ class DriverStateRenderer(Widget): self._dm_cone = gui_app.texture("icons_mici/onroad/driver_monitoring/dm_cone.png", cone_and_person_size, cone_and_person_size) center_size = round(36 / self.BASE_SIZE * self._rect.width) self._dm_center = gui_app.texture("icons_mici/onroad/driver_monitoring/dm_center.png", center_size, center_size) - self._dm_background = gui_app.texture("icons_mici/onroad/driver_monitoring/dm_background.png", self._rect.width, self._rect.height) + self._dm_background = gui_app.texture("icons_mici/onroad/driver_monitoring/dm_background.png", int(self._rect.width), int(self._rect.height)) def set_should_draw(self, should_draw: bool): self._should_draw = should_draw @@ -88,15 +88,14 @@ class DriverStateRenderer(Widget): if DEBUG: rl.draw_rectangle_lines_ex(self._rect, 1, rl.RED) - rl.draw_texture(self._dm_background, - int(self._rect.x), - int(self._rect.y), - rl.Color(255, 255, 255, int(255 * self._fade_filter.x))) + rl.draw_texture_ex(self._dm_background, + rl.Vector2(self._rect.x, self._rect.y), 0.0, 1.0, + rl.Color(255, 255, 255, int(255 * self._fade_filter.x))) - rl.draw_texture(self._dm_person, - int(self._rect.x + (self._rect.width - self._dm_person.width) / 2), - int(self._rect.y + (self._rect.height - self._dm_person.height) / 2), - rl.Color(255, 255, 255, int(255 * 0.9 * self._fade_filter.x))) + rl.draw_texture_ex(self._dm_person, + rl.Vector2(self._rect.x + (self._rect.width - self._dm_person.width) / 2, + self._rect.y + (self._rect.height - self._dm_person.height) / 2), 0.0, 1.0, + rl.Color(255, 255, 255, int(255 * 0.9 * self._fade_filter.x))) if self.effective_active: source_rect = rl.Rectangle(0, 0, self._dm_cone.width, self._dm_cone.height) diff --git a/selfdrive/ui/mici/onroad/hud_renderer.py b/selfdrive/ui/mici/onroad/hud_renderer.py index 21e30e7426..35d04fe702 100644 --- a/selfdrive/ui/mici/onroad/hud_renderer.py +++ b/selfdrive/ui/mici/onroad/hud_renderer.py @@ -219,7 +219,7 @@ class HudRenderer(Widget): EXCLAMATION_POINT_SPACING = 10 exclamation_pos_x = pos_x - self._txt_exclamation_point.width / 2 + wheel_txt.width / 2 + EXCLAMATION_POINT_SPACING exclamation_pos_y = pos_y - self._txt_exclamation_point.height / 2 - rl.draw_texture(self._txt_exclamation_point, int(exclamation_pos_x), int(exclamation_pos_y), rl.WHITE) + rl.draw_texture_ex(self._txt_exclamation_point, rl.Vector2(exclamation_pos_x, exclamation_pos_y), 0.0, 1.0, rl.WHITE) def _draw_set_speed(self, rect: rl.Rectangle) -> None: """Draw the MAX speed indicator box.""" diff --git a/selfdrive/ui/mici/widgets/dialog.py b/selfdrive/ui/mici/widgets/dialog.py index 1f67b987f1..396ea35cc8 100644 --- a/selfdrive/ui/mici/widgets/dialog.py +++ b/selfdrive/ui/mici/widgets/dialog.py @@ -44,20 +44,20 @@ class BigDialog(BigDialogBase): title_wrapped = '\n'.join(wrap_text(gui_app.font(FontWeight.BOLD), self._title, 50, int(max_width))) title_size = measure_text_cached(gui_app.font(FontWeight.BOLD), title_wrapped, 50) text_x_offset = 0 - title_rect = rl.Rectangle(int(self._rect.x + text_x_offset + PADDING), - int(self._rect.y + PADDING), - int(max_width), - int(title_size.y)) + title_rect = rl.Rectangle(self._rect.x + text_x_offset + PADDING, + self._rect.y + PADDING, + max_width, + title_size.y) gui_label(title_rect, title_wrapped, 50, font_weight=FontWeight.BOLD, alignment=rl.GuiTextAlignment.TEXT_ALIGN_CENTER) # draw description desc_wrapped = '\n'.join(wrap_text(gui_app.font(FontWeight.MEDIUM), self._description, 30, int(max_width))) desc_size = measure_text_cached(gui_app.font(FontWeight.MEDIUM), desc_wrapped, 30) - desc_rect = rl.Rectangle(int(self._rect.x + text_x_offset + PADDING), - int(self._rect.y + self._rect.height / 3), - int(max_width), - int(desc_size.y)) + desc_rect = rl.Rectangle(self._rect.x + text_x_offset + PADDING, + self._rect.y + self._rect.height / 3, + max_width, + desc_size.y) # TODO: text align doesn't seem to work properly with newlines gui_label(desc_rect, desc_wrapped, 30, font_weight=FontWeight.MEDIUM, alignment=rl.GuiTextAlignment.TEXT_ALIGN_CENTER) @@ -156,9 +156,9 @@ class BigInputDialog(BigDialogBase): bg_block_margin = 5 text_x = PADDING / 2 + self._enter_img.width + PADDING - text_field_rect = rl.Rectangle(text_x, int(self._rect.y + PADDING) - bg_block_margin, - int(self._rect.width - text_x * 2), - int(text_size.y)) + text_field_rect = rl.Rectangle(text_x, self._rect.y + PADDING - bg_block_margin, + self._rect.width - text_x * 2, + text_size.y) # draw text input # push text left with a gradient on left side if too long @@ -179,8 +179,8 @@ class BigInputDialog(BigDialogBase): # draw gradient on left side to indicate more text if text_size.x > text_field_rect.width: - rl.draw_rectangle_gradient_h(int(text_field_rect.x), int(text_field_rect.y), 80, int(text_field_rect.height), - rl.BLACK, rl.BLANK) + rl.draw_rectangle_gradient_ex(rl.Rectangle(text_field_rect.x, text_field_rect.y, 80, text_field_rect.height), + rl.BLACK, rl.BLANK, rl.BLANK, rl.BLACK) # draw cursor blink_alpha = (math.sin(rl.get_time() * 6) + 1) / 2 @@ -188,14 +188,14 @@ class BigInputDialog(BigDialogBase): cursor_x = min(text_x + text_size.x + 3, text_field_rect.x + text_field_rect.width) else: cursor_x = text_field_rect.x - 6 - rl.draw_rectangle_rounded(rl.Rectangle(int(cursor_x), int(text_field_rect.y), 4, int(text_size.y)), + rl.draw_rectangle_rounded(rl.Rectangle(cursor_x, text_field_rect.y, 4, text_size.y), 1, 4, rl.Color(255, 255, 255, int(255 * blink_alpha))) # draw backspace icon with nice fade self._backspace_img_alpha.update(255 * bool(text)) if self._backspace_img_alpha.x > 1: color = rl.Color(255, 255, 255, int(self._backspace_img_alpha.x)) - rl.draw_texture(self._backspace_img, int(self._rect.width - self._backspace_img.width - 27), int(self._rect.y + 14), color) + rl.draw_texture_ex(self._backspace_img, rl.Vector2(self._rect.width - self._backspace_img.width - 27, self._rect.y + 14), 0.0, 1.0, color) if not text and self._hint_label.text and not candidate_char: # draw description if no text entered yet and not drawing candidate char @@ -213,9 +213,9 @@ class BigInputDialog(BigDialogBase): # draw enter button self._enter_img_alpha.update(255 if len(text) >= self._minimum_length else 0) color = rl.Color(255, 255, 255, int(self._enter_img_alpha.x)) - rl.draw_texture(self._enter_img, int(self._rect.x + PADDING / 2), int(self._rect.y), color) + rl.draw_texture_ex(self._enter_img, rl.Vector2(self._rect.x + PADDING / 2, self._rect.y), 0.0, 1.0, color) color = rl.Color(255, 255, 255, 255 - int(self._enter_img_alpha.x)) - rl.draw_texture(self._enter_disabled_img, int(self._rect.x + PADDING / 2), int(self._rect.y), color) + rl.draw_texture_ex(self._enter_disabled_img, rl.Vector2(self._rect.x + PADDING / 2, self._rect.y), 0.0, 1.0, color) # keyboard goes over everything self._keyboard.render(self._rect) diff --git a/selfdrive/ui/mici/widgets/pairing_dialog.py b/selfdrive/ui/mici/widgets/pairing_dialog.py index 8421b516d2..a18b26ec02 100644 --- a/selfdrive/ui/mici/widgets/pairing_dialog.py +++ b/selfdrive/ui/mici/widgets/pairing_dialog.py @@ -92,7 +92,7 @@ class PairingDialog(NavWidget): return scale = self._rect.height / self._qr_texture.height - pos = rl.Vector2(self._rect.x + 8, self._rect.y) + pos = rl.Vector2(round(self._rect.x + 8), round(self._rect.y)) rl.draw_texture_ex(self._qr_texture, pos, 0.0, scale, rl.WHITE) def __del__(self): diff --git a/selfdrive/ui/onroad/exp_button.py b/selfdrive/ui/onroad/exp_button.py index e5d8171413..9a92ebc3c3 100644 --- a/selfdrive/ui/onroad/exp_button.py +++ b/selfdrive/ui/onroad/exp_button.py @@ -50,7 +50,7 @@ class ExpButton(Widget): texture = self._txt_exp if self._held_or_actual_mode() else self._txt_wheel rl.draw_circle(center_x, center_y, self._rect.width / 2, self._black_bg) - rl.draw_texture(texture, center_x - texture.width // 2, center_y - texture.height // 2, self._white_color) + rl.draw_texture_ex(texture, rl.Vector2(center_x - texture.width / 2, center_y - texture.height / 2), 0.0, 1.0, self._white_color) def _held_or_actual_mode(self): now = time.monotonic() diff --git a/system/ui/tici_setup.py b/system/ui/tici_setup.py index f98ab5ffaa..9eefb6af53 100755 --- a/system/ui/tici_setup.py +++ b/system/ui/tici_setup.py @@ -183,7 +183,7 @@ class Setup(Widget): self.state = SetupState.CUSTOM_SOFTWARE def render_low_voltage(self, rect: rl.Rectangle): - rl.draw_texture(self.warning, int(rect.x + 150), int(rect.y + 110), rl.WHITE) + rl.draw_texture_ex(self.warning, rl.Vector2(rect.x + 150, rect.y + 110), 0.0, 1.0, rl.WHITE) self._low_voltage_title_label.render(rl.Rectangle(rect.x + 150, rect.y + 110 + 150 + 100, rect.width - 500 - 150, TITLE_FONT_SIZE * FONT_SCALE)) self._low_voltage_body_label.render(rl.Rectangle(rect.x + 150, rect.y + 110 + 150 + 150, rect.width - 500, BODY_FONT_SIZE * FONT_SCALE * 3)) diff --git a/system/ui/widgets/button.py b/system/ui/widgets/button.py index 60f9e60735..36ef3bedab 100644 --- a/system/ui/widgets/button.py +++ b/system/ui/widgets/button.py @@ -191,7 +191,7 @@ class IconButton(Widget): color = rl.Color(255, 255, 255, int(255 * 0.9 * 0.35 * self._opacity_filter.x)) draw_x = rect.x + (rect.width - self._texture.width) / 2 draw_y = rect.y + (rect.height - self._texture.height) / 2 - rl.draw_texture(self._texture, int(draw_x), int(draw_y), color) + rl.draw_texture_ex(self._texture, rl.Vector2(draw_x, draw_y), 0.0, 1.0, color) class SmallCircleIconButton(Widget): @@ -219,7 +219,7 @@ class SmallCircleIconButton(Widget): bg_txt = self._icon_bg_pressed_txt if self.is_pressed else self._icon_bg_txt icon_white = white - rl.draw_texture(bg_txt, int(self.rect.x), int(self.rect.y), white) + rl.draw_texture_ex(bg_txt, rl.Vector2(self.rect.x, self.rect.y), 0.0, 1.0, white) icon_x = self.rect.x + (self.rect.width - self._icon_txt.width) / 2 icon_y = self.rect.y + (self.rect.height - self._icon_txt.height) / 2 - rl.draw_texture(self._icon_txt, int(icon_x), int(icon_y), icon_white) + rl.draw_texture_ex(self._icon_txt, rl.Vector2(icon_x, icon_y), 0.0, 1.0, icon_white) diff --git a/system/ui/widgets/layouts.py b/system/ui/widgets/layouts.py index 6fd3bffd8c..6bbc49e927 100644 --- a/system/ui/widgets/layouts.py +++ b/system/ui/widgets/layouts.py @@ -54,6 +54,6 @@ class HBoxLayout(Widget): y = self._rect.y + (self._rect.height - widget.rect.height) / 2 # Update widget position and render - widget.set_position(round(x), round(y)) + widget.set_position(x, y) widget.set_parent_rect(self._rect) widget.render() diff --git a/system/ui/widgets/list_view.py b/system/ui/widgets/list_view.py index 1cf530a66a..82613c37c8 100644 --- a/system/ui/widgets/list_view.py +++ b/system/ui/widgets/list_view.py @@ -355,7 +355,7 @@ class ListItem(Widget): if self.title: # Draw icon if present if self.icon: - rl.draw_texture(self._icon_texture, int(content_x), int(self._rect.y + (ITEM_BASE_HEIGHT - self._icon_texture.height) // 2), rl.WHITE) + rl.draw_texture_ex(self._icon_texture, rl.Vector2(content_x, self._rect.y + (ITEM_BASE_HEIGHT - self._icon_texture.height) / 2), 0.0, 1.0, rl.WHITE) text_x += ICON_SIZE + ITEM_PADDING # Draw main text diff --git a/system/ui/widgets/mici_keyboard.py b/system/ui/widgets/mici_keyboard.py index 74f1f56346..75a3c29e6b 100644 --- a/system/ui/widgets/mici_keyboard.py +++ b/system/ui/widgets/mici_keyboard.py @@ -95,7 +95,7 @@ class Key(Widget): self._size_filter.update(size) def _get_font_size(self) -> int: - return int(round(self._size_filter.x)) + return round(self._size_filter.x) class SmallKey(Key): diff --git a/system/ui/widgets/nav_widget.py b/system/ui/widgets/nav_widget.py index 6f2bbd025b..11770bbe5d 100644 --- a/system/ui/widgets/nav_widget.py +++ b/system/ui/widgets/nav_widget.py @@ -148,7 +148,7 @@ class NavWidget(Widget, abc.ABC): if self._playing_dismiss_animation: new_y = self._rect.height + DISMISS_PUSH_OFFSET - new_y = round(self._y_pos_filter.update(new_y)) + new_y = self._y_pos_filter.update(new_y) if abs(new_y) < 1 and abs(self._y_pos_filter.velocity.x) < 0.5: new_y = self._y_pos_filter.x = 0.0 self._y_pos_filter.velocity.x = 0.0 @@ -176,10 +176,10 @@ class NavWidget(Widget, abc.ABC): def _layout(self): # Dim whatever is behind this widget, fading with position (runs after _update_state so position is correct) overlay_alpha = int(200 * max(0.0, min(1.0, 1.0 - self._rect.y / self._rect.height))) if self._rect.height > 0 else 0 - rl.draw_rectangle(0, 0, int(self._rect.width), int(self._rect.height), rl.Color(0, 0, 0, overlay_alpha)) + rl.draw_rectangle_rec(rl.Rectangle(0, 0, self._rect.width, self._rect.height), rl.Color(0, 0, 0, overlay_alpha)) bounce_height = 20 - rl.draw_rectangle(int(self._rect.x), int(self._rect.y), int(self._rect.width), int(self._rect.height + bounce_height), rl.BLACK) + rl.draw_rectangle_rec(rl.Rectangle(self._rect.x, self._rect.y, self._rect.width, self._rect.height + bounce_height), rl.BLACK) def render(self, rect: rl.Rectangle | None = None) -> bool | int | None: ret = super().render(rect) @@ -196,7 +196,7 @@ class NavWidget(Widget, abc.ABC): else: self._nav_bar_y_filter.update(NAV_BAR_MARGIN) - self._nav_bar.set_position(bar_x, round(self._nav_bar_y_filter.x)) + self._nav_bar.set_position(bar_x, self._nav_bar_y_filter.x) self._nav_bar.render() return ret diff --git a/system/ui/widgets/scroller.py b/system/ui/widgets/scroller.py index 65f23739c1..a3a0d2b38f 100644 --- a/system/ui/widgets/scroller.py +++ b/system/ui/widgets/scroller.py @@ -190,7 +190,7 @@ class _Scroller(Widget): self.scroll_panel.set_enabled(scroll_enabled and self.enabled and not self._scrolling_to[1]) self.scroll_panel.update(self._rect, content_size) if not self._snap_items: - return round(self.scroll_panel.get_offset()) + return self.scroll_panel.get_offset() # Snap closest item to center center_pos = self._rect.x + self._rect.width / 2 if self._horizontal else self._rect.y + self._rect.height / 2 @@ -341,7 +341,7 @@ class _Scroller(Widget): x, y = self._do_move_animation(item, x, y) # Update item state - item.set_position(round(x), round(y)) # round to prevent jumping when settling + item.set_position(x, y) item.set_parent_rect(self._rect) def _render_item(self, item: Widget): diff --git a/tools/replay/ui.py b/tools/replay/ui.py index 8707f2be99..7fe2f405a1 100755 --- a/tools/replay/ui.py +++ b/tools/replay/ui.py @@ -218,7 +218,7 @@ def ui_thread(addr): # Update camera texture from numpy array img_rgba = cv2.cvtColor(img, cv2.COLOR_RGB2RGBA) rl.update_texture(camera_texture, rl.ffi.cast("void *", img_rgba.ctypes.data)) - rl.draw_texture(camera_texture, 0, 0, rl.WHITE) + rl.draw_texture(camera_texture, 0, 0, rl.WHITE) # noqa: TID251 # display alerts rl.draw_text_ex(font, sm['selfdriveState'].alertText1, rl.Vector2(180, 150), 30, 0, rl.RED) @@ -227,15 +227,15 @@ def ui_thread(addr): # draw plots (texture is reused internally) plot_texture = draw_plots(plot_arr) if hor_mode: - rl.draw_texture(plot_texture, 640 + 384, 0, rl.WHITE) + rl.draw_texture(plot_texture, 640 + 384, 0, rl.WHITE) # noqa: TID251 else: - rl.draw_texture(plot_texture, 0, 600, rl.WHITE) + rl.draw_texture(plot_texture, 0, 600, rl.WHITE) # noqa: TID251 # Convert lid_overlay to RGBA and update top_down texture # lid_overlay is (384, 960), need to transpose to (960, 384) for row-major RGBA buffer lid_rgba = palette[lid_overlay.T] rl.update_texture(top_down_texture, rl.ffi.cast("void *", np.ascontiguousarray(lid_rgba).ctypes.data)) - rl.draw_texture(top_down_texture, 640, 0, rl.WHITE) + rl.draw_texture(top_down_texture, 640, 0, rl.WHITE) # noqa: TID251 SPACING = 25 lines = [ From 7dfb7967b63c378211cb45013ed7bbae856bb5f8 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 11 Mar 2026 19:51:34 -0700 Subject: [PATCH 118/141] ui: proper mici scaling (#37652) * scale * remove low res image finder * check self scale * simplify --- system/ui/lib/application.py | 40 +++++++++++++++++++++++++++++++++--- 1 file changed, 37 insertions(+), 3 deletions(-) diff --git a/system/ui/lib/application.py b/system/ui/lib/application.py index ddcde01496..07a0169ccb 100644 --- a/system/ui/lib/application.py +++ b/system/ui/lib/application.py @@ -1,5 +1,6 @@ import atexit import cffi +import math import os import queue import time @@ -278,7 +279,7 @@ class GuiApplication: if self._scale != 1.0: rl.set_mouse_scale(1 / self._scale, 1 / self._scale) if needs_render_texture: - self._render_texture = rl.load_render_texture(self._width, self._height) + self._render_texture = rl.load_render_texture(self._scaled_width, self._scaled_height) rl.set_texture_filter(self._render_texture.texture, rl.TextureFilter.TEXTURE_FILTER_BILINEAR) if RECORD: @@ -289,7 +290,7 @@ class GuiApplication: '-nostats', # Suppress encoding progress '-f', 'rawvideo', # Input format '-pix_fmt', 'rgba', # Input pixel format - '-s', f'{self._width}x{self._height}', # Input resolution + '-s', f'{self._scaled_width}x{self._scaled_height}', # Input resolution '-r', str(fps), # Input frame rate '-i', 'pipe:0', # Input from stdin '-vf', 'vflip,format=yuv420p', # Flip vertically and convert to yuv420p @@ -319,6 +320,7 @@ class GuiApplication: self._set_styles() self._load_fonts() self._patch_text_functions() + self._patch_scissor_mode() if BURN_IN_MODE and self._burn_in_shader is None: self._burn_in_shader = rl.load_shader_from_memory(BURN_IN_VERTEX_SHADER, BURN_IN_FRAGMENT_SHADER) @@ -454,6 +456,12 @@ class GuiApplication: with as_file(ASSETS_DIR.joinpath(asset_path)) as fspath: image_obj = self._load_image_from_path(fspath.as_posix(), width, height, alpha_premultiply, keep_aspect_ratio, flip_x) texture_obj = self._load_texture_from_image(image_obj) + + # Set logical size so widget layout math stays at 1x coordinates + if self._scale != 1.0 and width is not None and height is not None: + texture_obj.width = width + texture_obj.height = height + self._textures[cache_key] = texture_obj return texture_obj @@ -465,6 +473,11 @@ class GuiApplication: if alpha_premultiply: rl.image_alpha_premultiply(image) + # Scale up load size for sharper rendering, capped at source resolution + if self._scale != 1.0 and width is not None and height is not None: + width = min(int(width * self._scale), image.width) + height = min(int(height * self._scale), image.height) + if width is not None and height is not None: same_dimensions = image.width == width and image.height == height @@ -588,6 +601,10 @@ class GuiApplication: rl.begin_drawing() rl.clear_background(rl.BLACK) + if self._scale != 1.0: + rl.rl_push_matrix() + rl.rl_scalef(self._scale, self._scale, 1.0) + # Allow a Widget to still run a function regardless of the stack depth for tick in self._nav_stack_ticks: tick() @@ -598,11 +615,14 @@ class GuiApplication: yield True + if self._scale != 1.0: + rl.rl_pop_matrix() + if self._render_texture: rl.end_texture_mode() rl.begin_drawing() rl.clear_background(rl.BLACK) - src_rect = rl.Rectangle(0, 0, float(self._width), -float(self._height)) + src_rect = rl.Rectangle(0, 0, float(self._scaled_width), -float(self._scaled_height)) dst_rect = rl.Rectangle(0, 0, float(self._scaled_width), float(self._scaled_height)) texture = self._render_texture.texture if texture: @@ -679,6 +699,20 @@ class GuiApplication: rl.draw_text_ex = _draw_text_ex_scaled + def _patch_scissor_mode(self): + if self._scale == 1.0: + return + + if not hasattr(rl, "_orig_begin_scissor_mode"): + rl._orig_begin_scissor_mode = rl.begin_scissor_mode + + def _begin_scissor_mode_scaled(x, y, width, height): + return rl._orig_begin_scissor_mode( + int(x * self._scale), int(y * self._scale), + int(math.ceil(width * self._scale)), int(math.ceil(height * self._scale))) + + rl.begin_scissor_mode = _begin_scissor_mode_scaled + def _set_log_callback(self): ffi_libc = cffi.FFI() ffi_libc.cdef(""" From c631a22eb6f01adcae677503835b9f45dfb36c0a Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 11 Mar 2026 23:19:02 -0700 Subject: [PATCH 119/141] ui: fix 1px flash at bottom of DM camera during onboarding swipe (#37653) Co-authored-by: Claude Opus 4.6 --- selfdrive/ui/mici/layouts/onboarding.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/selfdrive/ui/mici/layouts/onboarding.py b/selfdrive/ui/mici/layouts/onboarding.py index 781f60ee21..35db4412d5 100644 --- a/selfdrive/ui/mici/layouts/onboarding.py +++ b/selfdrive/ui/mici/layouts/onboarding.py @@ -151,8 +151,10 @@ class TrainingGuideDMTutorial(NavWidget): def _render(self, _): self._dialog.render(self._rect) - rl.draw_rectangle_gradient_v(int(self._rect.x), int(self._rect.y + self._rect.height - 80), - int(self._rect.width), 80, rl.BLANK, rl.BLACK) + gradient_y = int(self._rect.y + self._rect.height - 80) + gradient_h = int(self._rect.y) + int(self._rect.height) - gradient_y + rl.draw_rectangle_gradient_v(int(self._rect.x), gradient_y, + int(self._rect.width), gradient_h, rl.BLANK, rl.BLACK) # draw white ring around dm icon to indicate progress ring_thickness = 8 From 6e7587a75ce6d0c38d2e281e643e001c5cd41454 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 11 Mar 2026 23:35:56 -0700 Subject: [PATCH 120/141] modeld: quiet do_chunk output during scons build (#37654) * modeld: quiet do_chunk output during scons build SCons default-prints Python function actions with all their args. The do_chunk function has 1259 tinygrad source files as deps, causing a wall of text during builds. Wrap in SAction with a short strfunction. Co-Authored-By: Claude Opus 4.6 * split compile and chunk into separate Commands cleaner fix: do_chunk only depends on the pkl, not tinygrad files Co-Authored-By: Claude Opus 4.6 --------- Co-authored-by: Claude Opus 4.6 --- selfdrive/modeld/SConscript | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/selfdrive/modeld/SConscript b/selfdrive/modeld/SConscript index 95ac06bb1a..3f38324a6e 100644 --- a/selfdrive/modeld/SConscript +++ b/selfdrive/modeld/SConscript @@ -45,13 +45,17 @@ def tg_compile(flags, model_name): pkl = fn + "_tinygrad.pkl" onnx_path = fn + ".onnx" chunk_targets = get_chunk_paths(pkl, estimate_pickle_max_size(os.path.getsize(onnx_path))) + compile_node = lenv.Command( + pkl, + [onnx_path] + tinygrad_files + [chunker_file], + f'{pythonpath_string} {flags} {image_flag} python3 {Dir("#tinygrad_repo").abspath}/examples/openpilot/compile3.py {fn}.onnx {pkl}', + ) def do_chunk(target, source, env): chunk_file(pkl, chunk_targets) return lenv.Command( chunk_targets, - [onnx_path] + tinygrad_files + [chunker_file], - [f'{pythonpath_string} {flags} {image_flag} python3 {Dir("#tinygrad_repo").abspath}/examples/openpilot/compile3.py {fn}.onnx {pkl}', - do_chunk] + compile_node, + do_chunk, ) # Compile small models From 9bcd965f0ba44ccc7fd67d26b0df72203d3e0ec4 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 11 Mar 2026 23:38:51 -0700 Subject: [PATCH 121/141] ui: don't load unused light font --- system/ui/lib/application.py | 1 - 1 file changed, 1 deletion(-) diff --git a/system/ui/lib/application.py b/system/ui/lib/application.py index 07a0169ccb..4fbfbc0de2 100644 --- a/system/ui/lib/application.py +++ b/system/ui/lib/application.py @@ -95,7 +95,6 @@ FONT_DIR = ASSETS_DIR.joinpath("fonts") class FontWeight(StrEnum): - LIGHT = "Inter-Light.fnt" NORMAL = "Inter-Regular.fnt" if BIG_UI else "Inter-Medium.fnt" MEDIUM = "Inter-Medium.fnt" BOLD = "Inter-Bold.fnt" From d8ae8c201a635195b9113ddd8263309e068c5474 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Thu, 12 Mar 2026 00:15:14 -0700 Subject: [PATCH 122/141] onboarding: block back (#37655) no back from onboarding --- selfdrive/ui/mici/layouts/onboarding.py | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/selfdrive/ui/mici/layouts/onboarding.py b/selfdrive/ui/mici/layouts/onboarding.py index 35db4412d5..6f35370dc5 100644 --- a/selfdrive/ui/mici/layouts/onboarding.py +++ b/selfdrive/ui/mici/layouts/onboarding.py @@ -258,9 +258,11 @@ class TrainingGuideAttentionNotice(Scroller): class TrainingGuide(NavWidget): - def __init__(self, completed_callback: Callable[[], None]): + def __init__(self, completed_callback: Callable[[], None], block_back: bool = False): super().__init__() + self._block_back = block_back + self._steps = [ TrainingGuideAttentionNotice(continue_callback=lambda: gui_app.push_widget(self._steps[1])), TrainingGuidePreDMTutorial(continue_callback=lambda: gui_app.push_widget(self._steps[2])), @@ -271,6 +273,14 @@ class TrainingGuide(NavWidget): self._child(self._steps[0]) self._steps[0].set_enabled(lambda: self.enabled and not self.is_dismissing) # for nav stack + def _back_enabled(self) -> bool: + return not self._block_back + + def show_event(self): + super().show_event() + if self._block_back: + self._nav_bar._alpha = 0.0 + def _render(self, _): self._steps[0].render(self._rect) @@ -314,7 +324,8 @@ class TermsPage(Scroller): def __init__(self, on_accept, on_decline): super().__init__() - self._accept_button = BigConfirmationCircleButton("accept\nterms", gui_app.texture("icons_mici/setup/driver_monitoring/dm_check.png", 64, 64), on_accept) + self._accept_button = BigConfirmationCircleButton("accept\nterms", gui_app.texture("icons_mici/setup/driver_monitoring/dm_check.png", 64, 64), on_accept, + exit_on_confirm=False) self._decline_button = BigConfirmationCircleButton("decline &\nuninstall", gui_app.texture("icons_mici/setup/cancel.png", 64, 64), on_decline, red=True, exit_on_confirm=False) @@ -349,7 +360,7 @@ class OnboardingWindow(Widget): # Windows self._terms = TermsPage(on_accept=self._on_terms_accepted, on_decline=self._on_uninstall) self._terms.set_enabled(lambda: self.enabled) # for nav stack - self._training_guide = TrainingGuide(completed_callback=self._on_completed_training) + self._training_guide = TrainingGuide(completed_callback=self._on_completed_training, block_back=True) self._training_guide.set_enabled(lambda: self.enabled) # for nav stack def _on_uninstall(self): From 2b0aab3a38f71a32023ec50ebf02e8fbbc4bd85b Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Thu, 12 Mar 2026 01:47:20 -0700 Subject: [PATCH 123/141] ui: round QR code draw position in onboarding (#37656) Co-authored-by: Claude Opus 4.6 --- selfdrive/ui/mici/layouts/onboarding.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/ui/mici/layouts/onboarding.py b/selfdrive/ui/mici/layouts/onboarding.py index 6f35370dc5..e19ac9dcf1 100644 --- a/selfdrive/ui/mici/layouts/onboarding.py +++ b/selfdrive/ui/mici/layouts/onboarding.py @@ -313,7 +313,7 @@ class QRCodeWidget(Widget): def _render(self, _): if self._qr_texture: scale = self._size / self._qr_texture.height - rl.draw_texture_ex(self._qr_texture, rl.Vector2(self._rect.x, self._rect.y), 0.0, scale, rl.WHITE) + rl.draw_texture_ex(self._qr_texture, rl.Vector2(round(self._rect.x), round(self._rect.y)), 0.0, scale, rl.WHITE) def __del__(self): if self._qr_texture and self._qr_texture.id != 0: From bbed1a2551d625dfc1eef66a4293b31f5d37c473 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Thu, 12 Mar 2026 02:55:56 -0700 Subject: [PATCH 124/141] scroll: use iOS-style weighted velocity averaging for fling (#37659) * scroll: use iOS-style weighted velocity averaging for fling Weight older velocity samples more heavily on finger release to produce more consistent fling velocities. The last touch samples before lift are noisy (finger decelerating, rotating, jittering), so we trust the earlier steadier samples more: 60% oldest, 35% middle, 5% newest. Reverse-engineered from iOS UIScrollView by the Flutter team. Co-Authored-By: Claude Opus 4.6 * Update system/ui/lib/application.py * Apply suggestions from code review --------- Co-authored-by: Claude Opus 4.6 --- system/ui/lib/application.py | 4 ++++ system/ui/lib/scroll_panel2.py | 25 ++++++++++++++++++++++++- 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/system/ui/lib/application.py b/system/ui/lib/application.py index 4fbfbc0de2..5d40abdc14 100644 --- a/system/ui/lib/application.py +++ b/system/ui/lib/application.py @@ -169,6 +169,10 @@ class MouseState: self._rk.keep_time() def _handle_mouse_event(self): + # TODO: read touch events from evdev directly to get real kernel timestamps. + # Polling at 140Hz with time.monotonic() causes timing jitter that makes scroll + # velocity oscillate (alternating high/low). Real timestamps would also let us + # detect swipe-stop-lift via event gaps instead of the fragile decel heuristic. for slot in range(MAX_TOUCH_SLOTS): mouse_pos = rl.get_touch_position(slot) x = mouse_pos.x / self._scale if self._scale != 1.0 else mouse_pos.x diff --git a/system/ui/lib/scroll_panel2.py b/system/ui/lib/scroll_panel2.py index e2a548ba26..18fd8a9a67 100644 --- a/system/ui/lib/scroll_panel2.py +++ b/system/ui/lib/scroll_panel2.py @@ -20,6 +20,21 @@ MAX_SPEED = 10000.0 # px/s DEBUG = os.getenv("DEBUG_SCROLL", "0") == "1" +# Weights older (steadier) velocity samples more heavily on release. +# Finger-lift samples are noisy; trusting earlier samples gives consistent fling velocity. +# Reverse-engineered from iOS UIScrollView (tuned at 120Hz touch) by Flutter team: +# https://github.com/flutter/flutter/pull/60501 +# 3 samples ≈ 25ms at 120Hz (iOS) / ~21ms at 140Hz (comma). Scale if touch rate changes. +def weighted_velocity(buffer: deque) -> float: + if len(buffer) >= 3: + return buffer[-3] * 0.6 + buffer[-2] * 0.35 + buffer[-1] * 0.05 + elif len(buffer) == 2: + return buffer[-2] * 0.7 + buffer[-1] * 0.3 + elif len(buffer) == 1: + return buffer[-1] + return 0.0 + + # from https://ariya.io/2011/10/flick-list-with-its-momentum-scrolling-and-deceleration class ScrollState(Enum): STEADY = 0 @@ -151,7 +166,13 @@ class GuiScrollPanel2: # Touch rejection: when releasing finger after swiping and stopping, panel # reports a few erroneous touch events with high velocity, try to ignore. - # If velocity decelerates very quickly, assume user doesn't intend to auto scroll + # If velocity decelerates very quickly, assume user doesn't intend to auto scroll. + # Catches two cases: 1) swipe, stop finger, then lift (stale high velocity in buffer) + # 2) dirty finger lift where finger rotates/slides producing spurious velocity spike. + # TODO: this heuristic false-positives on fast swipes because 140Hz touch polling + # jitter causes velocity to oscillate (not real deceleration). Better approaches: + # - Use evdev kernel timestamps to eliminate velocity oscillation at the source + # - Replace with a time-since-last-event check (40ms timeout) for swipe-stop-lift high_decel = False if len(self._velocity_buffer) > 2: # We limit max to first half since final few velocities can surpass first few @@ -166,6 +187,8 @@ class GuiScrollPanel2: print('deceleration too high, going to STEADY') high_decel = True + self._velocity = weighted_velocity(self._velocity_buffer) + # If final velocity is below some threshold, switch to steady state too low_speed = abs(self._velocity) <= MIN_VELOCITY_FOR_CLICKING * 1.5 # plus some margin From d0375942b8cc49722a3ad846e9f3d1a6822cb90e Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Thu, 12 Mar 2026 20:03:22 -0700 Subject: [PATCH 125/141] Revert "onboarding: block back" (#37663) Revert "onboarding: block back (#37655)" This reverts commit d8ae8c201a635195b9113ddd8263309e068c5474. --- selfdrive/ui/mici/layouts/onboarding.py | 17 +++-------------- 1 file changed, 3 insertions(+), 14 deletions(-) diff --git a/selfdrive/ui/mici/layouts/onboarding.py b/selfdrive/ui/mici/layouts/onboarding.py index e19ac9dcf1..b918bf6ef6 100644 --- a/selfdrive/ui/mici/layouts/onboarding.py +++ b/selfdrive/ui/mici/layouts/onboarding.py @@ -258,11 +258,9 @@ class TrainingGuideAttentionNotice(Scroller): class TrainingGuide(NavWidget): - def __init__(self, completed_callback: Callable[[], None], block_back: bool = False): + def __init__(self, completed_callback: Callable[[], None]): super().__init__() - self._block_back = block_back - self._steps = [ TrainingGuideAttentionNotice(continue_callback=lambda: gui_app.push_widget(self._steps[1])), TrainingGuidePreDMTutorial(continue_callback=lambda: gui_app.push_widget(self._steps[2])), @@ -273,14 +271,6 @@ class TrainingGuide(NavWidget): self._child(self._steps[0]) self._steps[0].set_enabled(lambda: self.enabled and not self.is_dismissing) # for nav stack - def _back_enabled(self) -> bool: - return not self._block_back - - def show_event(self): - super().show_event() - if self._block_back: - self._nav_bar._alpha = 0.0 - def _render(self, _): self._steps[0].render(self._rect) @@ -324,8 +314,7 @@ class TermsPage(Scroller): def __init__(self, on_accept, on_decline): super().__init__() - self._accept_button = BigConfirmationCircleButton("accept\nterms", gui_app.texture("icons_mici/setup/driver_monitoring/dm_check.png", 64, 64), on_accept, - exit_on_confirm=False) + self._accept_button = BigConfirmationCircleButton("accept\nterms", gui_app.texture("icons_mici/setup/driver_monitoring/dm_check.png", 64, 64), on_accept) self._decline_button = BigConfirmationCircleButton("decline &\nuninstall", gui_app.texture("icons_mici/setup/cancel.png", 64, 64), on_decline, red=True, exit_on_confirm=False) @@ -360,7 +349,7 @@ class OnboardingWindow(Widget): # Windows self._terms = TermsPage(on_accept=self._on_terms_accepted, on_decline=self._on_uninstall) self._terms.set_enabled(lambda: self.enabled) # for nav stack - self._training_guide = TrainingGuide(completed_callback=self._on_completed_training, block_back=True) + self._training_guide = TrainingGuide(completed_callback=self._on_completed_training) self._training_guide.set_enabled(lambda: self.enabled) # for nav stack def _on_uninstall(self): From 5908b7cda00a0d79453cf876b5dc867397a23b85 Mon Sep 17 00:00:00 2001 From: David <49467229+TheSecurityDev@users.noreply.github.com> Date: Thu, 12 Mar 2026 22:09:10 -0500 Subject: [PATCH 126/141] ui replay: add mici UI exploration (#37641) * replay: add dragging gesture support * update dragging to support distance and duration; update mici script to go through settings * refactor * fix and add network * add more * interact device * fix * match statements * more * improve * simplify script * add keyboard test * format * simplify * improve * comment * improve * clarify * clean * simplify * simplify * move * improve * more delay * simplify keyboard test * simplify * comment * add onroad alert tests to mici * scroll less * test offroad alerts * remove space * scroll faster * more toggle tests * back to home * test settings onroad * fix pairing qr code * add replay progress bar * add replay progress bar * simplify * correct comment * remove _ * we don't need this * change click * add return types * fast typing * use frames instead * use frames instead * update * disable in CI * +1 * fix script * refactor how mici replay script cases are built * refactor * refactor: rename helper function for exploring settings in build_mici_script * remove onroad settings check * refactor * simplify * refactor: use explore_setting in more places to reduce duplication * add type * refactor: simplify explore_cases function by removing swipe_wait parameter * add case to open wifi selection * refactor: enhance run_actions to support after_each callback for interaction tests; rename explore_cases to scroll_through_cases * add review training guide * update comment * comments * comment * fix swipe back --- selfdrive/ui/tests/diff/replay_script.py | 226 ++++++++++++++++++++--- 1 file changed, 203 insertions(+), 23 deletions(-) diff --git a/selfdrive/ui/tests/diff/replay_script.py b/selfdrive/ui/tests/diff/replay_script.py index c43442a33d..c53d2f116b 100644 --- a/selfdrive/ui/tests/diff/replay_script.py +++ b/selfdrive/ui/tests/diff/replay_script.py @@ -15,9 +15,16 @@ from openpilot.selfdrive.ui.tests.diff.replay import FPS, LayoutVariant from openpilot.system.updated.updated import parse_release_notes # Default frames to wait after events -WAIT = FPS // 2 +WAIT_LONG = FPS +WAIT_SHORT = FPS // 2 FAST_CLICK = FPS // 6 +# Direction vectors for drag gestures +DIR_LEFT = (-1, 0) +DIR_RIGHT = (1, 0) +DIR_UP = (0, -1) +DIR_DOWN = (0, 1) + AlertSize = log.SelfdriveState.AlertSize AlertStatus = log.SelfdriveState.AlertStatus @@ -61,26 +68,47 @@ class Script: """Add a delay for the given number of frames followed by an empty event.""" self.add(ScriptEvent(), before=frames) - def setup(self, fn: Callable, wait_after: int = WAIT) -> None: + def setup(self, fn: Callable, wait_after: int = WAIT_SHORT) -> None: """Add a setup function to be called immediately followed by a delay of the given number of frames.""" self.add(ScriptEvent(setup=fn), after=wait_after) - def set_send(self, fn: Callable, wait_after: int = WAIT) -> None: + def set_send(self, fn: Callable, wait_after: int = WAIT_SHORT) -> None: """Set a new persistent send function to be called every frame.""" self.add(ScriptEvent(send_fn=fn), after=wait_after) - # TODO: Also add more complex gestures, like swipe or drag - def click(self, x: int, y: int, wait_after: int = WAIT, wait_between: int = 2) -> None: + def click(self, x: int, y: int, wait_after: int = WAIT_SHORT, wait_between: int = 2) -> None: """Add a click event to the script for the given position and specify frames to wait between mouse events or after the click.""" # NOTE: By default we wait a couple frames between mouse events so pressed states will be rendered from openpilot.system.ui.lib.application import MouseEvent, MousePos - # TODO: Add support for long press (left_down=True) mouse_down = MouseEvent(pos=MousePos(x, y), slot=0, left_pressed=True, left_released=False, left_down=False, t=self.get_frame_time()) self.add(ScriptEvent(mouse_events=[mouse_down]), after=wait_between) mouse_up = MouseEvent(pos=MousePos(x, y), slot=0, left_pressed=False, left_released=True, left_down=False, t=self.get_frame_time()) self.add(ScriptEvent(mouse_events=[mouse_up]), after=wait_after) + def drag(self, start_x: int, start_y: int, direction: tuple[int, int], distance: int, duration_frames: int, wait_after: int = WAIT_LONG) -> None: + """Add a drag gesture to the script from start position in the specified direction by the given distance over the given number of frames.""" + from openpilot.system.ui.lib.application import MouseEvent, MousePos + + # Calculate delta and end position based on direction and distance + delta_x, delta_y = direction[0] * distance, direction[1] * distance + end_x, end_y = start_x + delta_x, start_y + delta_y + + # Mouse down at start + mouse_down = MouseEvent(pos=MousePos(start_x, start_y), slot=0, left_pressed=True, left_released=False, left_down=True, t=self.get_frame_time()) + self.add(ScriptEvent(mouse_events=[mouse_down]), after=1) + + # Interpolate positions over duration_frames + for i in range(1, duration_frames): + t = i / duration_frames + x, y = int(start_x + delta_x * t), int(start_y + delta_y * t) + mouse_move = MouseEvent(pos=MousePos(x, y), slot=0, left_pressed=False, left_released=False, left_down=True, t=self.get_frame_time()) + self.add(ScriptEvent(mouse_events=[mouse_move]), after=1) + + # Mouse up at end + mouse_up = MouseEvent(pos=MousePos(end_x, end_y), slot=0, left_pressed=False, left_released=True, left_down=False, t=self.get_frame_time()) + self.add(ScriptEvent(mouse_events=[mouse_up]), after=wait_after) + # --- Setup functions --- @@ -169,18 +197,181 @@ def make_alert_setup(pm: PubMaster, size, text1, text2, status) -> Callable: return _send +def test_onroad_alerts(script: Script, pm: PubMaster) -> None: + """Go through various alert types and sizes and add them to the script to test alert rendering. + Each alert is sent as a separate event with a delay in between.""" + # Small alert (normal) + script.set_send(make_alert_setup(pm, AlertSize.small, "Small Alert", "This is a small alert", AlertStatus.normal)) + # Medium alert (userPrompt) + script.set_send(make_alert_setup(pm, AlertSize.mid, "Medium Alert", "This is a medium alert", AlertStatus.userPrompt)) + # Full alert (critical) + script.set_send(make_alert_setup(pm, AlertSize.full, "DISENGAGE IMMEDIATELY", "Driver Distracted", AlertStatus.critical)) + # Full alert multiline + script.set_send(make_alert_setup(pm, AlertSize.full, "Reverse\nGear", "", AlertStatus.normal)) + # Full alert long text + script.set_send(make_alert_setup(pm, AlertSize.full, "TAKE CONTROL IMMEDIATELY", "Calibration Invalid: Remount Device & Recalibrate", AlertStatus.userPrompt)) + + # --- Script builders --- def build_mici_script(pm: PubMaster, main_layout, script: Script) -> None: """Build the replay script for the mici layout.""" from openpilot.system.ui.lib.application import gui_app - center = (gui_app.width // 2, gui_app.height // 2) + width, height = gui_app.width, gui_app.height + center = (width // 2, height // 2) + right = (width * 4 // 5, height // 2) + left = (width // 5, height // 2) + top = (width // 2, height // 10) + bottom = (width // 2, height * 9 // 10) + + DURATION = 5 + SWIPE_WAIT = FPS * 3 // 4 + + def click(times: int = 1, wait_after: int = WAIT_SHORT) -> None: + """Click at the center of the screen the given number of times with optional delay after.""" + for _ in range(times): + script.click(*center, wait_after=wait_after) + + def press(x: int, y: int, duration_frames: int = DURATION, wait_after: int = WAIT_SHORT) -> None: + """Perform a drag with no movement to simulate a left_down mouse event at the given position for the specified duration and delay after.""" + script.drag(x, y, (0, 0), 0, duration_frames, wait_after=wait_after) + + def swipe_left(distance: int = right[0] - left[0], duration_frames: int = DURATION, wait_after: int = SWIPE_WAIT) -> None: + """Drag from right edge to left (scroll right / slide confirmation).""" + script.drag(*right, DIR_LEFT, distance, duration_frames, wait_after) + + def swipe_right(distance: int = right[0] - left[0], duration_frames: int = DURATION, wait_after: int = SWIPE_WAIT) -> None: + """Drag from left edge to right (scroll left).""" + script.drag(*left, DIR_RIGHT, distance, duration_frames, wait_after) + + def swipe_down(distance: int = bottom[1] - top[1], duration_frames: int = DURATION, wait_after: int = SWIPE_WAIT) -> None: + """Drag from top edge to bottom (scroll up / go back).""" + script.drag(*top, DIR_DOWN, distance, duration_frames, wait_after) + + def swipe_up(distance: int = bottom[1] - top[1], duration_frames: int = DURATION, wait_after: int = SWIPE_WAIT) -> None: + """Drag from bottom edge to top (scroll down).""" + script.drag(*bottom, DIR_UP, distance, duration_frames, wait_after) + + ActionFn = Callable[[], None] | None + Cases = list[ActionFn] + + def run_actions(*actions: ActionFn, after_each: ActionFn = None) -> None: + """Helper function to run a sequence of actions in order for interaction tests, calling after_each callback after each action if provided.""" + for action in actions: + if action is not None: + action() + if after_each is not None: + after_each() + + def explore_setting(*actions: ActionFn) -> None: + """Helper function to open a settings item, run the given actions, and go back.""" + run_actions(click, *actions, swipe_down) # open, interact, go back + + def scroll_through_cases(cases: Cases) -> None: + """Helper function to explore a panel by calling the interaction callbacks for each item/page before swiping to the next one.""" + run_actions(*cases, after_each=lambda: swipe_left(210, 10)) # swipe to roughly the center of the next toggle after each case + + def interact_keyboard() -> None: + """Interact with the keyboard in various ways to test different actions and states. + Assumes it's a password keyboard with 8 characters required. Closes by pressing confirm at the end.""" + KEY = (250, 160) # key in the middle of the keyboard ('G') + SHIFT = (50, 210) + NUMBERS = (480, 210) + SPACE = (500, 160) + BACKSPACE = (490, 30) + CONFIRM = (50, 30) + # Begin interactions + press(*CONFIRM, wait_after=FAST_CLICK) # confirm while disabled should do nothing + swipe_left(duration_frames=FPS // 2) # swipe to type + swipe_up(duration_frames=FPS // 2) # swipe out of keyboard (nothing typed) + # press various keys to test different states: + for key in [ + SHIFT, KEY, KEY, SHIFT, SHIFT, KEY, KEY, # test casing (upper, lower, caps lock) + SPACE, SPACE, BACKSPACE, BACKSPACE, # test multiple space and backspace + NUMBERS, KEY, center, SHIFT, KEY # test numbers and symbols + ]: + press(*key, wait_after=FAST_CLICK) + # press confirm to close + script.wait(WAIT_SHORT) # wait for confirm to enable + press(*CONFIRM) + + toggle_cases: Cases = [ + lambda: click(times=3, wait_after=FAST_CLICK), # first toggle is personality, which has 3 states + None, None, None, None, None, None, # skip other toggles to save time + lambda: click(times=2, wait_after=FAST_CLICK), # test final toggle (enable openpilot) + ] + + network_cases: Cases = [ + explore_setting, # select wifi (just open and close) + None, None, + lambda: run_actions(click, interact_keyboard), # tether password keyboard + ] + + device_cases: Cases = [ + None, + click, # update + explore_setting, # pairing (just open and close) + lambda: explore_setting( + # training guide + lambda: swipe_left(width * 2), click, # first page, click next + lambda: swipe_left(width * 2), swipe_down # second page, go back (TODO: make driver cam preview work) + ), + None, # TODO: preview driver camera; enabling this causes MultiplePublishersError later in onroad alert tests + lambda: explore_setting(swipe_left), # terms & conditions (swipe to view QR code) + lambda: explore_setting(lambda: swipe_up(height * 3), lambda: swipe_down(height * 3)), # regulatory info + lambda: run_actions(click, lambda: swipe_left(width)), # reset calibration confirm (goes back automatically) + lambda: explore_setting(lambda: swipe_left(width)), # uninstall + lambda: run_actions( + lambda: explore_setting(lambda: swipe_left(width)), # reboot + lambda: script.click(430, 120), lambda: swipe_left(width), swipe_down, # shutdown + ), + ] + + developer_cases: Cases = [ + lambda: click(times=2, wait_after=FAST_CLICK), # toggle ssh mode + explore_setting, # SSH keys keyboard (just open and close) + None, # joystick mode + lambda: click(wait_after=FAST_CLICK), # longitudinal maneuver mode (disabled; should do nothing) + lambda: click(times=2, wait_after=FAST_CLICK), # toggle UI debug mode + ] + + settings_cases: Cases = [ + lambda: scroll_through_cases(toggle_cases), + lambda: scroll_through_cases(network_cases), + lambda: scroll_through_cases(device_cases), + lambda: script.wait(WAIT_SHORT), # pairing + lambda: run_actions(lambda: swipe_up(height * 3), lambda: swipe_down(height * 3)), # firehose (scroll down and back up) + lambda: scroll_through_cases(developer_cases), + ] + + # === Homescreen === # + script.wait(WAIT_SHORT) + swipe_left(width, wait_after=WAIT_SHORT) # onroad screen + swipe_right(width, wait_after=WAIT_SHORT) # back to home + + # === Offroad Alerts === + def setup_offroad_alerts_and_refresh() -> None: + """Setup function to trigger offroad alerts and force a refresh on the alerts layout.""" + setup_offroad_alerts() + main_layout._alerts_layout.refresh() + + swipe_right(width, wait_after=WAIT_SHORT) # open alerts + script.setup(setup_offroad_alerts_and_refresh) # show alerts + swipe_up(height) # scroll alerts + swipe_left(width, wait_after=WAIT_SHORT) # close alerts + + # === Settings === # + click() # open settings + scroll_through_cases([lambda case=case: explore_setting(case) for case in settings_cases]) # explore settings + swipe_down() # back to home + + # === Onroad === + script.set_send(lambda: send_onroad(pm)) + swipe_left(width, wait_after=WAIT_SHORT) # onroad screen + test_onroad_alerts(script, pm) + swipe_right(width) # back to home - # TODO: Explore more - script.wait(FPS) - script.click(*center, FPS) # Open settings - script.click(*center, FPS) # Open toggles script.end() @@ -313,18 +504,7 @@ def build_tizi_script(pm: PubMaster, main_layout, script: Script) -> None: # === Onroad === script.set_send(lambda: send_onroad(pm)) script.click(1000, 500) # click onroad to toggle sidebar - - # === Onroad alerts === - # Small alert (normal) - script.set_send(make_alert_setup(pm, AlertSize.small, "Small Alert", "This is a small alert", AlertStatus.normal)) - # Medium alert (userPrompt) - script.set_send(make_alert_setup(pm, AlertSize.mid, "Medium Alert", "This is a medium alert", AlertStatus.userPrompt)) - # Full alert (critical) - script.set_send(make_alert_setup(pm, AlertSize.full, "DISENGAGE IMMEDIATELY", "Driver Distracted", AlertStatus.critical)) - # Full alert multiline - script.set_send(make_alert_setup(pm, AlertSize.full, "Reverse\nGear", "", AlertStatus.normal)) - # Full alert long text - script.set_send(make_alert_setup(pm, AlertSize.full, "TAKE CONTROL IMMEDIATELY", "Calibration Invalid: Remount Device & Recalibrate", AlertStatus.userPrompt)) + test_onroad_alerts(script, pm) # End script.end() From 0376660023fe0a141d1429e9dce7cdadf9f5e5c0 Mon Sep 17 00:00:00 2001 From: James Vecellio-Grant <159560811+Discountchubbs@users.noreply.github.com> Date: Fri, 13 Mar 2026 10:19:45 -0700 Subject: [PATCH 127/141] ci: modify models repo title (#1764) --- .github/workflows/build-all-tinygrad-models.yaml | 6 +++--- .github/workflows/build-single-tinygrad-model.yaml | 2 +- sunnypilot/models/fetcher.py | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build-all-tinygrad-models.yaml b/.github/workflows/build-all-tinygrad-models.yaml index e901baee0c..412676e5fd 100644 --- a/.github/workflows/build-all-tinygrad-models.yaml +++ b/.github/workflows/build-all-tinygrad-models.yaml @@ -34,10 +34,10 @@ jobs: echo "tinygrad_ref=$ref" >> $GITHUB_OUTPUT echo "tinygrad_ref is $ref" - - name: Checkout docs repo (sunnypilot-docs, gh-pages) + - name: Checkout docs repo (sunnypilot-models, gh-pages) uses: actions/checkout@v4 with: - repository: sunnypilot/sunnypilot-docs + repository: sunnypilot/sunnypilot-models ref: gh-pages path: docs ssh-key: ${{ secrets.CI_SUNNYPILOT_DOCS_PRIVATE_KEY }} @@ -202,7 +202,7 @@ jobs: - name: Checkout docs repo uses: actions/checkout@v4 with: - repository: sunnypilot/sunnypilot-docs + repository: sunnypilot/sunnypilot-models ref: gh-pages path: docs ssh-key: ${{ secrets.CI_SUNNYPILOT_DOCS_PRIVATE_KEY }} diff --git a/.github/workflows/build-single-tinygrad-model.yaml b/.github/workflows/build-single-tinygrad-model.yaml index fae9d6aa01..e7e3b67b51 100644 --- a/.github/workflows/build-single-tinygrad-model.yaml +++ b/.github/workflows/build-single-tinygrad-model.yaml @@ -119,7 +119,7 @@ jobs: - name: Checkout docs repo uses: actions/checkout@v4 with: - repository: sunnypilot/sunnypilot-docs + repository: sunnypilot/sunnypilot-models ref: gh-pages path: docs ssh-key: ${{ secrets.CI_SUNNYPILOT_DOCS_PRIVATE_KEY }} diff --git a/sunnypilot/models/fetcher.py b/sunnypilot/models/fetcher.py index 5990ee2e41..452c59e06b 100644 --- a/sunnypilot/models/fetcher.py +++ b/sunnypilot/models/fetcher.py @@ -116,7 +116,7 @@ class ModelCache: class ModelFetcher: """Handles fetching and caching of model data from remote source""" - MODEL_URL = "https://raw.githubusercontent.com/sunnypilot/sunnypilot-docs/refs/heads/gh-pages/docs/driving_models_v15.json" + MODEL_URL = "https://raw.githubusercontent.com/sunnypilot/sunnypilot-models/refs/heads/gh-pages/docs/driving_models_v15.json" def __init__(self, params: Params): self.params = params From 37ac33fbcccb17e7beb3b329de6c19aa5803c42f Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Fri, 13 Mar 2026 19:19:37 -0400 Subject: [PATCH 128/141] gitignore: add CLAUDE.md and SKILL.md --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index cd5e64e52b..ebe6ca6c9d 100644 --- a/.gitignore +++ b/.gitignore @@ -101,6 +101,8 @@ Pipfile .context/ PLAN.md TASK.md +CLAUDE.md +SKILL.md ### JetBrains ### !.idea/customTargets.xml From 2cc70ef2e4c3d0f7109342e119c67dafcb6f329c Mon Sep 17 00:00:00 2001 From: David <49467229+TheSecurityDev@users.noreply.github.com> Date: Fri, 13 Mar 2026 18:34:22 -0500 Subject: [PATCH 129/141] record: smaller clip sizes by adjusting preset (#37666) use veryfast instead of ultrafast --- system/ui/lib/application.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system/ui/lib/application.py b/system/ui/lib/application.py index 5d40abdc14..1d518309d3 100644 --- a/system/ui/lib/application.py +++ b/system/ui/lib/application.py @@ -299,7 +299,7 @@ class GuiApplication: '-vf', 'vflip,format=yuv420p', # Flip vertically and convert to yuv420p '-r', str(output_fps), # Output frame rate (for speed multiplier) '-c:v', 'libx264', - '-preset', 'ultrafast', + '-preset', 'veryfast', '-crf', str(RECORD_QUALITY) ] if RECORD_BITRATE: From 06630e8a398d9491d173fb4e70a19a58f41c8a2a Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Fri, 13 Mar 2026 19:20:02 -0700 Subject: [PATCH 130/141] setup: remove brew (#37669) --- tools/setup_dependencies.sh | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/tools/setup_dependencies.sh b/tools/setup_dependencies.sh index 0b785bf4a2..8132cd16dc 100755 --- a/tools/setup_dependencies.sh +++ b/tools/setup_dependencies.sh @@ -113,24 +113,12 @@ function install_python_deps() { source .venv/bin/activate } -function install_macos_deps() { - if ! command -v brew > /dev/null 2>&1; then - echo "homebrew not found, skipping macOS system dependency install" - return 0 - fi - - if ! command -v cmake > /dev/null 2>&1; then - brew install cmake - fi -} - # --- Main --- if [[ "$OSTYPE" == "linux-gnu"* ]]; then install_ubuntu_deps echo "[ ] installed system dependencies t=$SECONDS" elif [[ "$OSTYPE" == "darwin"* ]]; then - install_macos_deps if [[ $SHELL == "/bin/zsh" ]]; then RC_FILE="$HOME/.zshrc" elif [[ $SHELL == "/bin/bash" ]]; then From ee9da82aab4bfa59af65a5ce00604b4b2073786b Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Fri, 13 Mar 2026 19:20:33 -0700 Subject: [PATCH 131/141] cleanup build paths (#37667) * cleanup build paths * not used * lil more * rm those too * rm * lil more --- SConstruct | 28 ++++++------------- common/SConscript | 2 +- selfdrive/SConscript | 6 ---- selfdrive/ui/SConscript | 62 ++++++++++++++++++++--------------------- tools/cabana/SConscript | 4 +-- 5 files changed, 41 insertions(+), 61 deletions(-) delete mode 100644 selfdrive/SConscript diff --git a/SConstruct b/SConstruct index 6ec98b429e..0a4a3701a6 100644 --- a/SConstruct +++ b/SConstruct @@ -15,9 +15,6 @@ Decider('MD5-timestamp') SetOption('num_jobs', max(1, int(os.cpu_count()/2))) -AddOption('--asan', action='store_true', help='turn on ASAN') -AddOption('--ubsan', action='store_true', help='turn on UBSan') -AddOption('--mutation', action='store_true', help='generate mutation-ready code') AddOption('--ccflags', action='store', type='string', default='', help='pass arbitrary flags over the command line') AddOption('--verbose', action='store_true', default=False, help='show full build commands') AddOption('--minimal', @@ -99,8 +96,6 @@ if arch == "larch64": env["CC"] = "clang" env["CXX"] = "clang++" env.Append(LIBPATH=[ - "/usr/local/lib", - "/system/vendor/lib64", "/usr/lib/aarch64-linux-gnu", ]) arch_flags = ["-D__TICI__", "-mcpu=cortex-a57"] @@ -112,19 +107,6 @@ elif arch == "Darwin": ]) env.Append(CCFLAGS=["-DGL_SILENCE_DEPRECATION"]) env.Append(CXXFLAGS=["-DGL_SILENCE_DEPRECATION"]) -else: - env.Append(LIBPATH=[ - "/usr/lib", - "/usr/local/lib", - ]) - -# Sanitizers and extra CCFLAGS from CLI -if GetOption('asan'): - env.Append(CCFLAGS=["-fsanitize=address", "-fno-omit-frame-pointer"]) - env.Append(LINKFLAGS=["-fsanitize=address"]) -elif GetOption('ubsan'): - env.Append(CCFLAGS=["-fsanitize=undefined"]) - env.Append(LINKFLAGS=["-fsanitize=undefined"]) _extra_cc = shlex.split(GetOption('ccflags') or '') if _extra_cc: @@ -220,7 +202,15 @@ if arch == "larch64": # Build openpilot SConscript(['third_party/SConscript']) -SConscript(['selfdrive/SConscript']) +# Build selfdrive +SConscript([ + 'selfdrive/pandad/SConscript', + 'selfdrive/controls/lib/lateral_mpc_lib/SConscript', + 'selfdrive/controls/lib/longitudinal_mpc_lib/SConscript', + 'selfdrive/locationd/SConscript', + 'selfdrive/modeld/SConscript', + 'selfdrive/ui/SConscript', +]) if Dir('#tools/cabana/').exists() and arch != "larch64": SConscript(['tools/cabana/SConscript']) diff --git a/common/SConscript b/common/SConscript index 15a0e5eff1..c9bd1c72d1 100644 --- a/common/SConscript +++ b/common/SConscript @@ -1,4 +1,4 @@ -Import('env', 'envCython', 'arch') +Import('env', 'envCython') common_libs = [ 'params.cc', diff --git a/selfdrive/SConscript b/selfdrive/SConscript deleted file mode 100644 index 55f347c44e..0000000000 --- a/selfdrive/SConscript +++ /dev/null @@ -1,6 +0,0 @@ -SConscript(['pandad/SConscript']) -SConscript(['controls/lib/lateral_mpc_lib/SConscript']) -SConscript(['controls/lib/longitudinal_mpc_lib/SConscript']) -SConscript(['locationd/SConscript']) -SConscript(['modeld/SConscript']) -SConscript(['ui/SConscript']) diff --git a/selfdrive/ui/SConscript b/selfdrive/ui/SConscript index 4d7448c62f..1a662e6b24 100644 --- a/selfdrive/ui/SConscript +++ b/selfdrive/ui/SConscript @@ -1,4 +1,3 @@ -import re from pathlib import Path Import('env', 'arch', 'common') @@ -19,39 +18,38 @@ env.Command( if GetOption('extras') and arch == "larch64": # build installers - if arch != "Darwin": - raylib_env = env.Clone() - raylib_env['LIBPATH'] += [f'#third_party/raylib/{arch}/'] - raylib_env['LINKFLAGS'].append('-Wl,-strip-debug') + raylib_env = env.Clone() + raylib_env['LIBPATH'] += [f'#third_party/raylib/{arch}/'] + raylib_env['LINKFLAGS'].append('-Wl,-strip-debug') - raylib_libs = common + ["raylib"] - if arch == "larch64": - raylib_libs += ["GLESv2", "EGL", "gbm", "drm"] - else: - raylib_libs += ["GL"] + raylib_libs = common + ["raylib"] + if arch == "larch64": + raylib_libs += ["GLESv2", "EGL", "gbm", "drm"] + else: + raylib_libs += ["GL"] - release = "release3" - installers = [ - ("openpilot", release), - ("openpilot_test", f"{release}-staging"), - ("openpilot_nightly", "nightly"), - ("openpilot_internal", "nightly-dev"), - ] + release = "release3" + installers = [ + ("openpilot", release), + ("openpilot_test", f"{release}-staging"), + ("openpilot_nightly", "nightly"), + ("openpilot_internal", "nightly-dev"), + ] - cont = raylib_env.Command("installer/continue_openpilot.o", "installer/continue_openpilot.sh", + cont = raylib_env.Command("installer/continue_openpilot.o", "installer/continue_openpilot.sh", + "ld -r -b binary -o $TARGET $SOURCE") + inter = raylib_env.Command("installer/inter_ttf.o", "installer/inter-ascii.ttf", + "ld -r -b binary -o $TARGET $SOURCE") + inter_bold = raylib_env.Command("installer/inter_bold.o", "../assets/fonts/Inter-Bold.ttf", "ld -r -b binary -o $TARGET $SOURCE") - inter = raylib_env.Command("installer/inter_ttf.o", "installer/inter-ascii.ttf", - "ld -r -b binary -o $TARGET $SOURCE") - inter_bold = raylib_env.Command("installer/inter_bold.o", "../assets/fonts/Inter-Bold.ttf", - "ld -r -b binary -o $TARGET $SOURCE") - inter_light = raylib_env.Command("installer/inter_light.o", "../assets/fonts/Inter-Light.ttf", - "ld -r -b binary -o $TARGET $SOURCE") - for name, branch in installers: - d = {'BRANCH': f"'\"{branch}\"'"} - if "internal" in name: - d['INTERNAL'] = "1" + inter_light = raylib_env.Command("installer/inter_light.o", "../assets/fonts/Inter-Light.ttf", + "ld -r -b binary -o $TARGET $SOURCE") + for name, branch in installers: + d = {'BRANCH': f"'\"{branch}\"'"} + if "internal" in name: + d['INTERNAL'] = "1" - obj = raylib_env.Object(f"installer/installers/installer_{name}.o", ["installer/installer.cc"], CPPDEFINES=d) - f = raylib_env.Program(f"installer/installers/installer_{name}", [obj, cont, inter, inter_bold, inter_light], LIBS=raylib_libs) - # keep installers small - assert f[0].get_size() < 2500*1e3, f[0].get_size() + obj = raylib_env.Object(f"installer/installers/installer_{name}.o", ["installer/installer.cc"], CPPDEFINES=d) + f = raylib_env.Program(f"installer/installers/installer_{name}", [obj, cont, inter, inter_bold, inter_light], LIBS=raylib_libs) + # keep installers small + assert f[0].get_size() < 2500*1e3, f[0].get_size() diff --git a/tools/cabana/SConscript b/tools/cabana/SConscript index 1f7ba3eaef..ad77231ea6 100644 --- a/tools/cabana/SConscript +++ b/tools/cabana/SConscript @@ -71,14 +71,12 @@ if arch == "Darwin": else: base_libs.append('Qt5Charts') -qt_libs = base_libs - cabana_env = qt_env.Clone() if arch == "Darwin": cabana_env['CPPPATH'] += [f"{brew_prefix}/include"] cabana_env['LIBPATH'] += [f"{brew_prefix}/lib"] -cabana_libs = [cereal, messaging, visionipc, replay_lib, 'avformat', 'avcodec', 'swresample', 'avutil', 'x264', 'z', 'bz2', 'zstd', 'yuv', 'usb-1.0'] + qt_libs +cabana_libs = [cereal, messaging, visionipc, replay_lib, 'avformat', 'avcodec', 'swresample', 'avutil', 'x264', 'z', 'bz2', 'zstd', 'yuv', 'usb-1.0'] + base_libs opendbc_path = '-DOPENDBC_FILE_PATH=\'"%s"\'' % (cabana_env.Dir("../../opendbc/dbc").abspath) cabana_env['CXXFLAGS'] += [opendbc_path] From 9d19cca006648da50f8e86bb92f10c107a1bf63d Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Fri, 13 Mar 2026 20:12:13 -0700 Subject: [PATCH 132/141] scons: whitelist non-vendored includes and libraries (#37670) --- SConstruct | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/SConstruct b/SConstruct index 0a4a3701a6..be77027ad9 100644 --- a/SConstruct +++ b/SConstruct @@ -8,6 +8,7 @@ import importlib import numpy as np import SCons.Errors +from SCons.Defaults import _stripixes SCons.Warnings.warningAsException(True) @@ -39,6 +40,44 @@ assert arch in [ pkg_names = ['bzip2', 'capnproto', 'eigen', 'ffmpeg', 'libjpeg', 'libyuv', 'ncurses', 'zeromq', 'zstd'] pkgs = [importlib.import_module(name) for name in pkg_names] + +# ***** enforce a whitelist of system libraries ***** +# this prevents silently relying on a 3rd party package, +# e.g. apt-installed libusb. all libraries should either +# be distributed with all Linux distros and macOS, or +# vendored in commaai/dependencies. +allowed_system_libs = { + "EGL", "GLESv2", "GL", "Qt5Charts", "Qt5Core", "Qt5Gui", "Qt5Widgets", + "crypto", "dl", "drm", "gbm", "m", "pthread", "ssl", "usb-1.0", +} + +def _resolve_lib(env, name): + for d in env.Flatten(env.get('LIBPATH', [])): + p = Dir(str(d)).abspath + for ext in ('.a', '.so', '.dylib'): + f = File(os.path.join(p, f'lib{name}{ext}')) + if f.exists() or f.has_builder(): + return f + if name in allowed_system_libs: + return name + raise SCons.Errors.UserError(f"Unexpected non-vendored library '{name}'") + +def _libflags(target, source, env, for_signature): + libs = [] + lp = env.subst('$LIBLITERALPREFIX') + for lib in env.Flatten(env.get('LIBS', [])): + if isinstance(lib, str): + if os.sep in lib or lib.startswith('#'): + libs.append(File(lib)) + elif lib.startswith('-') or (lp and lib.startswith(lp)): + libs.append(lib) + else: + libs.append(_resolve_lib(env, lib)) + else: + libs.append(lib) + return _stripixes(env['LIBLINKPREFIX'], libs, env['LIBLINKSUFFIX'], + env['LIBPREFIXES'], env['LIBSUFFIXES'], env, env['LIBLITERALPREFIX']) + env = Environment( ENV={ "PATH": os.environ['PATH'], @@ -90,6 +129,7 @@ env = Environment( tools=["default", "cython", "compilation_db", "rednose_filter"], toolpath=["#site_scons/site_tools", "#rednose_repo/site_scons/site_tools"], ) +env['_LIBFLAGS'] = _libflags # Arch-specific flags and paths if arch == "larch64": From 24121f8abff77ec86d3cdbc79f5de4445d94716c Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Fri, 13 Mar 2026 20:16:34 -0700 Subject: [PATCH 133/141] ui: asynchronous ssh key fetcher (#37668) * async * clean on failure * fix * meh job * one less * no clear * disable * no clue * better * always passed --- .../ui/mici/layouts/settings/developer.py | 29 +++--- selfdrive/ui/widgets/ssh_key.py | 90 ++++++++++++------- 2 files changed, 77 insertions(+), 42 deletions(-) diff --git a/selfdrive/ui/mici/layouts/settings/developer.py b/selfdrive/ui/mici/layouts/settings/developer.py index eccaa3ec09..b471c50f99 100644 --- a/selfdrive/ui/mici/layouts/settings/developer.py +++ b/selfdrive/ui/mici/layouts/settings/developer.py @@ -5,25 +5,30 @@ from openpilot.selfdrive.ui.mici.widgets.dialog import BigDialog, BigInputDialog from openpilot.system.ui.lib.application import gui_app from openpilot.selfdrive.ui.layouts.settings.common import restart_needed_callback from openpilot.selfdrive.ui.ui_state import ui_state -from openpilot.selfdrive.ui.widgets.ssh_key import SshKeyAction +from openpilot.selfdrive.ui.widgets.ssh_key import SshKeyFetcher class DeveloperLayoutMici(NavScroller): def __init__(self): super().__init__() + self._ssh_fetcher = SshKeyFetcher(ui_state.params) def github_username_callback(username: str): if username: - ssh_keys = SshKeyAction() - ssh_keys._fetch_ssh_key(username) - if not ssh_keys._error_message: - self._ssh_keys_btn.set_value(username) - else: - dlg = BigDialog("", ssh_keys._error_message) - gui_app.push_widget(dlg) + self._ssh_keys_btn.set_value("Loading...") + self._ssh_keys_btn.set_enabled(False) + + def on_response(error): + self._ssh_keys_btn.set_enabled(True) + if error is None: + self._ssh_keys_btn.set_value(username) + else: + self._ssh_keys_btn.set_value("Not set") + gui_app.push_widget(BigDialog("", error)) + + self._ssh_fetcher.fetch(username, on_response) else: - ui_state.params.remove("GithubUsername") - ui_state.params.remove("GithubSshKeys") + self._ssh_fetcher.clear() self._ssh_keys_btn.set_value("Not set") def ssh_keys_callback(): @@ -99,6 +104,10 @@ class DeveloperLayoutMici(NavScroller): ui_state.add_offroad_transition_callback(self._update_toggles) + def _update_state(self): + super()._update_state() + self._ssh_fetcher.update() + def show_event(self): super().show_event() self._update_toggles() diff --git a/selfdrive/ui/widgets/ssh_key.py b/selfdrive/ui/widgets/ssh_key.py index b31a9eb3bd..b3ff5c1711 100644 --- a/selfdrive/ui/widgets/ssh_key.py +++ b/selfdrive/ui/widgets/ssh_key.py @@ -1,7 +1,6 @@ import pyray as rl import requests import threading -import copy from collections.abc import Callable from enum import Enum @@ -25,6 +24,51 @@ from openpilot.system.ui.widgets.list_view import ( VALUE_FONT_SIZE = 48 +class SshKeyFetcher: + HTTP_TIMEOUT = 15 # seconds + + def __init__(self, params: Params): + self._params = params + self._on_response: Callable[[str | None], None] | None = None + self._done: bool = False + self._error: str | None = None + + def fetch(self, username: str, on_response: Callable[[str | None], None]): + self._error = None + self._on_response = on_response + threading.Thread(target=self._fetch_thread, args=(username,), daemon=True).start() + + def update(self): + if not self._done: + return + self._done = False + if self._error is not None: + self.clear() + if self._on_response: + self._on_response(self._error) + + def clear(self): + self._params.remove("GithubUsername") + self._params.remove("GithubSshKeys") + + def _fetch_thread(self, username: str): + try: + response = requests.get(f"https://github.com/{username}.keys", timeout=self.HTTP_TIMEOUT) + response.raise_for_status() + keys = response.text.strip() + if not keys: + raise requests.exceptions.HTTPError("No SSH keys found") + + self._params.put("GithubUsername", username) + self._params.put("GithubSshKeys", keys) + except requests.exceptions.Timeout: + self._error = tr("Request timed out") + except Exception: + self._error = tr("No SSH keys found for user '{}'").format(username) + finally: + self._done = True + + class SshKeyActionState(Enum): LOADING = tr_noop("LOADING") ADD = tr_noop("ADD") @@ -32,7 +76,6 @@ class SshKeyActionState(Enum): class SshKeyAction(ItemAction): - HTTP_TIMEOUT = 15 # seconds MAX_WIDTH = 500 def __init__(self): @@ -40,7 +83,7 @@ class SshKeyAction(ItemAction): self._keyboard = Keyboard(min_text_size=1) self._params = Params() - self._error_message: str = "" + self._fetcher = SshKeyFetcher(self._params) self._text_font = gui_app.font(FontWeight.NORMAL) self._button = Button("", click_callback=self._handle_button_click, button_style=ButtonStyle.LIST_ACTION, border_radius=BUTTON_BORDER_RADIUS, font_size=BUTTON_FONT_SIZE) @@ -55,14 +98,11 @@ class SshKeyAction(ItemAction): self._username = self._params.get("GithubUsername") self._state = SshKeyActionState.REMOVE if self._params.get("GithubSshKeys") else SshKeyActionState.ADD - def _render(self, rect: rl.Rectangle) -> bool: - # Show error dialog if there's an error - if self._error_message: - message = copy.copy(self._error_message) - gui_app.push_widget(alert_dialog(message)) - self._username = "" - self._error_message = "" + def _update_state(self): + super()._update_state() + self._fetcher.update() + def _render(self, rect: rl.Rectangle) -> bool: # Draw username if exists if self._username: text_size = measure_text_cached(self._text_font, self._username, VALUE_FONT_SIZE) @@ -90,8 +130,7 @@ class SshKeyAction(ItemAction): self._keyboard.set_callback(self._on_username_submit) gui_app.push_widget(self._keyboard) elif self._state == SshKeyActionState.REMOVE: - self._params.remove("GithubUsername") - self._params.remove("GithubSshKeys") + self._fetcher.clear() self._refresh_state() def _on_username_submit(self, result: DialogResult): @@ -103,29 +142,16 @@ class SshKeyAction(ItemAction): return self._state = SshKeyActionState.LOADING - threading.Thread(target=lambda: self._fetch_ssh_key(username), daemon=True).start() + self._fetcher.fetch(username, self._on_fetch_response) - def _fetch_ssh_key(self, username: str): - try: - url = f"https://github.com/{username}.keys" - response = requests.get(url, timeout=self.HTTP_TIMEOUT) - response.raise_for_status() - keys = response.text.strip() - if not keys: - raise requests.exceptions.HTTPError(tr("No SSH keys found")) - - # Success - save keys - self._params.put("GithubUsername", username) - self._params.put("GithubSshKeys", keys) + def _on_fetch_response(self, error: str | None): + if error is None: self._state = SshKeyActionState.REMOVE - self._username = username - - except requests.exceptions.Timeout: - self._error_message = tr("Request timed out") - self._state = SshKeyActionState.ADD - except Exception: - self._error_message = tr("No SSH keys found for user '{}'").format(username) + self._username = self._params.get("GithubUsername") + else: self._state = SshKeyActionState.ADD + self._username = "" + gui_app.push_widget(alert_dialog(error)) def ssh_key_item(title: str | Callable[[], str], description: str | Callable[[], str]) -> ListItem: From 380d91c8f71a2d6cdc61384892be9d7fa3f1505f Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Fri, 13 Mar 2026 20:26:32 -0700 Subject: [PATCH 134/141] don't need to whitelist on larch64 --- SConstruct | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/SConstruct b/SConstruct index be77027ad9..e8edfe1fee 100644 --- a/SConstruct +++ b/SConstruct @@ -129,7 +129,8 @@ env = Environment( tools=["default", "cython", "compilation_db", "rednose_filter"], toolpath=["#site_scons/site_tools", "#rednose_repo/site_scons/site_tools"], ) -env['_LIBFLAGS'] = _libflags +if arch != "larch64": + env['_LIBFLAGS'] = _libflags # Arch-specific flags and paths if arch == "larch64": From 46bbe6890a8888e3e7db95b931650f024a64c0fb Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Fri, 13 Mar 2026 21:56:07 -0700 Subject: [PATCH 135/141] mici ui: consistent dialogs (#37671) * new dialog * clean up * got wish * use in mici reset * punctuation * clean up --- .../ui/mici/layouts/settings/developer.py | 2 +- selfdrive/ui/mici/layouts/settings/device.py | 8 ++-- selfdrive/ui/mici/widgets/button.py | 37 +++++++++++++++ selfdrive/ui/mici/widgets/dialog.py | 46 ++++--------------- system/ui/mici_reset.py | 20 +++----- system/ui/mici_setup.py | 39 +--------------- 6 files changed, 59 insertions(+), 93 deletions(-) diff --git a/selfdrive/ui/mici/layouts/settings/developer.py b/selfdrive/ui/mici/layouts/settings/developer.py index b471c50f99..386b468928 100644 --- a/selfdrive/ui/mici/layouts/settings/developer.py +++ b/selfdrive/ui/mici/layouts/settings/developer.py @@ -35,7 +35,7 @@ class DeveloperLayoutMici(NavScroller): github_username = ui_state.params.get("GithubUsername") or "" dlg = BigInputDialog("enter GitHub username...", github_username, minimum_length=0, confirm_callback=github_username_callback) if not system_time_valid(): - dlg = BigDialog("Please connect to Wi-Fi to fetch your key", "") + dlg = BigDialog("", "Please connect to Wi-Fi to fetch your key.") gui_app.push_widget(dlg) return gui_app.push_widget(dlg) diff --git a/selfdrive/ui/mici/layouts/settings/device.py b/selfdrive/ui/mici/layouts/settings/device.py index 909e30ac71..3c165b5bb3 100644 --- a/selfdrive/ui/mici/layouts/settings/device.py +++ b/selfdrive/ui/mici/layouts/settings/device.py @@ -74,7 +74,7 @@ def _engaged_confirmation_click(callback: Callable, action_text: str, icon: rl.T gui_app.push_widget(BigConfirmationDialog(f"slide to\n{action_text.lower()}", icon, confirm_callback, exit_on_confirm=exit_on_confirm, red=red)) else: - gui_app.push_widget(BigDialog(f"Disengage to {action_text}", "")) + gui_app.push_widget(BigDialog("", f"Disengage to {action_text}")) class EngagedConfirmationCircleButton(BigCircleButton): @@ -156,9 +156,9 @@ class PairBigButton(BigButton): return dlg: BigDialog | PairingDialog if not system_time_valid(): - dlg = BigDialog(tr("Please connect to Wi-Fi to complete initial pairing"), "") + dlg = BigDialog("", tr("Please connect to Wi-Fi to complete initial pairing.")) elif UNREGISTERED_DONGLE_ID == (ui_state.params.get("DongleId") or UNREGISTERED_DONGLE_ID): - dlg = BigDialog(tr("Device must be registered with the comma.ai backend to pair"), "") + dlg = BigDialog("", tr("Device must be registered with the comma.ai backend to pair.")) else: dlg = PairingDialog() gui_app.push_widget(dlg) @@ -188,7 +188,7 @@ class UpdateOpenpilotBigButton(BigButton): super()._handle_mouse_release(mouse_pos) if not system_time_valid(): - dlg = BigDialog(tr("Please connect to Wi-Fi to update"), "") + dlg = BigDialog("", tr("Please connect to Wi-Fi to update.")) gui_app.push_widget(dlg) return diff --git a/selfdrive/ui/mici/widgets/button.py b/selfdrive/ui/mici/widgets/button.py index 9724a18192..058c351fb6 100644 --- a/selfdrive/ui/mici/widgets/button.py +++ b/selfdrive/ui/mici/widgets/button.py @@ -335,6 +335,43 @@ class BigMultiToggle(BigToggle): y += 35 +class GreyBigButton(BigButton): + """Users should manage newlines with this class themselves""" + + LABEL_HORIZONTAL_PADDING = 30 + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.set_touch_valid_callback(lambda: False) + + self._rect.width = 476 + + self._label.set_font_size(36) + self._label.set_font_weight(FontWeight.BOLD) + self._label.set_line_height(1.0) + + self._sub_label.set_font_size(36) + self._sub_label.set_text_color(rl.Color(255, 255, 255, int(255 * 0.9))) + self._sub_label.set_font_weight(FontWeight.DISPLAY_REGULAR) + self._sub_label.set_alignment_vertical(rl.GuiTextAlignmentVertical.TEXT_ALIGN_MIDDLE if not self._label.text else + rl.GuiTextAlignmentVertical.TEXT_ALIGN_BOTTOM) + self._sub_label.set_line_height(0.95) + + @property + def LABEL_VERTICAL_PADDING(self): + return BigButton.LABEL_VERTICAL_PADDING if self._label.text else 18 + + def _width_hint(self) -> int: + return int(self._rect.width - self.LABEL_HORIZONTAL_PADDING * 2) + + def _get_label_font_size(self): + return 36 + + def _render(self, _): + rl.draw_rectangle_rounded(self._rect, 0.4, 10, rl.Color(255, 255, 255, int(255 * 0.15))) + self._draw_content(self._rect.y) + + class BigMultiParamToggle(BigMultiToggle): def __init__(self, text: str, param: str, options: list[str], toggle_callback: Callable | None = None, select_callback: Callable | None = None): diff --git a/selfdrive/ui/mici/widgets/dialog.py b/selfdrive/ui/mici/widgets/dialog.py index 396ea35cc8..ed1466449b 100644 --- a/selfdrive/ui/mici/widgets/dialog.py +++ b/selfdrive/ui/mici/widgets/dialog.py @@ -4,14 +4,13 @@ import pyray as rl from typing import Union from collections.abc import Callable from openpilot.system.ui.widgets.nav_widget import NavWidget -from openpilot.system.ui.widgets.label import UnifiedLabel, gui_label +from openpilot.system.ui.widgets.label import UnifiedLabel from openpilot.system.ui.widgets.mici_keyboard import MiciKeyboard from openpilot.system.ui.lib.text_measure import measure_text_cached -from openpilot.system.ui.lib.wrap_text import wrap_text from openpilot.system.ui.lib.application import gui_app, FontWeight, MousePos from openpilot.system.ui.widgets.slider import RedBigSlider, BigSlider from openpilot.common.filter_simple import FirstOrderFilter -from openpilot.selfdrive.ui.mici.widgets.button import BigCircleButton, BigButton +from openpilot.selfdrive.ui.mici.widgets.button import BigCircleButton, BigButton, GreyBigButton DEBUG = False @@ -25,42 +24,17 @@ class BigDialogBase(NavWidget, abc.ABC): class BigDialog(BigDialogBase): - def __init__(self, - title: str, - description: str): + def __init__(self, title: str, description: str, icon: Union[rl.Texture, None] = None): super().__init__() - self._title = title - self._description = description + self._card = GreyBigButton(title, description, icon) def _render(self, _): - super()._render(_) - - # draw title - # TODO: we desperately need layouts - # TODO: coming up with these numbers manually is a pain and not scalable - # TODO: no clue what any of these numbers mean. VBox and HBox would remove all of this shite - max_width = self._rect.width - PADDING * 2 - - title_wrapped = '\n'.join(wrap_text(gui_app.font(FontWeight.BOLD), self._title, 50, int(max_width))) - title_size = measure_text_cached(gui_app.font(FontWeight.BOLD), title_wrapped, 50) - text_x_offset = 0 - title_rect = rl.Rectangle(self._rect.x + text_x_offset + PADDING, - self._rect.y + PADDING, - max_width, - title_size.y) - gui_label(title_rect, title_wrapped, 50, font_weight=FontWeight.BOLD, - alignment=rl.GuiTextAlignment.TEXT_ALIGN_CENTER) - - # draw description - desc_wrapped = '\n'.join(wrap_text(gui_app.font(FontWeight.MEDIUM), self._description, 30, int(max_width))) - desc_size = measure_text_cached(gui_app.font(FontWeight.MEDIUM), desc_wrapped, 30) - desc_rect = rl.Rectangle(self._rect.x + text_x_offset + PADDING, - self._rect.y + self._rect.height / 3, - max_width, - desc_size.y) - # TODO: text align doesn't seem to work properly with newlines - gui_label(desc_rect, desc_wrapped, 30, font_weight=FontWeight.MEDIUM, - alignment=rl.GuiTextAlignment.TEXT_ALIGN_CENTER) + self._card.render(rl.Rectangle( + self._rect.x + self._rect.width / 2 - self._card.rect.width / 2, + self._rect.y + self._rect.height / 2 - self._card.rect.height / 2, + self._card.rect.width, + self._card.rect.height, + )) class BigConfirmationDialog(BigDialogBase): diff --git a/system/ui/mici_reset.py b/system/ui/mici_reset.py index af9d866d54..9cc6e7f3f8 100755 --- a/system/ui/mici_reset.py +++ b/system/ui/mici_reset.py @@ -10,9 +10,8 @@ import pyray as rl from openpilot.system.hardware import HARDWARE, PC from openpilot.system.ui.lib.application import gui_app from openpilot.system.ui.widgets.scroller import Scroller -from openpilot.system.ui.widgets.nav_widget import NavWidget from openpilot.system.ui.mici_setup import GreyBigButton, FailedPage -from openpilot.selfdrive.ui.mici.widgets.dialog import BigConfirmationCircleButton +from openpilot.selfdrive.ui.mici.widgets.dialog import BigDialog, BigConfirmationCircleButton USERDATA = "/dev/disk/by-partlabel/userdata" TIMEOUT = 3*60 @@ -36,16 +35,14 @@ class ResetFailedPage(FailedPage): return False -class ResettingPage(NavWidget): +class ResettingPage(BigDialog): DOT_STEP = 0.6 def __init__(self): - super().__init__() + super().__init__("resetting device", "this may take up to\na minute...", + gui_app.texture("icons_mici/setup/factory_reset.png", 64, 64)) self._show_time = 0.0 - self._resetting_card = GreyBigButton("resetting device", "this may take up to\na minute...", - gui_app.texture("icons_mici/setup/factory_reset.png", 64, 64)) - def show_event(self): super().show_event() self._nav_bar._alpha = 0.0 # not dismissable @@ -57,13 +54,8 @@ class ResettingPage(NavWidget): def _render(self, _): t = (rl.get_time() - self._show_time) % (self.DOT_STEP * 2) dots = "." * min(int(t / (self.DOT_STEP / 4)), 3) - self._resetting_card.set_value(f"this may take up to\na minute{dots}") - self._resetting_card.render(rl.Rectangle( - self._rect.x + self._rect.width / 2 - self._resetting_card.rect.width / 2, - self._rect.y + self._rect.height / 2 - self._resetting_card.rect.height / 2, - self._resetting_card.rect.width, - self._resetting_card.rect.height, - )) + self._card.set_value(f"this may take up to\na minute{dots}") + super()._render(_) class Reset(Scroller): diff --git a/system/ui/mici_setup.py b/system/ui/mici_setup.py index 62d9dcd827..d55fc5e1eb 100755 --- a/system/ui/mici_setup.py +++ b/system/ui/mici_setup.py @@ -28,7 +28,7 @@ from openpilot.system.ui.widgets.slider import LargerSlider from openpilot.selfdrive.ui.mici.layouts.settings.network import WifiNetworkButton from openpilot.selfdrive.ui.mici.layouts.settings.network.wifi_ui import WifiUIMici from openpilot.selfdrive.ui.mici.widgets.dialog import BigInputDialog, BigConfirmationCircleButton -from openpilot.selfdrive.ui.mici.widgets.button import BigButton +from openpilot.selfdrive.ui.mici.widgets.button import BigButton, GreyBigButton NetworkType = log.DeviceState.NetworkType @@ -254,43 +254,6 @@ class FailedPage(NavScroller): self._reason_card.set_visible(False) -class GreyBigButton(BigButton): - """Users should manage newlines with this class themselves""" - - LABEL_HORIZONTAL_PADDING = 30 - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.set_touch_valid_callback(lambda: False) - - self._rect.width = 476 - - self._label.set_font_size(36) - self._label.set_font_weight(FontWeight.BOLD) - self._label.set_line_height(1.0) - - self._sub_label.set_font_size(36) - self._sub_label.set_text_color(rl.Color(255, 255, 255, int(255 * 0.9))) - self._sub_label.set_font_weight(FontWeight.DISPLAY_REGULAR) - self._sub_label.set_alignment_vertical(rl.GuiTextAlignmentVertical.TEXT_ALIGN_MIDDLE if not self._label.text else - rl.GuiTextAlignmentVertical.TEXT_ALIGN_BOTTOM) - self._sub_label.set_line_height(0.95) - - @property - def LABEL_VERTICAL_PADDING(self): - return BigButton.LABEL_VERTICAL_PADDING if self._label.text else 18 - - def _width_hint(self) -> int: - return int(self._rect.width - self.LABEL_HORIZONTAL_PADDING * 2) - - def _get_label_font_size(self): - return 36 - - def _render(self, _): - rl.draw_rectangle_rounded(self._rect, 0.4, 10, rl.Color(255, 255, 255, int(255 * 0.15))) - self._draw_content(self._rect.y) - - class BigPillButton(BigButton): def __init__(self, *args, green: bool = False, disabled_background: bool = False, **kwargs): self._green = green From f4657aa2d5442b382a9db9c646a9f076b078281d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Harald=20Sch=C3=A4fer?= Date: Sat, 14 Mar 2026 13:42:57 -0700 Subject: [PATCH 136/141] Sconstruct: use name (#37675) --- SConstruct | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SConstruct b/SConstruct index e8edfe1fee..e649a6be16 100644 --- a/SConstruct +++ b/SConstruct @@ -57,7 +57,7 @@ def _resolve_lib(env, name): for ext in ('.a', '.so', '.dylib'): f = File(os.path.join(p, f'lib{name}{ext}')) if f.exists() or f.has_builder(): - return f + return name if name in allowed_system_libs: return name raise SCons.Errors.UserError(f"Unexpected non-vendored library '{name}'") From cc4f786846d0b43ecbb2cac26539dfaaa473831c Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sat, 14 Mar 2026 15:01:45 -0700 Subject: [PATCH 137/141] deps: switch vendored packages to per-package release branches (#37678) --- pyproject.toml | 22 +++++++++++----------- uv.lock | 44 ++++++++++++++++++++++---------------------- 2 files changed, 33 insertions(+), 33 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index bf84161317..b60346b031 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -26,17 +26,17 @@ dependencies = [ "numpy >=2.0", # vendored native dependencies - "bzip2 @ git+https://github.com/commaai/dependencies.git@releases#subdirectory=bzip2", - "capnproto @ git+https://github.com/commaai/dependencies.git@releases#subdirectory=capnproto", - "eigen @ git+https://github.com/commaai/dependencies.git@releases#subdirectory=eigen", - "ffmpeg @ git+https://github.com/commaai/dependencies.git@releases#subdirectory=ffmpeg", - "libjpeg @ git+https://github.com/commaai/dependencies.git@releases#subdirectory=libjpeg", - "libyuv @ git+https://github.com/commaai/dependencies.git@releases#subdirectory=libyuv", - "zstd @ git+https://github.com/commaai/dependencies.git@releases#subdirectory=zstd", - "ncurses @ git+https://github.com/commaai/dependencies.git@releases#subdirectory=ncurses", - "zeromq @ git+https://github.com/commaai/dependencies.git@releases#subdirectory=zeromq", - "git-lfs @ git+https://github.com/commaai/dependencies.git@releases#subdirectory=git-lfs", - "gcc-arm-none-eabi @ git+https://github.com/commaai/dependencies.git@releases#subdirectory=gcc-arm-none-eabi", + "bzip2 @ git+https://github.com/commaai/dependencies.git@release-bzip2#subdirectory=bzip2", + "capnproto @ git+https://github.com/commaai/dependencies.git@release-capnproto#subdirectory=capnproto", + "eigen @ git+https://github.com/commaai/dependencies.git@release-eigen#subdirectory=eigen", + "ffmpeg @ git+https://github.com/commaai/dependencies.git@release-ffmpeg#subdirectory=ffmpeg", + "libjpeg @ git+https://github.com/commaai/dependencies.git@release-libjpeg#subdirectory=libjpeg", + "libyuv @ git+https://github.com/commaai/dependencies.git@release-libyuv#subdirectory=libyuv", + "zstd @ git+https://github.com/commaai/dependencies.git@release-zstd#subdirectory=zstd", + "ncurses @ git+https://github.com/commaai/dependencies.git@release-ncurses#subdirectory=ncurses", + "zeromq @ git+https://github.com/commaai/dependencies.git@release-zeromq#subdirectory=zeromq", + "git-lfs @ git+https://github.com/commaai/dependencies.git@release-git-lfs#subdirectory=git-lfs", + "gcc-arm-none-eabi @ git+https://github.com/commaai/dependencies.git@release-gcc-arm-none-eabi#subdirectory=gcc-arm-none-eabi", # body / webrtcd "av", diff --git a/uv.lock b/uv.lock index 578b19f646..ce613e08ba 100644 --- a/uv.lock +++ b/uv.lock @@ -116,12 +116,12 @@ wheels = [ [[package]] name = "bzip2" version = "1.0.8" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=bzip2&rev=releases#9777ee38aa5ca9439843125392af38ed1262e500" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=bzip2&rev=release-bzip2#4808f76c45cb797e697ab21e5e37d68a0ab3b2d4" } [[package]] name = "capnproto" version = "1.0.1" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=capnproto&rev=releases#9777ee38aa5ca9439843125392af38ed1262e500" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=capnproto&rev=release-capnproto#f735fd22c66029b92019019d0596da6a4445b931" } [[package]] name = "casadi" @@ -371,7 +371,7 @@ wheels = [ [[package]] name = "eigen" version = "3.4.0" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=eigen&rev=releases#9777ee38aa5ca9439843125392af38ed1262e500" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=eigen&rev=release-eigen#fc9915c3a81d6488eafcdbbdc428f15d8123e540" } [[package]] name = "execnet" @@ -385,7 +385,7 @@ wheels = [ [[package]] name = "ffmpeg" version = "7.1.0" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=ffmpeg&rev=releases#9777ee38aa5ca9439843125392af38ed1262e500" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=ffmpeg&rev=release-ffmpeg#8d693da088e5905d4479550e07484961765df45b" } [[package]] name = "fonttools" @@ -432,7 +432,7 @@ wheels = [ [[package]] name = "gcc-arm-none-eabi" version = "13.2.1" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=gcc-arm-none-eabi&rev=releases#9777ee38aa5ca9439843125392af38ed1262e500" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=gcc-arm-none-eabi&rev=release-gcc-arm-none-eabi#e101138b29023effc932df7a58fb76a26c4e443a" } [[package]] name = "ghp-import" @@ -449,7 +449,7 @@ wheels = [ [[package]] name = "git-lfs" version = "3.6.1" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=git-lfs&rev=releases#9777ee38aa5ca9439843125392af38ed1262e500" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=git-lfs&rev=release-git-lfs#5407b1d37b7a8a9ae3747cc20cb6e7a7b01f5059" } [[package]] name = "google-crc32c" @@ -567,7 +567,7 @@ wheels = [ [[package]] name = "libjpeg" version = "3.1.0" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=libjpeg&rev=releases#9777ee38aa5ca9439843125392af38ed1262e500" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=libjpeg&rev=release-libjpeg#83fa530843e5109c51aef14327b6fde5dcb4507b" } [[package]] name = "libusb1" @@ -583,7 +583,7 @@ wheels = [ [[package]] name = "libyuv" version = "1922.0" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=libyuv&rev=releases#9777ee38aa5ca9439843125392af38ed1262e500" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=libyuv&rev=release-libyuv#600cdd08cb77cbcc001daeb031abcb5c6008c7c2" } [[package]] name = "markdown" @@ -735,7 +735,7 @@ wheels = [ [[package]] name = "ncurses" version = "6.5" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=ncurses&rev=releases#9777ee38aa5ca9439843125392af38ed1262e500" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=ncurses&rev=release-ncurses#0503ac0d54799b58c84f900dba75abcad17e780f" } [[package]] name = "numpy" @@ -857,30 +857,30 @@ requires-dist = [ { name = "aiohttp" }, { name = "aiortc" }, { name = "av" }, - { name = "bzip2", git = "https://github.com/commaai/dependencies.git?subdirectory=bzip2&rev=releases" }, - { name = "capnproto", git = "https://github.com/commaai/dependencies.git?subdirectory=capnproto&rev=releases" }, + { name = "bzip2", git = "https://github.com/commaai/dependencies.git?subdirectory=bzip2&rev=release-bzip2" }, + { name = "capnproto", git = "https://github.com/commaai/dependencies.git?subdirectory=capnproto&rev=release-capnproto" }, { name = "casadi", specifier = ">=3.6.6" }, { name = "cffi" }, { name = "codespell", marker = "extra == 'testing'" }, { name = "coverage", marker = "extra == 'testing'" }, { name = "crcmod-plus" }, { name = "cython" }, - { name = "eigen", git = "https://github.com/commaai/dependencies.git?subdirectory=eigen&rev=releases" }, - { name = "ffmpeg", git = "https://github.com/commaai/dependencies.git?subdirectory=ffmpeg&rev=releases" }, - { name = "gcc-arm-none-eabi", git = "https://github.com/commaai/dependencies.git?subdirectory=gcc-arm-none-eabi&rev=releases" }, - { name = "git-lfs", git = "https://github.com/commaai/dependencies.git?subdirectory=git-lfs&rev=releases" }, + { name = "eigen", git = "https://github.com/commaai/dependencies.git?subdirectory=eigen&rev=release-eigen" }, + { name = "ffmpeg", git = "https://github.com/commaai/dependencies.git?subdirectory=ffmpeg&rev=release-ffmpeg" }, + { name = "gcc-arm-none-eabi", git = "https://github.com/commaai/dependencies.git?subdirectory=gcc-arm-none-eabi&rev=release-gcc-arm-none-eabi" }, + { name = "git-lfs", git = "https://github.com/commaai/dependencies.git?subdirectory=git-lfs&rev=release-git-lfs" }, { name = "hypothesis", marker = "extra == 'testing'", specifier = "==6.47.*" }, { name = "inputs" }, { name = "jeepney" }, { name = "jinja2", marker = "extra == 'docs'" }, { name = "json-rpc" }, - { name = "libjpeg", git = "https://github.com/commaai/dependencies.git?subdirectory=libjpeg&rev=releases" }, + { name = "libjpeg", git = "https://github.com/commaai/dependencies.git?subdirectory=libjpeg&rev=release-libjpeg" }, { name = "libusb1" }, - { name = "libyuv", git = "https://github.com/commaai/dependencies.git?subdirectory=libyuv&rev=releases" }, + { name = "libyuv", git = "https://github.com/commaai/dependencies.git?subdirectory=libyuv&rev=release-libyuv" }, { name = "matplotlib", marker = "extra == 'dev'" }, { name = "metadrive-simulator", marker = "platform_machine != 'aarch64' and extra == 'tools'", git = "https://github.com/commaai/metadrive.git?rev=minimal" }, { name = "mkdocs", marker = "extra == 'docs'" }, - { name = "ncurses", git = "https://github.com/commaai/dependencies.git?subdirectory=ncurses&rev=releases" }, + { name = "ncurses", git = "https://github.com/commaai/dependencies.git?subdirectory=ncurses&rev=release-ncurses" }, { name = "numpy", specifier = ">=2.0" }, { name = "opencv-python-headless", marker = "extra == 'dev'" }, { name = "pillow" }, @@ -912,9 +912,9 @@ requires-dist = [ { name = "ty", marker = "extra == 'testing'" }, { name = "websocket-client" }, { name = "xattr" }, - { name = "zeromq", git = "https://github.com/commaai/dependencies.git?subdirectory=zeromq&rev=releases" }, + { name = "zeromq", git = "https://github.com/commaai/dependencies.git?subdirectory=zeromq&rev=release-zeromq" }, { name = "zstandard" }, - { name = "zstd", git = "https://github.com/commaai/dependencies.git?subdirectory=zstd&rev=releases" }, + { name = "zstd", git = "https://github.com/commaai/dependencies.git?subdirectory=zstd&rev=release-zstd" }, ] provides-extras = ["docs", "testing", "dev", "tools"] @@ -1652,7 +1652,7 @@ wheels = [ [[package]] name = "zeromq" version = "4.3.5" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=zeromq&rev=releases#9777ee38aa5ca9439843125392af38ed1262e500" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=zeromq&rev=release-zeromq#768bd6d6d67acc7b4e919993967187532af0d410" } [[package]] name = "zstandard" @@ -1682,4 +1682,4 @@ wheels = [ [[package]] name = "zstd" version = "1.5.6" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=zstd&rev=releases#9777ee38aa5ca9439843125392af38ed1262e500" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=zstd&rev=release-zstd#4d4dd0b74dfc52bdeec36706fd1a3a27754679ec" } From 5e7f5dd840995fb806433e2eacefce74fb3d92a1 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sat, 14 Mar 2026 16:43:19 -0700 Subject: [PATCH 138/141] replay/cabana: remove unused openssl dependency (#37680) --- SConstruct | 2 +- tools/cabana/SConscript | 2 +- tools/replay/SConscript | 2 +- tools/replay/util.cc | 10 ---------- tools/replay/util.h | 1 - 5 files changed, 3 insertions(+), 14 deletions(-) diff --git a/SConstruct b/SConstruct index e649a6be16..9d3dc8526e 100644 --- a/SConstruct +++ b/SConstruct @@ -48,7 +48,7 @@ pkgs = [importlib.import_module(name) for name in pkg_names] # vendored in commaai/dependencies. allowed_system_libs = { "EGL", "GLESv2", "GL", "Qt5Charts", "Qt5Core", "Qt5Gui", "Qt5Widgets", - "crypto", "dl", "drm", "gbm", "m", "pthread", "ssl", "usb-1.0", + "dl", "drm", "gbm", "m", "pthread", "usb-1.0", } def _resolve_lib(env, name): diff --git a/tools/cabana/SConscript b/tools/cabana/SConscript index ad77231ea6..d604fe67ef 100644 --- a/tools/cabana/SConscript +++ b/tools/cabana/SConscript @@ -64,7 +64,7 @@ qt_env['LIBPATH'] += ['#selfdrive/ui', ] qt_env['LIBS'] = qt_libs base_frameworks = qt_env['FRAMEWORKS'] -base_libs = [common, messaging, cereal, visionipc, 'm', 'ssl', 'crypto', 'pthread'] + qt_env["LIBS"] +base_libs = [common, messaging, cereal, visionipc, 'm', 'pthread'] + qt_env["LIBS"] if arch == "Darwin": base_frameworks.append('QtCharts') diff --git a/tools/replay/SConscript b/tools/replay/SConscript index 3efa970b37..757f3fec4e 100644 --- a/tools/replay/SConscript +++ b/tools/replay/SConscript @@ -4,7 +4,7 @@ replay_env = env.Clone() replay_env['CCFLAGS'] += ['-Wno-deprecated-declarations'] base_frameworks = [] -base_libs = [common, messaging, cereal, visionipc, 'm', 'ssl', 'crypto', 'pthread'] +base_libs = [common, messaging, cereal, visionipc, 'm', 'pthread'] replay_lib_src = ["replay.cc", "consoleui.cc", "camera.cc", "filereader.cc", "logreader.cc", "framereader.cc", "route.cc", "util.cc", "seg_mgr.cc", "timeline.cc", "py_downloader.cc"] diff --git a/tools/replay/util.cc b/tools/replay/util.cc index 7294de8282..7b308b5c3d 100644 --- a/tools/replay/util.cc +++ b/tools/replay/util.cc @@ -1,7 +1,6 @@ #include "tools/replay/util.h" #include -#include #include #include @@ -162,15 +161,6 @@ void precise_nano_sleep(int64_t nanoseconds, std::atomic &interrupt_reques } } -std::string sha256(const std::string &str) { - unsigned char hash[SHA256_DIGEST_LENGTH]; - SHA256_CTX sha256; - SHA256_Init(&sha256); - SHA256_Update(&sha256, str.c_str(), str.size()); - SHA256_Final(hash, &sha256); - return util::hexdump(hash, SHA256_DIGEST_LENGTH); -} - std::vector split(std::string_view source, char delimiter) { std::vector fields; size_t last = 0; diff --git a/tools/replay/util.h b/tools/replay/util.h index ee92190337..a2d0f6203a 100644 --- a/tools/replay/util.h +++ b/tools/replay/util.h @@ -46,7 +46,6 @@ private: static constexpr float growth_factor = 1.5; }; -std::string sha256(const std::string &str); void precise_nano_sleep(int64_t nanoseconds, std::atomic &interrupt_requested); std::string decompressBZ2(const std::string &in, std::atomic *abort = nullptr); std::string decompressBZ2(const std::byte *in, size_t in_size, std::atomic *abort = nullptr); From a68ea44af3418cf80d6b8d34bd1f7299af05c19a Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sat, 14 Mar 2026 16:47:17 -0700 Subject: [PATCH 139/141] cabana: use vendored libusb from commaai/dependencies (#37681) --- SConstruct | 2 +- pyproject.toml | 1 + tools/cabana/SConscript | 7 ++++--- uv.lock | 7 +++++++ 4 files changed, 13 insertions(+), 4 deletions(-) diff --git a/SConstruct b/SConstruct index 9d3dc8526e..feaadd5a41 100644 --- a/SConstruct +++ b/SConstruct @@ -48,7 +48,7 @@ pkgs = [importlib.import_module(name) for name in pkg_names] # vendored in commaai/dependencies. allowed_system_libs = { "EGL", "GLESv2", "GL", "Qt5Charts", "Qt5Core", "Qt5Gui", "Qt5Widgets", - "dl", "drm", "gbm", "m", "pthread", "usb-1.0", + "dl", "drm", "gbm", "m", "pthread", } def _resolve_lib(env, name): diff --git a/pyproject.toml b/pyproject.toml index b60346b031..a754f580bd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -35,6 +35,7 @@ dependencies = [ "zstd @ git+https://github.com/commaai/dependencies.git@release-zstd#subdirectory=zstd", "ncurses @ git+https://github.com/commaai/dependencies.git@release-ncurses#subdirectory=ncurses", "zeromq @ git+https://github.com/commaai/dependencies.git@release-zeromq#subdirectory=zeromq", + "libusb @ git+https://github.com/commaai/dependencies.git@release-libusb#subdirectory=libusb", "git-lfs @ git+https://github.com/commaai/dependencies.git@release-git-lfs#subdirectory=git-lfs", "gcc-arm-none-eabi @ git+https://github.com/commaai/dependencies.git@release-gcc-arm-none-eabi#subdirectory=gcc-arm-none-eabi", diff --git a/tools/cabana/SConscript b/tools/cabana/SConscript index d604fe67ef..dbe4dbc659 100644 --- a/tools/cabana/SConscript +++ b/tools/cabana/SConscript @@ -2,6 +2,8 @@ import subprocess import os import shutil +import libusb + Import('env', 'arch', 'common', 'messaging', 'visionipc', 'cereal') # Detect Qt - skip build if not available @@ -72,9 +74,8 @@ else: base_libs.append('Qt5Charts') cabana_env = qt_env.Clone() -if arch == "Darwin": - cabana_env['CPPPATH'] += [f"{brew_prefix}/include"] - cabana_env['LIBPATH'] += [f"{brew_prefix}/lib"] +cabana_env['CPPPATH'] += [libusb.INCLUDE_DIR] +cabana_env['LIBPATH'] += [libusb.LIB_DIR] cabana_libs = [cereal, messaging, visionipc, replay_lib, 'avformat', 'avcodec', 'swresample', 'avutil', 'x264', 'z', 'bz2', 'zstd', 'yuv', 'usb-1.0'] + base_libs opendbc_path = '-DOPENDBC_FILE_PATH=\'"%s"\'' % (cabana_env.Dir("../../opendbc/dbc").abspath) diff --git a/uv.lock b/uv.lock index ce613e08ba..8e597db6ef 100644 --- a/uv.lock +++ b/uv.lock @@ -569,6 +569,11 @@ name = "libjpeg" version = "3.1.0" source = { git = "https://github.com/commaai/dependencies.git?subdirectory=libjpeg&rev=release-libjpeg#83fa530843e5109c51aef14327b6fde5dcb4507b" } +[[package]] +name = "libusb" +version = "1.0.29" +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=libusb&rev=release-libusb#5f188080524c3c6d098ab6cb8d206ceef0394e8e" } + [[package]] name = "libusb1" version = "3.3.1" @@ -796,6 +801,7 @@ dependencies = [ { name = "jeepney" }, { name = "json-rpc" }, { name = "libjpeg" }, + { name = "libusb" }, { name = "libusb1" }, { name = "libyuv" }, { name = "ncurses" }, @@ -875,6 +881,7 @@ requires-dist = [ { name = "jinja2", marker = "extra == 'docs'" }, { name = "json-rpc" }, { name = "libjpeg", git = "https://github.com/commaai/dependencies.git?subdirectory=libjpeg&rev=release-libjpeg" }, + { name = "libusb", git = "https://github.com/commaai/dependencies.git?subdirectory=libusb&rev=release-libusb" }, { name = "libusb1" }, { name = "libyuv", git = "https://github.com/commaai/dependencies.git?subdirectory=libyuv&rev=release-libyuv" }, { name = "matplotlib", marker = "extra == 'dev'" }, From 23c774eb19fd7e5c00cc65f45e6a97f58fca2564 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Tue, 17 Mar 2026 06:21:03 -0400 Subject: [PATCH 140/141] sunnylinkd: fetch compressed params schema (#1771) --- sunnypilot/sunnylink/athena/sunnylinkd.py | 35 +++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/sunnypilot/sunnylink/athena/sunnylinkd.py b/sunnypilot/sunnylink/athena/sunnylinkd.py index e8066dca49..39fe5679ce 100755 --- a/sunnypilot/sunnylink/athena/sunnylinkd.py +++ b/sunnypilot/sunnylink/athena/sunnylinkd.py @@ -229,6 +229,41 @@ def getParamsAllKeysV1() -> dict[str, str]: raise +@dispatcher.add_method +def getParamsMetadata() -> str: + """Compressed equivalent of getParamsAllKeysV1 — same struct, gzipped + base64.""" + try: + with open(METADATA_PATH) as f: + metadata = json.load(f) + except Exception: + cloudlog.exception("sunnylinkd.getParamsMetadata.exception") + metadata = {} + + try: + available_keys: list[str] = [k.decode('utf-8') for k in Params().all_keys()] + + params_list: list[dict] = [] + for key in available_keys: + value = get_param_as_byte(key, get_default=True) + + param_entry: dict = { + "key": key, + "type": int(params.get_type(key).value), + "default_value": base64.b64encode(value).decode('utf-8') if value else None, + } + + if key in metadata: + param_entry["_extra"] = metadata[key] + + params_list.append(param_entry) + + raw = json.dumps(params_list, separators=(',', ':')).encode('utf-8') + return base64.b64encode(gzip.compress(raw)).decode('utf-8') + except Exception: + cloudlog.exception("sunnylinkd.getParamsMetadata.exception") + raise + + @dispatcher.add_method def getParams(params_keys: list[str], compression: bool = False) -> str | dict[str, str]: params = Params() From 1658898498b8867dca06b22be85bc650e6a284f9 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Wed, 18 Mar 2026 08:45:27 -0400 Subject: [PATCH 141/141] Controls: default Torque Lateral Control to v0 Tune --- common/params_keys.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/params_keys.h b/common/params_keys.h index a247798e30..3dc0222617 100644 --- a/common/params_keys.h +++ b/common/params_keys.h @@ -271,7 +271,7 @@ inline static std::unordered_map keys = { {"EnforceTorqueControl", {PERSISTENT | BACKUP, BOOL}}, {"LiveTorqueParamsToggle", {PERSISTENT | BACKUP , BOOL}}, {"LiveTorqueParamsRelaxedToggle", {PERSISTENT | BACKUP , BOOL}}, - {"TorqueControlTune", {PERSISTENT | BACKUP, FLOAT}}, + {"TorqueControlTune", {PERSISTENT | BACKUP, FLOAT, "0.0"}}, {"TorqueParamsOverrideEnabled", {PERSISTENT | BACKUP, BOOL, "0"}}, {"TorqueParamsOverrideFriction", {PERSISTENT | BACKUP, FLOAT, "0.1"}}, {"TorqueParamsOverrideLatAccelFactor", {PERSISTENT | BACKUP, FLOAT, "2.5"}},