[
  {
    "path": ".github/generate_notes.py",
    "content": "import re\n\nwith open('CHANGELOG.md') as f:\n    changelog = f.read()\n\nstart = re.search('## ', changelog).start()\nend   = re.search('^# ', changelog[start:], flags=re.MULTILINE).start() + start\n\nprint(\"\"\"\n> [!WARNING]\n> Scrap now pushes new experimental LLVM builds tagged with `-llvm`. Use these with caution!\n\"\"\")\n\nprint(changelog[start:end].strip())\n"
  },
  {
    "path": ".github/workflows/build.yml",
    "content": "name: Build\n\non:\n  push:\n    branches:\n      - \"main\"\n      - \"dev_**\"\n    paths-ignore:\n      - \"README.md\"\n      - \"LICENSE\"\n      - \"CHANGELOG.md\"\n  pull_request:\n    branches:\n      - \"main\"\n      - \"dev_**\"\n    paths-ignore:\n      - \"README.md\"\n      - \"LICENSE\"\n      - \"CHANGELOG.md\"\n\njobs:\n    build-windows:\n        name: Windows build\n        runs-on: windows-latest\n\n        defaults:\n          run:\n            shell: msys2 {0}\n\n        steps:\n            - name: Checkout code\n              uses: actions/checkout@v6\n              with:\n                submodules: recursive\n\n            - name: Install dependencies\n              uses: msys2/setup-msys2@v2\n              with:\n                msystem: UCRT64\n                update: true\n                install: mingw-w64-ucrt-x86_64-gcc mingw-w64-ucrt-x86_64-llvm make gettext\n\n            - name: Setup windres\n              run: ln -sf \"${MSYSTEM_PREFIX}/bin/windres.exe\" \"${MSYSTEM_PREFIX}/bin/x86_64-w64-mingw32-windres\"\n\n            - name: Build project (With compiler)\n              run: |\n                make clean\n                make USE_COMPILER=TRUE TARGET=WINDOWS\n\n            - name: Build project (With interpreter)\n              run: |\n                make clean\n                make TARGET=WINDOWS\n\n    build-linux:\n        name: Linux build\n        runs-on: ubuntu-22.04\n\n        steps:\n            - name: Checkout code\n              uses: actions/checkout@v6\n              with:\n                submodules: recursive\n\n            - name: Install dependencies\n              run: |\n                sudo apt-get update -y\n                sudo apt-get install -y --no-install-recommends libxcursor-dev libxrandr-dev libxinerama-dev libxi-dev gettext\n\n            - name: Install LLVM\n              run: |\n                wget -qO- https://apt.llvm.org/llvm-snapshot.gpg.key | gpg --dearmor -o llvm-snapshot.gpg\n                sudo mv llvm-snapshot.gpg /etc/apt/trusted.gpg.d/\n                sudo add-apt-repository \"deb http://apt.llvm.org/focal/ llvm-toolchain-focal-19 main\"\n                sudo apt-get update -y\n                sudo apt-get install -y --no-install-recommends llvm-19-dev\n\n            - name: Build project (With compiler)\n              run: |\n                make clean\n                make USE_COMPILER=TRUE LLVM_CONFIG=llvm-config-19\n\n            - name: Build project (With interpreter)\n              run: |\n                make clean\n                make\n"
  },
  {
    "path": ".github/workflows/release.yml",
    "content": "name: Release\n\non:\n  push:\n    tags:\n      - 'v*.*-*'\n\npermissions:\n  contents: write\n\nenv:\n  SCRAP_VERSION: ${{ github.ref_name }}\n\njobs:\n  build-linux:\n    name: Build Linux release\n    runs-on: ubuntu-22.04\n    steps:\n      - name: Checkout code\n        uses: actions/checkout@v6\n        with:\n          submodules: recursive\n      \n      - name: Install dependencies\n        run: |\n          sudo apt-get update -y\n          sudo apt-get upgrade -y\n          sudo apt-get install -y libxcursor-dev libxrandr-dev libxinerama-dev libxi-dev gettext zlib1g-dev libzstd-dev libxml2-dev\n\n      - name: Install LLVM\n        run: |\n          wget -qO- https://apt.llvm.org/llvm-snapshot.gpg.key | gpg --dearmor -o llvm-snapshot.gpg\n          sudo mv llvm-snapshot.gpg /etc/apt/trusted.gpg.d/\n          sudo add-apt-repository \"deb http://apt.llvm.org/focal/ llvm-toolchain-focal-19 main\"\n          sudo apt-get update -y\n          sudo apt-get install -y --no-install-recommends llvm-19-dev\n\n      - name: Build Scrap compiler\n        run: |\n          make clean\n          make USE_COMPILER=TRUE LLVM_CONFIG=llvm-config-19 LLVM_LINK_STATIC=TRUE\n          mkdir -p build/scrap-${SCRAP_VERSION}-llvm-linux\n          cp -r data examples extras locale build/scrap-${SCRAP_VERSION}-llvm-linux/\n          cp LICENSE README.md CHANGELOG.md scrap libscrapstd.a build/scrap-${SCRAP_VERSION}-llvm-linux/\n          tar --directory=build -czf build/scrap-${SCRAP_VERSION}-llvm-linux.tar.gz scrap-${SCRAP_VERSION}-llvm-linux\n          mkdir -p dist\n          mv build/*.gz dist/\n      \n      - name: Build Scrap interpreter\n        run: |\n          make clean\n          make\n          mkdir -p build/scrap-${SCRAP_VERSION}-linux\n          cp -r data examples extras locale build/scrap-${SCRAP_VERSION}-linux/\n          cp LICENSE README.md CHANGELOG.md scrap libscrapstd.a build/scrap-${SCRAP_VERSION}-linux/\n          tar --directory=build -czf build/scrap-${SCRAP_VERSION}-linux.tar.gz scrap-${SCRAP_VERSION}-linux\n          mv build/*.gz dist/\n          \n      - name: Upload Linux artifacts\n        uses: actions/upload-artifact@v6\n        with:\n          name: scrap-${{ env.SCRAP_VERSION }}-linux\n          path: dist/*\n          retention-days: 10\n      \n  build-appimage:\n    name: Build Appimage release\n    runs-on: ubuntu-22.04\n    steps:\n      - name: Checkout code\n        uses: actions/checkout@v6\n        with:\n          submodules: recursive\n\n      - name: Install dependencies\n        run: |\n          sudo apt-get update -y\n          sudo apt-get upgrade -y\n          sudo apt-get install -y libxcursor-dev libxrandr-dev libxinerama-dev libxi-dev gettext zlib1g-dev libzstd-dev libxml2-dev\n\n      - name: Install LLVM\n        run: |\n          wget -qO- https://apt.llvm.org/llvm-snapshot.gpg.key | gpg --dearmor -o llvm-snapshot.gpg\n          sudo mv llvm-snapshot.gpg /etc/apt/trusted.gpg.d/\n          sudo add-apt-repository \"deb http://apt.llvm.org/focal/ llvm-toolchain-focal-19 main\"\n          sudo apt-get update -y\n          sudo apt-get install -y --no-install-recommends llvm-19-dev\n      \n      - name: Install AppImageTool\n        run: |\n          wget -O appimagetool https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage\n          chmod +x appimagetool\n          sudo mv appimagetool /usr/bin/appimagetool\n        \n      - name: Build Scrap compiler\n        run: |\n          make clean\n          make USE_COMPILER=TRUE LLVM_CONFIG=llvm-config-19 LLVM_LINK_STATIC=TRUE\n          mkdir -p build/scrap.AppDir\n          cp scrap build/scrap.AppDir/AppRun\n          cp -r data locale build/scrap.AppDir/\n          cp scrap.desktop libscrapstd.a extras/scrap.png build/scrap.AppDir/\n          mkdir -p dist\n          appimagetool --appimage-extract-and-run build/scrap.AppDir build/Scrap-${SCRAP_VERSION}-llvm.AppImage\n          mv build/Scrap-${SCRAP_VERSION}-llvm.AppImage dist/\n\n      - name: Build Scrap interpreter\n        run: |\n          make clean\n          make\n          mkdir -p build/scrap.AppDir\n          cp scrap build/scrap.AppDir/AppRun\n          cp -r data locale build/scrap.AppDir/\n          cp scrap.desktop libscrapstd.a extras/scrap.png build/scrap.AppDir/\n          appimagetool --appimage-extract-and-run build/scrap.AppDir build/Scrap-${SCRAP_VERSION}.AppImage\n          mv build/Scrap-${SCRAP_VERSION}.AppImage dist/\n          \n      - name: Upload AppImage artifacts\n        uses: actions/upload-artifact@v6\n        with:\n          name: scrap-${{ env.SCRAP_VERSION }}-appimage\n          path: dist/*\n          retention-days: 10\n\n  build-windows:\n    name: Build Windows release\n    runs-on: windows-latest\n    defaults:\n      run:\n        shell: msys2 {0}\n    steps:\n      - name: Checkout code\n        uses: actions/checkout@v6\n        with:\n          submodules: recursive\n\n      - name: Install dependencies\n        uses: msys2/setup-msys2@v2\n        with:\n          msystem: UCRT64\n          update: true\n          install: mingw-w64-ucrt-x86_64-gcc mingw-w64-ucrt-x86_64-llvm make gettext\n\n      - name: Setup windres\n        run: ln -sf \"${MSYSTEM_PREFIX}/bin/windres.exe\" \"${MSYSTEM_PREFIX}/bin/x86_64-w64-mingw32-windres\"\n\n      - name: Build Scrap compiler\n        run: |\n           make clean\n           make USE_COMPILER=TRUE TARGET=WINDOWS\n           mkdir -p build/scrap-${SCRAP_VERSION}-llvm-windows64\n           cp -r locale data examples extras build/scrap-${SCRAP_VERSION}-llvm-windows64/\n           cp CHANGELOG.md LICENSE README.md libscrapstd-win.a scrap.exe build/scrap-${SCRAP_VERSION}-llvm-windows64/\n           powershell.exe -Command \"Compress-Archive -Path build/scrap-${SCRAP_VERSION}-llvm-windows64 -DestinationPath build/scrap-${SCRAP_VERSION}-llvm-windows64.zip -Force\"\n           mkdir -p dist\n           mv build/*.zip dist/\n           \n      - name: Build Scrap interpreter\n        run: |\n          make clean\n          make TARGET=WINDOWS\n          mkdir -p build/scrap-${SCRAP_VERSION}-windows64\n          cp -r locale data examples extras build/scrap-${SCRAP_VERSION}-windows64/\n          cp CHANGELOG.md LICENSE README.md libscrapstd-win.a scrap.exe build/scrap-${SCRAP_VERSION}-windows64/\n          powershell.exe -Command \"Compress-Archive -Path build/scrap-${SCRAP_VERSION}-windows64 -DestinationPath build/scrap-${SCRAP_VERSION}-windows64.zip -Force\"\n          mv build/*.zip dist/\n\n      - name: Upload Windows artifacts\n        uses: actions/upload-artifact@v6\n        with:\n          name: scrap-${{ env.SCRAP_VERSION }}-windows64\n          path: dist/*\n          retention-days: 10\n\n  release:\n    needs: [build-linux, build-appimage, build-windows]\n    runs-on: ubuntu-22.04\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v6\n\n      - name: Download build artifacts\n        uses: actions/download-artifact@v7\n        with:\n          path: dist\n          pattern: 'scrap-*'\n          merge-multiple: true\n      \n      - name: Generate release notes\n        run: python \"$GITHUB_WORKSPACE/.github/generate_notes.py\" > release_notes.md\n\n      - name: Create a draft release\n        env:\n          GITHUB_TOKEN: ${{ github.token }}\n        run: |\n          gh release create \"$SCRAP_VERSION\" --draft --title \"$SCRAP_VERSION\" --notes-file release_notes.md\n          gh release upload \"$SCRAP_VERSION\" \"$GITHUB_WORKSPACE/dist/*.AppImage\"\n          gh release upload \"$SCRAP_VERSION\" \"$GITHUB_WORKSPACE/dist/*.tar.gz\"\n          gh release upload \"$SCRAP_VERSION\" \"$GITHUB_WORKSPACE/dist/*.zip\"\n"
  },
  {
    "path": ".gitignore",
    "content": "tags\n*.o\nscrap\nscrap.exe\nprofile.json\ncoredump\nconfig.txt\nscrap.res\nprivate/\nlocale/\nscrap.AppDir/\nbuild/\na.out\nlibscrapstd.a\nlibscrapstd-win.a\n"
  },
  {
    "path": ".gitmodules",
    "content": "[submodule \"raylib\"]\n\tpath = raylib\n\turl = https://github.com/raysan5/raylib.git\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "# v0.6.1-beta *(27-02-2026)*\n\n## What's new?\n- Re-added block folding feature. Now if the last argument size was more than 500 pixels then the next arguments will be drawn on the next line to save horizontal space\n\n## Fixes\n- Fixed buffer overflow when rendering glyphs that are not present in font (This also fixes text in Kazakh language being absolutely broken)\n- Fixed crash when saving/loading blocks with color arguments\n- Fixed memory leak when converting legacy color arguments\n\n# v0.6-beta *(31-01-2026)*\n\n## What's new?\n- Scrap now shows confirmation window when trying to close scrap with unsaved changes\n- Blocks inside block pallete are now split into subcategories\n- Added new color type and integrated it with color related blocks\n- Added new example (neon.scrp) featuring new color system\n- Added ability to change colors of custom blocks\n- Added browse button for path text inputs\n- Added various icons to buttons and windows\n- Added multiple block pickup system. You can now pickup and drop multiple block chains using `shift` button\n- Added a settings option to toggle block previews\n- UI size and Font settings can now be applied without reloading the app\n\n## Fixes\n- Fixed block definitions being able to be edited in block palette\n- Fixed pow block not working with float values in compiler\n- Dropdowns now close when dragging code area with middle mouse button. This prevents a bunch of crashes related to dropdowns\n\n# v0.5.1-beta *(27-12-2025)*\n\n## What's new?\n- Updated Raylib version to latest commit (fc843dc557 as of time writing)\n- Added visual feedback when settings are applied\n- Text inputs in blocks are now rendered as slightly tinted to block color\n\n## Fixes\n- Fixed search menu spawning incomplete control blocks\n- Fixed text inputs being editable in block palette\n- Projects built with LLVM now switch to UTF-8 code page on windows. This fixes garbled output when printing characters outside ASCII range\n\n# v0.5-beta *(20-12-2025)*\n\n## What's new?\n- Added new experimental LLVM compiler backend. It is available as a separate download option in the releases (tagged with `-llvm`)\n- (LLVM only) Added option to build scrap project into executable. Windows users are expected to have gcc (Specifically `x86_64-w64-mingw32-gcc`) installed and added to PATH, Linux users only need a linker to build, such as `ld` (Configurable in build options)\n- All variables declared under `When |> clicked` are now considered global and can be accessed anywhere in the code. The rest of the variables are considered local and only visible in the scope they are declared\n- Added a lot of runtime/compile time errors, such as out of scope variable access, mismatched types and etc.\n- Shortened list and variable blocks to make code a bit more concise\n- Implemented text selection to all text inputs, as well as more useful shortcuts such as Shift selection (Shift-<arrows>), copy (Ctrl-C), paste (Ctrl-V), cut (Ctrl-X), delete all (Ctrl-U) and select all (Ctrl-A)\n- Terminal contents are now cropped when the terminal is resized\n- Added block previews when trying to attach blockchains together\n- Save files are no longer limited to 32 KB\n\n## Fixes\n- Fixed numpad Enter key not working in certain scenarios\n- Fixed race condition with thread running state after calling `pthread_create`. This should theoretically fix code not stopping its execution on Windows\n- Fixed code area being able to scroll while window is open\n- Fixed memory leak when copying blockdef with instantiated blocks\n- Fixed block search menu being able to open when code is running\n- Control blocks (like while, if) now render correctly in the block palette\n- Fixed code area being able to be dragged outside the code panel when code is running\n\n# v0.4.2-beta *(12-02-2025)*\n\n## Fixes\n- Fixed loaded custom blocks having undefined hints\n- Fixed textboxed being interactable while vm is running\n- Fixed code area floating away when editing panel while the block is selected\n- Minor translation fixes\n\n# v0.4.1-beta *(07-02-2025)*\n\n## What's new?\n- Added Ukrainian translation *(by @jackmophin)*\n\n## Fixes\n- Fixed localizations sometimes not working on Windows\n- Fixed codebase scrolling with search menu at the same time\n\n# v0.4-beta *(05-02-2025)*\n\n## What's new?\n- Added translations for 2 languages: Russian *(by @Grisshink)* and Kazakh *(by @unknownkeyNB)*\n- The sidebar (Now named as block palette to remove ambiguity) is now split up into various categories to make finding blocks easier\n- The terminal's background color has been changed to match with the color of other panels\n- All of the text boxes were upgraded to allow inserting or deleting at any position\n- Now if any block input is empty, it will show a small hint of what it needs\n- Added codebase movement through keyboard keys, see `README.md` for more details\n- Added block search menu. You can open it by pressing `S` in code area\n\n## Fixes\n- Fixed \"crash\" when vm overflows/underflows one its stacks\n- Fixed crash when trying to divide by zero in code\n- Fixed scrollbars sometimes appearing behind scroll container\n- Fixed text in the settings going out of bounds when the text is too large\n- Fixed codespace occasionally jumping far away when it is being dragged outside of the window\n- Fixed code renderer not checking with proper culling bounds. This should slightly improve performance of the renderer\n\n# v0.3.1-beta *(28-01-2025)*\n\n## Fixes\n- Fixed a crash when attaching a block on a chain which overlaps on top of another block which is an argument to another block in different chain\n- Fixed scroll containers not updating their lower bounds when resized\n\n# v0.3-beta *(26-01-2025)*\n\n## What's new?\n- The whole gui system was reworked to remove cluttered code and to allow more flexible UI. As a result some parts of the UI may have changed slightly\n- Added `New project` button to quickly clear the workspace\n- The code area has been split up into flexible, customizable panels. This gives a lot more choice for UI customization\n- Now scrap only redraws the screen if its internal state is changed or an input is recieved\n- Updated `actual_3d.scrp` example\n- Now scrap config is saved in OS specific folder, instead of saving relative to the working directory\n- Added terminal colors support\n- Added `colors.scrp` example to demonstrate the use of colors\n\n# v0.2-beta *(06-01-2025)*\n\n## What's new?\n- Added various string manipulation blocks\n- Added shortcuts to run/stop project (they are bound to `F5` and `F6` buttons respectively)\n- Added codebase movement through scroll wheel/touchpad\n- Added another 3D example\n\n## Fixes\n- Fixed save corruption when saving unused custom blocks\n- Fixed custom blocks breaking when deleting arguments\n- Fixed some compilation issues with gcc-9\n- Fixed AppImage paths issue again\n- Fixed block highlighting with custom blocks\n\n# v0.1.1-beta *(30-12-2024)*\n\n## What's new?\n- Added icon to the executable\n- Added experimental MacOS release provided by @arducat\n\n## Fixes\n- Fixed AppImage paths issue\n- Fixed some resizing issues on linux\n\n# v0.1-beta *(29-12-2024)*\n- First beta release!\n"
  },
  {
    "path": "LICENSE",
    "content": "zlib License\n\nCopyright (C) 2024-2026 Grisshink\n\nThis software is provided 'as-is', without any express or implied\nwarranty.  In no event will the authors be held liable for any damages\narising from the use of this software.\n\nPermission is granted to anyone to use this software for any purpose,\nincluding commercial applications, and to alter it and redistribute it\nfreely, subject to the following restrictions:\n\n1. The origin of this software must not be misrepresented; you must not\n   claim that you wrote the original software. If you use this software\n   in a product, an acknowledgment in the product documentation would be\n   appreciated but is not required.\n2. Altered source versions must be plainly marked as such, and must not be\n   misrepresented as being the original software.\n3. This notice may not be removed or altered from any source distribution.\n"
  },
  {
    "path": "Makefile",
    "content": "SCRAP_VERSION ?= dev\n\nMAKE ?= make\nTARGET ?= LINUX\nBUILD_MODE ?= RELEASE\nUSE_COMPILER ?= FALSE\nBUILD_FOLDER := build/\nPREFIX ?= /usr/local\n\nifeq ($(USE_COMPILER), TRUE)\n\tSCRAP_VERSION := $(SCRAP_VERSION)-llvm\nendif\n\nCFLAGS := -Wall -Wextra -std=c11 -D_GNU_SOURCE -DSCRAP_VERSION=\\\"$(SCRAP_VERSION)\\\" -I./raylib/src\n\nifeq ($(TARGET), LINUX)\n\tCC := gcc\n\tLDFLAGS := -lm -lpthread -lX11 -ldl\nelse ifeq ($(TARGET), OSX)\n\t# Thanks to @arducat for MacOS support\n\tCC := clang\n\tLDFLAGS := -framework CoreVideo -framework IOKit -framework Cocoa -framework GLUT -framework OpenGL -lm -lpthread -lintl\nelse\n\tCC := x86_64-w64-mingw32-gcc\n\tLDFLAGS := -static -lole32 -lcomdlg32 -lwinmm -lgdi32 -lintl -liconv -lshlwapi -Wl,--subsystem,windows\nendif\n\nifeq ($(ARABIC_MODE), TRUE)\n\tCFLAGS += -DARABIC_MODE\nendif\n\nifeq ($(RAM_OVERLOAD), TRUE)\n\tCFLAGS += -DRAM_OVERLOAD\nendif\n\nifeq ($(CC), clang)\n\tCFLAGS += -ferror-limit=5\nelse\n\tCFLAGS += -fmax-errors=5\nendif\n\nifeq ($(BUILD_MODE), RELEASE)\n\tCFLAGS += -s -O3\nelse\n\tCFLAGS += -g -O1 -DDEBUG\n\tLDFLAGS += -fsanitize=address -fsanitize=undefined -fno-omit-frame-pointer\nendif\n\nSTD_CFLAGS := $(CFLAGS) -fPIC\n\nifeq ($(BUILD_MODE), DEBUG)\n\tCFLAGS += -fsanitize=address -fsanitize=undefined -fno-omit-frame-pointer\nendif\n\nSTD_OBJFILES := $(addprefix $(BUILD_FOLDER),vec-stand.o gc-stand.o std-stand.o scrap-runtime.o)\nOBJFILES := $(addprefix $(BUILD_FOLDER),filedialogs.o render.o save.o term.o blocks.o scrap.o vec.o util.o ui.o scrap_gui.o window.o cfgpath.o platform.o ast.o gc.o std.o thread.o vm.o)\nBUNDLE_FILES := data examples extras locale LICENSE README.md CHANGELOG.md\nSCRAP_HEADERS := src/scrap.h src/ast.h src/config.h src/scrap_gui.h\nEXE_NAME := scrap\n\nifeq ($(TARGET), WINDOWS)\n\tSTD_NAME := libscrapstd-win.a\nelse\n\tSTD_NAME := libscrapstd.a\nendif\n\nifeq ($(USE_COMPILER), FALSE)\n\tOBJFILES += $(addprefix $(BUILD_FOLDER),interpreter.o)\n\tSCRAP_HEADERS += src/interpreter.h\n\tCFLAGS += -DUSE_INTERPRETER\nelse\n\tLLVM_CONFIG ?= llvm-config\n\tOBJFILES += $(addprefix $(BUILD_FOLDER),compiler.o)\n\tSCRAP_HEADERS += src/compiler.h\n\n\tLLVM_LDFLAGS := --ldflags --system-libs --libs core executionengine mcjit analysis native\n\tifeq ($(TARGET), WINDOWS)\n\t\tLDFLAGS += `$(LLVM_CONFIG) $(LLVM_FLAGS) --link-static $(LLVM_LDFLAGS) | sed 's/\\.dll//'`\n\telse\n\t\tifeq ($(LLVM_LINK_STATIC), TRUE)\n\t\t\tLDFLAGS += -Wl,-Bstatic `$(LLVM_CONFIG) $(LLVM_FLAGS) --link-static $(LLVM_LDFLAGS)` -Wl,-Bdynamic\n\t\telse\n\t\t\tLDFLAGS += `$(LLVM_CONFIG) $(LLVM_FLAGS) $(LLVM_LDFLAGS)`\n\t\tendif\n\tendif\n\tCFLAGS += `$(LLVM_CONFIG) --cflags`\n\n\tLDFLAGS += -lstdc++\nendif\n\n.PHONY: all clean target translations\n\nall: target std translations\n\nmkbuild:\n\tmkdir -p $(BUILD_FOLDER)\n\nclean:\n\t$(MAKE) -C raylib/src clean\n\trm -f scrap.res $(EXE_NAME) $(EXE_NAME).exe libscrapstd.a libscrapstd-win.a\n\trm -rf locale $(BUILD_FOLDER)\n\ntranslations:\n\t@echo === Generating locales... ===\n\trm -rf locale\n\tcp -r translations locale\n\tmsgfmt -o locale/ru/LC_MESSAGES/scrap.mo locale/ru/LC_MESSAGES/scrap.po\n\trm locale/ru/LC_MESSAGES/scrap.po\n\tmsgfmt -o locale/kk/LC_MESSAGES/scrap.mo locale/kk/LC_MESSAGES/scrap.po\n\trm locale/kk/LC_MESSAGES/scrap.po\n\tmsgfmt -o locale/uk/LC_MESSAGES/scrap.mo locale/uk/LC_MESSAGES/scrap.po\n\trm locale/uk/LC_MESSAGES/scrap.po\n\ninstall: translations target std\n\tmkdir -p $(PREFIX)/share/scrap\n\tmkdir -p $(PREFIX)/share/doc/scrap\n\tmkdir -p $(PREFIX)/bin\n\tmkdir -p $(PREFIX)/lib\n\tcp -r data $(PREFIX)/share/scrap\n\tcp -r locale $(PREFIX)/share\n\tcp -r examples $(PREFIX)/share/doc/scrap\n\tcp $(EXE_NAME) $(PREFIX)/bin\n\tcp $(STD_NAME) $(PREFIX)/lib\n\nuninstall:\n\trm -rf $(PREFIX)/share/scrap\n\trm -rf $(PREFIX)/share/doc/scrap\n\trm -f $(PREFIX)/bin/$(EXE_NAME)\n\trm -f $(PREFIX)/lib/$(STD_NAME)\n\nifeq ($(TARGET), WINDOWS)\ntarget: mkbuild $(EXE_NAME).exe\nelse\ntarget: mkbuild $(EXE_NAME)\nendif\n\n$(EXE_NAME).exe: $(OBJFILES)\n\t$(MAKE) -C raylib/src CC=$(CC) PLATFORM_OS=$(TARGET)\n\tx86_64-w64-mingw32-windres scrap.rc -O coff -o scrap.res\n\t$(CC) -o $@ $^ raylib/src/libraylib.a scrap.res $(LDFLAGS)\n\n$(EXE_NAME): $(OBJFILES)\n\t$(MAKE) -C raylib/src CC=$(CC) PLATFORM_OS=$(TARGET)\n\t$(CC) -o $@ $^ raylib/src/libraylib.a $(LDFLAGS)\n\nstd: mkbuild $(STD_OBJFILES)\n\tar rcs $(STD_NAME) $(STD_OBJFILES)\n\n$(BUILD_FOLDER)scrap.o: src/scrap.c $(SCRAP_HEADERS)\n\t$(CC) $(CFLAGS) -c -o $@ $<\n$(BUILD_FOLDER)window.o: src/window.c $(SCRAP_HEADERS) external/tinyfiledialogs.h\n\t$(CC) $(CFLAGS) -c -o $@ $<\n$(BUILD_FOLDER)scrap_gui.o: src/scrap_gui.c src/scrap_gui.h\n\t$(CC) $(CFLAGS) -c -o $@ $<\n$(BUILD_FOLDER)render.o: src/render.c $(SCRAP_HEADERS)\n\t$(CC) $(CFLAGS) -c -o $@ $<\n$(BUILD_FOLDER)save.o: src/save.c $(SCRAP_HEADERS) external/cfgpath.h\n\t$(CC) $(CFLAGS) -c -o $@ $<\n$(BUILD_FOLDER)term.o: src/term.c src/term.h $(SCRAP_HEADERS)\n\t$(CC) $(CFLAGS) -c -o $@ $<\n$(BUILD_FOLDER)blocks.o: src/blocks.c $(SCRAP_HEADERS)\n\t$(CC) $(CFLAGS) -c -o $@ $<\n$(BUILD_FOLDER)vec.o: src/vec.c\n\t$(CC) $(CFLAGS) -c -o $@ $<\n$(BUILD_FOLDER)util.o: src/util.c $(SCRAP_HEADERS)\n\t$(CC) $(CFLAGS) -c -o $@ $<\n$(BUILD_FOLDER)ui.o: src/ui.c $(SCRAP_HEADERS)\n\t$(CC) $(CFLAGS) -c -o $@ $<\n$(BUILD_FOLDER)platform.o: src/platform.c\n\t$(CC) $(CFLAGS) -c -o $@ $<\n$(BUILD_FOLDER)ast.o: src/ast.c src/ast.h\n\t$(CC) $(CFLAGS) -c -o $@ $<\n$(BUILD_FOLDER)interpreter.o: src/interpreter.c $(SCRAP_HEADERS)\n\t$(CC) $(CFLAGS) -c -o $@ $<\n$(BUILD_FOLDER)compiler.o: src/compiler.c src/compiler.h src/gc.h src/ast.h $(SCRAP_HEADERS)\n\t$(CC) $(CFLAGS) -c -o $@ $<\n$(BUILD_FOLDER)gc.o: src/gc.c src/gc.h src/vec.h src/std.h\n\t$(CC) $(CFLAGS) -c -o $@ $<\n$(BUILD_FOLDER)std.o: src/std.c src/std.h src/gc.h src/term.h\n\t$(CC) $(CFLAGS) -c -o $@ $<\n$(BUILD_FOLDER)thread.o: src/thread.c src/thread.h\n\t$(CC) $(CFLAGS) -c -o $@ $<\n$(BUILD_FOLDER)vm.o: src/vm.c $(SCRAP_HEADERS)\n\t$(CC) $(CFLAGS) -c -o $@ $<\n\n$(BUILD_FOLDER)gc-stand.o: src/gc.c src/gc.h src/vec.h src/std.h\n\t$(CC) $(STD_CFLAGS) -DSTANDALONE_STD -c -o $@ $<\n$(BUILD_FOLDER)std-stand.o: src/std.c src/std.h src/gc.h\n\t$(CC) $(STD_CFLAGS) -DSTANDALONE_STD -c -o $@ $<\n$(BUILD_FOLDER)scrap-runtime.o: src/scrap-runtime.c src/gc.h\n\t$(CC) $(STD_CFLAGS) -DSTANDALONE_STD -c -o $@ $<\n$(BUILD_FOLDER)vec-stand.o: src/vec.c\n\t$(CC) $(STD_CFLAGS) -DSTANDALONE_STD -c -o $@ $<\n\n$(BUILD_FOLDER)filedialogs.o: external/tinyfiledialogs.c\n\t$(CC) $(CFLAGS) -c -o $@ $<\n$(BUILD_FOLDER)cfgpath.o: external/cfgpath.c\n\t$(CC) $(CFLAGS) -c -o $@ $<"
  },
  {
    "path": "README.md",
    "content": "![Scrap splash](/extras/scrap_splash_v2.png)\n\n# Scrap\n\n![CI build](https://img.shields.io/github/actions/workflow/status/Grisshink/scrap/build.yml)\n![Version](https://img.shields.io/github/v/release/Grisshink/scrap)\n![Downloads](https://img.shields.io/github/downloads/Grisshink/scrap/total)\n![License](https://img.shields.io/github/license/Grisshink/scrap)\n\nScrap is a new block based programming language with the aim towards advanced users. \nIt is written in pure C and mostly inspired by other block based languages such as [Scratch](https://scratch.mit.edu/) and\nits forks such as [Turbowarp](https://turbowarp.org).\n\n> [!WARNING]\n> Scrap is currently in **Beta** stage. Some features may be missing or break, so use with caution!\n\n## Notable advantages from scratch\n\n- *(LLVM only)* Faster runtime. The speed is achieved using compilation through LLVM\n- The addition of separate else if, else blocks (C-end blocks as i call them), which eliminates a lot of nested checks with if-else blocks (i.e. more flexible variant of if-else block in Snap!)\n- Variables can have a lifetime, which avoids variable name conflicts and allows to make temporary variables\n- Custom blocks can return values and can be used as an argument for other block\n- Various string manipulation blocks and bitwise operator blocks\n- Data type conversion functions\n- More strict checks for [[] = []] and [[] != []] blocks. Now they are case sensitive and will check data type for equality\n- Lists are now a data type instead of a different type of variable, this allows nesting lists inside a list\n- The code runs in a separate thread. This solves some performance issues compared to Scratch\n- Modularized interface. Most of the interface can be rearranged or moved to another tab\n- *(LLVM only)* Standalone builds. Scrap projects can be built and run as native executables without significant runtime overhead\n\n## Controls\n\n- Click on blocks to pick up them, click again to drop them\n- Hold `Ctrl` to take single block, `Alt` to duplicate blocks and `Shift` to pickup/drop multiple block chains\n- Hold left mouse button to move around code space\n- Holding middle mouse button will do the same, except it works everywhere\n- Press `Tab` to jump to chain in code base (Useful if you got lost in code base)\n- Press `F5` to run the project. Press `F6` to stop it.\n- Press arrow keys while the block is highlighted to move the block cursor around\n- Press `Enter` to enter the highlighted text box and `Esc` to leave that text box\n- Press `S` to open block search menu\n\n## Binary Installation\n\n### Github releases\n\nSee [Releases](https://github.com/Grisshink/scrap/releases) page for all available download options for \nWindows, Linux as well as their respective LLVM builds\n\n### AUR\n\nScrap is now available for download from Arch User Repository (AUR) as [scrap-git](https://aur.archlinux.org/packages/scrap-git) package.\nThis package will download and build latest Scrap commit from git (LLVM PKGBUILDs are coming soon)\n\nTo install Scrap from AUR you can use your preferred AUR helper, for example with `yay`:\n\n```bash\nyay -S scrap-git\n```\n\n## Building\n\n### Dependencies\n\nScrap requires these dependencies to run:\n- [gettext](https://www.gnu.org/software/gettext/)\n- [LLVM](https://llvm.org/) *(Only required if building with `USE_COMPILER=TRUE` flag)*\n\nCurrently Scrap can be built for *Windows*, *Linux*, *MacOS* and *FreeBSD*. \n\n#### Download commands for Windows (MSYS2 UCRT64)\n\n```bash\npacman -S mingw-w64-ucrt-x86_64-gcc make gettext\nln -sf \"${MSYSTEM_PREFIX}/bin/windres.exe\" \"${MSYSTEM_PREFIX}/bin/x86_64-w64-mingw32-windres\"\n```\n\n#### Download commands for Windows (LLVM) *(Experimental)*\n\nIf you are going to compile with `USE_COMPILER=TRUE` flag, then you need to install additional dependencies:\n\n```bash\npacman -S mingw-w64-ucrt-x86_64-llvm\n```\n\n#### Download commands for Debian\n\n```bash\nsudo apt install libxcursor-dev libxrandr-dev libxinerama-dev libxi-dev gettext\n```\n\n#### Download commands for Arch linux\n\nDownload command for arch-based distributions:\n\n```bash\nsudo pacman -S libx11 libxrandr libxi libxcursor libxinerama gettext\n```\n\n#### Download commands for OpenSUSE\n\nDownload command for openSUSE:\n\n```bash\nsudo zypper install libX11-devel libXrandr-devel libXi-devel libXcursor-devel libXinerama-devel gettext\n```\n\n### Build\n\nBefore building the repo needs to be cloned along with its submodules. To do this, run:\n\n```bash\ngit clone --recursive https://github.com/Grisshink/scrap.git\ncd scrap\n```\n\n#### Windows build\n\nNOTE: This guide will assume that you have [MSYS2](https://www.msys2.org/) installed and running on your system. \n\nAfter that, run the following commands:\n\n```bash\nmake -B TARGET=WINDOWS\n./scrap.exe\n```\n\nNOTE: When running `make clean` MSYS2 will occasionally drop you into command prompt. \nTo fix this, just type `exit` in the cmd and the cleanup process will proceed\n\n#### Linux build\n\nTo build and run Scrap on linux you need to install `gcc` (10+) and `make`. After install, just run following commands:\n\n```bash\nmake -j$(nproc)\n./scrap\n```\n\n#### FreeBSD build\n\nTo build and run Scrap on FreeBSD you need to install `gcc` (10+) and `gmake`. After install, just run following commands:\n\n```bash\ngmake MAKE=gmake -j$(nproc)\n./scrap\n```\n\n#### NixOS build\n\nTo build and run Scrap on NixOS, just run the following commands:\n\n```bash\nnix-shell\nmake -j$(nproc)\n./scrap\n```\n\n#### MacOS build\n\n> [!WARNING]\n> MacOS build is not being tested right now, so it may not work properly or not at all, you have been warned!\n\nTo build and run Scrap on macOS, you need to install `gcc` (10+) and `make`.\nFirst, install Homebrew:\n\n```\n/bin/bash -c \"$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)\"\n```\n\nAfter that, you need to run the following commands:\n\n```bash\nbrew install gettext\nmake -j$(nproc) TARGET=OSX\n./scrap\n```\n\nThanks to [@arducat](https://github.com/arducat) for MacOS support.\n\n## Screenshots\n\n![Screenshot1](/extras/scrap_screenshot1.png)\n![Screenshot2](/extras/scrap_screenshot2.png)\n![Screenshot3](/extras/scrap_screenshot3.png)\n\n## Wait, there is more?\n\nIn `examples/` folder you can find some example code writen in Scrap that uses most features from Scrap\n\nIn `extras/` folder you can find some various artwork made for Scrap. \nThe splash art was made by [@FlaffyTheBest](https://scratch.mit.edu/users/FlaffyTheBest/), \nthe logo was made by [@Grisshink](https://github.com/Grisshink) with some inspiration for logo from [@unixource](https://github.com/unixource), \nthe wallpaper was made by [@Grisshink](https://github.com/Grisshink)\n\n## License\n\nAll scrap code is licensed under the terms of [zlib license](/LICENSE).\n"
  },
  {
    "path": "default.nix",
    "content": "with import <nixpkgs> {};\npkgs.mkShell {\n  stdenv = pkgs.clangStdenv;\n  packages = with pkgs; [\n    glfw\n    cmake\n    clang\n    wayland\n    libx11\n  ];\n  nativeBuildInputs = [\n    pkgs.libGL\n  ];\n\n  LD_LIBRARY_PATH = with pkgs; lib.makeLibraryPath [\n    libGL\n    libxrandr\n    libxinerama\n    libxcursor\n    libxi\n  ];\n  LIBCLANG_PATH = \"${pkgs.libclang.lib}/lib\";\n}\n\n# thanks to https://github.com/deltaphc/raylib-rs/issues/187 !\n# TODO: implement buildFHSEnv\n"
  },
  {
    "path": "external/cfgpath.c",
    "content": "#include \"cfgpath.h\"\n\n#if defined(_MSC_VER) || defined(__MINGW32__)\n    #include <direct.h>\n    #define mkdir _mkdir\n#endif\n\n#ifdef __unix__\n#include <sys/param.h>\n#endif\n\n#if defined(CFGPATH_LINUX)\n    #include <string.h>\n    #include <stdlib.h>\n    #include <sys/stat.h>\n#elif defined(CFGPATH_WINDOWS)\n    #include <shlobj.h>\n#elif defined(CFGPATH_MAC)\n    #include <CoreServices/CoreServices.h>\n    #include <sys/stat.h>\n#endif\n\nvoid get_user_config_file(char *out, unsigned int maxlen, const char *appname)\n{\n#ifdef CFGPATH_LINUX\n\tconst char *out_orig = out;\n\tchar *home = getenv(\"XDG_CONFIG_HOME\");\n\tunsigned int config_len = 0;\n\tif (!home) {\n\t\thome = getenv(\"HOME\");\n\t\tif (!home) {\n\t\t\t// Can't find home directory\n\t\t\tout[0] = 0;\n\t\t\treturn;\n\t\t}\n\t\tconfig_len = strlen(\".config/\");\n\t}\n\n\tunsigned int home_len = strlen(home);\n\tunsigned int appname_len = strlen(appname);\n\tconst int ext_len = strlen(\".conf\");\n\n\t/* first +1 is \"/\", second is terminating null */\n\tif (home_len + 1 + config_len + appname_len + ext_len + 1 > maxlen) {\n\t\tout[0] = 0;\n\t\treturn;\n\t}\n\n\tmemcpy(out, home, home_len);\n\tout += home_len;\n\t*out = '/';\n\tout++;\n\tif (config_len) {\n\t\tmemcpy(out, \".config/\", config_len);\n\t\tout += config_len;\n\t\t/* Make the .config folder if it doesn't already exist */\n\t\t*out = '\\0';\n\t\tmkdir(out_orig, 0755);\n\t}\n\tmemcpy(out, appname, appname_len);\n\tout += appname_len;\n\tmemcpy(out, \".conf\", ext_len);\n\tout += ext_len;\n\t*out = '\\0';\n#elif defined(CFGPATH_WINDOWS)\n\tif (maxlen < MAX_PATH) {\n\t\tout[0] = 0;\n\t\treturn;\n\t}\n\tif (!SUCCEEDED(SHGetFolderPath(NULL, CSIDL_APPDATA, NULL, 0, out))) {\n\t\tout[0] = 0;\n\t\treturn;\n\t}\n\t/* We don't try to create the AppData folder as it always exists already */\n\tunsigned int appname_len = strlen(appname);\n\tif (strlen(out) + 1 + appname_len + strlen(\".ini\") + 1 > maxlen) {\n\t\tout[0] = 0;\n\t\treturn;\n\t}\n\tstrcat(out, \"\\\\\");\n\tstrcat(out, appname);\n\tstrcat(out, \".ini\");\n#elif defined(CFGPATH_MAC)\n\tFSRef ref;\n\tFSFindFolder(kUserDomain, kApplicationSupportFolderType, kCreateFolder, &ref);\n\tchar home[MAX_PATH];\n\tFSRefMakePath(&ref, (UInt8 *)&home, MAX_PATH);\n\t/* first +1 is \"/\", second is terminating null */\n\tconst char *ext = \".conf\";\n\tif (strlen(home) + 1 + strlen(appname) + strlen(ext) + 1 > maxlen) {\n\t\tout[0] = 0;\n\t\treturn;\n\t}\n\n\tstrcpy(out, home);\n\tstrcat(out, PATH_SEPARATOR_STRING);\n\tstrcat(out, appname);\n\tstrcat(out, ext);\n#endif\n}\n\nvoid get_user_config_folder(char *out, unsigned int maxlen, const char *appname)\n{\n#ifdef CFGPATH_LINUX\n\tconst char *out_orig = out;\n\tchar *home = getenv(\"XDG_CONFIG_HOME\");\n\tunsigned int config_len = 0;\n\tif (!home) {\n\t\thome = getenv(\"HOME\");\n\t\tif (!home) {\n\t\t\t// Can't find home directory\n\t\t\tout[0] = 0;\n\t\t\treturn;\n\t\t}\n\t\tconfig_len = strlen(\".config/\");\n\t}\n\n\tunsigned int home_len = strlen(home);\n\tunsigned int appname_len = strlen(appname);\n\n\t/* first +1 is \"/\", second is trailing \"/\", third is terminating null */\n\tif (home_len + 1 + config_len + appname_len + 1 + 1 > maxlen) {\n\t\tout[0] = 0;\n\t\treturn;\n\t}\n\n\tmemcpy(out, home, home_len);\n\tout += home_len;\n\t*out = '/';\n\tout++;\n\tif (config_len) {\n\t\tmemcpy(out, \".config/\", config_len);\n\t\tout += config_len;\n\t\t/* Make the .config folder if it doesn't already exist */\n\t\t*out = '\\0';\n\t\tmkdir(out_orig, 0755);\n\t}\n\tmemcpy(out, appname, appname_len);\n\tout += appname_len;\n\t/* Make the .config/appname folder if it doesn't already exist */\n\t*out = '\\0';\n\tmkdir(out_orig, 0755);\n\t*out = '/';\n\tout++;\n\t*out = '\\0';\n#elif defined(CFGPATH_WINDOWS)\n\tif (maxlen < MAX_PATH) {\n\t\tout[0] = 0;\n\t\treturn;\n\t}\n\tif (!SUCCEEDED(SHGetFolderPath(NULL, CSIDL_APPDATA, NULL, 0, out))) {\n\t\tout[0] = 0;\n\t\treturn;\n\t}\n\t/* We don't try to create the AppData folder as it always exists already */\n\tunsigned int appname_len = strlen(appname);\n\tif (strlen(out) + 1 + appname_len + 1 + 1 > maxlen) {\n\t\tout[0] = 0;\n\t\treturn;\n\t}\n\tstrcat(out, \"\\\\\");\n\tstrcat(out, appname);\n\t/* Make the AppData\\appname folder if it doesn't already exist */\n\tmkdir(out);\n\tstrcat(out, \"\\\\\");\n#elif defined(CFGPATH_MAC)\n\tFSRef ref;\n\tFSFindFolder(kUserDomain, kApplicationSupportFolderType, kCreateFolder, &ref);\n\tchar home[MAX_PATH];\n\tFSRefMakePath(&ref, (UInt8 *)&home, MAX_PATH);\n\t/* first +1 is \"/\", second is trailing \"/\", third is terminating null */\n\tif (strlen(home) + 1 + strlen(appname) + 1 + 1 > maxlen) {\n\t\tout[0] = 0;\n\t\treturn;\n\t}\n\n\tstrcpy(out, home);\n\tstrcat(out, PATH_SEPARATOR_STRING);\n\tstrcat(out, appname);\n\t/* Make the .config/appname folder if it doesn't already exist */\n\tmkdir(out, 0755);\n\tstrcat(out, PATH_SEPARATOR_STRING);\n#endif\n}\n\nvoid get_user_data_folder(char *out, unsigned int maxlen, const char *appname)\n{\n#ifdef CFGPATH_LINUX\n\tconst char *out_orig = out;\n\tchar *home = getenv(\"XDG_DATA_HOME\");\n\tunsigned int config_len = 0;\n\tif (!home) {\n\t\thome = getenv(\"HOME\");\n\t\tif (!home) {\n\t\t\t// Can't find home directory\n\t\t\tout[0] = 0;\n\t\t\treturn;\n\t\t}\n\t\tconfig_len = strlen(\".local/share/\");\n\t}\n\n\tunsigned int home_len = strlen(home);\n\tunsigned int appname_len = strlen(appname);\n\n\t/* first +1 is \"/\", second is trailing \"/\", third is terminating null */\n\tif (home_len + 1 + config_len + appname_len + 1 + 1 > maxlen) {\n\t\tout[0] = 0;\n\t\treturn;\n\t}\n\n\tmemcpy(out, home, home_len);\n\tout += home_len;\n\t*out = '/';\n\tout++;\n\tif (config_len) {\n\t\tmemcpy(out, \".local/share/\", config_len);\n\t\tout += config_len;\n\t\t/* Make the .local/share folder if it doesn't already exist */\n\t\t*out = '\\0';\n\t\tmkdir(out_orig, 0755);\n\t}\n\tmemcpy(out, appname, appname_len);\n\tout += appname_len;\n\t/* Make the .local/share/appname folder if it doesn't already exist */\n\t*out = '\\0';\n\tmkdir(out_orig, 0755);\n\t*out = '/';\n\tout++;\n\t*out = '\\0';\n#elif defined(CFGPATH_WINDOWS) || defined(CFGPATH_MAC)\n\t/* No distinction under Windows or OS X */\n\tget_user_config_folder(out, maxlen, appname);\n#endif\n}\n\nvoid get_user_cache_folder(char *out, unsigned int maxlen, const char *appname)\n{\n#ifdef CFGPATH_LINUX\n\tconst char *out_orig = out;\n\tchar *home = getenv(\"XDG_CACHE_HOME\");\n\tunsigned int config_len = 0;\n\tif (!home) {\n\t\thome = getenv(\"HOME\");\n\t\tif (!home) {\n\t\t\t// Can't find home directory\n\t\t\tout[0] = 0;\n\t\t\treturn;\n\t\t}\n\t\tconfig_len = strlen(\".cache/\");\n\t}\n\n\tunsigned int home_len = strlen(home);\n\tunsigned int appname_len = strlen(appname);\n\n\t/* first +1 is \"/\", second is trailing \"/\", third is terminating null */\n\tif (home_len + 1 + config_len + appname_len + 1 + 1 > maxlen) {\n\t\tout[0] = 0;\n\t\treturn;\n\t}\n\n\tmemcpy(out, home, home_len);\n\tout += home_len;\n\t*out = '/';\n\tout++;\n\tif (config_len) {\n\t\tmemcpy(out, \".cache/\", config_len);\n\t\tout += config_len;\n\t\t/* Make the .cache folder if it doesn't already exist */\n\t\t*out = '\\0';\n\t\tmkdir(out_orig, 0755);\n\t}\n\tmemcpy(out, appname, appname_len);\n\tout += appname_len;\n\t/* Make the .cache/appname folder if it doesn't already exist */\n\t*out = '\\0';\n\tmkdir(out_orig, 0755);\n\t*out = '/';\n\tout++;\n\t*out = '\\0';\n#elif defined(CFGPATH_WINDOWS)\n\tif (maxlen < MAX_PATH) {\n\t\tout[0] = 0;\n\t\treturn;\n\t}\n\tif (!SUCCEEDED(SHGetFolderPath(NULL, CSIDL_LOCAL_APPDATA, NULL, 0, out))) {\n\t\tout[0] = 0;\n\t\treturn;\n\t}\n\t/* We don't try to create the AppData folder as it always exists already */\n\tunsigned int appname_len = strlen(appname);\n\tif (strlen(out) + 1 + appname_len + 1 + 1 > maxlen) {\n\t\tout[0] = 0;\n\t\treturn;\n\t}\n\tstrcat(out, \"\\\\\");\n\tstrcat(out, appname);\n\t/* Make the AppData\\appname folder if it doesn't already exist */\n\tmkdir(out);\n\tstrcat(out, \"\\\\\");\n#elif defined(CFGPATH_MAC)\n\t/* No distinction under OS X */\n\tget_user_config_folder(out, maxlen, appname);\n#endif\n}\n"
  },
  {
    "path": "external/cfgpath.h",
    "content": "/**\n * @file  cfgpath.h\n * @brief Cross platform methods for obtaining paths to configuration files.\n *\n * Copyright (C) 2013 Adam Nielsen <malvineous@shikadi.net>\n *\n * This code is placed in the public domain.  You are free to use it for any\n * purpose.  If you add new platform support, please contribute a patch!\n *\n * Example use:\n *\n * char cfgdir[256];\n * get_user_config_file(cfgdir, sizeof(cfgdir), \"myapp\");\n * if (cfgdir[0] == 0) {\n *     printf(\"Unable to find home directory.\\n\");\n *     return 1;\n * }\n * printf(\"Saving configuration file to %s\\n\", cfgdir);\n *\n * A number of constants are also defined:\n *\n *  - MAX_PATH: Maximum length of a path, in characters.  Used to allocate a\n *      char array large enough to hold the returned path.\n *\n *  - PATH_SEPARATOR_CHAR: The separator between folders.  This will be either a\n *      forward slash or a backslash depending on the platform.  This is a\n *      character constant.\n *\n *  - PATH_SEPARATOR_STRING: The same as PATH_SEPARATOR_CHAR but as a C string,\n *      to make it easier to append to other string constants.\n */\n\n#ifndef CFGPATH_H_\n#define CFGPATH_H_\n\n#define MAX_PATH 512  /* arbitrary value */\n\n#if defined(__linux__) || defined(BSD) || defined(__FreeBSD__)\n    #define CFGPATH_LINUX\n    #define PATH_SEPARATOR_CHAR '/'\n    #define PATH_SEPARATOR_STRING \"/\"\n#elif defined(_WIN32)\n    #define CFGPATH_WINDOWS\n    #define PATH_SEPARATOR_CHAR '\\\\'\n    #define PATH_SEPARATOR_STRING \"\\\\\"\n#elif defined(__APPLE__)\n    #define CFGPATH_MAC\n    #define PATH_SEPARATOR_CHAR '/'\n    #define PATH_SEPARATOR_STRING \"/\"\n#else\n    #error cfgpath.h functions have not been implemented for your platform!  Please send patches.\n#endif\n\n/** Get an absolute path to a single configuration file, specific to this user.\n *\n * This function is useful for programs that need only a single configuration\n * file.  The file is unique to the user account currently logged in.\n *\n * Output is typically:\n *\n *   Windows: C:\\Users\\jcitizen\\AppData\\Roaming\\appname.ini\n *   Linux: /home/jcitizen/.config/appname.conf\n *   Mac: /Users/jcitizen/Library/Application Support/appname.conf\n *\n * @param out\n *   Buffer to write the path.  On return will contain the path, or an empty\n *   string on error.\n *\n * @param maxlen\n *   Length of out.  Must be >= MAX_PATH.\n *\n * @param appname\n *   Short name of the application.  Avoid using spaces or version numbers, and\n *   use lowercase if possible.\n *\n * @post The file may or may not exist.\n * @post The folder holding the file is created if needed.\n */\nvoid get_user_config_file(char *out, unsigned int maxlen, const char *appname);\n\n/** Get an absolute path to a configuration folder, specific to this user.\n *\n * This function is useful for programs that need to store multiple\n * configuration files.  The output is a folder (which may not exist and will\n * need to be created) suitable for storing a number of files.\n *\n * The returned path will always end in a platform-specific trailing slash, so\n * that a filename can simply be appended to the path.\n *\n * Output is typically:\n *\n *   Windows: C:\\Users\\jcitizen\\AppData\\Roaming\\appname\\\n *   Linux: /home/jcitizen/.config/appname/\n *   Mac: /Users/jcitizen/Library/Application Support/appname/\n *\n * @param out\n *   Buffer to write the path.  On return will contain the path, or an empty\n *   string on error.\n *\n * @param maxlen\n *   Length of out.  Must be >= MAX_PATH.\n *\n * @param appname\n *   Short name of the application.  Avoid using spaces or version numbers, and\n *   use lowercase if possible.\n *\n * @post The folder is created if needed.\n */\nvoid get_user_config_folder(char *out, unsigned int maxlen, const char *appname);\n\n/** Get an absolute path to a data storage folder, specific to this user.\n *\n * This function is useful for programs that need to store larger amounts of\n * data specific to each user.  The output is a folder (which may not exist and\n * will need to be created) suitable for storing a number of data files.\n *\n * This path should be used for persistent, important data files the user would\n * want to keep.  Do not use this path for temporary files, cache files, or\n * other files that can be recreated if they are deleted.  Use\n * get_user_cache_folder() for those instead.\n *\n * The returned path will always end in a platform-specific trailing slash, so\n * that a filename can simply be appended to the path.\n *\n * Output is typically:\n *\n *   Windows: C:\\Users\\jcitizen\\AppData\\Roaming\\appname-data\\\n *   Linux: /home/jcitizen/.local/share/appname/\n *   Mac: /Users/jcitizen/Library/Application Support/appname-data/\n *\n * @param out\n *   Buffer to write the path.  On return will contain the path, or an empty\n *   string on error.\n *\n * @param maxlen\n *   Length of out.  Must be >= MAX_PATH.\n *\n * @param appname\n *   Short name of the application.  Avoid using spaces or version numbers, and\n *   use lowercase if possible.\n *\n * @post The folder is created if needed.\n */\nvoid get_user_data_folder(char *out, unsigned int maxlen, const char *appname);\n\n/** Get an absolute path to a temporary storage folder, specific to this user.\n *\n * This function is useful for programs that temporarily need to store larger\n * amounts of data specific to each user.  The output is a folder (which may\n * not exist and will need to be created) suitable for storing a number of\n * temporary files.  The files may be lost or deleted when the program\n * terminates.\n *\n * This path should be used for temporary, unimportant data files that can\n * safely be deleted after the program terminates.  Do not use this path for\n * any important files the user would want to keep.  Use get_user_data_folder()\n * for those instead.\n *\n * The returned path will always end in a platform-specific trailing slash, so\n * that a filename can simply be appended to the path.\n *\n * Output is typically:\n *\n *   Windows: C:\\Users\\jcitizen\\AppData\\Local\\appname\\\n *   Linux: /home/jcitizen/.cache/appname/\n *   Mac: /Users/jcitizen/Library/Application Support/appname/\n *\n * @param out\n *   Buffer to write the path.  On return will contain the path, or an empty\n *   string on error.\n *\n * @param maxlen\n *   Length of out.  Must be >= MAX_PATH.\n *\n * @param appname\n *   Short name of the application.  Avoid using spaces or version numbers, and\n *   use lowercase if possible.\n *\n * @post The folder is created if needed.\n */\nvoid get_user_cache_folder(char *out, unsigned int maxlen, const char *appname);\n\n#endif /* CFGPATH_H_ */\n"
  },
  {
    "path": "external/nanosvg.h",
    "content": "/*\n * Copyright (c) 2013-14 Mikko Mononen memon@inside.org\n *\n * This software is provided 'as-is', without any express or implied\n * warranty.  In no event will the authors be held liable for any damages\n * arising from the use of this software.\n *\n * Permission is granted to anyone to use this software for any purpose,\n * including commercial applications, and to alter it and redistribute it\n * freely, subject to the following restrictions:\n *\n * 1. The origin of this software must not be misrepresented; you must not\n * claim that you wrote the original software. If you use this software\n * in a product, an acknowledgment in the product documentation would be\n * appreciated but is not required.\n * 2. Altered source versions must be plainly marked as such, and must not be\n * misrepresented as being the original software.\n * 3. This notice may not be removed or altered from any source distribution.\n *\n * The SVG parser is based on Anti-Grain Geometry 2.4 SVG example\n * Copyright (C) 2002-2004 Maxim Shemanarev (McSeem) (http://www.antigrain.com/)\n *\n * Arc calculation code based on canvg (https://code.google.com/p/canvg/)\n *\n * Bounding box calculation based on http://blog.hackers-cafe.net/2009/06/how-to-calculate-bezier-curves-bounding.html\n *\n */\n\n#ifndef NANOSVG_H\n#define NANOSVG_H\n\n#ifndef NANOSVG_CPLUSPLUS\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n#endif\n\n// NanoSVG is a simple stupid single-header-file SVG parse. The output of the parser is a list of cubic bezier shapes.\n//\n// The library suits well for anything from rendering scalable icons in your editor application to prototyping a game.\n//\n// NanoSVG supports a wide range of SVG features, but something may be missing, feel free to create a pull request!\n//\n// The shapes in the SVG images are transformed by the viewBox and converted to specified units.\n// That is, you should get the same looking data as your designed in your favorite app.\n//\n// NanoSVG can return the paths in few different units. For example if you want to render an image, you may choose\n// to get the paths in pixels, or if you are feeding the data into a CNC-cutter, you may want to use millimeters.\n//\n// The units passed to NanoSVG should be one of: 'px', 'pt', 'pc' 'mm', 'cm', or 'in'.\n// DPI (dots-per-inch) controls how the unit conversion is done.\n//\n// If you don't know or care about the units stuff, \"px\" and 96 should get you going.\n\n\n/* Example Usage:\n\t// Load SVG\n\tNSVGimage* image;\n\timage = nsvgParseFromFile(\"test.svg\", \"px\", 96);\n\tprintf(\"size: %f x %f\\n\", image->width, image->height);\n\t// Use...\n\tfor (NSVGshape *shape = image->shapes; shape != NULL; shape = shape->next) {\n\t\tfor (NSVGpath *path = shape->paths; path != NULL; path = path->next) {\n\t\t\tfor (int i = 0; i < path->npts-1; i += 3) {\n\t\t\t\tfloat* p = &path->pts[i*2];\n\t\t\t\tdrawCubicBez(p[0],p[1], p[2],p[3], p[4],p[5], p[6],p[7]);\n\t\t\t}\n\t\t}\n\t}\n\t// Delete\n\tnsvgDelete(image);\n*/\n\nenum NSVGpaintType {\n\tNSVG_PAINT_UNDEF = -1,\n\tNSVG_PAINT_NONE = 0,\n\tNSVG_PAINT_COLOR = 1,\n\tNSVG_PAINT_LINEAR_GRADIENT = 2,\n\tNSVG_PAINT_RADIAL_GRADIENT = 3\n};\n\nenum NSVGspreadType {\n\tNSVG_SPREAD_PAD = 0,\n\tNSVG_SPREAD_REFLECT = 1,\n\tNSVG_SPREAD_REPEAT = 2\n};\n\nenum NSVGlineJoin {\n\tNSVG_JOIN_MITER = 0,\n\tNSVG_JOIN_ROUND = 1,\n\tNSVG_JOIN_BEVEL = 2\n};\n\nenum NSVGlineCap {\n\tNSVG_CAP_BUTT = 0,\n\tNSVG_CAP_ROUND = 1,\n\tNSVG_CAP_SQUARE = 2\n};\n\nenum NSVGfillRule {\n\tNSVG_FILLRULE_NONZERO = 0,\n\tNSVG_FILLRULE_EVENODD = 1\n};\n\nenum NSVGflags {\n\tNSVG_FLAGS_VISIBLE = 0x01\n};\n\nenum NSVGpaintOrder {\n\tNSVG_PAINT_FILL = 0x00,\n\tNSVG_PAINT_MARKERS = 0x01,\n\tNSVG_PAINT_STROKE = 0x02,\n};\n\ntypedef struct NSVGgradientStop {\n\tunsigned int color;\n\tfloat offset;\n} NSVGgradientStop;\n\ntypedef struct NSVGgradient {\n\tfloat xform[6];\n\tchar spread;\n\tfloat fx, fy;\n\tint nstops;\n\tNSVGgradientStop stops[1];\n} NSVGgradient;\n\ntypedef struct NSVGpaint {\n\tsigned char type;\n\tunion {\n\t\tunsigned int color;\n\t\tNSVGgradient* gradient;\n\t};\n} NSVGpaint;\n\ntypedef struct NSVGpath\n{\n\tfloat* pts;\t\t\t\t\t// Cubic bezier points: x0,y0, [cpx1,cpx1,cpx2,cpy2,x1,y1], ...\n\tint npts;\t\t\t\t\t// Total number of bezier points.\n\tchar closed;\t\t\t\t// Flag indicating if shapes should be treated as closed.\n\tfloat bounds[4];\t\t\t// Tight bounding box of the shape [minx,miny,maxx,maxy].\n\tstruct NSVGpath* next;\t\t// Pointer to next path, or NULL if last element.\n} NSVGpath;\n\ntypedef struct NSVGshape\n{\n\tchar id[64];\t\t\t\t// Optional 'id' attr of the shape or its group\n\tNSVGpaint fill;\t\t\t\t// Fill paint\n\tNSVGpaint stroke;\t\t\t// Stroke paint\n\tfloat opacity;\t\t\t\t// Opacity of the shape.\n\tfloat strokeWidth;\t\t\t// Stroke width (scaled).\n\tfloat strokeDashOffset;\t\t// Stroke dash offset (scaled).\n\tfloat strokeDashArray[8];\t// Stroke dash array (scaled).\n\tchar strokeDashCount;\t\t// Number of dash values in dash array.\n\tchar strokeLineJoin;\t\t// Stroke join type.\n\tchar strokeLineCap;\t\t\t// Stroke cap type.\n\tfloat miterLimit;\t\t\t// Miter limit\n\tchar fillRule;\t\t\t\t// Fill rule, see NSVGfillRule.\n    unsigned char paintOrder;\t// Encoded paint order (3×2-bit fields) see NSVGpaintOrder\n\tunsigned char flags;\t\t// Logical or of NSVG_FLAGS_* flags\n\tfloat bounds[4];\t\t\t// Tight bounding box of the shape [minx,miny,maxx,maxy].\n\tchar fillGradient[64];\t\t// Optional 'id' of fill gradient\n\tchar strokeGradient[64];\t// Optional 'id' of stroke gradient\n\tfloat xform[6];\t\t\t\t// Root transformation for fill/stroke gradient\n\tNSVGpath* paths;\t\t\t// Linked list of paths in the image.\n\tstruct NSVGshape* next;\t\t// Pointer to next shape, or NULL if last element.\n} NSVGshape;\n\ntypedef struct NSVGimage\n{\n\tfloat width;\t\t\t\t// Width of the image.\n\tfloat height;\t\t\t\t// Height of the image.\n\tNSVGshape* shapes;\t\t\t// Linked list of shapes in the image.\n} NSVGimage;\n\n// Parses SVG file from a file, returns SVG image as paths.\nNSVGimage* nsvgParseFromFile(const char* filename, const char* units, float dpi);\n\n// Parses SVG file from a null terminated string, returns SVG image as paths.\n// Important note: changes the string.\nNSVGimage* nsvgParse(char* input, const char* units, float dpi);\n\n// Duplicates a path.\nNSVGpath* nsvgDuplicatePath(NSVGpath* p);\n\n// Deletes an image.\nvoid nsvgDelete(NSVGimage* image);\n\n#ifndef NANOSVG_CPLUSPLUS\n#ifdef __cplusplus\n}\n#endif\n#endif\n\n#ifdef NANOSVG_IMPLEMENTATION\n\n#include <string.h>\n#include <stdlib.h>\n#include <stdio.h>\n#include <math.h>\n\n#define NSVG_PI (3.14159265358979323846264338327f)\n#define NSVG_KAPPA90 (0.5522847493f)\t// Length proportional to radius of a cubic bezier handle for 90deg arcs.\n\n#define NSVG_ALIGN_MIN 0\n#define NSVG_ALIGN_MID 1\n#define NSVG_ALIGN_MAX 2\n#define NSVG_ALIGN_NONE 0\n#define NSVG_ALIGN_MEET 1\n#define NSVG_ALIGN_SLICE 2\n\n#define NSVG_NOTUSED(v) do { (void)(1 ? (void)0 : ( (void)(v) ) ); } while(0)\n#define NSVG_RGB(r, g, b) (((unsigned int)r) | ((unsigned int)g << 8) | ((unsigned int)b << 16))\n\n#ifdef _MSC_VER\n\t#pragma warning (disable: 4996) // Switch off security warnings\n\t#pragma warning (disable: 4100) // Switch off unreferenced formal parameter warnings\n\t#ifdef __cplusplus\n\t#define NSVG_INLINE inline\n\t#else\n\t#define NSVG_INLINE\n\t#endif\n#else\n\t#define NSVG_INLINE inline\n#endif\n\n\nstatic int nsvg__isspace(char c)\n{\n\treturn strchr(\" \\t\\n\\v\\f\\r\", c) != 0;\n}\n\nstatic int nsvg__isdigit(char c)\n{\n\treturn c >= '0' && c <= '9';\n}\n\nstatic NSVG_INLINE float nsvg__minf(float a, float b) { return a < b ? a : b; }\nstatic NSVG_INLINE float nsvg__maxf(float a, float b) { return a > b ? a : b; }\n\n\n// Simple XML parser\n\n#define NSVG_XML_TAG 1\n#define NSVG_XML_CONTENT 2\n#define NSVG_XML_MAX_ATTRIBS 256\n\nstatic void nsvg__parseContent(char* s,\n\t\t\t\t\t\t\t   void (*contentCb)(void* ud, const char* s),\n\t\t\t\t\t\t\t   void* ud)\n{\n\t// Trim start white spaces\n\twhile (*s && nsvg__isspace(*s)) s++;\n\tif (!*s) return;\n\n\tif (contentCb)\n\t\t(*contentCb)(ud, s);\n}\n\nstatic void nsvg__parseElement(char* s,\n\t\t\t\t\t\t\t   void (*startelCb)(void* ud, const char* el, const char** attr),\n\t\t\t\t\t\t\t   void (*endelCb)(void* ud, const char* el),\n\t\t\t\t\t\t\t   void* ud)\n{\n\tconst char* attr[NSVG_XML_MAX_ATTRIBS];\n\tint nattr = 0;\n\tchar* name;\n\tint start = 0;\n\tint end = 0;\n\tchar quote;\n\n\t// Skip white space after the '<'\n\twhile (*s && nsvg__isspace(*s)) s++;\n\n\t// Check if the tag is end tag\n\tif (*s == '/') {\n\t\ts++;\n\t\tend = 1;\n\t} else {\n\t\tstart = 1;\n\t}\n\n\t// Skip comments, data and preprocessor stuff.\n\tif (!*s || *s == '?' || *s == '!')\n\t\treturn;\n\n\t// Get tag name\n\tname = s;\n\twhile (*s && !nsvg__isspace(*s)) s++;\n\tif (*s) { *s++ = '\\0'; }\n\n\t// Get attribs\n\twhile (!end && *s && nattr < NSVG_XML_MAX_ATTRIBS-3) {\n\t\tchar* name = NULL;\n\t\tchar* value = NULL;\n\n\t\t// Skip white space before the attrib name\n\t\twhile (*s && nsvg__isspace(*s)) s++;\n\t\tif (!*s) break;\n\t\tif (*s == '/') {\n\t\t\tend = 1;\n\t\t\tbreak;\n\t\t}\n\t\tname = s;\n\t\t// Find end of the attrib name.\n\t\twhile (*s && !nsvg__isspace(*s) && *s != '=') s++;\n\t\tif (*s) { *s++ = '\\0'; }\n\t\t// Skip until the beginning of the value.\n\t\twhile (*s && *s != '\\\"' && *s != '\\'') s++;\n\t\tif (!*s) break;\n\t\tquote = *s;\n\t\ts++;\n\t\t// Store value and find the end of it.\n\t\tvalue = s;\n\t\twhile (*s && *s != quote) s++;\n\t\tif (*s) { *s++ = '\\0'; }\n\n\t\t// Store only well formed attributes\n\t\tif (name && value) {\n\t\t\tattr[nattr++] = name;\n\t\t\tattr[nattr++] = value;\n\t\t}\n\t}\n\n\t// List terminator\n\tattr[nattr++] = 0;\n\tattr[nattr++] = 0;\n\n\t// Call callbacks.\n\tif (start && startelCb)\n\t\t(*startelCb)(ud, name, attr);\n\tif (end && endelCb)\n\t\t(*endelCb)(ud, name);\n}\n\nint nsvg__parseXML(char* input,\n\t\t\t\t   void (*startelCb)(void* ud, const char* el, const char** attr),\n\t\t\t\t   void (*endelCb)(void* ud, const char* el),\n\t\t\t\t   void (*contentCb)(void* ud, const char* s),\n\t\t\t\t   void* ud)\n{\n\tchar* s = input;\n\tchar* mark = s;\n\tint state = NSVG_XML_CONTENT;\n\twhile (*s) {\n\t\tif (*s == '<' && state == NSVG_XML_CONTENT) {\n\t\t\t// Start of a tag\n\t\t\t*s++ = '\\0';\n\t\t\tnsvg__parseContent(mark, contentCb, ud);\n\t\t\tmark = s;\n\t\t\tstate = NSVG_XML_TAG;\n\t\t} else if (*s == '>' && state == NSVG_XML_TAG) {\n\t\t\t// Start of a content or new tag.\n\t\t\t*s++ = '\\0';\n\t\t\tnsvg__parseElement(mark, startelCb, endelCb, ud);\n\t\t\tmark = s;\n\t\t\tstate = NSVG_XML_CONTENT;\n\t\t} else {\n\t\t\ts++;\n\t\t}\n\t}\n\n\treturn 1;\n}\n\n\n/* Simple SVG parser. */\n\n#define NSVG_MAX_ATTR 128\n\nenum NSVGgradientUnits {\n\tNSVG_USER_SPACE = 0,\n\tNSVG_OBJECT_SPACE = 1\n};\n\n#define NSVG_MAX_DASHES 8\n\nenum NSVGunits {\n\tNSVG_UNITS_USER,\n\tNSVG_UNITS_PX,\n\tNSVG_UNITS_PT,\n\tNSVG_UNITS_PC,\n\tNSVG_UNITS_MM,\n\tNSVG_UNITS_CM,\n\tNSVG_UNITS_IN,\n\tNSVG_UNITS_PERCENT,\n\tNSVG_UNITS_EM,\n\tNSVG_UNITS_EX\n};\n\ntypedef struct NSVGcoordinate {\n\tfloat value;\n\tint units;\n} NSVGcoordinate;\n\ntypedef struct NSVGlinearData {\n\tNSVGcoordinate x1, y1, x2, y2;\n} NSVGlinearData;\n\ntypedef struct NSVGradialData {\n\tNSVGcoordinate cx, cy, r, fx, fy;\n} NSVGradialData;\n\ntypedef struct NSVGgradientData\n{\n\tchar id[64];\n\tchar ref[64];\n\tsigned char type;\n\tunion {\n\t\tNSVGlinearData linear;\n\t\tNSVGradialData radial;\n\t};\n\tchar spread;\n\tchar units;\n\tfloat xform[6];\n\tint nstops;\n\tNSVGgradientStop* stops;\n\tstruct NSVGgradientData* next;\n} NSVGgradientData;\n\ntypedef struct NSVGattrib\n{\n\tchar id[64];\n\tfloat xform[6];\n\tunsigned int fillColor;\n\tunsigned int strokeColor;\n\tfloat opacity;\n\tfloat fillOpacity;\n\tfloat strokeOpacity;\n\tchar fillGradient[64];\n\tchar strokeGradient[64];\n\tfloat strokeWidth;\n\tfloat strokeDashOffset;\n\tfloat strokeDashArray[NSVG_MAX_DASHES];\n\tint strokeDashCount;\n\tchar strokeLineJoin;\n\tchar strokeLineCap;\n\tfloat miterLimit;\n\tchar fillRule;\n\tfloat fontSize;\n\tunsigned int stopColor;\n\tfloat stopOpacity;\n\tfloat stopOffset;\n\tchar hasFill;\n\tchar hasStroke;\n\tchar visible;\n    unsigned char paintOrder;\n} NSVGattrib;\n\ntypedef struct NSVGparser\n{\n\tNSVGattrib attr[NSVG_MAX_ATTR];\n\tint attrHead;\n\tfloat* pts;\n\tint npts;\n\tint cpts;\n\tNSVGpath* plist;\n\tNSVGimage* image;\n\tNSVGgradientData* gradients;\n\tNSVGshape* shapesTail;\n\tfloat viewMinx, viewMiny, viewWidth, viewHeight;\n\tint alignX, alignY, alignType;\n\tfloat dpi;\n\tchar pathFlag;\n\tchar defsFlag;\n} NSVGparser;\n\nstatic void nsvg__xformIdentity(float* t)\n{\n\tt[0] = 1.0f; t[1] = 0.0f;\n\tt[2] = 0.0f; t[3] = 1.0f;\n\tt[4] = 0.0f; t[5] = 0.0f;\n}\n\nstatic void nsvg__xformSetTranslation(float* t, float tx, float ty)\n{\n\tt[0] = 1.0f; t[1] = 0.0f;\n\tt[2] = 0.0f; t[3] = 1.0f;\n\tt[4] = tx; t[5] = ty;\n}\n\nstatic void nsvg__xformSetScale(float* t, float sx, float sy)\n{\n\tt[0] = sx; t[1] = 0.0f;\n\tt[2] = 0.0f; t[3] = sy;\n\tt[4] = 0.0f; t[5] = 0.0f;\n}\n\nstatic void nsvg__xformSetSkewX(float* t, float a)\n{\n\tt[0] = 1.0f; t[1] = 0.0f;\n\tt[2] = tanf(a); t[3] = 1.0f;\n\tt[4] = 0.0f; t[5] = 0.0f;\n}\n\nstatic void nsvg__xformSetSkewY(float* t, float a)\n{\n\tt[0] = 1.0f; t[1] = tanf(a);\n\tt[2] = 0.0f; t[3] = 1.0f;\n\tt[4] = 0.0f; t[5] = 0.0f;\n}\n\nstatic void nsvg__xformSetRotation(float* t, float a)\n{\n\tfloat cs = cosf(a), sn = sinf(a);\n\tt[0] = cs; t[1] = sn;\n\tt[2] = -sn; t[3] = cs;\n\tt[4] = 0.0f; t[5] = 0.0f;\n}\n\nstatic void nsvg__xformMultiply(float* t, float* s)\n{\n\tfloat t0 = t[0] * s[0] + t[1] * s[2];\n\tfloat t2 = t[2] * s[0] + t[3] * s[2];\n\tfloat t4 = t[4] * s[0] + t[5] * s[2] + s[4];\n\tt[1] = t[0] * s[1] + t[1] * s[3];\n\tt[3] = t[2] * s[1] + t[3] * s[3];\n\tt[5] = t[4] * s[1] + t[5] * s[3] + s[5];\n\tt[0] = t0;\n\tt[2] = t2;\n\tt[4] = t4;\n}\n\nstatic void nsvg__xformInverse(float* inv, float* t)\n{\n\tdouble invdet, det = (double)t[0] * t[3] - (double)t[2] * t[1];\n\tif (det > -1e-6 && det < 1e-6) {\n\t\tnsvg__xformIdentity(t);\n\t\treturn;\n\t}\n\tinvdet = 1.0 / det;\n\tinv[0] = (float)(t[3] * invdet);\n\tinv[2] = (float)(-t[2] * invdet);\n\tinv[4] = (float)(((double)t[2] * t[5] - (double)t[3] * t[4]) * invdet);\n\tinv[1] = (float)(-t[1] * invdet);\n\tinv[3] = (float)(t[0] * invdet);\n\tinv[5] = (float)(((double)t[1] * t[4] - (double)t[0] * t[5]) * invdet);\n}\n\nstatic void nsvg__xformPremultiply(float* t, float* s)\n{\n\tfloat s2[6];\n\tmemcpy(s2, s, sizeof(float)*6);\n\tnsvg__xformMultiply(s2, t);\n\tmemcpy(t, s2, sizeof(float)*6);\n}\n\nstatic void nsvg__xformPoint(float* dx, float* dy, float x, float y, float* t)\n{\n\t*dx = x*t[0] + y*t[2] + t[4];\n\t*dy = x*t[1] + y*t[3] + t[5];\n}\n\nstatic void nsvg__xformVec(float* dx, float* dy, float x, float y, float* t)\n{\n\t*dx = x*t[0] + y*t[2];\n\t*dy = x*t[1] + y*t[3];\n}\n\n#define NSVG_EPSILON (1e-12)\n\nstatic int nsvg__ptInBounds(float* pt, float* bounds)\n{\n\treturn pt[0] >= bounds[0] && pt[0] <= bounds[2] && pt[1] >= bounds[1] && pt[1] <= bounds[3];\n}\n\n\nstatic double nsvg__evalBezier(double t, double p0, double p1, double p2, double p3)\n{\n\tdouble it = 1.0-t;\n\treturn it*it*it*p0 + 3.0*it*it*t*p1 + 3.0*it*t*t*p2 + t*t*t*p3;\n}\n\nstatic void nsvg__curveBounds(float* bounds, float* curve)\n{\n\tint i, j, count;\n\tdouble roots[2], a, b, c, b2ac, t, v;\n\tfloat* v0 = &curve[0];\n\tfloat* v1 = &curve[2];\n\tfloat* v2 = &curve[4];\n\tfloat* v3 = &curve[6];\n\n\t// Start the bounding box by end points\n\tbounds[0] = nsvg__minf(v0[0], v3[0]);\n\tbounds[1] = nsvg__minf(v0[1], v3[1]);\n\tbounds[2] = nsvg__maxf(v0[0], v3[0]);\n\tbounds[3] = nsvg__maxf(v0[1], v3[1]);\n\n\t// Bezier curve fits inside the convex hull of it's control points.\n\t// If control points are inside the bounds, we're done.\n\tif (nsvg__ptInBounds(v1, bounds) && nsvg__ptInBounds(v2, bounds))\n\t\treturn;\n\n\t// Add bezier curve inflection points in X and Y.\n\tfor (i = 0; i < 2; i++) {\n\t\ta = -3.0 * v0[i] + 9.0 * v1[i] - 9.0 * v2[i] + 3.0 * v3[i];\n\t\tb = 6.0 * v0[i] - 12.0 * v1[i] + 6.0 * v2[i];\n\t\tc = 3.0 * v1[i] - 3.0 * v0[i];\n\t\tcount = 0;\n\t\tif (fabs(a) < NSVG_EPSILON) {\n\t\t\tif (fabs(b) > NSVG_EPSILON) {\n\t\t\t\tt = -c / b;\n\t\t\t\tif (t > NSVG_EPSILON && t < 1.0-NSVG_EPSILON)\n\t\t\t\t\troots[count++] = t;\n\t\t\t}\n\t\t} else {\n\t\t\tb2ac = b*b - 4.0*c*a;\n\t\t\tif (b2ac > NSVG_EPSILON) {\n\t\t\t\tt = (-b + sqrt(b2ac)) / (2.0 * a);\n\t\t\t\tif (t > NSVG_EPSILON && t < 1.0-NSVG_EPSILON)\n\t\t\t\t\troots[count++] = t;\n\t\t\t\tt = (-b - sqrt(b2ac)) / (2.0 * a);\n\t\t\t\tif (t > NSVG_EPSILON && t < 1.0-NSVG_EPSILON)\n\t\t\t\t\troots[count++] = t;\n\t\t\t}\n\t\t}\n\t\tfor (j = 0; j < count; j++) {\n\t\t\tv = nsvg__evalBezier(roots[j], v0[i], v1[i], v2[i], v3[i]);\n\t\t\tbounds[0+i] = nsvg__minf(bounds[0+i], (float)v);\n\t\t\tbounds[2+i] = nsvg__maxf(bounds[2+i], (float)v);\n\t\t}\n\t}\n}\n\nstatic unsigned char nsvg__encodePaintOrder(enum NSVGpaintOrder a, enum NSVGpaintOrder b, enum NSVGpaintOrder c) {\n    return (a & 0x03) | ((b & 0x03) << 2) | ((c & 0x03) << 4);\n}\n\nstatic NSVGparser* nsvg__createParser(void)\n{\n\tNSVGparser* p;\n\tp = (NSVGparser*)malloc(sizeof(NSVGparser));\n\tif (p == NULL) goto error;\n\tmemset(p, 0, sizeof(NSVGparser));\n\n\tp->image = (NSVGimage*)malloc(sizeof(NSVGimage));\n\tif (p->image == NULL) goto error;\n\tmemset(p->image, 0, sizeof(NSVGimage));\n\n\t// Init style\n\tnsvg__xformIdentity(p->attr[0].xform);\n\tmemset(p->attr[0].id, 0, sizeof p->attr[0].id);\n\tp->attr[0].fillColor = NSVG_RGB(0,0,0);\n\tp->attr[0].strokeColor = NSVG_RGB(0,0,0);\n\tp->attr[0].opacity = 1;\n\tp->attr[0].fillOpacity = 1;\n\tp->attr[0].strokeOpacity = 1;\n\tp->attr[0].stopOpacity = 1;\n\tp->attr[0].strokeWidth = 1;\n\tp->attr[0].strokeLineJoin = NSVG_JOIN_MITER;\n\tp->attr[0].strokeLineCap = NSVG_CAP_BUTT;\n\tp->attr[0].miterLimit = 4;\n\tp->attr[0].fillRule = NSVG_FILLRULE_NONZERO;\n\tp->attr[0].hasFill = 1;\n\tp->attr[0].visible = 1;\n    p->attr[0].paintOrder = nsvg__encodePaintOrder(NSVG_PAINT_FILL, NSVG_PAINT_STROKE, NSVG_PAINT_MARKERS);\n\n\treturn p;\n\nerror:\n\tif (p) {\n\t\tif (p->image) free(p->image);\n\t\tfree(p);\n\t}\n\treturn NULL;\n}\n\nstatic void nsvg__deletePaths(NSVGpath* path)\n{\n\twhile (path) {\n\t\tNSVGpath *next = path->next;\n\t\tif (path->pts != NULL)\n\t\t\tfree(path->pts);\n\t\tfree(path);\n\t\tpath = next;\n\t}\n}\n\nstatic void nsvg__deletePaint(NSVGpaint* paint)\n{\n\tif (paint->type == NSVG_PAINT_LINEAR_GRADIENT || paint->type == NSVG_PAINT_RADIAL_GRADIENT)\n\t\tfree(paint->gradient);\n}\n\nstatic void nsvg__deleteGradientData(NSVGgradientData* grad)\n{\n\tNSVGgradientData* next;\n\twhile (grad != NULL) {\n\t\tnext = grad->next;\n\t\tfree(grad->stops);\n\t\tfree(grad);\n\t\tgrad = next;\n\t}\n}\n\nstatic void nsvg__deleteParser(NSVGparser* p)\n{\n\tif (p != NULL) {\n\t\tnsvg__deletePaths(p->plist);\n\t\tnsvg__deleteGradientData(p->gradients);\n\t\tnsvgDelete(p->image);\n\t\tfree(p->pts);\n\t\tfree(p);\n\t}\n}\n\nstatic void nsvg__resetPath(NSVGparser* p)\n{\n\tp->npts = 0;\n}\n\nstatic void nsvg__addPoint(NSVGparser* p, float x, float y)\n{\n\tif (p->npts+1 > p->cpts) {\n\t\tp->cpts = p->cpts ? p->cpts*2 : 8;\n\t\tp->pts = (float*)realloc(p->pts, p->cpts*2*sizeof(float));\n\t\tif (!p->pts) return;\n\t}\n\tp->pts[p->npts*2+0] = x;\n\tp->pts[p->npts*2+1] = y;\n\tp->npts++;\n}\n\nstatic void nsvg__moveTo(NSVGparser* p, float x, float y)\n{\n\tif (p->npts > 0) {\n\t\tp->pts[(p->npts-1)*2+0] = x;\n\t\tp->pts[(p->npts-1)*2+1] = y;\n\t} else {\n\t\tnsvg__addPoint(p, x, y);\n\t}\n}\n\nstatic void nsvg__lineTo(NSVGparser* p, float x, float y)\n{\n\tfloat px,py, dx,dy;\n\tif (p->npts > 0) {\n\t\tpx = p->pts[(p->npts-1)*2+0];\n\t\tpy = p->pts[(p->npts-1)*2+1];\n\t\tdx = x - px;\n\t\tdy = y - py;\n\t\tnsvg__addPoint(p, px + dx/3.0f, py + dy/3.0f);\n\t\tnsvg__addPoint(p, x - dx/3.0f, y - dy/3.0f);\n\t\tnsvg__addPoint(p, x, y);\n\t}\n}\n\nstatic void nsvg__cubicBezTo(NSVGparser* p, float cpx1, float cpy1, float cpx2, float cpy2, float x, float y)\n{\n\tif (p->npts > 0) {\n\t\tnsvg__addPoint(p, cpx1, cpy1);\n\t\tnsvg__addPoint(p, cpx2, cpy2);\n\t\tnsvg__addPoint(p, x, y);\n\t}\n}\n\nstatic NSVGattrib* nsvg__getAttr(NSVGparser* p)\n{\n\treturn &p->attr[p->attrHead];\n}\n\nstatic void nsvg__pushAttr(NSVGparser* p)\n{\n\tif (p->attrHead < NSVG_MAX_ATTR-1) {\n\t\tp->attrHead++;\n\t\tmemcpy(&p->attr[p->attrHead], &p->attr[p->attrHead-1], sizeof(NSVGattrib));\n\t}\n}\n\nstatic void nsvg__popAttr(NSVGparser* p)\n{\n\tif (p->attrHead > 0)\n\t\tp->attrHead--;\n}\n\nstatic float nsvg__actualOrigX(NSVGparser* p)\n{\n\treturn p->viewMinx;\n}\n\nstatic float nsvg__actualOrigY(NSVGparser* p)\n{\n\treturn p->viewMiny;\n}\n\nstatic float nsvg__actualWidth(NSVGparser* p)\n{\n\treturn p->viewWidth;\n}\n\nstatic float nsvg__actualHeight(NSVGparser* p)\n{\n\treturn p->viewHeight;\n}\n\nstatic float nsvg__actualLength(NSVGparser* p)\n{\n\tfloat w = nsvg__actualWidth(p), h = nsvg__actualHeight(p);\n\treturn sqrtf(w*w + h*h) / sqrtf(2.0f);\n}\n\nstatic float nsvg__convertToPixels(NSVGparser* p, NSVGcoordinate c, float orig, float length)\n{\n\tNSVGattrib* attr = nsvg__getAttr(p);\n\tswitch (c.units) {\n\t\tcase NSVG_UNITS_USER:\t\treturn c.value;\n\t\tcase NSVG_UNITS_PX:\t\t\treturn c.value;\n\t\tcase NSVG_UNITS_PT:\t\t\treturn c.value / 72.0f * p->dpi;\n\t\tcase NSVG_UNITS_PC:\t\t\treturn c.value / 6.0f * p->dpi;\n\t\tcase NSVG_UNITS_MM:\t\t\treturn c.value / 25.4f * p->dpi;\n\t\tcase NSVG_UNITS_CM:\t\t\treturn c.value / 2.54f * p->dpi;\n\t\tcase NSVG_UNITS_IN:\t\t\treturn c.value * p->dpi;\n\t\tcase NSVG_UNITS_EM:\t\t\treturn c.value * attr->fontSize;\n\t\tcase NSVG_UNITS_EX:\t\t\treturn c.value * attr->fontSize * 0.52f; // x-height of Helvetica.\n\t\tcase NSVG_UNITS_PERCENT:\treturn orig + c.value / 100.0f * length;\n\t\tdefault:\t\t\t\t\treturn c.value;\n\t}\n\treturn c.value;\n}\n\nstatic NSVGgradientData* nsvg__findGradientData(NSVGparser* p, const char* id)\n{\n\tNSVGgradientData* grad = p->gradients;\n\tif (id == NULL || *id == '\\0')\n\t\treturn NULL;\n\twhile (grad != NULL) {\n\t\tif (strcmp(grad->id, id) == 0)\n\t\t\treturn grad;\n\t\tgrad = grad->next;\n\t}\n\treturn NULL;\n}\n\nstatic NSVGgradient* nsvg__createGradient(NSVGparser* p, const char* id, const float* localBounds, float *xform, signed char* paintType)\n{\n\tNSVGgradientData* data = NULL;\n\tNSVGgradientData* ref = NULL;\n\tNSVGgradientStop* stops = NULL;\n\tNSVGgradient* grad;\n\tfloat ox, oy, sw, sh, sl;\n\tint nstops = 0;\n\tint refIter;\n\n\tdata = nsvg__findGradientData(p, id);\n\tif (data == NULL) return NULL;\n\n\t// TODO: use ref to fill in all unset values too.\n\tref = data;\n\trefIter = 0;\n\twhile (ref != NULL) {\n\t\tNSVGgradientData* nextRef = NULL;\n\t\tif (stops == NULL && ref->stops != NULL) {\n\t\t\tstops = ref->stops;\n\t\t\tnstops = ref->nstops;\n\t\t\tbreak;\n\t\t}\n\t\tnextRef = nsvg__findGradientData(p, ref->ref);\n\t\tif (nextRef == ref) break; // prevent infite loops on malformed data\n\t\tref = nextRef;\n\t\trefIter++;\n\t\tif (refIter > 32) break; // prevent infite loops on malformed data\n\t}\n\tif (stops == NULL) return NULL;\n\n\tgrad = (NSVGgradient*)malloc(sizeof(NSVGgradient) + sizeof(NSVGgradientStop)*(nstops-1));\n\tif (grad == NULL) return NULL;\n\n\t// The shape width and height.\n\tif (data->units == NSVG_OBJECT_SPACE) {\n\t\tox = localBounds[0];\n\t\toy = localBounds[1];\n\t\tsw = localBounds[2] - localBounds[0];\n\t\tsh = localBounds[3] - localBounds[1];\n\t} else {\n\t\tox = nsvg__actualOrigX(p);\n\t\toy = nsvg__actualOrigY(p);\n\t\tsw = nsvg__actualWidth(p);\n\t\tsh = nsvg__actualHeight(p);\n\t}\n\tsl = sqrtf(sw*sw + sh*sh) / sqrtf(2.0f);\n\n\tif (data->type == NSVG_PAINT_LINEAR_GRADIENT) {\n\t\tfloat x1, y1, x2, y2, dx, dy;\n\t\tx1 = nsvg__convertToPixels(p, data->linear.x1, ox, sw);\n\t\ty1 = nsvg__convertToPixels(p, data->linear.y1, oy, sh);\n\t\tx2 = nsvg__convertToPixels(p, data->linear.x2, ox, sw);\n\t\ty2 = nsvg__convertToPixels(p, data->linear.y2, oy, sh);\n\t\t// Calculate transform aligned to the line\n\t\tdx = x2 - x1;\n\t\tdy = y2 - y1;\n\t\tgrad->xform[0] = dy; grad->xform[1] = -dx;\n\t\tgrad->xform[2] = dx; grad->xform[3] = dy;\n\t\tgrad->xform[4] = x1; grad->xform[5] = y1;\n\t} else {\n\t\tfloat cx, cy, fx, fy, r;\n\t\tcx = nsvg__convertToPixels(p, data->radial.cx, ox, sw);\n\t\tcy = nsvg__convertToPixels(p, data->radial.cy, oy, sh);\n\t\tfx = nsvg__convertToPixels(p, data->radial.fx, ox, sw);\n\t\tfy = nsvg__convertToPixels(p, data->radial.fy, oy, sh);\n\t\tr = nsvg__convertToPixels(p, data->radial.r, 0, sl);\n\t\t// Calculate transform aligned to the circle\n\t\tgrad->xform[0] = r; grad->xform[1] = 0;\n\t\tgrad->xform[2] = 0; grad->xform[3] = r;\n\t\tgrad->xform[4] = cx; grad->xform[5] = cy;\n\t\tgrad->fx = fx / r;\n\t\tgrad->fy = fy / r;\n\t}\n\n\tnsvg__xformMultiply(grad->xform, data->xform);\n\tnsvg__xformMultiply(grad->xform, xform);\n\n\tgrad->spread = data->spread;\n\tmemcpy(grad->stops, stops, nstops*sizeof(NSVGgradientStop));\n\tgrad->nstops = nstops;\n\n\t*paintType = data->type;\n\n\treturn grad;\n}\n\nstatic float nsvg__getAverageScale(float* t)\n{\n\tfloat sx = sqrtf(t[0]*t[0] + t[2]*t[2]);\n\tfloat sy = sqrtf(t[1]*t[1] + t[3]*t[3]);\n\treturn (sx + sy) * 0.5f;\n}\n\nstatic void nsvg__getLocalBounds(float* bounds, NSVGshape *shape, float* xform)\n{\n\tNSVGpath* path;\n\tfloat curve[4*2], curveBounds[4];\n\tint i, first = 1;\n\tfor (path = shape->paths; path != NULL; path = path->next) {\n\t\tnsvg__xformPoint(&curve[0], &curve[1], path->pts[0], path->pts[1], xform);\n\t\tfor (i = 0; i < path->npts-1; i += 3) {\n\t\t\tnsvg__xformPoint(&curve[2], &curve[3], path->pts[(i+1)*2], path->pts[(i+1)*2+1], xform);\n\t\t\tnsvg__xformPoint(&curve[4], &curve[5], path->pts[(i+2)*2], path->pts[(i+2)*2+1], xform);\n\t\t\tnsvg__xformPoint(&curve[6], &curve[7], path->pts[(i+3)*2], path->pts[(i+3)*2+1], xform);\n\t\t\tnsvg__curveBounds(curveBounds, curve);\n\t\t\tif (first) {\n\t\t\t\tbounds[0] = curveBounds[0];\n\t\t\t\tbounds[1] = curveBounds[1];\n\t\t\t\tbounds[2] = curveBounds[2];\n\t\t\t\tbounds[3] = curveBounds[3];\n\t\t\t\tfirst = 0;\n\t\t\t} else {\n\t\t\t\tbounds[0] = nsvg__minf(bounds[0], curveBounds[0]);\n\t\t\t\tbounds[1] = nsvg__minf(bounds[1], curveBounds[1]);\n\t\t\t\tbounds[2] = nsvg__maxf(bounds[2], curveBounds[2]);\n\t\t\t\tbounds[3] = nsvg__maxf(bounds[3], curveBounds[3]);\n\t\t\t}\n\t\t\tcurve[0] = curve[6];\n\t\t\tcurve[1] = curve[7];\n\t\t}\n\t}\n}\n\nstatic void nsvg__addShape(NSVGparser* p)\n{\n\tNSVGattrib* attr = nsvg__getAttr(p);\n\tfloat scale = 1.0f;\n\tNSVGshape* shape;\n\tNSVGpath* path;\n\tint i;\n\n\tif (p->plist == NULL)\n\t\treturn;\n\n\tshape = (NSVGshape*)malloc(sizeof(NSVGshape));\n\tif (shape == NULL) goto error;\n\tmemset(shape, 0, sizeof(NSVGshape));\n\n\tmemcpy(shape->id, attr->id, sizeof shape->id);\n\tmemcpy(shape->fillGradient, attr->fillGradient, sizeof shape->fillGradient);\n\tmemcpy(shape->strokeGradient, attr->strokeGradient, sizeof shape->strokeGradient);\n\tmemcpy(shape->xform, attr->xform, sizeof shape->xform);\n\tscale = nsvg__getAverageScale(attr->xform);\n\tshape->strokeWidth = attr->strokeWidth * scale;\n\tshape->strokeDashOffset = attr->strokeDashOffset * scale;\n\tshape->strokeDashCount = (char)attr->strokeDashCount;\n\tfor (i = 0; i < attr->strokeDashCount; i++)\n\t\tshape->strokeDashArray[i] = attr->strokeDashArray[i] * scale;\n\tshape->strokeLineJoin = attr->strokeLineJoin;\n\tshape->strokeLineCap = attr->strokeLineCap;\n\tshape->miterLimit = attr->miterLimit;\n\tshape->fillRule = attr->fillRule;\n\tshape->opacity = attr->opacity;\n    shape->paintOrder = attr->paintOrder;\n\n\tshape->paths = p->plist;\n\tp->plist = NULL;\n\n\t// Calculate shape bounds\n\tshape->bounds[0] = shape->paths->bounds[0];\n\tshape->bounds[1] = shape->paths->bounds[1];\n\tshape->bounds[2] = shape->paths->bounds[2];\n\tshape->bounds[3] = shape->paths->bounds[3];\n\tfor (path = shape->paths->next; path != NULL; path = path->next) {\n\t\tshape->bounds[0] = nsvg__minf(shape->bounds[0], path->bounds[0]);\n\t\tshape->bounds[1] = nsvg__minf(shape->bounds[1], path->bounds[1]);\n\t\tshape->bounds[2] = nsvg__maxf(shape->bounds[2], path->bounds[2]);\n\t\tshape->bounds[3] = nsvg__maxf(shape->bounds[3], path->bounds[3]);\n\t}\n\n\t// Set fill\n\tif (attr->hasFill == 0) {\n\t\tshape->fill.type = NSVG_PAINT_NONE;\n\t} else if (attr->hasFill == 1) {\n\t\tshape->fill.type = NSVG_PAINT_COLOR;\n\t\tshape->fill.color = attr->fillColor;\n\t\tshape->fill.color |= (unsigned int)(attr->fillOpacity*255) << 24;\n\t} else if (attr->hasFill == 2) {\n\t\tshape->fill.type = NSVG_PAINT_UNDEF;\n\t}\n\n\t// Set stroke\n\tif (attr->hasStroke == 0) {\n\t\tshape->stroke.type = NSVG_PAINT_NONE;\n\t} else if (attr->hasStroke == 1) {\n\t\tshape->stroke.type = NSVG_PAINT_COLOR;\n\t\tshape->stroke.color = attr->strokeColor;\n\t\tshape->stroke.color |= (unsigned int)(attr->strokeOpacity*255) << 24;\n\t} else if (attr->hasStroke == 2) {\n\t\tshape->stroke.type = NSVG_PAINT_UNDEF;\n\t}\n\n\t// Set flags\n\tshape->flags = (attr->visible ? NSVG_FLAGS_VISIBLE : 0x00);\n\n\t// Add to tail\n\tif (p->image->shapes == NULL)\n\t\tp->image->shapes = shape;\n\telse\n\t\tp->shapesTail->next = shape;\n\tp->shapesTail = shape;\n\n\treturn;\n\nerror:\n\tif (shape) free(shape);\n}\n\nstatic void nsvg__addPath(NSVGparser* p, char closed)\n{\n\tNSVGattrib* attr = nsvg__getAttr(p);\n\tNSVGpath* path = NULL;\n\tfloat bounds[4];\n\tfloat* curve;\n\tint i;\n\n\tif (p->npts < 4)\n\t\treturn;\n\n\tif (closed)\n\t\tnsvg__lineTo(p, p->pts[0], p->pts[1]);\n\n\t// Expect 1 + N*3 points (N = number of cubic bezier segments).\n\tif ((p->npts % 3) != 1)\n\t\treturn;\n\n\tpath = (NSVGpath*)malloc(sizeof(NSVGpath));\n\tif (path == NULL) goto error;\n\tmemset(path, 0, sizeof(NSVGpath));\n\n\tpath->pts = (float*)malloc(p->npts*2*sizeof(float));\n\tif (path->pts == NULL) goto error;\n\tpath->closed = closed;\n\tpath->npts = p->npts;\n\n\t// Transform path.\n\tfor (i = 0; i < p->npts; ++i)\n\t\tnsvg__xformPoint(&path->pts[i*2], &path->pts[i*2+1], p->pts[i*2], p->pts[i*2+1], attr->xform);\n\n\t// Find bounds\n\tfor (i = 0; i < path->npts-1; i += 3) {\n\t\tcurve = &path->pts[i*2];\n\t\tnsvg__curveBounds(bounds, curve);\n\t\tif (i == 0) {\n\t\t\tpath->bounds[0] = bounds[0];\n\t\t\tpath->bounds[1] = bounds[1];\n\t\t\tpath->bounds[2] = bounds[2];\n\t\t\tpath->bounds[3] = bounds[3];\n\t\t} else {\n\t\t\tpath->bounds[0] = nsvg__minf(path->bounds[0], bounds[0]);\n\t\t\tpath->bounds[1] = nsvg__minf(path->bounds[1], bounds[1]);\n\t\t\tpath->bounds[2] = nsvg__maxf(path->bounds[2], bounds[2]);\n\t\t\tpath->bounds[3] = nsvg__maxf(path->bounds[3], bounds[3]);\n\t\t}\n\t}\n\n\tpath->next = p->plist;\n\tp->plist = path;\n\n\treturn;\n\nerror:\n\tif (path != NULL) {\n\t\tif (path->pts != NULL) free(path->pts);\n\t\tfree(path);\n\t}\n}\n\n// We roll our own string to float because the std library one uses locale and messes things up.\nstatic double nsvg__atof(const char* s)\n{\n\tchar* cur = (char*)s;\n\tchar* end = NULL;\n\tdouble res = 0.0, sign = 1.0;\n\tlong long intPart = 0, fracPart = 0;\n\tchar hasIntPart = 0, hasFracPart = 0;\n\n\t// Parse optional sign\n\tif (*cur == '+') {\n\t\tcur++;\n\t} else if (*cur == '-') {\n\t\tsign = -1;\n\t\tcur++;\n\t}\n\n\t// Parse integer part\n\tif (nsvg__isdigit(*cur)) {\n\t\t// Parse digit sequence\n\t\tintPart = strtoll(cur, &end, 10);\n\t\tif (cur != end) {\n\t\t\tres = (double)intPart;\n\t\t\thasIntPart = 1;\n\t\t\tcur = end;\n\t\t}\n\t}\n\n\t// Parse fractional part.\n\tif (*cur == '.') {\n\t\tcur++; // Skip '.'\n\t\tif (nsvg__isdigit(*cur)) {\n\t\t\t// Parse digit sequence\n\t\t\tfracPart = strtoll(cur, &end, 10);\n\t\t\tif (cur != end) {\n\t\t\t\tres += (double)fracPart / pow(10.0, (double)(end - cur));\n\t\t\t\thasFracPart = 1;\n\t\t\t\tcur = end;\n\t\t\t}\n\t\t}\n\t}\n\n\t// A valid number should have integer or fractional part.\n\tif (!hasIntPart && !hasFracPart)\n\t\treturn 0.0;\n\n\t// Parse optional exponent\n\tif (*cur == 'e' || *cur == 'E') {\n\t\tlong expPart = 0;\n\t\tcur++; // skip 'E'\n\t\texpPart = strtol(cur, &end, 10); // Parse digit sequence with sign\n\t\tif (cur != end) {\n\t\t\tres *= pow(10.0, (double)expPart);\n\t\t}\n\t}\n\n\treturn res * sign;\n}\n\n\nstatic const char* nsvg__parseNumber(const char* s, char* it, const int size)\n{\n\tconst int last = size-1;\n\tint i = 0;\n\n\t// sign\n\tif (*s == '-' || *s == '+') {\n\t\tif (i < last) it[i++] = *s;\n\t\ts++;\n\t}\n\t// integer part\n\twhile (*s && nsvg__isdigit(*s)) {\n\t\tif (i < last) it[i++] = *s;\n\t\ts++;\n\t}\n\tif (*s == '.') {\n\t\t// decimal point\n\t\tif (i < last) it[i++] = *s;\n\t\ts++;\n\t\t// fraction part\n\t\twhile (*s && nsvg__isdigit(*s)) {\n\t\t\tif (i < last) it[i++] = *s;\n\t\t\ts++;\n\t\t}\n\t}\n\t// exponent\n\tif ((*s == 'e' || *s == 'E') && (s[1] != 'm' && s[1] != 'x')) {\n\t\tif (i < last) it[i++] = *s;\n\t\ts++;\n\t\tif (*s == '-' || *s == '+') {\n\t\t\tif (i < last) it[i++] = *s;\n\t\t\ts++;\n\t\t}\n\t\twhile (*s && nsvg__isdigit(*s)) {\n\t\t\tif (i < last) it[i++] = *s;\n\t\t\ts++;\n\t\t}\n\t}\n\tit[i] = '\\0';\n\n\treturn s;\n}\n\nstatic const char* nsvg__getNextPathItemWhenArcFlag(const char* s, char* it)\n{\n\tit[0] = '\\0';\n\twhile (*s && (nsvg__isspace(*s) || *s == ',')) s++;\n\tif (!*s) return s;\n\tif (*s == '0' || *s == '1') {\n\t\tit[0] = *s++;\n\t\tit[1] = '\\0';\n\t\treturn s;\n\t}\n\treturn s;\n}\n\nstatic const char* nsvg__getNextPathItem(const char* s, char* it)\n{\n\tit[0] = '\\0';\n\t// Skip white spaces and commas\n\twhile (*s && (nsvg__isspace(*s) || *s == ',')) s++;\n\tif (!*s) return s;\n\tif (*s == '-' || *s == '+' || *s == '.' || nsvg__isdigit(*s)) {\n\t\ts = nsvg__parseNumber(s, it, 64);\n\t} else {\n\t\t// Parse command\n\t\tit[0] = *s++;\n\t\tit[1] = '\\0';\n\t\treturn s;\n\t}\n\n\treturn s;\n}\n\nstatic unsigned int nsvg__parseColorHex(const char* str)\n{\n\tunsigned int r=0, g=0, b=0;\n\tif (sscanf(str, \"#%2x%2x%2x\", &r, &g, &b) == 3 )\t\t// 2 digit hex\n\t\treturn NSVG_RGB(r, g, b);\n\tif (sscanf(str, \"#%1x%1x%1x\", &r, &g, &b) == 3 )\t\t// 1 digit hex, e.g. #abc -> 0xccbbaa\n\t\treturn NSVG_RGB(r*17, g*17, b*17);\t\t\t// same effect as (r<<4|r), (g<<4|g), ..\n\treturn NSVG_RGB(128, 128, 128);\n}\n\n// Parse rgb color. The pointer 'str' must point at \"rgb(\" (4+ characters).\n// This function returns gray (rgb(128, 128, 128) == '#808080') on parse errors\n// for backwards compatibility. Note: other image viewers return black instead.\n\nstatic unsigned int nsvg__parseColorRGB(const char* str)\n{\n\tint i;\n\tunsigned int rgbi[3];\n\tfloat rgbf[3];\n\t// try decimal integers first\n\tif (sscanf(str, \"rgb(%u, %u, %u)\", &rgbi[0], &rgbi[1], &rgbi[2]) != 3) {\n\t\t// integers failed, try percent values (float, locale independent)\n\t\tconst char delimiter[3] = {',', ',', ')'};\n\t\tstr += 4; // skip \"rgb(\"\n\t\tfor (i = 0; i < 3; i++) {\n\t\t\twhile (*str && (nsvg__isspace(*str))) str++; \t// skip leading spaces\n\t\t\tif (*str == '+') str++;\t\t\t\t// skip '+' (don't allow '-')\n\t\t\tif (!*str) break;\n\t\t\trgbf[i] = nsvg__atof(str);\n\n\t\t\t// Note 1: it would be great if nsvg__atof() returned how many\n\t\t\t// bytes it consumed but it doesn't. We need to skip the number,\n\t\t\t// the '%' character, spaces, and the delimiter ',' or ')'.\n\n\t\t\t// Note 2: The following code does not allow values like \"33.%\",\n\t\t\t// i.e. a decimal point w/o fractional part, but this is consistent\n\t\t\t// with other image viewers, e.g. firefox, chrome, eog, gimp.\n\n\t\t\twhile (*str && nsvg__isdigit(*str)) str++;\t\t// skip integer part\n\t\t\tif (*str == '.') {\n\t\t\t\tstr++;\n\t\t\t\tif (!nsvg__isdigit(*str)) break;\t\t// error: no digit after '.'\n\t\t\t\twhile (*str && nsvg__isdigit(*str)) str++;\t// skip fractional part\n\t\t\t}\n\t\t\tif (*str == '%') str++; else break;\n\t\t\twhile (*str && nsvg__isspace(*str)) str++;\n\t\t\tif (*str == delimiter[i]) str++;\n\t\t\telse break;\n\t\t}\n\t\tif (i == 3) {\n\t\t\trgbi[0] = roundf(rgbf[0] * 2.55f);\n\t\t\trgbi[1] = roundf(rgbf[1] * 2.55f);\n\t\t\trgbi[2] = roundf(rgbf[2] * 2.55f);\n\t\t} else {\n\t\t\trgbi[0] = rgbi[1] = rgbi[2] = 128;\n\t\t}\n\t}\n\t// clip values as the CSS spec requires\n\tfor (i = 0; i < 3; i++) {\n\t\tif (rgbi[i] > 255) rgbi[i] = 255;\n\t}\n\treturn NSVG_RGB(rgbi[0], rgbi[1], rgbi[2]);\n}\n\ntypedef struct NSVGNamedColor {\n\tconst char* name;\n\tunsigned int color;\n} NSVGNamedColor;\n\nNSVGNamedColor nsvg__colors[] = {\n\n\t{ \"red\", NSVG_RGB(255, 0, 0) },\n\t{ \"green\", NSVG_RGB( 0, 128, 0) },\n\t{ \"blue\", NSVG_RGB( 0, 0, 255) },\n\t{ \"yellow\", NSVG_RGB(255, 255, 0) },\n\t{ \"cyan\", NSVG_RGB( 0, 255, 255) },\n\t{ \"magenta\", NSVG_RGB(255, 0, 255) },\n\t{ \"black\", NSVG_RGB( 0, 0, 0) },\n\t{ \"grey\", NSVG_RGB(128, 128, 128) },\n\t{ \"gray\", NSVG_RGB(128, 128, 128) },\n\t{ \"white\", NSVG_RGB(255, 255, 255) },\n\n#ifdef NANOSVG_ALL_COLOR_KEYWORDS\n\t{ \"aliceblue\", NSVG_RGB(240, 248, 255) },\n\t{ \"antiquewhite\", NSVG_RGB(250, 235, 215) },\n\t{ \"aqua\", NSVG_RGB( 0, 255, 255) },\n\t{ \"aquamarine\", NSVG_RGB(127, 255, 212) },\n\t{ \"azure\", NSVG_RGB(240, 255, 255) },\n\t{ \"beige\", NSVG_RGB(245, 245, 220) },\n\t{ \"bisque\", NSVG_RGB(255, 228, 196) },\n\t{ \"blanchedalmond\", NSVG_RGB(255, 235, 205) },\n\t{ \"blueviolet\", NSVG_RGB(138, 43, 226) },\n\t{ \"brown\", NSVG_RGB(165, 42, 42) },\n\t{ \"burlywood\", NSVG_RGB(222, 184, 135) },\n\t{ \"cadetblue\", NSVG_RGB( 95, 158, 160) },\n\t{ \"chartreuse\", NSVG_RGB(127, 255, 0) },\n\t{ \"chocolate\", NSVG_RGB(210, 105, 30) },\n\t{ \"coral\", NSVG_RGB(255, 127, 80) },\n\t{ \"cornflowerblue\", NSVG_RGB(100, 149, 237) },\n\t{ \"cornsilk\", NSVG_RGB(255, 248, 220) },\n\t{ \"crimson\", NSVG_RGB(220, 20, 60) },\n\t{ \"darkblue\", NSVG_RGB( 0, 0, 139) },\n\t{ \"darkcyan\", NSVG_RGB( 0, 139, 139) },\n\t{ \"darkgoldenrod\", NSVG_RGB(184, 134, 11) },\n\t{ \"darkgray\", NSVG_RGB(169, 169, 169) },\n\t{ \"darkgreen\", NSVG_RGB( 0, 100, 0) },\n\t{ \"darkgrey\", NSVG_RGB(169, 169, 169) },\n\t{ \"darkkhaki\", NSVG_RGB(189, 183, 107) },\n\t{ \"darkmagenta\", NSVG_RGB(139, 0, 139) },\n\t{ \"darkolivegreen\", NSVG_RGB( 85, 107, 47) },\n\t{ \"darkorange\", NSVG_RGB(255, 140, 0) },\n\t{ \"darkorchid\", NSVG_RGB(153, 50, 204) },\n\t{ \"darkred\", NSVG_RGB(139, 0, 0) },\n\t{ \"darksalmon\", NSVG_RGB(233, 150, 122) },\n\t{ \"darkseagreen\", NSVG_RGB(143, 188, 143) },\n\t{ \"darkslateblue\", NSVG_RGB( 72, 61, 139) },\n\t{ \"darkslategray\", NSVG_RGB( 47, 79, 79) },\n\t{ \"darkslategrey\", NSVG_RGB( 47, 79, 79) },\n\t{ \"darkturquoise\", NSVG_RGB( 0, 206, 209) },\n\t{ \"darkviolet\", NSVG_RGB(148, 0, 211) },\n\t{ \"deeppink\", NSVG_RGB(255, 20, 147) },\n\t{ \"deepskyblue\", NSVG_RGB( 0, 191, 255) },\n\t{ \"dimgray\", NSVG_RGB(105, 105, 105) },\n\t{ \"dimgrey\", NSVG_RGB(105, 105, 105) },\n\t{ \"dodgerblue\", NSVG_RGB( 30, 144, 255) },\n\t{ \"firebrick\", NSVG_RGB(178, 34, 34) },\n\t{ \"floralwhite\", NSVG_RGB(255, 250, 240) },\n\t{ \"forestgreen\", NSVG_RGB( 34, 139, 34) },\n\t{ \"fuchsia\", NSVG_RGB(255, 0, 255) },\n\t{ \"gainsboro\", NSVG_RGB(220, 220, 220) },\n\t{ \"ghostwhite\", NSVG_RGB(248, 248, 255) },\n\t{ \"gold\", NSVG_RGB(255, 215, 0) },\n\t{ \"goldenrod\", NSVG_RGB(218, 165, 32) },\n\t{ \"greenyellow\", NSVG_RGB(173, 255, 47) },\n\t{ \"honeydew\", NSVG_RGB(240, 255, 240) },\n\t{ \"hotpink\", NSVG_RGB(255, 105, 180) },\n\t{ \"indianred\", NSVG_RGB(205, 92, 92) },\n\t{ \"indigo\", NSVG_RGB( 75, 0, 130) },\n\t{ \"ivory\", NSVG_RGB(255, 255, 240) },\n\t{ \"khaki\", NSVG_RGB(240, 230, 140) },\n\t{ \"lavender\", NSVG_RGB(230, 230, 250) },\n\t{ \"lavenderblush\", NSVG_RGB(255, 240, 245) },\n\t{ \"lawngreen\", NSVG_RGB(124, 252, 0) },\n\t{ \"lemonchiffon\", NSVG_RGB(255, 250, 205) },\n\t{ \"lightblue\", NSVG_RGB(173, 216, 230) },\n\t{ \"lightcoral\", NSVG_RGB(240, 128, 128) },\n\t{ \"lightcyan\", NSVG_RGB(224, 255, 255) },\n\t{ \"lightgoldenrodyellow\", NSVG_RGB(250, 250, 210) },\n\t{ \"lightgray\", NSVG_RGB(211, 211, 211) },\n\t{ \"lightgreen\", NSVG_RGB(144, 238, 144) },\n\t{ \"lightgrey\", NSVG_RGB(211, 211, 211) },\n\t{ \"lightpink\", NSVG_RGB(255, 182, 193) },\n\t{ \"lightsalmon\", NSVG_RGB(255, 160, 122) },\n\t{ \"lightseagreen\", NSVG_RGB( 32, 178, 170) },\n\t{ \"lightskyblue\", NSVG_RGB(135, 206, 250) },\n\t{ \"lightslategray\", NSVG_RGB(119, 136, 153) },\n\t{ \"lightslategrey\", NSVG_RGB(119, 136, 153) },\n\t{ \"lightsteelblue\", NSVG_RGB(176, 196, 222) },\n\t{ \"lightyellow\", NSVG_RGB(255, 255, 224) },\n\t{ \"lime\", NSVG_RGB( 0, 255, 0) },\n\t{ \"limegreen\", NSVG_RGB( 50, 205, 50) },\n\t{ \"linen\", NSVG_RGB(250, 240, 230) },\n\t{ \"maroon\", NSVG_RGB(128, 0, 0) },\n\t{ \"mediumaquamarine\", NSVG_RGB(102, 205, 170) },\n\t{ \"mediumblue\", NSVG_RGB( 0, 0, 205) },\n\t{ \"mediumorchid\", NSVG_RGB(186, 85, 211) },\n\t{ \"mediumpurple\", NSVG_RGB(147, 112, 219) },\n\t{ \"mediumseagreen\", NSVG_RGB( 60, 179, 113) },\n\t{ \"mediumslateblue\", NSVG_RGB(123, 104, 238) },\n\t{ \"mediumspringgreen\", NSVG_RGB( 0, 250, 154) },\n\t{ \"mediumturquoise\", NSVG_RGB( 72, 209, 204) },\n\t{ \"mediumvioletred\", NSVG_RGB(199, 21, 133) },\n\t{ \"midnightblue\", NSVG_RGB( 25, 25, 112) },\n\t{ \"mintcream\", NSVG_RGB(245, 255, 250) },\n\t{ \"mistyrose\", NSVG_RGB(255, 228, 225) },\n\t{ \"moccasin\", NSVG_RGB(255, 228, 181) },\n\t{ \"navajowhite\", NSVG_RGB(255, 222, 173) },\n\t{ \"navy\", NSVG_RGB( 0, 0, 128) },\n\t{ \"oldlace\", NSVG_RGB(253, 245, 230) },\n\t{ \"olive\", NSVG_RGB(128, 128, 0) },\n\t{ \"olivedrab\", NSVG_RGB(107, 142, 35) },\n\t{ \"orange\", NSVG_RGB(255, 165, 0) },\n\t{ \"orangered\", NSVG_RGB(255, 69, 0) },\n\t{ \"orchid\", NSVG_RGB(218, 112, 214) },\n\t{ \"palegoldenrod\", NSVG_RGB(238, 232, 170) },\n\t{ \"palegreen\", NSVG_RGB(152, 251, 152) },\n\t{ \"paleturquoise\", NSVG_RGB(175, 238, 238) },\n\t{ \"palevioletred\", NSVG_RGB(219, 112, 147) },\n\t{ \"papayawhip\", NSVG_RGB(255, 239, 213) },\n\t{ \"peachpuff\", NSVG_RGB(255, 218, 185) },\n\t{ \"peru\", NSVG_RGB(205, 133, 63) },\n\t{ \"pink\", NSVG_RGB(255, 192, 203) },\n\t{ \"plum\", NSVG_RGB(221, 160, 221) },\n\t{ \"powderblue\", NSVG_RGB(176, 224, 230) },\n\t{ \"purple\", NSVG_RGB(128, 0, 128) },\n\t{ \"rosybrown\", NSVG_RGB(188, 143, 143) },\n\t{ \"royalblue\", NSVG_RGB( 65, 105, 225) },\n\t{ \"saddlebrown\", NSVG_RGB(139, 69, 19) },\n\t{ \"salmon\", NSVG_RGB(250, 128, 114) },\n\t{ \"sandybrown\", NSVG_RGB(244, 164, 96) },\n\t{ \"seagreen\", NSVG_RGB( 46, 139, 87) },\n\t{ \"seashell\", NSVG_RGB(255, 245, 238) },\n\t{ \"sienna\", NSVG_RGB(160, 82, 45) },\n\t{ \"silver\", NSVG_RGB(192, 192, 192) },\n\t{ \"skyblue\", NSVG_RGB(135, 206, 235) },\n\t{ \"slateblue\", NSVG_RGB(106, 90, 205) },\n\t{ \"slategray\", NSVG_RGB(112, 128, 144) },\n\t{ \"slategrey\", NSVG_RGB(112, 128, 144) },\n\t{ \"snow\", NSVG_RGB(255, 250, 250) },\n\t{ \"springgreen\", NSVG_RGB( 0, 255, 127) },\n\t{ \"steelblue\", NSVG_RGB( 70, 130, 180) },\n\t{ \"tan\", NSVG_RGB(210, 180, 140) },\n\t{ \"teal\", NSVG_RGB( 0, 128, 128) },\n\t{ \"thistle\", NSVG_RGB(216, 191, 216) },\n\t{ \"tomato\", NSVG_RGB(255, 99, 71) },\n\t{ \"turquoise\", NSVG_RGB( 64, 224, 208) },\n\t{ \"violet\", NSVG_RGB(238, 130, 238) },\n\t{ \"wheat\", NSVG_RGB(245, 222, 179) },\n\t{ \"whitesmoke\", NSVG_RGB(245, 245, 245) },\n\t{ \"yellowgreen\", NSVG_RGB(154, 205, 50) },\n#endif\n};\n\nstatic unsigned int nsvg__parseColorName(const char* str)\n{\n\tint i, ncolors = sizeof(nsvg__colors) / sizeof(NSVGNamedColor);\n\n\tfor (i = 0; i < ncolors; i++) {\n\t\tif (strcmp(nsvg__colors[i].name, str) == 0) {\n\t\t\treturn nsvg__colors[i].color;\n\t\t}\n\t}\n\n\treturn NSVG_RGB(128, 128, 128);\n}\n\nstatic unsigned int nsvg__parseColor(const char* str)\n{\n\tsize_t len = 0;\n\twhile(*str == ' ') ++str;\n\tlen = strlen(str);\n\tif (len >= 1 && *str == '#')\n\t\treturn nsvg__parseColorHex(str);\n\telse if (len >= 4 && str[0] == 'r' && str[1] == 'g' && str[2] == 'b' && str[3] == '(')\n\t\treturn nsvg__parseColorRGB(str);\n\treturn nsvg__parseColorName(str);\n}\n\nstatic float nsvg__parseOpacity(const char* str)\n{\n\tfloat val = nsvg__atof(str);\n\tif (val < 0.0f) val = 0.0f;\n\tif (val > 1.0f) val = 1.0f;\n\treturn val;\n}\n\nstatic float nsvg__parseMiterLimit(const char* str)\n{\n\tfloat val = nsvg__atof(str);\n\tif (val < 0.0f) val = 0.0f;\n\treturn val;\n}\n\nstatic int nsvg__parseUnits(const char* units)\n{\n\tif (units[0] == 'p' && units[1] == 'x')\n\t\treturn NSVG_UNITS_PX;\n\telse if (units[0] == 'p' && units[1] == 't')\n\t\treturn NSVG_UNITS_PT;\n\telse if (units[0] == 'p' && units[1] == 'c')\n\t\treturn NSVG_UNITS_PC;\n\telse if (units[0] == 'm' && units[1] == 'm')\n\t\treturn NSVG_UNITS_MM;\n\telse if (units[0] == 'c' && units[1] == 'm')\n\t\treturn NSVG_UNITS_CM;\n\telse if (units[0] == 'i' && units[1] == 'n')\n\t\treturn NSVG_UNITS_IN;\n\telse if (units[0] == '%')\n\t\treturn NSVG_UNITS_PERCENT;\n\telse if (units[0] == 'e' && units[1] == 'm')\n\t\treturn NSVG_UNITS_EM;\n\telse if (units[0] == 'e' && units[1] == 'x')\n\t\treturn NSVG_UNITS_EX;\n\treturn NSVG_UNITS_USER;\n}\n\nstatic int nsvg__isCoordinate(const char* s)\n{\n\t// optional sign\n\tif (*s == '-' || *s == '+')\n\t\ts++;\n\t// must have at least one digit, or start by a dot\n\treturn (nsvg__isdigit(*s) || *s == '.');\n}\n\nstatic NSVGcoordinate nsvg__parseCoordinateRaw(const char* str)\n{\n\tNSVGcoordinate coord = {0, NSVG_UNITS_USER};\n\tchar buf[64];\n\tcoord.units = nsvg__parseUnits(nsvg__parseNumber(str, buf, 64));\n\tcoord.value = nsvg__atof(buf);\n\treturn coord;\n}\n\nstatic NSVGcoordinate nsvg__coord(float v, int units)\n{\n\tNSVGcoordinate coord = {v, units};\n\treturn coord;\n}\n\nstatic float nsvg__parseCoordinate(NSVGparser* p, const char* str, float orig, float length)\n{\n\tNSVGcoordinate coord = nsvg__parseCoordinateRaw(str);\n\treturn nsvg__convertToPixels(p, coord, orig, length);\n}\n\nstatic int nsvg__parseTransformArgs(const char* str, float* args, int maxNa, int* na)\n{\n\tconst char* end;\n\tconst char* ptr;\n\tchar it[64];\n\n\t*na = 0;\n\tptr = str;\n\twhile (*ptr && *ptr != '(') ++ptr;\n\tif (*ptr == 0)\n\t\treturn 1;\n\tend = ptr;\n\twhile (*end && *end != ')') ++end;\n\tif (*end == 0)\n\t\treturn 1;\n\n\twhile (ptr < end) {\n\t\tif (*ptr == '-' || *ptr == '+' || *ptr == '.' || nsvg__isdigit(*ptr)) {\n\t\t\tif (*na >= maxNa) return 0;\n\t\t\tptr = nsvg__parseNumber(ptr, it, 64);\n\t\t\targs[(*na)++] = (float)nsvg__atof(it);\n\t\t} else {\n\t\t\t++ptr;\n\t\t}\n\t}\n\treturn (int)(end - str);\n}\n\n\nstatic int nsvg__parseMatrix(float* xform, const char* str)\n{\n\tfloat t[6];\n\tint na = 0;\n\tint len = nsvg__parseTransformArgs(str, t, 6, &na);\n\tif (na != 6) return len;\n\tmemcpy(xform, t, sizeof(float)*6);\n\treturn len;\n}\n\nstatic int nsvg__parseTranslate(float* xform, const char* str)\n{\n\tfloat args[2];\n\tfloat t[6];\n\tint na = 0;\n\tint len = nsvg__parseTransformArgs(str, args, 2, &na);\n\tif (na == 1) args[1] = 0.0;\n\n\tnsvg__xformSetTranslation(t, args[0], args[1]);\n\tmemcpy(xform, t, sizeof(float)*6);\n\treturn len;\n}\n\nstatic int nsvg__parseScale(float* xform, const char* str)\n{\n\tfloat args[2];\n\tint na = 0;\n\tfloat t[6];\n\tint len = nsvg__parseTransformArgs(str, args, 2, &na);\n\tif (na == 1) args[1] = args[0];\n\tnsvg__xformSetScale(t, args[0], args[1]);\n\tmemcpy(xform, t, sizeof(float)*6);\n\treturn len;\n}\n\nstatic int nsvg__parseSkewX(float* xform, const char* str)\n{\n\tfloat args[1];\n\tint na = 0;\n\tfloat t[6];\n\tint len = nsvg__parseTransformArgs(str, args, 1, &na);\n\tnsvg__xformSetSkewX(t, args[0]/180.0f*NSVG_PI);\n\tmemcpy(xform, t, sizeof(float)*6);\n\treturn len;\n}\n\nstatic int nsvg__parseSkewY(float* xform, const char* str)\n{\n\tfloat args[1];\n\tint na = 0;\n\tfloat t[6];\n\tint len = nsvg__parseTransformArgs(str, args, 1, &na);\n\tnsvg__xformSetSkewY(t, args[0]/180.0f*NSVG_PI);\n\tmemcpy(xform, t, sizeof(float)*6);\n\treturn len;\n}\n\nstatic int nsvg__parseRotate(float* xform, const char* str)\n{\n\tfloat args[3];\n\tint na = 0;\n\tfloat m[6];\n\tfloat t[6];\n\tint len = nsvg__parseTransformArgs(str, args, 3, &na);\n\tif (na == 1)\n\t\targs[1] = args[2] = 0.0f;\n\tnsvg__xformIdentity(m);\n\n\tif (na > 1) {\n\t\tnsvg__xformSetTranslation(t, -args[1], -args[2]);\n\t\tnsvg__xformMultiply(m, t);\n\t}\n\n\tnsvg__xformSetRotation(t, args[0]/180.0f*NSVG_PI);\n\tnsvg__xformMultiply(m, t);\n\n\tif (na > 1) {\n\t\tnsvg__xformSetTranslation(t, args[1], args[2]);\n\t\tnsvg__xformMultiply(m, t);\n\t}\n\n\tmemcpy(xform, m, sizeof(float)*6);\n\n\treturn len;\n}\n\nstatic void nsvg__parseTransform(float* xform, const char* str)\n{\n\tfloat t[6];\n\tint len;\n\tnsvg__xformIdentity(xform);\n\twhile (*str)\n\t{\n\t\tif (strncmp(str, \"matrix\", 6) == 0)\n\t\t\tlen = nsvg__parseMatrix(t, str);\n\t\telse if (strncmp(str, \"translate\", 9) == 0)\n\t\t\tlen = nsvg__parseTranslate(t, str);\n\t\telse if (strncmp(str, \"scale\", 5) == 0)\n\t\t\tlen = nsvg__parseScale(t, str);\n\t\telse if (strncmp(str, \"rotate\", 6) == 0)\n\t\t\tlen = nsvg__parseRotate(t, str);\n\t\telse if (strncmp(str, \"skewX\", 5) == 0)\n\t\t\tlen = nsvg__parseSkewX(t, str);\n\t\telse if (strncmp(str, \"skewY\", 5) == 0)\n\t\t\tlen = nsvg__parseSkewY(t, str);\n\t\telse{\n\t\t\t++str;\n\t\t\tcontinue;\n\t\t}\n\t\tif (len != 0) {\n\t\t\tstr += len;\n\t\t} else {\n\t\t\t++str;\n\t\t\tcontinue;\n\t\t}\n\n\t\tnsvg__xformPremultiply(xform, t);\n\t}\n}\n\nstatic void nsvg__parseUrl(char* id, const char* str)\n{\n\tint i = 0;\n\tstr += 4; // \"url(\";\n\tif (*str && *str == '#')\n\t\tstr++;\n\twhile (i < 63 && *str && *str != ')') {\n\t\tid[i] = *str++;\n\t\ti++;\n\t}\n\tid[i] = '\\0';\n}\n\nstatic char nsvg__parseLineCap(const char* str)\n{\n\tif (strcmp(str, \"butt\") == 0)\n\t\treturn NSVG_CAP_BUTT;\n\telse if (strcmp(str, \"round\") == 0)\n\t\treturn NSVG_CAP_ROUND;\n\telse if (strcmp(str, \"square\") == 0)\n\t\treturn NSVG_CAP_SQUARE;\n\t// TODO: handle inherit.\n\treturn NSVG_CAP_BUTT;\n}\n\nstatic char nsvg__parseLineJoin(const char* str)\n{\n\tif (strcmp(str, \"miter\") == 0)\n\t\treturn NSVG_JOIN_MITER;\n\telse if (strcmp(str, \"round\") == 0)\n\t\treturn NSVG_JOIN_ROUND;\n\telse if (strcmp(str, \"bevel\") == 0)\n\t\treturn NSVG_JOIN_BEVEL;\n\t// TODO: handle inherit.\n\treturn NSVG_JOIN_MITER;\n}\n\nstatic char nsvg__parseFillRule(const char* str)\n{\n\tif (strcmp(str, \"nonzero\") == 0)\n\t\treturn NSVG_FILLRULE_NONZERO;\n\telse if (strcmp(str, \"evenodd\") == 0)\n\t\treturn NSVG_FILLRULE_EVENODD;\n\t// TODO: handle inherit.\n\treturn NSVG_FILLRULE_NONZERO;\n}\n\nstatic unsigned char nsvg__parsePaintOrder(const char* str)\n{\n\tif (strcmp(str, \"normal\") == 0 || strcmp(str, \"fill stroke markers\") == 0)\n\t\treturn nsvg__encodePaintOrder(NSVG_PAINT_FILL, NSVG_PAINT_STROKE, NSVG_PAINT_MARKERS);\n\telse if (strcmp(str, \"fill markers stroke\") == 0)\n\t\treturn nsvg__encodePaintOrder(NSVG_PAINT_FILL, NSVG_PAINT_MARKERS, NSVG_PAINT_STROKE);\n\telse if (strcmp(str, \"markers fill stroke\") == 0)\n\t\treturn nsvg__encodePaintOrder(NSVG_PAINT_MARKERS, NSVG_PAINT_FILL, NSVG_PAINT_STROKE);\n\telse if (strcmp(str, \"markers stroke fill\") == 0)\n\t\treturn nsvg__encodePaintOrder(NSVG_PAINT_MARKERS, NSVG_PAINT_STROKE, NSVG_PAINT_FILL);\n\telse if (strcmp(str, \"stroke fill markers\") == 0)\n\t\treturn nsvg__encodePaintOrder(NSVG_PAINT_STROKE, NSVG_PAINT_FILL, NSVG_PAINT_MARKERS);\n\telse if (strcmp(str, \"stroke markers fill\") == 0)\n\t\treturn nsvg__encodePaintOrder(NSVG_PAINT_STROKE, NSVG_PAINT_MARKERS, NSVG_PAINT_FILL);\n\t// TODO: handle inherit.\n\treturn nsvg__encodePaintOrder(NSVG_PAINT_FILL, NSVG_PAINT_STROKE, NSVG_PAINT_MARKERS);\n}\n\nstatic const char* nsvg__getNextDashItem(const char* s, char* it)\n{\n\tint n = 0;\n\tit[0] = '\\0';\n\t// Skip white spaces and commas\n\twhile (*s && (nsvg__isspace(*s) || *s == ',')) s++;\n\t// Advance until whitespace, comma or end.\n\twhile (*s && (!nsvg__isspace(*s) && *s != ',')) {\n\t\tif (n < 63)\n\t\t\tit[n++] = *s;\n\t\ts++;\n\t}\n\tit[n++] = '\\0';\n\treturn s;\n}\n\nstatic int nsvg__parseStrokeDashArray(NSVGparser* p, const char* str, float* strokeDashArray)\n{\n\tchar item[64];\n\tint count = 0, i;\n\tfloat sum = 0.0f;\n\n\t// Handle \"none\"\n\tif (str[0] == 'n')\n\t\treturn 0;\n\n\t// Parse dashes\n\twhile (*str) {\n\t\tstr = nsvg__getNextDashItem(str, item);\n\t\tif (!*item) break;\n\t\tif (count < NSVG_MAX_DASHES)\n\t\t\tstrokeDashArray[count++] = fabsf(nsvg__parseCoordinate(p, item, 0.0f, nsvg__actualLength(p)));\n\t}\n\n\tfor (i = 0; i < count; i++)\n\t\tsum += strokeDashArray[i];\n\tif (sum <= 1e-6f)\n\t\tcount = 0;\n\n\treturn count;\n}\n\nstatic void nsvg__parseStyle(NSVGparser* p, const char* str);\n\nstatic int nsvg__parseAttr(NSVGparser* p, const char* name, const char* value)\n{\n\tfloat xform[6];\n\tNSVGattrib* attr = nsvg__getAttr(p);\n\tif (!attr) return 0;\n\n\tif (strcmp(name, \"style\") == 0) {\n\t\tnsvg__parseStyle(p, value);\n\t} else if (strcmp(name, \"display\") == 0) {\n\t\tif (strcmp(value, \"none\") == 0)\n\t\t\tattr->visible = 0;\n\t\t// Don't reset ->visible on display:inline, one display:none hides the whole subtree\n\n\t} else if (strcmp(name, \"fill\") == 0) {\n\t\tif (strcmp(value, \"none\") == 0) {\n\t\t\tattr->hasFill = 0;\n\t\t} else if (strncmp(value, \"url(\", 4) == 0) {\n\t\t\tattr->hasFill = 2;\n\t\t\tnsvg__parseUrl(attr->fillGradient, value);\n\t\t} else {\n\t\t\tattr->hasFill = 1;\n\t\t\tattr->fillColor = nsvg__parseColor(value);\n\t\t}\n\t} else if (strcmp(name, \"opacity\") == 0) {\n\t\tattr->opacity = nsvg__parseOpacity(value);\n\t} else if (strcmp(name, \"fill-opacity\") == 0) {\n\t\tattr->fillOpacity = nsvg__parseOpacity(value);\n\t} else if (strcmp(name, \"stroke\") == 0) {\n\t\tif (strcmp(value, \"none\") == 0) {\n\t\t\tattr->hasStroke = 0;\n\t\t} else if (strncmp(value, \"url(\", 4) == 0) {\n\t\t\tattr->hasStroke = 2;\n\t\t\tnsvg__parseUrl(attr->strokeGradient, value);\n\t\t} else {\n\t\t\tattr->hasStroke = 1;\n\t\t\tattr->strokeColor = nsvg__parseColor(value);\n\t\t}\n\t} else if (strcmp(name, \"stroke-width\") == 0) {\n\t\tattr->strokeWidth = nsvg__parseCoordinate(p, value, 0.0f, nsvg__actualLength(p));\n\t} else if (strcmp(name, \"stroke-dasharray\") == 0) {\n\t\tattr->strokeDashCount = nsvg__parseStrokeDashArray(p, value, attr->strokeDashArray);\n\t} else if (strcmp(name, \"stroke-dashoffset\") == 0) {\n\t\tattr->strokeDashOffset = nsvg__parseCoordinate(p, value, 0.0f, nsvg__actualLength(p));\n\t} else if (strcmp(name, \"stroke-opacity\") == 0) {\n\t\tattr->strokeOpacity = nsvg__parseOpacity(value);\n\t} else if (strcmp(name, \"stroke-linecap\") == 0) {\n\t\tattr->strokeLineCap = nsvg__parseLineCap(value);\n\t} else if (strcmp(name, \"stroke-linejoin\") == 0) {\n\t\tattr->strokeLineJoin = nsvg__parseLineJoin(value);\n\t} else if (strcmp(name, \"stroke-miterlimit\") == 0) {\n\t\tattr->miterLimit = nsvg__parseMiterLimit(value);\n\t} else if (strcmp(name, \"fill-rule\") == 0) {\n\t\tattr->fillRule = nsvg__parseFillRule(value);\n\t} else if (strcmp(name, \"font-size\") == 0) {\n\t\tattr->fontSize = nsvg__parseCoordinate(p, value, 0.0f, nsvg__actualLength(p));\n\t} else if (strcmp(name, \"transform\") == 0) {\n\t\tnsvg__parseTransform(xform, value);\n\t\tnsvg__xformPremultiply(attr->xform, xform);\n\t} else if (strcmp(name, \"stop-color\") == 0) {\n\t\tattr->stopColor = nsvg__parseColor(value);\n\t} else if (strcmp(name, \"stop-opacity\") == 0) {\n\t\tattr->stopOpacity = nsvg__parseOpacity(value);\n\t} else if (strcmp(name, \"offset\") == 0) {\n\t\tattr->stopOffset = nsvg__parseCoordinate(p, value, 0.0f, 1.0f);\n\t} else if (strcmp(name, \"paint-order\") == 0) {\n\t\tattr->paintOrder = nsvg__parsePaintOrder(value);\n\t} else if (strcmp(name, \"id\") == 0) {\n\t\tstrncpy(attr->id, value, 63);\n\t\tattr->id[63] = '\\0';\n\t} else {\n\t\treturn 0;\n\t}\n\treturn 1;\n}\n\nstatic int nsvg__parseNameValue(NSVGparser* p, const char* start, const char* end)\n{\n\tconst char* str;\n\tconst char* val;\n\tchar name[512];\n\tchar value[512];\n\tint n;\n\n\tstr = start;\n\twhile (str < end && *str != ':') ++str;\n\n\tval = str;\n\n\t// Right Trim\n\twhile (str > start &&  (*str == ':' || nsvg__isspace(*str))) --str;\n\t++str;\n\n\tn = (int)(str - start);\n\tif (n > 511) n = 511;\n\tif (n) memcpy(name, start, n);\n\tname[n] = 0;\n\n\twhile (val < end && (*val == ':' || nsvg__isspace(*val))) ++val;\n\n\tn = (int)(end - val);\n\tif (n > 511) n = 511;\n\tif (n) memcpy(value, val, n);\n\tvalue[n] = 0;\n\n\treturn nsvg__parseAttr(p, name, value);\n}\n\nstatic void nsvg__parseStyle(NSVGparser* p, const char* str)\n{\n\tconst char* start;\n\tconst char* end;\n\n\twhile (*str) {\n\t\t// Left Trim\n\t\twhile(*str && nsvg__isspace(*str)) ++str;\n\t\tstart = str;\n\t\twhile(*str && *str != ';') ++str;\n\t\tend = str;\n\n\t\t// Right Trim\n\t\twhile (end > start &&  (*end == ';' || nsvg__isspace(*end))) --end;\n\t\t++end;\n\n\t\tnsvg__parseNameValue(p, start, end);\n\t\tif (*str) ++str;\n\t}\n}\n\nstatic void nsvg__parseAttribs(NSVGparser* p, const char** attr)\n{\n\tint i;\n\tfor (i = 0; attr[i]; i += 2)\n\t{\n\t\tif (strcmp(attr[i], \"style\") == 0)\n\t\t\tnsvg__parseStyle(p, attr[i + 1]);\n\t\telse\n\t\t\tnsvg__parseAttr(p, attr[i], attr[i + 1]);\n\t}\n}\n\nstatic int nsvg__getArgsPerElement(char cmd)\n{\n\tswitch (cmd) {\n\t\tcase 'v':\n\t\tcase 'V':\n\t\tcase 'h':\n\t\tcase 'H':\n\t\t\treturn 1;\n\t\tcase 'm':\n\t\tcase 'M':\n\t\tcase 'l':\n\t\tcase 'L':\n\t\tcase 't':\n\t\tcase 'T':\n\t\t\treturn 2;\n\t\tcase 'q':\n\t\tcase 'Q':\n\t\tcase 's':\n\t\tcase 'S':\n\t\t\treturn 4;\n\t\tcase 'c':\n\t\tcase 'C':\n\t\t\treturn 6;\n\t\tcase 'a':\n\t\tcase 'A':\n\t\t\treturn 7;\n\t\tcase 'z':\n\t\tcase 'Z':\n\t\t\treturn 0;\n\t}\n\treturn -1;\n}\n\nstatic void nsvg__pathMoveTo(NSVGparser* p, float* cpx, float* cpy, float* args, int rel)\n{\n\tif (rel) {\n\t\t*cpx += args[0];\n\t\t*cpy += args[1];\n\t} else {\n\t\t*cpx = args[0];\n\t\t*cpy = args[1];\n\t}\n\tnsvg__moveTo(p, *cpx, *cpy);\n}\n\nstatic void nsvg__pathLineTo(NSVGparser* p, float* cpx, float* cpy, float* args, int rel)\n{\n\tif (rel) {\n\t\t*cpx += args[0];\n\t\t*cpy += args[1];\n\t} else {\n\t\t*cpx = args[0];\n\t\t*cpy = args[1];\n\t}\n\tnsvg__lineTo(p, *cpx, *cpy);\n}\n\nstatic void nsvg__pathHLineTo(NSVGparser* p, float* cpx, float* cpy, float* args, int rel)\n{\n\tif (rel)\n\t\t*cpx += args[0];\n\telse\n\t\t*cpx = args[0];\n\tnsvg__lineTo(p, *cpx, *cpy);\n}\n\nstatic void nsvg__pathVLineTo(NSVGparser* p, float* cpx, float* cpy, float* args, int rel)\n{\n\tif (rel)\n\t\t*cpy += args[0];\n\telse\n\t\t*cpy = args[0];\n\tnsvg__lineTo(p, *cpx, *cpy);\n}\n\nstatic void nsvg__pathCubicBezTo(NSVGparser* p, float* cpx, float* cpy,\n\t\t\t\t\t\t\t\t float* cpx2, float* cpy2, float* args, int rel)\n{\n\tfloat x2, y2, cx1, cy1, cx2, cy2;\n\n\tif (rel) {\n\t\tcx1 = *cpx + args[0];\n\t\tcy1 = *cpy + args[1];\n\t\tcx2 = *cpx + args[2];\n\t\tcy2 = *cpy + args[3];\n\t\tx2 = *cpx + args[4];\n\t\ty2 = *cpy + args[5];\n\t} else {\n\t\tcx1 = args[0];\n\t\tcy1 = args[1];\n\t\tcx2 = args[2];\n\t\tcy2 = args[3];\n\t\tx2 = args[4];\n\t\ty2 = args[5];\n\t}\n\n\tnsvg__cubicBezTo(p, cx1,cy1, cx2,cy2, x2,y2);\n\n\t*cpx2 = cx2;\n\t*cpy2 = cy2;\n\t*cpx = x2;\n\t*cpy = y2;\n}\n\nstatic void nsvg__pathCubicBezShortTo(NSVGparser* p, float* cpx, float* cpy,\n\t\t\t\t\t\t\t\t\t  float* cpx2, float* cpy2, float* args, int rel)\n{\n\tfloat x1, y1, x2, y2, cx1, cy1, cx2, cy2;\n\n\tx1 = *cpx;\n\ty1 = *cpy;\n\tif (rel) {\n\t\tcx2 = *cpx + args[0];\n\t\tcy2 = *cpy + args[1];\n\t\tx2 = *cpx + args[2];\n\t\ty2 = *cpy + args[3];\n\t} else {\n\t\tcx2 = args[0];\n\t\tcy2 = args[1];\n\t\tx2 = args[2];\n\t\ty2 = args[3];\n\t}\n\n\tcx1 = 2*x1 - *cpx2;\n\tcy1 = 2*y1 - *cpy2;\n\n\tnsvg__cubicBezTo(p, cx1,cy1, cx2,cy2, x2,y2);\n\n\t*cpx2 = cx2;\n\t*cpy2 = cy2;\n\t*cpx = x2;\n\t*cpy = y2;\n}\n\nstatic void nsvg__pathQuadBezTo(NSVGparser* p, float* cpx, float* cpy,\n\t\t\t\t\t\t\t\tfloat* cpx2, float* cpy2, float* args, int rel)\n{\n\tfloat x1, y1, x2, y2, cx, cy;\n\tfloat cx1, cy1, cx2, cy2;\n\n\tx1 = *cpx;\n\ty1 = *cpy;\n\tif (rel) {\n\t\tcx = *cpx + args[0];\n\t\tcy = *cpy + args[1];\n\t\tx2 = *cpx + args[2];\n\t\ty2 = *cpy + args[3];\n\t} else {\n\t\tcx = args[0];\n\t\tcy = args[1];\n\t\tx2 = args[2];\n\t\ty2 = args[3];\n\t}\n\n\t// Convert to cubic bezier\n\tcx1 = x1 + 2.0f/3.0f*(cx - x1);\n\tcy1 = y1 + 2.0f/3.0f*(cy - y1);\n\tcx2 = x2 + 2.0f/3.0f*(cx - x2);\n\tcy2 = y2 + 2.0f/3.0f*(cy - y2);\n\n\tnsvg__cubicBezTo(p, cx1,cy1, cx2,cy2, x2,y2);\n\n\t*cpx2 = cx;\n\t*cpy2 = cy;\n\t*cpx = x2;\n\t*cpy = y2;\n}\n\nstatic void nsvg__pathQuadBezShortTo(NSVGparser* p, float* cpx, float* cpy,\n\t\t\t\t\t\t\t\t\t float* cpx2, float* cpy2, float* args, int rel)\n{\n\tfloat x1, y1, x2, y2, cx, cy;\n\tfloat cx1, cy1, cx2, cy2;\n\n\tx1 = *cpx;\n\ty1 = *cpy;\n\tif (rel) {\n\t\tx2 = *cpx + args[0];\n\t\ty2 = *cpy + args[1];\n\t} else {\n\t\tx2 = args[0];\n\t\ty2 = args[1];\n\t}\n\n\tcx = 2*x1 - *cpx2;\n\tcy = 2*y1 - *cpy2;\n\n\t// Convert to cubix bezier\n\tcx1 = x1 + 2.0f/3.0f*(cx - x1);\n\tcy1 = y1 + 2.0f/3.0f*(cy - y1);\n\tcx2 = x2 + 2.0f/3.0f*(cx - x2);\n\tcy2 = y2 + 2.0f/3.0f*(cy - y2);\n\n\tnsvg__cubicBezTo(p, cx1,cy1, cx2,cy2, x2,y2);\n\n\t*cpx2 = cx;\n\t*cpy2 = cy;\n\t*cpx = x2;\n\t*cpy = y2;\n}\n\nstatic float nsvg__sqr(float x) { return x*x; }\nstatic float nsvg__vmag(float x, float y) { return sqrtf(x*x + y*y); }\n\nstatic float nsvg__vecrat(float ux, float uy, float vx, float vy)\n{\n\treturn (ux*vx + uy*vy) / (nsvg__vmag(ux,uy) * nsvg__vmag(vx,vy));\n}\n\nstatic float nsvg__vecang(float ux, float uy, float vx, float vy)\n{\n\tfloat r = nsvg__vecrat(ux,uy, vx,vy);\n\tif (r < -1.0f) r = -1.0f;\n\tif (r > 1.0f) r = 1.0f;\n\treturn ((ux*vy < uy*vx) ? -1.0f : 1.0f) * acosf(r);\n}\n\nstatic void nsvg__pathArcTo(NSVGparser* p, float* cpx, float* cpy, float* args, int rel)\n{\n\t// Ported from canvg (https://code.google.com/p/canvg/)\n\tfloat rx, ry, rotx;\n\tfloat x1, y1, x2, y2, cx, cy, dx, dy, d;\n\tfloat x1p, y1p, cxp, cyp, s, sa, sb;\n\tfloat ux, uy, vx, vy, a1, da;\n\tfloat x, y, tanx, tany, a, px = 0, py = 0, ptanx = 0, ptany = 0, t[6];\n\tfloat sinrx, cosrx;\n\tint fa, fs;\n\tint i, ndivs;\n\tfloat hda, kappa;\n\n\trx = fabsf(args[0]);\t\t\t\t// y radius\n\try = fabsf(args[1]);\t\t\t\t// x radius\n\trotx = args[2] / 180.0f * NSVG_PI;\t\t// x rotation angle\n\tfa = fabsf(args[3]) > 1e-6 ? 1 : 0;\t// Large arc\n\tfs = fabsf(args[4]) > 1e-6 ? 1 : 0;\t// Sweep direction\n\tx1 = *cpx;\t\t\t\t\t\t\t// start point\n\ty1 = *cpy;\n\tif (rel) {\t\t\t\t\t\t\t// end point\n\t\tx2 = *cpx + args[5];\n\t\ty2 = *cpy + args[6];\n\t} else {\n\t\tx2 = args[5];\n\t\ty2 = args[6];\n\t}\n\n\tdx = x1 - x2;\n\tdy = y1 - y2;\n\td = sqrtf(dx*dx + dy*dy);\n\tif (d < 1e-6f || rx < 1e-6f || ry < 1e-6f) {\n\t\t// The arc degenerates to a line\n\t\tnsvg__lineTo(p, x2, y2);\n\t\t*cpx = x2;\n\t\t*cpy = y2;\n\t\treturn;\n\t}\n\n\tsinrx = sinf(rotx);\n\tcosrx = cosf(rotx);\n\n\t// Convert to center point parameterization.\n\t// http://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes\n\t// 1) Compute x1', y1'\n\tx1p = cosrx * dx / 2.0f + sinrx * dy / 2.0f;\n\ty1p = -sinrx * dx / 2.0f + cosrx * dy / 2.0f;\n\td = nsvg__sqr(x1p)/nsvg__sqr(rx) + nsvg__sqr(y1p)/nsvg__sqr(ry);\n\tif (d > 1) {\n\t\td = sqrtf(d);\n\t\trx *= d;\n\t\try *= d;\n\t}\n\t// 2) Compute cx', cy'\n\ts = 0.0f;\n\tsa = nsvg__sqr(rx)*nsvg__sqr(ry) - nsvg__sqr(rx)*nsvg__sqr(y1p) - nsvg__sqr(ry)*nsvg__sqr(x1p);\n\tsb = nsvg__sqr(rx)*nsvg__sqr(y1p) + nsvg__sqr(ry)*nsvg__sqr(x1p);\n\tif (sa < 0.0f) sa = 0.0f;\n\tif (sb > 0.0f)\n\t\ts = sqrtf(sa / sb);\n\tif (fa == fs)\n\t\ts = -s;\n\tcxp = s * rx * y1p / ry;\n\tcyp = s * -ry * x1p / rx;\n\n\t// 3) Compute cx,cy from cx',cy'\n\tcx = (x1 + x2)/2.0f + cosrx*cxp - sinrx*cyp;\n\tcy = (y1 + y2)/2.0f + sinrx*cxp + cosrx*cyp;\n\n\t// 4) Calculate theta1, and delta theta.\n\tux = (x1p - cxp) / rx;\n\tuy = (y1p - cyp) / ry;\n\tvx = (-x1p - cxp) / rx;\n\tvy = (-y1p - cyp) / ry;\n\ta1 = nsvg__vecang(1.0f,0.0f, ux,uy);\t// Initial angle\n\tda = nsvg__vecang(ux,uy, vx,vy);\t\t// Delta angle\n\n//\tif (vecrat(ux,uy,vx,vy) <= -1.0f) da = NSVG_PI;\n//\tif (vecrat(ux,uy,vx,vy) >= 1.0f) da = 0;\n\n\tif (fs == 0 && da > 0)\n\t\tda -= 2 * NSVG_PI;\n\telse if (fs == 1 && da < 0)\n\t\tda += 2 * NSVG_PI;\n\n\t// Approximate the arc using cubic spline segments.\n\tt[0] = cosrx; t[1] = sinrx;\n\tt[2] = -sinrx; t[3] = cosrx;\n\tt[4] = cx; t[5] = cy;\n\n\t// Split arc into max 90 degree segments.\n\t// The loop assumes an iteration per end point (including start and end), this +1.\n\tndivs = (int)(fabsf(da) / (NSVG_PI*0.5f) + 1.0f);\n\thda = (da / (float)ndivs) / 2.0f;\n\t// Fix for ticket #179: division by 0: avoid cotangens around 0 (infinite)\n\tif ((hda < 1e-3f) && (hda > -1e-3f))\n\t\thda *= 0.5f;\n\telse\n\t\thda = (1.0f - cosf(hda)) / sinf(hda);\n\tkappa = fabsf(4.0f / 3.0f * hda);\n\tif (da < 0.0f)\n\t\tkappa = -kappa;\n\n\tfor (i = 0; i <= ndivs; i++) {\n\t\ta = a1 + da * ((float)i/(float)ndivs);\n\t\tdx = cosf(a);\n\t\tdy = sinf(a);\n\t\tnsvg__xformPoint(&x, &y, dx*rx, dy*ry, t); // position\n\t\tnsvg__xformVec(&tanx, &tany, -dy*rx * kappa, dx*ry * kappa, t); // tangent\n\t\tif (i > 0)\n\t\t\tnsvg__cubicBezTo(p, px+ptanx,py+ptany, x-tanx, y-tany, x, y);\n\t\tpx = x;\n\t\tpy = y;\n\t\tptanx = tanx;\n\t\tptany = tany;\n\t}\n\n\t*cpx = x2;\n\t*cpy = y2;\n}\n\nstatic void nsvg__parsePath(NSVGparser* p, const char** attr)\n{\n\tconst char* s = NULL;\n\tchar cmd = '\\0';\n\tfloat args[10];\n\tint nargs;\n\tint rargs = 0;\n\tchar initPoint;\n\tfloat cpx, cpy, cpx2, cpy2;\n\tconst char* tmp[4];\n\tchar closedFlag;\n\tint i;\n\tchar item[64];\n\n\tfor (i = 0; attr[i]; i += 2) {\n\t\tif (strcmp(attr[i], \"d\") == 0) {\n\t\t\ts = attr[i + 1];\n\t\t} else {\n\t\t\ttmp[0] = attr[i];\n\t\t\ttmp[1] = attr[i + 1];\n\t\t\ttmp[2] = 0;\n\t\t\ttmp[3] = 0;\n\t\t\tnsvg__parseAttribs(p, tmp);\n\t\t}\n\t}\n\n\tif (s) {\n\t\tnsvg__resetPath(p);\n\t\tcpx = 0; cpy = 0;\n\t\tcpx2 = 0; cpy2 = 0;\n\t\tinitPoint = 0;\n\t\tclosedFlag = 0;\n\t\tnargs = 0;\n\n\t\twhile (*s) {\n\t\t\titem[0] = '\\0';\n\t\t\tif ((cmd == 'A' || cmd == 'a') && (nargs == 3 || nargs == 4))\n\t\t\t\ts = nsvg__getNextPathItemWhenArcFlag(s, item);\n\t\t\tif (!*item)\n\t\t\t\ts = nsvg__getNextPathItem(s, item);\n\t\t\tif (!*item) break;\n\t\t\tif (cmd != '\\0' && nsvg__isCoordinate(item)) {\n\t\t\t\tif (nargs < 10)\n\t\t\t\t\targs[nargs++] = (float)nsvg__atof(item);\n\t\t\t\tif (nargs >= rargs) {\n\t\t\t\t\tswitch (cmd) {\n\t\t\t\t\t\tcase 'm':\n\t\t\t\t\t\tcase 'M':\n\t\t\t\t\t\t\tnsvg__pathMoveTo(p, &cpx, &cpy, args, cmd == 'm' ? 1 : 0);\n\t\t\t\t\t\t\t// Moveto can be followed by multiple coordinate pairs,\n\t\t\t\t\t\t\t// which should be treated as linetos.\n\t\t\t\t\t\t\tcmd = (cmd == 'm') ? 'l' : 'L';\n\t\t\t\t\t\t\trargs = nsvg__getArgsPerElement(cmd);\n\t\t\t\t\t\t\tcpx2 = cpx; cpy2 = cpy;\n\t\t\t\t\t\t\tinitPoint = 1;\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase 'l':\n\t\t\t\t\t\tcase 'L':\n\t\t\t\t\t\t\tnsvg__pathLineTo(p, &cpx, &cpy, args, cmd == 'l' ? 1 : 0);\n\t\t\t\t\t\t\tcpx2 = cpx; cpy2 = cpy;\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase 'H':\n\t\t\t\t\t\tcase 'h':\n\t\t\t\t\t\t\tnsvg__pathHLineTo(p, &cpx, &cpy, args, cmd == 'h' ? 1 : 0);\n\t\t\t\t\t\t\tcpx2 = cpx; cpy2 = cpy;\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase 'V':\n\t\t\t\t\t\tcase 'v':\n\t\t\t\t\t\t\tnsvg__pathVLineTo(p, &cpx, &cpy, args, cmd == 'v' ? 1 : 0);\n\t\t\t\t\t\t\tcpx2 = cpx; cpy2 = cpy;\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase 'C':\n\t\t\t\t\t\tcase 'c':\n\t\t\t\t\t\t\tnsvg__pathCubicBezTo(p, &cpx, &cpy, &cpx2, &cpy2, args, cmd == 'c' ? 1 : 0);\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase 'S':\n\t\t\t\t\t\tcase 's':\n\t\t\t\t\t\t\tnsvg__pathCubicBezShortTo(p, &cpx, &cpy, &cpx2, &cpy2, args, cmd == 's' ? 1 : 0);\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase 'Q':\n\t\t\t\t\t\tcase 'q':\n\t\t\t\t\t\t\tnsvg__pathQuadBezTo(p, &cpx, &cpy, &cpx2, &cpy2, args, cmd == 'q' ? 1 : 0);\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase 'T':\n\t\t\t\t\t\tcase 't':\n\t\t\t\t\t\t\tnsvg__pathQuadBezShortTo(p, &cpx, &cpy, &cpx2, &cpy2, args, cmd == 't' ? 1 : 0);\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase 'A':\n\t\t\t\t\t\tcase 'a':\n\t\t\t\t\t\t\tnsvg__pathArcTo(p, &cpx, &cpy, args, cmd == 'a' ? 1 : 0);\n\t\t\t\t\t\t\tcpx2 = cpx; cpy2 = cpy;\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tdefault:\n\t\t\t\t\t\t\tif (nargs >= 2) {\n\t\t\t\t\t\t\t\tcpx = args[nargs-2];\n\t\t\t\t\t\t\t\tcpy = args[nargs-1];\n\t\t\t\t\t\t\t\tcpx2 = cpx; cpy2 = cpy;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t\tnargs = 0;\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tcmd = item[0];\n\t\t\t\tif (cmd == 'M' || cmd == 'm') {\n\t\t\t\t\t// Commit path.\n\t\t\t\t\tif (p->npts > 0)\n\t\t\t\t\t\tnsvg__addPath(p, closedFlag);\n\t\t\t\t\t// Start new subpath.\n\t\t\t\t\tnsvg__resetPath(p);\n\t\t\t\t\tclosedFlag = 0;\n\t\t\t\t\tnargs = 0;\n\t\t\t\t} else if (initPoint == 0) {\n\t\t\t\t\t// Do not allow other commands until initial point has been set (moveTo called once).\n\t\t\t\t\tcmd = '\\0';\n\t\t\t\t}\n\t\t\t\tif (cmd == 'Z' || cmd == 'z') {\n\t\t\t\t\tclosedFlag = 1;\n\t\t\t\t\t// Commit path.\n\t\t\t\t\tif (p->npts > 0) {\n\t\t\t\t\t\t// Move current point to first point\n\t\t\t\t\t\tcpx = p->pts[0];\n\t\t\t\t\t\tcpy = p->pts[1];\n\t\t\t\t\t\tcpx2 = cpx; cpy2 = cpy;\n\t\t\t\t\t\tnsvg__addPath(p, closedFlag);\n\t\t\t\t\t}\n\t\t\t\t\t// Start new subpath.\n\t\t\t\t\tnsvg__resetPath(p);\n\t\t\t\t\tnsvg__moveTo(p, cpx, cpy);\n\t\t\t\t\tclosedFlag = 0;\n\t\t\t\t\tnargs = 0;\n\t\t\t\t}\n\t\t\t\trargs = nsvg__getArgsPerElement(cmd);\n\t\t\t\tif (rargs == -1) {\n\t\t\t\t\t// Command not recognized\n\t\t\t\t\tcmd = '\\0';\n\t\t\t\t\trargs = 0;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\t// Commit path.\n\t\tif (p->npts)\n\t\t\tnsvg__addPath(p, closedFlag);\n\t}\n\n\tnsvg__addShape(p);\n}\n\nstatic void nsvg__parseRect(NSVGparser* p, const char** attr)\n{\n\tfloat x = 0.0f;\n\tfloat y = 0.0f;\n\tfloat w = 0.0f;\n\tfloat h = 0.0f;\n\tfloat rx = -1.0f; // marks not set\n\tfloat ry = -1.0f;\n\tint i;\n\n\tfor (i = 0; attr[i]; i += 2) {\n\t\tif (!nsvg__parseAttr(p, attr[i], attr[i + 1])) {\n\t\t\tif (strcmp(attr[i], \"x\") == 0) x = nsvg__parseCoordinate(p, attr[i+1], nsvg__actualOrigX(p), nsvg__actualWidth(p));\n\t\t\tif (strcmp(attr[i], \"y\") == 0) y = nsvg__parseCoordinate(p, attr[i+1], nsvg__actualOrigY(p), nsvg__actualHeight(p));\n\t\t\tif (strcmp(attr[i], \"width\") == 0) w = nsvg__parseCoordinate(p, attr[i+1], 0.0f, nsvg__actualWidth(p));\n\t\t\tif (strcmp(attr[i], \"height\") == 0) h = nsvg__parseCoordinate(p, attr[i+1], 0.0f, nsvg__actualHeight(p));\n\t\t\tif (strcmp(attr[i], \"rx\") == 0) rx = fabsf(nsvg__parseCoordinate(p, attr[i+1], 0.0f, nsvg__actualWidth(p)));\n\t\t\tif (strcmp(attr[i], \"ry\") == 0) ry = fabsf(nsvg__parseCoordinate(p, attr[i+1], 0.0f, nsvg__actualHeight(p)));\n\t\t}\n\t}\n\n\tif (rx < 0.0f && ry > 0.0f) rx = ry;\n\tif (ry < 0.0f && rx > 0.0f) ry = rx;\n\tif (rx < 0.0f) rx = 0.0f;\n\tif (ry < 0.0f) ry = 0.0f;\n\tif (rx > w/2.0f) rx = w/2.0f;\n\tif (ry > h/2.0f) ry = h/2.0f;\n\n\tif (w != 0.0f && h != 0.0f) {\n\t\tnsvg__resetPath(p);\n\n\t\tif (rx < 0.00001f || ry < 0.0001f) {\n\t\t\tnsvg__moveTo(p, x, y);\n\t\t\tnsvg__lineTo(p, x+w, y);\n\t\t\tnsvg__lineTo(p, x+w, y+h);\n\t\t\tnsvg__lineTo(p, x, y+h);\n\t\t} else {\n\t\t\t// Rounded rectangle\n\t\t\tnsvg__moveTo(p, x+rx, y);\n\t\t\tnsvg__lineTo(p, x+w-rx, y);\n\t\t\tnsvg__cubicBezTo(p, x+w-rx*(1-NSVG_KAPPA90), y, x+w, y+ry*(1-NSVG_KAPPA90), x+w, y+ry);\n\t\t\tnsvg__lineTo(p, x+w, y+h-ry);\n\t\t\tnsvg__cubicBezTo(p, x+w, y+h-ry*(1-NSVG_KAPPA90), x+w-rx*(1-NSVG_KAPPA90), y+h, x+w-rx, y+h);\n\t\t\tnsvg__lineTo(p, x+rx, y+h);\n\t\t\tnsvg__cubicBezTo(p, x+rx*(1-NSVG_KAPPA90), y+h, x, y+h-ry*(1-NSVG_KAPPA90), x, y+h-ry);\n\t\t\tnsvg__lineTo(p, x, y+ry);\n\t\t\tnsvg__cubicBezTo(p, x, y+ry*(1-NSVG_KAPPA90), x+rx*(1-NSVG_KAPPA90), y, x+rx, y);\n\t\t}\n\n\t\tnsvg__addPath(p, 1);\n\n\t\tnsvg__addShape(p);\n\t}\n}\n\nstatic void nsvg__parseCircle(NSVGparser* p, const char** attr)\n{\n\tfloat cx = 0.0f;\n\tfloat cy = 0.0f;\n\tfloat r = 0.0f;\n\tint i;\n\n\tfor (i = 0; attr[i]; i += 2) {\n\t\tif (!nsvg__parseAttr(p, attr[i], attr[i + 1])) {\n\t\t\tif (strcmp(attr[i], \"cx\") == 0) cx = nsvg__parseCoordinate(p, attr[i+1], nsvg__actualOrigX(p), nsvg__actualWidth(p));\n\t\t\tif (strcmp(attr[i], \"cy\") == 0) cy = nsvg__parseCoordinate(p, attr[i+1], nsvg__actualOrigY(p), nsvg__actualHeight(p));\n\t\t\tif (strcmp(attr[i], \"r\") == 0) r = fabsf(nsvg__parseCoordinate(p, attr[i+1], 0.0f, nsvg__actualLength(p)));\n\t\t}\n\t}\n\n\tif (r > 0.0f) {\n\t\tnsvg__resetPath(p);\n\n\t\tnsvg__moveTo(p, cx+r, cy);\n\t\tnsvg__cubicBezTo(p, cx+r, cy+r*NSVG_KAPPA90, cx+r*NSVG_KAPPA90, cy+r, cx, cy+r);\n\t\tnsvg__cubicBezTo(p, cx-r*NSVG_KAPPA90, cy+r, cx-r, cy+r*NSVG_KAPPA90, cx-r, cy);\n\t\tnsvg__cubicBezTo(p, cx-r, cy-r*NSVG_KAPPA90, cx-r*NSVG_KAPPA90, cy-r, cx, cy-r);\n\t\tnsvg__cubicBezTo(p, cx+r*NSVG_KAPPA90, cy-r, cx+r, cy-r*NSVG_KAPPA90, cx+r, cy);\n\n\t\tnsvg__addPath(p, 1);\n\n\t\tnsvg__addShape(p);\n\t}\n}\n\nstatic void nsvg__parseEllipse(NSVGparser* p, const char** attr)\n{\n\tfloat cx = 0.0f;\n\tfloat cy = 0.0f;\n\tfloat rx = 0.0f;\n\tfloat ry = 0.0f;\n\tint i;\n\n\tfor (i = 0; attr[i]; i += 2) {\n\t\tif (!nsvg__parseAttr(p, attr[i], attr[i + 1])) {\n\t\t\tif (strcmp(attr[i], \"cx\") == 0) cx = nsvg__parseCoordinate(p, attr[i+1], nsvg__actualOrigX(p), nsvg__actualWidth(p));\n\t\t\tif (strcmp(attr[i], \"cy\") == 0) cy = nsvg__parseCoordinate(p, attr[i+1], nsvg__actualOrigY(p), nsvg__actualHeight(p));\n\t\t\tif (strcmp(attr[i], \"rx\") == 0) rx = fabsf(nsvg__parseCoordinate(p, attr[i+1], 0.0f, nsvg__actualWidth(p)));\n\t\t\tif (strcmp(attr[i], \"ry\") == 0) ry = fabsf(nsvg__parseCoordinate(p, attr[i+1], 0.0f, nsvg__actualHeight(p)));\n\t\t}\n\t}\n\n\tif (rx > 0.0f && ry > 0.0f) {\n\n\t\tnsvg__resetPath(p);\n\n\t\tnsvg__moveTo(p, cx+rx, cy);\n\t\tnsvg__cubicBezTo(p, cx+rx, cy+ry*NSVG_KAPPA90, cx+rx*NSVG_KAPPA90, cy+ry, cx, cy+ry);\n\t\tnsvg__cubicBezTo(p, cx-rx*NSVG_KAPPA90, cy+ry, cx-rx, cy+ry*NSVG_KAPPA90, cx-rx, cy);\n\t\tnsvg__cubicBezTo(p, cx-rx, cy-ry*NSVG_KAPPA90, cx-rx*NSVG_KAPPA90, cy-ry, cx, cy-ry);\n\t\tnsvg__cubicBezTo(p, cx+rx*NSVG_KAPPA90, cy-ry, cx+rx, cy-ry*NSVG_KAPPA90, cx+rx, cy);\n\n\t\tnsvg__addPath(p, 1);\n\n\t\tnsvg__addShape(p);\n\t}\n}\n\nstatic void nsvg__parseLine(NSVGparser* p, const char** attr)\n{\n\tfloat x1 = 0.0;\n\tfloat y1 = 0.0;\n\tfloat x2 = 0.0;\n\tfloat y2 = 0.0;\n\tint i;\n\n\tfor (i = 0; attr[i]; i += 2) {\n\t\tif (!nsvg__parseAttr(p, attr[i], attr[i + 1])) {\n\t\t\tif (strcmp(attr[i], \"x1\") == 0) x1 = nsvg__parseCoordinate(p, attr[i + 1], nsvg__actualOrigX(p), nsvg__actualWidth(p));\n\t\t\tif (strcmp(attr[i], \"y1\") == 0) y1 = nsvg__parseCoordinate(p, attr[i + 1], nsvg__actualOrigY(p), nsvg__actualHeight(p));\n\t\t\tif (strcmp(attr[i], \"x2\") == 0) x2 = nsvg__parseCoordinate(p, attr[i + 1], nsvg__actualOrigX(p), nsvg__actualWidth(p));\n\t\t\tif (strcmp(attr[i], \"y2\") == 0) y2 = nsvg__parseCoordinate(p, attr[i + 1], nsvg__actualOrigY(p), nsvg__actualHeight(p));\n\t\t}\n\t}\n\n\tnsvg__resetPath(p);\n\n\tnsvg__moveTo(p, x1, y1);\n\tnsvg__lineTo(p, x2, y2);\n\n\tnsvg__addPath(p, 0);\n\n\tnsvg__addShape(p);\n}\n\nstatic void nsvg__parsePoly(NSVGparser* p, const char** attr, int closeFlag)\n{\n\tint i;\n\tconst char* s;\n\tfloat args[2];\n\tint nargs, npts = 0;\n\tchar item[64];\n\n\tnsvg__resetPath(p);\n\n\tfor (i = 0; attr[i]; i += 2) {\n\t\tif (!nsvg__parseAttr(p, attr[i], attr[i + 1])) {\n\t\t\tif (strcmp(attr[i], \"points\") == 0) {\n\t\t\t\ts = attr[i + 1];\n\t\t\t\tnargs = 0;\n\t\t\t\twhile (*s) {\n\t\t\t\t\ts = nsvg__getNextPathItem(s, item);\n\t\t\t\t\targs[nargs++] = (float)nsvg__atof(item);\n\t\t\t\t\tif (nargs >= 2) {\n\t\t\t\t\t\tif (npts == 0)\n\t\t\t\t\t\t\tnsvg__moveTo(p, args[0], args[1]);\n\t\t\t\t\t\telse\n\t\t\t\t\t\t\tnsvg__lineTo(p, args[0], args[1]);\n\t\t\t\t\t\tnargs = 0;\n\t\t\t\t\t\tnpts++;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tnsvg__addPath(p, (char)closeFlag);\n\n\tnsvg__addShape(p);\n}\n\nstatic void nsvg__parseSVG(NSVGparser* p, const char** attr)\n{\n\tint i;\n\tfor (i = 0; attr[i]; i += 2) {\n\t\tif (!nsvg__parseAttr(p, attr[i], attr[i + 1])) {\n\t\t\tif (strcmp(attr[i], \"width\") == 0) {\n\t\t\t\tp->image->width = nsvg__parseCoordinate(p, attr[i + 1], 0.0f, 0.0f);\n\t\t\t} else if (strcmp(attr[i], \"height\") == 0) {\n\t\t\t\tp->image->height = nsvg__parseCoordinate(p, attr[i + 1], 0.0f, 0.0f);\n\t\t\t} else if (strcmp(attr[i], \"viewBox\") == 0) {\n\t\t\t\tconst char *s = attr[i + 1];\n\t\t\t\tchar buf[64];\n\t\t\t\ts = nsvg__parseNumber(s, buf, 64);\n\t\t\t\tp->viewMinx = nsvg__atof(buf);\n\t\t\t\twhile (*s && (nsvg__isspace(*s) || *s == '%' || *s == ',')) s++;\n\t\t\t\tif (!*s) return;\n\t\t\t\ts = nsvg__parseNumber(s, buf, 64);\n\t\t\t\tp->viewMiny = nsvg__atof(buf);\n\t\t\t\twhile (*s && (nsvg__isspace(*s) || *s == '%' || *s == ',')) s++;\n\t\t\t\tif (!*s) return;\n\t\t\t\ts = nsvg__parseNumber(s, buf, 64);\n\t\t\t\tp->viewWidth = nsvg__atof(buf);\n\t\t\t\twhile (*s && (nsvg__isspace(*s) || *s == '%' || *s == ',')) s++;\n\t\t\t\tif (!*s) return;\n\t\t\t\ts = nsvg__parseNumber(s, buf, 64);\n\t\t\t\tp->viewHeight = nsvg__atof(buf);\n\t\t\t} else if (strcmp(attr[i], \"preserveAspectRatio\") == 0) {\n\t\t\t\tif (strstr(attr[i + 1], \"none\") != 0) {\n\t\t\t\t\t// No uniform scaling\n\t\t\t\t\tp->alignType = NSVG_ALIGN_NONE;\n\t\t\t\t} else {\n\t\t\t\t\t// Parse X align\n\t\t\t\t\tif (strstr(attr[i + 1], \"xMin\") != 0)\n\t\t\t\t\t\tp->alignX = NSVG_ALIGN_MIN;\n\t\t\t\t\telse if (strstr(attr[i + 1], \"xMid\") != 0)\n\t\t\t\t\t\tp->alignX = NSVG_ALIGN_MID;\n\t\t\t\t\telse if (strstr(attr[i + 1], \"xMax\") != 0)\n\t\t\t\t\t\tp->alignX = NSVG_ALIGN_MAX;\n\t\t\t\t\t// Parse X align\n\t\t\t\t\tif (strstr(attr[i + 1], \"yMin\") != 0)\n\t\t\t\t\t\tp->alignY = NSVG_ALIGN_MIN;\n\t\t\t\t\telse if (strstr(attr[i + 1], \"yMid\") != 0)\n\t\t\t\t\t\tp->alignY = NSVG_ALIGN_MID;\n\t\t\t\t\telse if (strstr(attr[i + 1], \"yMax\") != 0)\n\t\t\t\t\t\tp->alignY = NSVG_ALIGN_MAX;\n\t\t\t\t\t// Parse meet/slice\n\t\t\t\t\tp->alignType = NSVG_ALIGN_MEET;\n\t\t\t\t\tif (strstr(attr[i + 1], \"slice\") != 0)\n\t\t\t\t\t\tp->alignType = NSVG_ALIGN_SLICE;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\nstatic void nsvg__parseGradient(NSVGparser* p, const char** attr, signed char type)\n{\n\tint i;\n\tNSVGgradientData* grad = (NSVGgradientData*)malloc(sizeof(NSVGgradientData));\n\tif (grad == NULL) return;\n\tmemset(grad, 0, sizeof(NSVGgradientData));\n\tgrad->units = NSVG_OBJECT_SPACE;\n\tgrad->type = type;\n\tif (grad->type == NSVG_PAINT_LINEAR_GRADIENT) {\n\t\tgrad->linear.x1 = nsvg__coord(0.0f, NSVG_UNITS_PERCENT);\n\t\tgrad->linear.y1 = nsvg__coord(0.0f, NSVG_UNITS_PERCENT);\n\t\tgrad->linear.x2 = nsvg__coord(100.0f, NSVG_UNITS_PERCENT);\n\t\tgrad->linear.y2 = nsvg__coord(0.0f, NSVG_UNITS_PERCENT);\n\t} else if (grad->type == NSVG_PAINT_RADIAL_GRADIENT) {\n\t\tgrad->radial.cx = nsvg__coord(50.0f, NSVG_UNITS_PERCENT);\n\t\tgrad->radial.cy = nsvg__coord(50.0f, NSVG_UNITS_PERCENT);\n\t\tgrad->radial.r = nsvg__coord(50.0f, NSVG_UNITS_PERCENT);\n\t}\n\n\tnsvg__xformIdentity(grad->xform);\n\n\tfor (i = 0; attr[i]; i += 2) {\n\t\tif (strcmp(attr[i], \"id\") == 0) {\n\t\t\tstrncpy(grad->id, attr[i+1], 63);\n\t\t\tgrad->id[63] = '\\0';\n\t\t} else if (!nsvg__parseAttr(p, attr[i], attr[i + 1])) {\n\t\t\tif (strcmp(attr[i], \"gradientUnits\") == 0) {\n\t\t\t\tif (strcmp(attr[i+1], \"objectBoundingBox\") == 0)\n\t\t\t\t\tgrad->units = NSVG_OBJECT_SPACE;\n\t\t\t\telse\n\t\t\t\t\tgrad->units = NSVG_USER_SPACE;\n\t\t\t} else if (strcmp(attr[i], \"gradientTransform\") == 0) {\n\t\t\t\tnsvg__parseTransform(grad->xform, attr[i + 1]);\n\t\t\t} else if (strcmp(attr[i], \"cx\") == 0) {\n\t\t\t\tgrad->radial.cx = nsvg__parseCoordinateRaw(attr[i + 1]);\n\t\t\t} else if (strcmp(attr[i], \"cy\") == 0) {\n\t\t\t\tgrad->radial.cy = nsvg__parseCoordinateRaw(attr[i + 1]);\n\t\t\t} else if (strcmp(attr[i], \"r\") == 0) {\n\t\t\t\tgrad->radial.r = nsvg__parseCoordinateRaw(attr[i + 1]);\n\t\t\t} else if (strcmp(attr[i], \"fx\") == 0) {\n\t\t\t\tgrad->radial.fx = nsvg__parseCoordinateRaw(attr[i + 1]);\n\t\t\t} else if (strcmp(attr[i], \"fy\") == 0) {\n\t\t\t\tgrad->radial.fy = nsvg__parseCoordinateRaw(attr[i + 1]);\n\t\t\t} else if (strcmp(attr[i], \"x1\") == 0) {\n\t\t\t\tgrad->linear.x1 = nsvg__parseCoordinateRaw(attr[i + 1]);\n\t\t\t} else if (strcmp(attr[i], \"y1\") == 0) {\n\t\t\t\tgrad->linear.y1 = nsvg__parseCoordinateRaw(attr[i + 1]);\n\t\t\t} else if (strcmp(attr[i], \"x2\") == 0) {\n\t\t\t\tgrad->linear.x2 = nsvg__parseCoordinateRaw(attr[i + 1]);\n\t\t\t} else if (strcmp(attr[i], \"y2\") == 0) {\n\t\t\t\tgrad->linear.y2 = nsvg__parseCoordinateRaw(attr[i + 1]);\n\t\t\t} else if (strcmp(attr[i], \"spreadMethod\") == 0) {\n\t\t\t\tif (strcmp(attr[i+1], \"pad\") == 0)\n\t\t\t\t\tgrad->spread = NSVG_SPREAD_PAD;\n\t\t\t\telse if (strcmp(attr[i+1], \"reflect\") == 0)\n\t\t\t\t\tgrad->spread = NSVG_SPREAD_REFLECT;\n\t\t\t\telse if (strcmp(attr[i+1], \"repeat\") == 0)\n\t\t\t\t\tgrad->spread = NSVG_SPREAD_REPEAT;\n\t\t\t} else if (strcmp(attr[i], \"xlink:href\") == 0) {\n\t\t\t\tconst char *href = attr[i+1];\n\t\t\t\tstrncpy(grad->ref, href+1, 62);\n\t\t\t\tgrad->ref[62] = '\\0';\n\t\t\t}\n\t\t}\n\t}\n\n\tgrad->next = p->gradients;\n\tp->gradients = grad;\n}\n\nstatic void nsvg__parseGradientStop(NSVGparser* p, const char** attr)\n{\n\tNSVGattrib* curAttr = nsvg__getAttr(p);\n\tNSVGgradientData* grad;\n\tNSVGgradientStop* stop;\n\tint i, idx;\n\n\tcurAttr->stopOffset = 0;\n\tcurAttr->stopColor = 0;\n\tcurAttr->stopOpacity = 1.0f;\n\n\tfor (i = 0; attr[i]; i += 2) {\n\t\tnsvg__parseAttr(p, attr[i], attr[i + 1]);\n\t}\n\n\t// Add stop to the last gradient.\n\tgrad = p->gradients;\n\tif (grad == NULL) return;\n\n\tgrad->nstops++;\n\tgrad->stops = (NSVGgradientStop*)realloc(grad->stops, sizeof(NSVGgradientStop)*grad->nstops);\n\tif (grad->stops == NULL) return;\n\n\t// Insert\n\tidx = grad->nstops-1;\n\tfor (i = 0; i < grad->nstops-1; i++) {\n\t\tif (curAttr->stopOffset < grad->stops[i].offset) {\n\t\t\tidx = i;\n\t\t\tbreak;\n\t\t}\n\t}\n\tif (idx != grad->nstops-1) {\n\t\tfor (i = grad->nstops-1; i > idx; i--)\n\t\t\tgrad->stops[i] = grad->stops[i-1];\n\t}\n\n\tstop = &grad->stops[idx];\n\tstop->color = curAttr->stopColor;\n\tstop->color |= (unsigned int)(curAttr->stopOpacity*255) << 24;\n\tstop->offset = curAttr->stopOffset;\n}\n\nstatic void nsvg__startElement(void* ud, const char* el, const char** attr)\n{\n\tNSVGparser* p = (NSVGparser*)ud;\n\n\tif (p->defsFlag) {\n\t\t// Skip everything but gradients in defs\n\t\tif (strcmp(el, \"linearGradient\") == 0) {\n\t\t\tnsvg__parseGradient(p, attr, NSVG_PAINT_LINEAR_GRADIENT);\n\t\t} else if (strcmp(el, \"radialGradient\") == 0) {\n\t\t\tnsvg__parseGradient(p, attr, NSVG_PAINT_RADIAL_GRADIENT);\n\t\t} else if (strcmp(el, \"stop\") == 0) {\n\t\t\tnsvg__parseGradientStop(p, attr);\n\t\t}\n\t\treturn;\n\t}\n\n\tif (strcmp(el, \"g\") == 0) {\n\t\tnsvg__pushAttr(p);\n\t\tnsvg__parseAttribs(p, attr);\n\t} else if (strcmp(el, \"path\") == 0) {\n\t\tif (p->pathFlag)\t// Do not allow nested paths.\n\t\t\treturn;\n\t\tnsvg__pushAttr(p);\n\t\tnsvg__parsePath(p, attr);\n\t\tnsvg__popAttr(p);\n\t} else if (strcmp(el, \"rect\") == 0) {\n\t\tnsvg__pushAttr(p);\n\t\tnsvg__parseRect(p, attr);\n\t\tnsvg__popAttr(p);\n\t} else if (strcmp(el, \"circle\") == 0) {\n\t\tnsvg__pushAttr(p);\n\t\tnsvg__parseCircle(p, attr);\n\t\tnsvg__popAttr(p);\n\t} else if (strcmp(el, \"ellipse\") == 0) {\n\t\tnsvg__pushAttr(p);\n\t\tnsvg__parseEllipse(p, attr);\n\t\tnsvg__popAttr(p);\n\t} else if (strcmp(el, \"line\") == 0)  {\n\t\tnsvg__pushAttr(p);\n\t\tnsvg__parseLine(p, attr);\n\t\tnsvg__popAttr(p);\n\t} else if (strcmp(el, \"polyline\") == 0)  {\n\t\tnsvg__pushAttr(p);\n\t\tnsvg__parsePoly(p, attr, 0);\n\t\tnsvg__popAttr(p);\n\t} else if (strcmp(el, \"polygon\") == 0)  {\n\t\tnsvg__pushAttr(p);\n\t\tnsvg__parsePoly(p, attr, 1);\n\t\tnsvg__popAttr(p);\n\t} else  if (strcmp(el, \"linearGradient\") == 0) {\n\t\tnsvg__parseGradient(p, attr, NSVG_PAINT_LINEAR_GRADIENT);\n\t} else if (strcmp(el, \"radialGradient\") == 0) {\n\t\tnsvg__parseGradient(p, attr, NSVG_PAINT_RADIAL_GRADIENT);\n\t} else if (strcmp(el, \"stop\") == 0) {\n\t\tnsvg__parseGradientStop(p, attr);\n\t} else if (strcmp(el, \"defs\") == 0) {\n\t\tp->defsFlag = 1;\n\t} else if (strcmp(el, \"svg\") == 0) {\n\t\tnsvg__parseSVG(p, attr);\n\t}\n}\n\nstatic void nsvg__endElement(void* ud, const char* el)\n{\n\tNSVGparser* p = (NSVGparser*)ud;\n\n\tif (strcmp(el, \"g\") == 0) {\n\t\tnsvg__popAttr(p);\n\t} else if (strcmp(el, \"path\") == 0) {\n\t\tp->pathFlag = 0;\n\t} else if (strcmp(el, \"defs\") == 0) {\n\t\tp->defsFlag = 0;\n\t}\n}\n\nstatic void nsvg__content(void* ud, const char* s)\n{\n\tNSVG_NOTUSED(ud);\n\tNSVG_NOTUSED(s);\n\t// empty\n}\n\nstatic void nsvg__imageBounds(NSVGparser* p, float* bounds)\n{\n\tNSVGshape* shape;\n\tshape = p->image->shapes;\n\tif (shape == NULL) {\n\t\tbounds[0] = bounds[1] = bounds[2] = bounds[3] = 0.0;\n\t\treturn;\n\t}\n\tbounds[0] = shape->bounds[0];\n\tbounds[1] = shape->bounds[1];\n\tbounds[2] = shape->bounds[2];\n\tbounds[3] = shape->bounds[3];\n\tfor (shape = shape->next; shape != NULL; shape = shape->next) {\n\t\tbounds[0] = nsvg__minf(bounds[0], shape->bounds[0]);\n\t\tbounds[1] = nsvg__minf(bounds[1], shape->bounds[1]);\n\t\tbounds[2] = nsvg__maxf(bounds[2], shape->bounds[2]);\n\t\tbounds[3] = nsvg__maxf(bounds[3], shape->bounds[3]);\n\t}\n}\n\nstatic float nsvg__viewAlign(float content, float container, int type)\n{\n\tif (type == NSVG_ALIGN_MIN)\n\t\treturn 0;\n\telse if (type == NSVG_ALIGN_MAX)\n\t\treturn container - content;\n\t// mid\n\treturn (container - content) * 0.5f;\n}\n\nstatic void nsvg__scaleGradient(NSVGgradient* grad, float tx, float ty, float sx, float sy)\n{\n\tfloat t[6];\n\tnsvg__xformSetTranslation(t, tx, ty);\n\tnsvg__xformMultiply (grad->xform, t);\n\n\tnsvg__xformSetScale(t, sx, sy);\n\tnsvg__xformMultiply (grad->xform, t);\n}\n\nstatic void nsvg__scaleToViewbox(NSVGparser* p, const char* units)\n{\n\tNSVGshape* shape;\n\tNSVGpath* path;\n\tfloat tx, ty, sx, sy, us, bounds[4], t[6], avgs;\n\tint i;\n\tfloat* pt;\n\n\t// Guess image size if not set completely.\n\tnsvg__imageBounds(p, bounds);\n\n\tif (p->viewWidth == 0) {\n\t\tif (p->image->width > 0) {\n\t\t\tp->viewWidth = p->image->width;\n\t\t} else {\n\t\t\tp->viewMinx = bounds[0];\n\t\t\tp->viewWidth = bounds[2] - bounds[0];\n\t\t}\n\t}\n\tif (p->viewHeight == 0) {\n\t\tif (p->image->height > 0) {\n\t\t\tp->viewHeight = p->image->height;\n\t\t} else {\n\t\t\tp->viewMiny = bounds[1];\n\t\t\tp->viewHeight = bounds[3] - bounds[1];\n\t\t}\n\t}\n\tif (p->image->width == 0)\n\t\tp->image->width = p->viewWidth;\n\tif (p->image->height == 0)\n\t\tp->image->height = p->viewHeight;\n\n\ttx = -p->viewMinx;\n\tty = -p->viewMiny;\n\tsx = p->viewWidth > 0 ? p->image->width / p->viewWidth : 0;\n\tsy = p->viewHeight > 0 ? p->image->height / p->viewHeight : 0;\n\t// Unit scaling\n\tus = 1.0f / nsvg__convertToPixels(p, nsvg__coord(1.0f, nsvg__parseUnits(units)), 0.0f, 1.0f);\n\n\t// Fix aspect ratio\n\tif (p->alignType == NSVG_ALIGN_MEET) {\n\t\t// fit whole image into viewbox\n\t\tsx = sy = nsvg__minf(sx, sy);\n\t\ttx += nsvg__viewAlign(p->viewWidth*sx, p->image->width, p->alignX) / sx;\n\t\tty += nsvg__viewAlign(p->viewHeight*sy, p->image->height, p->alignY) / sy;\n\t} else if (p->alignType == NSVG_ALIGN_SLICE) {\n\t\t// fill whole viewbox with image\n\t\tsx = sy = nsvg__maxf(sx, sy);\n\t\ttx += nsvg__viewAlign(p->viewWidth*sx, p->image->width, p->alignX) / sx;\n\t\tty += nsvg__viewAlign(p->viewHeight*sy, p->image->height, p->alignY) / sy;\n\t}\n\n\t// Transform\n\tsx *= us;\n\tsy *= us;\n\tavgs = (sx+sy) / 2.0f;\n\tfor (shape = p->image->shapes; shape != NULL; shape = shape->next) {\n\t\tshape->bounds[0] = (shape->bounds[0] + tx) * sx;\n\t\tshape->bounds[1] = (shape->bounds[1] + ty) * sy;\n\t\tshape->bounds[2] = (shape->bounds[2] + tx) * sx;\n\t\tshape->bounds[3] = (shape->bounds[3] + ty) * sy;\n\t\tfor (path = shape->paths; path != NULL; path = path->next) {\n\t\t\tpath->bounds[0] = (path->bounds[0] + tx) * sx;\n\t\t\tpath->bounds[1] = (path->bounds[1] + ty) * sy;\n\t\t\tpath->bounds[2] = (path->bounds[2] + tx) * sx;\n\t\t\tpath->bounds[3] = (path->bounds[3] + ty) * sy;\n\t\t\tfor (i =0; i < path->npts; i++) {\n\t\t\t\tpt = &path->pts[i*2];\n\t\t\t\tpt[0] = (pt[0] + tx) * sx;\n\t\t\t\tpt[1] = (pt[1] + ty) * sy;\n\t\t\t}\n\t\t}\n\n\t\tif (shape->fill.type == NSVG_PAINT_LINEAR_GRADIENT || shape->fill.type == NSVG_PAINT_RADIAL_GRADIENT) {\n\t\t\tnsvg__scaleGradient(shape->fill.gradient, tx,ty, sx,sy);\n\t\t\tmemcpy(t, shape->fill.gradient->xform, sizeof(float)*6);\n\t\t\tnsvg__xformInverse(shape->fill.gradient->xform, t);\n\t\t}\n\t\tif (shape->stroke.type == NSVG_PAINT_LINEAR_GRADIENT || shape->stroke.type == NSVG_PAINT_RADIAL_GRADIENT) {\n\t\t\tnsvg__scaleGradient(shape->stroke.gradient, tx,ty, sx,sy);\n\t\t\tmemcpy(t, shape->stroke.gradient->xform, sizeof(float)*6);\n\t\t\tnsvg__xformInverse(shape->stroke.gradient->xform, t);\n\t\t}\n\n\t\tshape->strokeWidth *= avgs;\n\t\tshape->strokeDashOffset *= avgs;\n\t\tfor (i = 0; i < shape->strokeDashCount; i++)\n\t\t\tshape->strokeDashArray[i] *= avgs;\n\t}\n}\n\nstatic void nsvg__createGradients(NSVGparser* p)\n{\n\tNSVGshape* shape;\n\n\tfor (shape = p->image->shapes; shape != NULL; shape = shape->next) {\n\t\tif (shape->fill.type == NSVG_PAINT_UNDEF) {\n\t\t\tif (shape->fillGradient[0] != '\\0') {\n\t\t\t\tfloat inv[6], localBounds[4];\n\t\t\t\tnsvg__xformInverse(inv, shape->xform);\n\t\t\t\tnsvg__getLocalBounds(localBounds, shape, inv);\n\t\t\t\tshape->fill.gradient = nsvg__createGradient(p, shape->fillGradient, localBounds, shape->xform, &shape->fill.type);\n\t\t\t}\n\t\t\tif (shape->fill.type == NSVG_PAINT_UNDEF) {\n\t\t\t\tshape->fill.type = NSVG_PAINT_NONE;\n\t\t\t}\n\t\t}\n\t\tif (shape->stroke.type == NSVG_PAINT_UNDEF) {\n\t\t\tif (shape->strokeGradient[0] != '\\0') {\n\t\t\t\tfloat inv[6], localBounds[4];\n\t\t\t\tnsvg__xformInverse(inv, shape->xform);\n\t\t\t\tnsvg__getLocalBounds(localBounds, shape, inv);\n\t\t\t\tshape->stroke.gradient = nsvg__createGradient(p, shape->strokeGradient, localBounds, shape->xform, &shape->stroke.type);\n\t\t\t}\n\t\t\tif (shape->stroke.type == NSVG_PAINT_UNDEF) {\n\t\t\t\tshape->stroke.type = NSVG_PAINT_NONE;\n\t\t\t}\n\t\t}\n\t}\n}\n\nNSVGimage* nsvgParse(char* input, const char* units, float dpi)\n{\n\tNSVGparser* p;\n\tNSVGimage* ret = 0;\n\n\tp = nsvg__createParser();\n\tif (p == NULL) {\n\t\treturn NULL;\n\t}\n\tp->dpi = dpi;\n\n\tnsvg__parseXML(input, nsvg__startElement, nsvg__endElement, nsvg__content, p);\n\n\t// Create gradients after all definitions have been parsed\n\tnsvg__createGradients(p);\n\n\t// Scale to viewBox\n\tnsvg__scaleToViewbox(p, units);\n\n\tret = p->image;\n\tp->image = NULL;\n\n\tnsvg__deleteParser(p);\n\n\treturn ret;\n}\n\nNSVGimage* nsvgParseFromFile(const char* filename, const char* units, float dpi)\n{\n\tFILE* fp = NULL;\n\tsize_t size;\n\tchar* data = NULL;\n\tNSVGimage* image = NULL;\n\n\tfp = fopen(filename, \"rb\");\n\tif (!fp) goto error;\n\tfseek(fp, 0, SEEK_END);\n\tsize = ftell(fp);\n\tfseek(fp, 0, SEEK_SET);\n\tdata = (char*)malloc(size+1);\n\tif (data == NULL) goto error;\n\tif (fread(data, 1, size, fp) != size) goto error;\n\tdata[size] = '\\0';\t// Must be null terminated.\n\tfclose(fp);\n\timage = nsvgParse(data, units, dpi);\n\tfree(data);\n\n\treturn image;\n\nerror:\n\tif (fp) fclose(fp);\n\tif (data) free(data);\n\tif (image) nsvgDelete(image);\n\treturn NULL;\n}\n\nNSVGpath* nsvgDuplicatePath(NSVGpath* p)\n{\n    NSVGpath* res = NULL;\n\n    if (p == NULL)\n        return NULL;\n\n    res = (NSVGpath*)malloc(sizeof(NSVGpath));\n    if (res == NULL) goto error;\n    memset(res, 0, sizeof(NSVGpath));\n\n    res->pts = (float*)malloc(p->npts*2*sizeof(float));\n    if (res->pts == NULL) goto error;\n    memcpy(res->pts, p->pts, p->npts * sizeof(float) * 2);\n    res->npts = p->npts;\n\n    memcpy(res->bounds, p->bounds, sizeof(p->bounds));\n\n    res->closed = p->closed;\n\n    return res;\n\nerror:\n    if (res != NULL) {\n        free(res->pts);\n        free(res);\n    }\n    return NULL;\n}\n\nvoid nsvgDelete(NSVGimage* image)\n{\n\tNSVGshape *snext, *shape;\n\tif (image == NULL) return;\n\tshape = image->shapes;\n\twhile (shape != NULL) {\n\t\tsnext = shape->next;\n\t\tnsvg__deletePaths(shape->paths);\n\t\tnsvg__deletePaint(&shape->fill);\n\t\tnsvg__deletePaint(&shape->stroke);\n\t\tfree(shape);\n\t\tshape = snext;\n\t}\n\tfree(image);\n}\n\n#endif // NANOSVG_IMPLEMENTATION\n\n#endif // NANOSVG_H\n"
  },
  {
    "path": "external/nanosvgrast.h",
    "content": "/*\n * Copyright (c) 2013-14 Mikko Mononen memon@inside.org\n *\n * This software is provided 'as-is', without any express or implied\n * warranty.  In no event will the authors be held liable for any damages\n * arising from the use of this software.\n *\n * Permission is granted to anyone to use this software for any purpose,\n * including commercial applications, and to alter it and redistribute it\n * freely, subject to the following restrictions:\n *\n * 1. The origin of this software must not be misrepresented; you must not\n * claim that you wrote the original software. If you use this software\n * in a product, an acknowledgment in the product documentation would be\n * appreciated but is not required.\n * 2. Altered source versions must be plainly marked as such, and must not be\n * misrepresented as being the original software.\n * 3. This notice may not be removed or altered from any source distribution.\n *\n * The polygon rasterization is heavily based on stb_truetype rasterizer\n * by Sean Barrett - http://nothings.org/\n *\n */\n\n#ifndef NANOSVGRAST_H\n#define NANOSVGRAST_H\n\n#include \"nanosvg.h\"\n\n#ifndef NANOSVGRAST_CPLUSPLUS\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n#endif\n\ntypedef struct NSVGrasterizer NSVGrasterizer;\n\n/* Example Usage:\n\t// Load SVG\n\tNSVGimage* image;\n\timage = nsvgParseFromFile(\"test.svg\", \"px\", 96);\n\n\t// Create rasterizer (can be used to render multiple images).\n\tstruct NSVGrasterizer* rast = nsvgCreateRasterizer();\n\t// Allocate memory for image\n\tunsigned char* img = malloc(w*h*4);\n\t// Rasterize\n\tnsvgRasterize(rast, image, 0,0,1, img, w, h, w*4);\n*/\n\n// Allocated rasterizer context.\nNSVGrasterizer* nsvgCreateRasterizer(void);\n\n// Rasterizes SVG image, returns RGBA image (non-premultiplied alpha)\n//   r - pointer to rasterizer context\n//   image - pointer to image to rasterize\n//   tx,ty - image offset (applied after scaling)\n//   scale - image scale\n//   dst - pointer to destination image data, 4 bytes per pixel (RGBA)\n//   w - width of the image to render\n//   h - height of the image to render\n//   stride - number of bytes per scaleline in the destination buffer\nvoid nsvgRasterize(NSVGrasterizer* r,\n\t\t\t\t   NSVGimage* image, float tx, float ty, float scale,\n\t\t\t\t   unsigned char* dst, int w, int h, int stride);\n\n// Deletes rasterizer context.\nvoid nsvgDeleteRasterizer(NSVGrasterizer*);\n\n\n#ifndef NANOSVGRAST_CPLUSPLUS\n#ifdef __cplusplus\n}\n#endif\n#endif\n\n#ifdef NANOSVGRAST_IMPLEMENTATION\n\n#include <math.h>\n#include <stdlib.h>\n#include <string.h>\n\n#define NSVG__SUBSAMPLES\t5\n#define NSVG__FIXSHIFT\t\t10\n#define NSVG__FIX\t\t\t(1 << NSVG__FIXSHIFT)\n#define NSVG__FIXMASK\t\t(NSVG__FIX-1)\n#define NSVG__MEMPAGE_SIZE\t1024\n\ntypedef struct NSVGedge {\n\tfloat x0,y0, x1,y1;\n\tint dir;\n\tstruct NSVGedge* next;\n} NSVGedge;\n\ntypedef struct NSVGpoint {\n\tfloat x, y;\n\tfloat dx, dy;\n\tfloat len;\n\tfloat dmx, dmy;\n\tunsigned char flags;\n} NSVGpoint;\n\ntypedef struct NSVGactiveEdge {\n\tint x,dx;\n\tfloat ey;\n\tint dir;\n\tstruct NSVGactiveEdge *next;\n} NSVGactiveEdge;\n\ntypedef struct NSVGmemPage {\n\tunsigned char mem[NSVG__MEMPAGE_SIZE];\n\tint size;\n\tstruct NSVGmemPage* next;\n} NSVGmemPage;\n\ntypedef struct NSVGcachedPaint {\n\tsigned char type;\n\tchar spread;\n\tfloat xform[6];\n\tunsigned int colors[256];\n} NSVGcachedPaint;\n\nstruct NSVGrasterizer\n{\n\tfloat px, py;\n\n\tfloat tessTol;\n\tfloat distTol;\n\n\tNSVGedge* edges;\n\tint nedges;\n\tint cedges;\n\n\tNSVGpoint* points;\n\tint npoints;\n\tint cpoints;\n\n\tNSVGpoint* points2;\n\tint npoints2;\n\tint cpoints2;\n\n\tNSVGactiveEdge* freelist;\n\tNSVGmemPage* pages;\n\tNSVGmemPage* curpage;\n\n\tunsigned char* scanline;\n\tint cscanline;\n\n\tunsigned char* bitmap;\n\tint width, height, stride;\n};\n\nNSVGrasterizer* nsvgCreateRasterizer(void)\n{\n\tNSVGrasterizer* r = (NSVGrasterizer*)malloc(sizeof(NSVGrasterizer));\n\tif (r == NULL) goto error;\n\tmemset(r, 0, sizeof(NSVGrasterizer));\n\n\tr->tessTol = 0.25f;\n\tr->distTol = 0.01f;\n\n\treturn r;\n\nerror:\n\tnsvgDeleteRasterizer(r);\n\treturn NULL;\n}\n\nvoid nsvgDeleteRasterizer(NSVGrasterizer* r)\n{\n\tNSVGmemPage* p;\n\n\tif (r == NULL) return;\n\n\tp = r->pages;\n\twhile (p != NULL) {\n\t\tNSVGmemPage* next = p->next;\n\t\tfree(p);\n\t\tp = next;\n\t}\n\n\tif (r->edges) free(r->edges);\n\tif (r->points) free(r->points);\n\tif (r->points2) free(r->points2);\n\tif (r->scanline) free(r->scanline);\n\n\tfree(r);\n}\n\nstatic NSVGmemPage* nsvg__nextPage(NSVGrasterizer* r, NSVGmemPage* cur)\n{\n\tNSVGmemPage *newp;\n\n\t// If using existing chain, return the next page in chain\n\tif (cur != NULL && cur->next != NULL) {\n\t\treturn cur->next;\n\t}\n\n\t// Alloc new page\n\tnewp = (NSVGmemPage*)malloc(sizeof(NSVGmemPage));\n\tif (newp == NULL) return NULL;\n\tmemset(newp, 0, sizeof(NSVGmemPage));\n\n\t// Add to linked list\n\tif (cur != NULL)\n\t\tcur->next = newp;\n\telse\n\t\tr->pages = newp;\n\n\treturn newp;\n}\n\nstatic void nsvg__resetPool(NSVGrasterizer* r)\n{\n\tNSVGmemPage* p = r->pages;\n\twhile (p != NULL) {\n\t\tp->size = 0;\n\t\tp = p->next;\n\t}\n\tr->curpage = r->pages;\n}\n\nstatic unsigned char* nsvg__alloc(NSVGrasterizer* r, int size)\n{\n\tunsigned char* buf;\n\tif (size > NSVG__MEMPAGE_SIZE) return NULL;\n\tif (r->curpage == NULL || r->curpage->size+size > NSVG__MEMPAGE_SIZE) {\n\t\tr->curpage = nsvg__nextPage(r, r->curpage);\n\t}\n\tbuf = &r->curpage->mem[r->curpage->size];\n\tr->curpage->size += size;\n\treturn buf;\n}\n\nstatic int nsvg__ptEquals(float x1, float y1, float x2, float y2, float tol)\n{\n\tfloat dx = x2 - x1;\n\tfloat dy = y2 - y1;\n\treturn dx*dx + dy*dy < tol*tol;\n}\n\nstatic void nsvg__addPathPoint(NSVGrasterizer* r, float x, float y, int flags)\n{\n\tNSVGpoint* pt;\n\n\tif (r->npoints > 0) {\n\t\tpt = &r->points[r->npoints-1];\n\t\tif (nsvg__ptEquals(pt->x,pt->y, x,y, r->distTol)) {\n\t\t\tpt->flags = (unsigned char)(pt->flags | flags);\n\t\t\treturn;\n\t\t}\n\t}\n\n\tif (r->npoints+1 > r->cpoints) {\n\t\tr->cpoints = r->cpoints > 0 ? r->cpoints * 2 : 64;\n\t\tr->points = (NSVGpoint*)realloc(r->points, sizeof(NSVGpoint) * r->cpoints);\n\t\tif (r->points == NULL) return;\n\t}\n\n\tpt = &r->points[r->npoints];\n\tpt->x = x;\n\tpt->y = y;\n\tpt->flags = (unsigned char)flags;\n\tr->npoints++;\n}\n\nstatic void nsvg__appendPathPoint(NSVGrasterizer* r, NSVGpoint pt)\n{\n\tif (r->npoints+1 > r->cpoints) {\n\t\tr->cpoints = r->cpoints > 0 ? r->cpoints * 2 : 64;\n\t\tr->points = (NSVGpoint*)realloc(r->points, sizeof(NSVGpoint) * r->cpoints);\n\t\tif (r->points == NULL) return;\n\t}\n\tr->points[r->npoints] = pt;\n\tr->npoints++;\n}\n\nstatic void nsvg__duplicatePoints(NSVGrasterizer* r)\n{\n\tif (r->npoints > r->cpoints2) {\n\t\tr->cpoints2 = r->npoints;\n\t\tr->points2 = (NSVGpoint*)realloc(r->points2, sizeof(NSVGpoint) * r->cpoints2);\n\t\tif (r->points2 == NULL) return;\n\t}\n\n\tmemcpy(r->points2, r->points, sizeof(NSVGpoint) * r->npoints);\n\tr->npoints2 = r->npoints;\n}\n\nstatic void nsvg__addEdge(NSVGrasterizer* r, float x0, float y0, float x1, float y1)\n{\n\tNSVGedge* e;\n\n\t// Skip horizontal edges\n\tif (y0 == y1)\n\t\treturn;\n\n\tif (r->nedges+1 > r->cedges) {\n\t\tr->cedges = r->cedges > 0 ? r->cedges * 2 : 64;\n\t\tr->edges = (NSVGedge*)realloc(r->edges, sizeof(NSVGedge) * r->cedges);\n\t\tif (r->edges == NULL) return;\n\t}\n\n\te = &r->edges[r->nedges];\n\tr->nedges++;\n\n\tif (y0 < y1) {\n\t\te->x0 = x0;\n\t\te->y0 = y0;\n\t\te->x1 = x1;\n\t\te->y1 = y1;\n\t\te->dir = 1;\n\t} else {\n\t\te->x0 = x1;\n\t\te->y0 = y1;\n\t\te->x1 = x0;\n\t\te->y1 = y0;\n\t\te->dir = -1;\n\t}\n}\n\nstatic float nsvg__normalize(float *x, float* y)\n{\n\tfloat d = sqrtf((*x)*(*x) + (*y)*(*y));\n\tif (d > 1e-6f) {\n\t\tfloat id = 1.0f / d;\n\t\t*x *= id;\n\t\t*y *= id;\n\t}\n\treturn d;\n}\n\nstatic float nsvg__absf(float x) { return x < 0 ? -x : x; }\nstatic float nsvg__roundf(float x) { return (x >= 0) ? floorf(x + 0.5) : ceilf(x - 0.5); }\n\nstatic void nsvg__flattenCubicBez(NSVGrasterizer* r,\n\t\t\t\t\t\t\t\t  float x1, float y1, float x2, float y2,\n\t\t\t\t\t\t\t\t  float x3, float y3, float x4, float y4,\n\t\t\t\t\t\t\t\t  int level, int type)\n{\n\tfloat x12,y12,x23,y23,x34,y34,x123,y123,x234,y234,x1234,y1234;\n\tfloat dx,dy,d2,d3;\n\n\tif (level > 10) return;\n\n\tx12 = (x1+x2)*0.5f;\n\ty12 = (y1+y2)*0.5f;\n\tx23 = (x2+x3)*0.5f;\n\ty23 = (y2+y3)*0.5f;\n\tx34 = (x3+x4)*0.5f;\n\ty34 = (y3+y4)*0.5f;\n\tx123 = (x12+x23)*0.5f;\n\ty123 = (y12+y23)*0.5f;\n\n\tdx = x4 - x1;\n\tdy = y4 - y1;\n\td2 = nsvg__absf((x2 - x4) * dy - (y2 - y4) * dx);\n\td3 = nsvg__absf((x3 - x4) * dy - (y3 - y4) * dx);\n\n\tif ((d2 + d3)*(d2 + d3) < r->tessTol * (dx*dx + dy*dy)) {\n\t\tnsvg__addPathPoint(r, x4, y4, type);\n\t\treturn;\n\t}\n\n\tx234 = (x23+x34)*0.5f;\n\ty234 = (y23+y34)*0.5f;\n\tx1234 = (x123+x234)*0.5f;\n\ty1234 = (y123+y234)*0.5f;\n\n\tnsvg__flattenCubicBez(r, x1,y1, x12,y12, x123,y123, x1234,y1234, level+1, 0);\n\tnsvg__flattenCubicBez(r, x1234,y1234, x234,y234, x34,y34, x4,y4, level+1, type);\n}\n\nstatic void nsvg__flattenShape(NSVGrasterizer* r, NSVGshape* shape, float scale)\n{\n\tint i, j;\n\tNSVGpath* path;\n\n\tfor (path = shape->paths; path != NULL; path = path->next) {\n\t\tr->npoints = 0;\n\t\t// Flatten path\n\t\tnsvg__addPathPoint(r, path->pts[0]*scale, path->pts[1]*scale, 0);\n\t\tfor (i = 0; i < path->npts-1; i += 3) {\n\t\t\tfloat* p = &path->pts[i*2];\n\t\t\tnsvg__flattenCubicBez(r, p[0]*scale,p[1]*scale, p[2]*scale,p[3]*scale, p[4]*scale,p[5]*scale, p[6]*scale,p[7]*scale, 0, 0);\n\t\t}\n\t\t// Close path\n\t\tnsvg__addPathPoint(r, path->pts[0]*scale, path->pts[1]*scale, 0);\n\t\t// Build edges\n\t\tfor (i = 0, j = r->npoints-1; i < r->npoints; j = i++)\n\t\t\tnsvg__addEdge(r, r->points[j].x, r->points[j].y, r->points[i].x, r->points[i].y);\n\t}\n}\n\nenum NSVGpointFlags\n{\n\tNSVG_PT_CORNER = 0x01,\n\tNSVG_PT_BEVEL = 0x02,\n\tNSVG_PT_LEFT = 0x04\n};\n\nstatic void nsvg__initClosed(NSVGpoint* left, NSVGpoint* right, NSVGpoint* p0, NSVGpoint* p1, float lineWidth)\n{\n\tfloat w = lineWidth * 0.5f;\n\tfloat dx = p1->x - p0->x;\n\tfloat dy = p1->y - p0->y;\n\tfloat len = nsvg__normalize(&dx, &dy);\n\tfloat px = p0->x + dx*len*0.5f, py = p0->y + dy*len*0.5f;\n\tfloat dlx = dy, dly = -dx;\n\tfloat lx = px - dlx*w, ly = py - dly*w;\n\tfloat rx = px + dlx*w, ry = py + dly*w;\n\tleft->x = lx; left->y = ly;\n\tright->x = rx; right->y = ry;\n}\n\nstatic void nsvg__buttCap(NSVGrasterizer* r, NSVGpoint* left, NSVGpoint* right, NSVGpoint* p, float dx, float dy, float lineWidth, int connect)\n{\n\tfloat w = lineWidth * 0.5f;\n\tfloat px = p->x, py = p->y;\n\tfloat dlx = dy, dly = -dx;\n\tfloat lx = px - dlx*w, ly = py - dly*w;\n\tfloat rx = px + dlx*w, ry = py + dly*w;\n\n\tnsvg__addEdge(r, lx, ly, rx, ry);\n\n\tif (connect) {\n\t\tnsvg__addEdge(r, left->x, left->y, lx, ly);\n\t\tnsvg__addEdge(r, rx, ry, right->x, right->y);\n\t}\n\tleft->x = lx; left->y = ly;\n\tright->x = rx; right->y = ry;\n}\n\nstatic void nsvg__squareCap(NSVGrasterizer* r, NSVGpoint* left, NSVGpoint* right, NSVGpoint* p, float dx, float dy, float lineWidth, int connect)\n{\n\tfloat w = lineWidth * 0.5f;\n\tfloat px = p->x - dx*w, py = p->y - dy*w;\n\tfloat dlx = dy, dly = -dx;\n\tfloat lx = px - dlx*w, ly = py - dly*w;\n\tfloat rx = px + dlx*w, ry = py + dly*w;\n\n\tnsvg__addEdge(r, lx, ly, rx, ry);\n\n\tif (connect) {\n\t\tnsvg__addEdge(r, left->x, left->y, lx, ly);\n\t\tnsvg__addEdge(r, rx, ry, right->x, right->y);\n\t}\n\tleft->x = lx; left->y = ly;\n\tright->x = rx; right->y = ry;\n}\n\n#ifndef NSVG_PI\n#define NSVG_PI (3.14159265358979323846264338327f)\n#endif\n\nstatic void nsvg__roundCap(NSVGrasterizer* r, NSVGpoint* left, NSVGpoint* right, NSVGpoint* p, float dx, float dy, float lineWidth, int ncap, int connect)\n{\n\tint i;\n\tfloat w = lineWidth * 0.5f;\n\tfloat px = p->x, py = p->y;\n\tfloat dlx = dy, dly = -dx;\n\tfloat lx = 0, ly = 0, rx = 0, ry = 0, prevx = 0, prevy = 0;\n\n\tfor (i = 0; i < ncap; i++) {\n\t\tfloat a = (float)i/(float)(ncap-1)*NSVG_PI;\n\t\tfloat ax = cosf(a) * w, ay = sinf(a) * w;\n\t\tfloat x = px - dlx*ax - dx*ay;\n\t\tfloat y = py - dly*ax - dy*ay;\n\n\t\tif (i > 0)\n\t\t\tnsvg__addEdge(r, prevx, prevy, x, y);\n\n\t\tprevx = x;\n\t\tprevy = y;\n\n\t\tif (i == 0) {\n\t\t\tlx = x; ly = y;\n\t\t} else if (i == ncap-1) {\n\t\t\trx = x; ry = y;\n\t\t}\n\t}\n\n\tif (connect) {\n\t\tnsvg__addEdge(r, left->x, left->y, lx, ly);\n\t\tnsvg__addEdge(r, rx, ry, right->x, right->y);\n\t}\n\n\tleft->x = lx; left->y = ly;\n\tright->x = rx; right->y = ry;\n}\n\nstatic void nsvg__bevelJoin(NSVGrasterizer* r, NSVGpoint* left, NSVGpoint* right, NSVGpoint* p0, NSVGpoint* p1, float lineWidth)\n{\n\tfloat w = lineWidth * 0.5f;\n\tfloat dlx0 = p0->dy, dly0 = -p0->dx;\n\tfloat dlx1 = p1->dy, dly1 = -p1->dx;\n\tfloat lx0 = p1->x - (dlx0 * w), ly0 = p1->y - (dly0 * w);\n\tfloat rx0 = p1->x + (dlx0 * w), ry0 = p1->y + (dly0 * w);\n\tfloat lx1 = p1->x - (dlx1 * w), ly1 = p1->y - (dly1 * w);\n\tfloat rx1 = p1->x + (dlx1 * w), ry1 = p1->y + (dly1 * w);\n\n\tnsvg__addEdge(r, lx0, ly0, left->x, left->y);\n\tnsvg__addEdge(r, lx1, ly1, lx0, ly0);\n\n\tnsvg__addEdge(r, right->x, right->y, rx0, ry0);\n\tnsvg__addEdge(r, rx0, ry0, rx1, ry1);\n\n\tleft->x = lx1; left->y = ly1;\n\tright->x = rx1; right->y = ry1;\n}\n\nstatic void nsvg__miterJoin(NSVGrasterizer* r, NSVGpoint* left, NSVGpoint* right, NSVGpoint* p0, NSVGpoint* p1, float lineWidth)\n{\n\tfloat w = lineWidth * 0.5f;\n\tfloat dlx0 = p0->dy, dly0 = -p0->dx;\n\tfloat dlx1 = p1->dy, dly1 = -p1->dx;\n\tfloat lx0, rx0, lx1, rx1;\n\tfloat ly0, ry0, ly1, ry1;\n\n\tif (p1->flags & NSVG_PT_LEFT) {\n\t\tlx0 = lx1 = p1->x - p1->dmx * w;\n\t\tly0 = ly1 = p1->y - p1->dmy * w;\n\t\tnsvg__addEdge(r, lx1, ly1, left->x, left->y);\n\n\t\trx0 = p1->x + (dlx0 * w);\n\t\try0 = p1->y + (dly0 * w);\n\t\trx1 = p1->x + (dlx1 * w);\n\t\try1 = p1->y + (dly1 * w);\n\t\tnsvg__addEdge(r, right->x, right->y, rx0, ry0);\n\t\tnsvg__addEdge(r, rx0, ry0, rx1, ry1);\n\t} else {\n\t\tlx0 = p1->x - (dlx0 * w);\n\t\tly0 = p1->y - (dly0 * w);\n\t\tlx1 = p1->x - (dlx1 * w);\n\t\tly1 = p1->y - (dly1 * w);\n\t\tnsvg__addEdge(r, lx0, ly0, left->x, left->y);\n\t\tnsvg__addEdge(r, lx1, ly1, lx0, ly0);\n\n\t\trx0 = rx1 = p1->x + p1->dmx * w;\n\t\try0 = ry1 = p1->y + p1->dmy * w;\n\t\tnsvg__addEdge(r, right->x, right->y, rx1, ry1);\n\t}\n\n\tleft->x = lx1; left->y = ly1;\n\tright->x = rx1; right->y = ry1;\n}\n\nstatic void nsvg__roundJoin(NSVGrasterizer* r, NSVGpoint* left, NSVGpoint* right, NSVGpoint* p0, NSVGpoint* p1, float lineWidth, int ncap)\n{\n\tint i, n;\n\tfloat w = lineWidth * 0.5f;\n\tfloat dlx0 = p0->dy, dly0 = -p0->dx;\n\tfloat dlx1 = p1->dy, dly1 = -p1->dx;\n\tfloat a0 = atan2f(dly0, dlx0);\n\tfloat a1 = atan2f(dly1, dlx1);\n\tfloat da = a1 - a0;\n\tfloat lx, ly, rx, ry;\n\n\tif (da < NSVG_PI) da += NSVG_PI*2;\n\tif (da > NSVG_PI) da -= NSVG_PI*2;\n\n\tn = (int)ceilf((nsvg__absf(da) / NSVG_PI) * (float)ncap);\n\tif (n < 2) n = 2;\n\tif (n > ncap) n = ncap;\n\n\tlx = left->x;\n\tly = left->y;\n\trx = right->x;\n\try = right->y;\n\n\tfor (i = 0; i < n; i++) {\n\t\tfloat u = (float)i/(float)(n-1);\n\t\tfloat a = a0 + u*da;\n\t\tfloat ax = cosf(a) * w, ay = sinf(a) * w;\n\t\tfloat lx1 = p1->x - ax, ly1 = p1->y - ay;\n\t\tfloat rx1 = p1->x + ax, ry1 = p1->y + ay;\n\n\t\tnsvg__addEdge(r, lx1, ly1, lx, ly);\n\t\tnsvg__addEdge(r, rx, ry, rx1, ry1);\n\n\t\tlx = lx1; ly = ly1;\n\t\trx = rx1; ry = ry1;\n\t}\n\n\tleft->x = lx; left->y = ly;\n\tright->x = rx; right->y = ry;\n}\n\nstatic void nsvg__straightJoin(NSVGrasterizer* r, NSVGpoint* left, NSVGpoint* right, NSVGpoint* p1, float lineWidth)\n{\n\tfloat w = lineWidth * 0.5f;\n\tfloat lx = p1->x - (p1->dmx * w), ly = p1->y - (p1->dmy * w);\n\tfloat rx = p1->x + (p1->dmx * w), ry = p1->y + (p1->dmy * w);\n\n\tnsvg__addEdge(r, lx, ly, left->x, left->y);\n\tnsvg__addEdge(r, right->x, right->y, rx, ry);\n\n\tleft->x = lx; left->y = ly;\n\tright->x = rx; right->y = ry;\n}\n\nstatic int nsvg__curveDivs(float r, float arc, float tol)\n{\n\tfloat da = acosf(r / (r + tol)) * 2.0f;\n\tint divs = (int)ceilf(arc / da);\n\tif (divs < 2) divs = 2;\n\treturn divs;\n}\n\nstatic void nsvg__expandStroke(NSVGrasterizer* r, NSVGpoint* points, int npoints, int closed, int lineJoin, int lineCap, float lineWidth)\n{\n\tint ncap = nsvg__curveDivs(lineWidth*0.5f, NSVG_PI, r->tessTol);\t// Calculate divisions per half circle.\n\tNSVGpoint left = {0,0,0,0,0,0,0,0}, right = {0,0,0,0,0,0,0,0}, firstLeft = {0,0,0,0,0,0,0,0}, firstRight = {0,0,0,0,0,0,0,0};\n\tNSVGpoint* p0, *p1;\n\tint j, s, e;\n\n\t// Build stroke edges\n\tif (closed) {\n\t\t// Looping\n\t\tp0 = &points[npoints-1];\n\t\tp1 = &points[0];\n\t\ts = 0;\n\t\te = npoints;\n\t} else {\n\t\t// Add cap\n\t\tp0 = &points[0];\n\t\tp1 = &points[1];\n\t\ts = 1;\n\t\te = npoints-1;\n\t}\n\n\tif (closed) {\n\t\tnsvg__initClosed(&left, &right, p0, p1, lineWidth);\n\t\tfirstLeft = left;\n\t\tfirstRight = right;\n\t} else {\n\t\t// Add cap\n\t\tfloat dx = p1->x - p0->x;\n\t\tfloat dy = p1->y - p0->y;\n\t\tnsvg__normalize(&dx, &dy);\n\t\tif (lineCap == NSVG_CAP_BUTT)\n\t\t\tnsvg__buttCap(r, &left, &right, p0, dx, dy, lineWidth, 0);\n\t\telse if (lineCap == NSVG_CAP_SQUARE)\n\t\t\tnsvg__squareCap(r, &left, &right, p0, dx, dy, lineWidth, 0);\n\t\telse if (lineCap == NSVG_CAP_ROUND)\n\t\t\tnsvg__roundCap(r, &left, &right, p0, dx, dy, lineWidth, ncap, 0);\n\t}\n\n\tfor (j = s; j < e; ++j) {\n\t\tif (p1->flags & NSVG_PT_CORNER) {\n\t\t\tif (lineJoin == NSVG_JOIN_ROUND)\n\t\t\t\tnsvg__roundJoin(r, &left, &right, p0, p1, lineWidth, ncap);\n\t\t\telse if (lineJoin == NSVG_JOIN_BEVEL || (p1->flags & NSVG_PT_BEVEL))\n\t\t\t\tnsvg__bevelJoin(r, &left, &right, p0, p1, lineWidth);\n\t\t\telse\n\t\t\t\tnsvg__miterJoin(r, &left, &right, p0, p1, lineWidth);\n\t\t} else {\n\t\t\tnsvg__straightJoin(r, &left, &right, p1, lineWidth);\n\t\t}\n\t\tp0 = p1++;\n\t}\n\n\tif (closed) {\n\t\t// Loop it\n\t\tnsvg__addEdge(r, firstLeft.x, firstLeft.y, left.x, left.y);\n\t\tnsvg__addEdge(r, right.x, right.y, firstRight.x, firstRight.y);\n\t} else {\n\t\t// Add cap\n\t\tfloat dx = p1->x - p0->x;\n\t\tfloat dy = p1->y - p0->y;\n\t\tnsvg__normalize(&dx, &dy);\n\t\tif (lineCap == NSVG_CAP_BUTT)\n\t\t\tnsvg__buttCap(r, &right, &left, p1, -dx, -dy, lineWidth, 1);\n\t\telse if (lineCap == NSVG_CAP_SQUARE)\n\t\t\tnsvg__squareCap(r, &right, &left, p1, -dx, -dy, lineWidth, 1);\n\t\telse if (lineCap == NSVG_CAP_ROUND)\n\t\t\tnsvg__roundCap(r, &right, &left, p1, -dx, -dy, lineWidth, ncap, 1);\n\t}\n}\n\nstatic void nsvg__prepareStroke(NSVGrasterizer* r, float miterLimit, int lineJoin)\n{\n\tint i, j;\n\tNSVGpoint* p0, *p1;\n\n\tp0 = &r->points[r->npoints-1];\n\tp1 = &r->points[0];\n\tfor (i = 0; i < r->npoints; i++) {\n\t\t// Calculate segment direction and length\n\t\tp0->dx = p1->x - p0->x;\n\t\tp0->dy = p1->y - p0->y;\n\t\tp0->len = nsvg__normalize(&p0->dx, &p0->dy);\n\t\t// Advance\n\t\tp0 = p1++;\n\t}\n\n\t// calculate joins\n\tp0 = &r->points[r->npoints-1];\n\tp1 = &r->points[0];\n\tfor (j = 0; j < r->npoints; j++) {\n\t\tfloat dlx0, dly0, dlx1, dly1, dmr2, cross;\n\t\tdlx0 = p0->dy;\n\t\tdly0 = -p0->dx;\n\t\tdlx1 = p1->dy;\n\t\tdly1 = -p1->dx;\n\t\t// Calculate extrusions\n\t\tp1->dmx = (dlx0 + dlx1) * 0.5f;\n\t\tp1->dmy = (dly0 + dly1) * 0.5f;\n\t\tdmr2 = p1->dmx*p1->dmx + p1->dmy*p1->dmy;\n\t\tif (dmr2 > 0.000001f) {\n\t\t\tfloat s2 = 1.0f / dmr2;\n\t\t\tif (s2 > 600.0f) {\n\t\t\t\ts2 = 600.0f;\n\t\t\t}\n\t\t\tp1->dmx *= s2;\n\t\t\tp1->dmy *= s2;\n\t\t}\n\n\t\t// Clear flags, but keep the corner.\n\t\tp1->flags = (p1->flags & NSVG_PT_CORNER) ? NSVG_PT_CORNER : 0;\n\n\t\t// Keep track of left turns.\n\t\tcross = p1->dx * p0->dy - p0->dx * p1->dy;\n\t\tif (cross > 0.0f)\n\t\t\tp1->flags |= NSVG_PT_LEFT;\n\n\t\t// Check to see if the corner needs to be beveled.\n\t\tif (p1->flags & NSVG_PT_CORNER) {\n\t\t\tif ((dmr2 * miterLimit*miterLimit) < 1.0f || lineJoin == NSVG_JOIN_BEVEL || lineJoin == NSVG_JOIN_ROUND) {\n\t\t\t\tp1->flags |= NSVG_PT_BEVEL;\n\t\t\t}\n\t\t}\n\n\t\tp0 = p1++;\n\t}\n}\n\nstatic void nsvg__flattenShapeStroke(NSVGrasterizer* r, NSVGshape* shape, float scale)\n{\n\tint i, j, closed;\n\tNSVGpath* path;\n\tNSVGpoint* p0, *p1;\n\tfloat miterLimit = shape->miterLimit;\n\tint lineJoin = shape->strokeLineJoin;\n\tint lineCap = shape->strokeLineCap;\n\tfloat lineWidth = shape->strokeWidth * scale;\n\n\tfor (path = shape->paths; path != NULL; path = path->next) {\n\t\t// Flatten path\n\t\tr->npoints = 0;\n\t\tnsvg__addPathPoint(r, path->pts[0]*scale, path->pts[1]*scale, NSVG_PT_CORNER);\n\t\tfor (i = 0; i < path->npts-1; i += 3) {\n\t\t\tfloat* p = &path->pts[i*2];\n\t\t\tnsvg__flattenCubicBez(r, p[0]*scale,p[1]*scale, p[2]*scale,p[3]*scale, p[4]*scale,p[5]*scale, p[6]*scale,p[7]*scale, 0, NSVG_PT_CORNER);\n\t\t}\n\t\tif (r->npoints < 2)\n\t\t\tcontinue;\n\n\t\tclosed = path->closed;\n\n\t\t// If the first and last points are the same, remove the last, mark as closed path.\n\t\tp0 = &r->points[r->npoints-1];\n\t\tp1 = &r->points[0];\n\t\tif (nsvg__ptEquals(p0->x,p0->y, p1->x,p1->y, r->distTol)) {\n\t\t\tr->npoints--;\n\t\t\tp0 = &r->points[r->npoints-1];\n\t\t\tclosed = 1;\n\t\t}\n\n\t\tif (shape->strokeDashCount > 0) {\n\t\t\tint idash = 0, dashState = 1;\n\t\t\tfloat totalDist = 0, dashLen, allDashLen, dashOffset;\n\t\t\tNSVGpoint cur;\n\n\t\t\tif (closed)\n\t\t\t\tnsvg__appendPathPoint(r, r->points[0]);\n\n\t\t\t// Duplicate points -> points2.\n\t\t\tnsvg__duplicatePoints(r);\n\n\t\t\tr->npoints = 0;\n \t\t\tcur = r->points2[0];\n\t\t\tnsvg__appendPathPoint(r, cur);\n\n\t\t\t// Figure out dash offset.\n\t\t\tallDashLen = 0;\n\t\t\tfor (j = 0; j < shape->strokeDashCount; j++)\n\t\t\t\tallDashLen += shape->strokeDashArray[j];\n\t\t\tif (shape->strokeDashCount & 1)\n\t\t\t\tallDashLen *= 2.0f;\n\t\t\t// Find location inside pattern\n\t\t\tdashOffset = fmodf(shape->strokeDashOffset, allDashLen);\n\t\t\tif (dashOffset < 0.0f)\n\t\t\t\tdashOffset += allDashLen;\n\n\t\t\twhile (dashOffset > shape->strokeDashArray[idash]) {\n\t\t\t\tdashOffset -= shape->strokeDashArray[idash];\n\t\t\t\tidash = (idash + 1) % shape->strokeDashCount;\n\t\t\t}\n\t\t\tdashLen = (shape->strokeDashArray[idash] - dashOffset) * scale;\n\n\t\t\tfor (j = 1; j < r->npoints2; ) {\n\t\t\t\tfloat dx = r->points2[j].x - cur.x;\n\t\t\t\tfloat dy = r->points2[j].y - cur.y;\n\t\t\t\tfloat dist = sqrtf(dx*dx + dy*dy);\n\n\t\t\t\tif ((totalDist + dist) > dashLen) {\n\t\t\t\t\t// Calculate intermediate point\n\t\t\t\t\tfloat d = (dashLen - totalDist) / dist;\n\t\t\t\t\tfloat x = cur.x + dx * d;\n\t\t\t\t\tfloat y = cur.y + dy * d;\n\t\t\t\t\tnsvg__addPathPoint(r, x, y, NSVG_PT_CORNER);\n\n\t\t\t\t\t// Stroke\n\t\t\t\t\tif (r->npoints > 1 && dashState) {\n\t\t\t\t\t\tnsvg__prepareStroke(r, miterLimit, lineJoin);\n\t\t\t\t\t\tnsvg__expandStroke(r, r->points, r->npoints, 0, lineJoin, lineCap, lineWidth);\n\t\t\t\t\t}\n\t\t\t\t\t// Advance dash pattern\n\t\t\t\t\tdashState = !dashState;\n\t\t\t\t\tidash = (idash+1) % shape->strokeDashCount;\n\t\t\t\t\tdashLen = shape->strokeDashArray[idash] * scale;\n\t\t\t\t\t// Restart\n\t\t\t\t\tcur.x = x;\n\t\t\t\t\tcur.y = y;\n\t\t\t\t\tcur.flags = NSVG_PT_CORNER;\n\t\t\t\t\ttotalDist = 0.0f;\n\t\t\t\t\tr->npoints = 0;\n\t\t\t\t\tnsvg__appendPathPoint(r, cur);\n\t\t\t\t} else {\n\t\t\t\t\ttotalDist += dist;\n\t\t\t\t\tcur = r->points2[j];\n\t\t\t\t\tnsvg__appendPathPoint(r, cur);\n\t\t\t\t\tj++;\n\t\t\t\t}\n\t\t\t}\n\t\t\t// Stroke any leftover path\n\t\t\tif (r->npoints > 1 && dashState) {\n\t\t\t\tnsvg__prepareStroke(r, miterLimit, lineJoin);\n\t\t\t\tnsvg__expandStroke(r, r->points, r->npoints, 0, lineJoin, lineCap, lineWidth);\n\t\t\t}\n\t\t} else {\n\t\t\tnsvg__prepareStroke(r, miterLimit, lineJoin);\n\t\t\tnsvg__expandStroke(r, r->points, r->npoints, closed, lineJoin, lineCap, lineWidth);\n\t\t}\n\t}\n}\n\nstatic int nsvg__cmpEdge(const void *p, const void *q)\n{\n\tconst NSVGedge* a = (const NSVGedge*)p;\n\tconst NSVGedge* b = (const NSVGedge*)q;\n\n\tif (a->y0 < b->y0) return -1;\n\tif (a->y0 > b->y0) return  1;\n\treturn 0;\n}\n\n\nstatic NSVGactiveEdge* nsvg__addActive(NSVGrasterizer* r, NSVGedge* e, float startPoint)\n{\n\t NSVGactiveEdge* z;\n\n\tif (r->freelist != NULL) {\n\t\t// Restore from freelist.\n\t\tz = r->freelist;\n\t\tr->freelist = z->next;\n\t} else {\n\t\t// Alloc new edge.\n\t\tz = (NSVGactiveEdge*)nsvg__alloc(r, sizeof(NSVGactiveEdge));\n\t\tif (z == NULL) return NULL;\n\t}\n\n\tfloat dxdy = (e->x1 - e->x0) / (e->y1 - e->y0);\n//\tSTBTT_assert(e->y0 <= start_point);\n\t// round dx down to avoid going too far\n\tif (dxdy < 0)\n\t\tz->dx = (int)(-nsvg__roundf(NSVG__FIX * -dxdy));\n\telse\n\t\tz->dx = (int)nsvg__roundf(NSVG__FIX * dxdy);\n\tz->x = (int)nsvg__roundf(NSVG__FIX * (e->x0 + dxdy * (startPoint - e->y0)));\n//\tz->x -= off_x * FIX;\n\tz->ey = e->y1;\n\tz->next = 0;\n\tz->dir = e->dir;\n\n\treturn z;\n}\n\nstatic void nsvg__freeActive(NSVGrasterizer* r, NSVGactiveEdge* z)\n{\n\tz->next = r->freelist;\n\tr->freelist = z;\n}\n\nstatic void nsvg__fillScanline(unsigned char* scanline, int len, int x0, int x1, int maxWeight, int* xmin, int* xmax)\n{\n\tint i = x0 >> NSVG__FIXSHIFT;\n\tint j = x1 >> NSVG__FIXSHIFT;\n\tif (i < *xmin) *xmin = i;\n\tif (j > *xmax) *xmax = j;\n\tif (i < len && j >= 0) {\n\t\tif (i == j) {\n\t\t\t// x0,x1 are the same pixel, so compute combined coverage\n\t\t\tscanline[i] = (unsigned char)(scanline[i] + ((x1 - x0) * maxWeight >> NSVG__FIXSHIFT));\n\t\t} else {\n\t\t\tif (i >= 0) // add antialiasing for x0\n\t\t\t\tscanline[i] = (unsigned char)(scanline[i] + (((NSVG__FIX - (x0 & NSVG__FIXMASK)) * maxWeight) >> NSVG__FIXSHIFT));\n\t\t\telse\n\t\t\t\ti = -1; // clip\n\n\t\t\tif (j < len) // add antialiasing for x1\n\t\t\t\tscanline[j] = (unsigned char)(scanline[j] + (((x1 & NSVG__FIXMASK) * maxWeight) >> NSVG__FIXSHIFT));\n\t\t\telse\n\t\t\t\tj = len; // clip\n\n\t\t\tfor (++i; i < j; ++i) // fill pixels between x0 and x1\n\t\t\t\tscanline[i] = (unsigned char)(scanline[i] + maxWeight);\n\t\t}\n\t}\n}\n\n// note: this routine clips fills that extend off the edges... ideally this\n// wouldn't happen, but it could happen if the truetype glyph bounding boxes\n// are wrong, or if the user supplies a too-small bitmap\nstatic void nsvg__fillActiveEdges(unsigned char* scanline, int len, NSVGactiveEdge* e, int maxWeight, int* xmin, int* xmax, char fillRule)\n{\n\t// non-zero winding fill\n\tint x0 = 0, w = 0;\n\n\tif (fillRule == NSVG_FILLRULE_NONZERO) {\n\t\t// Non-zero\n\t\twhile (e != NULL) {\n\t\t\tif (w == 0) {\n\t\t\t\t// if we're currently at zero, we need to record the edge start point\n\t\t\t\tx0 = e->x; w += e->dir;\n\t\t\t} else {\n\t\t\t\tint x1 = e->x; w += e->dir;\n\t\t\t\t// if we went to zero, we need to draw\n\t\t\t\tif (w == 0)\n\t\t\t\t\tnsvg__fillScanline(scanline, len, x0, x1, maxWeight, xmin, xmax);\n\t\t\t}\n\t\t\te = e->next;\n\t\t}\n\t} else if (fillRule == NSVG_FILLRULE_EVENODD) {\n\t\t// Even-odd\n\t\twhile (e != NULL) {\n\t\t\tif (w == 0) {\n\t\t\t\t// if we're currently at zero, we need to record the edge start point\n\t\t\t\tx0 = e->x; w = 1;\n\t\t\t} else {\n\t\t\t\tint x1 = e->x; w = 0;\n\t\t\t\tnsvg__fillScanline(scanline, len, x0, x1, maxWeight, xmin, xmax);\n\t\t\t}\n\t\t\te = e->next;\n\t\t}\n\t}\n}\n\nstatic float nsvg__clampf(float a, float mn, float mx) {\n\tif (isnan(a))\n\t\treturn mn;\n\treturn a < mn ? mn : (a > mx ? mx : a);\n}\n\nstatic unsigned int nsvg__RGBA(unsigned char r, unsigned char g, unsigned char b, unsigned char a)\n{\n\treturn ((unsigned int)r) | ((unsigned int)g << 8) | ((unsigned int)b << 16) | ((unsigned int)a << 24);\n}\n\nstatic unsigned int nsvg__lerpRGBA(unsigned int c0, unsigned int c1, float u)\n{\n\tint iu = (int)(nsvg__clampf(u, 0.0f, 1.0f) * 256.0f);\n\tint r = (((c0) & 0xff)*(256-iu) + (((c1) & 0xff)*iu)) >> 8;\n\tint g = (((c0>>8) & 0xff)*(256-iu) + (((c1>>8) & 0xff)*iu)) >> 8;\n\tint b = (((c0>>16) & 0xff)*(256-iu) + (((c1>>16) & 0xff)*iu)) >> 8;\n\tint a = (((c0>>24) & 0xff)*(256-iu) + (((c1>>24) & 0xff)*iu)) >> 8;\n\treturn nsvg__RGBA((unsigned char)r, (unsigned char)g, (unsigned char)b, (unsigned char)a);\n}\n\nstatic unsigned int nsvg__applyOpacity(unsigned int c, float u)\n{\n\tint iu = (int)(nsvg__clampf(u, 0.0f, 1.0f) * 256.0f);\n\tint r = (c) & 0xff;\n\tint g = (c>>8) & 0xff;\n\tint b = (c>>16) & 0xff;\n\tint a = (((c>>24) & 0xff)*iu) >> 8;\n\treturn nsvg__RGBA((unsigned char)r, (unsigned char)g, (unsigned char)b, (unsigned char)a);\n}\n\nstatic inline int nsvg__div255(int x)\n{\n    return ((x+1) * 257) >> 16;\n}\n\nstatic void nsvg__scanlineSolid(unsigned char* dst, int count, unsigned char* cover, int x, int y,\n\t\t\t\t\t\t\t\tfloat tx, float ty, float scale, NSVGcachedPaint* cache)\n{\n\n\tif (cache->type == NSVG_PAINT_COLOR) {\n\t\tint i, cr, cg, cb, ca;\n\t\tcr = cache->colors[0] & 0xff;\n\t\tcg = (cache->colors[0] >> 8) & 0xff;\n\t\tcb = (cache->colors[0] >> 16) & 0xff;\n\t\tca = (cache->colors[0] >> 24) & 0xff;\n\n\t\tfor (i = 0; i < count; i++) {\n\t\t\tint r,g,b;\n\t\t\tint a = nsvg__div255((int)cover[0] * ca);\n\t\t\tint ia = 255 - a;\n\t\t\t// Premultiply\n\t\t\tr = nsvg__div255(cr * a);\n\t\t\tg = nsvg__div255(cg * a);\n\t\t\tb = nsvg__div255(cb * a);\n\n\t\t\t// Blend over\n\t\t\tr += nsvg__div255(ia * (int)dst[0]);\n\t\t\tg += nsvg__div255(ia * (int)dst[1]);\n\t\t\tb += nsvg__div255(ia * (int)dst[2]);\n\t\t\ta += nsvg__div255(ia * (int)dst[3]);\n\n\t\t\tdst[0] = (unsigned char)r;\n\t\t\tdst[1] = (unsigned char)g;\n\t\t\tdst[2] = (unsigned char)b;\n\t\t\tdst[3] = (unsigned char)a;\n\n\t\t\tcover++;\n\t\t\tdst += 4;\n\t\t}\n\t} else if (cache->type == NSVG_PAINT_LINEAR_GRADIENT) {\n\t\t// TODO: spread modes.\n\t\t// TODO: plenty of opportunities to optimize.\n\t\tfloat fx, fy, dx, gy;\n\t\tfloat* t = cache->xform;\n\t\tint i, cr, cg, cb, ca;\n\t\tunsigned int c;\n\n\t\tfx = ((float)x - tx) / scale;\n\t\tfy = ((float)y - ty) / scale;\n\t\tdx = 1.0f / scale;\n\n\t\tfor (i = 0; i < count; i++) {\n\t\t\tint r,g,b,a,ia;\n\t\t\tgy = fx*t[1] + fy*t[3] + t[5];\n\t\t\tc = cache->colors[(int)nsvg__clampf(gy*255.0f, 0, 255.0f)];\n\t\t\tcr = (c) & 0xff;\n\t\t\tcg = (c >> 8) & 0xff;\n\t\t\tcb = (c >> 16) & 0xff;\n\t\t\tca = (c >> 24) & 0xff;\n\n\t\t\ta = nsvg__div255((int)cover[0] * ca);\n\t\t\tia = 255 - a;\n\n\t\t\t// Premultiply\n\t\t\tr = nsvg__div255(cr * a);\n\t\t\tg = nsvg__div255(cg * a);\n\t\t\tb = nsvg__div255(cb * a);\n\n\t\t\t// Blend over\n\t\t\tr += nsvg__div255(ia * (int)dst[0]);\n\t\t\tg += nsvg__div255(ia * (int)dst[1]);\n\t\t\tb += nsvg__div255(ia * (int)dst[2]);\n\t\t\ta += nsvg__div255(ia * (int)dst[3]);\n\n\t\t\tdst[0] = (unsigned char)r;\n\t\t\tdst[1] = (unsigned char)g;\n\t\t\tdst[2] = (unsigned char)b;\n\t\t\tdst[3] = (unsigned char)a;\n\n\t\t\tcover++;\n\t\t\tdst += 4;\n\t\t\tfx += dx;\n\t\t}\n\t} else if (cache->type == NSVG_PAINT_RADIAL_GRADIENT) {\n\t\t// TODO: spread modes.\n\t\t// TODO: plenty of opportunities to optimize.\n\t\t// TODO: focus (fx,fy)\n\t\tfloat fx, fy, dx, gx, gy, gd;\n\t\tfloat* t = cache->xform;\n\t\tint i, cr, cg, cb, ca;\n\t\tunsigned int c;\n\n\t\tfx = ((float)x - tx) / scale;\n\t\tfy = ((float)y - ty) / scale;\n\t\tdx = 1.0f / scale;\n\n\t\tfor (i = 0; i < count; i++) {\n\t\t\tint r,g,b,a,ia;\n\t\t\tgx = fx*t[0] + fy*t[2] + t[4];\n\t\t\tgy = fx*t[1] + fy*t[3] + t[5];\n\t\t\tgd = sqrtf(gx*gx + gy*gy);\n\t\t\tc = cache->colors[(int)nsvg__clampf(gd*255.0f, 0, 255.0f)];\n\t\t\tcr = (c) & 0xff;\n\t\t\tcg = (c >> 8) & 0xff;\n\t\t\tcb = (c >> 16) & 0xff;\n\t\t\tca = (c >> 24) & 0xff;\n\n\t\t\ta = nsvg__div255((int)cover[0] * ca);\n\t\t\tia = 255 - a;\n\n\t\t\t// Premultiply\n\t\t\tr = nsvg__div255(cr * a);\n\t\t\tg = nsvg__div255(cg * a);\n\t\t\tb = nsvg__div255(cb * a);\n\n\t\t\t// Blend over\n\t\t\tr += nsvg__div255(ia * (int)dst[0]);\n\t\t\tg += nsvg__div255(ia * (int)dst[1]);\n\t\t\tb += nsvg__div255(ia * (int)dst[2]);\n\t\t\ta += nsvg__div255(ia * (int)dst[3]);\n\n\t\t\tdst[0] = (unsigned char)r;\n\t\t\tdst[1] = (unsigned char)g;\n\t\t\tdst[2] = (unsigned char)b;\n\t\t\tdst[3] = (unsigned char)a;\n\n\t\t\tcover++;\n\t\t\tdst += 4;\n\t\t\tfx += dx;\n\t\t}\n\t}\n}\n\nstatic void nsvg__rasterizeSortedEdges(NSVGrasterizer *r, float tx, float ty, float scale, NSVGcachedPaint* cache, char fillRule)\n{\n\tNSVGactiveEdge *active = NULL;\n\tint y, s;\n\tint e = 0;\n\tint maxWeight = (255 / NSVG__SUBSAMPLES);  // weight per vertical scanline\n\tint xmin, xmax;\n\n\tfor (y = 0; y < r->height; y++) {\n\t\tmemset(r->scanline, 0, r->width);\n\t\txmin = r->width;\n\t\txmax = 0;\n\t\tfor (s = 0; s < NSVG__SUBSAMPLES; ++s) {\n\t\t\t// find center of pixel for this scanline\n\t\t\tfloat scany = (float)(y*NSVG__SUBSAMPLES + s) + 0.5f;\n\t\t\tNSVGactiveEdge **step = &active;\n\n\t\t\t// update all active edges;\n\t\t\t// remove all active edges that terminate before the center of this scanline\n\t\t\twhile (*step) {\n\t\t\t\tNSVGactiveEdge *z = *step;\n\t\t\t\tif (z->ey <= scany) {\n\t\t\t\t\t*step = z->next; // delete from list\n//\t\t\t\t\tNSVG__assert(z->valid);\n\t\t\t\t\tnsvg__freeActive(r, z);\n\t\t\t\t} else {\n\t\t\t\t\tz->x += z->dx; // advance to position for current scanline\n\t\t\t\t\tstep = &((*step)->next); // advance through list\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// resort the list if needed\n\t\t\tfor (;;) {\n\t\t\t\tint changed = 0;\n\t\t\t\tstep = &active;\n\t\t\t\twhile (*step && (*step)->next) {\n\t\t\t\t\tif ((*step)->x > (*step)->next->x) {\n\t\t\t\t\t\tNSVGactiveEdge* t = *step;\n\t\t\t\t\t\tNSVGactiveEdge* q = t->next;\n\t\t\t\t\t\tt->next = q->next;\n\t\t\t\t\t\tq->next = t;\n\t\t\t\t\t\t*step = q;\n\t\t\t\t\t\tchanged = 1;\n\t\t\t\t\t}\n\t\t\t\t\tstep = &(*step)->next;\n\t\t\t\t}\n\t\t\t\tif (!changed) break;\n\t\t\t}\n\n\t\t\t// insert all edges that start before the center of this scanline -- omit ones that also end on this scanline\n\t\t\twhile (e < r->nedges && r->edges[e].y0 <= scany) {\n\t\t\t\tif (r->edges[e].y1 > scany) {\n\t\t\t\t\tNSVGactiveEdge* z = nsvg__addActive(r, &r->edges[e], scany);\n\t\t\t\t\tif (z == NULL) break;\n\t\t\t\t\t// find insertion point\n\t\t\t\t\tif (active == NULL) {\n\t\t\t\t\t\tactive = z;\n\t\t\t\t\t} else if (z->x < active->x) {\n\t\t\t\t\t\t// insert at front\n\t\t\t\t\t\tz->next = active;\n\t\t\t\t\t\tactive = z;\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// find thing to insert AFTER\n\t\t\t\t\t\tNSVGactiveEdge* p = active;\n\t\t\t\t\t\twhile (p->next && p->next->x < z->x)\n\t\t\t\t\t\t\tp = p->next;\n\t\t\t\t\t\t// at this point, p->next->x is NOT < z->x\n\t\t\t\t\t\tz->next = p->next;\n\t\t\t\t\t\tp->next = z;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\te++;\n\t\t\t}\n\n\t\t\t// now process all active edges in non-zero fashion\n\t\t\tif (active != NULL)\n\t\t\t\tnsvg__fillActiveEdges(r->scanline, r->width, active, maxWeight, &xmin, &xmax, fillRule);\n\t\t}\n\t\t// Blit\n\t\tif (xmin < 0) xmin = 0;\n\t\tif (xmax > r->width-1) xmax = r->width-1;\n\t\tif (xmin <= xmax) {\n\t\t\tnsvg__scanlineSolid(&r->bitmap[y * r->stride] + xmin*4, xmax-xmin+1, &r->scanline[xmin], xmin, y, tx,ty, scale, cache);\n\t\t}\n\t}\n\n}\n\nstatic void nsvg__unpremultiplyAlpha(unsigned char* image, int w, int h, int stride)\n{\n\tint x,y;\n\n\t// Unpremultiply\n\tfor (y = 0; y < h; y++) {\n\t\tunsigned char *row = &image[y*stride];\n\t\tfor (x = 0; x < w; x++) {\n\t\t\tint r = row[0], g = row[1], b = row[2], a = row[3];\n\t\t\tif (a != 0) {\n\t\t\t\trow[0] = (unsigned char)(r*255/a);\n\t\t\t\trow[1] = (unsigned char)(g*255/a);\n\t\t\t\trow[2] = (unsigned char)(b*255/a);\n\t\t\t}\n\t\t\trow += 4;\n\t\t}\n\t}\n\n\t// Defringe\n\tfor (y = 0; y < h; y++) {\n\t\tunsigned char *row = &image[y*stride];\n\t\tfor (x = 0; x < w; x++) {\n\t\t\tint r = 0, g = 0, b = 0, a = row[3], n = 0;\n\t\t\tif (a == 0) {\n\t\t\t\tif (x-1 > 0 && row[-1] != 0) {\n\t\t\t\t\tr += row[-4];\n\t\t\t\t\tg += row[-3];\n\t\t\t\t\tb += row[-2];\n\t\t\t\t\tn++;\n\t\t\t\t}\n\t\t\t\tif (x+1 < w && row[7] != 0) {\n\t\t\t\t\tr += row[4];\n\t\t\t\t\tg += row[5];\n\t\t\t\t\tb += row[6];\n\t\t\t\t\tn++;\n\t\t\t\t}\n\t\t\t\tif (y-1 > 0 && row[-stride+3] != 0) {\n\t\t\t\t\tr += row[-stride];\n\t\t\t\t\tg += row[-stride+1];\n\t\t\t\t\tb += row[-stride+2];\n\t\t\t\t\tn++;\n\t\t\t\t}\n\t\t\t\tif (y+1 < h && row[stride+3] != 0) {\n\t\t\t\t\tr += row[stride];\n\t\t\t\t\tg += row[stride+1];\n\t\t\t\t\tb += row[stride+2];\n\t\t\t\t\tn++;\n\t\t\t\t}\n\t\t\t\tif (n > 0) {\n\t\t\t\t\trow[0] = (unsigned char)(r/n);\n\t\t\t\t\trow[1] = (unsigned char)(g/n);\n\t\t\t\t\trow[2] = (unsigned char)(b/n);\n\t\t\t\t}\n\t\t\t}\n\t\t\trow += 4;\n\t\t}\n\t}\n}\n\n\nstatic void nsvg__initPaint(NSVGcachedPaint* cache, NSVGpaint* paint, float opacity)\n{\n\tint i, j;\n\tNSVGgradient* grad;\n\n\tcache->type = paint->type;\n\n\tif (paint->type == NSVG_PAINT_COLOR) {\n\t\tcache->colors[0] = nsvg__applyOpacity(paint->color, opacity);\n\t\treturn;\n\t}\n\n\tgrad = paint->gradient;\n\n\tcache->spread = grad->spread;\n\tmemcpy(cache->xform, grad->xform, sizeof(float)*6);\n\n\tif (grad->nstops == 0) {\n\t\tfor (i = 0; i < 256; i++)\n\t\t\tcache->colors[i] = 0;\n\t} else if (grad->nstops == 1) {\n\t\tunsigned int color = nsvg__applyOpacity(grad->stops[0].color, opacity);\n\t\tfor (i = 0; i < 256; i++)\n\t\t\tcache->colors[i] = color;\n\t} else {\n\t\tunsigned int ca, cb = 0;\n\t\tfloat ua, ub, du, u;\n\t\tint ia, ib, count;\n\n\t\tca = nsvg__applyOpacity(grad->stops[0].color, opacity);\n\t\tua = nsvg__clampf(grad->stops[0].offset, 0, 1);\n\t\tub = nsvg__clampf(grad->stops[grad->nstops-1].offset, ua, 1);\n\t\tia = (int)(ua * 255.0f);\n\t\tib = (int)(ub * 255.0f);\n\t\tfor (i = 0; i < ia; i++) {\n\t\t\tcache->colors[i] = ca;\n\t\t}\n\n\t\tfor (i = 0; i < grad->nstops-1; i++) {\n\t\t\tca = nsvg__applyOpacity(grad->stops[i].color, opacity);\n\t\t\tcb = nsvg__applyOpacity(grad->stops[i+1].color, opacity);\n\t\t\tua = nsvg__clampf(grad->stops[i].offset, 0, 1);\n\t\t\tub = nsvg__clampf(grad->stops[i+1].offset, 0, 1);\n\t\t\tia = (int)(ua * 255.0f);\n\t\t\tib = (int)(ub * 255.0f);\n\t\t\tcount = ib - ia;\n\t\t\tif (count <= 0) continue;\n\t\t\tu = 0;\n\t\t\tdu = 1.0f / (float)count;\n\t\t\tfor (j = 0; j < count; j++) {\n\t\t\t\tcache->colors[ia+j] = nsvg__lerpRGBA(ca,cb,u);\n\t\t\t\tu += du;\n\t\t\t}\n\t\t}\n\n\t\tfor (i = ib; i < 256; i++)\n\t\t\tcache->colors[i] = cb;\n\t}\n\n}\n\n/*\nstatic void dumpEdges(NSVGrasterizer* r, const char* name)\n{\n\tfloat xmin = 0, xmax = 0, ymin = 0, ymax = 0;\n\tNSVGedge *e = NULL;\n\tint i;\n\tif (r->nedges == 0) return;\n\tFILE* fp = fopen(name, \"w\");\n\tif (fp == NULL) return;\n\n\txmin = xmax = r->edges[0].x0;\n\tymin = ymax = r->edges[0].y0;\n\tfor (i = 0; i < r->nedges; i++) {\n\t\te = &r->edges[i];\n\t\txmin = nsvg__minf(xmin, e->x0);\n\t\txmin = nsvg__minf(xmin, e->x1);\n\t\txmax = nsvg__maxf(xmax, e->x0);\n\t\txmax = nsvg__maxf(xmax, e->x1);\n\t\tymin = nsvg__minf(ymin, e->y0);\n\t\tymin = nsvg__minf(ymin, e->y1);\n\t\tymax = nsvg__maxf(ymax, e->y0);\n\t\tymax = nsvg__maxf(ymax, e->y1);\n\t}\n\n\tfprintf(fp, \"<svg viewBox=\\\"%f %f %f %f\\\" xmlns=\\\"http://www.w3.org/2000/svg\\\">\", xmin, ymin, (xmax - xmin), (ymax - ymin));\n\n\tfor (i = 0; i < r->nedges; i++) {\n\t\te = &r->edges[i];\n\t\tfprintf(fp ,\"<line x1=\\\"%f\\\" y1=\\\"%f\\\" x2=\\\"%f\\\" y2=\\\"%f\\\" style=\\\"stroke:#000;\\\" />\", e->x0,e->y0, e->x1,e->y1);\n\t}\n\n\tfor (i = 0; i < r->npoints; i++) {\n\t\tif (i+1 < r->npoints)\n\t\t\tfprintf(fp ,\"<line x1=\\\"%f\\\" y1=\\\"%f\\\" x2=\\\"%f\\\" y2=\\\"%f\\\" style=\\\"stroke:#f00;\\\" />\", r->points[i].x, r->points[i].y, r->points[i+1].x, r->points[i+1].y);\n\t\tfprintf(fp ,\"<circle cx=\\\"%f\\\" cy=\\\"%f\\\" r=\\\"1\\\" style=\\\"fill:%s;\\\" />\", r->points[i].x, r->points[i].y, r->points[i].flags == 0 ? \"#f00\" : \"#0f0\");\n\t}\n\n\tfprintf(fp, \"</svg>\");\n\tfclose(fp);\n}\n*/\n\nvoid nsvgRasterize(NSVGrasterizer* r,\n\t\t\t\t   NSVGimage* image, float tx, float ty, float scale,\n\t\t\t\t   unsigned char* dst, int w, int h, int stride)\n{\n\tNSVGshape *shape = NULL;\n\tNSVGedge *e = NULL;\n\tNSVGcachedPaint cache;\n\tint i;\n    int j;\n    unsigned char paintOrder;\n\n\tr->bitmap = dst;\n\tr->width = w;\n\tr->height = h;\n\tr->stride = stride;\n\n\tif (w > r->cscanline) {\n\t\tr->cscanline = w;\n\t\tr->scanline = (unsigned char*)realloc(r->scanline, w);\n\t\tif (r->scanline == NULL) return;\n\t}\n\n\tfor (i = 0; i < h; i++)\n\t\tmemset(&dst[i*stride], 0, w*4);\n\n\tfor (shape = image->shapes; shape != NULL; shape = shape->next) {\n\t\tif (!(shape->flags & NSVG_FLAGS_VISIBLE))\n\t\t\tcontinue;\n\n        for (j = 0; j < 3; j++) {\n            paintOrder = (shape->paintOrder >> (2 * j)) & 0x03;\n\n            if (paintOrder == NSVG_PAINT_FILL && shape->fill.type != NSVG_PAINT_NONE) {\n                nsvg__resetPool(r);\n                r->freelist = NULL;\n                r->nedges = 0;\n\n                nsvg__flattenShape(r, shape, scale);\n\n                // Scale and translate edges\n                for (i = 0; i < r->nedges; i++) {\n                    e = &r->edges[i];\n                    e->x0 = tx + e->x0;\n                    e->y0 = (ty + e->y0) * NSVG__SUBSAMPLES;\n                    e->x1 = tx + e->x1;\n                    e->y1 = (ty + e->y1) * NSVG__SUBSAMPLES;\n                }\n\n                // Rasterize edges\n                if (r->nedges != 0)\n                    qsort(r->edges, r->nedges, sizeof(NSVGedge), nsvg__cmpEdge);\n\n                // now, traverse the scanlines and find the intersections on each scanline, use non-zero rule\n                nsvg__initPaint(&cache, &shape->fill, shape->opacity);\n\n                nsvg__rasterizeSortedEdges(r, tx,ty,scale, &cache, shape->fillRule);\n            }\n            if (paintOrder == NSVG_PAINT_STROKE && shape->stroke.type != NSVG_PAINT_NONE && (shape->strokeWidth * scale) > 0.01f) {\n                nsvg__resetPool(r);\n                r->freelist = NULL;\n                r->nedges = 0;\n\n                nsvg__flattenShapeStroke(r, shape, scale);\n\n    //\t\t\tdumpEdges(r, \"edge.svg\");\n\n                // Scale and translate edges\n                for (i = 0; i < r->nedges; i++) {\n                    e = &r->edges[i];\n                    e->x0 = tx + e->x0;\n                    e->y0 = (ty + e->y0) * NSVG__SUBSAMPLES;\n                    e->x1 = tx + e->x1;\n                    e->y1 = (ty + e->y1) * NSVG__SUBSAMPLES;\n                }\n\n                // Rasterize edges\n                if (r->nedges != 0)\n                    qsort(r->edges, r->nedges, sizeof(NSVGedge), nsvg__cmpEdge);\n\n                // now, traverse the scanlines and find the intersections on each scanline, use non-zero rule\n                nsvg__initPaint(&cache, &shape->stroke, shape->opacity);\n\n                nsvg__rasterizeSortedEdges(r, tx,ty,scale, &cache, NSVG_FILLRULE_NONZERO);\n            }\n        }\n\t}\n\n\tnsvg__unpremultiplyAlpha(dst, w, h, stride);\n\n\tr->bitmap = NULL;\n\tr->width = 0;\n\tr->height = 0;\n\tr->stride = 0;\n}\n\n#endif // NANOSVGRAST_IMPLEMENTATION\n\n#endif // NANOSVGRAST_H\n"
  },
  {
    "path": "external/rprand.h",
    "content": "/**********************************************************************************************\n*\n*   rprand v1.0 - A simple and easy-to-use pseudo-random numbers generator (PRNG)\n*\n*   FEATURES:\n*       - Pseudo-random values generation, 32 bits: [0..4294967295]\n*       - Sequence generation avoiding duplicate values\n*       - Using standard and proven prng algorithm (Xoshiro128**)\n*       - State initialized with a separate generator (SplitMix64)\n*\n*   LIMITATIONS:\n*       - No negative numbers, up to the user to manage them\n*\n*   POSSIBLE IMPROVEMENTS:\n*       - Support 64 bits generation\n*\n*   ADDITIONAL NOTES:\n*     This library implements two pseudo-random number generation algorithms: \n*\n*         - Xoshiro128** : https://prng.di.unimi.it/xoshiro128starstar.c\n*         - SplitMix64   : https://prng.di.unimi.it/splitmix64.c\n*\n*     SplitMix64 is used to initialize the Xoshiro128** state, from a provided seed\n*\n*     It's suggested to use SplitMix64 to initialize the state of the generators starting from \n*     a 64-bit seed, as research has shown that initialization must be performed with a generator \n*     radically different in nature from the one initialized to avoid correlation on similar seeds.\n*\n*   CONFIGURATION:\n*       #define RPRAND_IMPLEMENTATION\n*           Generates the implementation of the library into the included file.\n*           If not defined, the library is in header only mode and can be included in other headers\n*           or source files without problems. But only ONE file should hold the implementation.\n* \n*   DEPENDENCIES: none\n*\n*   VERSIONS HISTORY:\n*       1.0 (01-Jun-2023) First version\n*\n*\n*   LICENSE: zlib/libpng\n*\n*   Copyright (c) 2023 Ramon Santamaria (@raysan5)\n*\n*   This software is provided \"as-is\", without any express or implied warranty. In no event\n*   will the authors be held liable for any damages arising from the use of this software.\n*\n*   Permission is granted to anyone to use this software for any purpose, including commercial\n*   applications, and to alter it and redistribute it freely, subject to the following restrictions:\n*\n*     1. The origin of this software must not be misrepresented; you must not claim that you\n*     wrote the original software. If you use this software in a product, an acknowledgment\n*     in the product documentation would be appreciated but is not required.\n*\n*     2. Altered source versions must be plainly marked as such, and must not be misrepresented\n*     as being the original software.\n*\n*     3. This notice may not be removed or altered from any source distribution.\n*\n**********************************************************************************************/\n\n#ifndef RPRAND_H\n#define RPRAND_H\n\n#define RPRAND_VERSION    \"1.0\"\n\n// Function specifiers in case library is build/used as a shared library (Windows)\n// NOTE: Microsoft specifiers to tell compiler that symbols are imported/exported from a .dll\n#if defined(_WIN32)\n    #if defined(BUILD_LIBTYPE_SHARED)\n        #define RPRAND __declspec(dllexport)     // We are building the library as a Win32 shared library (.dll)\n    #elif defined(USE_LIBTYPE_SHARED)\n        #define RPRAND __declspec(dllimport)     // We are using the library as a Win32 shared library (.dll)\n    #endif\n#endif\n\n// Function specifiers definition\n#ifndef RPRANDAPI\n    #define RPRANDAPI       // Functions defined as 'extern' by default (implicit specifiers)\n#endif\n\n//----------------------------------------------------------------------------------\n// Defines and Macros\n//----------------------------------------------------------------------------------\n// Allow custom memory allocators\n#ifndef RPRAND_CALLOC\n    #define RPRAND_CALLOC(ptr,sz)     calloc(ptr,sz)\n#endif\n#ifndef RPRAND_FREE\n    #define RPRAND_FREE(ptr)          free(ptr)\n#endif\n\n// Simple log system to avoid RPNG_LOG() calls if required\n// NOTE: Avoiding those calls, also avoids const strings memory usage\n#define RPRAND_SHOW_LOG_INFO\n#if defined(RPNG_SHOW_LOG_INFO)\n  #define RPRAND_LOG(...) printf(__VA_ARGS__)\n#else\n  #define RPRAND_LOG(...)\n#endif\n\n//----------------------------------------------------------------------------------\n// Types and Structures Definition\n//----------------------------------------------------------------------------------\n//...\n\n#ifdef __cplusplus\nextern \"C\" {                // Prevents name mangling of functions\n#endif\n\n//----------------------------------------------------------------------------------\n// Global Variables Definition\n//----------------------------------------------------------------------------------\n//...\n\n//----------------------------------------------------------------------------------\n// Module Functions Declaration\n//----------------------------------------------------------------------------------\nRPRANDAPI void rprand_set_seed(unsigned long long seed);        // Set rprand_state for Xoshiro128**, seed is 64bit\nRPRANDAPI int rprand_get_value(int min, int max);               // Get random value within a range, min and max included\n\nRPRANDAPI int *rprand_load_sequence(unsigned int count, int min, int max); // Load pseudo-random numbers sequence with no duplicates\nRPRANDAPI void rprand_unload_sequence(int *sequence);           // Unload pseudo-random numbers sequence\n\n#ifdef __cplusplus\n}\n#endif\n\n#endif // RPRAND_H\n\n/***********************************************************************************\n*\n*   RPRAND IMPLEMENTATION\n*\n************************************************************************************/\n\n#if defined(RPRAND_IMPLEMENTATION)\n\n#include <stdlib.h>     // Required for: calloc(), free(), abs()\n#include <stdint.h>     // Required for data types: uint32_t, uint64_t\n\n//----------------------------------------------------------------------------------\n// Types and Structures Definition\n//----------------------------------------------------------------------------------\n// ...\n\n//----------------------------------------------------------------------------------\n// Global Variables Definition\n//----------------------------------------------------------------------------------\nstatic uint64_t rprand_seed = 0xAABBCCDD;       // SplitMix64 default seed (aligned to rprand_state)\nstatic uint32_t rprand_state[4] = {             // Xoshiro128** state, initialized by SplitMix64\n    0x96ea83c1,\n    0x218b21e5,\n    0xaa91febd,\n    0x976414d4\n};        \n\n//----------------------------------------------------------------------------------\n// Module internal functions declaration\n//----------------------------------------------------------------------------------\nstatic uint32_t rprand_xoshiro(void);           // Xoshiro128** generator (uses global rprand_state)\nstatic uint64_t rprand_splitmix64(void);        // SplitMix64 generator (uses seed to generate rprand_state)\n\n//----------------------------------------------------------------------------------\n// Module functions definition\n//----------------------------------------------------------------------------------\n// Set rprand_state for Xoshiro128**\n// NOTE: We use a custom generation algorithm using SplitMix64\nvoid rprand_set_seed(unsigned long long seed)\n{\n    rprand_seed = (uint64_t)seed;    // Set SplitMix64 seed for further use\n\n    // To generate the Xoshiro128** state, we use SplitMix64 generator first\n    // We generate 4 pseudo-random 64bit numbers that we combine using their LSB|MSB\n    rprand_state[0] = (uint32_t)(rprand_splitmix64() & 0xffffffff);\n    rprand_state[1] = (uint32_t)((rprand_splitmix64() & 0xffffffff00000000) >> 32);\n    rprand_state[2] = (uint32_t)(rprand_splitmix64() & 0xffffffff);\n    rprand_state[3] = (uint32_t)((rprand_splitmix64() & 0xffffffff00000000) >> 32);\n}\n\n// Get random value within a range, min and max included\nint rprand_get_value(int min, int max)\n{\n    int value = rprand_xoshiro()%(abs(max - min) + 1) + min;\n\n    return value;\n}\n\n// Load pseudo-random numbers sequence with no duplicates, min and max included\nint *rprand_load_sequence(unsigned int count, int min, int max)\n{\n    int *sequence = NULL;\n    \n    if (count > (unsigned int)(abs(max - min) + 1)) \n    {\n        RPRAND_LOG(\"WARNING: Sequence count required is greater than range provided\\n\");\n        //count = (max - min);\n        return sequence;\n    }\n\n    sequence = (int *)RPRAND_CALLOC(count, sizeof(int));\n\n    int value = 0;\n    bool value_is_dup = false;\n\n    for (unsigned int i = 0; i < count;)\n    {\n        value = ((unsigned int)rprand_xoshiro()%(abs(max - min) + 1)) + min;\n\n        for (unsigned int j = 0; j < i; j++)\n        {\n            if (sequence[j] == value)\n            {\n                value_is_dup = true;\n                break;\n            }\n        }\n\n        if (!value_is_dup)\n        {\n            sequence[i] = value;\n            i++;\n        }\n\n        value_is_dup = false;\n    }\n\n    return sequence;\n}\n\n// Unload pseudo-random numbers sequence\nvoid rprand_unload_sequence(int *sequence)\n{\n    RPRAND_FREE(sequence);\n    sequence = NULL;\n}\n\n//----------------------------------------------------------------------------------\n// Module internal functions definition\n//----------------------------------------------------------------------------------\nstatic inline uint32_t rprand_rotate_left(const uint32_t x, int k)\n{\n    return (x << k) | (x >> (32 - k));\n}\n\n// Xoshiro128** generator info:\n//   \n//   Written in 2018 by David Blackman and Sebastiano Vigna (vigna@acm.org)\n//   \n//   To the extent possible under law, the author has dedicated all copyright\n//   and related and neighboring rights to this software to the public domain\n//   worldwide. This software is distributed without any warranty.\n//   \n//   See <http://creativecommons.org/publicdomain/zero/1.0/>.\n//   \n//   This is xoshiro128** 1.1, one of our 32-bit all-purpose, rock-solid\n//   generators. It has excellent speed, a state size (128 bits) that is\n//   large enough for mild parallelism, and it passes all tests we are aware\n//   of.\n// \n//   Note that version 1.0 had mistakenly s[0] instead of s[1] as state\n//   word passed to the scrambler.\n// \n//   For generating just single-precision (i.e., 32-bit) floating-point\n//   numbers, xoshiro128+ is even faster.\n// \n//   The state must be seeded so that it is not everywhere zero.\n//\nuint32_t rprand_xoshiro(void)\n{\n    const uint32_t result = rprand_rotate_left(rprand_state[1]*5, 7)*9;\n    const uint32_t t = rprand_state[1] << 9;\n\n    rprand_state[2] ^= rprand_state[0];\n    rprand_state[3] ^= rprand_state[1];\n    rprand_state[1] ^= rprand_state[2];\n    rprand_state[0] ^= rprand_state[3];\n    \n    rprand_state[2] ^= t;\n    \n    rprand_state[3] = rprand_rotate_left(rprand_state[3], 11);\n\n    return result;\n}\n\n// SplitMix64 generator info:\n//   \n//   Written in 2015 by Sebastiano Vigna (vigna@acm.org)\n//   \n//   To the extent possible under law, the author has dedicated all copyright\n//   and related and neighboring rights to this software to the public domain\n//   worldwide. This software is distributed without any warranty.\n//   \n//   See <http://creativecommons.org/publicdomain/zero/1.0/>.\n//   \n//\n//   This is a fixed-increment version of Java 8's SplittableRandom generator\n//   See http://dx.doi.org/10.1145/2714064.2660195 and\n//   http://docs.oracle.com/javase/8/docs/api/java/util/SplittableRandom.html\n//   \n//   It is a very fast generator passing BigCrush, and it can be useful if\n//   for some reason you absolutely want 64 bits of state.\nuint64_t rprand_splitmix64()\n{\n    uint64_t z = (rprand_seed += 0x9e3779b97f4a7c15);\n    z = (z ^ (z >> 30))*0xbf58476d1ce4e5b9;\n    z = (z ^ (z >> 27))*0x94d049bb133111eb;\n    return z ^ (z >> 31);\n}\n\n#endif  // RPRAND_IMPLEMENTATION"
  },
  {
    "path": "external/tinyfiledialogs.c",
    "content": "/* SPDX-License-Identifier: Zlib\nCopyright (c) 2014 - 2024 Guillaume Vareille http://ysengrin.com\n\t ________________________________________________________________\n\t|                                                                |\n\t| 100% compatible C C++  ->  You can rename this .c file as .cpp |\n\t|________________________________________________________________|\n\n********* TINY FILE DIALOGS OFFICIAL WEBSITE IS ON SOURCEFORGE *********\n  _________\n /         \\ tinyfiledialogs.c v3.18.2 [Jun 8, 2024] zlib licence\n |tiny file| Unique code file created [November 9, 2014]\n | dialogs |\n \\____  ___/ http://tinyfiledialogs.sourceforge.net\n\t  \\|     git clone http://git.code.sf.net/p/tinyfiledialogs/code tinyfd\n\t\t\t  ____________________________________________\n\t\t\t |                                            |\n\t\t\t |   email: tinyfiledialogs at ysengrin.com   |\n\t\t\t |____________________________________________|\n  _________________________________________________________________________________\n |                                                                                 |\n | the windows only wchar_t UTF-16 prototypes are at the bottom of the header file |\n |_________________________________________________________________________________|\n  _________________________________________________________\n |                                                         |\n | on windows: - since v3.6 char is UTF-8 by default       |\n |             - if you want MBCS set tinyfd_winUtf8 to 0  |\n |             - functions like fopen expect MBCS          |\n |_________________________________________________________|\n\nIf you like tinyfiledialogs, please upvote my stackoverflow answer\nhttps://stackoverflow.com/a/47651444\n\n- License -\nThis software is provided 'as-is', without any express or implied\nwarranty.  In no event will the authors be held liable for any damages\narising from the use of this software.\nPermission is granted to anyone to use this software for any purpose,\nincluding commercial applications, and to alter it and redistribute it\nfreely, subject to the following restrictions:\n1. The origin of this software must not be misrepresented; you must not\nclaim that you wrote the original software.  If you use this software\nin a product, an acknowledgment in the product documentation would be\nappreciated but is not required.\n2. Altered source versions must be plainly marked as such, and must not be\nmisrepresented as being the original software.\n3. This notice may not be removed or altered from any source distribution.\n\n     __________________________________________\n    |  ______________________________________  |\n    | |                                      | |\n    | | DO NOT USE USER INPUT IN THE DIALOGS | |\n    | |______________________________________| |\n    |__________________________________________|\n*/\n\n\n#if defined(__GNUC__) || defined(__clang__)\n#ifndef _GNU_SOURCE\n #define _GNU_SOURCE /* used only to resolve symbolic links. Can be commented out */\n #ifndef _POSIX_C_SOURCE\n  #ifdef __FreeBSD__\n    #define _POSIX_C_SOURCE 199506L /* 199506L is enough for freebsd for realpath() */\n  #elif defined(__illumos__) || defined(__solaris__)\n    #define _POSIX_C_SOURCE 200112L /* illumos/solaris needs 200112L for realpath() */\n  #else\n    #define _POSIX_C_SOURCE 2 /* to accept POSIX 2 in old ANSI C standards */\n  #endif\n #endif\n#endif\n#endif\n\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include <ctype.h>\n#include <sys/stat.h>\n\n#ifdef _WIN32\n #ifdef __BORLANDC__\n  #define _getch getch\n #endif\n #ifndef _WIN32_WINNT\n  #define _WIN32_WINNT 0x0500\n #endif\n #include <windows.h>\n #include <commdlg.h>\n #include <shlobj.h>\n #include <conio.h>\n #include <direct.h>\n #define TINYFD_NOCCSUNICODE\n #define TINYFD_SLASH \"\\\\\"\n#else\n #include <limits.h>\n #include <unistd.h>\n #include <dirent.h> /* on old systems try <sys/dir.h> instead */\n #include <termios.h>\n #include <sys/utsname.h>\n #include <signal.h> /* on old systems try <sys/signal.h> instead */\n #define TINYFD_SLASH \"/\"\n#endif /* _WIN32 */\n\n#include \"tinyfiledialogs.h\"\n\n#define MAX_PATH_OR_CMD 1024 /* _MAX_PATH or MAX_PATH */\n\n#ifndef MAX_MULTIPLE_FILES\n#define MAX_MULTIPLE_FILES 1024\n#endif\n#define LOW_MULTIPLE_FILES 32\n\nchar tinyfd_version[8] = \"3.18.2\";\n\n/******************************************************************************************************/\n/**************************************** UTF-8 on Windows ********************************************/\n/******************************************************************************************************/\n#ifdef _WIN32\n/* if you want to use UTF-8 ( instead of the UTF-16/wchar_t functions at the end of tinyfiledialogs.h )\nMake sure your code is really prepared for UTF-8 (on windows, functions like fopen() expect MBCS and not UTF-8) */\nint tinyfd_winUtf8 = 1; /* on windows char strings can be 1:UTF-8(default) or 0:MBCS */\n/* for MBCS change this to 0, here or in your code */\n#endif\n/******************************************************************************************************/\n/******************************************************************************************************/\n/******************************************************************************************************/\n\nint tinyfd_verbose = 0 ; /* on unix: prints the command line calls */\nint tinyfd_silent = 1 ; /* 1 (default) or 0 : on unix, hide errors and warnings from called dialogs */\n\n/* Curses dialogs are difficult to use, on windows they are only ascii and uses the unix backslah */\nint tinyfd_allowCursesDialogs = 0 ; /* 0 (default) or 1 */\nint tinyfd_forceConsole = 0 ; /* 0 (default) or 1 */\n/* for unix & windows: 0 (graphic mode) or 1 (console mode).\n0: try to use a graphic solution, if it fails then it uses console mode.\n1: forces all dialogs into console mode even when the X server is present.\n   it can use the package dialog or dialog.exe.\n   on windows it only make sense for console applications */\n\nint tinyfd_assumeGraphicDisplay = 0; /* 0 (default) or 1  */\n/* some systems don't set the environment variable DISPLAY even when a graphic display is present.\nset this to 1 to tell tinyfiledialogs to assume the existence of a graphic display */\n\n\nchar tinyfd_response[1024];\n/* if you pass \"tinyfd_query\" as aTitle,\nthe functions will not display the dialogs\nbut return 0 for console mode, 1 for graphic mode.\ntinyfd_response is then filled with the retain solution.\npossible values for tinyfd_response are (all lowercase)\nfor graphic mode:\n  windows_wchar windows applescript kdialog zenity zenity3 yad matedialog\n  shellementary qarma python2-tkinter python3-tkinter python-dbus\n  perl-dbus gxmessage gmessage xmessage xdialog gdialog dunst\nfor console mode:\n  dialog whiptail basicinput no_solution */\n\nstatic int gWarningDisplayed = 0 ;\nstatic char gTitle[]=\"missing software! (we will try basic console input)\";\n\n#ifdef _WIN32\nchar tinyfd_needs[] = \"\\\n ___________\\n\\\n/           \\\\ \\n\\\n| tiny file |\\n\\\n|  dialogs  |\\n\\\n\\\\_____  ____/\\n\\\n      \\\\|\\\n\\ntiny file dialogs on Windows needs:\\\n\\n   a graphic display\\\n\\nor dialog.exe (curses console mode  ** Disabled by default **)\\\n\\nor a console for basic input\";\n#else\nchar tinyfd_needs[] = \"\\\n ___________\\n\\\n/           \\\\ \\n\\\n| tiny file |\\n\\\n|  dialogs  |\\n\\\n\\\\_____  ____/\\n\\\n      \\\\|\\\n\\ntiny file dialogs on UNIX needs:\\\n\\n   applescript or kdialog or yad or Xdialog\\\n\\nor zenity (or matedialog or shellementary or qarma)\\\n\\nor python (2 or 3) + tkinter + python-dbus (optional)\\\n\\nor dialog (opens console if needed) ** Disabled by default **\\\n\\nor xterm + bash (opens console for basic input)\\\n\\nor existing console for basic input.\";\n\n#endif\n\n#ifdef _MSC_VER\n#pragma warning(disable:4996) /* allows usage of strncpy, strcpy, strcat, sprintf, fopen */\n#pragma warning(disable:4100) /* allows usage of strncpy, strcpy, strcat, sprintf, fopen */\n#pragma warning(disable:4706) /* allows usage of strncpy, strcpy, strcat, sprintf, fopen */\n#endif\n\nstatic int getenvDISPLAY(void)\n{\n\t\treturn tinyfd_assumeGraphicDisplay || getenv(\"DISPLAY\");\n}\n\n\nstatic char * getCurDir(void)\n{\n\t\tstatic char lCurDir[MAX_PATH_OR_CMD];\n\t\treturn getcwd(lCurDir, sizeof(lCurDir));\n}\n\n\nstatic char * getPathWithoutFinalSlash(\n\t\tchar * aoDestination, /* make sure it is allocated, use _MAX_PATH */\n\t\tchar const * aSource) /* aoDestination and aSource can be the same */\n{\n\t\tchar const * lTmp ;\n\t\tif ( aSource )\n\t\t{\n\t\t\t\tlTmp = strrchr(aSource, '/');\n\t\t\t\tif (!lTmp)\n\t\t\t\t{\n\t\t\t\t\t\tlTmp = strrchr(aSource, '\\\\');\n\t\t\t\t}\n\t\t\t\tif (lTmp)\n\t\t\t\t{\n\t\t\t\t\t\tstrncpy(aoDestination, aSource, lTmp - aSource );\n\t\t\t\t\t\taoDestination[lTmp - aSource] = '\\0';\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\t\t* aoDestination = '\\0';\n\t\t\t\t}\n\t\t}\n\t\telse\n\t\t{\n\t\t\t\t* aoDestination = '\\0';\n\t\t}\n\t\treturn aoDestination;\n}\n\n\nstatic char * getLastName(\n\t\tchar * aoDestination, /* make sure it is allocated */\n\t\tchar const * aSource)\n{\n\t\t/* copy the last name after '/' or '\\' */\n\t\tchar const * lTmp ;\n\t\tif ( aSource )\n\t\t{\n\t\t\t\tlTmp = strrchr(aSource, '/');\n\t\t\t\tif (!lTmp)\n\t\t\t\t{\n\t\t\t\t\t\tlTmp = strrchr(aSource, '\\\\');\n\t\t\t\t}\n\t\t\t\tif (lTmp)\n\t\t\t\t{\n\t\t\t\t\t\tstrcpy(aoDestination, lTmp + 1);\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\t\tstrcpy(aoDestination, aSource);\n\t\t\t\t}\n\t\t}\n\t\telse\n\t\t{\n\t\t\t\t* aoDestination = '\\0';\n\t\t}\n\t\treturn aoDestination;\n}\n\n\nstatic void ensureFinalSlash( char * aioString )\n{\n\t\tif ( aioString && strlen( aioString ) )\n\t\t{\n\t\t\t\tchar * lastcar = aioString + strlen( aioString ) - 1 ;\n\t\t\t\tif ( strncmp( lastcar , TINYFD_SLASH , 1 ) )\n\t\t\t\t{\n\t\t\t\t\t\tstrcat( lastcar , TINYFD_SLASH ) ;\n\t\t\t\t}\n\t\t}\n}\n\n\nstatic void Hex2RGB( char const aHexRGB[8] , unsigned char aoResultRGB[3] )\n{\n\t\tchar lColorChannel[8] ;\n\t\tif ( aoResultRGB )\n\t\t{\n\t\t\t\tif ( aHexRGB )\n\t\t\t\t{\n\t\t\t\t\t\tstrcpy(lColorChannel, aHexRGB ) ;\n\t\t\t\t\t\taoResultRGB[2] = (unsigned char)strtoul(lColorChannel+5,NULL,16);\n\t\t\t\t\t\tlColorChannel[5] = '\\0';\n\t\t\t\t\t\taoResultRGB[1] = (unsigned char)strtoul(lColorChannel+3,NULL,16);\n\t\t\t\t\t\tlColorChannel[3] = '\\0';\n\t\t\t\t\t\taoResultRGB[0] = (unsigned char)strtoul(lColorChannel+1,NULL,16);\n/* printf(\"%d %d %d\\n\", aoResultRGB[0], aoResultRGB[1], aoResultRGB[2]); */\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\t\taoResultRGB[0]=0;\n\t\t\t\t\t\taoResultRGB[1]=0;\n\t\t\t\t\t\taoResultRGB[2]=0;\n\t\t\t\t}\n\t\t}\n}\n\nstatic void RGB2Hex( unsigned char const aRGB[3], char aoResultHexRGB[8] )\n{\n\t\tif ( aoResultHexRGB )\n\t\t{\n\t\t\t\tif ( aRGB )\n\t\t\t\t{\n#if (defined(__cplusplus ) && __cplusplus >= 201103L) || (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L) || defined(__clang__)\n\tsprintf(aoResultHexRGB, \"#%02hhx%02hhx%02hhx\", aRGB[0], aRGB[1], aRGB[2]);\n#else\n\tsprintf(aoResultHexRGB, \"#%02hx%02hx%02hx\", aRGB[0], aRGB[1], aRGB[2]);\n#endif\n\t\t\t\t\t\t /*printf(\"aoResultHexRGB %s\\n\", aoResultHexRGB);*/\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\t\taoResultHexRGB[0]=0;\n\t\t\t\t\t\taoResultHexRGB[1]=0;\n\t\t\t\t\t\taoResultHexRGB[2]=0;\n\t\t\t\t}\n\t\t}\n}\n\n\nvoid tfd_replaceSubStr( char const * aSource, char const * aOldSubStr,\n\t\t\t\t\t\tchar const * aNewSubStr, char * aoDestination )\n{\n\t\tchar const * pOccurence ;\n\t\tchar const * p ;\n\t\tchar const * lNewSubStr = \"\" ;\n\t\tsize_t lOldSubLen = strlen( aOldSubStr ) ;\n\n\t\tif ( ! aSource )\n\t\t{\n\t\t\t\t* aoDestination = '\\0' ;\n\t\t\t\treturn ;\n\t\t}\n\t\tif ( ! aOldSubStr )\n\t\t{\n\t\t\t\tstrcpy( aoDestination , aSource ) ;\n\t\t\t\treturn ;\n\t\t}\n\t\tif ( aNewSubStr )\n\t\t{\n\t\t\t\tlNewSubStr = aNewSubStr ;\n\t\t}\n\t\tp = aSource ;\n\t\t* aoDestination = '\\0' ;\n\t\twhile ( ( pOccurence = strstr( p , aOldSubStr ) ) != NULL )\n\t\t{\n\t\t\t\tstrncat( aoDestination , p , pOccurence - p ) ;\n\t\t\t\tstrcat( aoDestination , lNewSubStr ) ;\n\t\t\t\tp = pOccurence + lOldSubLen ;\n\t\t}\n\t\tstrcat( aoDestination , p ) ;\n}\n\n\nstatic int filenameValid( char const * aFileNameWithoutPath )\n{\n\t\tif ( ! aFileNameWithoutPath\n\t\t  || ! strlen(aFileNameWithoutPath)\n\t\t  || strpbrk(aFileNameWithoutPath , \"\\\\/:*?\\\"<>|\") )\n\t\t{\n\t\t\t\treturn 0 ;\n\t\t}\n\t\treturn 1 ;\n}\n\n#ifndef _WIN32\n\nstatic int fileExists( char const * aFilePathAndName )\n{\n\t\tFILE * lIn ;\n\t\tif ( ! aFilePathAndName || ! strlen(aFilePathAndName) )\n\t\t{\n\t\t\t\treturn 0 ;\n\t\t}\n\t\tlIn = fopen( aFilePathAndName , \"r\" ) ;\n\t\tif ( ! lIn )\n\t\t{\n\t\t\t\treturn 0 ;\n\t\t}\n\t\tfclose( lIn ) ;\n\t\treturn 1 ;\n}\n\n#endif\n\n\nstatic void wipefile(char const * aFilename)\n{\n\t\tint i;\n\t\tstruct stat st;\n\t\tFILE * lIn;\n\n\t\tif (stat(aFilename, &st) == 0)\n\t\t{\n\t\t\t\tif ((lIn = fopen(aFilename, \"w\")))\n\t\t\t\t{\n\t\t\t\t\t\tfor (i = 0; i < st.st_size; i++)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tfputc('A', lIn);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tfclose(lIn);\n\t\t\t\t}\n\t\t}\n}\n\n\nint tfd_quoteDetected(char const * aString)\n{\n\tchar const * p;\n\n\tif (!aString) return 0;\n\n\tp = aString;\n\tif ( strchr(p, '\\''))\n\t{\n\t\treturn 1;\n\t}\n\n\tif ( strchr(p, '\\\"'))\n\t{\n\t\treturn 1;\n\t}\n\n\tif ( strchr(p, '`'))\n\t{\n\t\treturn 1;\n\t}\n\n\tp = aString;\n\twhile ((p = strchr(p, '$')))\n\t{\n\t\tp ++ ;\n\t\tif ( ( * p == '(' ) || ( * p == '_' ) || isalpha( * p) ) return 1 ;\n\t}\n\n\treturn 0;\n}\n\n\nchar const * tinyfd_getGlobalChar(char const * aCharVariableName) /* to be called from C# (you don't need this in C or C++) */\n{\n\t\tif (!aCharVariableName || !strlen(aCharVariableName)) return NULL;\n\t\telse if (!strcmp(aCharVariableName, \"tinyfd_version\")) return tinyfd_version;\n\t\telse if (!strcmp(aCharVariableName, \"tinyfd_needs\")) return tinyfd_needs;\n\t\telse if (!strcmp(aCharVariableName, \"tinyfd_response\")) return tinyfd_response;\n\t\telse return NULL ;\n}\n\n\nint tinyfd_getGlobalInt(char const * aIntVariableName) /* to be called from C# (you don't need this in C or C++) */\n{\n\t\tif ( !aIntVariableName || !strlen(aIntVariableName) ) return -1 ;\n\t\telse if ( !strcmp(aIntVariableName, \"tinyfd_verbose\") ) return tinyfd_verbose ;\n\t\telse if ( !strcmp(aIntVariableName, \"tinyfd_silent\") ) return tinyfd_silent ;\n\t\telse if ( !strcmp(aIntVariableName, \"tinyfd_allowCursesDialogs\") ) return tinyfd_allowCursesDialogs ;\n\t\telse if ( !strcmp(aIntVariableName, \"tinyfd_forceConsole\") ) return tinyfd_forceConsole ;\n\t\telse if ( !strcmp(aIntVariableName, \"tinyfd_assumeGraphicDisplay\") ) return tinyfd_assumeGraphicDisplay ;\n#ifdef _WIN32\n\t\telse if ( !strcmp(aIntVariableName, \"tinyfd_winUtf8\") ) return tinyfd_winUtf8 ;\n#endif\n\t\telse return -1;\n}\n\n\nint tinyfd_setGlobalInt(char const * aIntVariableName, int aValue) /* to be called from C# (you don't need this in C or C++) */\n{\n\t\tif (!aIntVariableName || !strlen(aIntVariableName)) return -1 ;\n\t\telse if (!strcmp(aIntVariableName, \"tinyfd_verbose\")) { tinyfd_verbose = aValue; return tinyfd_verbose; }\n\t\telse if (!strcmp(aIntVariableName, \"tinyfd_silent\")) { tinyfd_silent = aValue; return tinyfd_silent; }\n\t\telse if (!strcmp(aIntVariableName, \"tinyfd_allowCursesDialogs\")) { tinyfd_allowCursesDialogs = aValue; return tinyfd_allowCursesDialogs; }\n\t\telse if (!strcmp(aIntVariableName, \"tinyfd_forceConsole\")) { tinyfd_forceConsole = aValue; return tinyfd_forceConsole; }\n\t\telse if (!strcmp(aIntVariableName, \"tinyfd_assumeGraphicDisplay\")) { tinyfd_assumeGraphicDisplay = aValue; return tinyfd_assumeGraphicDisplay; }\n#ifdef _WIN32\n\t\telse if (!strcmp(aIntVariableName, \"tinyfd_winUtf8\")) { tinyfd_winUtf8 = aValue; return tinyfd_winUtf8; }\n#endif\n\t\telse return -1;\n}\n\n\n#ifdef _WIN32\nstatic int powershellPresent(void)\n{ /*only on vista and above (or installed on xp)*/\n\tstatic int lPowershellPresent = -1;\n\tchar lBuff[MAX_PATH_OR_CMD];\n\tFILE* lIn;\n\tchar const* lString = \"powershell.exe\";\n\n\tif (lPowershellPresent < 0)\n\t{\n\t\tif (!(lIn = _popen(\"where powershell.exe\", \"r\")))\n\t\t{\n\t\t\tlPowershellPresent = 0;\n\t\t\treturn 0;\n\t\t}\n\t\twhile (fgets(lBuff, sizeof(lBuff), lIn) != NULL)\n\t\t{\n\t\t}\n\t\t_pclose(lIn);\n\t\tif (lBuff[strlen(lBuff) - 1] == '\\n')\n\t\t{\n\t\t\tlBuff[strlen(lBuff) - 1] = '\\0';\n\t\t}\n\t\tif (strcmp(lBuff + strlen(lBuff) - strlen(lString), lString))\n\t\t{\n\t\t\tlPowershellPresent = 0;\n\t\t}\n\t\telse\n\t\t{\n\t\t\tlPowershellPresent = 1;\n\t\t}\n\t}\n\treturn lPowershellPresent;\n}\n\nstatic int windowsVersion(void)\n{\n#if !defined(__MINGW32__) || defined(__MINGW64_VERSION_MAJOR)\n\ttypedef LONG NTSTATUS  ;\n\ttypedef NTSTATUS(WINAPI* RtlGetVersionPtr)(PRTL_OSVERSIONINFOW);\n\tHMODULE hMod;\n\tRtlGetVersionPtr lFxPtr;\n\tRTL_OSVERSIONINFOW lRovi = { 0 };\n\n\thMod = GetModuleHandleW(L\"ntdll.dll\");\n\tif (hMod) {\n\t\tlFxPtr = (RtlGetVersionPtr)GetProcAddress(hMod, \"RtlGetVersion\");\n\t\tif (lFxPtr)\n\t\t{\n\t\t\tlRovi.dwOSVersionInfoSize = sizeof(lRovi);\n\t\t\tif (!lFxPtr(&lRovi))\n\t\t\t{\n\t\t\t\treturn lRovi.dwMajorVersion;\n\t\t\t}\n\t\t}\n\t}\n#endif\n\tif (powershellPresent()) return 6; /*minimum is vista or installed on xp*/\n\treturn 0;\n}\n\n\nstatic void replaceChr(char * aString, char aOldChr, char aNewChr)\n{\n\t\tchar * p;\n\n\t\tif (!aString) return;\n\t\tif (aOldChr == aNewChr) return;\n\n\t\tp = aString;\n\t\twhile ((p = strchr(p, aOldChr)))\n\t\t{\n\t\t\t\t*p = aNewChr;\n\t\t\t\tp++;\n\t\t}\n\t\treturn;\n}\n\n\n#if !defined(WC_ERR_INVALID_CHARS)\n/* undefined prior to Vista, so not yet in MINGW header file */\n#define WC_ERR_INVALID_CHARS 0x00000000 /* 0x00000080 for MINGW maybe ? */\n#endif\n\nstatic int sizeUtf16From8(char const * aUtf8string)\n{\n\t\treturn MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS,\n\t\t\t\taUtf8string, -1, NULL, 0);\n}\n\n\nstatic int sizeUtf16FromMbcs(char const * aMbcsString)\n{\n\t\treturn MultiByteToWideChar(CP_ACP, MB_ERR_INVALID_CHARS,\n\t\t\t\taMbcsString, -1, NULL, 0);\n}\n\n\nstatic int sizeUtf8(wchar_t const * aUtf16string)\n{\n\t\treturn WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS,\n\t\t\t\taUtf16string, -1, NULL, 0, NULL, NULL);\n}\n\n\nstatic int sizeMbcs(wchar_t const * aMbcsString)\n{\n\t\tint lRes = WideCharToMultiByte(CP_ACP, 0,\n\t\t\t\taMbcsString, -1, NULL, 0, NULL, NULL);\n\t\t/* DWORD licic = GetLastError(); */\n\t\treturn lRes;\n}\n\n\nwchar_t* tinyfd_mbcsTo16(char const* aMbcsString)\n{\n\tstatic wchar_t* lMbcsString = NULL;\n\tint lSize;\n\n\tfree(lMbcsString);\n\tif (!aMbcsString) { lMbcsString = NULL; return NULL; }\n\tlSize = sizeUtf16FromMbcs(aMbcsString);\n\tif (lSize)\n\t{\n\t\tlMbcsString = (wchar_t*) malloc(lSize * sizeof(wchar_t));\n\t\tlSize = MultiByteToWideChar(CP_ACP, 0, aMbcsString, -1, lMbcsString, lSize);\n\t}\n\telse wcscpy(lMbcsString, L\"\");\n\treturn lMbcsString;\n}\n\n\nwchar_t * tinyfd_utf8to16(char const * aUtf8string)\n{\n\t\tstatic wchar_t * lUtf16string = NULL;\n\t\tint lSize;\n\n\t\tfree(lUtf16string);\n\t\tif (!aUtf8string) {lUtf16string = NULL; return NULL;}\n\t\tlSize = sizeUtf16From8(aUtf8string);\n\tif (lSize)\n\t{\n\t\tlUtf16string = (wchar_t*) malloc(lSize * sizeof(wchar_t));\n\t\tlSize = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS,\n\t\t\taUtf8string, -1, lUtf16string, lSize);\n\t\treturn lUtf16string;\n\t}\n\telse\n\t{\n\t\t/* let's try mbcs anyway */\n\t\tlUtf16string = NULL;\n\t\treturn tinyfd_mbcsTo16(aUtf8string);\n\t}\n}\n\n\nchar * tinyfd_utf16toMbcs(wchar_t const * aUtf16string)\n{\n\t\tstatic char * lMbcsString = NULL;\n\t\tint lSize;\n\n\t\tfree(lMbcsString);\n\t\tif (!aUtf16string) { lMbcsString = NULL; return NULL; }\n\t\tlSize = sizeMbcs(aUtf16string);\n\tif (lSize)\n\t{\n\t\tlMbcsString = (char*) malloc(lSize);\n\t\tlSize = WideCharToMultiByte(CP_ACP, 0, aUtf16string, -1, lMbcsString, lSize, NULL, NULL);\n\t}\n\telse strcpy(lMbcsString, \"\");\n\t\treturn lMbcsString;\n}\n\n\nchar * tinyfd_utf8toMbcs(char const * aUtf8string)\n{\n\t\twchar_t const * lUtf16string;\n\t\tlUtf16string = tinyfd_utf8to16(aUtf8string);\n\t\treturn tinyfd_utf16toMbcs(lUtf16string);\n}\n\n\nchar * tinyfd_utf16to8(wchar_t const * aUtf16string)\n{\n\t\tstatic char * lUtf8string = NULL;\n\t\tint lSize;\n\n\t\tfree(lUtf8string);\n\t\tif (!aUtf16string) { lUtf8string = NULL; return NULL; }\n\t\tlSize = sizeUtf8(aUtf16string);\n\tif (lSize)\n\t{\n\t\tlUtf8string = (char*) malloc(lSize);\n\t\tlSize = WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, aUtf16string, -1, lUtf8string, lSize, NULL, NULL);\n\t}\n\telse strcpy(lUtf8string, \"\");\n\t\treturn lUtf8string;\n}\n\n\nchar * tinyfd_mbcsTo8(char const * aMbcsString)\n{\n\t\twchar_t const * lUtf16string;\n\t\tlUtf16string = tinyfd_mbcsTo16(aMbcsString);\n\t\treturn tinyfd_utf16to8(lUtf16string);\n}\n\n\nvoid tinyfd_beep(void)\n{\n\tif (windowsVersion() > 5) Beep(440, 300);\n\telse MessageBeep(MB_OK);\n}\n\n\nstatic void wipefileW(wchar_t const * aFilename)\n{\n\t\tint i;\n\t\tFILE * lIn;\n#if (defined(__MINGW32_MAJOR_VERSION) && !defined(__MINGW64__) && (__MINGW32_MAJOR_VERSION <= 3)) || defined(__BORLANDC__) || defined(__WATCOMC__)\n\t\tstruct _stat st;\n\t\tif (_wstat(aFilename, &st) == 0)\n#else\n\t\tstruct __stat64 st;\n\t\tif (_wstat64(aFilename, &st) == 0)\n#endif\n\t\t{\n\t\t\t\tif ((lIn = _wfopen(aFilename, L\"w\")))\n\t\t\t\t{\n\t\t\t\t\t\tfor (i = 0; i < st.st_size; i++)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tfputc('A', lIn);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tfclose(lIn);\n\t\t\t\t}\n\t\t}\n}\n\n\nstatic wchar_t * getPathWithoutFinalSlashW(\n\t\twchar_t * aoDestination, /* make sure it is allocated, use _MAX_PATH */\n\t\twchar_t const * aSource) /* aoDestination and aSource can be the same */\n{\n\t\twchar_t const * lTmp;\n\t\tif (aSource)\n\t\t{\n\t\t\t\tlTmp = wcsrchr(aSource, L'/');\n\t\t\t\tif (!lTmp)\n\t\t\t\t{\n\t\t\t\t\t\tlTmp = wcsrchr(aSource, L'\\\\');\n\t\t\t\t}\n\t\t\t\tif (lTmp)\n\t\t\t\t{\n\t\t\t\t\t\twcsncpy(aoDestination, aSource, lTmp - aSource);\n\t\t\t\t\t\taoDestination[lTmp - aSource] = L'\\0';\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\t\t*aoDestination = L'\\0';\n\t\t\t\t}\n\t\t}\n\t\telse\n\t\t{\n\t\t\t\t*aoDestination = L'\\0';\n\t\t}\n\t\treturn aoDestination;\n}\n\n\nstatic wchar_t * getLastNameW(\n\t\twchar_t * aoDestination, /* make sure it is allocated */\n\t\twchar_t const * aSource)\n{\n\t\t/* copy the last name after '/' or '\\' */\n\t\twchar_t const * lTmp;\n\t\tif (aSource)\n\t\t{\n\t\t\t\tlTmp = wcsrchr(aSource, L'/');\n\t\t\t\tif (!lTmp)\n\t\t\t\t{\n\t\t\t\t\t\tlTmp = wcsrchr(aSource, L'\\\\');\n\t\t\t\t}\n\t\t\t\tif (lTmp)\n\t\t\t\t{\n\t\t\t\t\t\twcscpy(aoDestination, lTmp + 1);\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\t\twcscpy(aoDestination, aSource);\n\t\t\t\t}\n\t\t}\n\t\telse\n\t\t{\n\t\t\t\t*aoDestination = L'\\0';\n\t\t}\n\t\treturn aoDestination;\n}\n\n\nstatic void Hex2RGBW(wchar_t const aHexRGB[8], unsigned char aoResultRGB[3])\n{\n\t\twchar_t lColorChannel[8];\n\t\tif (aoResultRGB)\n\t\t{\n\t\t\t\tif (aHexRGB)\n\t\t\t\t{\n\t\t\t\t\t\twcscpy(lColorChannel, aHexRGB);\n\t\t\t\t\t\taoResultRGB[2] = (unsigned char)wcstoul(lColorChannel + 5, NULL, 16);\n\t\t\t\t\t\tlColorChannel[5] = '\\0';\n\t\t\t\t\t\taoResultRGB[1] = (unsigned char)wcstoul(lColorChannel + 3, NULL, 16);\n\t\t\t\t\t\tlColorChannel[3] = '\\0';\n\t\t\t\t\t\taoResultRGB[0] = (unsigned char)wcstoul(lColorChannel + 1, NULL, 16);\n\t\t\t\t\t\t/* printf(\"%d %d %d\\n\", aoResultRGB[0], aoResultRGB[1], aoResultRGB[2]); */\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\t\taoResultRGB[0] = 0;\n\t\t\t\t\t\taoResultRGB[1] = 0;\n\t\t\t\t\t\taoResultRGB[2] = 0;\n\t\t\t\t}\n\t\t}\n}\n\n\nstatic void RGB2HexW( unsigned char const aRGB[3], wchar_t aoResultHexRGB[8])\n{\n#if (defined(__cplusplus ) && __cplusplus >= 201103L) || (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L) || defined(__clang__)\n\t\twchar_t const * const lPrintFormat = L\"#%02hhx%02hhx%02hhx\";\n#else\n\t\twchar_t const * const lPrintFormat = L\"#%02hx%02hx%02hx\";\n#endif\n\n\t\tif (aoResultHexRGB)\n\t\t{\n\t\t\t\tif (aRGB)\n\t\t\t\t{\n\t\t\t\t\t\t/* wprintf(L\"aoResultHexRGB %s\\n\", aoResultHexRGB); */\n#if !defined(__BORLANDC__) && !defined(__TINYC__) && !(defined(__MINGW32__) && !defined(__MINGW64_VERSION_MAJOR))\n\t\t\t\t\t\t\t\t\t\tswprintf(aoResultHexRGB, 8, lPrintFormat, aRGB[0], aRGB[1], aRGB[2]);\n#else\n\t\t\t\t\t\t\t\t\t\tswprintf(aoResultHexRGB, lPrintFormat, aRGB[0], aRGB[1], aRGB[2]);\n#endif\n\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\t\taoResultHexRGB[0] = 0;\n\t\t\t\t\t\taoResultHexRGB[1] = 0;\n\t\t\t\t\t\taoResultHexRGB[2] = 0;\n\t\t\t\t}\n\t\t}\n}\n\n\nstatic int dirExists(char const * aDirPath)\n{\n#if (defined(__MINGW32_MAJOR_VERSION) && !defined(__MINGW64__) && (__MINGW32_MAJOR_VERSION <= 3)) || defined(__BORLANDC__) || defined(__WATCOMC__)\n\tstruct _stat lInfo;\n#else\n\tstruct __stat64 lInfo;\n#endif\n\t\twchar_t * lTmpWChar;\n\t\tint lStatRet;\n\t\t\t\tsize_t lDirLen;\n\n\t\t\t\tif (!aDirPath)\n\t\t\t\t\t\treturn 0;\n\t\t\t\tlDirLen = strlen(aDirPath);\n\t\t\t\tif (!lDirLen)\n\t\t\t\t\t\treturn 1;\n\t\t\t\tif ( (lDirLen == 2) && (aDirPath[1] == ':') )\n\t\t\t\t\t\treturn 1;\n\n\t\tif (tinyfd_winUtf8)\n\t\t{\n\t\t\t\t\t\tlTmpWChar = tinyfd_utf8to16(aDirPath);\n#if (defined(__MINGW32_MAJOR_VERSION) && !defined(__MINGW64__) && (__MINGW32_MAJOR_VERSION <= 3)) || defined(__BORLANDC__) || defined(__WATCOMC__)\n\t\t\tlStatRet = _wstat(lTmpWChar, &lInfo);\n#else\n\t\t\tlStatRet = _wstat64(lTmpWChar, &lInfo);\n#endif\n\t\t\tif (lStatRet != 0)\n\t\t\t\t\treturn 0;\n\t\t\telse if (lInfo.st_mode & S_IFDIR)\n\t\t\t\t\treturn 1;\n\t\t\telse\n\t\t\t\t\t\treturn 0;\n\t\t}\n#if (defined(__MINGW32_MAJOR_VERSION) && !defined(__MINGW64__) && (__MINGW32_MAJOR_VERSION <= 3)) || defined(__BORLANDC__) || defined(__WATCOMC__)\n\t\telse if (_stat(aDirPath, &lInfo) != 0)\n#else\n\t\telse if (_stat64(aDirPath, &lInfo) != 0)\n#endif\n\t\t\t\treturn 0;\n\t\telse if (lInfo.st_mode & S_IFDIR)\n\t\t\t\treturn 1;\n\t\telse\n\t\t\t\treturn 0;\n}\n\n\nstatic int fileExists(char const * aFilePathAndName)\n{\n#if (defined(__MINGW32_MAJOR_VERSION) && !defined(__MINGW64__) && (__MINGW32_MAJOR_VERSION <= 3)) || defined(__BORLANDC__) || defined(__WATCOMC__)\n\tstruct _stat lInfo;\n#else\n\tstruct __stat64 lInfo;\n#endif\n\t\twchar_t * lTmpWChar;\n\t\tint lStatRet;\n\t\tFILE * lIn;\n\n\t\tif (!aFilePathAndName || !strlen(aFilePathAndName))\n\t\t{\n\t\t\t\treturn 0;\n\t\t}\n\n\t\tif (tinyfd_winUtf8)\n\t\t{\n\t\t\t\t\t\tlTmpWChar = tinyfd_utf8to16(aFilePathAndName);\n#if (defined(__MINGW32_MAJOR_VERSION) && !defined(__MINGW64__) && (__MINGW32_MAJOR_VERSION <= 3)) || defined(__BORLANDC__) || defined(__WATCOMC__)\n\t\t\tlStatRet = _wstat(lTmpWChar, &lInfo);\n#else\n\t\t\tlStatRet = _wstat64(lTmpWChar, &lInfo);\n#endif\n\n\t\t\tif (lStatRet != 0)\n\t\t\t\t\treturn 0;\n\t\t\telse if (lInfo.st_mode & _S_IFREG)\n\t\t\t\t\treturn 1;\n\t\t\telse\n\t\t\t\t\treturn 0;\n\t\t}\n\t\telse\n\t\t{\n\t\t\t\tlIn = fopen(aFilePathAndName, \"r\");\n\t\t\t\tif (!lIn)\n\t\t\t\t{\n\t\t\t\t\t\treturn 0;\n\t\t\t\t}\n\t\t\t\tfclose(lIn);\n\t\t\t\treturn 1;\n\t\t}\n}\n\nstatic void replaceWchar(wchar_t * aString,\n\t\twchar_t aOldChr,\n\t\twchar_t aNewChr)\n{\n\t\twchar_t * p;\n\n\t\tif (!aString)\n\t\t{\n\t\t\t\treturn ;\n\t\t}\n\n\t\tif (aOldChr == aNewChr)\n\t\t{\n\t\t\t\treturn ;\n\t\t}\n\n\t\tp = aString;\n\t\twhile ((p = wcsrchr(p, aOldChr)))\n\t\t{\n\t\t\t\t*p = aNewChr;\n#ifdef TINYFD_NOCCSUNICODE\n\t\t\t\tp++;\n#endif\n\t\t\t\tp++;\n\t\t}\n\t\treturn ;\n}\n\n\nstatic int quoteDetectedW(wchar_t const * aString)\n{\n\t\twchar_t const * p;\n\n\t\tif (!aString) return 0;\n\n\t\tp = aString;\n\t\twhile ((p = wcsrchr(p, L'\\'')))\n\t\t{\n\t\t\t\treturn 1;\n\t\t}\n\n\t\tp = aString;\n\t\twhile ((p = wcsrchr(p, L'\\\"')))\n\t\t{\n\t\t\t\treturn 1;\n\t\t}\n\n\t\treturn 0;\n}\n\n#endif /* _WIN32 */\n\n/* source and destination can be the same or ovelap*/\nstatic char * ensureFilesExist(char * aDestination,\n\t\tchar const * aSourcePathsAndNames)\n{\n\t\tchar * lDestination = aDestination;\n\t\tchar const * p;\n\t\tchar const * p2;\n\t\tsize_t lLen;\n\n\t\tif (!aSourcePathsAndNames)\n\t\t{\n\t\t\t\treturn NULL;\n\t\t}\n\t\tlLen = strlen(aSourcePathsAndNames);\n\t\tif (!lLen)\n\t\t{\n\t\t\t\treturn NULL;\n\t\t}\n\n\t\tp = aSourcePathsAndNames;\n\t\twhile ((p2 = strchr(p, '|')) != NULL)\n\t\t{\n\t\t\t\tlLen = p2 - p;\n\t\t\t\tmemmove(lDestination, p, lLen);\n\t\t\t\tlDestination[lLen] = '\\0';\n\t\t\t\tif (fileExists(lDestination))\n\t\t\t\t{\n\t\t\t\t\t\tlDestination += lLen;\n\t\t\t\t\t\t*lDestination = '|';\n\t\t\t\t\t\tlDestination++;\n\t\t\t\t}\n\t\t\t\tp = p2 + 1;\n\t\t}\n\t\tif (fileExists(p))\n\t\t{\n\t\t\t\tlLen = strlen(p);\n\t\t\t\tmemmove(lDestination, p, lLen);\n\t\t\t\tlDestination[lLen] = '\\0';\n\t\t}\n\t\telse\n\t\t{\n\t\t\t\t*(lDestination - 1) = '\\0';\n\t\t}\n\t\treturn aDestination;\n}\n\n#ifdef _WIN32\n\nstatic int __stdcall EnumThreadWndProc(HWND hwnd, LPARAM lParam)\n{\n\t\twchar_t lTitleName[MAX_PATH];\n\t\twchar_t const* lDialogTitle = (wchar_t const *) lParam;\n\n\t\tGetWindowTextW(hwnd, lTitleName, MAX_PATH);\n\t\t/* wprintf(L\"lTitleName %ls \\n\", lTitleName);  */\n\n\t\tif (wcscmp(lDialogTitle, lTitleName) == 0)\n\t\t{\n\t\t\t\tSetWindowPos(hwnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE);\n\t\t\t\treturn 0;\n\t\t}\n\t\treturn 1;\n}\n\n\nstatic void hiddenConsoleW(wchar_t const * aString, wchar_t const * aDialogTitle, int aInFront)\n{\n\t\tSTARTUPINFOW StartupInfo;\n\t\tPROCESS_INFORMATION ProcessInfo;\n\n\t\tif (!aString || !wcslen(aString) ) return;\n\n\t\tmemset(&StartupInfo, 0, sizeof(StartupInfo));\n\t\tStartupInfo.cb = sizeof(STARTUPINFOW);\n\t\tStartupInfo.dwFlags = STARTF_USESHOWWINDOW;\n\t\tStartupInfo.wShowWindow = SW_HIDE;\n\n\t\tif (!CreateProcessW(NULL, (LPWSTR)aString, NULL, NULL, FALSE,\n\t\t\t\t\t\t\t\tCREATE_NEW_CONSOLE, NULL, NULL,\n\t\t\t\t\t\t\t\t&StartupInfo, &ProcessInfo))\n\t\t{\n\t\t\t\treturn; /* GetLastError(); */\n\t\t}\n\n\t\tWaitForInputIdle(ProcessInfo.hProcess, INFINITE);\n\t\tif (aInFront)\n\t\t{\n\t\t\t\twhile (EnumWindows(EnumThreadWndProc, (LPARAM)aDialogTitle)) {}\n\t\t}\n\t\tWaitForSingleObject(ProcessInfo.hProcess, INFINITE);\n\t\tCloseHandle(ProcessInfo.hThread);\n\t\tCloseHandle(ProcessInfo.hProcess);\n}\n\n\nint tinyfd_messageBoxW(\n\t\twchar_t const * aTitle, /* NULL or \"\" */\n\t\twchar_t const * aMessage, /* NULL or \"\"  may contain \\n and \\t */\n\t\twchar_t const * aDialogType, /* \"ok\" \"okcancel\" \"yesno\" \"yesnocancel\" */\n\t\twchar_t const * aIconType, /* \"info\" \"warning\" \"error\" \"question\" */\n\t\tint aDefaultButton) /* 0 for cancel/no , 1 for ok/yes , 2 for no in yesnocancel */\n{\n\t\tint lBoxReturnValue;\n\t\tUINT aCode;\n\n\t\tif (aTitle&&!wcscmp(aTitle, L\"tinyfd_query\")){ strcpy(tinyfd_response, \"windows_wchar\"); return 1; }\n\n\t\t/*if (quoteDetectedW(aTitle)) return tinyfd_messageBoxW(L\"INVALID TITLE WITH QUOTES\", aMessage, aDialogType, aIconType, aDefaultButton);\n\t\tif (quoteDetectedW(aMessage)) return tinyfd_messageBoxW(aTitle, L\"INVALID MESSAGE WITH QUOTES\", aDialogType, aIconType, aDefaultButton);*/\n\n\t\tif (aIconType && !wcscmp(L\"warning\", aIconType))\n\t\t{\n\t\t\t\taCode = MB_ICONWARNING;\n\t\t}\n\t\telse if (aIconType && !wcscmp(L\"error\", aIconType))\n\t\t{\n\t\t\t\taCode = MB_ICONERROR;\n\t\t}\n\t\telse if (aIconType && !wcscmp(L\"question\", aIconType))\n\t\t{\n\t\t\t\taCode = MB_ICONQUESTION;\n\t\t}\n\t\telse\n\t\t{\n\t\t\t\taCode = MB_ICONINFORMATION;\n\t\t}\n\n\t\tif (aDialogType && !wcscmp(L\"okcancel\", aDialogType))\n\t\t{\n\t\t\t\taCode += MB_OKCANCEL;\n\t\t\t\tif (!aDefaultButton)\n\t\t\t\t{\n\t\t\t\t\t\taCode += MB_DEFBUTTON2;\n\t\t\t\t}\n\t\t}\n\t\telse if (aDialogType && !wcscmp(L\"yesno\", aDialogType))\n\t\t{\n\t\t\t\taCode += MB_YESNO;\n\t\t\t\tif (!aDefaultButton)\n\t\t\t\t{\n\t\t\t\t\t\taCode += MB_DEFBUTTON2;\n\t\t\t\t}\n\t\t}\n\t\telse if (aDialogType && !wcscmp(L\"yesnocancel\", aDialogType))\n\t\t{\n\t\t\taCode += MB_YESNOCANCEL;\n\t\t\tif (aDefaultButton == 1)\n\t\t\t{\n\t\t\t\taCode += MB_DEFBUTTON1;\n\t\t\t}\n\t\t\telse if (aDefaultButton == 2)\n\t\t\t{\n\t\t\t\taCode += MB_DEFBUTTON2;\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\taCode += MB_DEFBUTTON3;\n\t\t\t}\n\t\t}\n\t\telse\n\t\t{\n\t\t\t\taCode += MB_OK;\n\t\t}\n\n\t\taCode += MB_TOPMOST;\n\n\t\tlBoxReturnValue = MessageBoxW(GetForegroundWindow(), aMessage, aTitle, aCode);\n\n\t\tif ( (lBoxReturnValue == IDNO) && (aDialogType && !wcscmp(L\"yesnocancel\", aDialogType)) )\n\t\t{\n\t\t\treturn 2;\n\t\t}\n\t\telse if ( (lBoxReturnValue == IDOK) || (lBoxReturnValue == IDYES) )\n\t\t{\n\t\t\treturn 1;\n\t\t}\n\t\telse\n\t\t{\n\t\t\treturn 0;\n\t\t}\n}\n\n\n/* int tinyfd_notifyPopupW_ORIGINAL(\n\t\twchar_t const * aTitle,\n\t\twchar_t const * aMessage,\n\t\twchar_t const * aIconType)\n{\n\t\twchar_t * lDialogString;\n\t\tsize_t lTitleLen;\n\t\tsize_t lMessageLen;\n\t\tsize_t lDialogStringLen;\n\n\t\tif (aTitle && !wcscmp(aTitle, L\"tinyfd_query\")) { strcpy(tinyfd_response, \"windows_wchar\"); return 1; }\n\n\t\tif (quoteDetectedW(aTitle)) return tinyfd_notifyPopupW(L\"INVALID TITLE WITH QUOTES\", aMessage, aIconType);\n\t\tif (quoteDetectedW(aMessage)) return tinyfd_notifyPopupW(aTitle, L\"INVALID MESSAGE WITH QUOTES\", aIconType);\n\n\t\tlTitleLen = aTitle ? wcslen(aTitle) : 0;\n\t\tlMessageLen = aMessage ? wcslen(aMessage) : 0;\n\t\tlDialogStringLen = 3 * MAX_PATH_OR_CMD + lTitleLen + lMessageLen;\n\t\tlDialogString = (wchar_t *) malloc(2 * lDialogStringLen);\n\t\tif (!lDialogString) return 0;\n\n\t\twcscpy(lDialogString, L\"powershell.exe -executionpolicy bypass -command \\\"\\\nfunction Show-BalloonTip {\\\n[cmdletbinding()] \\\nparam( \\\n[string]$Title = ' ', \\\n[string]$Message = ' ', \\\n[ValidateSet('info', 'warning', 'error')] \\\n[string]$IconType = 'info');\\\n[system.Reflection.Assembly]::LoadWithPartialName('System.Windows.Forms') | Out-Null ; \\\n$balloon = New-Object System.Windows.Forms.NotifyIcon ; \\\n$path = Get-Process -id $pid | Select-Object -ExpandProperty Path ; \\\n$icon = [System.Drawing.Icon]::ExtractAssociatedIcon($path) ;\");\n\n\t\twcscat(lDialogString, L\"\\\n$balloon.Icon = $icon ; \\\n$balloon.BalloonTipIcon = $IconType ; \\\n$balloon.BalloonTipText = $Message ; \\\n$balloon.BalloonTipTitle = $Title ; \\\n$balloon.Text = 'tinyfiledialogs' ; \\\n$balloon.Visible = $true ; \\\n$balloon.ShowBalloonTip(5000)};\\\nShow-BalloonTip\");\n\n\t\tif (aTitle && wcslen(aTitle))\n\t\t{\n\t\t\t\twcscat(lDialogString, L\" -Title '\");\n\t\t\t\twcscat(lDialogString, aTitle);\n\t\t\t\twcscat(lDialogString, L\"'\");\n\t\t}\n\t\tif (aMessage && wcslen(aMessage))\n\t\t{\n\t\t\t\twcscat(lDialogString, L\" -Message '\");\n\t\t\t\twcscat(lDialogString, aMessage);\n\t\t\t\twcscat(lDialogString, L\"'\");\n\t\t}\n\t\tif (aMessage && wcslen(aIconType))\n\t\t{\n\t\t\t\twcscat(lDialogString, L\" -IconType '\");\n\t\t\t\twcscat(lDialogString, aIconType);\n\t\t\t\twcscat(lDialogString, L\"'\");\n\t\t}\n\t\twcscat(lDialogString, L\"\\\"\");\n\n\t\thiddenConsoleW(lDialogString, aTitle, 0);\n\t\tfree(lDialogString);\n\t\treturn 1;\n}*/\n\n\n/* return has only meaning for tinyfd_query */\nint tinyfd_notifyPopupW(\n\twchar_t const* aTitle, /* NULL or L\"\" */\n\twchar_t const* aMessage, /* NULL or L\"\" may contain \\n \\t */\n\twchar_t const* aIconType) /* L\"info\" L\"warning\" L\"error\" */\n{\n\twchar_t* lDialogString;\n\tsize_t lTitleLen;\n\tsize_t lMessageLen;\n\tsize_t lDialogStringLen;\n\n\tFILE* lIn;\n\n\tif (aTitle && !wcscmp(aTitle, L\"tinyfd_query\")) { strcpy(tinyfd_response, \"windows_wchar\"); return 1; }\n\n\tif (quoteDetectedW(aTitle)) return tinyfd_notifyPopupW(L\"INVALID TITLE WITH QUOTES\", aMessage, aIconType);\n\tif (quoteDetectedW(aMessage)) return tinyfd_notifyPopupW(aTitle, L\"INVALID MESSAGE WITH QUOTES\", aIconType);\n\n\tlTitleLen = aTitle ? wcslen(aTitle) : 0;\n\tlMessageLen = aMessage ? wcslen(aMessage) : 0;\n\tlDialogStringLen = 3 * MAX_PATH_OR_CMD + lTitleLen + lMessageLen;\n\tlDialogString = (wchar_t*)malloc(2 * lDialogStringLen);\n\tif (!lDialogString) return 0;\n\n\tswprintf(lDialogString,\n#if !defined(__BORLANDC__) && !defined(__TINYC__) && !(defined(__MINGW32__) && !defined(__MINGW64_VERSION_MAJOR))\n\t\tlDialogStringLen,\n#endif\n\t\tL\"%ls\\\\tinyfd.hta\", _wgetenv(L\"TEMP\"));\n\n\tlIn = _wfopen(lDialogString, L\"w\");\n\tif (!lIn)\n\t{\n\t\tfree(lDialogString);\n\t\treturn 0;\n\t}\n\n\twcscpy(lDialogString, L\"\\n\\\n<html>\\n\\\n<head>\\n\\\n<title>\");\n\tif ( aTitle && wcslen(aTitle) ) wcscat(lDialogString, aTitle);\n\twcscat(lDialogString, L\"</title>\\n\\\n</head>\\n\\\n<HTA:APPLICATION\\n\\\nSysMenu = 'no'\\n\\\nID = 'tinyfdHTA'\\n\\\nAPPLICATIONNAME = 'tinyfd_notifyPopup'\\n\\\nMINIMIZEBUTTON = 'no'\\n\\\nMAXIMIZEBUTTON = 'no'\\n\\\nBORDER = 'dialog'\\n\\\nSCROLL = 'no'\\n\\\nSINGLEINSTANCE = 'yes'\\n\\\nWINDOWSTATE = 'hidden'>\\n\\\n<script language = 'VBScript'>\\n\\\nintWidth = Screen.Width/4\\n\\\nintHeight = Screen.Height/10\\n\\\nResizeTo intWidth, intHeight\\n\\\nMoveTo Screen.Width * .7, Screen.Height * .8\\n\\\nresult = 0\\n\\\nSub Window_onLoad\\n\\\nidTimer = window.setTimeout(\\\"PausedSection\\\", 3000, \\\"VBScript\\\")\\n\\\nEnd Sub\\n\");\n\n\twcscat(lDialogString, L\"\\n\\\nSub PausedSection\\n\\\nwindow.Close\\n\\\nEnd Sub\\n\\\n</script>\\n\\\n<body style = 'background-color:#EEEEEE' onkeypress = 'vbs:Default_Buttons' align = 'top'>\\n\\\n<table width = '100%' height = '80%' align = 'center' border = '0'>\\n\\\n<tr border = '0'>\\n\\\n<td align = 'left' valign = 'middle' style='Font-Family:Arial'>\\n\");\n\n\twcscat(lDialogString, aMessage ? aMessage : L\"\");\n\n\twcscat(lDialogString, L\"\\n\\\n</body>\\n\\\n</html>\\n\\\n\");\n\n\tfputws(lDialogString, lIn);\n\tfclose(lIn);\n\n\tif (aTitle && wcslen(aTitle))\n\t{\n\t\twcscat(lDialogString, L\" -Title '\");\n\t\twcscat(lDialogString, aTitle);\n\t\twcscat(lDialogString, L\"'\");\n\t}\n\tif (aMessage && wcslen(aMessage))\n\t{\n\t\twcscat(lDialogString, L\" -Message '\");\n\t\twcscat(lDialogString, aMessage);\n\t\twcscat(lDialogString, L\"'\");\n\t}\n\tif (aMessage && wcslen(aIconType))\n\t{\n\t\twcscat(lDialogString, L\" -IconType '\");\n\t\twcscat(lDialogString, aIconType);\n\t\twcscat(lDialogString, L\"'\");\n\t}\n\twcscat(lDialogString, L\"\\\"\");\n\n\t/* wprintf ( L\"lDialogString: %ls\\n\" , lDialogString ) ; */\n\twcscpy(lDialogString,\n\t\tL\"cmd.exe /c mshta.exe \\\"%TEMP%\\\\tinyfd.hta\\\"\");\n\n\thiddenConsoleW(lDialogString, aTitle, 0);\n\tfree(lDialogString);\n\treturn 1;\n}\n\n\nwchar_t * tinyfd_inputBoxW(\n\t\twchar_t const * aTitle, /* NULL or L\"\" */\n\t\twchar_t const * aMessage, /* NULL or L\"\" (\\n and \\t have no effect) */\n\t\twchar_t const * aDefaultInput) /* L\"\" , if NULL it's a passwordBox */\n{\n\t\tstatic wchar_t lBuff[MAX_PATH_OR_CMD];\n\t\twchar_t * lDialogString;\n\t\tFILE * lIn;\n\t\tFILE * lFile;\n\t\tint lResult;\n\t\tsize_t lTitleLen;\n\t\tsize_t lMessageLen;\n\t\tsize_t lDialogStringLen;\n\n\t\tif (aTitle&&!wcscmp(aTitle, L\"tinyfd_query\")){ strcpy(tinyfd_response, \"windows_wchar\"); return (wchar_t *)1; }\n\n\t\tif (quoteDetectedW(aTitle)) return tinyfd_inputBoxW(L\"INVALID TITLE WITH QUOTES\", aMessage, aDefaultInput);\n\t\tif (quoteDetectedW(aMessage)) return tinyfd_inputBoxW(aTitle, L\"INVALID MESSAGE WITH QUOTES\", aDefaultInput);\n\t\tif (quoteDetectedW(aDefaultInput)) return tinyfd_inputBoxW(aTitle, aMessage, L\"INVALID DEFAULT_INPUT WITH QUOTES: use the GRAVE ACCENT \\\\x60 instead.\");\n\n\t\tlTitleLen =  aTitle ? wcslen(aTitle) : 0 ;\n\t\tlMessageLen =  aMessage ? wcslen(aMessage) : 0 ;\n\t\tlDialogStringLen = 3 * MAX_PATH_OR_CMD + lTitleLen + lMessageLen;\n\t\tlDialogString = (wchar_t *) malloc(2 * lDialogStringLen);\n\n\t\tif (aDefaultInput)\n\t\t{\n\t\t\t\t\t\tswprintf(lDialogString,\n#if !defined(__BORLANDC__) && !defined(__TINYC__) && !(defined(__MINGW32__) && !defined(__MINGW64_VERSION_MAJOR))\n\t\t\t\tlDialogStringLen,\n#endif\n\t\t\t\tL\"%ls\\\\tinyfd.vbs\", _wgetenv(L\"TEMP\"));\n\t\t}\n\t\telse\n\t\t{\n\t\t\t\tswprintf(lDialogString,\n#if !defined(__BORLANDC__) && !defined(__TINYC__) && !(defined(__MINGW32__) && !defined(__MINGW64_VERSION_MAJOR))\n\t\t\t\t\t\tlDialogStringLen,\n#endif\n\t\t\t\tL\"%ls\\\\tinyfd.hta\", _wgetenv(L\"TEMP\"));\n\t\t}\n\t\tlIn = _wfopen(lDialogString, L\"w\");\n\t\tif (!lIn)\n\t\t{\n\t\t\t\tfree(lDialogString);\n\t\t\t\treturn NULL;\n\t\t}\n\n\t\tif ( aDefaultInput )\n\t\t{\n\t\t\t\twcscpy(lDialogString, L\"Dim result:result=InputBox(\\\"\");\n\t\t\t\tif (aMessage && wcslen(aMessage))\n\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\twcscpy(lBuff, aMessage);\n\t\t\t\t\t\t\t\t\t\treplaceWchar(lBuff, L'\\n', L' ');\n\t\t\t\t\t\t\t\t\t\twcscat(lDialogString, lBuff);\n\t\t\t\t}\n\t\t\t\twcscat(lDialogString, L\"\\\",\\\"\");\n\t\t\t\tif (aTitle) wcscat(lDialogString, aTitle);\n\t\t\t\twcscat(lDialogString, L\"\\\",\\\"\");\n\n\t\t\t\tif (aDefaultInput && wcslen(aDefaultInput))\n\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\twcscpy(lBuff, aDefaultInput);\n\t\t\t\t\t\t\t\t\t\treplaceWchar(lBuff, L'\\n', L' ');\n\t\t\t\t\t\t\t\t\t\twcscat(lDialogString, lBuff);\n\t\t\t\t}\n\t\t\t\twcscat(lDialogString, L\"\\\"):If IsEmpty(result) then:WScript.Echo 0\");\n\t\t\t\twcscat(lDialogString, L\":Else: WScript.Echo \\\"1\\\" & result : End If\");\n\t\t}\n\t\telse\n\t\t{\n\t\t\t\twcscpy(lDialogString, L\"\\n\\\n<html>\\n\\\n<head>\\n\\\n<title>\");\n\t\t\t\tif (aTitle) wcscat(lDialogString, aTitle);\n\t\t\t\twcscat(lDialogString, L\"</title>\\n\\\n</head>\\n\\\n<HTA:APPLICATION\\n\\\nID = 'tinyfdHTA'\\n\\\nAPPLICATIONNAME = 'tinyfd_inputBox'\\n\\\nMINIMIZEBUTTON = 'no'\\n\\\nMAXIMIZEBUTTON = 'no'\\n\\\nBORDER = 'dialog'\\n\\\nSCROLL = 'no'\\n\\\nSINGLEINSTANCE = 'yes'\\n\\\nWINDOWSTATE = 'hidden'>\\n\\\n\\n\\\n<script language = 'VBScript'>\\n\\\n\\n\\\nintWidth = Screen.Width/4\\n\\\nintHeight = Screen.Height/6\\n\\\nResizeTo intWidth, intHeight\\n\\\nMoveTo((Screen.Width/2)-(intWidth/2)),((Screen.Height/2)-(intHeight/2))\\n\\\nresult = 0\\n\\\n\\n\\\nSub Window_onLoad\\n\\\ntxt_input.Focus\\n\\\nEnd Sub\\n\\\n\\n\");\n\n\t\t\t\twcscat(lDialogString, L\"\\\nSub Window_onUnload\\n\\\nSet objFSO = CreateObject(\\\"Scripting.FileSystemObject\\\")\\n\\\nSet oShell = CreateObject(\\\"WScript.Shell\\\")\\n\\\nstrTempFolder = oShell.ExpandEnvironmentStrings(\\\"%TEMP%\\\")\\n\\\nSet objFile = objFSO.CreateTextFile(strTempFolder & \\\"\\\\tinyfd.txt\\\",True,True)\\n\\\nIf result = 1 Then\\n\\\nobjFile.Write 1 & txt_input.Value\\n\\\nElse\\n\\\nobjFile.Write 0\\n\\\nEnd If\\n\\\nobjFile.Close\\n\\\nEnd Sub\\n\\\n\\n\\\nSub Run_ProgramOK\\n\\\nresult = 1\\n\\\nwindow.Close\\n\\\nEnd Sub\\n\\\n\\n\\\nSub Run_ProgramCancel\\n\\\nwindow.Close\\n\\\nEnd Sub\\n\\\n\\n\");\n\n\t\t\t\twcscat(lDialogString, L\"Sub Default_Buttons\\n\\\nIf Window.Event.KeyCode = 13 Then\\n\\\nbtn_OK.Click\\n\\\nElseIf Window.Event.KeyCode = 27 Then\\n\\\nbtn_Cancel.Click\\n\\\nEnd If\\n\\\nEnd Sub\\n\\\n\\n\\\n</script>\\n\\\n<body style = 'background-color:#EEEEEE' onkeypress = 'vbs:Default_Buttons' align = 'top'>\\n\\\n<table width = '100%' height = '80%' align = 'center' border = '0'>\\n\\\n<tr border = '0'>\\n\\\n<td align = 'left' valign = 'middle' style='Font-Family:Arial'>\\n\");\n\n\t\t\t\twcscat(lDialogString, aMessage ? aMessage : L\"\");\n\n\t\t\t\twcscat(lDialogString, L\"\\n\\\n</td>\\n\\\n<td align = 'right' valign = 'middle' style = 'margin-top: 0em'>\\n\\\n<table  align = 'right' style = 'margin-right: 0em;'>\\n\\\n<tr align = 'right' style = 'margin-top: 5em;'>\\n\\\n<input type = 'button' value = 'OK' name = 'btn_OK' onClick = 'vbs:Run_ProgramOK' style = 'width: 5em; margin-top: 2em;'><br>\\n\\\n<input type = 'button' value = 'Cancel' name = 'btn_Cancel' onClick = 'vbs:Run_ProgramCancel' style = 'width: 5em;'><br><br>\\n\\\n</tr>\\n\\\n</table>\\n\\\n</td>\\n\\\n</tr>\\n\\\n</table>\\n\");\n\n\t\t\t\twcscat(lDialogString, L\"<table width = '100%' height = '100%' align = 'center' border = '0'>\\n\\\n<tr>\\n\\\n<td align = 'left' valign = 'top'>\\n\\\n<input type = 'password' id = 'txt_input'\\n\\\nname = 'txt_input' value = '' style = 'float:left;width:100%' ><BR>\\n\\\n</td>\\n\\\n</tr>\\n\\\n</table>\\n\\\n</body>\\n\\\n</html>\\n\\\n\"               ) ;\n\t\t}\n\t\tfputws(lDialogString, lIn);\n\t\tfclose(lIn);\n\n\t\tif (aDefaultInput)\n\t\t{\n\t\t\t\tswprintf(lDialogString,\n#if !defined(__BORLANDC__) && !defined(__TINYC__) && !(defined(__MINGW32__) && !defined(__MINGW64_VERSION_MAJOR))\n\t\t\t\t\t\tlDialogStringLen,\n#endif\n\t\t\t\t\t\tL\"%ls\\\\tinyfd.txt\",_wgetenv(L\"TEMP\"));\n\n#ifdef TINYFD_NOCCSUNICODE\n\t\t\t\t\t\t\t\tlFile = _wfopen(lDialogString, L\"w\");\n\t\t\t\t\t\t\t\tfputc(0xFF, lFile);\n\t\t\t\t\t\t\t\tfputc(0xFE, lFile);\n#else\n\t\t\t\t\t\t\t\tlFile = _wfopen(lDialogString, L\"wt, ccs=UNICODE\"); /*or ccs=UTF-16LE*/\n#endif\n\t\t\t\t\t\t\t\tfclose(lFile);\n\n\t\t\t\twcscpy(lDialogString, L\"cmd.exe /c cscript.exe //U //Nologo \");\n\t\t\t\twcscat(lDialogString, L\"\\\"%TEMP%\\\\tinyfd.vbs\\\" \");\n\t\t\t\twcscat(lDialogString, L\">> \\\"%TEMP%\\\\tinyfd.txt\\\"\");\n\t\t}\n\t\telse\n\t\t{\n\t\t\t\twcscpy(lDialogString,\n\t\t\t\t\t\tL\"cmd.exe /c mshta.exe \\\"%TEMP%\\\\tinyfd.hta\\\"\");\n\t\t}\n\n\t\t/* wprintf ( \"lDialogString: %ls\\n\" , lDialogString ) ; */\n\n\t\thiddenConsoleW(lDialogString, aTitle, 1);\n\n\t\tswprintf(lDialogString,\n#if !defined(__BORLANDC__) && !defined(__TINYC__) && !(defined(__MINGW32__) && !defined(__MINGW64_VERSION_MAJOR))\n\t\t\t\tlDialogStringLen,\n#endif\n\t\t\t\t\t\t\t\tL\"%ls\\\\tinyfd.txt\", _wgetenv(L\"TEMP\"));\n\t\t\t\t/* wprintf(L\"lDialogString: %ls\\n\", lDialogString); */\n#ifdef TINYFD_NOCCSUNICODE\n\t\t\t\tif (!(lIn = _wfopen(lDialogString, L\"r\")))\n#else\n\t\t\t\tif (!(lIn = _wfopen(lDialogString, L\"rt, ccs=UNICODE\"))) /*or ccs=UTF-16LE*/\n#endif\n\t\t\t\t{\n\t\t\t\t_wremove(lDialogString);\n\t\t\t\tfree(lDialogString);\n\t\t\t\treturn NULL;\n\t\t}\n\n\t\t\t\tmemset(lBuff, 0, MAX_PATH_OR_CMD * sizeof(wchar_t) );\n\n#ifdef TINYFD_NOCCSUNICODE\n\t\t\t\tfgets((char *)lBuff, 2*MAX_PATH_OR_CMD, lIn);\n#else\n\t\t\t\tfgetws(lBuff, MAX_PATH_OR_CMD, lIn);\n#endif\n\t\t\t\tfclose(lIn);\n\t\t\t\twipefileW(lDialogString);\n\t\t\t\t_wremove(lDialogString);\n\n\t\t\t\tif (aDefaultInput)\n\t\t\t\t{\n\t\t\t\t\t\tswprintf(lDialogString,\n#if !defined(__BORLANDC__) && !defined(__TINYC__) && !(defined(__MINGW32__) && !defined(__MINGW64_VERSION_MAJOR))\n\t\t\t\t\t\tlDialogStringLen,\n#endif\n\t\t\t\t\t\tL\"%ls\\\\tinyfd.vbs\", _wgetenv(L\"TEMP\"));\n\t\t}\n\t\telse\n\t\t{\n\t\t\t\tswprintf(lDialogString,\n#if !defined(__BORLANDC__) && !defined(__TINYC__) && !(defined(__MINGW32__) && !defined(__MINGW64_VERSION_MAJOR))\n\t\t\t\t\t\tlDialogStringLen,\n#endif\n\t\t\t\t\t\tL\"%ls\\\\tinyfd.hta\", _wgetenv(L\"TEMP\"));\n\t\t}\n\t\t_wremove(lDialogString);\n\t\tfree(lDialogString);\n\t\t/* wprintf( L\"lBuff: %ls\\n\" , lBuff ) ; */\n#ifdef TINYFD_NOCCSUNICODE\n\t\t\t\tlResult = !wcsncmp(lBuff+1, L\"1\", 1);\n#else\n\t\t\t\tlResult = !wcsncmp(lBuff, L\"1\", 1);\n#endif\n\n\t\t/* printf( \"lResult: %d \\n\" , lResult ) ; */\n\t\tif (!lResult)\n\t\t{\n\t\t\treturn NULL ;\n\t\t}\n\n\t\t/* wprintf( \"lBuff+1: %ls\\n\" , lBuff+1 ) ; */\n\n#ifdef TINYFD_NOCCSUNICODE\n\t\t\t\tif (aDefaultInput)\n\t\t\t\t{\n\t\t\t\t\t\tlDialogStringLen = wcslen(lBuff) ;\n\t\t\t\t\t\tlBuff[lDialogStringLen - 1] = L'\\0';\n\t\t\t\t\t\tlBuff[lDialogStringLen - 2] = L'\\0';\n\t\t\t\t}\n\t\t\t\treturn lBuff + 2;\n#else\n\t\t\t\tif (aDefaultInput) lBuff[wcslen(lBuff) - 1] = L'\\0';\n\t\t\t\treturn lBuff + 1;\n#endif\n}\n\n\nwchar_t * tinyfd_saveFileDialogW(\n\t\twchar_t const * aTitle, /* NULL or \"\" */\n\t\twchar_t const * aDefaultPathAndOrFile, /* NULL or \"\" */\n\t\tint aNumOfFilterPatterns, /* 0 */\n\t\twchar_t const * const * aFilterPatterns, /* NULL or {\"*.jpg\",\"*.png\"} */\n\t\twchar_t const * aSingleFilterDescription) /* NULL or \"image files\" */\n{\n\t\tstatic wchar_t lBuff[MAX_PATH_OR_CMD];\n\t\twchar_t lDirname[MAX_PATH_OR_CMD];\n\t\twchar_t lDialogString[MAX_PATH_OR_CMD];\n\t\twchar_t lFilterPatterns[MAX_PATH_OR_CMD] = L\"\";\n\t\twchar_t * p;\n\t\twchar_t * lRetval;\n\t\t\t\twchar_t const * ldefExt = NULL;\n\t\t\t\tint i;\n\t\tHRESULT lHResult;\n\t\tOPENFILENAMEW ofn = {0};\n\n\t\tif (aTitle&&!wcscmp(aTitle, L\"tinyfd_query\")){ strcpy(tinyfd_response, \"windows_wchar\"); return (wchar_t *)1; }\n\n\t\t/*if (quoteDetectedW(aTitle)) return tinyfd_saveFileDialogW(L\"INVALID TITLE WITH QUOTES\", aDefaultPathAndOrFile, aNumOfFilterPatterns, aFilterPatterns, aSingleFilterDescription);\n\t\tif (quoteDetectedW(aDefaultPathAndOrFile)) return tinyfd_saveFileDialogW(aTitle, L\"INVALID DEFAULT_PATH WITH QUOTES\", aNumOfFilterPatterns, aFilterPatterns, aSingleFilterDescription);\n\t\tif (quoteDetectedW(aSingleFilterDescription)) return tinyfd_saveFileDialogW(aTitle, aDefaultPathAndOrFile, aNumOfFilterPatterns, aFilterPatterns, L\"INVALID FILTER_DESCRIPTION WITH QUOTES\");\n\t\tfor (i = 0; i < aNumOfFilterPatterns; i++)\n\t\t{\n\t\t\t\tif (quoteDetectedW(aFilterPatterns[i])) return tinyfd_saveFileDialogW(L\"INVALID FILTER_PATTERN WITH QUOTES: use the GRAVE ACCENT \\\\x60 instead.\", aDefaultPathAndOrFile, 0, NULL, NULL);\n\t\t}*/\n\n\t\tlHResult = CoInitializeEx(NULL, 0);\n\n\t\tgetPathWithoutFinalSlashW(lDirname, aDefaultPathAndOrFile);\n\t\tgetLastNameW(lBuff, aDefaultPathAndOrFile);\n\n\t\tif (aNumOfFilterPatterns > 0)\n\t\t{\n\t\t\t\t\t\tldefExt = aFilterPatterns[0];\n\n\t\t\t\tif (aSingleFilterDescription && wcslen(aSingleFilterDescription))\n\t\t\t\t{\n\t\t\t\t\t\twcscpy(lFilterPatterns, aSingleFilterDescription);\n\t\t\t\t\t\twcscat(lFilterPatterns, L\"\\n\");\n\t\t\t\t}\n\t\t\t\twcscat(lFilterPatterns, aFilterPatterns[0]);\n\t\t\t\tfor (i = 1; i < aNumOfFilterPatterns; i++)\n\t\t\t\t{\n\t\t\t\t\t\twcscat(lFilterPatterns, L\";\");\n\t\t\t\t\t\twcscat(lFilterPatterns, aFilterPatterns[i]);\n\t\t\t\t}\n\t\t\t\twcscat(lFilterPatterns, L\"\\n\");\n\t\t\t\tif (!(aSingleFilterDescription && wcslen(aSingleFilterDescription)))\n\t\t\t\t{\n\t\t\t\t\t\twcscpy(lDialogString, lFilterPatterns);\n\t\t\t\t\t\twcscat(lFilterPatterns, lDialogString);\n\t\t\t\t}\n\t\t\t\twcscat(lFilterPatterns, L\"All Files\\n*.*\\n\");\n\t\t\t\tp = lFilterPatterns;\n\t\t\t\twhile ((p = wcschr(p, L'\\n')) != NULL)\n\t\t\t\t{\n\t\t\t\t\t\t*p = L'\\0';\n\t\t\t\t\t\tp++;\n\t\t\t\t}\n\t\t}\n\n\t\tofn.lStructSize = sizeof(OPENFILENAMEW);\n\t\tofn.hwndOwner = GetForegroundWindow();\n\t\tofn.hInstance = 0;\n\t\tofn.lpstrFilter = wcslen(lFilterPatterns) ? lFilterPatterns : NULL;\n\t\tofn.lpstrCustomFilter = NULL;\n\t\tofn.nMaxCustFilter = 0;\n\t\tofn.nFilterIndex = 1;\n\t\tofn.lpstrFile = lBuff;\n\n\t\tofn.nMaxFile = MAX_PATH_OR_CMD;\n\t\tofn.lpstrFileTitle = NULL;\n\t\tofn.nMaxFileTitle = MAX_PATH_OR_CMD/2;\n\t\tofn.lpstrInitialDir = wcslen(lDirname) ? lDirname : NULL;\n\t\tofn.lpstrTitle = aTitle && wcslen(aTitle) ? aTitle : NULL;\n\t\tofn.Flags = OFN_OVERWRITEPROMPT | OFN_NOCHANGEDIR | OFN_PATHMUSTEXIST ;\n\t\tofn.nFileOffset = 0;\n\t\tofn.nFileExtension = 0;\n\t\t\t\tofn.lpstrDefExt = ldefExt;\n\t\tofn.lCustData = 0L;\n\t\tofn.lpfnHook = NULL;\n\t\tofn.lpTemplateName = NULL;\n\n\t\tif (GetSaveFileNameW(&ofn) == 0)\n\t\t{\n\t\t\t\tlRetval = NULL;\n\t\t}\n\t\telse\n\t\t{\n\t\t\t\tlRetval = lBuff;\n\t\t}\n\n\t\tif (lHResult == S_OK || lHResult == S_FALSE)\n\t\t{\n\t\t\t\tCoUninitialize();\n\t\t}\n\t\treturn lRetval;\n}\n\n\nwchar_t * tinyfd_openFileDialogW(\n\t\twchar_t const * aTitle, /* NULL or \"\" */\n\t\twchar_t const * aDefaultPathAndOrFile, /* NULL or \"\" */\n\t\tint aNumOfFilterPatterns, /* 0 */\n\t\twchar_t const * const * aFilterPatterns, /* NULL or {\"*.jpg\",\"*.png\"} */\n\t\twchar_t const * aSingleFilterDescription, /* NULL or \"image files\" */\n\t\tint aAllowMultipleSelects) /* 0 or 1 ; -1 to free allocated memory and return */\n{\n\t\tsize_t lLengths[MAX_MULTIPLE_FILES];\n\t\twchar_t lDirname[MAX_PATH_OR_CMD];\n\t\twchar_t lFilterPatterns[MAX_PATH_OR_CMD] = L\"\";\n\t\twchar_t lDialogString[MAX_PATH_OR_CMD];\n\t\twchar_t * lPointers[MAX_MULTIPLE_FILES+1];\n\t\twchar_t * p;\n\t\tint i, j;\n\t\tsize_t lBuffLen;\n\t\tDWORD lFullBuffLen;\n\t\tHRESULT lHResult;\n\t\tOPENFILENAMEW ofn = { 0 };\n\t\tstatic wchar_t * lBuff = NULL;\n\n\t\tfree(lBuff);\n\t\tlBuff = NULL;\n\t\tif (aAllowMultipleSelects < 0) return (wchar_t *)0;\n\n\t\tif (aTitle&&!wcscmp(aTitle, L\"tinyfd_query\")){ strcpy(tinyfd_response, \"windows_wchar\"); return (wchar_t *)1; }\n\n\t\t/*if (quoteDetectedW(aTitle)) return tinyfd_openFileDialogW(L\"INVALID TITLE WITH QUOTES\", aDefaultPathAndOrFile, aNumOfFilterPatterns, aFilterPatterns, aSingleFilterDescription, aAllowMultipleSelects);\n\t\tif (quoteDetectedW(aDefaultPathAndOrFile)) return tinyfd_openFileDialogW(aTitle, L\"INVALID DEFAULT_PATH WITH QUOTES\", aNumOfFilterPatterns, aFilterPatterns, aSingleFilterDescription, aAllowMultipleSelects);\n\t\tif (quoteDetectedW(aSingleFilterDescription)) return tinyfd_openFileDialogW(aTitle, aDefaultPathAndOrFile, aNumOfFilterPatterns, aFilterPatterns, L\"INVALID FILTER_DESCRIPTION WITH QUOTES\", aAllowMultipleSelects);\n\t\tfor (i = 0; i < aNumOfFilterPatterns; i++)\n\t\t{\n\t\t\t\tif (quoteDetectedW(aFilterPatterns[i])) return tinyfd_openFileDialogW(L\"INVALID FILTER_PATTERN WITH QUOTES: use the GRAVE ACCENT \\\\x60 instead.\", aDefaultPathAndOrFile, 0, NULL, NULL, aAllowMultipleSelects);\n\t\t}*/\n\n\t\tif (aAllowMultipleSelects)\n\t\t{\n\t\t\t\tlFullBuffLen = MAX_MULTIPLE_FILES * MAX_PATH_OR_CMD + 1;\n\t\t\t\tlBuff = (wchar_t*) malloc(lFullBuffLen * sizeof(wchar_t));\n\t\t\t\tif (!lBuff)\n\t\t\t\t{\n\t\t\t\t\t\tlFullBuffLen = LOW_MULTIPLE_FILES * MAX_PATH_OR_CMD + 1;\n\t\t\t\t\t\tlBuff = (wchar_t*) malloc( lFullBuffLen * sizeof(wchar_t));\n\t\t\t\t}\n\t\t}\n\t\telse\n\t\t{\n\t\t\t\tlFullBuffLen = MAX_PATH_OR_CMD + 1;\n\t\t\t\tlBuff = (wchar_t*) malloc(lFullBuffLen * sizeof(wchar_t));\n\t\t}\n\t\tif (!lBuff) return NULL;\n\n\t\tlHResult = CoInitializeEx(NULL, 0);\n\n\t\tgetPathWithoutFinalSlashW(lDirname, aDefaultPathAndOrFile);\n\t\tgetLastNameW(lBuff, aDefaultPathAndOrFile);\n\n\t\tif (aNumOfFilterPatterns > 0)\n\t\t{\n\t\t\tif (aSingleFilterDescription && wcslen(aSingleFilterDescription))\n\t\t\t{\n\t\t\t\twcscpy(lFilterPatterns, aSingleFilterDescription);\n\t\t\t\twcscat(lFilterPatterns, L\"\\n\");\n\t\t\t}\n\t\t\twcscat(lFilterPatterns, aFilterPatterns[0]);\n\t\t\tfor (i = 1; i < aNumOfFilterPatterns; i++)\n\t\t\t{\n\t\t\t\twcscat(lFilterPatterns, L\";\");\n\t\t\t\twcscat(lFilterPatterns, aFilterPatterns[i]);\n\t\t\t}\n\t\t\twcscat(lFilterPatterns, L\"\\n\");\n\t\t\tif (!(aSingleFilterDescription && wcslen(aSingleFilterDescription)))\n\t\t\t{\n\t\t\t\twcscpy(lDialogString, lFilterPatterns);\n\t\t\t\twcscat(lFilterPatterns, lDialogString);\n\t\t\t}\n\t\t\twcscat(lFilterPatterns, L\"All Files\\n*.*\\n\");\n\t\t\tp = lFilterPatterns;\n\t\t\twhile ((p = wcschr(p, L'\\n')) != NULL)\n\t\t\t{\n\t\t\t\t*p = L'\\0';\n\t\t\t\tp++;\n\t\t\t}\n\t\t}\n\n\t\tofn.lStructSize = sizeof(OPENFILENAME);\n\t\tofn.hwndOwner = GetForegroundWindow();\n\t\tofn.hInstance = 0;\n\t\tofn.lpstrFilter = wcslen(lFilterPatterns) ? lFilterPatterns : NULL;\n\t\tofn.lpstrCustomFilter = NULL;\n\t\tofn.nMaxCustFilter = 0;\n\t\tofn.nFilterIndex = 1;\n\t\tofn.lpstrFile = lBuff;\n\t\tofn.nMaxFile = lFullBuffLen;\n\t\tofn.lpstrFileTitle = NULL;\n\t\tofn.nMaxFileTitle = MAX_PATH_OR_CMD / 2;\n\t\tofn.lpstrInitialDir = wcslen(lDirname) ? lDirname : NULL;\n\t\tofn.lpstrTitle = aTitle && wcslen(aTitle) ? aTitle : NULL;\n\t\tofn.Flags = OFN_EXPLORER | OFN_NOCHANGEDIR | OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST;\n\t\tofn.nFileOffset = 0;\n\t\tofn.nFileExtension = 0;\n\t\tofn.lpstrDefExt = NULL;\n\t\tofn.lCustData = 0L;\n\t\tofn.lpfnHook = NULL;\n\t\tofn.lpTemplateName = NULL;\n\n\t\tif (aAllowMultipleSelects)\n\t\t{\n\t\t\tofn.Flags |= OFN_ALLOWMULTISELECT;\n\t\t}\n\n\t\tif (GetOpenFileNameW(&ofn) == 0)\n\t\t{\n\t\t\tfree(lBuff);\n\t\t\tlBuff = NULL;\n\t\t}\n\t\telse\n\t\t{\n\t\t\tlBuffLen = wcslen(lBuff);\n\t\t\tlPointers[0] = lBuff + lBuffLen + 1;\n\t\t\tif (aAllowMultipleSelects && (lPointers[0][0] != L'\\0'))\n\t\t\t{\n\t\t\t\ti = 0;\n\t\t\t\tdo\n\t\t\t\t{\n\t\t\t\t\tlLengths[i] = wcslen(lPointers[i]);\n\t\t\t\t\tlPointers[i + 1] = lPointers[i] + lLengths[i] + 1;\n\t\t\t\t\ti++;\n\t\t\t\t} while (lPointers[i][0] != L'\\0' && i < MAX_MULTIPLE_FILES );\n\n\t\t\t\tif (i > MAX_MULTIPLE_FILES)\n\t\t\t\t{\n\t\t\t\t\t\tfree(lBuff);\n\t\t\t\t\t\tlBuff = NULL;\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\t\ti--;\n\t\t\t\t\t\tp = lBuff + lFullBuffLen - 1;\n\t\t\t\t\t\t*p = L'\\0';\n\t\t\t\t\t\tfor (j = i; j >= 0; j--)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tp -= lLengths[j];\n\t\t\t\t\t\t\t\tmemmove(p, lPointers[j], lLengths[j] * sizeof(wchar_t));\n\t\t\t\t\t\t\t\tp--;\n\t\t\t\t\t\t\t\t*p = L'\\\\';\n\t\t\t\t\t\t\t\tp -= lBuffLen;\n\t\t\t\t\t\t\t\tmemmove(p, lBuff, lBuffLen*sizeof(wchar_t));\n\t\t\t\t\t\t\t\tp--;\n\t\t\t\t\t\t\t\t*p = L'|';\n\t\t\t\t\t\t}\n\t\t\t\t\t\tp++;\n\t\t\t\t\t\twcscpy(lBuff, p);\n\t\t\t\t\t\tlBuffLen = wcslen(lBuff);\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (lBuff) lBuff = (wchar_t*)(realloc(lBuff, (lBuffLen + 1) * sizeof(wchar_t)));\n\t\t}\n\n\t\tif (lHResult == S_OK || lHResult == S_FALSE)\n\t\t{\n\t\t\tCoUninitialize();\n\t\t}\n\n\t\treturn lBuff;\n}\n\n\nBOOL CALLBACK BrowseCallbackProcW_enum(HWND hWndChild, LPARAM lParam)\n{\n\twchar_t buf[255];\n\t(void)lParam;\n\tGetClassNameW(hWndChild, buf, sizeof(buf));\n\tif (wcscmp(buf, L\"SysTreeView32\") == 0)\n\t{\n\t\tHTREEITEM hNode = TreeView_GetSelection(hWndChild);\n\t\tTreeView_EnsureVisible(hWndChild, hNode);\n\t\treturn FALSE;\n\t}\n\treturn TRUE;\n}\n\n\nstatic int __stdcall BrowseCallbackProcW(HWND hwnd, UINT uMsg, LPARAM lp, LPARAM pData)\n{\n\t(void)lp;\n\tswitch (uMsg)\n\t{\n\t\tcase BFFM_INITIALIZED:\n\t\t\tSendMessage(hwnd, BFFM_SETSELECTIONW, TRUE, (LPARAM)pData);\n\t\t\tbreak;\n\t\tcase BFFM_SELCHANGED:\n\t\t\tEnumChildWindows(hwnd, BrowseCallbackProcW_enum, 0);\n\t}\n\treturn 0;\n}\n\nwchar_t * tinyfd_selectFolderDialogW(\n\t\twchar_t const * aTitle, /* NULL or \"\" */\n\t\twchar_t const * aDefaultPath) /* NULL or \"\" */\n{\n\t\tstatic wchar_t lBuff[MAX_PATH_OR_CMD];\n\t\twchar_t * lRetval;\n\n\t\tBROWSEINFOW bInfo;\n\t\tLPITEMIDLIST lpItem;\n\t\tHRESULT lHResult;\n\n\t\tif (aTitle&&!wcscmp(aTitle, L\"tinyfd_query\")){ strcpy(tinyfd_response, \"windows_wchar\"); return (wchar_t *)1; }\n\n\t\t/*if (quoteDetectedW(aTitle)) return tinyfd_selectFolderDialogW(L\"INVALID TITLE WITH QUOTES\", aDefaultPath);\n\t\tif (quoteDetectedW(aDefaultPath)) return tinyfd_selectFolderDialogW(aTitle, L\"INVALID DEFAULT_PATH WITH QUOTES\");*/\n\n\t\tlHResult = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);\n\n\t\tbInfo.hwndOwner = GetForegroundWindow();\n\t\tbInfo.pidlRoot = NULL;\n\t\tbInfo.pszDisplayName = lBuff;\n\t\tbInfo.lpszTitle = aTitle && wcslen(aTitle) ? aTitle : NULL;\n\t\tif (lHResult == S_OK || lHResult == S_FALSE)\n\t\t{\n\t\t\t\tbInfo.ulFlags = BIF_USENEWUI;\n\t\t}\n\t\tbInfo.lpfn = BrowseCallbackProcW;\n\t\tbInfo.lParam = (LPARAM)aDefaultPath;\n\t\tbInfo.iImage = -1;\n\n\t\tlpItem = SHBrowseForFolderW(&bInfo);\n\t\tif (!lpItem)\n\t\t\t\t{\n\t\t\t\t\t\tlRetval = NULL;\n\t\t\t\t}\n\t\t\t\telse\n\t\t{\n\t\t\t\tSHGetPathFromIDListW(lpItem, lBuff);\n\t\t\t\t\t\t\t\tlRetval = lBuff ;\n\t\t}\n\n\t\tif (lHResult == S_OK || lHResult == S_FALSE)\n\t\t{\n\t\t\t\tCoUninitialize();\n\t\t}\n\t\t\t\treturn lRetval;\n}\n\n\nwchar_t * tinyfd_colorChooserW(\n\t\twchar_t const * aTitle, /* NULL or \"\" */\n\t\twchar_t const * aDefaultHexRGB, /* NULL or \"#FF0000\"*/\n\t\tunsigned char const aDefaultRGB[3], /* { 0 , 255 , 255 } */\n\t\tunsigned char aoResultRGB[3]) /* { 0 , 0 , 0 } */\n{\n\t\tstatic wchar_t lResultHexRGB[8];\n\t\tCHOOSECOLORW cc;\n\t\tCOLORREF crCustColors[16];\n\t\tunsigned char lDefaultRGB[3];\n\t\tint lRet;\n\n\t\tHRESULT lHResult;\n\n\t\tif (aTitle&&!wcscmp(aTitle, L\"tinyfd_query\")){ strcpy(tinyfd_response, \"windows_wchar\"); return (wchar_t *)1; }\n\n\t\t/*if (quoteDetectedW(aTitle)) return tinyfd_colorChooserW(L\"INVALID TITLE WITH QUOTES\", aDefaultHexRGB, aDefaultRGB, aoResultRGB);\n\t\tif (quoteDetectedW(aDefaultHexRGB)) return tinyfd_colorChooserW(aTitle, L\"INVALID DEFAULT_HEX_RGB WITH QUOTES: use the GRAVE ACCENT \\\\x60 instead.\", aDefaultRGB, aoResultRGB);*/\n\n\t\tlHResult = CoInitializeEx(NULL, 0);\n\n\t\tif ( aDefaultHexRGB && wcslen(aDefaultHexRGB) )\n\t\t{\n\t\t\t\tHex2RGBW(aDefaultHexRGB, lDefaultRGB);\n\t\t}\n\t\telse\n\t\t{\n\t\t\t\tlDefaultRGB[0] = aDefaultRGB[0];\n\t\t\t\tlDefaultRGB[1] = aDefaultRGB[1];\n\t\t\t\tlDefaultRGB[2] = aDefaultRGB[2];\n\t\t}\n\n\t\t/* we can't use aTitle */\n\t\tcc.lStructSize = sizeof(CHOOSECOLOR);\n\t\tcc.hwndOwner = GetForegroundWindow();\n\t\tcc.hInstance = NULL;\n\t\tcc.rgbResult = RGB(lDefaultRGB[0], lDefaultRGB[1], lDefaultRGB[2]);\n\t\tcc.lpCustColors = crCustColors;\n\t\tcc.Flags = CC_RGBINIT | CC_FULLOPEN | CC_ANYCOLOR ;\n\t\tcc.lCustData = 0;\n\t\tcc.lpfnHook = NULL;\n\t\tcc.lpTemplateName = NULL;\n\n\t\tlRet = ChooseColorW(&cc);\n\n\t\tif (!lRet)\n\t\t{\n\t\t\t\treturn NULL;\n\t\t}\n\n\t\taoResultRGB[0] = GetRValue(cc.rgbResult);\n\t\taoResultRGB[1] = GetGValue(cc.rgbResult);\n\t\taoResultRGB[2] = GetBValue(cc.rgbResult);\n\n\t\tRGB2HexW(aoResultRGB, lResultHexRGB);\n\n\t\tif (lHResult == S_OK || lHResult == S_FALSE)\n\t\t{\n\t\t\t\tCoUninitialize();\n\t\t}\n\n\t\treturn lResultHexRGB;\n}\n\n\nstatic int messageBoxWinGui(\n\t\tchar const * aTitle, /* NULL or \"\" */\n\t\tchar const * aMessage, /* NULL or \"\"  may contain \\n and \\t */\n\t\tchar const * aDialogType, /* \"ok\" \"okcancel\" \"yesno\" \"yesnocancel\" */\n\t\tchar const * aIconType, /* \"info\" \"warning\" \"error\" \"question\" */\n\t\tint aDefaultButton) /* 0 for cancel/no , 1 for ok/yes , 2 for no in yesnocancel */\n{\n\t\tint lIntRetVal;\n\t\twchar_t lTitle[128] = L\"\";\n\t\twchar_t * lMessage = NULL;\n\t\twchar_t lDialogType[16] = L\"\";\n\t\twchar_t lIconType[16] = L\"\";\n\t\twchar_t * lTmpWChar;\n\n\t\tif (aTitle)\n\t\t{\n\t\t\t\tif (tinyfd_winUtf8) lTmpWChar = tinyfd_utf8to16(aTitle);\n\t\t\t\telse lTmpWChar = tinyfd_mbcsTo16(aTitle);\n\t\t\t\twcscpy(lTitle, lTmpWChar);\n\t\t}\n\t\tif (aMessage)\n\t\t{\n\t\t\t\tif (tinyfd_winUtf8) lTmpWChar = tinyfd_utf8to16(aMessage);\n\t\t\t\telse lTmpWChar = tinyfd_mbcsTo16(aMessage);\n\t\t\t\tlMessage = (wchar_t *) malloc((wcslen(lTmpWChar) + 1)* sizeof(wchar_t));\n\t\t\t\tif (lMessage) wcscpy(lMessage, lTmpWChar);\n\t\t}\n\t\tif (aDialogType)\n\t\t{\n\t\t\t\tif (tinyfd_winUtf8) lTmpWChar = tinyfd_utf8to16(aDialogType);\n\t\t\t\telse lTmpWChar = tinyfd_mbcsTo16(aDialogType);\n\t\t\t\twcscpy(lDialogType, lTmpWChar);\n\t\t}\n\t\tif (aIconType)\n\t\t{\n\t\t\t\tif (tinyfd_winUtf8) lTmpWChar = tinyfd_utf8to16(aIconType);\n\t\t\t\telse lTmpWChar = tinyfd_mbcsTo16(aIconType);\n\t\t\t\twcscpy(lIconType, lTmpWChar);\n\t\t}\n\n\t\tlIntRetVal = tinyfd_messageBoxW(lTitle, lMessage, lDialogType, lIconType, aDefaultButton);\n\n\t\tfree(lMessage);\n\n\t\treturn lIntRetVal;\n}\n\n\nstatic int notifyWinGui(\n\t\tchar const * aTitle, /* NULL or \"\" */\n\t\tchar const * aMessage, /* NULL or \"\" may NOT contain \\n nor \\t */\n\t\tchar const * aIconType)\n{\n\t\twchar_t lTitle[128] = L\"\";\n\t\twchar_t * lMessage = NULL;\n\t\twchar_t lIconType[16] = L\"\";\n\t\twchar_t * lTmpWChar;\n\n\t\tif (aTitle)\n\t\t{\n\t\t\t\tif (tinyfd_winUtf8) lTmpWChar = tinyfd_utf8to16(aTitle);\n\t\t\t\telse lTmpWChar = tinyfd_mbcsTo16(aTitle);\n\t\t\t\twcscpy(lTitle, lTmpWChar);\n\t\t}\n\t\tif (aMessage)\n\t\t{\n\t\t\t\tif (tinyfd_winUtf8) lTmpWChar = tinyfd_utf8to16(aMessage);\n\t\t\t\telse lTmpWChar = tinyfd_mbcsTo16(aMessage);\n\t\t\t\tlMessage = (wchar_t *) malloc((wcslen(lTmpWChar) + 1)* sizeof(wchar_t));\n\t\tif (lMessage) wcscpy(lMessage, lTmpWChar);\n\t\t}\n\t\tif (aIconType)\n\t\t{\n\t\t\t\tif (tinyfd_winUtf8) lTmpWChar = tinyfd_utf8to16(aIconType);\n\t\t\t\telse lTmpWChar = tinyfd_mbcsTo16(aIconType);\n\t\t\t\twcscpy(lIconType, lTmpWChar);\n\t\t}\n\n\t\ttinyfd_notifyPopupW(lTitle, lMessage, lIconType);\n\n\t\tfree(lMessage);\n\n\t\treturn 1;\n}\n\n\nstatic int inputBoxWinGui(\n\t\tchar * aoBuff,\n\t\tchar const * aTitle, /* NULL or \"\" */\n\t\tchar const * aMessage, /* NULL or \"\" may NOT contain \\n nor \\t */\n\t\tchar const * aDefaultInput) /* \"\" , if NULL it's a passwordBox */\n{\n\t\twchar_t lTitle[128] = L\"\";\n\t\twchar_t * lMessage = NULL;\n\t\twchar_t lDefaultInput[MAX_PATH_OR_CMD] = L\"\";\n\t\twchar_t * lTmpWChar;\n\t\tchar * lTmpChar;\n\n\t\tif (aTitle)\n\t\t{\n\t\t\t\tif (tinyfd_winUtf8) lTmpWChar = tinyfd_utf8to16(aTitle);\n\t\t\t\telse lTmpWChar = tinyfd_mbcsTo16(aTitle);\n\t\t\t\twcscpy(lTitle, lTmpWChar);\n\t\t}\n\t\tif (aMessage)\n\t\t{\n\t\t\t\tif (tinyfd_winUtf8) lTmpWChar = tinyfd_utf8to16(aMessage);\n\t\t\t\telse lTmpWChar = tinyfd_mbcsTo16(aMessage);\n\t\t\t\tlMessage = (wchar_t *) malloc((wcslen(lTmpWChar) + 1)* sizeof(wchar_t));\n\t  if (lMessage) wcscpy(lMessage, lTmpWChar);\n\t\t}\n\t\tif (aDefaultInput)\n\t\t{\n\t\t\t\tif (tinyfd_winUtf8) lTmpWChar = tinyfd_utf8to16(aDefaultInput);\n\t\t\t\telse lTmpWChar = tinyfd_mbcsTo16(aDefaultInput);\n\t\t\t\twcscpy(lDefaultInput, lTmpWChar);\n\t\tlTmpWChar = tinyfd_inputBoxW(lTitle, lMessage, lDefaultInput);\n\t\t}\n\telse lTmpWChar = tinyfd_inputBoxW(lTitle, lMessage, NULL);\n\n\t\tfree(lMessage);\n\n\t\tif (!lTmpWChar)\n\t\t{\n\t\t\t\taoBuff[0] = '\\0';\n\t\t\t\treturn 0;\n\t\t}\n\n\t\tif (tinyfd_winUtf8) lTmpChar = tinyfd_utf16to8(lTmpWChar);\n\t\telse lTmpChar = tinyfd_utf16toMbcs(lTmpWChar);\n\n\t\tstrcpy(aoBuff, lTmpChar);\n\n\t\treturn 1;\n}\n\n\nstatic char * saveFileDialogWinGui(\n\t\tchar * aoBuff,\n\t\tchar const * aTitle, /* NULL or \"\" */\n\t\tchar const * aDefaultPathAndOrFile, /* NULL or \"\" */\n\t\tint aNumOfFilterPatterns, /* 0 */\n\t\tchar const * const * aFilterPatterns, /* NULL or {\"*.jpg\",\"*.png\"} */\n\t\tchar const * aSingleFilterDescription) /* NULL or \"image files\" */\n{\n\t\twchar_t lTitle[128] = L\"\";\n\t\twchar_t lDefaultPathAndFile[MAX_PATH_OR_CMD] = L\"\";\n\t\twchar_t lSingleFilterDescription[128] = L\"\";\n\t\twchar_t * * lFilterPatterns;\n\t\twchar_t * lTmpWChar;\n\t\tchar * lTmpChar;\n\t\tint i;\n\n\t\tlFilterPatterns = (wchar_t **) malloc(aNumOfFilterPatterns*sizeof(wchar_t *));\n\t\tfor (i = 0; i < aNumOfFilterPatterns; i++)\n\t\t{\n\t\t\t\tif (tinyfd_winUtf8) lTmpWChar = tinyfd_utf8to16(aFilterPatterns[i]);\n\t\t\t\telse lTmpWChar = tinyfd_mbcsTo16(aFilterPatterns[i]);\n\t\t\t\tlFilterPatterns[i] = (wchar_t *) malloc((wcslen(lTmpWChar) + 1) * sizeof(wchar_t *));\n\t\t\t\tif (lFilterPatterns[i]) wcscpy(lFilterPatterns[i], lTmpWChar);\n\t\t}\n\n\t\tif (aTitle)\n\t\t{\n\t\t\t\tif (tinyfd_winUtf8) lTmpWChar = tinyfd_utf8to16(aTitle);\n\t\t\t\telse lTmpWChar = tinyfd_mbcsTo16(aTitle);\n\t\t\t\twcscpy(lTitle, lTmpWChar);\n\t\t}\n\t\tif (aDefaultPathAndOrFile)\n\t\t{\n\t\t\t\tif (tinyfd_winUtf8) lTmpWChar = tinyfd_utf8to16(aDefaultPathAndOrFile);\n\t\t\t\telse lTmpWChar = tinyfd_mbcsTo16(aDefaultPathAndOrFile);\n\t\t\t\twcscpy(lDefaultPathAndFile, lTmpWChar);\n\t\t}\n\t\tif (aSingleFilterDescription)\n\t\t{\n\t\t\t\tif (tinyfd_winUtf8) lTmpWChar = tinyfd_utf8to16(aSingleFilterDescription);\n\t\t\t\telse lTmpWChar = tinyfd_mbcsTo16(aSingleFilterDescription);\n\t\t\t\twcscpy(lSingleFilterDescription, lTmpWChar);\n\t\t}\n\n\t\tlTmpWChar = tinyfd_saveFileDialogW(\n\t\t\t\tlTitle,\n\t\t\t\tlDefaultPathAndFile,\n\t\t\t\taNumOfFilterPatterns,\n\t\t\t\t(wchar_t const**) lFilterPatterns, /*stupid cast for gcc*/\n\t\t\t\tlSingleFilterDescription);\n\n\t\tfor (i = 0; i < aNumOfFilterPatterns; i++)\n\t\t{\n\t\t\t\tfree(lFilterPatterns[i]);\n\t\t}\n\t\tfree(lFilterPatterns);\n\n\t\tif (!lTmpWChar)\n\t\t{\n\t\t\t\treturn NULL;\n\t\t}\n\n\t\tif (tinyfd_winUtf8) lTmpChar = tinyfd_utf16to8(lTmpWChar);\n\t\telse lTmpChar = tinyfd_utf16toMbcs(lTmpWChar);\n\t\tstrcpy(aoBuff, lTmpChar);\n\t\tif (tinyfd_winUtf8) (void)tinyfd_utf16to8(NULL);\n\t\telse (void)tinyfd_utf16toMbcs(NULL);\n\n\t\treturn aoBuff;\n}\n\n\nstatic char * openFileDialogWinGui(\n\t\tchar const * aTitle, /*  NULL or \"\" */\n\t\tchar const * aDefaultPathAndOrFile, /*  NULL or \"\" */\n\t\tint aNumOfFilterPatterns, /* 0 */\n\t\tchar const * const * aFilterPatterns, /* NULL or {\"*.jpg\",\"*.png\"} */\n\t\tchar const * aSingleFilterDescription, /* NULL or \"image files\" */\n\t\tint aAllowMultipleSelects) /* 0 or 1 */\n{\n\t\twchar_t lTitle[128] = L\"\";\n\t\twchar_t lDefaultPathAndFile[MAX_PATH_OR_CMD] = L\"\";\n\t\twchar_t lSingleFilterDescription[128] = L\"\";\n\t\twchar_t * * lFilterPatterns;\n\t\twchar_t * lTmpWChar;\n\t\tchar * lTmpChar;\n\t\tint i;\n\n\t\tlFilterPatterns = (wchar_t * *) malloc(aNumOfFilterPatterns*sizeof(wchar_t *));\n\t\tfor (i = 0; i < aNumOfFilterPatterns; i++)\n\t\t{\n\t\t\t\tif (tinyfd_winUtf8) lTmpWChar = tinyfd_utf8to16(aFilterPatterns[i]);\n\t\t\t\telse lTmpWChar = tinyfd_mbcsTo16(aFilterPatterns[i]);\n\t\t\t\tlFilterPatterns[i] = (wchar_t *) malloc((wcslen(lTmpWChar) + 1)*sizeof(wchar_t *));\n\t  if (lFilterPatterns[i]) wcscpy(lFilterPatterns[i], lTmpWChar);\n\t\t}\n\n\t\tif (aTitle)\n\t\t{\n\t\t\t\tif (tinyfd_winUtf8) lTmpWChar = tinyfd_utf8to16(aTitle);\n\t\t\t\telse lTmpWChar = tinyfd_mbcsTo16(aTitle);\n\t\t\t\twcscpy(lTitle, lTmpWChar);\n\t\t}\n\t\tif (aDefaultPathAndOrFile)\n\t\t{\n\t\t\t\tif (tinyfd_winUtf8) lTmpWChar = tinyfd_utf8to16(aDefaultPathAndOrFile);\n\t\t\t\telse lTmpWChar = tinyfd_mbcsTo16(aDefaultPathAndOrFile);\n\t\t\t\twcscpy(lDefaultPathAndFile, lTmpWChar);\n\t\t}\n\t\tif (aSingleFilterDescription)\n\t\t{\n\t\t\t\tif (tinyfd_winUtf8) lTmpWChar = tinyfd_utf8to16(aSingleFilterDescription);\n\t\t\t\telse lTmpWChar = tinyfd_mbcsTo16(aSingleFilterDescription);\n\t\t\t\twcscpy(lSingleFilterDescription, lTmpWChar);\n\t\t}\n\n\t\tlTmpWChar = tinyfd_openFileDialogW(\n\t\t\t\tlTitle,\n\t\t\t\tlDefaultPathAndFile,\n\t\t\t\taNumOfFilterPatterns,\n\t\t\t\t(wchar_t const**) lFilterPatterns, /*stupid cast for gcc*/\n\t\t\t\tlSingleFilterDescription,\n\t\t\t\taAllowMultipleSelects);\n\n\t\tfor (i = 0; i < aNumOfFilterPatterns; i++)\n\t\t{\n\t\t\t\tfree(lFilterPatterns[i]);\n\t\t}\n\t\tfree(lFilterPatterns);\n\n\t\tif (!lTmpWChar) return NULL;\n\n\t\tif (tinyfd_winUtf8) lTmpChar = tinyfd_utf16to8(lTmpWChar);\n\t\telse lTmpChar = tinyfd_utf16toMbcs(lTmpWChar);\n\t\t(void)tinyfd_openFileDialogW(NULL, NULL, 0, NULL, NULL, -1);\n\n\t\treturn lTmpChar;\n}\n\n\nstatic char * selectFolderDialogWinGui(\n\t\tchar * aoBuff,\n\t\tchar const * aTitle, /*  NULL or \"\" */\n\t\tchar const * aDefaultPath) /* NULL or \"\" */\n{\n\t\twchar_t lTitle[128] = L\"\";\n\t\twchar_t lDefaultPath[MAX_PATH_OR_CMD] = L\"\";\n\t\twchar_t * lTmpWChar;\n\t\tchar * lTmpChar;\n\n\t\tif (aTitle)\n\t\t{\n\t\t\t\tif (tinyfd_winUtf8) lTmpWChar = tinyfd_utf8to16(aTitle);\n\t\t\t\telse lTmpWChar = tinyfd_mbcsTo16(aTitle);\n\t\t\t\twcscpy(lTitle, lTmpWChar);\n\t\t}\n\t\tif (aDefaultPath)\n\t\t{\n\t\t\t\tif (tinyfd_winUtf8) lTmpWChar = tinyfd_utf8to16(aDefaultPath);\n\t\t\t\telse lTmpWChar = tinyfd_mbcsTo16(aDefaultPath);\n\t\t\t\twcscpy(lDefaultPath, lTmpWChar);\n\t\t}\n\n\t\tlTmpWChar = tinyfd_selectFolderDialogW(\n\t\t\t\tlTitle,\n\t\t\t\tlDefaultPath);\n\n\t\tif (!lTmpWChar)\n\t\t{\n\t\t\t\treturn NULL;\n\t\t}\n\n\t\tif (tinyfd_winUtf8) lTmpChar = tinyfd_utf16to8(lTmpWChar);\n\t\telse lTmpChar = tinyfd_utf16toMbcs(lTmpWChar);\n\t\tstrcpy(aoBuff, lTmpChar);\n\n\t\treturn aoBuff;\n}\n\n\nstatic char * colorChooserWinGui(\n\t\tchar const * aTitle, /* NULL or \"\" */\n\t\tchar const * aDefaultHexRGB, /* NULL or \"#FF0000\"*/\n\t\tunsigned char const aDefaultRGB[3], /* { 0 , 255 , 255 } */\n\t\tunsigned char aoResultRGB[3]) /* { 0 , 0 , 0 } */\n{\n\t\tstatic char lResultHexRGB[8];\n\n\t\twchar_t lTitle[128];\n\t\twchar_t * lTmpWChar;\n\t\tchar * lTmpChar;\n\t\twchar_t lDefaultHexRGB[16] = L\"\";\n\n\t\t\t\tif (aTitle)\n\t\t\t\t{\n\t\t\t\t\t\tif (tinyfd_winUtf8) lTmpWChar = tinyfd_utf8to16(aTitle);\n\t\t\t\t\t\telse lTmpWChar = tinyfd_mbcsTo16(aTitle);\n\t\t\t\t\t\twcscpy(lTitle, lTmpWChar);\n\t\t\t\t}\n\t\t\t\tif (aDefaultHexRGB)\n\t\t\t\t{\n\t\t\t\t\t\tif (tinyfd_winUtf8) lTmpWChar = tinyfd_utf8to16(aDefaultHexRGB);\n\t\t\t\t\t\telse lTmpWChar = tinyfd_mbcsTo16(aDefaultHexRGB);\n\t\t\t\t\t\twcscpy(lDefaultHexRGB, lTmpWChar);\n\t\t\t\t}\n\n\t\tlTmpWChar = tinyfd_colorChooserW(\n\t\t\t\tlTitle,\n\t\t\t\tlDefaultHexRGB,\n\t\t\t\taDefaultRGB,\n\t\t\t\taoResultRGB );\n\n\t\tif (!lTmpWChar)\n\t\t{\n\t\t\t\treturn NULL;\n\t\t}\n\n\t\t\t\tif (tinyfd_winUtf8) lTmpChar = tinyfd_utf16to8(lTmpWChar);\n\t\t\t\telse lTmpChar = tinyfd_utf16toMbcs(lTmpWChar);\n\t\t\t\tstrcpy(lResultHexRGB, lTmpChar);\n\n\t\treturn lResultHexRGB;\n}\n\n\nstatic int dialogPresent(void)\n{\n\t\tstatic int lDialogPresent = -1 ;\n\t\tchar lBuff[MAX_PATH_OR_CMD] ;\n\t\tFILE * lIn ;\n\t\tchar const * lString = \"dialog.exe\";\n\t\t\t\tif (!tinyfd_allowCursesDialogs) return 0;\n\t\t\t\tif (lDialogPresent < 0)\n\t\t{\n\t\t\t\tlIn = _popen(\"where dialog.exe\", \"r\");\n\t\t\t\tif ( ! lIn )\n\t\t\t\t{\n\t\t\t\t\t\tlDialogPresent = 0 ;\n\t\t\t\t\t\treturn 0 ;\n\t\t\t\t}\n\t\t\t\twhile ( fgets( lBuff , sizeof( lBuff ) , lIn ) != NULL )\n\t\t\t\t{}\n\t\t\t\t_pclose( lIn ) ;\n\t\t\t\tif ( lBuff[strlen( lBuff ) -1] == '\\n' )\n\t\t\t\t{\n\t\t\t\t\t\tlBuff[strlen( lBuff ) -1] = '\\0' ;\n\t\t\t\t}\n\t\t\t\tif ( strcmp(lBuff+strlen(lBuff)-strlen(lString),lString) )\n\t\t\t\t{\n\t\t\t\t\t\tlDialogPresent = 0 ;\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\t\tlDialogPresent = 1 ;\n\t\t\t\t}\n\t\t}\n\t\t\t\treturn lDialogPresent;\n}\n\n\nstatic int messageBoxWinConsole(\n\tchar const * aTitle , /* NULL or \"\" */\n\tchar const * aMessage , /* NULL or \"\"  may contain \\n and \\t */\n\tchar const * aDialogType , /* \"ok\" \"okcancel\" \"yesno\" \"yesnocancel\" */\n\tchar const * aIconType , /* \"info\" \"warning\" \"error\" \"question\" */\n\tint aDefaultButton ) /* 0 for cancel/no , 1 for ok/yes , 2 for no in yesnocancel */\n{\n\t\tchar lDialogString[MAX_PATH_OR_CMD];\n\t\tchar lDialogFile[MAX_PATH_OR_CMD];\n\t\tFILE * lIn;\n\t\tchar lBuff[MAX_PATH_OR_CMD] = \"\";\n\t\t(void)aIconType;\n\n\t\t\t\tstrcpy(lDialogString, \"dialog \");\n\t\t\t\tif (aTitle && strlen(aTitle))\n\t\t{\n\t\t\t\tstrcat(lDialogString, \"--title \\\"\") ;\n\t\t\t\tstrcat(lDialogString, aTitle) ;\n\t\t\t\tstrcat(lDialogString, \"\\\" \") ;\n\t\t}\n\n\t\tif ( aDialogType && ( !strcmp( \"okcancel\" , aDialogType )\n\t\t\t\t|| !strcmp(\"yesno\", aDialogType) || !strcmp(\"yesnocancel\", aDialogType) ) )\n\t\t{\n\t\t\t\tstrcat(lDialogString, \"--backtitle \\\"\") ;\n\t\t\t\tstrcat(lDialogString, \"tab: move focus\") ;\n\t\t\t\tstrcat(lDialogString, \"\\\" \") ;\n\t\t}\n\n\t\tif ( aDialogType && ! strcmp( \"okcancel\" , aDialogType ) )\n\t\t{\n\t\t\t\tif ( ! aDefaultButton )\n\t\t\t\t{\n\t\t\t\t\t\tstrcat( lDialogString , \"--defaultno \" ) ;\n\t\t\t\t}\n\t\t\t\tstrcat( lDialogString ,\n\t\t\t\t\t\t\t\t\"--yes-label \\\"Ok\\\" --no-label \\\"Cancel\\\" --yesno \" ) ;\n\t\t}\n\t\telse if ( aDialogType && ! strcmp( \"yesno\" , aDialogType ) )\n\t\t{\n\t\t\t\tif ( ! aDefaultButton )\n\t\t\t\t{\n\t\t\t\t\t\tstrcat( lDialogString , \"--defaultno \" ) ;\n\t\t\t\t}\n\t\t\t\tstrcat( lDialogString , \"--yesno \" ) ;\n\t\t}\n\t\telse if (aDialogType && !strcmp(\"yesnocancel\", aDialogType))\n\t\t{\n\t\t\t\tif (!aDefaultButton)\n\t\t\t\t{\n\t\t\t\t\t\tstrcat(lDialogString, \"--defaultno \");\n\t\t\t\t}\n\t\t\t\tstrcat(lDialogString, \"--menu \");\n\t\t}\n\t\telse\n\t\t{\n\t\t\t\tstrcat( lDialogString , \"--msgbox \" ) ;\n\t\t}\n\n\t\tstrcat( lDialogString , \"\\\"\" ) ;\n\t\tif ( aMessage && strlen(aMessage) )\n\t\t{\n\t\t\t\ttfd_replaceSubStr( aMessage , \"\\n\" , \"\\\\n\" , lBuff ) ;\n\t\t\t\tstrcat(lDialogString, lBuff) ;\n\t\t\t\tlBuff[0]='\\0';\n\t\t}\n\t\tstrcat(lDialogString, \"\\\" \");\n\n\t\tif (aDialogType && !strcmp(\"yesnocancel\", aDialogType))\n\t\t{\n\t\t\t\tstrcat(lDialogString, \"0 60 0 Yes \\\"\\\" No \\\"\\\"\");\n\t\t\t\tstrcat(lDialogString, \"2>>\");\n\t\t}\n\t\telse\n\t\t{\n\t\t\t\tstrcat(lDialogString, \"10 60\");\n\t\t\t\tstrcat(lDialogString, \" && echo 1 > \");\n\t\t}\n\n\t\tstrcpy(lDialogFile, getenv(\"TEMP\"));\n\t\tstrcat(lDialogFile, \"\\\\tinyfd.txt\");\n\t\tstrcat(lDialogString, lDialogFile);\n\n\t\t/*if (tinyfd_verbose) printf( \"lDialogString: %s\\n\" , lDialogString ) ;*/\n\t\tsystem( lDialogString ) ;\n\n\t\tif (!(lIn = fopen(lDialogFile, \"r\")))\n\t\t{\n\t\t\t\tremove(lDialogFile);\n\t\t\t\treturn 0 ;\n\t\t}\n\t\t\t\twhile (fgets(lBuff, sizeof(lBuff), lIn) != NULL)\n\t\t{}\n\t\tfclose(lIn);\n\t\tremove(lDialogFile);\n\tif ( lBuff[strlen( lBuff ) -1] == '\\n' )\n\t{\n\t\tlBuff[strlen( lBuff ) -1] = '\\0' ;\n\t}\n\n\t\t/* if (tinyfd_verbose) printf(\"lBuff: %s\\n\", lBuff); */\n\t\tif ( ! strlen(lBuff) )\n\t\t{\n\t\t\t\treturn 0;\n\t\t}\n\n\t\tif (aDialogType && !strcmp(\"yesnocancel\", aDialogType))\n\t\t{\n\t\t\t\tif (lBuff[0] == 'Y') return 1;\n\t\t\t\telse return 2;\n\t\t}\n\n\t\treturn 1;\n}\n\n\nstatic int inputBoxWinConsole(\n\t\tchar * aoBuff ,\n\t\tchar const * aTitle , /* NULL or \"\" */\n\t\tchar const * aMessage , /* NULL or \"\" may NOT contain \\n nor \\t */\n\t\tchar const * aDefaultInput ) /* \"\" , if NULL it's a passwordBox */\n{\n\t\tchar lDialogString[MAX_PATH_OR_CMD];\n\t\tchar lDialogFile[MAX_PATH_OR_CMD];\n\t\tFILE * lIn;\n\t\tint lResult;\n\n\t\tstrcpy(lDialogFile, getenv(\"TEMP\"));\n\t\tstrcat(lDialogFile, \"\\\\tinyfd.txt\");\n\t\tstrcpy(lDialogString , \"echo|set /p=1 >\" ) ;\n\t\tstrcat(lDialogString, lDialogFile);\n\t\tstrcat( lDialogString , \" & \" ) ;\n\n\t\tstrcat( lDialogString , \"dialog \" ) ;\n\t\tif ( aTitle && strlen(aTitle) )\n\t\t{\n\t\t\t\tstrcat(lDialogString, \"--title \\\"\") ;\n\t\t\t\tstrcat(lDialogString, aTitle) ;\n\t\t\t\tstrcat(lDialogString, \"\\\" \") ;\n\t\t}\n\n\t\tstrcat(lDialogString, \"--backtitle \\\"\") ;\n\t\tstrcat(lDialogString, \"tab: move focus\") ;\n\t\tif ( ! aDefaultInput )\n\t\t{\n\t\t\t\tstrcat(lDialogString, \" (sometimes nothing, no blink nor star, is shown in text field)\") ;\n\t\t}\n\n\t\tstrcat(lDialogString, \"\\\" \") ;\n\n\t\tif ( ! aDefaultInput )\n\t\t{\n\t\t\t\tstrcat( lDialogString , \"--insecure --passwordbox\" ) ;\n\t\t}\n\t\telse\n\t\t{\n\t\t\t\tstrcat( lDialogString , \"--inputbox\" ) ;\n\t\t}\n\t\tstrcat( lDialogString , \" \\\"\" ) ;\n\t\tif ( aMessage && strlen(aMessage) )\n\t\t{\n\t\t\t\tstrcat(lDialogString, aMessage) ;\n\t\t}\n\t\tstrcat(lDialogString,\"\\\" 10 60 \") ;\n\t\tif ( aDefaultInput && strlen(aDefaultInput) )\n\t\t{\n\t\t\t\tstrcat(lDialogString, \"\\\"\") ;\n\t\t\t\tstrcat(lDialogString, aDefaultInput) ;\n\t\t\t\tstrcat(lDialogString, \"\\\" \") ;\n\t\t}\n\n\t\tstrcat(lDialogString, \"2>>\");\n\t\tstrcpy(lDialogFile, getenv(\"TEMP\"));\n\t\tstrcat(lDialogFile, \"\\\\tinyfd.txt\");\n\t\tstrcat(lDialogString, lDialogFile);\n\t\tstrcat(lDialogString, \" || echo 0 > \");\n\t\tstrcat(lDialogString, lDialogFile);\n\n\t\t/* printf( \"lDialogString: %s\\n\" , lDialogString ) ; */\n\t\tsystem( lDialogString ) ;\n\n\t\tif (!(lIn = fopen(lDialogFile, \"r\")))\n\t\t{\n\t\t\t\tremove(lDialogFile);\n\t\t\t\t\t\t\t\taoBuff[0] = '\\0';\n\t\t\t\t\t\t\t\treturn 0;\n\t\t}\n\t\twhile (fgets(aoBuff, MAX_PATH_OR_CMD, lIn) != NULL)\n\t\t{}\n\t\tfclose(lIn);\n\n\t\twipefile(lDialogFile);\n\t\tremove(lDialogFile);\n\tif ( aoBuff[strlen( aoBuff ) -1] == '\\n' )\n\t{\n\t\taoBuff[strlen( aoBuff ) -1] = '\\0' ;\n\t}\n\t\t/* printf( \"aoBuff: %s\\n\" , aoBuff ) ; */\n\n\t\t/* printf( \"aoBuff: %s len: %lu \\n\" , aoBuff , strlen(aoBuff) ) ; */\n\tlResult =  strncmp( aoBuff , \"1\" , 1) ? 0 : 1 ;\n\t\t/* printf( \"lResult: %d \\n\" , lResult ) ; */\n\t\tif ( ! lResult )\n\t\t{\n\t\t\t\taoBuff[0] = '\\0';\n\t\t\t\treturn 0 ;\n\t\t}\n\t\t/* printf( \"aoBuff+1: %s\\n\" , aoBuff+1 ) ; */\n\t\tstrcpy(aoBuff, aoBuff+3);\n\t\treturn 1;\n}\n\n\nstatic char * saveFileDialogWinConsole(\n\t\tchar * aoBuff ,\n\t\tchar const * aTitle , /* NULL or \"\" */\n\t\tchar const * aDefaultPathAndOrFile ) /* NULL or \"\" */\n{\n\t\tchar lDialogString[MAX_PATH_OR_CMD];\n\t\tchar lPathAndFile[MAX_PATH_OR_CMD] = \"\";\n\t\tFILE * lIn;\n\n\t\tstrcpy( lDialogString , \"dialog \" ) ;\n\t\tif ( aTitle && strlen(aTitle) )\n\t\t{\n\t\t\t\tstrcat(lDialogString, \"--title \\\"\") ;\n\t\t\t\tstrcat(lDialogString, aTitle) ;\n\t\t\t\tstrcat(lDialogString, \"\\\" \") ;\n\t\t}\n\n\t\tstrcat(lDialogString, \"--backtitle \\\"\") ;\n\t\tstrcat(lDialogString,\n\t\t\t\t\"tab: focus | /: populate | spacebar: fill text field | ok: TEXT FIELD ONLY\") ;\n\t\tstrcat(lDialogString, \"\\\" \") ;\n\n\t\tstrcat( lDialogString , \"--fselect \\\"\" ) ;\n\t\tif ( aDefaultPathAndOrFile && strlen(aDefaultPathAndOrFile) )\n\t\t{\n\t\t\t\t/* dialog.exe uses unix separators even on windows */\n\t\t\t\tstrcpy(lPathAndFile, aDefaultPathAndOrFile);\n\t\t\t\treplaceChr( lPathAndFile , '\\\\' , '/' ) ;\n\t\t}\n\n\t\t/* dialog.exe needs at least one separator */\n\t\tif ( ! strchr(lPathAndFile, '/') )\n\t\t{\n\t\t\t\tstrcat(lDialogString, \"./\") ;\n\t\t}\n\t\tstrcat(lDialogString, lPathAndFile) ;\n\t\tstrcat(lDialogString, \"\\\" 0 60 2>\");\n\t\tstrcpy(lPathAndFile, getenv(\"TEMP\"));\n\t\tstrcat(lPathAndFile, \"\\\\tinyfd.txt\");\n\t\tstrcat(lDialogString, lPathAndFile);\n\n\t\t/* printf( \"lDialogString: %s\\n\" , lDialogString ) ; */\n\t\tsystem( lDialogString ) ;\n\n\t\tif (!(lIn = fopen(lPathAndFile, \"r\")))\n\t\t{\n\t\t\t\tremove(lPathAndFile);\n\t\t\t\treturn NULL;\n\t\t}\n\t\twhile (fgets(aoBuff, MAX_PATH_OR_CMD, lIn) != NULL)\n\t\t{}\n\t\tfclose(lIn);\n\t\tremove(lPathAndFile);\n\t\treplaceChr( aoBuff , '/' , '\\\\' ) ;\n\t\t/* printf( \"aoBuff: %s\\n\" , aoBuff ) ; */\n\t\tgetLastName(lDialogString,aoBuff);\n\t\tif ( ! strlen(lDialogString) )\n\t\t{\n\t\t\t\treturn NULL;\n\t\t}\n\t\treturn aoBuff;\n}\n\n\nstatic char * openFileDialogWinConsole(\n\t\tchar const * aTitle , /*  NULL or \"\" */\n\t\tchar const * aDefaultPathAndOrFile ) /*  NULL or \"\" */\n{\n\t\tchar lFilterPatterns[MAX_PATH_OR_CMD] = \"\";\n\t\tchar lDialogString[MAX_PATH_OR_CMD] ;\n\t\tFILE * lIn;\n\n\t\t\t\tstatic char aoBuff[MAX_PATH_OR_CMD];\n\n\t\tstrcpy( lDialogString , \"dialog \" ) ;\n\t\tif ( aTitle && strlen(aTitle) )\n\t\t{\n\t\t\t\tstrcat(lDialogString, \"--title \\\"\") ;\n\t\t\t\tstrcat(lDialogString, aTitle) ;\n\t\t\t\tstrcat(lDialogString, \"\\\" \") ;\n\t\t}\n\n\t\tstrcat(lDialogString, \"--backtitle \\\"\") ;\n\t\tstrcat(lDialogString,\n\t\t\t\t\"tab: focus | /: populate | spacebar: fill text field | ok: TEXT FIELD ONLY\") ;\n\t\tstrcat(lDialogString, \"\\\" \") ;\n\n\t\tstrcat( lDialogString , \"--fselect \\\"\" ) ;\n\t\tif ( aDefaultPathAndOrFile && strlen(aDefaultPathAndOrFile) )\n\t\t{\n\t\t\t\t/* dialog.exe uses unix separators even on windows */\n\t\t\t\tstrcpy(lFilterPatterns, aDefaultPathAndOrFile);\n\t\t\t\treplaceChr( lFilterPatterns , '\\\\' , '/' ) ;\n\t\t}\n\n\t\t/* dialog.exe needs at least one separator */\n\t\tif ( ! strchr(lFilterPatterns, '/') )\n\t\t{\n\t\t\t\tstrcat(lDialogString, \"./\") ;\n\t\t}\n\t\tstrcat(lDialogString, lFilterPatterns) ;\n\t\tstrcat(lDialogString, \"\\\" 0 60 2>\");\n\t\tstrcpy(lFilterPatterns, getenv(\"TEMP\"));\n\t\tstrcat(lFilterPatterns, \"\\\\tinyfd.txt\");\n\t\tstrcat(lDialogString, lFilterPatterns);\n\n\t\t/* printf( \"lDialogString: %s\\n\" , lDialogString ) ; */\n\t\tsystem( lDialogString ) ;\n\n\t\tif (!(lIn = fopen(lFilterPatterns, \"r\")))\n\t\t{\n\t\t\t\tremove(lFilterPatterns);\n\t\t\t\treturn NULL;\n\t\t}\n\t\twhile (fgets(aoBuff, MAX_PATH_OR_CMD, lIn) != NULL)\n\t\t{}\n\t\tfclose(lIn);\n\t\tremove(lFilterPatterns);\n\t\treplaceChr( aoBuff , '/' , '\\\\' ) ;\n\t\t/* printf( \"aoBuff: %s\\n\" , aoBuff ) ; */\n\t\treturn aoBuff;\n}\n\n\nstatic char * selectFolderDialogWinConsole(\n\t\tchar * aoBuff ,\n\t\tchar const * aTitle , /*  NULL or \"\" */\n\t\tchar const * aDefaultPath ) /* NULL or \"\" */\n{\n\t\tchar lDialogString[MAX_PATH_OR_CMD] ;\n\t\tchar lString[MAX_PATH_OR_CMD] ;\n\t\tFILE * lIn ;\n\n\t\tstrcpy( lDialogString , \"dialog \" ) ;\n\t\tif ( aTitle && strlen(aTitle) )\n\t\t{\n\t\t\t\tstrcat(lDialogString, \"--title \\\"\") ;\n\t\t\t\tstrcat(lDialogString, aTitle) ;\n\t\t\t\tstrcat(lDialogString, \"\\\" \") ;\n\t\t}\n\n\t\tstrcat(lDialogString, \"--backtitle \\\"\") ;\n\t\tstrcat(lDialogString,\n\t\t\t\t\"tab: focus | /: populate | spacebar: fill text field | ok: TEXT FIELD ONLY\") ;\n\t\tstrcat(lDialogString, \"\\\" \") ;\n\n\t\tstrcat( lDialogString , \"--dselect \\\"\" ) ;\n\t\tif ( aDefaultPath && strlen(aDefaultPath) )\n\t\t{\n\t\t\t\t/* dialog.exe uses unix separators even on windows */\n\t\t\t\tstrcpy(lString, aDefaultPath) ;\n\t\t\t\tensureFinalSlash(lString);\n\t\t\t\treplaceChr( lString , '\\\\' , '/' ) ;\n\t\t\t\tstrcat(lDialogString, lString) ;\n\t\t}\n\t\telse\n\t\t{\n\t\t\t\t/* dialog.exe needs at least one separator */\n\t\t\t\tstrcat(lDialogString, \"./\") ;\n\t\t}\n\t\tstrcat(lDialogString, \"\\\" 0 60 2>\");\n\t\tstrcpy(lString, getenv(\"TEMP\"));\n\t\tstrcat(lString, \"\\\\tinyfd.txt\");\n\t\tstrcat(lDialogString, lString);\n\n\t\t/* printf( \"lDialogString: %s\\n\" , lDialogString ) ; */\n\t\tsystem( lDialogString ) ;\n\n\t\tif (!(lIn = fopen(lString, \"r\")))\n\t\t{\n\t\t\t\tremove(lString);\n\t\t\t\treturn NULL;\n\t\t}\n\t\twhile (fgets(aoBuff, MAX_PATH_OR_CMD, lIn) != NULL)\n\t\t{}\n\t\tfclose(lIn);\n\t\tremove(lString);\n\t\treplaceChr( aoBuff , '/' , '\\\\' ) ;\n\t\t/* printf( \"aoBuff: %s\\n\" , aoBuff ) ; */\n\t\treturn aoBuff;\n}\n\nstatic void writeUtf8( char const * aUtf8String )\n{\n\t\tunsigned long lNum;\n\t\tvoid * lConsoleHandle;\n\t\twchar_t * lTmpWChar;\n\n\t\tlConsoleHandle = GetStdHandle(STD_OUTPUT_HANDLE);\n\t\tlTmpWChar = tinyfd_utf8to16(aUtf8String);\n\t\t(void)WriteConsoleW(lConsoleHandle, lTmpWChar, (DWORD) wcslen(lTmpWChar), &lNum, NULL);\n}\n\n\nint tinyfd_messageBox(\n\t\tchar const * aTitle, /* NULL or \"\" */\n\t\tchar const * aMessage, /* NULL or \"\"  may contain \\n and \\t */\n\t\tchar const * aDialogType, /* \"ok\" \"okcancel\" \"yesno\" \"yesnocancel\" */\n\t\tchar const * aIconType, /* \"info\" \"warning\" \"error\" \"question\" */\n\t\tint aDefaultButton) /* 0 for cancel/no , 1 for ok/yes , 2 for no in yesnocancel */\n{\n\t\tchar lChar;\n\t\tUINT lOriginalCP = 0;\n\t\tUINT lOriginalOutputCP = 0;\n\n\t\tif (tfd_quoteDetected(aTitle)) return tinyfd_messageBox(\"INVALID TITLE WITH QUOTES\", aMessage, aDialogType, aIconType, aDefaultButton);\n\t\tif (tfd_quoteDetected(aMessage)) return tinyfd_messageBox(aTitle, \"INVALID MESSAGE WITH QUOTES\", aDialogType, aIconType, aDefaultButton);\n\n\t\tif ((!tinyfd_forceConsole || !(GetConsoleWindow() || dialogPresent()))\n\t\t\t\t&& (!getenv(\"SSH_CLIENT\") || getenvDISPLAY()))\n\t\t{\n\t\t\t\tif (aTitle&&!strcmp(aTitle, \"tinyfd_query\")){ strcpy(tinyfd_response, \"windows\"); return 1; }\n\t\t\t\treturn messageBoxWinGui(aTitle, aMessage, aDialogType, aIconType, aDefaultButton);\n\t\t}\n\t\telse if (dialogPresent())\n\t\t{\n\t\t\t\tif (aTitle&&!strcmp(aTitle, \"tinyfd_query\")){ strcpy(tinyfd_response, \"dialog\"); return 0; }\n\t\t\t\treturn messageBoxWinConsole(\n\t\t\t\t\t\taTitle, aMessage, aDialogType, aIconType, aDefaultButton);\n\t\t}\n\t\telse\n\t\t{\n\t\t\t\tif (!tinyfd_winUtf8)\n\t\t\t\t{\n\t\t\t\t\t\tlOriginalCP = GetConsoleCP();\n\t\t\t\t\t\tlOriginalOutputCP = GetConsoleOutputCP();\n\t\t\t\t\t\t(void)SetConsoleCP(GetACP());\n\t\t\t\t\t\t(void)SetConsoleOutputCP(GetACP());\n\t\t\t\t}\n\n\t\t\t\tif (aTitle&&!strcmp(aTitle, \"tinyfd_query\")){ strcpy(tinyfd_response, \"basicinput\"); return 0; }\n\t\t\t\tif (!gWarningDisplayed && !tinyfd_forceConsole)\n\t\t\t\t{\n\t\t\t\t\t\tgWarningDisplayed = 1;\n\t\t\t\t\t\tprintf(\"\\n\\n%s\\n\", gTitle);\n\t\t\t\t\t\tprintf(\"%s\\n\\n\", tinyfd_needs);\n\t\t\t\t}\n\n\t\t\t\tif (aTitle && strlen(aTitle))\n\t\t\t\t{\n\t\t\t\t\t\tprintf(\"\\n\");\n\t\t\t\t\t\tif (tinyfd_winUtf8) writeUtf8(aTitle);\n\t\t\t\t\t\telse printf(\"%s\", aTitle);\n\t\t\t\t\t\tprintf(\"\\n\\n\");\n\t\t\t\t}\n\t\t\t\tif (aDialogType && !strcmp(\"yesno\", aDialogType))\n\t\t\t\t{\n\t\t\t\t\t\tdo\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tif (aMessage && strlen(aMessage))\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tif (tinyfd_winUtf8) writeUtf8(aMessage);\n\t\t\t\t\t\t\t\t\t\telse printf(\"%s\", aMessage);\n\t\t\t\t\t\t\t\t\t\tprintf(\"\\n\");\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tprintf(\"y/n: \");\n\t\t\t\t\t\t\t\tlChar = (char)tolower(_getch());\n\t\t\t\t\t\t\t\tprintf(\"\\n\\n\");\n\t\t\t\t\t\t} while (lChar != 'y' && lChar != 'n');\n\t\t\t\t\t\tif (!tinyfd_winUtf8) { (void)SetConsoleCP(lOriginalCP); (void)SetConsoleOutputCP(lOriginalOutputCP); }\n\t\t\t\t\t\treturn lChar == 'y' ? 1 : 0;\n\t\t\t\t}\n\t\t\t\telse if (aDialogType && !strcmp(\"okcancel\", aDialogType))\n\t\t\t\t{\n\t\t\t\t\t\tdo\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tif (aMessage && strlen(aMessage))\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tif (tinyfd_winUtf8) writeUtf8(aMessage);\n\t\t\t\t\t\t\t\t\t\telse printf(\"%s\", aMessage);\n\t\t\t\t\t\t\t\t\t\tprintf(\"\\n\");\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tprintf(\"[O]kay/[C]ancel: \");\n\t\t\t\t\t\t\t\tlChar = (char)tolower(_getch());\n\t\t\t\t\t\t\t\tprintf(\"\\n\\n\");\n\t\t\t\t\t\t} while (lChar != 'o' && lChar != 'c');\n\t\t\t\t\t\tif (!tinyfd_winUtf8) { (void)SetConsoleCP(lOriginalCP); (void)SetConsoleOutputCP(lOriginalOutputCP); }\n\t\t\t\t\t\treturn lChar == 'o' ? 1 : 0;\n\t\t\t\t}\n\t\t\t\telse if (aDialogType && !strcmp(\"yesnocancel\", aDialogType))\n\t\t\t\t{\n\t\t\t\t\t\tdo\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tif (aMessage && strlen(aMessage))\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tif (tinyfd_winUtf8) writeUtf8(aMessage);\n\t\t\t\t\t\t\t\t\t\telse printf(\"%s\", aMessage);\n\t\t\t\t\t\t\t\t\t\tprintf(\"\\n\");\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tprintf(\"[Y]es/[N]o/[C]ancel: \");\n\t\t\t\t\t\t\t\tlChar = (char)tolower(_getch());\n\t\t\t\t\t\t\t\tprintf(\"\\n\\n\");\n\t\t\t\t\t\t} while (lChar != 'y' && lChar != 'n' && lChar != 'c');\n\t\t\t\t\t\tif (!tinyfd_winUtf8) { (void)SetConsoleCP(lOriginalCP); (void)SetConsoleOutputCP(lOriginalOutputCP); }\n\t\t\t\t\t\treturn (lChar == 'y') ? 1 : (lChar == 'n') ? 2 : 0;\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\t\tif (aMessage && strlen(aMessage))\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tif (tinyfd_winUtf8) writeUtf8(aMessage);\n\t\t\t\t\t\t\t\telse printf(\"%s\", aMessage);\n\t\t\t\t\t\t\t\tprintf(\"\\n\\n\");\n\t\t\t\t\t\t}\n\t\t\t\t\t\tprintf(\"press enter to continue \"); fflush(stdout);\n\t\t\t\t\t\tlChar = (char)_getch();\n\t\t\t\t\t\tprintf(\"\\n\\n\");\n\t\t\t\t\t\tif (!tinyfd_winUtf8) { (void)SetConsoleCP(lOriginalCP); (void)SetConsoleOutputCP(lOriginalOutputCP); }\n\t\t\t\t\t\treturn 1;\n\t\t\t\t}\n\t\t}\n}\n\n\n/* return has only meaning for tinyfd_query */\nint tinyfd_notifyPopup(\n\t\tchar const * aTitle, /* NULL or \"\" */\n\t\tchar const * aMessage , /* NULL or \"\" may contain \\n \\t */\n\t\tchar const * aIconType ) /* \"info\" \"warning\" \"error\" */\n{\n\t\tif (tfd_quoteDetected(aTitle)) return tinyfd_notifyPopup(\"INVALID TITLE WITH QUOTES\", aMessage, aIconType);\n\t\tif (tfd_quoteDetected(aMessage)) return tinyfd_notifyPopup(aTitle, \"INVALID MESSAGE WITH QUOTES\", aIconType);\n\n\tif ( powershellPresent() && (!tinyfd_forceConsole || !(\n\t\t\tGetConsoleWindow() ||\n\t\t\tdialogPresent()))\n\t\t\t\t\t\t&& (!getenv(\"SSH_CLIENT\") || getenvDISPLAY()))\n\t{\n\t\t\tif (aTitle&&!strcmp(aTitle,\"tinyfd_query\")){strcpy(tinyfd_response,\"windows\");return 1;}\n\t\t\treturn notifyWinGui(aTitle, aMessage, aIconType);\n\t}\n\telse\n\t\t\treturn tinyfd_messageBox(aTitle, aMessage, \"ok\" , aIconType, 0);\n}\n\n\n/* returns NULL on cancel */\nchar * tinyfd_inputBox(\n\t\tchar const * aTitle , /* NULL or \"\" */\n\t\tchar const * aMessage , /* NULL or \"\" (\\n and \\t have no effect) */\n\t\tchar const * aDefaultInput ) /* \"\" , if NULL it's a passwordBox */\n{\n\t\tstatic char lBuff[MAX_PATH_OR_CMD] = \"\";\n\t\tchar * lEOF;\n\n\t\tDWORD mode = 0;\n\t\tHANDLE hStdin = GetStdHandle(STD_INPUT_HANDLE);\n\n\t\tunsigned long lNum;\n\t\tvoid * lConsoleHandle;\n\t\tchar * lTmpChar;\n\t\twchar_t lBuffW[1024];\n\n\t\tUINT lOriginalCP = 0;\n\t\tUINT lOriginalOutputCP = 0;\n\n\t\tif (!aTitle && !aMessage && !aDefaultInput) return lBuff; /* now I can fill lBuff from outside */\n\n\t\tif (tfd_quoteDetected(aTitle)) return tinyfd_inputBox(\"INVALID TITLE WITH QUOTES\", aMessage, aDefaultInput);\n\t\tif (tfd_quoteDetected(aMessage)) return tinyfd_inputBox(aTitle, \"INVALID MESSAGE WITH QUOTES\", aDefaultInput);\n\t\tif (tfd_quoteDetected(aDefaultInput)) return tinyfd_inputBox(aTitle, aMessage, \"INVALID DEFAULT_INPUT WITH QUOTES: use the GRAVE ACCENT \\\\x60 instead.\");\n\n\tmode = 0;\n\thStdin = GetStdHandle(STD_INPUT_HANDLE);\n\n\tif ((!tinyfd_forceConsole || !(\n\t\t\tGetConsoleWindow() ||\n\t\t\tdialogPresent()))\n\t\t\t\t\t\t&& (!getenv(\"SSH_CLIENT\") || getenvDISPLAY()))\n\t{\n\t\tif (aTitle&&!strcmp(aTitle,\"tinyfd_query\")){strcpy(tinyfd_response,\"windows\");return (char *)1;}\n\t\tlBuff[0]='\\0';\n\t\t\t\tif (inputBoxWinGui(lBuff, aTitle, aMessage, aDefaultInput)) return lBuff;\n\t\t\t\telse return NULL;\n\t\t}\n\telse if ( dialogPresent() )\n\t{\n\t\tif (aTitle&&!strcmp(aTitle,\"tinyfd_query\")){strcpy(tinyfd_response,\"dialog\");return (char *)0;}\n\t\tlBuff[0]='\\0';\n\t\t\t\tif (inputBoxWinConsole(lBuff, aTitle, aMessage, aDefaultInput) ) return lBuff;\n\t\t\t\telse return NULL;\n\t\t}\n\telse\n\t{\n\t  if (aTitle&&!strcmp(aTitle,\"tinyfd_query\")){strcpy(tinyfd_response,\"basicinput\");return (char *)0;}\n\t  lBuff[0]='\\0';\n\t  if (!gWarningDisplayed && !tinyfd_forceConsole)\n\t  {\n\t\t  gWarningDisplayed = 1 ;\n\t\t  printf(\"\\n\\n%s\\n\", gTitle);\n\t\t  printf(\"%s\\n\\n\", tinyfd_needs);\n\t  }\n\n\t  if (!tinyfd_winUtf8)\n\t  {\n\t\t\t  lOriginalCP = GetConsoleCP();\n\t\t\t  lOriginalOutputCP = GetConsoleOutputCP();\n\t\t\t  (void)SetConsoleCP(GetACP());\n\t\t\t  (void)SetConsoleOutputCP(GetACP());\n\t  }\n\n\t  if (aTitle && strlen(aTitle))\n\t  {\n\t\t\t\tprintf(\"\\n\");\n\t\t\t\tif (tinyfd_winUtf8) writeUtf8(aTitle);\n\t\t\t\telse printf(\"%s\", aTitle);\n\t\t\t\tprintf(\"\\n\\n\");\n\t\t  }\n\t  if ( aMessage && strlen(aMessage) )\n\t  {\n\t\t\t\tif (tinyfd_winUtf8) writeUtf8(aMessage);\n\t\t\t\telse printf(\"%s\", aMessage);\n\t\t\t\tprintf(\"\\n\");\n\t  }\n\t  printf(\"(ctrl-Z + enter to cancel): \"); fflush(stdout);\n\t  if ( ! aDefaultInput )\n\t  {\n\t\t\t\t  (void) GetConsoleMode(hStdin, &mode);\n\t\t\t\t  (void) SetConsoleMode(hStdin, mode & (~ENABLE_ECHO_INPUT));\n\t  }\n\t\t  if (tinyfd_winUtf8)\n\t\t  {\n\t\t\t\tlConsoleHandle = GetStdHandle(STD_INPUT_HANDLE);\n\t\t\t\t(void) ReadConsoleW(lConsoleHandle, lBuffW, MAX_PATH_OR_CMD, &lNum, NULL);\n\t\t\t\tif (!aDefaultInput)\n\t\t\t\t{\n\t\t\t\t\t\t(void)SetConsoleMode(hStdin, mode);\n\t\t\t\t\t\tprintf(\"\\n\");\n\t\t\t\t}\n\t\t\t\tlBuffW[lNum] = '\\0';\n\t\t\t\tif (lBuffW[wcslen(lBuffW) - 1] == '\\n') lBuffW[wcslen(lBuffW) - 1] = '\\0';\n\t\t\t\tif (lBuffW[wcslen(lBuffW) - 1] == '\\r') lBuffW[wcslen(lBuffW) - 1] = '\\0';\n\t\t\t\tlTmpChar = tinyfd_utf16to8(lBuffW);\n\t\t\t\tif (lTmpChar)\n\t\t\t\t{\n\t\t\t\t\t\tstrcpy(lBuff, lTmpChar);\n\t\t\t\t\t\treturn lBuff;\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t\t\treturn NULL;\n\t\t  }\n\t\t  else\n\t\t  {\n\t\t\t\t  lEOF = fgets(lBuff, MAX_PATH_OR_CMD, stdin);\n\t\t\t\t  if (!aDefaultInput)\n\t\t\t\t  {\n\t\t\t\t\t\t  (void)SetConsoleMode(hStdin, mode);\n\t\t\t\t\t\t  printf(\"\\n\");\n\t\t\t\t  }\n\n\t\t\t\t  if (!tinyfd_winUtf8)\n\t\t\t\t  {\n\t\t\t\t\t\t  (void)SetConsoleCP(lOriginalCP);\n\t\t\t\t\t\t  (void)SetConsoleOutputCP(lOriginalOutputCP);\n\t\t\t\t  }\n\n\t\t\t\t  if (!lEOF)\n\t\t\t\t  {\n\t\t\t\t\t\t  return NULL;\n\t\t\t\t  }\n\t\t\t\t  printf(\"\\n\");\n\t\t\t\t  if (strchr(lBuff, 27))\n\t\t\t\t  {\n\t\t\t\t\t\t  return NULL;\n\t\t\t\t  }\n\t\t\t\t  if (lBuff[strlen(lBuff) - 1] == '\\n')\n\t\t\t\t  {\n\t\t\t\t\t\t  lBuff[strlen(lBuff) - 1] = '\\0';\n\t\t\t\t  }\n\t\t\t\t  return lBuff;\n\t\t\t\t}\n\t\t}\n}\n\n\nchar * tinyfd_saveFileDialog(\n\t\tchar const * aTitle , /* NULL or \"\" */\n\t\tchar const * aDefaultPathAndOrFile , /* NULL or \"\" */\n\t\tint aNumOfFilterPatterns , /* 0 */\n\t\tchar const * const * aFilterPatterns , /* NULL or {\"*.jpg\",\"*.png\"} */\n\t\tchar const * aSingleFilterDescription ) /* NULL or \"image files\" */\n{\n\t\tstatic char lBuff[MAX_PATH_OR_CMD] ;\n\t\tchar lString[MAX_PATH_OR_CMD] ;\n\t\tchar * p ;\n\t\t\t\tchar * lPointerInputBox;\n\t\t\t\tint i;\n\n\t\tlBuff[0]='\\0';\n\n\t\tif ( ! aFilterPatterns ) aNumOfFilterPatterns = 0 ;\n\t\t\t\tif (tfd_quoteDetected(aTitle)) return tinyfd_saveFileDialog(\"INVALID TITLE WITH QUOTES\", aDefaultPathAndOrFile, aNumOfFilterPatterns, aFilterPatterns, aSingleFilterDescription);\n\t\t\t\tif (tfd_quoteDetected(aDefaultPathAndOrFile)) return tinyfd_saveFileDialog(aTitle, \"INVALID DEFAULT_PATH WITH QUOTES\", aNumOfFilterPatterns, aFilterPatterns, aSingleFilterDescription);\n\t\t\t\tif (tfd_quoteDetected(aSingleFilterDescription)) return tinyfd_saveFileDialog(aTitle, aDefaultPathAndOrFile, aNumOfFilterPatterns, aFilterPatterns, \"INVALID FILTER_DESCRIPTION WITH QUOTES\");\n\t\t\t\tfor (i = 0; i < aNumOfFilterPatterns; i++)\n\t\t\t\t{\n\t\t\t\t\t\tif (tfd_quoteDetected(aFilterPatterns[i])) return tinyfd_saveFileDialog(\"INVALID FILTER_PATTERN WITH QUOTES: use the GRAVE ACCENT \\\\x60 instead.\", aDefaultPathAndOrFile, 0, NULL, NULL);\n\t\t\t\t}\n\n\n\t\t\t\tif ( ( !tinyfd_forceConsole || !( GetConsoleWindow() || dialogPresent() ) )\n\t\t\t\t\t\t&& (!getenv(\"SSH_CLIENT\") || getenvDISPLAY()))\n\t\t{\n\t\t\tif (aTitle&&!strcmp(aTitle,\"tinyfd_query\")){strcpy(tinyfd_response,\"windows\");return (char *)1;}\n\t\t\tp = saveFileDialogWinGui(lBuff,\n\t\t\t\t\t\t\t\taTitle, aDefaultPathAndOrFile, aNumOfFilterPatterns, (char const * const *)aFilterPatterns, aSingleFilterDescription);\n\t\t}\n\t\t\t\telse if (dialogPresent())\n\t\t\t\t{\n\t\t\t\t\t\tif (aTitle&&!strcmp(aTitle, \"tinyfd_query\")){ strcpy(tinyfd_response, \"dialog\"); return (char *)0; }\n\t\t\t\t\t\tp = saveFileDialogWinConsole(lBuff, aTitle, aDefaultPathAndOrFile);\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\t\tif (aTitle&&!strcmp(aTitle, \"tinyfd_query\")){ strcpy(tinyfd_response, \"basicinput\"); return (char *)0; }\n\t\t\t\t\t\tstrcpy(lBuff, \"Save file in \");\n\t\t\t\t\t\tstrcat(lBuff, getCurDir());\n\n\t\t\t\t\t\tlPointerInputBox = tinyfd_inputBox(NULL,NULL,NULL); /* obtain a pointer on the current content of tinyfd_inputBox */\n\t\t\t\t\t\tif (lPointerInputBox) strcpy(lString, lPointerInputBox); /* preserve the current content of tinyfd_inputBox */\n\t\t\t\t\t\tp = tinyfd_inputBox(aTitle, lBuff, \"\");\n\t\t\t\t\t\tif (p) strcpy(lBuff, p); else lBuff[0] = '\\0';\n\t\t\t\t\t\tif (lPointerInputBox) strcpy(lPointerInputBox, lString); /* restore its previous content to tinyfd_inputBox */\n\t\t\t\t\t\tp = lBuff;\n\t\t\t\t}\n\n\t\tif ( ! p || ! strlen( p )  )\n\t\t{\n\t\t\t\treturn NULL;\n\t\t}\n\t\tgetPathWithoutFinalSlash( lString , p ) ;\n\t\tif ( strlen( lString ) && ! dirExists( lString ) )\n\t\t{\n\t\t\t\treturn NULL ;\n\t\t}\n\t\tgetLastName(lString,p);\n\t\tif ( ! filenameValid(lString) )\n\t\t{\n\t\t\t\treturn NULL;\n\t\t}\n\t\treturn p ;\n}\n\n\n/* in case of multiple files, the separator is | */\nchar * tinyfd_openFileDialog(\n\tchar const * aTitle , /* NULL or \"\" */\n\t\tchar const * aDefaultPathAndOrFile, /* NULL or \"\" */\n\tint aNumOfFilterPatterns , /* 0 */\n\t\tchar const * const * aFilterPatterns, /* NULL or {\"*.jpg\",\"*.png\"} */\n\t\tchar const * aSingleFilterDescription, /* NULL or \"image files\" */\n\tint aAllowMultipleSelects ) /* 0 or 1 */\n{\n\tstatic char lBuff[MAX_PATH_OR_CMD];\n\tchar lString[MAX_PATH_OR_CMD];\n\t\tchar * p;\n\t\tchar * lPointerInputBox;\n\t\tint i;\n\n\tif ( ! aFilterPatterns ) aNumOfFilterPatterns = 0 ;\n\t\tif (tfd_quoteDetected(aTitle)) return tinyfd_openFileDialog(\"INVALID TITLE WITH QUOTES\", aDefaultPathAndOrFile, aNumOfFilterPatterns, aFilterPatterns, aSingleFilterDescription, aAllowMultipleSelects);\n\t\tif (tfd_quoteDetected(aDefaultPathAndOrFile)) return tinyfd_openFileDialog(aTitle, \"INVALID DEFAULT_PATH WITH QUOTES\", aNumOfFilterPatterns, aFilterPatterns, aSingleFilterDescription, aAllowMultipleSelects);\n\t\tif (tfd_quoteDetected(aSingleFilterDescription)) return tinyfd_openFileDialog(aTitle, aDefaultPathAndOrFile, aNumOfFilterPatterns, aFilterPatterns, \"INVALID FILTER_DESCRIPTION WITH QUOTES\", aAllowMultipleSelects);\n\t\tfor (i = 0; i < aNumOfFilterPatterns; i++)\n\t\t{\n\t\t\t\tif (tfd_quoteDetected(aFilterPatterns[i])) return tinyfd_openFileDialog(\"INVALID FILTER_PATTERN WITH QUOTES: use the GRAVE ACCENT \\\\x60 instead.\", aDefaultPathAndOrFile, 0, NULL, NULL, aAllowMultipleSelects);\n\t\t}\n\n\tif ( ( !tinyfd_forceConsole || !( GetConsoleWindow() || dialogPresent() ) )\n\t\t\t\t&& (!getenv(\"SSH_CLIENT\") || getenvDISPLAY()))\n\t\t{\n\t\t\t\tif (aTitle&&!strcmp(aTitle,\"tinyfd_query\")){strcpy(tinyfd_response,\"windows\");return (char *)1;}\n\t\t\t\tp = openFileDialogWinGui( aTitle, aDefaultPathAndOrFile, aNumOfFilterPatterns,\n\t\t\t\t\t\t\t\t\t\t(char const * const *)aFilterPatterns, aSingleFilterDescription, aAllowMultipleSelects);\n\t\t}\n\t\t\t\telse if (dialogPresent())\n\t\t\t\t{\n\t\t\t\t\t\tif (aTitle&&!strcmp(aTitle, \"tinyfd_query\")){ strcpy(tinyfd_response, \"dialog\"); return (char *)0; }\n\t\t\t\t\t\tp = openFileDialogWinConsole(aTitle, aDefaultPathAndOrFile);\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\t\tif (aTitle&&!strcmp(aTitle, \"tinyfd_query\")){ strcpy(tinyfd_response, \"basicinput\"); return (char *)0; }\n\t\t\t\t\t\tstrcpy(lBuff, \"Open file from \");\n\t\t\t\t\t\tstrcat(lBuff, getCurDir());\n\t\t\t\t\t\tlPointerInputBox = tinyfd_inputBox(NULL, NULL, NULL); /* obtain a pointer on the current content of tinyfd_inputBox */\n\t\t\t\t\t\tif (lPointerInputBox) strcpy(lString, lPointerInputBox); /* preserve the current content of tinyfd_inputBox */\n\t\t\t\t\t\tp = tinyfd_inputBox(aTitle, lBuff, \"\");\n\t\t\t\t\t\tif (p) strcpy(lBuff, p); else lBuff[0] = '\\0';\n\t\t\t\t\t\tif (lPointerInputBox) strcpy(lPointerInputBox, lString); /* restore its previous content to tinyfd_inputBox */\n\t\t\t\t\t\tp = lBuff;\n\t\t\t\t}\n\n\t\tif ( ! p || ! strlen( p )  )\n\t\t{\n\t\t\t\treturn NULL;\n\t\t}\n\t\tif ( aAllowMultipleSelects && strchr(p, '|') )\n\t\t{\n\t\t\t\tp = ensureFilesExist( (char *) p , p ) ;\n\t\t}\n\t\telse if ( ! fileExists(p) )\n\t\t{\n\t\t\t\treturn NULL ;\n\t\t}\n\t\t/* printf( \"lBuff3: %s\\n\" , p ) ; */\n\t\treturn p ;\n}\n\n\nchar * tinyfd_selectFolderDialog(\n\t\tchar const * aTitle , /* NULL or \"\" */\n\t\tchar const * aDefaultPath ) /* NULL or \"\" */\n{\n\t\tstatic char lBuff[MAX_PATH_OR_CMD];\n\t\tchar * p;\n\t\tchar * lPointerInputBox;\n\t\tchar lString[MAX_PATH_OR_CMD];\n\n\t\tif (tfd_quoteDetected(aTitle)) return tinyfd_selectFolderDialog(\"INVALID TITLE WITH QUOTES\", aDefaultPath);\n\t\tif (tfd_quoteDetected(aDefaultPath)) return tinyfd_selectFolderDialog(aTitle, \"INVALID DEFAULT_PATH WITH QUOTES\");\n\n\tif ( ( !tinyfd_forceConsole || !( GetConsoleWindow() || dialogPresent() ) )\n\t\t\t\t&& (!getenv(\"SSH_CLIENT\") || getenvDISPLAY()))\n\t\t{\n\t\t\t\tif (aTitle&&!strcmp(aTitle,\"tinyfd_query\")){strcpy(tinyfd_response,\"windows\");return (char *)1;}\n\t\t\t\tp = selectFolderDialogWinGui(lBuff, aTitle, aDefaultPath);\n\t\t}\n\t\t\t\telse\n\t\t\t\tif (dialogPresent())\n\t\t\t\t{\n\t\t\t\t\t\tif (aTitle&&!strcmp(aTitle, \"tinyfd_query\")){ strcpy(tinyfd_response, \"dialog\"); return (char *)0; }\n\t\t\t\t\t\tp = selectFolderDialogWinConsole(lBuff, aTitle, aDefaultPath);\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\t\tif (aTitle&&!strcmp(aTitle, \"tinyfd_query\")){ strcpy(tinyfd_response, \"basicinput\"); return (char *)0; }\n\t\t\t\t\t\tstrcpy(lBuff, \"Select folder from \");\n\t\t\t\t\t\tstrcat(lBuff, getCurDir());\n\t\t\t\t\t\tlPointerInputBox = tinyfd_inputBox(NULL, NULL, NULL); /* obtain a pointer on the current content of tinyfd_inputBox */\n\t\t\t\t\t\tif (lPointerInputBox) strcpy(lString, lPointerInputBox); /* preserve the current content of tinyfd_inputBox */\n\t\t\t\t\t\tp = tinyfd_inputBox(aTitle, lBuff, \"\");\n\t\t\t\t\t\tif (p) strcpy(lBuff, p); else lBuff[0] = '\\0';\n\t\t\t\t\t\tif (lPointerInputBox) strcpy(lPointerInputBox, lString); /* restore its previous content to tinyfd_inputBox */\n\t\t\t\t\t\tp = lBuff;\n\t\t\t\t}\n\n\t\tif ( ! p || ! strlen( p ) || ! dirExists( p ) )\n\t\t{\n\t\t\t\treturn NULL ;\n\t\t}\n\t\treturn p ;\n}\n\n\n/* aDefaultRGB is used only if aDefaultHexRGB is absent */\n/* aDefaultRGB and aoResultRGB can be the same array */\n/* returns NULL on cancel */\n/* returns the hexcolor as a string \"#FF0000\" */\n/* aoResultRGB also contains the result */\nchar * tinyfd_colorChooser(\n\t\tchar const * aTitle, /* NULL or \"\" */\n\t\tchar const * aDefaultHexRGB, /* NULL or \"\" or \"#FF0000\"*/\n\t\tunsigned char const aDefaultRGB[3], /* { 0 , 255 , 255 } */\n\t\tunsigned char aoResultRGB[3]) /* { 0 , 0 , 0 } */\n{\n\t\tstatic char lDefaultHexRGB[16];\n\tint i;\n\tchar * p ;\n\t\tchar * lPointerInputBox;\n\t\tchar lString[MAX_PATH_OR_CMD];\n\n\t\tlDefaultHexRGB[0] = '\\0';\n\n\t\tif (tfd_quoteDetected(aTitle)) return tinyfd_colorChooser(\"INVALID TITLE WITH QUOTES\", aDefaultHexRGB, aDefaultRGB, aoResultRGB);\n\t\tif (tfd_quoteDetected(aDefaultHexRGB)) return tinyfd_colorChooser(aTitle, \"INVALID DEFAULT_HEX_RGB WITH QUOTES: use the GRAVE ACCENT \\\\x60 instead.\", aDefaultRGB, aoResultRGB);\n\n\tif ( (!tinyfd_forceConsole || !( GetConsoleWindow() || dialogPresent()) )\n\t\t\t\t&& (!getenv(\"SSH_CLIENT\") || getenvDISPLAY()))\n\t{\n\t\t\t\tif (aTitle&&!strcmp(aTitle,\"tinyfd_query\")){strcpy(tinyfd_response,\"windows\");return (char *)1;}\n\t\t\t\tp = colorChooserWinGui(aTitle, aDefaultHexRGB, aDefaultRGB, aoResultRGB);\n\t\tif (p)\n\t\t{\n\t\t\tstrcpy(lDefaultHexRGB, p);\n\t\t\treturn lDefaultHexRGB;\n\t\t}\n\t\treturn NULL;\n\t}\n\t\telse if (dialogPresent())\n\t\t{\n\t\t\t\tif (aTitle&&!strcmp(aTitle, \"tinyfd_query\")){ strcpy(tinyfd_response, \"dialog\"); return (char *)0; }\n\t\t}\n\t\telse\n\t\t{\n\t\t\t\tif (aTitle&&!strcmp(aTitle, \"tinyfd_query\")){ strcpy(tinyfd_response, \"basicinput\"); return (char *)0; }\n\t\t}\n\n\t\tif (aDefaultHexRGB && (strlen(aDefaultHexRGB)==7) )\n\t\t{\n\t\t\t\tstrncpy(lDefaultHexRGB, aDefaultHexRGB,7);\n\t\t\t\tlDefaultHexRGB[7]='\\0';\n\t\t}\n\t\telse\n\t\t{\n\t\t\t\tRGB2Hex(aDefaultRGB, lDefaultHexRGB);\n\t\t}\n\n\t\tlPointerInputBox = tinyfd_inputBox(NULL, NULL, NULL); /* obtain a pointer on the current content of tinyfd_inputBox */\n\t\tif (lPointerInputBox) strcpy(lString, lPointerInputBox); /* preserve the current content of tinyfd_inputBox */\n\t\tp = tinyfd_inputBox(aTitle, \"Enter hex rgb color (i.e. #f5ca20)\", lDefaultHexRGB);\n\n\tif ( !p || (strlen(p) != 7) || (p[0] != '#') )\n\t{\n\t\t\treturn NULL ;\n\t}\n\tfor ( i = 1 ; i < 7 ; i ++ )\n\t{\n\t\t\tif ( ! isxdigit( (int) p[i] ) )\n\t\t\t{\n\t\t\t\t\treturn NULL ;\n\t\t\t}\n\t}\n\tHex2RGB(p,aoResultRGB);\n\n\t\tstrcpy(lDefaultHexRGB, p);\n\n\t\tif (lPointerInputBox) strcpy(lPointerInputBox, lString); /* restore its previous content to tinyfd_inputBox */\n\n\t\treturn lDefaultHexRGB;\n}\n\n\n#else /* unix */\n\nstatic char gPython2Name[16];\nstatic char gPython3Name[16];\nstatic char gPythonName[16];\n\nint tfd_isDarwin(void)\n{\n\t\tstatic int lsIsDarwin = -1 ;\n\t\tstruct utsname lUtsname ;\n\t\tif ( lsIsDarwin < 0 )\n\t\t{\n\t\t\t\tlsIsDarwin = !uname(&lUtsname) && !strcmp(lUtsname.sysname,\"Darwin\") ;\n\t\t}\n\t\treturn lsIsDarwin ;\n}\n\n\nstatic int dirExists( char const * aDirPath )\n{\n\t\tDIR * lDir ;\n\t\tif ( ! aDirPath || ! strlen( aDirPath ) )\n\t\t\t\treturn 0 ;\n\t\tlDir = opendir( aDirPath ) ;\n\t\tif ( ! lDir )\n\t\t{\n\t\t\treturn 0 ;\n\t\t}\n\t\tclosedir( lDir ) ;\n\t\treturn 1 ;\n}\n\n\nstatic int detectPresence( char const * aExecutable )\n{\n   char lBuff[MAX_PATH_OR_CMD] ;\n   char lTestedString[MAX_PATH_OR_CMD] = \"command -v \" ;\n   FILE * lIn ;\n#ifdef _GNU_SOURCE\n   char* lAllocatedCharString;\n   int lSubstringUndetected;\n#endif\n\n   strcat( lTestedString , aExecutable ) ;\n   strcat( lTestedString, \" 2>/dev/null \");\n   lIn = popen( lTestedString , \"r\" ) ;\n   if ( ( fgets( lBuff , sizeof( lBuff ) , lIn ) != NULL )\n\t&& ( ! strchr( lBuff , ':' ) ) && ( strncmp(lBuff, \"no \", 3) ) )\n   {   /* present */\n\t  pclose( lIn ) ;\n\n#ifdef _GNU_SOURCE /*to bypass this, just comment out \"#define _GNU_SOURCE\" at the top of the file*/\n\t  if ( lBuff[strlen( lBuff ) -1] == '\\n' ) lBuff[strlen( lBuff ) -1] = '\\0' ;\n\t  lAllocatedCharString = realpath(lBuff,NULL); /*same as canonicalize_file_name*/\n\t  lSubstringUndetected = ! strstr(lAllocatedCharString, aExecutable);\n\t  free(lAllocatedCharString);\n\t  if (lSubstringUndetected)\n\t  {\n\t\t if (tinyfd_verbose) printf(\"detectPresence %s %d\\n\", aExecutable, 0);\n\t\t return 0;\n\t  }\n#endif /*_GNU_SOURCE*/\n\n\t  if (tinyfd_verbose) printf(\"detectPresence %s %d\\n\", aExecutable, 1);\n\t  return 1 ;\n   }\n   else\n   {\n\t  pclose( lIn ) ;\n\t  if (tinyfd_verbose) printf(\"detectPresence %s %d\\n\", aExecutable, 0);\n\t  return 0 ;\n   }\n}\n\n\nstatic char * getVersion( char const * aExecutable ) /*version must be first numeral*/\n{\n\t\tstatic char lBuff[MAX_PATH_OR_CMD] ;\n\t\tchar lTestedString[MAX_PATH_OR_CMD] ;\n\t\tFILE * lIn ;\n\t\tchar * lTmp ;\n\n\tstrcpy( lTestedString , aExecutable ) ;\n\tstrcat( lTestedString , \" --version\" ) ;\n\n\tlIn = popen( lTestedString , \"r\" ) ;\n\t\tlTmp = fgets( lBuff , sizeof( lBuff ) , lIn ) ;\n\t\tpclose( lIn ) ;\n\n\t\tlTmp += strcspn(lTmp,\"0123456789\");\n\t\t /* printf(\"lTmp:%s\\n\", lTmp); */\n\t\treturn lTmp ;\n}\n\n\nstatic int * getMajorMinorPatch( char const * aExecutable )\n{\n\t\tstatic int lArray[3] ;\n\t\tchar * lTmp ;\n\n\t\tlTmp = (char *) getVersion(aExecutable);\n\t\tlArray[0] = atoi( strtok(lTmp,\" ,.-\") ) ;\n\t\t/* printf(\"lArray0 %d\\n\", lArray[0]); */\n\t\tlArray[1] = atoi( strtok(0,\" ,.-\") ) ;\n\t\t/* printf(\"lArray1 %d\\n\", lArray[1]); */\n\t\tlArray[2] = atoi( strtok(0,\" ,.-\") ) ;\n\t\t/* printf(\"lArray2 %d\\n\", lArray[2]); */\n\n\t\tif ( !lArray[0] && !lArray[1] && !lArray[2] ) return NULL;\n\t\treturn lArray ;\n}\n\n\nstatic int tryCommand( char const * aCommand )\n{\n\t\tchar lBuff[MAX_PATH_OR_CMD] ;\n\t\tFILE * lIn ;\n\n\t\tlIn = popen( aCommand , \"r\" ) ;\n\t\tif ( fgets( lBuff , sizeof( lBuff ) , lIn ) == NULL )\n\t\t{       /* present */\n\t\t\t\tpclose( lIn ) ;\n\t\t\t\treturn 1 ;\n\t\t}\n\t\telse\n\t\t{\n\t\t\t\tpclose( lIn ) ;\n\t\t\t\treturn 0 ;\n\t\t}\n\n}\n\n\nstatic int isTerminalRunning(void)\n{\n\t\tstatic int lIsTerminalRunning = -1 ;\n\t\tif ( lIsTerminalRunning < 0 )\n\t\t{\n\t\t\t\tlIsTerminalRunning = isatty(1);\n\t\t\t\tif (tinyfd_verbose) printf(\"isTerminalRunning %d\\n\", lIsTerminalRunning );\n\t\t}\n\t\treturn lIsTerminalRunning;\n}\n\n\nstatic char * dialogNameOnly(void)\n{\n\t\tstatic char lDialogName[128] = \"*\" ;\n\t\tif ( lDialogName[0] == '*' )\n\t\t{\n\t\t\t\tif (!tinyfd_allowCursesDialogs)\n\t\t\t\t{\n\t\t\t\t\t\tstrcpy(lDialogName , \"\" );\n\t\t\t\t}\n\t\t\t\telse if ( tfd_isDarwin() && * strcpy(lDialogName , \"/opt/local/bin/dialog\" )\n\t\t\t\t\t\t&& detectPresence( lDialogName ) )\n\t\t\t\t{}\n\t\t\t\telse if ( * strcpy(lDialogName , \"dialog\" )\n\t\t\t\t\t\t&& detectPresence( lDialogName ) )\n\t\t\t\t{}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\t\tstrcpy(lDialogName , \"\" );\n\t\t\t\t}\n\t\t}\n\t\treturn lDialogName ;\n}\n\n\nint isDialogVersionBetter09b(void)\n{\n\t\tchar const * lDialogName ;\n\t\tchar * lVersion ;\n\t\tint lMajor ;\n\t\tint lMinor ;\n\t\tint lDate ;\n\t\tint lResult ;\n\t\tchar * lMinorP ;\n\t\tchar * lLetter ;\n\t\tchar lBuff[128] ;\n\n\t\t/*char lTest[128] = \" 0.9b-20031126\" ;*/\n\n\t\tlDialogName = dialogNameOnly() ;\n\t\tif ( ! strlen(lDialogName) || !(lVersion = (char *) getVersion(lDialogName)) ) return 0 ;\n\t\t/*lVersion = lTest ;*/\n\t\t/*printf(\"lVersion %s\\n\", lVersion);*/\n\t\tstrcpy(lBuff,lVersion);\n\t\tlMajor = atoi( strtok(lVersion,\" ,.-\") ) ;\n\t\t/*printf(\"lMajor %d\\n\", lMajor);*/\n\t\tlMinorP = strtok(0,\" ,.-abcdefghijklmnopqrstuvxyz\");\n\t\tlMinor = atoi( lMinorP ) ;\n\t\t/*printf(\"lMinor %d\\n\", lMinor );*/\n\t\tlDate = atoi( strtok(0,\" ,.-\") ) ;\n\t\tif (lDate<0) lDate = - lDate;\n\t\t/*printf(\"lDate %d\\n\", lDate);*/\n\t\tlLetter = lMinorP + strlen(lMinorP) ;\n\t\tstrcpy(lVersion,lBuff);\n\t\tstrtok(lLetter,\" ,.-\");\n\t\t/*printf(\"lLetter %s\\n\", lLetter);*/\n\t\tlResult = (lMajor > 0) || ( ( lMinor == 9 ) && (*lLetter == 'b') && (lDate >= 20031126) );\n\t\t/*printf(\"lResult %d\\n\", lResult);*/\n\t\treturn lResult;\n}\n\n\nstatic int whiptailPresentOnly(void)\n{\n\t\tstatic int lWhiptailPresent = -1 ;\n\t\t\t\tif (!tinyfd_allowCursesDialogs) return 0;\n\t\tif ( lWhiptailPresent < 0 )\n\t\t{\n\t\t\t\tlWhiptailPresent = detectPresence( \"whiptail\" ) ;\n\t\t}\n\t\treturn lWhiptailPresent ;\n}\n\n\nstatic char * terminalName(void)\n{\n\t\tstatic char lTerminalName[128] = \"*\" ;\n\t\tchar lShellName[64] = \"*\" ;\n\t\tint * lArray;\n\n\t\tif ( lTerminalName[0] == '*' )\n\t\t{\n\t\t\t\tif ( detectPresence( \"bash\" ) )\n\t\t\t\t{\n\t\t\t\t\t\tstrcpy(lShellName , \"bash -c \" ) ; /*good for basic input*/\n\t\t\t\t}\n\t\t\t\t\t\t\t\telse if ( strlen(dialogNameOnly()) || whiptailPresentOnly() )\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\tstrcpy(lShellName , \"sh -c \" ) ; /*good enough for dialog & whiptail*/\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\telse\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tstrcpy(lTerminalName , \"\" ) ;\n\t\t\t\t\t\t\t\t\t\treturn NULL ;\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\tif ( tfd_isDarwin() )\n\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tif ( * strcpy(lTerminalName , \"/opt/X11/bin/xterm\" )\n\t\t\t\t\t  && detectPresence( lTerminalName ) )\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tstrcat(lTerminalName , \" -fa 'DejaVu Sans Mono' -fs 10 -title tinyfiledialogs -e \" ) ;\n\t\t\t\t\t\t\t\tstrcat(lTerminalName , lShellName ) ;\n\t\t\t\t\t\t}\n\t\t\t\t\t\telse\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tstrcpy(lTerminalName , \"\" ) ;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\telse if ( * strcpy(lTerminalName,\"xterm\") /*good (small without parameters)*/\n\t\t\t\t\t\t&& detectPresence(lTerminalName) )\n\t\t\t\t{\n\t\t\t\t\t\tstrcat(lTerminalName , \" -fa 'DejaVu Sans Mono' -fs 10 -title tinyfiledialogs -e \" ) ;\n\t\t\t\t\t\tstrcat(lTerminalName , lShellName ) ;\n\t\t\t\t}\n\t\t\t\telse if ( * strcpy(lTerminalName,\"terminator\") /*good*/\n\t\t\t\t\t\t  && detectPresence(lTerminalName) )\n\t\t\t\t{\n\t\t\t\t\t\tstrcat(lTerminalName , \" -x \" ) ;\n\t\t\t\t\t\tstrcat(lTerminalName , lShellName ) ;\n\t\t\t\t}\n\t\t\t\telse if ( * strcpy(lTerminalName,\"lxterminal\") /*good*/\n\t\t\t\t\t\t  && detectPresence(lTerminalName) )\n\t\t\t\t{\n\t\t\t\t\t\tstrcat(lTerminalName , \" -e \" ) ;\n\t\t\t\t\t\tstrcat(lTerminalName , lShellName ) ;\n\t\t\t\t}\n\t\t\t\telse if ( * strcpy(lTerminalName,\"konsole\") /*good*/\n\t\t\t\t\t\t  && detectPresence(lTerminalName) )\n\t\t\t\t{\n\t\t\t\t\t\tstrcat(lTerminalName , \" -e \" ) ;\n\t\t\t\t\t\tstrcat(lTerminalName , lShellName ) ;\n\t\t\t\t}\n\t\t\t\telse if ( * strcpy(lTerminalName,\"kterm\") /*good*/\n\t\t\t\t\t\t  && detectPresence(lTerminalName) )\n\t\t\t\t{\n\t\t\t\t\t\tstrcat(lTerminalName , \" -e \" ) ;\n\t\t\t\t\t\tstrcat(lTerminalName , lShellName ) ;\n\t\t\t\t}\n\t\t\t\telse if ( * strcpy(lTerminalName,\"tilix\") /*good*/\n\t\t\t\t\t\t  && detectPresence(lTerminalName) )\n\t\t\t\t{\n\t\t\t\t\t\tstrcat(lTerminalName , \" -e \" ) ;\n\t\t\t\t\t\tstrcat(lTerminalName , lShellName ) ;\n\t\t\t\t}\n\t\t\t\telse if ( * strcpy(lTerminalName,\"xfce4-terminal\") /*good*/\n\t\t\t\t\t\t  && detectPresence(lTerminalName) )\n\t\t\t\t{\n\t\t\t\t\t\tstrcat(lTerminalName , \" -x \" ) ;\n\t\t\t\t\t\tstrcat(lTerminalName , lShellName ) ;\n\t\t\t\t}\n\t\t\t\telse if ( * strcpy(lTerminalName,\"mate-terminal\") /*good*/\n\t\t\t\t\t\t  && detectPresence(lTerminalName) )\n\t\t\t\t{\n\t\t\t\t\t\tstrcat(lTerminalName , \" -x \" ) ;\n\t\t\t\t\t\tstrcat(lTerminalName , lShellName ) ;\n\t\t\t\t}\n\t\t\t\telse if ( * strcpy(lTerminalName,\"Eterm\") /*good*/\n\t\t\t\t\t\t  && detectPresence(lTerminalName) )\n\t\t\t\t{\n\t\t\t\t\t\tstrcat(lTerminalName , \" -e \" ) ;\n\t\t\t\t\t\tstrcat(lTerminalName , lShellName ) ;\n\t\t\t\t}\n\t\t\t\telse if ( * strcpy(lTerminalName,\"evilvte\") /*good*/\n\t\t\t\t\t\t  && detectPresence(lTerminalName) )\n\t\t\t\t{\n\t\t\t\t\t\tstrcat(lTerminalName , \" -e \" ) ;\n\t\t\t\t\t\tstrcat(lTerminalName , lShellName ) ;\n\t\t\t\t}\n\t\t\t\telse if ( * strcpy(lTerminalName,\"pterm\") /*good (only letters)*/\n\t\t\t\t\t\t  && detectPresence(lTerminalName) )\n\t\t\t\t{\n\t\t\t\t\t\tstrcat(lTerminalName , \" -e \" ) ;\n\t\t\t\t\t\tstrcat(lTerminalName , lShellName ) ;\n\t\t\t\t}\n\t\t\t\t\t\t\t\telse if ( * strcpy(lTerminalName,\"gnome-terminal\")\n\t\t\t\t&& detectPresence(lTerminalName) && (lArray = getMajorMinorPatch(lTerminalName))\n\t\t\t\t\t\t\t\t&& ((lArray[0]<3) || (lArray[0]==3 && lArray[1]<=6)) )\n\t\t\t\t{\n\t\t\t\t\t\tstrcat(lTerminalName , \" --disable-factory -x \" ) ;\n\t\t\t\t\t\tstrcat(lTerminalName , lShellName ) ;\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\t\tstrcpy(lTerminalName , \"\" ) ;\n\t\t\t\t}\n\t\t\t\t/* bad: koi rxterm guake tilda vala-terminal qterminal kgx\n\t\t\t\taterm Terminal terminology sakura lilyterm weston-terminal\n\t\t\t\troxterm termit xvt rxvt mrxvt urxvt */\n\t\t}\n\t\tif ( strlen(lTerminalName) )\n\t\t{\n\t\t\t\treturn lTerminalName ;\n\t\t}\n\t\telse\n\t\t{\n\t\t\t\treturn NULL ;\n\t\t}\n}\n\n\nstatic char * dialogName(void)\n{\n\tchar * lDialogName ;\n\tlDialogName = dialogNameOnly( ) ;\n\t\tif ( strlen(lDialogName) && ( isTerminalRunning() || terminalName() ) )\n\t\t{\n\t\t\t\treturn lDialogName ;\n\t\t}\n\t\telse\n\t\t{\n\t\t\t\treturn NULL ;\n\t\t}\n}\n\n\nstatic int whiptailPresent(void)\n{\n\t\tint lWhiptailPresent ;\n\tlWhiptailPresent = whiptailPresentOnly( ) ;\n\t\tif ( lWhiptailPresent && ( isTerminalRunning() || terminalName() ) )\n\t\t{\n\t\t\t\treturn lWhiptailPresent ;\n\t\t}\n\t\telse\n\t\t{\n\t\t\t\treturn 0 ;\n\t\t}\n}\n\n\n\nstatic int graphicMode(void)\n{\n\t\treturn !( tinyfd_forceConsole && (isTerminalRunning() || terminalName()) )\n\t\t\t\t\t\t&& ( getenvDISPLAY()\n\t\t\t\t\t\t|| (tfd_isDarwin() && (!getenv(\"SSH_TTY\") || getenvDISPLAY() ) ) ) ;\n}\n\n\nstatic int ffplayPresent(void)\n{\n   static int lFFplayPresent = -1;\n   if (lFFplayPresent < 0)\n   {\n\t  lFFplayPresent = detectPresence(\"ffplay\");\n   }\n   return lFFplayPresent;\n}\n\n\nstatic int pactlPresent( void )\n{\n\tstatic int lPactlPresent = -1 ;\n\tchar lBuff [256] ;\n\tFILE * lIn ;\n\n\tif ( lPactlPresent < 0 )\n\t{\n\t\tlPactlPresent = detectPresence(\"pactl\") ;\n\t\tif ( lPactlPresent )\n\t\t{\n\t\t\tlIn = popen( \"pactl info | grep -iF pulseaudio\" , \"r\" ) ;\n\t\t\tif ( ! (fgets( lBuff , sizeof( lBuff ) , lIn ) && ! strstr(lBuff, \"PipeWire\") ) )\n\t\t\t{\n\t\t\t\tlPactlPresent = 0 ;\n\t\t\t}\n\t\t\tpclose( lIn ) ;\n\t\t\tif (tinyfd_verbose) printf(\"is pactl valid ? %d\\n\", lPactlPresent);\n\t\t}\n\t}\n\treturn lPactlPresent ;\n}\n\n\nstatic int speakertestPresent(void)\n{\n\t\tstatic int lSpeakertestPresent = -1 ;\n\t\tif ( lSpeakertestPresent < 0 )\n\t\t{\n\t\t\t\tlSpeakertestPresent = detectPresence(\"speaker-test\") ;\n\t\t}\n\t\treturn lSpeakertestPresent ;\n}\n\n\nstatic int playPresent(void) /* play is part of sox */\n{\n   static int lPlayPresent = -1;\n   if (lPlayPresent < 0)\n   {\n\t  lPlayPresent = detectPresence(\"sox\"); /*if sox is present, play is ready*/\n   }\n   return lPlayPresent;\n}\n\n\nstatic int beepexePresent(void)\n{\n   static int lBeepexePresent = -1;\n   if (lBeepexePresent < 0)\n   {\n\t  lBeepexePresent = detectPresence(\"beep.exe\");\n   }\n   return lBeepexePresent;\n}\n\n\n/*static int beepPresent(void)\n{\n\t\tstatic int lBeepPresent = -1 ;\n\t\tif ( lBeepPresent < 0 )\n\t\t{\n\t\t\t\tlBeepPresent = detectPresence(\"beep\") ;\n\t\t}\n\t\treturn lBeepPresent ;\n}*/\n\n\nstatic int playsoundPresent(void) /* playsound is part of pipewire */\n{\n    static int lPlaysoundPresent = -1 ;\n    if (lPlaysoundPresent < 0)\n    {\n        lPlaysoundPresent = detectPresence(\"playsound_simple\");\n        if ( lPlaysoundPresent && ! fileExists(\"/usr/share/sounds/freedesktop/stereo/bell.oga\") )\n        {\n            lPlaysoundPresent = 0 ;\n        }\n    }\n    return lPlaysoundPresent;\n}\n\n\nstatic int paplayPresent(void) /* playsound is part of pipewire */\n{\n    static int lPaplayPresent = -1 ;\n    if (lPaplayPresent < 0)\n    {\n        lPaplayPresent = detectPresence(\"paplay\");\n        if ( lPaplayPresent && ! fileExists(\"/usr/share/sounds/freedesktop/stereo/bell.oga\") )\n        {\n            lPaplayPresent = 0 ;\n        }\n    }\n    return lPaplayPresent;\n}\n\n\nstatic int xmessagePresent(void)\n{\n\t\tstatic int lXmessagePresent = -1 ;\n\t\tif ( lXmessagePresent < 0 )\n\t\t{\n\t\t\t\tlXmessagePresent = detectPresence(\"xmessage\");/*if not tty,not on osxpath*/\n\t\t}\n\t\treturn lXmessagePresent && graphicMode( ) ;\n}\n\n\nstatic int gxmessagePresent(void)\n{\n\tstatic int lGxmessagePresent = -1 ;\n\tif ( lGxmessagePresent < 0 )\n\t{\n\t\tlGxmessagePresent = detectPresence(\"gxmessage\") ;\n\t}\n\treturn lGxmessagePresent && graphicMode( ) ;\n}\n\n\nstatic int gmessagePresent(void)\n{\n\t\tstatic int lGmessagePresent = -1 ;\n\t\tif ( lGmessagePresent < 0 )\n\t\t{\n\t\t\t\tlGmessagePresent = detectPresence(\"gmessage\") ;\n\t\t}\n\t\treturn lGmessagePresent && graphicMode( ) ;\n}\n\n\nstatic int notifysendPresent(void)\n{\n\tstatic int lNotifysendPresent = -1 ;\n\tif ( lNotifysendPresent < 0 )\n\t{\n\t\tlNotifysendPresent = detectPresence(\"notify-send\") ;\n\t}\n\treturn lNotifysendPresent && graphicMode( ) ;\n}\n\n\nstatic int perlPresent(void)\n{\n   static int lPerlPresent = -1 ;\n   char lBuff[MAX_PATH_OR_CMD] ;\n   FILE * lIn ;\n\n   if ( lPerlPresent < 0 )\n   {\n\t  lPerlPresent = detectPresence(\"perl\") ;\n\t  if (lPerlPresent)\n\t  {\n\t\t lIn = popen(\"perl -MNet::DBus -e \\\"Net::DBus->session->get_service('org.freedesktop.Notifications')\\\" 2>&1\", \"r\");\n\t\t if (fgets(lBuff, sizeof(lBuff), lIn) == NULL)\n\t\t {\n\t\t\tlPerlPresent = 2;\n\t\t }\n\t\t pclose(lIn);\n\t\t if (tinyfd_verbose) printf(\"perl-dbus %d\\n\", lPerlPresent);\n\t  }\n   }\n   return graphicMode() ? lPerlPresent : 0 ;\n}\n\n\nstatic int afplayPresent(void)\n{\n\t\tstatic int lAfplayPresent = -1 ;\n\t\tchar lBuff[MAX_PATH_OR_CMD] ;\n\t\tFILE * lIn ;\n\n\t\tif ( lAfplayPresent < 0 )\n\t\t{\n\t\t\t\tlAfplayPresent = detectPresence(\"afplay\") ;\n\t\t\t\tif ( lAfplayPresent )\n\t\t\t\t{\n\t\t\t\t\t\tlIn = popen( \"test -e /System/Library/Sounds/Ping.aiff || echo Ping\" , \"r\" ) ;\n\t\t\t\t\t\tif ( fgets( lBuff , sizeof( lBuff ) , lIn ) == NULL )\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tlAfplayPresent = 2 ;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tpclose( lIn ) ;\n\t\t\t\t\t\tif (tinyfd_verbose) printf(\"afplay %d\\n\", lAfplayPresent);\n\t\t\t\t}\n\t\t}\n\t\treturn graphicMode() ? lAfplayPresent : 0 ;\n}\n\n\nstatic int xdialogPresent(void)\n{\n\tstatic int lXdialogPresent = -1 ;\n\tif ( lXdialogPresent < 0 )\n\t{\n\t\tlXdialogPresent = detectPresence(\"Xdialog\") ;\n\t}\n\treturn lXdialogPresent && graphicMode( ) ;\n}\n\n\nstatic int gdialogPresent(void)\n{\n\tstatic int lGdialoglPresent = -1 ;\n\tif ( lGdialoglPresent < 0 )\n\t{\n\t\tlGdialoglPresent = detectPresence( \"gdialog\" ) ;\n\t}\n\treturn lGdialoglPresent && graphicMode( ) ;\n}\n\n\nstatic int osascriptPresent(void)\n{\n\tstatic int lOsascriptPresent = -1 ;\n\tif ( lOsascriptPresent < 0 )\n\t{\n\t\t\t\tgWarningDisplayed |= !!getenv(\"SSH_TTY\");\n\t\t\t\tlOsascriptPresent = detectPresence( \"osascript\" ) ;\n\t}\n\t\treturn lOsascriptPresent && graphicMode() && !getenv(\"SSH_TTY\") ;\n}\n\n\nstatic int dunstifyPresent(void)\n{\n\tstatic int lDunstifyPresent = -1 ;\n\t\tstatic char lBuff[MAX_PATH_OR_CMD] ;\n\t\tFILE * lIn ;\n\t\tchar * lTmp ;\n\n\tif ( lDunstifyPresent < 0 )\n\t{\n\t\tlDunstifyPresent = detectPresence( \"dunstify\" ) ;\n\t\tif ( lDunstifyPresent )\n\t\t{\n\t\t\tlIn = popen( \"dunstify -s\" , \"r\" ) ;\n\t\t\tlTmp = fgets( lBuff , sizeof( lBuff ) , lIn ) ;\n\t\t\tpclose( lIn ) ;\n\t\t\t/* printf(\"lTmp:%s\\n\", lTmp); */\n\t\t\tlDunstifyPresent = strstr(lTmp,\"name:dunst\\n\") ? 1 : 0 ;\n\t\t\tif (tinyfd_verbose) printf(\"lDunstifyPresent %d\\n\", lDunstifyPresent);\n\t\t}\n\t}\n\treturn lDunstifyPresent && graphicMode( ) ;\n}\n\n\nstatic int dunstPresent(void)\n{\n\tstatic int lDunstPresent = -1 ;\n\t\tstatic char lBuff[MAX_PATH_OR_CMD] ;\n\t\tFILE * lIn ;\n\t\tchar * lTmp ;\n\n\tif ( lDunstPresent < 0 )\n\t{\n\t\tlDunstPresent = detectPresence( \"dunst\" ) ;\n\t\tif ( lDunstPresent )\n\t\t{\n\t\t\tlIn = popen( \"ps -e | grep dunst | grep -v grep\" , \"r\" ) ; /* add \"| wc -l\" to receive the number of lines */\n\t\t\tlTmp = fgets( lBuff , sizeof( lBuff ) , lIn ) ;\n\t\t\tpclose( lIn ) ;\n\t\t\t/* if ( lTmp ) printf(\"lTmp:%s\\n\", lTmp); */\n\t\t\tif ( lTmp ) lDunstPresent = 1 ;\n\t\t\telse lDunstPresent = 0 ;\n\t\t\tif (tinyfd_verbose) printf(\"lDunstPresent %d\\n\", lDunstPresent);\n\t\t}\n\t}\n\treturn lDunstPresent && graphicMode( ) ;\n}\n\n\nint tfd_qarmaPresent(void)\n{\n\t\tstatic int lQarmaPresent = -1 ;\n\t\tif ( lQarmaPresent < 0 )\n\t\t{\n\t\t\t\tlQarmaPresent = detectPresence(\"qarma\") ;\n\t\t}\n\t\treturn lQarmaPresent && graphicMode( ) ;\n}\n\n\nint tfd_matedialogPresent(void)\n{\n\t\tstatic int lMatedialogPresent = -1 ;\n\t\tif ( lMatedialogPresent < 0 )\n\t\t{\n\t\t\t\tlMatedialogPresent = detectPresence(\"matedialog\") ;\n\t\t}\n\t\treturn lMatedialogPresent && graphicMode( ) ;\n}\n\n\nint tfd_shellementaryPresent(void)\n{\n\t\tstatic int lShellementaryPresent = -1 ;\n\t\tif ( lShellementaryPresent < 0 )\n\t\t{\n\t\t\t\tlShellementaryPresent = 0 ; /*detectPresence(\"shellementary\"); shellementary is not ready yet */\n\t\t}\n\t\treturn lShellementaryPresent && graphicMode( ) ;\n}\n\n\nint tfd_xpropPresent(void)\n{\n\tstatic int lXpropReady = 0 ;\n\tstatic int lXpropDetected = -1 ;\n\tchar lBuff[MAX_PATH_OR_CMD] ;\n\tFILE * lIn ;\n\n\tif ( lXpropDetected < 0 )\n\t{\n\t\tlXpropDetected = detectPresence(\"xprop\") ;\n\t}\n\n\tif ( !lXpropReady && lXpropDetected )\n\t{\t/* xwayland Debian issue reported by Kay F. Jahnke and solved with his help */\n\t\tlIn = popen( \"xprop -root 32x '\t$0' _NET_ACTIVE_WINDOW\" , \"r\" ) ;\n\t\tif ( fgets( lBuff , sizeof( lBuff ) , lIn ) != NULL )\n\t\t{\n\t\t\tif ( ! strstr( lBuff , \"not found\" ) )\n\t\t\t{\n\t\t\t\tif (tinyfd_verbose) printf(\"xprop is ready\\n\");\n\t\t\t\tlXpropReady = 1 ;\n\t\t\t}\n\t\t}\n\t\tpclose( lIn ) ;\n\t}\n\treturn graphicMode() ? lXpropReady : 0 ;\n}\n\n\nint tfd_zenityPresent(void)\n{\n\t\tstatic int lZenityPresent = -1 ;\n\t\tif ( lZenityPresent < 0 )\n\t\t{\n\t\t\t\tlZenityPresent = detectPresence(\"zenity\") ;\n\t\t}\n\t\treturn lZenityPresent && graphicMode( ) ;\n}\n\n\nint tfd_yadPresent(void)\n{\n   static int lYadPresent = -1;\n   if (lYadPresent < 0)\n   {\n\t  lYadPresent = detectPresence(\"yad\");\n   }\n   return lYadPresent && graphicMode();\n}\n\n\nint tfd_zenity3Present(void)\n{\n\t\tstatic int lZenity3Present = -1 ;\n\t\tchar lBuff[MAX_PATH_OR_CMD] ;\n\t\tFILE * lIn ;\n\t\t\t\tint lIntTmp ;\n\n\t\tif ( lZenity3Present < 0 )\n\t\t{\n\t\t\t\tlZenity3Present = 0 ;\n\t\t\t\tif ( tfd_zenityPresent() )\n\t\t\t\t{\n\t\t\t\t\t\tlIn = popen( \"zenity --version\" , \"r\" ) ;\n\t\t\t\t\t\tif ( fgets( lBuff , sizeof( lBuff ) , lIn ) != NULL )\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tif ( atoi(lBuff) >= 3 )\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\tlZenity3Present = 3 ;\n\t\t\t\t\t\t\t\t\tlIntTmp = atoi(strtok(lBuff,\".\")+2 ) ;\n\t\t\t\t\t\t\t\t\tif ( lIntTmp >= 18 )\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\tlZenity3Present = 5 ;\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\telse if ( lIntTmp >= 10 )\n\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\tlZenity3Present = 4 ;\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\telse if ( ( atoi(lBuff) == 2 ) && ( atoi(strtok(lBuff,\".\")+2 ) >= 32 ) )\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tlZenity3Present = 2 ;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tif (tinyfd_verbose) printf(\"zenity type %d\\n\", lZenity3Present);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tpclose( lIn ) ;\n\t\t\t\t}\n\t\t}\n\t\treturn graphicMode() ? lZenity3Present : 0 ;\n}\n\n\nint tfd_kdialogPresent(void)\n{\n    static int lKdialogPresent = -1 ;\n    char lBuff[MAX_PATH_OR_CMD] ;\n    FILE * lIn ;\n    char * lDesktop;\n\n    if ( lKdialogPresent < 0 )\n    {\n        lDesktop = getenv(\"XDG_SESSION_DESKTOP\");\n        if ( !lDesktop  || ( strcmp(lDesktop, \"KDE\") && strcmp(lDesktop, \"lxqt\") ) )\n        {\n            if ( tfd_zenityPresent() )\n            {\n                lKdialogPresent = 0 ;\n                return lKdialogPresent ;\n            }\n        }\n\n        lKdialogPresent = detectPresence(\"kdialog\") ;\n        if ( lKdialogPresent && !getenv(\"SSH_TTY\") )\n        {\n            lIn = popen( \"kdialog --attach 2>&1\" , \"r\" ) ;\n            if ( fgets( lBuff , sizeof( lBuff ) , lIn ) != NULL )\n            {\n                if ( ! strstr( \"Unknown\" , lBuff ) )\n                {\n                    lKdialogPresent = 2 ;\n                    if (tinyfd_verbose) printf(\"kdialog-attach %d\\n\", lKdialogPresent);\n                }\n            }\n            pclose( lIn ) ;\n\n            if (lKdialogPresent == 2)\n            {\n                lKdialogPresent = 1 ;\n                lIn = popen( \"kdialog --passivepopup 2>&1\" , \"r\" ) ;\n                if ( fgets( lBuff , sizeof( lBuff ) , lIn ) != NULL )\n                {\n                    if ( ! strstr( \"Unknown\" , lBuff ) )\n                    {\n                        lKdialogPresent = 2 ;\n                        if (tinyfd_verbose) printf(\"kdialog-popup %d\\n\", lKdialogPresent);\n                    }\n                }\n                pclose( lIn ) ;\n            }\n        }\n    }\n    return graphicMode() ? lKdialogPresent : 0 ;\n}\n\n\nstatic int osx9orBetter(void)\n{\n\t\tstatic int lOsx9orBetter = -1 ;\n\t\tchar lBuff[MAX_PATH_OR_CMD] ;\n\t\tFILE * lIn ;\n\t\tint V,v;\n\n\t\tif ( lOsx9orBetter < 0 )\n\t\t{\n\t\t\t\tlOsx9orBetter = 0 ;\n\t\t\t\tlIn = popen( \"osascript -e 'set osver to system version of (system info)'\" , \"r\" ) ;\n\t\t\t\tV = 0 ;\n\t\t\t\tif ( ( fgets( lBuff , sizeof( lBuff ) , lIn ) != NULL )\n\t\t\t\t\t\t&& ( 2 == sscanf(lBuff, \"%d.%d\", &V, &v) ) )\n\t\t\t\t{\n\t\t\t\t\t\tV = V * 100 + v;\n\t\t\t\t\t\tif ( V >= 1009 )\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tlOsx9orBetter = 1 ;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tpclose( lIn ) ;\n\t\t\t\tif (tinyfd_verbose) printf(\"Osx10 = %d, %d = %s\\n\", lOsx9orBetter, V, lBuff) ;\n\t\t}\n\t\treturn lOsx9orBetter ;\n}\n\n\nstatic int python3Present(void)\n{\n\t\tstatic int lPython3Present = -1 ;\n\n\t\tif ( lPython3Present < 0 )\n\t\t{\n\t\t\t\tlPython3Present = 0 ;\n\t\t\t\tstrcpy(gPython3Name , \"python3\" ) ;\n\t\t\t\tif ( detectPresence(gPython3Name) ) lPython3Present = 1;\n\t\t\t\tif (tinyfd_verbose) printf(\"lPython3Present %d\\n\", lPython3Present) ;\n\t\t\t\tif (tinyfd_verbose) printf(\"gPython3Name %s\\n\", gPython3Name) ;\n\t\t}\n\t\t\t\treturn lPython3Present ;\n}\n\n\nstatic int python2Present(void)\n{\n\t\tstatic int lPython2Present = -1 ;\n\n\t\tif ( lPython2Present < 0 )\n\t\t{\n\t\t\t\tlPython2Present = 0 ;\n\t\t\t\tstrcpy(gPython2Name , \"python2\" ) ;\n\t\t\t\tif ( detectPresence(gPython2Name) ) lPython2Present = 1;\n\t\t\t\tif (tinyfd_verbose) printf(\"lPython2Present %d\\n\", lPython2Present) ;\n\t\t\t\tif (tinyfd_verbose) printf(\"gPython2Name %s\\n\", gPython2Name) ;\n\t\t}\n\t\treturn lPython2Present ;\n}\n\n\nstatic int tkinter3Present(void)\n{\n\t\tstatic int lTkinter3Present = -1 ;\n\t\tchar lPythonCommand[256];\n\t\tchar lPythonParams[128] =\n\t\t\t\t\"-S -c \\\"try:\\n\\timport tkinter;\\nexcept:\\n\\tprint(0);\\\"\";\n\n\t\tif ( lTkinter3Present < 0 )\n\t\t{\n\t\t\t\tlTkinter3Present = 0 ;\n\t\t\t\tif ( python3Present() )\n\t\t\t\t{\n\t\t\t\t\t\tsprintf( lPythonCommand , \"%s %s\" , gPython3Name , lPythonParams ) ;\n\t\t\t\t\t\tlTkinter3Present = tryCommand(lPythonCommand) ;\n\t\t\t\t}\n\t\t\t\tif (tinyfd_verbose) printf(\"lTkinter3Present %d\\n\", lTkinter3Present) ;\n\t\t}\n\t\t\t\treturn lTkinter3Present && graphicMode() && !(tfd_isDarwin() && getenv(\"SSH_TTY\") );\n}\n\n\nstatic int tkinter2Present(void)\n{\n\t\tstatic int lTkinter2Present = -1 ;\n\t\tchar lPythonCommand[256];\n\t\tchar lPythonParams[128] =\n\t\t\t\t\"-S -c \\\"try:\\n\\timport Tkinter;\\nexcept:\\n\\tprint 0;\\\"\";\n\n\t\tif ( lTkinter2Present < 0 )\n\t\t{\n\t\t\t\tlTkinter2Present = 0 ;\n\t\t\t\tif ( python2Present() )\n\t\t\t\t{\n\t\t\t\t\t\tsprintf( lPythonCommand , \"%s %s\" , gPython2Name , lPythonParams ) ;\n\t\t\t\t\t\tlTkinter2Present = tryCommand(lPythonCommand) ;\n\t\t\t\t}\n\t\t\t\tif (tinyfd_verbose) printf(\"lTkinter2Present %d graphicMode %d \\n\", lTkinter2Present, graphicMode() ) ;\n\t\t}\n\t\treturn lTkinter2Present && graphicMode() && !(tfd_isDarwin() && getenv(\"SSH_TTY\") );\n}\n\n\nstatic int pythonDbusPresent(void)\n{\n\tstatic int lPythonDbusPresent = -1 ;\n\t\tchar lPythonCommand[384];\n\t\tchar lPythonParams[256] =\n\"-c \\\"try:\\n\\timport dbus;bus=dbus.SessionBus();\\\nnotif=bus.get_object('org.freedesktop.Notifications','/org/freedesktop/Notifications');\\\nnotify=dbus.Interface(notif,'org.freedesktop.Notifications');\\nexcept:\\n\\tprint(0);\\\"\";\n\n\t\tif (lPythonDbusPresent < 0 )\n\t\t{\n\t\t   lPythonDbusPresent = 0 ;\n\t\t\t\tif ( python2Present() )\n\t\t\t\t{\n\t\t\t\t\t\tstrcpy(gPythonName , gPython2Name ) ;\n\t\t\t\t\t\tsprintf( lPythonCommand , \"%s %s\" , gPythonName , lPythonParams ) ;\n\t\t\t\t\t\tlPythonDbusPresent = tryCommand(lPythonCommand) ;\n\t\t\t\t}\n\n\t\t\t\tif ( !lPythonDbusPresent && python3Present() )\n\t\t\t\t{\n\t\t\t\t\t\tstrcpy(gPythonName , gPython3Name ) ;\n\t\t\t\t\t\tsprintf( lPythonCommand , \"%s %s\" , gPythonName , lPythonParams ) ;\n\t\t\t\t\t\tlPythonDbusPresent = tryCommand(lPythonCommand) ;\n\t\t\t\t}\n\n\t\t\t\tif (tinyfd_verbose) printf(\"lPythonDbusPresent %d\\n\", lPythonDbusPresent) ;\n\t\t\t\tif (tinyfd_verbose) printf(\"gPythonName %s\\n\", gPythonName) ;\n\t\t}\n\t\treturn lPythonDbusPresent && graphicMode() && !(tfd_isDarwin() && getenv(\"SSH_TTY\") );\n}\n\n\nstatic void sigHandler(int signum)\n{\n    FILE * lIn ;\n    if ( ( lIn = popen( \"pactl unload-module module-sine\" , \"r\" ) ) )\n    {\n        pclose( lIn ) ;\n    }\n    if (tinyfd_verbose) printf(\"tinyfiledialogs caught signal %d\\n\", signum);\n}\n\n\nvoid tinyfd_beep(void)\n{\n    char lDialogString[256] ;\n    FILE * lIn ;\n\n    if ( pactlPresent() )\n    {\n        signal(SIGINT, sigHandler);\n        strcpy( lDialogString ,\n            \"thnum=$(pactl load-module module-sine frequency=440);sleep .3;pactl unload-module $thnum\" ) ;\n    }\n    else if ( osascriptPresent() )\n    {\n        if ( afplayPresent() >= 2 )\n        {\n            strcpy( lDialogString , \"afplay /System/Library/Sounds/Ping.aiff\") ;\n        }\n        else\n        {\n            strcpy( lDialogString , \"osascript -e 'tell application \\\"System Events\\\" to beep'\") ;\n        }\n    }\n    else if ( speakertestPresent() )\n    {\n        /*strcpy( lDialogString , \"timeout -k .3 .3 speaker-test --frequency 440 --test sine > /dev/tty\" ) ;*/\n        strcpy( lDialogString , \"( speaker-test -t sine -f 440 > /dev/tty )& pid=$!;sleep .5; kill -9 $pid\" ) ; /*.3 was too short for mac g3*/\n    }\n    else if ( ffplayPresent() )\n    {\n        strcpy(lDialogString, \"ffplay -f lavfi -i sine=f=440:d=0.15 -autoexit -nodisp\" );\n    }\n    else if (playPresent()) /* play is part of sox */\n    {\n        strcpy(lDialogString, \"play -q -n synth .3 sine 440\");\n    }\n    else if ( playsoundPresent() )\n    {\n        strcpy( lDialogString , \"playsound_simple /usr/share/sounds/freedesktop/stereo/bell.oga\") ;\n    }\n    else if ( paplayPresent() )\n    {\n        strcpy( lDialogString , \"paplay /usr/share/sounds/freedesktop/stereo/bell.oga\") ;\n    }\n    else if (beepexePresent())\n    {\n        strcpy(lDialogString, \"beep.exe 440 300\");\n    }\n    /*else if ( beepPresent() )\n    {\n        strcpy( lDialogString , \"beep -f 440 -l 300\" ) ;\n    }*/\n    else\n    {\n        strcpy( lDialogString , \"printf '\\\\a' > /dev/tty\" ) ;\n    }\n\n    if (tinyfd_verbose) printf( \"lDialogString: %s\\n\" , lDialogString ) ;\n\n    if ( ( lIn = popen( lDialogString , \"r\" ) ) )\n    {\n            pclose( lIn ) ;\n    }\n\n    if ( pactlPresent() )\n    {\n            signal(SIGINT, SIG_DFL);\n    }\n}\n\n\nint tinyfd_messageBox(\n\t\tchar const * aTitle , /* NULL or \"\" */\n\t\tchar const * aMessage , /* NULL or \"\"  may contain \\n and \\t */\n\t\tchar const * aDialogType , /* \"ok\" \"okcancel\" \"yesno\" \"yesnocancel\" */\n\t\tchar const * aIconType , /* \"info\" \"warning\" \"error\" \"question\" */\n\t\tint aDefaultButton ) /* 0 for cancel/no , 1 for ok/yes , 2 for no in yesnocancel */\n{\n\t\tchar lBuff[MAX_PATH_OR_CMD] ;\n\t\tchar * lDialogString = NULL ;\n\t\tchar * lpDialogString;\n\t\tFILE * lIn ;\n\t\tint lWasGraphicDialog = 0 ;\n\t\tint lWasXterm = 0 ;\n\t\tint lResult ;\n\t\tchar lChar ;\n\t\tstruct termios infoOri;\n\t\tstruct termios info;\n\t\tsize_t lTitleLen ;\n\t\tsize_t lMessageLen ;\n\n\t\tlBuff[0]='\\0';\n\n\t\t\t\tif (tfd_quoteDetected(aTitle)) return tinyfd_messageBox(\"INVALID TITLE WITH QUOTES\", aMessage, aDialogType, aIconType, aDefaultButton);\n\t\t\t\tif (tfd_quoteDetected(aMessage)) return tinyfd_messageBox(aTitle, \"INVALID MESSAGE WITH QUOTES\", aDialogType, aIconType, aDefaultButton);\n\n\t\tlTitleLen =  aTitle ? strlen(aTitle) : 0 ;\n\t\tlMessageLen =  aMessage ? strlen(aMessage) : 0 ;\n\t\tif ( !aTitle || strcmp(aTitle,\"tinyfd_query\") )\n\t\t{\n\t\t\t\tlDialogString = (char *) malloc( MAX_PATH_OR_CMD + lTitleLen + lMessageLen );\n\t\t}\n\n\t\tif ( osascriptPresent( ) )\n\t\t{\n\t\t\t\tif (aTitle&&!strcmp(aTitle,\"tinyfd_query\")){strcpy(tinyfd_response,\"applescript\");return 1;}\n\n\t\t\t\tstrcpy( lDialogString , \"osascript \");\n\t\t\t\tif ( ! osx9orBetter() ) strcat( lDialogString , \" -e 'tell application \\\"System Events\\\"' -e 'Activate'\");\n\t\t\t\tstrcat( lDialogString , \" -e 'try' -e 'set {vButton} to {button returned} of ( display dialog \\\"\") ;\n\t\t\t\tif ( aMessage && strlen(aMessage) )\n\t\t\t\t{\n\t\t\t\t\t\tstrcat(lDialogString, aMessage) ;\n\t\t\t\t}\n\t\t\t\tstrcat(lDialogString, \"\\\" \") ;\n\t\t\t\tif ( aTitle && strlen(aTitle) )\n\t\t\t\t{\n\t\t\t\t\t\tstrcat(lDialogString, \"with title \\\"\") ;\n\t\t\t\t\t\tstrcat(lDialogString, aTitle) ;\n\t\t\t\t\t\tstrcat(lDialogString, \"\\\" \") ;\n\t\t\t\t}\n\t\t\t\tstrcat(lDialogString, \"with icon \") ;\n\t\t\t\tif ( aIconType && ! strcmp( \"error\" , aIconType ) )\n\t\t\t\t{\n\t\t\t\t\t\tstrcat(lDialogString, \"stop \" ) ;\n\t\t\t\t}\n\t\t\t\telse if ( aIconType && ! strcmp( \"warning\" , aIconType ) )\n\t\t\t\t{\n\t\t\t\t\t\tstrcat(lDialogString, \"caution \" ) ;\n\t\t\t\t}\n\t\t\t\telse /* question or info */\n\t\t\t\t{\n\t\t\t\t\t\tstrcat(lDialogString, \"note \" ) ;\n\t\t\t\t}\n\t\t\t\tif ( aDialogType && ! strcmp( \"okcancel\" , aDialogType ) )\n\t\t\t\t{\n\t\t\t\t\t\tif ( ! aDefaultButton )\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tstrcat( lDialogString ,\"default button \\\"Cancel\\\" \" ) ;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\telse if ( aDialogType && ! strcmp( \"yesno\" , aDialogType ) )\n\t\t\t\t{\n\t\t\t\t\t\tstrcat( lDialogString ,\"buttons {\\\"No\\\", \\\"Yes\\\"} \" ) ;\n\t\t\t\t\t\tif (aDefaultButton)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tstrcat( lDialogString ,\"default button \\\"Yes\\\" \" ) ;\n\t\t\t\t\t\t}\n\t\t\t\t\t\telse\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tstrcat( lDialogString ,\"default button \\\"No\\\" \" ) ;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tstrcat( lDialogString ,\"cancel button \\\"No\\\"\" ) ;\n\t\t\t\t}\n\t\t\t\telse if ( aDialogType && ! strcmp( \"yesnocancel\" , aDialogType ) )\n\t\t\t\t{\n\t\t\t\t\t\tstrcat( lDialogString ,\"buttons {\\\"No\\\", \\\"Yes\\\", \\\"Cancel\\\"} \" ) ;\n\t\t\t\t\t\tswitch (aDefaultButton)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tcase 1: strcat( lDialogString ,\"default button \\\"Yes\\\" \" ) ; break;\n\t\t\t\t\t\t\t\tcase 2: strcat( lDialogString ,\"default button \\\"No\\\" \" ) ; break;\n\t\t\t\t\t\t\t\tcase 0: strcat( lDialogString ,\"default button \\\"Cancel\\\" \" ) ; break;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tstrcat( lDialogString ,\"cancel button \\\"Cancel\\\"\" ) ;\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\t\tstrcat( lDialogString ,\"buttons {\\\"OK\\\"} \" ) ;\n\t\t\t\t\t\tstrcat( lDialogString ,\"default button \\\"OK\\\" \" ) ;\n\t\t\t\t}\n\t\t\t\tstrcat( lDialogString, \")' \") ;\n\n\t\t\t\tstrcat( lDialogString,\n\"-e 'if vButton is \\\"Yes\\\" then' -e 'return 1'\\\n -e 'else if vButton is \\\"OK\\\" then' -e 'return 1'\\\n -e 'else if vButton is \\\"No\\\" then' -e 'return 2'\\\n -e 'else' -e 'return 0' -e 'end if' \" );\n\n\t\t\t\tstrcat( lDialogString, \"-e 'on error number -128' \" ) ;\n\t\t\t\tstrcat( lDialogString, \"-e '0' \" );\n\n\t\t\t\tstrcat( lDialogString, \"-e 'end try'\") ;\n\t\t\t\tif ( ! osx9orBetter() ) strcat( lDialogString, \" -e 'end tell'\") ;\n\t\t}\n\t\telse if ( tfd_kdialogPresent() )\n\t\t{\n\t\t\t\tif (aTitle&&!strcmp(aTitle,\"tinyfd_query\")){strcpy(tinyfd_response,\"kdialog\");return 1;}\n\n\t\t\t\tstrcpy( lDialogString , \"kdialog\" ) ;\n\t\t\t\t\t\t\t\tif ( (tfd_kdialogPresent() == 2) && tfd_xpropPresent() )\n\t\t\t\t{\n\t\t\t\t\t\tstrcat(lDialogString, \" --attach=$(xprop -root 32x '\\t$0' _NET_ACTIVE_WINDOW | cut -f 2)\"); /* contribution: Paul Rouget */\n\t\t\t\t}\n\n\t\t\t\tstrcat( lDialogString , \" --\" ) ;\n\t\t\t\tif ( aDialogType && ( ! strcmp( \"okcancel\" , aDialogType )\n\t\t\t\t\t\t|| ! strcmp( \"yesno\" , aDialogType ) || ! strcmp( \"yesnocancel\" , aDialogType ) ) )\n\t\t\t\t{\n\t\t\t\t\t\tif ( aIconType && ( ! strcmp( \"warning\" , aIconType )\n\t\t\t\t\t\t\t\t|| ! strcmp( \"error\" , aIconType ) ) )\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tstrcat( lDialogString , \"warning\" ) ;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif ( ! strcmp( \"yesnocancel\" , aDialogType ) )\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tstrcat( lDialogString , \"yesnocancel\" ) ;\n\t\t\t\t\t\t}\n\t\t\t\t\t\telse\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tstrcat( lDialogString , \"yesno\" ) ;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\telse if ( aIconType && ! strcmp( \"error\" , aIconType ) )\n\t\t\t\t{\n\t\t\t\t\t\tstrcat( lDialogString , \"error\" ) ;\n\t\t\t\t}\n\t\t\t\telse if ( aIconType && ! strcmp( \"warning\" , aIconType ) )\n\t\t\t\t{\n\t\t\t\t\t\tstrcat( lDialogString , \"sorry\" ) ;\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\t\tstrcat( lDialogString , \"msgbox\" ) ;\n\t\t\t\t}\n\t\t\t\tstrcat( lDialogString , \" \\\"\" ) ;\n\t\t\t\tif ( aMessage )\n\t\t\t\t{\n\t\t\t\t\t\tstrcat( lDialogString , aMessage ) ;\n\t\t\t\t}\n\t\t\t\tstrcat( lDialogString , \"\\\"\" ) ;\n\t\t\t\tif ( aDialogType && ! strcmp( \"okcancel\" , aDialogType ) )\n\t\t\t\t{\n\t\t\t\t\t\tstrcat( lDialogString ,\n\t\t\t\t\t\t\t\t\" --yes-label Ok --no-label Cancel\" ) ;\n\t\t\t\t}\n\t\t\t\tif ( aTitle && strlen(aTitle) )\n\t\t\t\t{\n\t\t\t\t\t\tstrcat(lDialogString, \" --title \\\"\") ;\n\t\t\t\t\t\tstrcat(lDialogString, aTitle) ;\n\t\t\t\t\t\tstrcat(lDialogString, \"\\\"\") ;\n\t\t\t\t}\n\n\t\t\t\tif ( ! strcmp( \"yesnocancel\" , aDialogType ) )\n\t\t\t\t{\n\t\t\t\t\t\tstrcat( lDialogString , \"; x=$? ;if [ $x = 0 ] ;then echo 1;elif [ $x = 1 ] ;then echo 2;else echo 0;fi\");\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\t\tstrcat( lDialogString , \";if [ $? = 0 ];then echo 1;else echo 0;fi\");\n\t\t\t\t}\n\t\t}\n\t\telse if ( tfd_zenityPresent() || tfd_matedialogPresent() || tfd_shellementaryPresent() || tfd_qarmaPresent() )\n\t\t{\n\t\t\t\tif ( tfd_zenityPresent() )\n\t\t\t\t{\n\t\t\t\t\t\tif (aTitle&&!strcmp(aTitle,\"tinyfd_query\")){strcpy(tinyfd_response,\"zenity\");return 1;}\n\t\t\t\t\t\tstrcpy( lDialogString , \"szAnswer=$(zenity\" ) ;\n\t\t\t\t\t\t\t\t\t\t\t\tif ( (tfd_zenity3Present() >= 4) && !getenv(\"SSH_TTY\") && tfd_xpropPresent() )\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tstrcat(lDialogString, \" --attach=$(sleep .01;xprop -root 32x '\\t$0' _NET_ACTIVE_WINDOW | cut -f 2)\"); /* contribution: Paul Rouget */\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\telse if ( tfd_matedialogPresent() )\n\t\t\t\t{\n\t\t\t\t\t\tif (aTitle&&!strcmp(aTitle,\"tinyfd_query\")){strcpy(tinyfd_response,\"matedialog\");return 1;}\n\t\t\t\t\t\tstrcpy( lDialogString , \"szAnswer=$(matedialog\" ) ;\n\t\t\t\t}\n\t\t\t\telse if ( tfd_shellementaryPresent() )\n\t\t\t\t{\n\t\t\t\t\t\tif (aTitle&&!strcmp(aTitle,\"tinyfd_query\")){strcpy(tinyfd_response,\"shellementary\");return 1;}\n\t\t\t\t\t\tstrcpy( lDialogString , \"szAnswer=$(shellementary\" ) ;\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\t\tif (aTitle&&!strcmp(aTitle,\"tinyfd_query\")){strcpy(tinyfd_response,\"qarma\");return 1;}\n\t\t\t\t\t\tstrcpy( lDialogString , \"szAnswer=$(qarma\" ) ;\n\t\t\t\t\t\t\t\t\t\t\t\tif ( !getenv(\"SSH_TTY\") && tfd_xpropPresent() )\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tstrcat(lDialogString, \" --attach=$(xprop -root 32x '\\t$0' _NET_ACTIVE_WINDOW | cut -f 2)\"); /* contribution: Paul Rouget */\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tstrcat(lDialogString, \" --\");\n\n\t\t\t\tif ( aDialogType && ! strcmp( \"okcancel\" , aDialogType ) )\n\t\t\t\t{\n\t\t\t\t\t\t\t\tstrcat( lDialogString ,\n\t\t\t\t\t\t\t\t\t\t\t\t\"question --ok-label=Ok --cancel-label=Cancel\" ) ;\n\t\t\t\t}\n\t\t\t\telse if ( aDialogType && ! strcmp( \"yesno\" , aDialogType ) )\n\t\t\t\t{\n\t\t\t\t\t\t\t\tstrcat( lDialogString , \"question\" ) ;\n\t\t\t\t}\n\t\t\t\telse if ( aDialogType && ! strcmp( \"yesnocancel\" , aDialogType ) )\n\t\t\t\t{\n\t\t\t\t\t\tstrcat( lDialogString , \"list --column \\\"\\\" --hide-header \\\"Yes\\\" \\\"No\\\"\" ) ;\n\t\t\t\t}\n\t\t\t\telse if ( aIconType && ! strcmp( \"error\" , aIconType ) )\n\t\t\t\t{\n\t\t\t\t\tstrcat( lDialogString , \"error\" ) ;\n\t\t\t\t}\n\t\t\t\telse if ( aIconType && ! strcmp( \"warning\" , aIconType ) )\n\t\t\t\t{\n\t\t\t\t\tstrcat( lDialogString , \"warning\" ) ;\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\tstrcat( lDialogString , \"info\" ) ;\n\t\t\t\t}\n\n\t\t\t\tstrcat(lDialogString, \" --title=\\\"\");\n\t\t\t\tif ( aTitle && strlen(aTitle) ) strcat(lDialogString, aTitle) ;\n\t\t\t\tstrcat(lDialogString, \"\\\"\");\n\n\t\t\t\tif (strcmp(\"yesnocancel\", aDialogType)) strcat(lDialogString, \" --no-wrap\");\n\n\t\t\t\tstrcat(lDialogString, \" --text=\\\"\") ;\n\t\t\t\tif (aMessage && strlen(aMessage)) strcat(lDialogString, aMessage) ;\n\t\t\t\tstrcat(lDialogString, \"\\\"\") ;\n\n\t\t\t\tif ( (tfd_zenity3Present() >= 3) || (!tfd_zenityPresent() && (tfd_shellementaryPresent() || tfd_qarmaPresent()) ) )\n\t\t\t\t{\n\t\t\t\t\t\tstrcat( lDialogString , \" --icon-name=dialog-\" ) ;\n\t\t\t\t\t\tif ( aIconType && (! strcmp( \"question\" , aIconType )\n\t\t\t\t\t\t  || ! strcmp( \"error\" , aIconType )\n\t\t\t\t\t\t  || ! strcmp( \"warning\" , aIconType ) ) )\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tstrcat( lDialogString , aIconType ) ;\n\t\t\t\t\t\t}\n\t\t\t\t\t\telse\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tstrcat( lDialogString , \"information\" ) ;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif (tinyfd_silent) strcat( lDialogString , \" 2>/dev/null \");\n\n\t\t\t\tif ( ! strcmp( \"yesnocancel\" , aDialogType ) )\n\t\t\t\t{\n\t\t\t\t\t\tstrcat( lDialogString ,\n\");if [ $? = 1 ];then echo 0;elif [ $szAnswer = \\\"No\\\" ];then echo 2;else echo 1;fi\");\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\t\tstrcat( lDialogString , \");if [ $? = 0 ];then echo 1;else echo 0;fi\");\n\t\t\t\t}\n\t  }\n\n\t  else if (tfd_yadPresent())\n\t  {\n\t\t if (aTitle && !strcmp(aTitle, \"tinyfd_query\")) { strcpy(tinyfd_response, \"yad\"); return 1; }\n\t\t strcpy(lDialogString, \"szAnswer=$(yad --\");\n\t\t if (aDialogType && !strcmp(\"ok\", aDialogType))\n\t\t {\n\t\t\tstrcat(lDialogString,\"button=Ok:1\");\n\t\t }\n\t\t else if (aDialogType && !strcmp(\"okcancel\", aDialogType))\n\t\t {\n\t\t\tstrcat(lDialogString,\"button=Ok:1 --button=Cancel:0\");\n\t\t }\n\t\t else if (aDialogType && !strcmp(\"yesno\", aDialogType))\n\t\t {\n\t\t\tstrcat(lDialogString, \"button=Yes:1 --button=No:0\");\n\t\t }\n\t\t else if (aDialogType && !strcmp(\"yesnocancel\", aDialogType))\n\t\t {\n\t\t\tstrcat(lDialogString, \"button=Yes:1 --button=No:2 --button=Cancel:0\");\n\t\t }\n\t\t else if (aIconType && !strcmp(\"error\", aIconType))\n\t\t {\n\t\t\tstrcat(lDialogString, \"error\");\n\t\t }\n\t\t else if (aIconType && !strcmp(\"warning\", aIconType))\n\t\t {\n\t\t\tstrcat(lDialogString, \"warning\");\n\t\t }\n\t\t else\n\t\t {\n\t\t\tstrcat(lDialogString, \"info\");\n\t\t }\n\t\t if (aTitle && strlen(aTitle))\n\t\t {\n\t\t\tstrcat(lDialogString, \" --title=\\\"\");\n\t\t\tstrcat(lDialogString, aTitle);\n\t\t\tstrcat(lDialogString, \"\\\"\");\n\t\t }\n\t\t if (aMessage && strlen(aMessage))\n\t\t {\n\t\t\tstrcat(lDialogString, \" --text=\\\"\");\n\t\t\tstrcat(lDialogString, aMessage);\n\t\t\tstrcat(lDialogString, \"\\\"\");\n\t\t }\n\n\t\t strcat(lDialogString, \" --image=dialog-\");\n\t\t if (aIconType && (!strcmp(\"question\", aIconType)\n\t\t\t|| !strcmp(\"error\", aIconType)\n\t\t\t|| !strcmp(\"warning\", aIconType)))\n\t\t {\n\t\t\tstrcat(lDialogString, aIconType);\n\t\t }\n\t\t else\n\t\t {\n\t\t\tstrcat(lDialogString, \"information\");\n\t\t }\n\n\t\t if (tinyfd_silent) strcat(lDialogString, \" 2>/dev/null \");\n\t\t strcat(lDialogString,\");echo $?\");\n\t  }\n\n\t  else if ( !gxmessagePresent() && !gmessagePresent() && !gdialogPresent() && !xdialogPresent() && tkinter3Present() )\n\t\t\t\t{\n\t\t\t\t\t\tif (aTitle&&!strcmp(aTitle,\"tinyfd_query\")){strcpy(tinyfd_response,\"python3-tkinter\");return 1;}\n\n\t\t\t\t\t\tstrcpy( lDialogString , gPython3Name ) ;\n\t\t\t\t\t\tstrcat( lDialogString ,\n\t\t\t\t\t\t\t\t\" -S -c \\\"import tkinter;from tkinter import messagebox;root=tkinter.Tk();root.withdraw();\");\n\n\t\t\t\t\t\tstrcat( lDialogString ,\"res=messagebox.\" ) ;\n\t\t\t\t\t\tif ( aDialogType && ! strcmp( \"okcancel\" , aDialogType ) )\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tstrcat( lDialogString , \"askokcancel(\" ) ;\n\t\t\t\t\t\t\t\tif ( aDefaultButton )\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tstrcat( lDialogString , \"default=messagebox.OK,\" ) ;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\telse\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tstrcat( lDialogString , \"default=messagebox.CANCEL,\" ) ;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\telse if ( aDialogType && ! strcmp( \"yesno\" , aDialogType ) )\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tstrcat( lDialogString , \"askyesno(\" ) ;\n\t\t\t\t\t\t\t\tif ( aDefaultButton )\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tstrcat( lDialogString , \"default=messagebox.YES,\" ) ;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\telse\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tstrcat( lDialogString , \"default=messagebox.NO,\" ) ;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\telse if ( aDialogType && ! strcmp( \"yesnocancel\" , aDialogType ) )\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tstrcat( lDialogString , \"askyesnocancel(\" ) ;\n\t\t\t\t\t\t\t\tswitch ( aDefaultButton )\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tcase 1: strcat( lDialogString , \"default=messagebox.YES,\" ); break;\n\t\t\t\t\t\t\t\tcase 2: strcat( lDialogString , \"default=messagebox.NO,\" ); break;\n\t\t\t\t\t\t\t\tcase 0: strcat( lDialogString , \"default=messagebox.CANCEL,\" ); break;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\telse\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tstrcat( lDialogString , \"showinfo(\" ) ;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tstrcat( lDialogString , \"icon='\" ) ;\n\t\t\t\t\t\tif ( aIconType && (! strcmp( \"question\" , aIconType )\n\t\t\t\t\t\t\t\t|| ! strcmp( \"error\" , aIconType )\n\t\t\t\t\t\t\t\t|| ! strcmp( \"warning\" , aIconType ) ) )\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tstrcat( lDialogString , aIconType ) ;\n\t\t\t\t\t\t}\n\t\t\t\t\t\telse\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tstrcat( lDialogString , \"info\" ) ;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tstrcat(lDialogString, \"',\") ;\n\t\t\t\t\t\tif ( aTitle && strlen(aTitle) )\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tstrcat(lDialogString, \"title='\") ;\n\t\t\t\t\t\t\t\tstrcat(lDialogString, aTitle) ;\n\t\t\t\t\t\t\t\tstrcat(lDialogString, \"',\") ;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif ( aMessage && strlen(aMessage) )\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tstrcat(lDialogString, \"message='\") ;\n\t\t\t\t\t\t\t\tlpDialogString = lDialogString + strlen(lDialogString);\n\t\t\t\t\t\t\t\ttfd_replaceSubStr( aMessage , \"\\n\" , \"\\\\n\" , lpDialogString ) ;\n\t\t\t\t\t\t\t\tstrcat(lDialogString, \"'\") ;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif ( aDialogType && ! strcmp( \"yesnocancel\" , aDialogType ) )\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tstrcat(lDialogString, \");\\n\\\nif res is None :\\n\\tprint(0)\\n\\\nelif res is False :\\n\\tprint(2)\\n\\\nelse :\\n\\tprint (1)\\n\\\"\" ) ;\n\t\t\t\t\t\t}\n\t\t\t\t\t\telse\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tstrcat(lDialogString, \");\\n\\\nif res is False :\\n\\tprint(0)\\n\\\nelse :\\n\\tprint(1)\\n\\\"\" ) ;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\telse if ( !gxmessagePresent() && !gmessagePresent() && !gdialogPresent() && !xdialogPresent() && tkinter2Present() )\n\t\t{\n\t\t\t\tif (aTitle&&!strcmp(aTitle,\"tinyfd_query\")){strcpy(tinyfd_response,\"python2-tkinter\");return 1;}\n\t\t\t\t\t\t\t\tstrcpy( lDialogString , \"export PYTHONIOENCODING=utf-8;\" ) ;\n\t\t\t\t\t\t\t\tstrcat( lDialogString , gPython2Name ) ;\n\t\t\t\t\t\t\t\tif ( ! isTerminalRunning( ) && tfd_isDarwin( ) )\n\t\t\t\t{\n\t\t\t\t\t\tstrcat( lDialogString , \" -i\" ) ;  /* for osx without console */\n\t\t\t\t}\n\n\t\t\t\tstrcat( lDialogString ,\n\" -S -c \\\"import Tkinter,tkMessageBox;root=Tkinter.Tk();root.withdraw();\");\n\n\t\t\t\tif ( tfd_isDarwin( ) )\n\t\t\t\t{\n\t\t\t\t\t\tstrcat( lDialogString ,\n\"import os;os.system('''/usr/bin/osascript -e 'tell app \\\\\\\"Finder\\\\\\\" to set \\\nfrontmost of process \\\\\\\"Python\\\\\\\" to true' ''');\");\n\t\t\t\t}\n\n\t\t\t\tstrcat( lDialogString ,\"res=tkMessageBox.\" ) ;\n\t\t\t\tif ( aDialogType && ! strcmp( \"okcancel\" , aDialogType ) )\n\t\t\t\t{\n\t\t\t\t  strcat( lDialogString , \"askokcancel(\" ) ;\n\t\t\t\t  if ( aDefaultButton )\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tstrcat( lDialogString , \"default=tkMessageBox.OK,\" ) ;\n\t\t\t\t\t\t}\n\t\t\t\t\t\telse\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tstrcat( lDialogString , \"default=tkMessageBox.CANCEL,\" ) ;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\telse if ( aDialogType && ! strcmp( \"yesno\" , aDialogType ) )\n\t\t\t\t{\n\t\t\t\t\t\tstrcat( lDialogString , \"askyesno(\" ) ;\n\t\t\t\t\t\tif ( aDefaultButton )\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tstrcat( lDialogString , \"default=tkMessageBox.YES,\" ) ;\n\t\t\t\t\t\t}\n\t\t\t\t\t\telse\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tstrcat( lDialogString , \"default=tkMessageBox.NO,\" ) ;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\telse if ( aDialogType && ! strcmp( \"yesnocancel\" , aDialogType ) )\n\t\t\t\t{\n\t\t\t\t\t\tstrcat( lDialogString , \"askyesnocancel(\" ) ;\n\t\t\t\t\t\tswitch ( aDefaultButton )\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tcase 1: strcat( lDialogString , \"default=tkMessageBox.YES,\" ); break;\n\t\t\t\t\t\t\t\tcase 2: strcat( lDialogString , \"default=tkMessageBox.NO,\" ); break;\n\t\t\t\t\t\t\t\tcase 0: strcat( lDialogString , \"default=tkMessageBox.CANCEL,\" ); break;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\t\t\t\tstrcat( lDialogString , \"showinfo(\" ) ;\n\t\t\t\t}\n\n\t\t\t\tstrcat( lDialogString , \"icon='\" ) ;\n\t\t\t\tif ( aIconType && (! strcmp( \"question\" , aIconType )\n\t\t\t\t  || ! strcmp( \"error\" , aIconType )\n\t\t\t\t  || ! strcmp( \"warning\" , aIconType ) ) )\n\t\t\t\t{\n\t\t\t\t\t\t\t\tstrcat( lDialogString , aIconType ) ;\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\t\t\t\tstrcat( lDialogString , \"info\" ) ;\n\t\t\t\t}\n\n\t\t\t\tstrcat(lDialogString, \"',\") ;\n\t\t\t\tif ( aTitle && strlen(aTitle) )\n\t\t\t\t{\n\t\t\t\t\t\t\t\tstrcat(lDialogString, \"title='\") ;\n\t\t\t\t\t\t\t\tstrcat(lDialogString, aTitle) ;\n\t\t\t\t\t\t\t\tstrcat(lDialogString, \"',\") ;\n\t\t\t\t}\n\t\t\t\tif ( aMessage && strlen(aMessage) )\n\t\t\t\t{\n\t\t\t\t\t\tstrcat(lDialogString, \"message='\") ;\n\t\t\t\t\t\tlpDialogString = lDialogString + strlen(lDialogString);\n\t\t\t\t\t\ttfd_replaceSubStr( aMessage , \"\\n\" , \"\\\\n\" , lpDialogString ) ;\n\t\t\t\t\t\tstrcat(lDialogString, \"'\") ;\n\t\t\t\t}\n\n\t\t\t\tif ( aDialogType && ! strcmp( \"yesnocancel\" , aDialogType ) )\n\t\t\t\t{\n\t\t\t\t\t\tstrcat(lDialogString, \");\\n\\\nif res is None :\\n\\tprint 0\\n\\\nelif res is False :\\n\\tprint 2\\n\\\nelse :\\n\\tprint 1\\n\\\"\" ) ;\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\t\tstrcat(lDialogString, \");\\n\\\nif res is False :\\n\\tprint 0\\n\\\nelse :\\n\\tprint 1\\n\\\"\" ) ;\n\t\t\t\t}\n\t}\n\t\telse if ( gxmessagePresent() || gmessagePresent() || (!gdialogPresent() && !xdialogPresent() && xmessagePresent()) )\n\t\t{\n\t\t\t\tif ( gxmessagePresent() )\n\t\t\t\t{\n\t\t\t\t\t\tif (aTitle&&!strcmp(aTitle,\"tinyfd_query\")){strcpy(tinyfd_response,\"gxmessage\");return 1;}\n\t\t\t\t\t\tstrcpy( lDialogString , \"gxmessage\");\n\t\t\t\t}\n\t\t\t\telse if ( gmessagePresent() )\n\t\t\t\t{\n\t\t\t\t\t\tif (aTitle&&!strcmp(aTitle,\"tinyfd_query\")){strcpy(tinyfd_response,\"gmessage\");return 1;}\n\t\t\t\t\t\tstrcpy( lDialogString , \"gmessage\");\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\t\tif (aTitle&&!strcmp(aTitle,\"tinyfd_query\")){strcpy(tinyfd_response,\"xmessage\");return 1;}\n\t\t\t\t\t\tstrcpy( lDialogString , \"xmessage\");\n\t\t\t\t}\n\n\t\t\t\tif ( aDialogType && ! strcmp(\"okcancel\" , aDialogType) )\n\t\t\t\t{\n\t\t\t\t\t\tstrcat( lDialogString , \" -buttons Ok:1,Cancel:0\");\n\t\t\t\t\t\tswitch ( aDefaultButton )\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tcase 1: strcat( lDialogString , \" -default Ok\"); break;\n\t\t\t\t\t\t\t\tcase 0: strcat( lDialogString , \" -default Cancel\"); break;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\telse if ( aDialogType && ! strcmp(\"yesno\" , aDialogType) )\n\t\t\t\t{\n\t\t\t\t\t\tstrcat( lDialogString , \" -buttons Yes:1,No:0\");\n\t\t\t\t\t\tswitch ( aDefaultButton )\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tcase 1: strcat( lDialogString , \" -default Yes\"); break;\n\t\t\t\t\t\t\t\tcase 0: strcat( lDialogString , \" -default No\"); break;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\telse if ( aDialogType && ! strcmp(\"yesnocancel\" , aDialogType) )\n\t\t\t\t{\n\t\t\t\t\t\tstrcat( lDialogString , \" -buttons Yes:1,No:2,Cancel:0\");\n\t\t\t\t\t\tswitch ( aDefaultButton )\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tcase 1: strcat( lDialogString , \" -default Yes\"); break;\n\t\t\t\t\t\t\t\tcase 2: strcat( lDialogString , \" -default No\"); break;\n\t\t\t\t\t\t\t\tcase 0: strcat( lDialogString , \" -default Cancel\"); break;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\t\tstrcat( lDialogString , \" -buttons Ok:1\");\n\t\t\t\t\t\tstrcat( lDialogString , \" -default Ok\");\n\t\t\t\t}\n\n\t\t\t\tstrcat( lDialogString , \" -center \\\"\");\n\t\t\t\tif ( aMessage && strlen(aMessage) )\n\t\t\t\t{\n\t\t\t\t\t\tstrcat( lDialogString , aMessage ) ;\n\t\t\t\t}\n\t\t\t\tstrcat(lDialogString, \"\\\"\" ) ;\n\t\t\t\tif ( aTitle && strlen(aTitle) )\n\t\t\t\t{\n\t\t\t\t\t\tstrcat( lDialogString , \" -title  \\\"\");\n\t\t\t\t\t\tstrcat( lDialogString , aTitle ) ;\n\t\t\t\t\t\tstrcat( lDialogString, \"\\\"\" ) ;\n\t\t\t\t}\n\t\t\t\tstrcat( lDialogString , \" ; echo $? \");\n\t\t}\n\t\telse if ( xdialogPresent() || gdialogPresent() || dialogName() || whiptailPresent() )\n\t\t{\n\t\t\t\tif ( gdialogPresent( ) )\n\t\t\t\t{\n\t\t\t\t\t\tif (aTitle&&!strcmp(aTitle,\"tinyfd_query\")){strcpy(tinyfd_response,\"gdialog\");return 1;}\n\t\t\t\t\t\tlWasGraphicDialog = 1 ;\n\t\t\t\t\t\tstrcpy( lDialogString , \"(gdialog \" ) ;\n\t\t\t\t}\n\t\t\t\telse if ( xdialogPresent( ) )\n\t\t\t\t{\n\t\t\t\t\t\tif (aTitle&&!strcmp(aTitle,\"tinyfd_query\")){strcpy(tinyfd_response,\"xdialog\");return 1;}\n\t\t\t\t\t\tlWasGraphicDialog = 1 ;\n\t\t\t\t\t\tstrcpy( lDialogString , \"(Xdialog \" ) ;\n\t\t\t\t}\n\t\t\t\telse if ( dialogName( ) )\n\t\t\t\t{\n\t\t\t\t\t\tif (aTitle&&!strcmp(aTitle,\"tinyfd_query\")){strcpy(tinyfd_response,\"dialog\");return 0;}\n\t\t\t\t\t\tif ( isTerminalRunning( ) )\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tstrcpy( lDialogString , \"(dialog \" ) ;\n\t\t\t\t\t\t}\n\t\t\t\t\t\telse\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tlWasXterm = 1 ;\n\t\t\t\t\t\t\t\tstrcpy( lDialogString , terminalName() ) ;\n\t\t\t\t\t\t\t\tstrcat( lDialogString , \"'(\" ) ;\n\t\t\t\t\t\t\t\tstrcat( lDialogString , dialogName() ) ;\n\t\t\t\t\t\t\t\tstrcat( lDialogString , \" \" ) ;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\telse if ( isTerminalRunning( ) )\n\t\t\t\t{\n\t\t\t\t\t\tif (aTitle&&!strcmp(aTitle,\"tinyfd_query\")){strcpy(tinyfd_response,\"whiptail\");return 0;}\n\t\t\t\t\t\tstrcpy( lDialogString , \"(whiptail \" ) ;\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\t\tif (aTitle&&!strcmp(aTitle,\"tinyfd_query\")){strcpy(tinyfd_response,\"whiptail\");return 0;}\n\t\t\t\t\t\tlWasXterm = 1 ;\n\t\t\t\t\t\tstrcpy( lDialogString , terminalName() ) ;\n\t\t\t\t\t\tstrcat( lDialogString , \"'(whiptail \" ) ;\n\t\t\t\t}\n\n\t\t\t\tif ( aTitle && strlen(aTitle) )\n\t\t\t\t{\n\t\t\t\t\t\tstrcat(lDialogString, \"--title \\\"\") ;\n\t\t\t\t\t\tstrcat(lDialogString, aTitle) ;\n\t\t\t\t\t\tstrcat(lDialogString, \"\\\" \") ;\n\t\t\t\t}\n\n\t\t\t\tif ( !xdialogPresent() && !gdialogPresent() )\n\t\t\t\t{\n\t\t\t\t\t\tif ( aDialogType && ( !strcmp( \"okcancel\" , aDialogType ) || !strcmp( \"yesno\" , aDialogType )\n\t\t\t\t\t\t\t\t|| !strcmp( \"yesnocancel\" , aDialogType ) ) )\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tstrcat(lDialogString, \"--backtitle \\\"\") ;\n\t\t\t\t\t\t\t\tstrcat(lDialogString, \"tab: move focus\") ;\n\t\t\t\t\t\t\t\tstrcat(lDialogString, \"\\\" \") ;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif ( aDialogType && ! strcmp( \"okcancel\" , aDialogType ) )\n\t\t\t\t{\n\t\t\t\t\t\tif ( ! aDefaultButton )\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tstrcat( lDialogString , \"--defaultno \" ) ;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tstrcat( lDialogString ,\n\t\t\t\t\t\t\t\t\t\t\"--yes-label \\\"Ok\\\" --no-label \\\"Cancel\\\" --yesno \" ) ;\n\t\t\t\t}\n\t\t\t\telse if ( aDialogType && ! strcmp( \"yesno\" , aDialogType ) )\n\t\t\t\t{\n\t\t\t\t\t\tif ( ! aDefaultButton )\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tstrcat( lDialogString , \"--defaultno \" ) ;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tstrcat( lDialogString , \"--yesno \" ) ;\n\t\t\t\t}\n\t\t\t\telse if (aDialogType && !strcmp(\"yesnocancel\", aDialogType))\n\t\t\t\t{\n\t\t\t\t\t\tif (!aDefaultButton)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tstrcat(lDialogString, \"--defaultno \");\n\t\t\t\t\t\t}\n\t\t\t\t\t\tstrcat(lDialogString, \"--menu \");\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\t\tstrcat( lDialogString , \"--msgbox \" ) ;\n\n\t\t\t\t}\n\t\t\t\tstrcat( lDialogString , \"\\\"\" ) ;\n\t\t\t\tif ( aMessage && strlen(aMessage) )\n\t\t\t\t{\n\t\t\t\t\t\tstrcat(lDialogString, aMessage) ;\n\t\t\t\t}\n\t\t\t\tstrcat(lDialogString, \"\\\" \");\n\n\t\t\t\tif ( lWasGraphicDialog )\n\t\t\t\t{\n\t\t\t\t\t\tif (aDialogType && !strcmp(\"yesnocancel\", aDialogType))\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tstrcat(lDialogString,\"0 60 0 Yes \\\"\\\" No \\\"\\\") 2>/tmp/tinyfd.txt;\\\nif [ $? = 0 ];then tinyfdBool=1;else tinyfdBool=0;fi;\\\ntinyfdRes=$(cat /tmp/tinyfd.txt);echo $tinyfdBool$tinyfdRes\") ;\n\t\t\t\t\t\t}\n\t\t\t\t\t\telse\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tstrcat(lDialogString,\n\t\t\t\t\t\t\t\t   \"10 60 ) 2>&1;if [ $? = 0 ];then echo 1;else echo 0;fi\");\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\t\tif (aDialogType && !strcmp(\"yesnocancel\", aDialogType))\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tstrcat(lDialogString,\"0 60 0 Yes \\\"\\\" No \\\"\\\" >/dev/tty ) 2>/tmp/tinyfd.txt;\\\n\t\t\t\tif [ $? = 0 ];then tinyfdBool=1;else tinyfdBool=0;fi;\\\n\t\t\t\ttinyfdRes=$(cat /tmp/tinyfd.txt);echo $tinyfdBool$tinyfdRes\") ;\n\n\t\t\t\t\t\t\t\tif ( lWasXterm )\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tstrcat(lDialogString,\" >/tmp/tinyfd0.txt';cat /tmp/tinyfd0.txt\");\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\telse\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tstrcat(lDialogString, \"; clear >/dev/tty\") ;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\telse\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tstrcat(lDialogString, \"10 60 >/dev/tty) 2>&1;if [ $? = 0 ];\");\n\t\t\t\t\t\t\t\tif ( lWasXterm )\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tstrcat( lDialogString ,\n\"then\\n\\techo 1\\nelse\\n\\techo 0\\nfi >/tmp/tinyfd.txt';cat /tmp/tinyfd.txt;rm /tmp/tinyfd.txt\");\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\telse\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t   strcat(lDialogString,\n\t\t\t\t\t\t\t\t\t\t\t\t  \"then echo 1;else echo 0;fi;clear >/dev/tty\");\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\telse if (  !isTerminalRunning() && terminalName() )\n\t\t{\n\t\t\t\tif (aTitle&&!strcmp(aTitle,\"tinyfd_query\")){strcpy(tinyfd_response,\"basicinput\");return 0;}\n\t\t\t\tstrcpy( lDialogString , terminalName() ) ;\n\t\t\t\tstrcat( lDialogString , \"'\" ) ;\n\t\t\t\tif ( !gWarningDisplayed && !tinyfd_forceConsole)\n\t\t\t\t{\n\t\t\t\t\t\tgWarningDisplayed = 1 ;\n\t\t\t\t\t\tstrcat( lDialogString , \"echo \\\"\" ) ;\n\t\t\t\t\t\tstrcat( lDialogString, gTitle) ;\n\t\t\t\t\t\tstrcat( lDialogString , \"\\\";\" ) ;\n\t\t\t\t\t\tstrcat( lDialogString , \"echo \\\"\" ) ;\n\t\t\t\t\t\tstrcat( lDialogString, tinyfd_needs) ;\n\t\t\t\t\t\tstrcat( lDialogString , \"\\\";echo;echo;\" ) ;\n\t\t\t\t}\n\t\t\t\tif ( aTitle && strlen(aTitle) )\n\t\t\t\t{\n\t\t\t\t\t\tstrcat( lDialogString , \"echo \\\"\" ) ;\n\t\t\t\t\t\tstrcat( lDialogString, aTitle) ;\n\t\t\t\t\t\tstrcat( lDialogString , \"\\\";echo;\" ) ;\n\t\t\t\t}\n\t\t\t\tif ( aMessage && strlen(aMessage) )\n\t\t\t\t{\n\t\t\t\t\t\tstrcat( lDialogString , \"echo \\\"\" ) ;\n\t\t\t\t\t\tstrcat( lDialogString, aMessage) ;\n\t\t\t\t\t\tstrcat( lDialogString , \"\\\"; \" ) ;\n\t\t\t\t}\n\t\t\t\tif ( aDialogType && !strcmp(\"yesno\",aDialogType) )\n\t\t\t\t{\n\t\t\t\t\t\tstrcat( lDialogString , \"echo -n \\\"y/n: \\\"; \" ) ;\n\t\t\t\t\t\tstrcat( lDialogString , \"stty sane -echo;\" ) ;\n\t\t\t\t\t\tstrcat( lDialogString ,\n\t\t\t\t\t\t\t\t\"answer=$( while ! head -c 1 | grep -i [ny];do true ;done);\");\n\t\t\t\t\t\tstrcat( lDialogString ,\n\t\t\t\t\t\t\t\t\"if echo \\\"$answer\\\" | grep -iq \\\"^y\\\";then\\n\");\n\t\t\t\t\t\tstrcat( lDialogString , \"\\techo 1\\nelse\\n\\techo 0\\nfi\" ) ;\n\t\t\t\t}\n\t\t\t\telse if ( aDialogType && !strcmp(\"okcancel\",aDialogType) )\n\t\t\t\t{\n\t\t\t\t\t\tstrcat( lDialogString , \"echo -n \\\"[O]kay/[C]ancel: \\\"; \" ) ;\n\t\t\t\t\t\tstrcat( lDialogString , \"stty sane -echo;\" ) ;\n\t\t\t\t\t\tstrcat( lDialogString ,\n\t\t\t\t\t\t\t\t\"answer=$( while ! head -c 1 | grep -i [oc];do true ;done);\");\n\t\t\t\t\t\tstrcat( lDialogString ,\n\t\t\t\t\t\t\t\t\"if echo \\\"$answer\\\" | grep -iq \\\"^o\\\";then\\n\");\n\t\t\t\t\t\tstrcat( lDialogString , \"\\techo 1\\nelse\\n\\techo 0\\nfi\" ) ;\n\t\t\t\t}\n\t\t\t\telse if ( aDialogType && !strcmp(\"yesnocancel\",aDialogType) )\n\t\t\t\t{\n\t\t\t\t\t\tstrcat( lDialogString , \"echo -n \\\"[Y]es/[N]o/[C]ancel: \\\"; \" ) ;\n\t\t\t\t\t\tstrcat( lDialogString , \"stty sane -echo;\" ) ;\n\t\t\t\t\t\tstrcat( lDialogString ,\n\t\t\t\t\t\t\t\t\"answer=$( while ! head -c 1 | grep -i [nyc];do true ;done);\");\n\t\t\t\t\t\tstrcat( lDialogString ,\n\t\t\t\t\t\t\t\t\"if echo \\\"$answer\\\" | grep -iq \\\"^y\\\";then\\n\\techo 1\\n\");\n\t\t\t\t\t\tstrcat( lDialogString , \"elif echo \\\"$answer\\\" | grep -iq \\\"^n\\\";then\\n\\techo 2\\n\" ) ;\n\t\t\t\t\t\tstrcat( lDialogString , \"else\\n\\techo 0\\nfi\" ) ;\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\t\tstrcat(lDialogString , \"echo -n \\\"press enter to continue \\\"; \");\n\t\t\t\t\t\tstrcat( lDialogString , \"stty sane -echo;\" ) ;\n\t\t\t\t\t\tstrcat( lDialogString ,\n\t\t\t\t\t\t\t\t\"answer=$( while ! head -c 1;do true ;done);echo 1\");\n\t\t\t\t}\n\t\t\t\tstrcat( lDialogString ,\n\t\t\t\t\t\t\" >/tmp/tinyfd.txt';cat /tmp/tinyfd.txt;rm /tmp/tinyfd.txt\");\n\t\t}\n\t\telse if ( !isTerminalRunning() && pythonDbusPresent() && !strcmp(\"ok\" , aDialogType) )\n\t\t{\n\t\t\t\tif (aTitle&&!strcmp(aTitle,\"tinyfd_query\")){strcpy(tinyfd_response,\"python-dbus\");return 1;}\n\t\t\t\tstrcpy( lDialogString , gPythonName ) ;\n\t\t\t\tstrcat( lDialogString ,\" -c \\\"import dbus;bus=dbus.SessionBus();\");\n\t\t\t\tstrcat( lDialogString ,\"notif=bus.get_object('org.freedesktop.Notifications','/org/freedesktop/Notifications');\" ) ;\n\t\t\t\tstrcat( lDialogString ,\"notify=dbus.Interface(notif,'org.freedesktop.Notifications');\" ) ;\n\t\t\t\tstrcat( lDialogString ,\"notify.Notify('',0,'\" ) ;\n\t\t\t\tif ( aIconType && strlen(aIconType) )\n\t\t\t\t{\n\t\t\t\t\t\tstrcat( lDialogString , aIconType ) ;\n\t\t\t\t}\n\t\t\t\tstrcat(lDialogString, \"','\") ;\n\t\t\t\tif ( aTitle && strlen(aTitle) )\n\t\t\t\t{\n\t\t\t\t\t\tstrcat(lDialogString, aTitle) ;\n\t\t\t\t}\n\t\t\t\tstrcat(lDialogString, \"','\") ;\n\t\t\t\tif ( aMessage && strlen(aMessage) )\n\t\t\t\t{\n\t\t\t\t\t\tlpDialogString = lDialogString + strlen(lDialogString);\n\t\t\t\t\t\ttfd_replaceSubStr( aMessage , \"\\n\" , \"\\\\n\" , lpDialogString ) ;\n\t\t\t\t}\n\t\t\t\tstrcat(lDialogString, \"','','',5000)\\\"\") ;\n\t\t}\n\t\telse if ( !isTerminalRunning() && (perlPresent() >= 2)  && !strcmp(\"ok\" , aDialogType) )\n\t\t{\n\t\t\t\tif (aTitle&&!strcmp(aTitle,\"tinyfd_query\")){strcpy(tinyfd_response,\"perl-dbus\");return 1;}\n\n\t\t\t\t\t\t\t\tstrcpy( lDialogString ,  \"perl -e \\\"use Net::DBus;\\\nmy \\\\$sessionBus = Net::DBus->session;\\\nmy \\\\$notificationsService = \\\\$sessionBus->get_service('org.freedesktop.Notifications');\\\nmy \\\\$notificationsObject = \\\\$notificationsService->get_object('/org/freedesktop/Notifications',\\\n'org.freedesktop.Notifications');\");\n\n\t\t\t\t\t\t\t\tsprintf( lDialogString + strlen(lDialogString),\n\"my \\\\$notificationId;\\\\$notificationId = \\\\$notificationsObject->Notify(shift, 0, '%s', '%s', '%s', [], {}, -1);\\\" \",\n\t\t\t\t\t\t\t\t\t\t\t\t\t\taIconType?aIconType:\"\", aTitle?aTitle:\"\", aMessage?aMessage:\"\" ) ;\n\t\t}\n\t\telse if ( !isTerminalRunning() && notifysendPresent() && !strcmp(\"ok\" , aDialogType) )\n\t\t{\n\n\t\t\t\tif (aTitle&&!strcmp(aTitle,\"tinyfd_query\")){strcpy(tinyfd_response,\"notifysend\");return 1;}\n\t\t\t\tstrcpy( lDialogString , \"notify-send\" ) ;\n\t\t\t\tif ( aIconType && strlen(aIconType) )\n\t\t\t\t{\n\t\t\t\t\t\tstrcat( lDialogString , \" -i '\" ) ;\n\t\t\t\t\t\tstrcat( lDialogString , aIconType ) ;\n\t\t\t\t\t\tstrcat( lDialogString , \"'\" ) ;\n\t\t\t\t}\n\t\tstrcat( lDialogString , \" \\\"\" ) ;\n\t\t\t\tif ( aTitle && strlen(aTitle) )\n\t\t\t\t{\n\t\t\t\t\t\tstrcat(lDialogString, aTitle) ;\n\t\t\t\t\t\tstrcat( lDialogString , \" | \" ) ;\n\t\t\t\t}\n\t\t\t\tif ( aMessage && strlen(aMessage) )\n\t\t\t\t{\n\t\t\ttfd_replaceSubStr( aMessage , \"\\n\\t\" , \" |  \" , lBuff ) ;\n\t\t\ttfd_replaceSubStr( aMessage , \"\\n\" , \" | \" , lBuff ) ;\n\t\t\ttfd_replaceSubStr( aMessage , \"\\t\" , \"  \" , lBuff ) ;\n\t\t\t\t\t\tstrcat(lDialogString, lBuff) ;\n\t\t\t\t}\n\t\t\t\tstrcat( lDialogString , \"\\\"\" ) ;\n\t\t}\n\t\telse\n\t\t{\n\t\t\t\tif (aTitle&&!strcmp(aTitle,\"tinyfd_query\")){strcpy(tinyfd_response,\"basicinput\");return 0;}\n\t\t\t\tif ( !gWarningDisplayed && !tinyfd_forceConsole)\n\t\t\t\t{\n\t\t\t\t\t\tgWarningDisplayed = 1 ;\n\t\t\t\t\t\tprintf(\"\\n\\n%s\\n\", gTitle);\n\t\t\t\t\t\tprintf(\"%s\\n\\n\", tinyfd_needs);\n\t\t\t\t}\n\t\t\t\tif ( aTitle && strlen(aTitle) )\n\t\t\t\t{\n\t\t\t\t\t\tprintf(\"\\n%s\\n\", aTitle);\n\t\t\t\t}\n\n\t\t\t\ttcgetattr(0, &infoOri);\n\t\t\t\ttcgetattr(0, &info);\n\t\t\t\tinfo.c_lflag &= ~ICANON;\n\t\t\t\tinfo.c_cc[VMIN] = 1;\n\t\t\t\tinfo.c_cc[VTIME] = 0;\n\t\t\t\ttcsetattr(0, TCSANOW, &info);\n\t\t\t\tif ( aDialogType && !strcmp(\"yesno\",aDialogType) )\n\t\t\t\t{\n\t\t\t\t\t\tdo\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tif ( aMessage && strlen(aMessage) )\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tprintf(\"\\n%s\\n\",aMessage);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tprintf(\"y/n: \"); fflush(stdout);\n\t\t\t\t\t\t\t\tlChar = (char) tolower( getchar() ) ;\n\t\t\t\t\t\t\t\tprintf(\"\\n\\n\");\n\t\t\t\t\t\t}\n\t\t\t\t\t\twhile ( lChar != 'y' && lChar != 'n' );\n\t\t\t\t\t\tlResult = lChar == 'y' ? 1 : 0 ;\n\t\t\t\t}\n\t\t\t\telse if ( aDialogType && !strcmp(\"okcancel\",aDialogType) )\n\t\t\t\t{\n\t\t\t\t\t\tdo\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tif ( aMessage && strlen(aMessage) )\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tprintf(\"\\n%s\\n\",aMessage);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tprintf(\"[O]kay/[C]ancel: \"); fflush(stdout);\n\t\t\t\t\t\t\t\tlChar = (char) tolower( getchar() ) ;\n\t\t\t\t\t\t\t\tprintf(\"\\n\\n\");\n\t\t\t\t\t\t}\n\t\t\t\t\t\twhile ( lChar != 'o' && lChar != 'c' );\n\t\t\t\t\t\tlResult = lChar == 'o' ? 1 : 0 ;\n\t\t\t\t}\n\t\t\t\telse if ( aDialogType && !strcmp(\"yesnocancel\",aDialogType) )\n\t\t\t\t{\n\t\t\t\t\t\tdo\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tif ( aMessage && strlen(aMessage) )\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tprintf(\"\\n%s\\n\",aMessage);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tprintf(\"[Y]es/[N]o/[C]ancel: \"); fflush(stdout);\n\t\t\t\t\t\t\t\tlChar = (char) tolower( getchar() ) ;\n\t\t\t\t\t\t\t\tprintf(\"\\n\\n\");\n\t\t\t\t\t\t}\n\t\t\t\t\t\twhile ( lChar != 'y' && lChar != 'n' && lChar != 'c' );\n\t\t\t\t\t\tlResult = (lChar == 'y') ? 1 : (lChar == 'n') ? 2 : 0 ;\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\t\tif ( aMessage && strlen(aMessage) )\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tprintf(\"\\n%s\\n\\n\",aMessage);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tprintf(\"press enter to continue \"); fflush(stdout);\n\t\t\t\t\t\tgetchar() ;\n\t\t\t\t\t\tprintf(\"\\n\\n\");\n\t\t\t\t\t\tlResult = 1 ;\n\t\t\t\t}\n\t\t\t\ttcsetattr(0, TCSANOW, &infoOri);\n\t\t\t\tfree(lDialogString);\n\t\t\t\treturn lResult ;\n\t\t}\n\n\t\tif (tinyfd_verbose) printf( \"lDialogString: %s\\n\" , lDialogString ) ;\n\n\t\tif ( ! ( lIn = popen( lDialogString , \"r\" ) ) )\n\t\t{\n\t\t\t\tfree(lDialogString);\n\t\t\t\treturn 0 ;\n\t\t}\n\t\twhile ( fgets( lBuff , sizeof( lBuff ) , lIn ) != NULL )\n\t\t{}\n\n\t\tpclose( lIn ) ;\n\n\t\t/* printf( \"lBuff: %s len: %lu \\n\" , lBuff , strlen(lBuff) ) ; */\n\t\tif ( strlen( lBuff ) && lBuff[strlen( lBuff ) -1] == '\\n' )\n\t\t{\n\t\t\t\tlBuff[strlen( lBuff ) -1] = '\\0' ;\n\t\t}\n\t\t/* printf( \"lBuff1: %s len: %lu \\n\" , lBuff , strlen(lBuff) ) ; */\n\n\t\tif (aDialogType && !strcmp(\"yesnocancel\", aDialogType))\n\t\t{\n\t\t\t\tif ( lBuff[0]=='1' )\n\t\t\t\t{\n\t\t\t\t\t\tif ( !strcmp( lBuff+1 , \"Yes\" )) strcpy(lBuff,\"1\");\n\t\t\t\t\t\telse if ( !strcmp( lBuff+1 , \"No\" )) strcpy(lBuff,\"2\");\n\t\t\t\t}\n\t\t}\n\t\t/* printf( \"lBuff2: %s len: %lu \\n\" , lBuff , strlen(lBuff) ) ; */\n\n\t\tlResult =  !strcmp( lBuff , \"2\" ) ? 2 : !strcmp( lBuff , \"1\" ) ? 1 : 0;\n\n\t\t/* printf( \"lResult: %d\\n\" , lResult ) ; */\n\t\tfree(lDialogString);\n\t\treturn lResult ;\n}\n\n\n/* return has only meaning for tinyfd_query */\nint tinyfd_notifyPopup(\n\t\tchar const * aTitle , /* NULL or \"\" */\n\t\tchar const * aMessage , /* NULL or \"\"  may contain \\n and \\t */\n\t\tchar const * aIconType ) /* \"info\" \"warning\" \"error\" */\n{\n\t\tchar lBuff[MAX_PATH_OR_CMD];\n\t\tchar * lDialogString = NULL ;\n\t\tchar * lpDialogString ;\n\t\tFILE * lIn ;\n\t\tsize_t lTitleLen ;\n\t\tsize_t lMessageLen ;\n\n\t\t\t\tif (tfd_quoteDetected(aTitle)) return tinyfd_notifyPopup(\"INVALID TITLE WITH QUOTES\", aMessage, aIconType);\n\t\t\t\tif (tfd_quoteDetected(aMessage)) return tinyfd_notifyPopup(aTitle, \"INVALID MESSAGE WITH QUOTES\", aIconType);\n\n\t\tif ( getenv(\"SSH_TTY\") && !dunstifyPresent() && !dunstPresent() )\n\t\t{\n\t\t\treturn tinyfd_messageBox(aTitle, aMessage, \"ok\", aIconType, 0);\n\t\t}\n\n\t\tlTitleLen =  aTitle ? strlen(aTitle) : 0 ;\n\t\tlMessageLen =  aMessage ? strlen(aMessage) : 0 ;\n\t\tif ( !aTitle || strcmp(aTitle,\"tinyfd_query\") )\n\t\t{\n\t\t\tlDialogString = (char *) malloc( MAX_PATH_OR_CMD + lTitleLen + lMessageLen );\n\t\t}\n\n\t\tif ( getenv(\"SSH_TTY\") )\n\t\t{\n\t\t\tif (aTitle&&!strcmp(aTitle,\"tinyfd_query\")){strcpy(tinyfd_response,\"dunst\");return 1;}\n\t\t\tstrcpy( lDialogString , \"notify-send \\\"\" ) ;\n\t\t\tif ( aTitle && strlen(aTitle) )\n\t\t\t{\n\t\t\t\tstrcat( lDialogString , aTitle ) ;\n\t\t\t\tstrcat( lDialogString , \"\\\" \\\"\" ) ;\n\t\t\t}\n\t\t\tif ( aMessage && strlen(aMessage) )\n\t\t\t{\n\t\t\t\tstrcat(lDialogString, aMessage) ;\n\t\t\t}\n\t\t\tstrcat( lDialogString , \"\\\"\" ) ;\n\t\t}\n\t\telse if ( osascriptPresent( ) )\n\t\t{\n\t\t\t\tif (aTitle&&!strcmp(aTitle,\"tinyfd_query\")){strcpy(tinyfd_response,\"applescript\");return 1;}\n\n\t\t\t\tstrcpy( lDialogString , \"osascript \");\n\t\t\t\tif ( ! osx9orBetter() ) strcat( lDialogString , \" -e 'tell application \\\"System Events\\\"' -e 'Activate'\");\n\t\t\t\tstrcat( lDialogString , \" -e 'try' -e 'display notification \\\"\") ;\n\t\t\t\tif ( aMessage && strlen(aMessage) )\n\t\t\t\t{\n\t\t\t\t\t\tstrcat(lDialogString, aMessage) ;\n\t\t\t\t}\n\t\t\t\tstrcat(lDialogString, \" \\\" \") ;\n\t\t\t\tif ( aTitle && strlen(aTitle) )\n\t\t\t\t{\n\t\t\t\t\t\tstrcat(lDialogString, \"with title \\\"\") ;\n\t\t\t\t\t\tstrcat(lDialogString, aTitle) ;\n\t\t\t\t\t\tstrcat(lDialogString, \"\\\" \") ;\n\t\t\t\t}\n\n\t\t\t\tstrcat( lDialogString, \"' -e 'end try'\") ;\n\t\t\t\tif ( ! osx9orBetter() ) strcat( lDialogString, \" -e 'end tell'\") ;\n\t\t}\n\t\telse if ( tfd_kdialogPresent() )\n\t\t{\n\t\t\t\tif (aTitle&&!strcmp(aTitle,\"tinyfd_query\")){strcpy(tinyfd_response,\"kdialog\");return 1;}\n\t\t\t\tstrcpy( lDialogString , \"kdialog\" ) ;\n\n\t\t\t\tif ( aIconType && strlen(aIconType) )\n\t\t\t\t{\n\t\t\t\t\t\tstrcat( lDialogString , \" --icon '\" ) ;\n\t\t\t\t\t\tstrcat( lDialogString , aIconType ) ;\n\t\t\t\t\t\tstrcat( lDialogString , \"'\" ) ;\n\t\t\t\t}\n\t\t\t\tif ( aTitle && strlen(aTitle) )\n\t\t\t\t{\n\t\t\t\t\t\tstrcat( lDialogString , \" --title \\\"\" ) ;\n\t\t\t\t\t\tstrcat( lDialogString , aTitle ) ;\n\t\t\t\t\t\tstrcat( lDialogString , \"\\\"\" ) ;\n\t\t\t\t}\n\n\t\t\t\tstrcat( lDialogString , \" --passivepopup\" ) ;\n\t\t\t\tstrcat( lDialogString , \" \\\"\" ) ;\n\t\t\t\tif ( aMessage )\n\t\t\t\t{\n\t\t\t\t\t\tstrcat( lDialogString , aMessage ) ;\n\t\t\t\t}\n\t\t\t\tstrcat( lDialogString , \" \\\" 5\" ) ;\n\t\t}\n\t\telse if ( tfd_yadPresent() )\n\t\t{\n\t\t\tif (aTitle&&!strcmp(aTitle,\"tinyfd_query\")){strcpy(tinyfd_response,\"yad\");return 1;}\n\t\t\tstrcpy( lDialogString , \"yad --notification\");\n\n\t\t\tif ( aIconType && strlen( aIconType ) )\n\t\t\t{\n\t\t\t\t\tstrcat( lDialogString , \" --image=\\\"\");\n\t\t\t\t\tstrcat( lDialogString , aIconType ) ;\n\t\t\t\t\tstrcat( lDialogString , \"\\\"\" ) ;\n\t\t\t}\n\n\t\t\tstrcat( lDialogString , \" --text=\\\"\" ) ;\n\t\t\tif ( aTitle && strlen(aTitle) )\n\t\t\t{\n\t\t\t\t\tstrcat(lDialogString, aTitle) ;\n\t\t\t\t\tstrcat(lDialogString, \"\\n\") ;\n\t\t\t}\n\t\t\tif ( aMessage && strlen( aMessage ) )\n\t\t\t{\n\t\t\t\t\tstrcat( lDialogString , aMessage ) ;\n\t\t\t}\n\t\t\tstrcat( lDialogString , \" \\\"\" ) ;\n\t\t}\n\t\telse if ( perlPresent() >= 2 )\n\t\t{\n\t\t\t\tif (aTitle&&!strcmp(aTitle,\"tinyfd_query\")){strcpy(tinyfd_response,\"perl-dbus\");return 1;}\n\n\t\t\t\tstrcpy( lDialogString , \"perl -e \\\"use Net::DBus;\\\nmy \\\\$sessionBus = Net::DBus->session;\\\nmy \\\\$notificationsService = \\\\$sessionBus->get_service('org.freedesktop.Notifications');\\\nmy \\\\$notificationsObject = \\\\$notificationsService->get_object('/org/freedesktop/Notifications',\\\n'org.freedesktop.Notifications');\");\n\n\t\t\t\tsprintf( lDialogString + strlen(lDialogString) ,\n\"my \\\\$notificationId;\\\\$notificationId = \\\\$notificationsObject->Notify(shift, 0, '%s', '%s', '%s', [], {}, -1);\\\" \",\naIconType?aIconType:\"\", aTitle?aTitle:\"\", aMessage?aMessage:\"\" ) ;\n\t\t}\n\t\telse if ( pythonDbusPresent( ) )\n\t\t{\n\t\t\t\tif (aTitle&&!strcmp(aTitle,\"tinyfd_query\")){strcpy(tinyfd_response,\"python-dbus\");return 1;}\n\t\t\t\tstrcpy( lDialogString , gPythonName ) ;\n\t\t\t\tstrcat( lDialogString ,\" -c \\\"import dbus;bus=dbus.SessionBus();\");\n\t\t\t\tstrcat( lDialogString ,\"notif=bus.get_object('org.freedesktop.Notifications','/org/freedesktop/Notifications');\" ) ;\n\t\t\t\tstrcat( lDialogString ,\"notify=dbus.Interface(notif,'org.freedesktop.Notifications');\" ) ;\n\t\t\t\tstrcat( lDialogString ,\"notify.Notify('',0,'\" ) ;\n\t\t\t\tif ( aIconType && strlen(aIconType) )\n\t\t\t\t{\n\t\t\t\t\t\tstrcat( lDialogString , aIconType ) ;\n\t\t\t\t}\n\t\t\t\tstrcat(lDialogString, \"','\") ;\n\t\t\t\tif ( aTitle && strlen(aTitle) )\n\t\t\t\t{\n\t\t\t\t\t\tstrcat(lDialogString, aTitle) ;\n\t\t\t\t}\n\t\t\t\tstrcat(lDialogString, \"','\") ;\n\t\t\t\tif ( aMessage && strlen(aMessage) )\n\t\t\t\t{\n\t\t\t\t\t\tlpDialogString = lDialogString + strlen(lDialogString);\n\t\t\t\t\t\ttfd_replaceSubStr( aMessage , \"\\n\" , \"\\\\n\" , lpDialogString ) ;\n\t\t\t\t}\n\t\t\t\tstrcat(lDialogString, \"','','',5000)\\\"\") ;\n\t\t}\n\t\telse if ( notifysendPresent() )\n\t\t{\n\t\t\tif (aTitle&&!strcmp(aTitle,\"tinyfd_query\")){strcpy(tinyfd_response,\"notifysend\");return 1;}\n\t\t\tstrcpy( lDialogString , \"notify-send\" ) ;\n\t\t\tif ( aIconType && strlen(aIconType) )\n\t\t\t{\n\t\t\t\t\tstrcat( lDialogString , \" -i '\" ) ;\n\t\t\t\t\tstrcat( lDialogString , aIconType ) ;\n\t\t\t\t\tstrcat( lDialogString , \"'\" ) ;\n\t\t\t}\n\t\t\tstrcat( lDialogString , \" \\\"\" ) ;\n\t\t\tif ( aTitle && strlen(aTitle) )\n\t\t\t{\n\t\t\t\t\tstrcat(lDialogString, aTitle) ;\n\t\t\t\t\tstrcat( lDialogString , \" | \" ) ;\n\t\t\t}\n\t\t\tif ( aMessage && strlen(aMessage) )\n\t\t\t{\n\t\t\t\ttfd_replaceSubStr( aMessage , \"\\n\\t\" , \" |  \" , lBuff ) ;\n\t\t\t\ttfd_replaceSubStr( aMessage , \"\\n\" , \" | \" , lBuff ) ;\n\t\t\t\ttfd_replaceSubStr( aMessage , \"\\t\" , \"  \" , lBuff ) ;\n\t\t\t\tstrcat(lDialogString, lBuff) ;\n\t\t\t}\n\t\t\tstrcat( lDialogString , \"\\\"\" ) ;\n\t\t}\n\t\telse if ( (tfd_zenity3Present()>=5) )\n\t\t{\n\t\t\t\t/* zenity 2.32 & 3.14 has the notification but with a bug: it doesnt return from it */\n\t\t\t\t/* zenity 3.8 show the notification as an alert ok cancel box */\n\t\t\t\t/* zenity 3.44 doesn't have the notification (3.42 has it) */\n\t\t\t\tif (aTitle&&!strcmp(aTitle,\"tinyfd_query\")){strcpy(tinyfd_response,\"zenity\");return 1;}\n\t\t\t\tstrcpy( lDialogString , \"zenity --notification\");\n\n\t\t\t\tif ( aIconType && strlen( aIconType ) )\n\t\t\t\t{\n\t\t\t\t\t\tstrcat( lDialogString , \" --window-icon '\");\n\t\t\t\t\t\tstrcat( lDialogString , aIconType ) ;\n\t\t\t\t\t\tstrcat( lDialogString , \"'\" ) ;\n\t\t\t\t}\n\n\t\t\t\tstrcat( lDialogString , \" --text \\\"\" ) ;\n\t\t\t\tif ( aTitle && strlen(aTitle) )\n\t\t\t\t{\n\t\t\t\t\t\tstrcat(lDialogString, aTitle) ;\n\t\t\t\t\t\tstrcat(lDialogString, \"\\n\") ;\n\t\t\t\t}\n\t\t\t\tif ( aMessage && strlen( aMessage ) )\n\t\t\t\t{\n\t\t\t\t\t\tstrcat( lDialogString , aMessage ) ;\n\t\t\t\t}\n\t\t\t\tstrcat( lDialogString , \" \\\"\" ) ;\n\t\t}\n\t\telse\n\t\t{\n\t\t\tif (lDialogString) free(lDialogString);\n\t\t\treturn tinyfd_messageBox(aTitle, aMessage, \"ok\", aIconType, 0);\n\t\t}\n\n\t\tif (tinyfd_verbose) printf( \"lDialogString: %s\\n\" , lDialogString ) ;\n\n\t\tif ( ! ( lIn = popen( lDialogString , \"r\" ) ) )\n\t\t{\n\t\t\t\tfree(lDialogString);\n\t\t\t\treturn 0 ;\n\t\t}\n\n\t\tpclose( lIn ) ;\n\t\tfree(lDialogString);\n\t\treturn 1;\n}\n\n\n/* returns NULL on cancel */\nchar * tinyfd_inputBox(\n\t\tchar const * aTitle , /* NULL or \"\" */\n\t\tchar const * aMessage , /* NULL or \"\" (\\n and \\t have no effect) */\n\t\tchar const * aDefaultInput ) /* \"\" , if NULL it's a passwordBox */\n{\n\t\tstatic char lBuff[MAX_PATH_OR_CMD];\n\t\tchar * lDialogString = NULL;\n\t\tchar * lpDialogString;\n\t\tFILE * lIn ;\n\t\tint lResult ;\n\t\tint lWasGdialog = 0 ;\n\t\tint lWasGraphicDialog = 0 ;\n\t\tint lWasXterm = 0 ;\n\t\tint lWasBasicXterm = 0 ;\n\t\tstruct termios oldt ;\n\t\tstruct termios newt ;\n\t\tchar * lEOF;\n\t\tsize_t lTitleLen ;\n\t\tsize_t lMessageLen ;\n\n\t\t\t\tif (!aTitle && !aMessage && !aDefaultInput) return lBuff; /* now I can fill lBuff from outside */\n\n\t\tlBuff[0]='\\0';\n\n\t\t\t\tif (tfd_quoteDetected(aTitle)) return tinyfd_inputBox(\"INVALID TITLE WITH QUOTES\", aMessage, aDefaultInput);\n\t\t\t\tif (tfd_quoteDetected(aMessage)) return tinyfd_inputBox(aTitle, \"INVALID MESSAGE WITH QUOTES\", aDefaultInput);\n\t\t\t\tif (tfd_quoteDetected(aDefaultInput)) return tinyfd_inputBox(aTitle, aMessage, \"INVALID DEFAULT_INPUT WITH QUOTES: use the GRAVE ACCENT \\\\x60 instead.\");\n\n\t\tlTitleLen =  aTitle ? strlen(aTitle) : 0 ;\n\t\tlMessageLen =  aMessage ? strlen(aMessage) : 0 ;\n\t\tif ( !aTitle || strcmp(aTitle,\"tinyfd_query\") )\n\t\t{\n\t\t\t\tlDialogString = (char *) malloc( MAX_PATH_OR_CMD + lTitleLen + lMessageLen );\n\t\t}\n\n\t\tif ( osascriptPresent( ) )\n\t\t{\n\t\t\t\tif (aTitle&&!strcmp(aTitle,\"tinyfd_query\")){strcpy(tinyfd_response,\"applescript\");return (char *)1;}\n\t\t\t\tstrcpy( lDialogString , \"osascript \");\n\t\t\t\tif ( ! osx9orBetter() ) strcat( lDialogString , \" -e 'tell application \\\"System Events\\\"' -e 'Activate'\");\n\t\t\t\tstrcat( lDialogString , \" -e 'try' -e 'display dialog \\\"\") ;\n\t\t\t\tif ( aMessage && strlen(aMessage) )\n\t\t\t\t{\n\t\t\t\t\t\tstrcat(lDialogString, aMessage) ;\n\t\t\t\t}\n\t\t\t\tstrcat(lDialogString, \"\\\" \") ;\n\t\t\t\tstrcat(lDialogString, \"default answer \\\"\") ;\n\t\t\t\tif ( aDefaultInput && strlen(aDefaultInput) )\n\t\t\t\t{\n\t\t\t\t\t\tstrcat(lDialogString, aDefaultInput) ;\n\t\t\t\t}\n\t\t\t\tstrcat(lDialogString, \"\\\" \") ;\n\t\t\t\tif ( ! aDefaultInput )\n\t\t\t\t{\n\t\t\t\t\t\tstrcat(lDialogString, \"hidden answer true \") ;\n\t\t\t\t}\n\t\t\t\tif ( aTitle && strlen(aTitle) )\n\t\t\t\t{\n\t\t\t\t\t\tstrcat(lDialogString, \"with title \\\"\") ;\n\t\t\t\t\t\tstrcat(lDialogString, aTitle) ;\n\t\t\t\t\t\tstrcat(lDialogString, \"\\\" \") ;\n\t\t\t\t}\n\t\t\t\tstrcat(lDialogString, \"with icon note' \") ;\n\t\t\t\tstrcat(lDialogString, \"-e '\\\"1\\\" & text returned of result' \" );\n\t\t\t\tstrcat(lDialogString, \"-e 'on error number -128' \" ) ;\n\t\t\t\tstrcat(lDialogString, \"-e '0' \" );\n\t\t\t\tstrcat(lDialogString, \"-e 'end try'\") ;\n\t\t\t\tif ( ! osx9orBetter() ) strcat(lDialogString, \" -e 'end tell'\") ;\n\t\t}\n\t\telse if ( tfd_kdialogPresent() )\n\t\t{\n\t\t\t\tif (aTitle&&!strcmp(aTitle,\"tinyfd_query\")){strcpy(tinyfd_response,\"kdialog\");return (char *)1;}\n\t\t\t\tstrcpy( lDialogString , \"szAnswer=$(kdialog\" ) ;\n\n\t\t\t\t\t\t\t\tif ( (tfd_kdialogPresent() == 2) && tfd_xpropPresent() )\n\t\t\t\t{\n\t\t\t\t\t\tstrcat(lDialogString, \" --attach=$(xprop -root 32x '\\t$0' _NET_ACTIVE_WINDOW | cut -f 2)\"); /* contribution: Paul Rouget */\n\t\t\t\t}\n\n\t\t\t\tif ( ! aDefaultInput )\n\t\t\t\t{\n\t\t\t\t\t\tstrcat(lDialogString, \" --password \") ;\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\t\tstrcat(lDialogString, \" --inputbox \") ;\n\n\t\t\t\t}\n\t\t\t\tstrcat(lDialogString, \"\\\"\") ;\n\t\t\t\tif ( aMessage && strlen(aMessage) )\n\t\t\t\t{\n\t\t\t\t\t\tstrcat(lDialogString, aMessage ) ;\n\t\t\t\t}\n\t\t\t\tstrcat(lDialogString , \"\\\" \\\"\" ) ;\n\t\t\t\tif ( aDefaultInput && strlen(aDefaultInput) )\n\t\t\t\t{\n\t\t\t\t\t\tstrcat(lDialogString, aDefaultInput ) ;\n\t\t\t\t}\n\t\t\t\tstrcat(lDialogString , \"\\\"\" ) ;\n\t\t\t\tif ( aTitle && strlen(aTitle) )\n\t\t\t\t{\n\t\t\t\t\t\tstrcat(lDialogString, \" --title \\\"\") ;\n\t\t\t\t\t\tstrcat(lDialogString, aTitle) ;\n\t\t\t\t\t\tstrcat(lDialogString, \"\\\"\") ;\n\t\t\t\t}\n\t\t\t\tstrcat( lDialogString ,\n\t\t\t\t\t\t\");if [ $? = 0 ];then echo 1$szAnswer;else echo 0$szAnswer;fi\");\n\t\t}\n\t\telse if ( tfd_zenityPresent() || tfd_matedialogPresent() || tfd_shellementaryPresent() || tfd_qarmaPresent() )\n\t\t{\n\t\t\t\tif ( tfd_zenityPresent() )\n\t\t\t\t{\n\t\t\t\t\t\tif (aTitle&&!strcmp(aTitle,\"tinyfd_query\")){strcpy(tinyfd_response,\"zenity\");return (char *)1;}\n\t\t\t\t\t\tstrcpy( lDialogString , \"szAnswer=$(zenity\" ) ;\n\t\t\t\t\t\t\t\t\t\t\t\tif ( (tfd_zenity3Present() >= 4) && !getenv(\"SSH_TTY\") && tfd_xpropPresent() )\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tstrcat( lDialogString, \" --attach=$(sleep .01;xprop -root 32x '\\t$0' _NET_ACTIVE_WINDOW | cut -f 2)\"); /* contribution: Paul Rouget */\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\telse if ( tfd_matedialogPresent() )\n\t\t\t\t{\n\t\t\t\t\t\tif (aTitle&&!strcmp(aTitle,\"tinyfd_query\")){strcpy(tinyfd_response,\"matedialog\");return (char *)1;}\n\t\t\t\t\t\tstrcpy( lDialogString ,  \"szAnswer=$(matedialog\" ) ;\n\t\t\t\t}\n\t\t\t\telse if ( tfd_shellementaryPresent() )\n\t\t\t\t{\n\t\t\t\t\t\tif (aTitle&&!strcmp(aTitle,\"tinyfd_query\")){strcpy(tinyfd_response,\"shellementary\");return (char *)1;}\n\t\t\t\t\t\tstrcpy( lDialogString , \"szAnswer=$(shellementary\" ) ;\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\t\tif (aTitle&&!strcmp(aTitle,\"tinyfd_query\")){strcpy(tinyfd_response,\"qarma\");return (char *)1;}\n\t\t\t\t\t\tstrcpy( lDialogString ,  \"szAnswer=$(qarma\" ) ;\n\t\t\t\t\t\t\t\t\t\t\t\tif ( !getenv(\"SSH_TTY\") && tfd_xpropPresent() )\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tstrcat(lDialogString, \" --attach=$(xprop -root 32x '\\t$0' _NET_ACTIVE_WINDOW | cut -f 2)\"); /* contribution: Paul Rouget */\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tstrcat( lDialogString ,\" --entry\" ) ;\n\n\t\t\t\tstrcat(lDialogString, \" --title=\\\"\") ;\n\t\t\t\tif (aTitle && strlen(aTitle)) strcat(lDialogString, aTitle) ;\n\t\t\t\tstrcat(lDialogString, \"\\\"\") ;\n\n\t\t\t\tstrcat(lDialogString, \" --text=\\\"\") ;\n\t\t\t\tif (aMessage && strlen(aMessage)) strcat(lDialogString, aMessage) ;\n\t\t\t\tstrcat(lDialogString, \"\\\"\") ;\n\n\t\t\t\tif ( aDefaultInput )\n\t\t\t\t{\n\t\t\t\t\t\tstrcat(lDialogString, \" --entry-text=\\\"\") ;\n\t\t\t\t\t\tstrcat(lDialogString, aDefaultInput) ;\n\t\t\t\t\t\tstrcat(lDialogString, \"\\\"\") ;\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\t\tstrcat(lDialogString, \" --hide-text\") ;\n\t\t\t\t}\n\t\t\t\tif (tinyfd_silent) strcat( lDialogString , \" 2>/dev/null \");\n\t\t\t\tstrcat( lDialogString ,\n\t\t\t\t\t\t\t\t\");if [ $? = 0 ];then echo 1$szAnswer;else echo 0$szAnswer;fi\");\n\t\t}\n\t\telse if (tfd_yadPresent())\n\t\t{\n\t\t   if (aTitle && !strcmp(aTitle, \"tinyfd_query\")) { strcpy(tinyfd_response, \"yad\"); return (char*)1; }\n\t\t   strcpy(lDialogString, \"szAnswer=$(yad --entry\");\n\t\t   if (aTitle && strlen(aTitle))\n\t\t   {\n\t\t\t  strcat(lDialogString, \" --title=\\\"\");\n\t\t\t  strcat(lDialogString, aTitle);\n\t\t\t  strcat(lDialogString, \"\\\"\");\n\t\t   }\n\t\t   if (aMessage && strlen(aMessage))\n\t\t   {\n\t\t\t  strcat(lDialogString, \" --text=\\\"\");\n\t\t\t  strcat(lDialogString, aMessage);\n\t\t\t  strcat(lDialogString, \"\\\"\");\n\t\t   }\n\t\t   if (aDefaultInput && strlen(aDefaultInput))\n\t\t   {\n\t\t\t  strcat(lDialogString, \" --entry-text=\\\"\");\n\t\t\t  strcat(lDialogString, aDefaultInput);\n\t\t\t  strcat(lDialogString, \"\\\"\");\n\t\t   }\n\t\t   else\n\t\t   {\n\t\t\t  strcat(lDialogString, \" --hide-text\");\n\t\t   }\n\t\t   if (tinyfd_silent) strcat(lDialogString, \" 2>/dev/null \");\n\t\t   strcat(lDialogString,\n\t\t\t  \");if [ $? = 0 ];then echo 1$szAnswer;else echo 0$szAnswer;fi\");\n\t\t}\n\t\telse if ( gxmessagePresent() || gmessagePresent() )\n\t\t{\n\t\t\t\tif ( gxmessagePresent() ) {\n\t\t\t\t\t\tif (aTitle&&!strcmp(aTitle,\"tinyfd_query\")){strcpy(tinyfd_response,\"gxmessage\");return (char *)1;}\n\t\t\t\t\t\tstrcpy( lDialogString , \"szAnswer=$(gxmessage -buttons Ok:1,Cancel:0 -center \\\"\");\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\t\tif (aTitle&&!strcmp(aTitle,\"tinyfd_query\")){strcpy(tinyfd_response,\"gmessage\");return (char *)1;}\n\t\t\t\t\t\tstrcpy( lDialogString , \"szAnswer=$(gmessage -buttons Ok:1,Cancel:0 -center \\\"\");\n\t\t\t\t}\n\n\t\t\t\tif ( aMessage && strlen(aMessage) )\n\t\t\t\t{\n\t\t\t\t\t\tstrcat( lDialogString , aMessage ) ;\n\t\t\t\t}\n\t\t\t\tstrcat(lDialogString, \"\\\"\" ) ;\n\t\t\t\tif ( aTitle && strlen(aTitle) )\n\t\t\t\t{\n\t\t\t\t\t\tstrcat( lDialogString , \" -title  \\\"\");\n\t\t\t\t\t\tstrcat( lDialogString , aTitle ) ;\n\t\t\t\t\t\tstrcat(lDialogString, \"\\\" \" ) ;\n\t\t\t\t}\n\t\t\t\tstrcat(lDialogString, \" -entrytext \\\"\" ) ;\n\t\t\t\tif ( aDefaultInput && strlen(aDefaultInput) )\n\t\t\t\t{\n\t\t\t\t\t\tstrcat( lDialogString , aDefaultInput ) ;\n\t\t\t\t}\n\t\t\t\tstrcat(lDialogString, \"\\\"\" ) ;\n\t\t\t\tstrcat( lDialogString , \");echo $?$szAnswer\");\n\t\t}\n\t\t\t\telse if ( !gdialogPresent() && !xdialogPresent() && tkinter3Present( ) )\n\t\t\t\t{\n\t\t\t\t\t\tif (aTitle&&!strcmp(aTitle,\"tinyfd_query\")){strcpy(tinyfd_response,\"python3-tkinter\");return (char *)1;}\n\t\t\t\t\t\tstrcpy( lDialogString , gPython3Name ) ;\n\t\t\t\t\t\tstrcat( lDialogString ,\n\t\t\t\t\t\t\t\t\" -S -c \\\"import tkinter; from tkinter import simpledialog;root=tkinter.Tk();root.withdraw();\");\n\t\t\t\t\t\tstrcat( lDialogString ,\"res=simpledialog.askstring(\" ) ;\n\t\t\t\t\t\tif ( aTitle && strlen(aTitle) )\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tstrcat(lDialogString, \"title='\") ;\n\t\t\t\t\t\t\t\tstrcat(lDialogString, aTitle) ;\n\t\t\t\t\t\t\t\tstrcat(lDialogString, \"',\") ;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif ( aMessage && strlen(aMessage) )\n\t\t\t\t\t\t{\n\n\t\t\t\t\t\t\t\tstrcat(lDialogString, \"prompt='\") ;\n\t\t\t\t\t\t\t\tlpDialogString = lDialogString + strlen(lDialogString);\n\t\t\t\t\t\t\t\ttfd_replaceSubStr( aMessage , \"\\n\" , \"\\\\n\" , lpDialogString ) ;\n\t\t\t\t\t\t\t\tstrcat(lDialogString, \"',\") ;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif ( aDefaultInput )\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tif ( strlen(aDefaultInput) )\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tstrcat(lDialogString, \"initialvalue='\") ;\n\t\t\t\t\t\t\t\t\t\tstrcat(lDialogString, aDefaultInput) ;\n\t\t\t\t\t\t\t\t\t\tstrcat(lDialogString, \"',\") ;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\telse\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tstrcat(lDialogString, \"show='*'\") ;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tstrcat(lDialogString, \");\\nif res is None :\\n\\tprint(0)\");\n\t\t\t\t\t\tstrcat(lDialogString, \"\\nelse :\\n\\tprint('1'+res)\\n\\\"\" ) ;\n\t\t\t\t}\n\t\t\t\telse if ( !gdialogPresent() && !xdialogPresent() && tkinter2Present( ) )\n\t\t{\n\t\t\t\tif (aTitle&&!strcmp(aTitle,\"tinyfd_query\")){strcpy(tinyfd_response,\"python2-tkinter\");return (char *)1;}\n\t\t\t\t\t\t\t\tstrcpy( lDialogString , \"export PYTHONIOENCODING=utf-8;\" ) ;\n\t\t\t\t\t\t\t\tstrcat( lDialogString , gPython2Name ) ;\n\t\t\t\t\t\t\t\tif ( ! isTerminalRunning( ) && tfd_isDarwin( ) )\n\t\t\t\t{\n\t\t\t\tstrcat( lDialogString , \" -i\" ) ;  /* for osx without console */\n\t\t\t\t}\n\n\t\t\t\t\t\t\t\tstrcat( lDialogString ,\n\t\t\t\t\t\t\t\t\t\t\" -S -c \\\"import Tkinter,tkSimpleDialog;root=Tkinter.Tk();root.withdraw();\");\n\n\t\t\t\tif ( tfd_isDarwin( ) )\n\t\t\t\t{\n\t\t\t\t\t\tstrcat( lDialogString ,\n\"import os;os.system('''/usr/bin/osascript -e 'tell app \\\\\\\"Finder\\\\\\\" to set \\\nfrontmost of process \\\\\\\"Python\\\\\\\" to true' ''');\");\n\t\t\t\t}\n\n\t\t\t\tstrcat( lDialogString ,\"res=tkSimpleDialog.askstring(\" ) ;\n\t\t\t\tif ( aTitle && strlen(aTitle) )\n\t\t\t\t{\n\t\t\t\t\t\tstrcat(lDialogString, \"title='\") ;\n\t\t\t\t\t\tstrcat(lDialogString, aTitle) ;\n\t\t\t\t\t\tstrcat(lDialogString, \"',\") ;\n\t\t\t\t}\n\t\t\t\tif ( aMessage && strlen(aMessage) )\n\t\t\t\t{\n\n\t\t\t\t\t\tstrcat(lDialogString, \"prompt='\") ;\n\t\t\t\t\t\tlpDialogString = lDialogString + strlen(lDialogString);\n\t\t\t\t\t\ttfd_replaceSubStr( aMessage , \"\\n\" , \"\\\\n\" , lpDialogString ) ;\n\t\t\t\t\t\tstrcat(lDialogString, \"',\") ;\n\t\t\t\t}\n\t\t\t\tif ( aDefaultInput )\n\t\t\t\t{\n\t\t\t\t\t\tif ( strlen(aDefaultInput) )\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tstrcat(lDialogString, \"initialvalue='\") ;\n\t\t\t\t\t\t\t\tstrcat(lDialogString, aDefaultInput) ;\n\t\t\t\t\t\t\t\tstrcat(lDialogString, \"',\") ;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\t\tstrcat(lDialogString, \"show='*'\") ;\n\t\t\t\t}\n\t\t\t\tstrcat(lDialogString, \");\\nif res is None :\\n\\tprint 0\");\n\t\t\t\tstrcat(lDialogString, \"\\nelse :\\n\\tprint '1'+res\\n\\\"\" ) ;\n\t\t}\n\t\telse if ( gdialogPresent() || xdialogPresent() || dialogName() || whiptailPresent() )\n\t\t{\n\t\t\t\tif ( gdialogPresent( ) )\n\t\t\t\t{\n\t\t\t\t\t\tif (aTitle&&!strcmp(aTitle,\"tinyfd_query\")){strcpy(tinyfd_response,\"gdialog\");return (char *)1;}\n\t\t\t\t\t\tlWasGraphicDialog = 1 ;\n\t\t\t\t\t\tlWasGdialog = 1 ;\n\t\t\t\t\t\tstrcpy( lDialogString , \"(gdialog \" ) ;\n\t\t\t\t}\n\t\t\t\telse if ( xdialogPresent( ) )\n\t\t\t\t{\n\t\t\t\t\t\tif (aTitle&&!strcmp(aTitle,\"tinyfd_query\")){strcpy(tinyfd_response,\"xdialog\");return (char *)1;}\n\t\t\t\t\t\tlWasGraphicDialog = 1 ;\n\t\t\t\t\t\tstrcpy( lDialogString , \"(Xdialog \" ) ;\n\t\t\t\t}\n\t\t\t\telse if ( dialogName( ) )\n\t\t\t\t{\n\t\t\t\t\t\tif (aTitle&&!strcmp(aTitle,\"tinyfd_query\")){strcpy(tinyfd_response,\"dialog\");return (char *)0;}\n\t\t\t\t\t\tif ( isTerminalRunning( ) )\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tstrcpy( lDialogString , \"(dialog \" ) ;\n\t\t\t\t\t\t}\n\t\t\t\t\t\telse\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tlWasXterm = 1 ;\n\t\t\t\t\t\t\t\tstrcpy( lDialogString , terminalName() ) ;\n\t\t\t\t\t\t\t\tstrcat( lDialogString , \"'(\" ) ;\n\t\t\t\t\t\t\t\tstrcat( lDialogString , dialogName() ) ;\n\t\t\t\t\t\t\t\tstrcat( lDialogString , \" \" ) ;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\telse if ( isTerminalRunning( ) )\n\t\t\t\t{\n\t\t\t\t\t\tif (aTitle&&!strcmp(aTitle,\"tinyfd_query\")){strcpy(tinyfd_response,\"whiptail\");return (char *)0;}\n\t\t\t\t\t\tstrcpy( lDialogString , \"(whiptail \" ) ;\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\t\tif (aTitle&&!strcmp(aTitle,\"tinyfd_query\")){strcpy(tinyfd_response,\"whiptail\");return (char *)0;}\n\t\t\t\t\t\tlWasXterm = 1 ;\n\t\t\t\t\t\tstrcpy( lDialogString , terminalName() ) ;\n\t\t\t\t\t\tstrcat( lDialogString , \"'(whiptail \" ) ;\n\t\t\t\t}\n\n\t\t\t\tif ( aTitle && strlen(aTitle) )\n\t\t\t\t{\n\t\t\t\t\t\tstrcat(lDialogString, \"--title \\\"\") ;\n\t\t\t\t\t\tstrcat(lDialogString, aTitle) ;\n\t\t\t\t\t\tstrcat(lDialogString, \"\\\" \") ;\n\t\t\t\t}\n\n\t\t\t\tif ( !xdialogPresent() && !gdialogPresent() )\n\t\t\t\t{\n\t\t\t\t\t\tstrcat(lDialogString, \"--backtitle \\\"\") ;\n\t\t\t\t\t\tstrcat(lDialogString, \"tab: move focus\") ;\n\t\t\t\t\t\tif ( ! aDefaultInput && !lWasGdialog )\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tstrcat(lDialogString, \" (sometimes nothing, no blink nor star, is shown in text field)\") ;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tstrcat(lDialogString, \"\\\" \") ;\n\t\t\t\t}\n\n\t\t\t\tif ( aDefaultInput || lWasGdialog )\n\t\t\t\t{\n\t\t\t\t\t\tstrcat( lDialogString , \"--inputbox\" ) ;\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\t\tif ( !lWasGraphicDialog && dialogName() && isDialogVersionBetter09b() )\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tstrcat( lDialogString , \"--insecure \" ) ;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tstrcat( lDialogString , \"--passwordbox\" ) ;\n\t\t\t\t}\n\t\t\t\tstrcat( lDialogString , \" \\\"\" ) ;\n\t\t\t\tif ( aMessage && strlen(aMessage) )\n\t\t\t\t{\n\t\t\t\t\t\tstrcat(lDialogString, aMessage) ;\n\t\t\t\t}\n\t\t\t\tstrcat(lDialogString,\"\\\" 10 60 \") ;\n\t\t\t\tif ( aDefaultInput && strlen(aDefaultInput) )\n\t\t\t\t{\n\t\t\t\t\t\tstrcat(lDialogString, \"\\\"\") ;\n\t\t\t\t\t\tstrcat(lDialogString, aDefaultInput) ;\n\t\t\t\t\t\tstrcat(lDialogString, \"\\\" \") ;\n\t\t\t\t}\n\t\t\t\tif ( lWasGraphicDialog )\n\t\t\t\t{\n\t\t\t\t\t\tstrcat(lDialogString,\") 2>/tmp/tinyfd.txt;\\\n\t\tif [ $? = 0 ];then tinyfdBool=1;else tinyfdBool=0;fi;\\\n\t\ttinyfdRes=$(cat /tmp/tinyfd.txt);echo $tinyfdBool$tinyfdRes\") ;\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\t\tstrcat(lDialogString,\">/dev/tty ) 2>/tmp/tinyfd.txt;\\\n\t\tif [ $? = 0 ];then tinyfdBool=1;else tinyfdBool=0;fi;\\\n\t\ttinyfdRes=$(cat /tmp/tinyfd.txt);echo $tinyfdBool$tinyfdRes\") ;\n\n\t\t\t\t\t\tif ( lWasXterm )\n\t\t\t\t\t\t{\n\t\t\t\tstrcat(lDialogString,\" >/tmp/tinyfd0.txt';cat /tmp/tinyfd0.txt\");\n\t\t\t\t\t\t}\n\t\t\t\t\t\telse\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tstrcat(lDialogString, \"; clear >/dev/tty\") ;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\telse if ( ! isTerminalRunning( ) && terminalName() )\n\t\t{\n\t\t\t\tif (aTitle&&!strcmp(aTitle,\"tinyfd_query\")){strcpy(tinyfd_response,\"basicinput\");return (char *)0;}\n\t\t\t\tlWasBasicXterm = 1 ;\n\t\t\t\tstrcpy( lDialogString , terminalName() ) ;\n\t\t\t\tstrcat( lDialogString , \"'\" ) ;\n\t\t\t\tif ( !gWarningDisplayed && !tinyfd_forceConsole)\n\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tgWarningDisplayed = 1 ;\n\t\t\t\t\t\t\t\t\t\ttinyfd_messageBox(gTitle,tinyfd_needs,\"ok\",\"warning\",0);\n\t\t\t\t}\n\t\t\t\tif ( aTitle && strlen(aTitle) && !tinyfd_forceConsole)\n\t\t\t\t{\n\t\t\t\t\t\tstrcat( lDialogString , \"echo \\\"\" ) ;\n\t\t\t\t\t\tstrcat( lDialogString, aTitle) ;\n\t\t\t\t\t\tstrcat( lDialogString , \"\\\";echo;\" ) ;\n\t\t\t\t}\n\n\t\t\t\tstrcat( lDialogString , \"echo \\\"\" ) ;\n\t\t\t\tif ( aMessage && strlen(aMessage) )\n\t\t\t\t{\n\t\t\t\t\t\tstrcat( lDialogString, aMessage) ;\n\t\t\t\t}\n\t\t\t\tstrcat( lDialogString , \"\\\";read \" ) ;\n\t\t\t\tif ( ! aDefaultInput )\n\t\t\t\t{\n\t\t\t\t\t\tstrcat( lDialogString , \"-s \" ) ;\n\t\t\t\t}\n\t\t\t\tstrcat( lDialogString , \"-p \\\"\" ) ;\n\t\t\t\tstrcat( lDialogString , \"(esc+enter to cancel): \\\" ANSWER \" ) ;\n\t\t\t\tstrcat( lDialogString , \";echo 1$ANSWER >/tmp/tinyfd.txt';\" ) ;\n\t\t\t\tstrcat( lDialogString , \"cat -v /tmp/tinyfd.txt\");\n\t\t}\n\t\telse if ( !gWarningDisplayed && ! isTerminalRunning( ) && ! terminalName() ) {\n\t\t\t\t\t\tgWarningDisplayed = 1 ;\n\t\t\t\t\t\ttinyfd_messageBox(gTitle,tinyfd_needs,\"ok\",\"warning\",0);\n\t\t\t\t\t\tif (aTitle&&!strcmp(aTitle,\"tinyfd_query\")){strcpy(tinyfd_response,\"no_solution\");return (char *)0;}\n\t\t\t\t\t\tfree(lDialogString);\n\t\t\t\t\t\treturn NULL;\n\t\t}\n\t\telse\n\t\t{\n\t\t\t\tif (aTitle&&!strcmp(aTitle,\"tinyfd_query\")){strcpy(tinyfd_response,\"basicinput\");return (char *)0;}\n\t\t\t\tif ( !gWarningDisplayed && !tinyfd_forceConsole)\n\t\t\t\t{\n\t\t\t\t\t\tgWarningDisplayed = 1 ;\n\t\t\t\t\t\ttinyfd_messageBox(gTitle,tinyfd_needs,\"ok\",\"warning\",0);\n\t\t\t\t}\n\t\t\t\tif ( aTitle && strlen(aTitle) )\n\t\t\t\t{\n\t\t\t\t\t\tprintf(\"\\n%s\\n\", aTitle);\n\t\t\t\t}\n\t\t\t\tif ( aMessage && strlen(aMessage) )\n\t\t\t\t{\n\t\t\t\t\t\tprintf(\"\\n%s\\n\",aMessage);\n\t\t\t\t}\n\t\t\t\tprintf(\"(esc+enter to cancel): \"); fflush(stdout);\n\t\t\t\tif ( ! aDefaultInput )\n\t\t\t\t{\n\t\t\t\t\t\ttcgetattr(STDIN_FILENO, & oldt) ;\n\t\t\t\t\t\tnewt = oldt ;\n\t\t\t\t\t\tnewt.c_lflag &= ~ECHO ;\n\t\t\t\t\t\ttcsetattr(STDIN_FILENO, TCSANOW, & newt);\n\t\t\t\t}\n\n\t\t\t\tlEOF = fgets(lBuff, MAX_PATH_OR_CMD, stdin);\n\t\t\t\t/* printf(\"lbuff<%c><%d>\\n\",lBuff[0],lBuff[0]); */\n\t\t\t\tif ( ! lEOF  || (lBuff[0] == '\\0') )\n\t\t\t\t{\n\t\t\t\t\t\tfree(lDialogString);\n\t\t\t\t\t\treturn NULL;\n\t\t\t\t}\n\n\t\t\t\tif ( lBuff[0] == '\\n' )\n\t\t\t\t{\n\t\t\t\t\t\tlEOF = fgets(lBuff, MAX_PATH_OR_CMD, stdin);\n\t\t\t\t\t\t/* printf(\"lbuff<%c><%d>\\n\",lBuff[0],lBuff[0]); */\n\t\t\t\t\t\tif ( ! lEOF  || (lBuff[0] == '\\0') )\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tfree(lDialogString);\n\t\t\t\t\t\t\t\treturn NULL;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif ( ! aDefaultInput )\n\t\t\t\t{\n\t\t\t\t\t\ttcsetattr(STDIN_FILENO, TCSANOW, & oldt);\n\t\t\t\t\t\tprintf(\"\\n\");\n\t\t\t\t}\n\t\t\t\tprintf(\"\\n\");\n\t\t\t\tif ( strchr(lBuff,27) )\n\t\t\t\t{\n\t\t\t\t\t\tfree(lDialogString);\n\t\t\t\t\t\treturn NULL ;\n\t\t\t\t}\n\t\t\t\tif ( lBuff[strlen( lBuff ) -1] == '\\n' )\n\t\t\t\t{\n\t\t\t\t\t\tlBuff[strlen( lBuff ) -1] = '\\0' ;\n\t\t\t\t}\n\t\t\t\tfree(lDialogString);\n\t\t\t\treturn lBuff ;\n\t\t}\n\n\t\tif (tinyfd_verbose) printf( \"lDialogString: %s\\n\" , lDialogString ) ;\n\t\tlIn = popen( lDialogString , \"r\" );\n\t\tif ( ! lIn  )\n\t\t{\n\t\t\t\tif ( fileExists(\"/tmp/tinyfd.txt\") )\n\t\t\t\t{\n\t\t\t\t\t\twipefile(\"/tmp/tinyfd.txt\");\n\t\t\t\t\t\tremove(\"/tmp/tinyfd.txt\");\n\t\t\t\t}\n\t\t\t\tif ( fileExists(\"/tmp/tinyfd0.txt\") )\n\t\t\t\t{\n\t\t\t\t\t\twipefile(\"/tmp/tinyfd0.txt\");\n\t\t\t\t\t\tremove(\"/tmp/tinyfd0.txt\");\n\t\t\t\t}\n\t\t\t\tfree(lDialogString);\n\t\t\t\treturn NULL ;\n\t\t}\n\t\twhile ( fgets( lBuff , sizeof( lBuff ) , lIn ) != NULL )\n\t\t{}\n\n\t\tpclose( lIn ) ;\n\n\t\tif ( fileExists(\"/tmp/tinyfd.txt\") )\n\t\t{\n\t\t\t\twipefile(\"/tmp/tinyfd.txt\");\n\t\t\t\tremove(\"/tmp/tinyfd.txt\");\n\t\t}\n\t\tif ( fileExists(\"/tmp/tinyfd0.txt\") )\n\t\t{\n\t\t\t\twipefile(\"/tmp/tinyfd0.txt\");\n\t\t\t\tremove(\"/tmp/tinyfd0.txt\");\n\t\t}\n\n\t\t/* printf( \"len Buff: %lu\\n\" , strlen(lBuff) ) ; */\n\t\t/* printf( \"lBuff0: %s\\n\" , lBuff ) ; */\n\t\tif ( strlen( lBuff ) && lBuff[strlen( lBuff ) -1] == '\\n' )\n\t\t{\n\t\t\t\tlBuff[strlen( lBuff ) -1] = '\\0' ;\n\t\t}\n\t\t/* printf( \"lBuff1: %s len: %lu \\n\" , lBuff , strlen(lBuff) ) ; */\n\t\tif ( lWasBasicXterm )\n\t\t{\n\t\t\t\tif ( strstr(lBuff,\"^[\") ) /* esc was pressed */\n\t\t\t\t{\n\t\t\t\t\t\tfree(lDialogString);\n\t\t\t\t\t\treturn NULL ;\n\t\t\t\t}\n\t\t}\n\n\t\tlResult =  strncmp( lBuff , \"1\" , 1) ? 0 : 1 ;\n\t\t/* printf( \"lResult: %d \\n\" , lResult ) ; */\n\t\tif ( ! lResult )\n\t\t{\n\t\t\t\tfree(lDialogString);\n\t\t\t\treturn NULL ;\n\t\t}\n\n\t\t/* printf( \"lBuff+1: %s\\n\" , lBuff+1 ) ; */\n\t\tfree(lDialogString);\n\t\treturn lBuff+1 ;\n}\n\n\nchar * tinyfd_saveFileDialog(\n        char const * aTitle , /* NULL or \"\" */\n        char const * aDefaultPathAndOrFile , /* NULL or \"\" , ends with / to set only a directory */\n        int aNumOfFilterPatterns , /* 0 */\n        char const * const * aFilterPatterns , /* NULL or {\"*.txt\",\"*.doc\"} */\n        char const * aSingleFilterDescription ) /* NULL or \"text files\" */\n{\n\t\tstatic char lBuff[MAX_PATH_OR_CMD] ;\n\t\tstatic char lLastDirectory[MAX_PATH_OR_CMD] = \"$PWD\" ;\n\n\t\tchar lDialogString[MAX_PATH_OR_CMD] ;\n\t\tchar lString[MAX_PATH_OR_CMD] ;\n\t\tint i ;\n\t\tint lWasGraphicDialog = 0 ;\n\t\tint lWasXterm = 0 ;\n\t\tchar * p ;\n        char * lPointerInputBox ;\n\t\tFILE * lIn ;\n\t\tlBuff[0]='\\0';\n\n\t\tif ( ! aFilterPatterns ) aNumOfFilterPatterns = 0 ;\n\t\t\t\tif (tfd_quoteDetected(aTitle)) return tinyfd_saveFileDialog(\"INVALID TITLE WITH QUOTES\", aDefaultPathAndOrFile, aNumOfFilterPatterns, aFilterPatterns, aSingleFilterDescription);\n\t\t\t\tif (tfd_quoteDetected(aDefaultPathAndOrFile)) return tinyfd_saveFileDialog(aTitle, \"INVALID DEFAULT_PATH WITH QUOTES\", aNumOfFilterPatterns, aFilterPatterns, aSingleFilterDescription);\n\t\t\t\tif (tfd_quoteDetected(aSingleFilterDescription)) return tinyfd_saveFileDialog(aTitle, aDefaultPathAndOrFile, aNumOfFilterPatterns, aFilterPatterns, \"INVALID FILTER_DESCRIPTION WITH QUOTES\");\n\t\t\t\tfor (i = 0; i < aNumOfFilterPatterns; i++)\n\t\t\t\t{\n\t\t\t\t\t\tif (tfd_quoteDetected(aFilterPatterns[i])) return tinyfd_saveFileDialog(\"INVALID FILTER_PATTERN WITH QUOTES: use the GRAVE ACCENT \\\\x60 instead.\", aDefaultPathAndOrFile, 0, NULL, NULL);\n\t\t\t\t}\n\n\t\tif ( osascriptPresent( ) )\n\t\t{\n\t\t\t\tif (aTitle&&!strcmp(aTitle,\"tinyfd_query\")){strcpy(tinyfd_response,\"applescript\");return (char *)1;}\n\t\t\t\tstrcpy( lDialogString , \"osascript \");\n\t\t\t\tif ( ! osx9orBetter() ) strcat( lDialogString , \" -e 'tell application \\\"Finder\\\"' -e 'Activate'\");\n\t\t\t\tstrcat( lDialogString , \" -e 'try' -e 'POSIX path of ( choose file name \" );\n\t\t\t\tif ( aTitle && strlen(aTitle) )\n\t\t\t\t{\n\t\t\t\t\t\tstrcat(lDialogString, \"with prompt \\\"\") ;\n\t\t\t\t\t\tstrcat(lDialogString, aTitle) ;\n\t\t\t\t\t\tstrcat(lDialogString, \"\\\" \") ;\n\t\t\t\t}\n\t\t\t\tgetPathWithoutFinalSlash( lString , aDefaultPathAndOrFile ) ;\n\t\t\t\tif ( strlen(lString) )\n\t\t\t\t{\n\t\t\t\t\t\tstrcat(lDialogString, \"default location \\\"\") ;\n\t\t\t\t\t\tstrcat(lDialogString, lString ) ;\n\t\t\t\t\t\tstrcat(lDialogString , \"\\\" \" ) ;\n\t\t\t\t}\n\t\t\t\tgetLastName( lString , aDefaultPathAndOrFile ) ;\n\t\t\t\tif ( strlen(lString) )\n\t\t\t\t{\n\t\t\t\t\t\tstrcat(lDialogString, \"default name \\\"\") ;\n\t\t\t\t\t\tstrcat(lDialogString, lString ) ;\n\t\t\t\t\t\tstrcat(lDialogString , \"\\\" \" ) ;\n\t\t\t\t}\n\t\t\t\tstrcat( lDialogString , \")' \" ) ;\n\t\t\t\tstrcat(lDialogString, \"-e 'on error number -128' \" ) ;\n\t\t\t\tstrcat(lDialogString, \"-e 'end try'\") ;\n\t\t\t\tif ( ! osx9orBetter() ) strcat( lDialogString, \" -e 'end tell'\") ;\n\t\t}\n\t\telse if ( tfd_kdialogPresent() )\n\t\t{\n\t\t\t\tif (aTitle&&!strcmp(aTitle,\"tinyfd_query\")){strcpy(tinyfd_response,\"kdialog\");return (char *)1;}\n\n\t\t\t\tstrcpy( lDialogString , \"kdialog\" ) ;\n                if ( (tfd_kdialogPresent() == 2) && tfd_xpropPresent() )\n\t\t\t\t{\n                    strcat(lDialogString, \" --attach=$(xprop -root 32x '\\t$0' _NET_ACTIVE_WINDOW | cut -f 2)\"); /* contribution: Paul Rouget */\n\t\t\t\t}\n\t\t\t\tstrcat( lDialogString , \" --getsavefilename \" ) ;\n\n\t\t\t\tif ( aDefaultPathAndOrFile && strlen(aDefaultPathAndOrFile) )\n\t\t\t\t{\n                    if ( aDefaultPathAndOrFile[0] != '/' )\n                    {\n                        strcat(lDialogString, lLastDirectory) ;\n                        strcat(lDialogString , \"/\" ) ;\n                    }\n                    strcat(lDialogString, \"\\\"\") ;\n                    strcat(lDialogString, aDefaultPathAndOrFile ) ;\n                    strcat(lDialogString , \"\\\"\" ) ;\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n                    strcat(lDialogString, lLastDirectory) ;\n                    strcat(lDialogString , \"/\" ) ;\n\t\t\t\t}\n\n\t\t\t\tif ( aNumOfFilterPatterns > 0 )\n\t\t\t\t{\n\t\t\t\t\t\tstrcat(lDialogString , \" \\\"\" ) ;\n                        strcat( lDialogString , aFilterPatterns[0] ) ;\n\t\t\t\t\t\tfor ( i = 1 ; i < aNumOfFilterPatterns ; i ++ )\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tstrcat( lDialogString , \" \" ) ;\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tstrcat( lDialogString , aFilterPatterns[i] ) ;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif ( aSingleFilterDescription && strlen(aSingleFilterDescription) )\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tstrcat( lDialogString , \" | \" ) ;\n\t\t\t\t\t\t\t\tstrcat( lDialogString , aSingleFilterDescription ) ;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tstrcat( lDialogString , \"\\\"\" ) ;\n\t\t\t\t}\n\t\t\t\tif ( aTitle && strlen(aTitle) )\n\t\t\t\t{\n\t\t\t\t\t\tstrcat(lDialogString, \" --title \\\"\") ;\n\t\t\t\t\t\tstrcat(lDialogString, aTitle) ;\n\t\t\t\t\t\tstrcat(lDialogString, \"\\\"\") ;\n\t\t\t\t}\n\t\t}\n\t\telse if ( tfd_zenityPresent() || tfd_matedialogPresent() || tfd_shellementaryPresent() || tfd_qarmaPresent() )\n\t\t{\n\t\t\t\tif ( tfd_zenityPresent() )\n\t\t\t\t{\n\t\t\t\t\t\tif (aTitle&&!strcmp(aTitle,\"tinyfd_query\")){strcpy(tinyfd_response,\"zenity\");return (char *)1;}\n\t\t\t\t\t\tstrcpy( lDialogString , \"zenity\" ) ;\n\t\t\t\t\t\t\t\t\t\t\t\tif ( (tfd_zenity3Present() >= 4) && !getenv(\"SSH_TTY\") && tfd_xpropPresent() )\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tstrcat( lDialogString, \" --attach=$(sleep .01;xprop -root 32x '\\t$0' _NET_ACTIVE_WINDOW | cut -f 2)\"); /* contribution: Paul Rouget */\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\telse if ( tfd_matedialogPresent() )\n\t\t\t\t{\n\t\t\t\t\t\tif (aTitle&&!strcmp(aTitle,\"tinyfd_query\")){strcpy(tinyfd_response,\"matedialog\");return (char *)1;}\n\t\t\t\t\t\tstrcpy( lDialogString , \"matedialog\" ) ;\n\t\t\t\t}\n\t\t\t\telse if ( tfd_shellementaryPresent() )\n\t\t\t\t{\n\t\t\t\t\t\tif (aTitle&&!strcmp(aTitle,\"tinyfd_query\")){strcpy(tinyfd_response,\"shellementary\");return (char *)1;}\n\t\t\t\t\t\tstrcpy( lDialogString , \"shellementary\" ) ;\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\t\tif (aTitle&&!strcmp(aTitle,\"tinyfd_query\")){strcpy(tinyfd_response,\"qarma\");return (char *)1;}\n\t\t\t\t\t\tstrcpy( lDialogString , \"qarma\" ) ;\n\t\t\t\t\t\t\t\t\t\t\t\tif ( !getenv(\"SSH_TTY\") && tfd_xpropPresent() )\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tstrcat(lDialogString, \" --attach=$(xprop -root 32x '\\t$0' _NET_ACTIVE_WINDOW | cut -f 2)\"); /* contribution: Paul Rouget */\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tstrcat(lDialogString, \" --file-selection --save --confirm-overwrite\" ) ;\n\n\t\t\t\tstrcat(lDialogString, \" --title=\\\"\") ;\n\t\t\t\tif (aTitle && strlen(aTitle)) strcat(lDialogString, aTitle) ;\n\t\t\t\tstrcat(lDialogString, \"\\\"\") ;\n\n\t\t\t\tif ( aDefaultPathAndOrFile && strlen(aDefaultPathAndOrFile) )\n\t\t\t\t{\n\t\t\t\t\t\tstrcat(lDialogString, \" --filename=\\\"\") ;\n\t\t\t\t\t\tstrcat(lDialogString, aDefaultPathAndOrFile) ;\n\t\t\t\t\t\tstrcat(lDialogString, \"\\\"\") ;\n\t\t\t\t}\n\t\t\t\tif ( aNumOfFilterPatterns > 0 )\n\t\t\t\t{\n\t\t\t\t\t\tstrcat( lDialogString , \" --file-filter='\" ) ;\n\t\t\t\t\t\tif ( aSingleFilterDescription && strlen(aSingleFilterDescription) )\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tstrcat( lDialogString , aSingleFilterDescription ) ;\n\t\t\t\t\t\t\t\tstrcat( lDialogString , \" |\" ) ;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tfor ( i = 0 ; i < aNumOfFilterPatterns ; i ++ )\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tstrcat( lDialogString , \" \" ) ;\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tstrcat( lDialogString , aFilterPatterns[i] ) ;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tstrcat( lDialogString , \"' --file-filter='All files | *'\" ) ;\n\t\t\t\t}\n\t\t\t\tif (tinyfd_silent) strcat( lDialogString , \" 2>/dev/null \");\n\t\t}\n\t\telse if (tfd_yadPresent())\n\t\t{\n\t\t   if (aTitle && !strcmp(aTitle, \"tinyfd_query\")) { strcpy(tinyfd_response, \"yad\"); return (char*)1; }\n\t\t   strcpy(lDialogString, \"yad --file --save --confirm-overwrite\");\n\t\t   if (aTitle && strlen(aTitle))\n\t\t   {\n\t\t\t  strcat(lDialogString, \" --title=\\\"\");\n\t\t\t  strcat(lDialogString, aTitle);\n\t\t\t  strcat(lDialogString, \"\\\"\");\n\t\t   }\n\t\t   if (aDefaultPathAndOrFile && strlen(aDefaultPathAndOrFile))\n\t\t   {\n\t\t\t  strcat(lDialogString, \" --filename=\\\"\");\n\t\t\t  strcat(lDialogString, aDefaultPathAndOrFile);\n\t\t\t  strcat(lDialogString, \"\\\"\");\n\t\t   }\n\t\t   if (aNumOfFilterPatterns > 0)\n\t\t   {\n\t\t\t  strcat(lDialogString, \" --file-filter='\");\n\t\t\t  if (aSingleFilterDescription && strlen(aSingleFilterDescription))\n\t\t\t  {\n\t\t\t\t strcat(lDialogString, aSingleFilterDescription);\n\t\t\t\t strcat(lDialogString, \" |\");\n\t\t\t  }\n\t\t\t  for (i = 0; i < aNumOfFilterPatterns; i++)\n\t\t\t  {\n\t\t\t\t strcat(lDialogString, \" \");\n\t\t\t\t strcat(lDialogString, aFilterPatterns[i]);\n\t\t\t  }\n\t\t\t  strcat(lDialogString, \"' --file-filter='All files | *'\");\n\t\t   }\n\t\t   if (tinyfd_silent) strcat(lDialogString, \" 2>/dev/null \");\n\t  }\n\t  else if ( !xdialogPresent() && tkinter3Present( ) )\n\t\t\t\t{\n\t\t\t\t\t\tif (aTitle&&!strcmp(aTitle,\"tinyfd_query\")){strcpy(tinyfd_response,\"python3-tkinter\");return (char *)1;}\n\t\t\t\t\t\tstrcpy( lDialogString , gPython3Name ) ;\n\t\t\t\t\t\tstrcat( lDialogString ,\n\t\t\t\t\t\t\t\t\" -S -c \\\"import tkinter;from tkinter import filedialog;root=tkinter.Tk();root.withdraw();\");\n\t\t\t\t\t\tstrcat( lDialogString , \"res=filedialog.asksaveasfilename(\");\n\t\t\t\t\t\tif ( aTitle && strlen(aTitle) )\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tstrcat(lDialogString, \"title='\") ;\n\t\t\t\t\t\t\t\tstrcat(lDialogString, aTitle) ;\n\t\t\t\t\t\t\t\tstrcat(lDialogString, \"',\") ;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif ( aDefaultPathAndOrFile && strlen(aDefaultPathAndOrFile) )\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tgetPathWithoutFinalSlash( lString , aDefaultPathAndOrFile ) ;\n\t\t\t\t\t\t\t\tif ( strlen(lString) )\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tstrcat(lDialogString, \"initialdir='\") ;\n\t\t\t\t\t\t\t\t\t\tstrcat(lDialogString, lString ) ;\n\t\t\t\t\t\t\t\t\t\tstrcat(lDialogString , \"',\" ) ;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tgetLastName( lString , aDefaultPathAndOrFile ) ;\n\t\t\t\t\t\t\t\tif ( strlen(lString) )\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tstrcat(lDialogString, \"initialfile='\") ;\n\t\t\t\t\t\t\t\t\t\tstrcat(lDialogString, lString ) ;\n\t\t\t\t\t\t\t\t\t\tstrcat(lDialogString , \"',\" ) ;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif ( ( aNumOfFilterPatterns > 1 )\n\t\t\t\t\t\t\t\t|| ( (aNumOfFilterPatterns == 1) /* test because poor osx behaviour */\n\t\t\t\t\t\t\t\t&& ( aFilterPatterns[0][strlen(aFilterPatterns[0])-1] != '*' ) ) )\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tstrcat(lDialogString , \"filetypes=(\" ) ;\n\t\t\t\t\t\t\t\tstrcat( lDialogString , \"('\" ) ;\n\t\t\t\t\t\t\t\tif ( aSingleFilterDescription && strlen(aSingleFilterDescription) )\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tstrcat( lDialogString , aSingleFilterDescription ) ;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tstrcat( lDialogString , \"',(\" ) ;\n\t\t\t\t\t\t\t\tfor ( i = 0 ; i < aNumOfFilterPatterns ; i ++ )\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tstrcat( lDialogString , \"'\" ) ;\n\t\t\t\t\t\t\t\t\t\tstrcat( lDialogString , aFilterPatterns[i] ) ;\n\t\t\t\t\t\t\t\t\t\tstrcat( lDialogString , \"',\" ) ;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tstrcat( lDialogString , \")),\" ) ;\n\t\t\t\t\t\t\t\tstrcat( lDialogString , \"('All files','*'))\" ) ;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tstrcat( lDialogString, \");\\nif not isinstance(res, tuple):\\n\\tprint(res)\\n\\\"\" ) ;\n\t\t\t\t}\n\t\t\t\telse if ( !xdialogPresent() && tkinter2Present( ) )\n\t\t{\n\t\t\t\tif (aTitle&&!strcmp(aTitle,\"tinyfd_query\")){strcpy(tinyfd_response,\"python2-tkinter\");return (char *)1;}\n\t\t\t\t\t\t\t\tstrcpy( lDialogString , \"export PYTHONIOENCODING=utf-8;\" ) ;\n\t\t\t\t\t\t\t\tstrcat( lDialogString , gPython2Name ) ;\n\t\t\t\t\t\t\t\tif ( ! isTerminalRunning( ) && tfd_isDarwin( ))\n\t\t\t\t{\n\t\t\t\tstrcat( lDialogString , \" -i\" ) ;  /* for osx without console */\n\t\t\t\t}\n\t\t\tstrcat( lDialogString ,\n\" -S -c \\\"import Tkinter,tkFileDialog;root=Tkinter.Tk();root.withdraw();\");\n\n\t\tif ( tfd_isDarwin( ) )\n\t\t{\n                strcat( lDialogString ,\n\"import os;os.system('''/usr/bin/osascript -e 'tell app \\\\\\\"Finder\\\\\\\" to set\\\n frontmost of process \\\\\\\"Python\\\\\\\" to true' ''');\");\n\t\t\t\t}\n\n\t\t\t\tstrcat( lDialogString , \"res=tkFileDialog.asksaveasfilename(\");\n\t\t\t\tif ( aTitle && strlen(aTitle) )\n\t\t\t\t{\n\t\t\t\t\t\tstrcat(lDialogString, \"title='\") ;\n\t\t\t\t\t\tstrcat(lDialogString, aTitle) ;\n\t\t\t\t\t\tstrcat(lDialogString, \"',\") ;\n\t\t\t\t}\n\t\t\tif ( aDefaultPathAndOrFile && strlen(aDefaultPathAndOrFile) )\n\t\t\t{\n\t\t\t\t\t\tgetPathWithoutFinalSlash( lString , aDefaultPathAndOrFile ) ;\n\t\t\t\t\t\tif ( strlen(lString) )\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tstrcat(lDialogString, \"initialdir='\") ;\n\t\t\t\t\t\t\t\tstrcat(lDialogString, lString ) ;\n\t\t\t\t\t\t\t\tstrcat(lDialogString , \"',\" ) ;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tgetLastName( lString , aDefaultPathAndOrFile ) ;\n\t\t\t\t\t\tif ( strlen(lString) )\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tstrcat(lDialogString, \"initialfile='\") ;\n\t\t\t\t\t\t\t\tstrcat(lDialogString, lString ) ;\n\t\t\t\t\t\t\t\tstrcat(lDialogString , \"',\" ) ;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\tif ( ( aNumOfFilterPatterns > 1 )\n\t\t\t\t  || ( (aNumOfFilterPatterns == 1) /* test because poor osx behaviour */\n\t\t\t\t\t\t&& ( aFilterPatterns[0][strlen(aFilterPatterns[0])-1] != '*' ) ) )\n\t\t\t{\n\t\t\t\t\t\tstrcat(lDialogString , \"filetypes=(\" ) ;\n\t\t\t\t\t\tstrcat( lDialogString , \"('\" ) ;\n\t\t\t\t\t\tif ( aSingleFilterDescription && strlen(aSingleFilterDescription) )\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tstrcat( lDialogString , aSingleFilterDescription ) ;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tstrcat( lDialogString , \"',(\" ) ;\n\t\t\t\t\t\tfor ( i = 0 ; i < aNumOfFilterPatterns ; i ++ )\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tstrcat( lDialogString , \"'\" ) ;\n\t\t\t\t\t\t\t\tstrcat( lDialogString , aFilterPatterns[i] ) ;\n\t\t\t\t\t\t\t\tstrcat( lDialogString , \"',\" ) ;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tstrcat( lDialogString , \")),\" ) ;\n\t\t\t\t\t\tstrcat( lDialogString , \"('All files','*'))\" ) ;\n\t\t\t}\n\t\t\t\t\t\tstrcat( lDialogString, \");\\nif not isinstance(res, tuple):\\n\\tprint res \\n\\\"\" ) ;\n\t\t\t\t}\n\t\telse if ( xdialogPresent() || dialogName() )\n\t\t{\n\t\t\t\tif ( xdialogPresent( ) )\n\t\t\t\t{\n\t\t\t\t\t\tif (aTitle&&!strcmp(aTitle,\"tinyfd_query\")){strcpy(tinyfd_response,\"xdialog\");return (char *)1;}\n\t\t\t\t\t\tlWasGraphicDialog = 1 ;\n\t\t\t\t\t\tstrcpy( lDialogString , \"(Xdialog \" ) ;\n\t\t\t\t}\n\t\t\t\telse if ( isTerminalRunning( ) )\n\t\t\t\t{\n\t\t\t\t\t\tif (aTitle&&!strcmp(aTitle,\"tinyfd_query\")){strcpy(tinyfd_response,\"dialog\");return (char *)0;}\n\t\t\t\t\t\tstrcpy( lDialogString , \"(dialog \" ) ;\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\t\tif (aTitle&&!strcmp(aTitle,\"tinyfd_query\")){strcpy(tinyfd_response,\"dialog\");return (char *)0;}\n\t\t\t\t\t\tlWasXterm = 1 ;\n\t\t\t\t\t\tstrcpy( lDialogString , terminalName() ) ;\n\t\t\t\t\t\tstrcat( lDialogString , \"'(\" ) ;\n\t\t\t\t\t\tstrcat( lDialogString , dialogName() ) ;\n\t\t\t\t\t\tstrcat( lDialogString , \" \" ) ;\n\t\t\t\t}\n\n\t\t\t\tif ( aTitle && strlen(aTitle) )\n\t\t\t\t{\n\t\t\t\t\t\tstrcat(lDialogString, \"--title \\\"\") ;\n\t\t\t\t\t\tstrcat(lDialogString, aTitle) ;\n\t\t\t\t\t\tstrcat(lDialogString, \"\\\" \") ;\n\t\t\t\t}\n\n\t\t\t\tif ( !xdialogPresent() && !gdialogPresent() )\n\t\t\t\t{\n\t\t\t\t\t\tstrcat(lDialogString, \"--backtitle \\\"\") ;\n\t\t\t\t\t\tstrcat(lDialogString,\n\t\t\t\t\t\t\t\t\"tab: focus | /: populate | spacebar: fill text field | ok: TEXT FIELD ONLY\") ;\n\t\t\t\t\t\tstrcat(lDialogString, \"\\\" \") ;\n\t\t\t\t}\n\n\t\t\t\tstrcat( lDialogString , \"--fselect \\\"\" ) ;\n\t\t\t\tif ( aDefaultPathAndOrFile && strlen(aDefaultPathAndOrFile) )\n\t\t\t\t{\n\t\t\t\t\t\tif ( ! strchr(aDefaultPathAndOrFile, '/') )\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tstrcat(lDialogString, \"./\") ;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tstrcat(lDialogString, aDefaultPathAndOrFile) ;\n\t\t\t\t}\n\t\t\t\telse if ( ! isTerminalRunning( ) && !lWasGraphicDialog )\n\t\t\t\t{\n\t\t\t\t\t\tstrcat(lDialogString, getenv(\"HOME\")) ;\n\t\t\t\t\t\tstrcat(lDialogString, \"/\") ;\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\t\tstrcat(lDialogString, \"./\") ;\n\t\t\t\t}\n\n\t\t\t\tif ( lWasGraphicDialog )\n\t\t\t\t{\n\t\t\t\t\t\tstrcat(lDialogString, \"\\\" 0 60 ) 2>&1 \") ;\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\t\tstrcat(lDialogString, \"\\\" 0 60  >/dev/tty) \") ;\n\t\t\t\t\t\tif ( lWasXterm )\n\t\t\t\t\t\t{\n\t\t\t\t\t\t  strcat( lDialogString ,\n\t\t\t\t\t\t\t\t\"2>/tmp/tinyfd.txt';cat /tmp/tinyfd.txt;rm /tmp/tinyfd.txt\");\n\t\t\t\t\t\t}\n\t\t\t\t\t\telse\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tstrcat(lDialogString, \"2>&1 ; clear >/dev/tty\") ;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\telse\n\t\t{\n\t\t\t\tif (aTitle&&!strcmp(aTitle,\"tinyfd_query\")){return tinyfd_inputBox(aTitle,NULL,NULL);}\n\t\t\t\t\t\t\t\tstrcpy(lBuff, \"Save file in \");\n\t\t\t\t\t\t\t\tstrcat(lBuff, getCurDir());\n\t\t\t\t\t\t\t\tlPointerInputBox = tinyfd_inputBox(NULL, NULL, NULL); /* obtain a pointer on the current content of tinyfd_inputBox */\n\t\t\t\t\t\t\t\tif (lPointerInputBox) strcpy(lString, lPointerInputBox); /* preserve the current content of tinyfd_inputBox */\n\t\t\t\t\t\t\t\tp = tinyfd_inputBox(aTitle, lBuff, \"\");\n\t\t\t\t\t\t\t\tif (p) strcpy(lBuff, p); else lBuff[0] = '\\0';\n\t\t\t\t\t\t\t\tif (lPointerInputBox) strcpy(lPointerInputBox, lString); /* restore its previous content to tinyfd_inputBox */\n\t\t\t\t\t\t\t\tp = lBuff;\n\n\t\t\t\t\t\t\t\tgetPathWithoutFinalSlash( lString , p ) ;\n\t\t\t\tif ( strlen( lString ) && ! dirExists( lString ) )\n\t\t\t\t{\n\t\t\t\t\t\treturn NULL ;\n\t\t\t\t}\n\t\t\t\tgetLastName(lString,p);\n\t\t\t\tif ( ! strlen(lString) )\n\t\t\t\t{\n\t\t\t\t\t\treturn NULL;\n\t\t\t\t}\n\t\t\t\treturn p ;\n\t\t}\n\n\t\tif (tinyfd_verbose) printf( \"lDialogString: %s\\n\" , lDialogString ) ;\n\tif ( ! ( lIn = popen( lDialogString , \"r\" ) ) )\n\t{\n\t\treturn NULL ;\n\t}\n\twhile ( fgets( lBuff , sizeof( lBuff ) , lIn ) != NULL )\n\t{}\n\tpclose( lIn ) ;\n\tif ( strlen(lBuff) && lBuff[strlen( lBuff ) -1] == '\\n' )\n\t{\n\t\tlBuff[strlen( lBuff ) -1] = '\\0' ;\n\t}\n    /* printf( \"lBuff: %s\\n\" , lBuff ) ; */\n    if ( ! strlen(lBuff) )\n    {\n            return NULL;\n    }\n\n\tgetPathWithoutFinalSlash( lString , lBuff ) ;\n\tif ( strlen( lString ) && ! dirExists( lString ) )\n\t{\n\t\treturn NULL ;\n\t}\n\tstrcpy(lLastDirectory, lString) ;\n\n    getLastName(lString,lBuff);\n    if ( ! filenameValid(lString) )\n    {\n            return NULL;\n    }\n\treturn lBuff ;\n}\n\n\n/* in case of multiple files, the separator is | */\nchar * tinyfd_openFileDialog(\n    char const * aTitle , /* NULL or \"\" */\n    char const * aDefaultPathAndOrFile , /* NULL or \"\" , ends with / to set only a directory */\n\tint aNumOfFilterPatterns , /* 0 */\n    char const * const * aFilterPatterns , /* NULL or {\"*.jpg\",\"*.png\"} */\n\tchar const * aSingleFilterDescription , /* NULL or \"image files\" */\n\tint aAllowMultipleSelects ) /* 0 or 1 */\n{\n    static char * lBuff = NULL;\n    static char lLastDirectory[MAX_PATH_OR_CMD] = \"$PWD\" ;\n\n    char lDialogString[MAX_PATH_OR_CMD] ;\n    char lString[MAX_PATH_OR_CMD] ;\n    int i ;\n    FILE * lIn ;\n    char * p ;\n    char * lPointerInputBox ;\n    size_t lFullBuffLen ;\n    int lWasKdialog = 0 ;\n    int lWasGraphicDialog = 0 ;\n    int lWasXterm = 0 ;\n\n\t\tif ( ! aFilterPatterns ) aNumOfFilterPatterns = 0 ;\n\t\t\t\tif (tfd_quoteDetected(aTitle)) return tinyfd_openFileDialog(\"INVALID TITLE WITH QUOTES\", aDefaultPathAndOrFile, aNumOfFilterPatterns, aFilterPatterns, aSingleFilterDescription, aAllowMultipleSelects);\n\t\t\t\tif (tfd_quoteDetected(aDefaultPathAndOrFile)) return tinyfd_openFileDialog(aTitle, \"INVALID DEFAULT_PATH WITH QUOTES\", aNumOfFilterPatterns, aFilterPatterns, aSingleFilterDescription, aAllowMultipleSelects);\n\t\t\t\tif (tfd_quoteDetected(aSingleFilterDescription)) return tinyfd_openFileDialog(aTitle, aDefaultPathAndOrFile, aNumOfFilterPatterns, aFilterPatterns, \"INVALID FILTER_DESCRIPTION WITH QUOTES\", aAllowMultipleSelects);\n\t\t\t\tfor (i = 0; i < aNumOfFilterPatterns; i++)\n\t\t\t\t{\n\t\t\t\t\t\tif (tfd_quoteDetected(aFilterPatterns[i])) return tinyfd_openFileDialog(\"INVALID FILTER_PATTERN WITH QUOTES: use the GRAVE ACCENT \\\\x60 instead.\", aDefaultPathAndOrFile, 0, NULL, NULL, aAllowMultipleSelects);\n\t\t\t\t}\n\n\t\t\t\tfree(lBuff);\n\t\t\t\tif (aTitle&&!strcmp(aTitle,\"tinyfd_query\"))\n\t\t\t\t{\n\t\t\t\t\t\tlBuff = NULL;\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\t\tif (aAllowMultipleSelects)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tlFullBuffLen = MAX_MULTIPLE_FILES * MAX_PATH_OR_CMD + 1;\n\t\t\t\t\t\t\t\tlBuff = (char *) malloc(lFullBuffLen * sizeof(char));\n\t\t\t\t\t\t\t\tif (!lBuff)\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tlFullBuffLen = LOW_MULTIPLE_FILES * MAX_PATH_OR_CMD + 1;\n\t\t\t\t\t\t\t\t\t\tlBuff = (char *) malloc( lFullBuffLen * sizeof(char));\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\telse\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tlFullBuffLen = MAX_PATH_OR_CMD + 1;\n\t\t\t\t\t\t\t\tlBuff = (char *) malloc(lFullBuffLen * sizeof(char));\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (!lBuff) return NULL;\n\t\t\t\t\t\tlBuff[0]='\\0';\n\t\t\t\t}\n\n\t\tif ( osascriptPresent( ) )\n\t\t{\n\t\t\t\tif (aTitle&&!strcmp(aTitle,\"tinyfd_query\")){strcpy(tinyfd_response,\"applescript\");return (char *)1;}\n\t\t\t\tstrcpy( lDialogString , \"osascript \");\n\t\t\t\tif ( ! osx9orBetter() ) strcat( lDialogString , \" -e 'tell application \\\"System Events\\\"' -e 'Activate'\");\n\t\t\t\tstrcat( lDialogString , \" -e 'try' -e '\" );\n\tif ( ! aAllowMultipleSelects )\n\t{\n\n\n\t\t\t\t\t\tstrcat( lDialogString , \"POSIX path of ( \" );\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\t\tstrcat( lDialogString , \"set mylist to \" );\n\t\t\t\t}\n\t\t\t\tstrcat( lDialogString , \"choose file \" );\n\t\t\tif ( aTitle && strlen(aTitle) )\n\t\t\t{\n\t\t\t\t\t\tstrcat(lDialogString, \"with prompt \\\"\") ;\n\t\t\t\t\t\tstrcat(lDialogString, aTitle) ;\n\t\t\t\t\t\tstrcat(lDialogString, \"\\\" \") ;\n\t\t\t}\n\t\t\t\tgetPathWithoutFinalSlash( lString , aDefaultPathAndOrFile ) ;\n\t\t\t\tif ( strlen(lString) )\n\t\t\t\t{\n\t\t\t\t\t\tstrcat(lDialogString, \"default location \\\"\") ;\n\t\t\t\t\t\tstrcat(lDialogString, lString ) ;\n\t\t\t\t\t\tstrcat(lDialogString , \"\\\" \" ) ;\n\t\t\t\t}\n\t\t\t\tif ( aNumOfFilterPatterns > 0 )\n\t\t\t\t{\n\t\t\t\t\t\tstrcat(lDialogString , \"of type {\\\"\" );\n\t\t\t\t\t\tstrcat( lDialogString , aFilterPatterns[0] + 2 ) ;\n\t\t\t\t\t\tstrcat( lDialogString , \"\\\"\" ) ;\n\t\t\t\t\t\tfor ( i = 1 ; i < aNumOfFilterPatterns ; i ++ )\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tstrcat( lDialogString , \",\\\"\" ) ;\n\t\t\t\t\t\t\t\tstrcat( lDialogString , aFilterPatterns[i] + 2) ;\n\t\t\t\t\t\t\t\tstrcat( lDialogString , \"\\\"\" ) ;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tstrcat( lDialogString , \"} \" ) ;\n\t\t\t\t}\n\t\t\t\tif ( aAllowMultipleSelects )\n\t\t\t\t{\n\t\t\t\t\t\tstrcat( lDialogString , \"multiple selections allowed true ' \" ) ;\n\t\t\t\t\t\tstrcat( lDialogString ,\n\t\t\t\t\t\t\t\t\t\t\"-e 'set mystring to POSIX path of item 1 of mylist' \" );\n\t\t\t\t\t\tstrcat( lDialogString ,\n\t\t\t\t\t\t\t\t\t\t\"-e 'repeat with  i from 2 to the count of mylist' \" );\n\t\t\t\t\t\tstrcat( lDialogString , \"-e 'set mystring to mystring & \\\"|\\\"' \" );\n\t\t\t\t\t\tstrcat( lDialogString ,\n\t\t\t\t\t\t\"-e 'set mystring to mystring & POSIX path of item i of mylist' \" );\n\t\t\t\t\t\tstrcat( lDialogString , \"-e 'end repeat' \" );\n\t\t\t\t\t\tstrcat( lDialogString , \"-e 'mystring' \" );\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\t\tstrcat( lDialogString , \")' \" ) ;\n\t\t\t\t}\n\t\t\t\tstrcat(lDialogString, \"-e 'on error number -128' \" ) ;\n\t\t\t\tstrcat(lDialogString, \"-e 'end try'\") ;\n\t\t\t\tif ( ! osx9orBetter() ) strcat( lDialogString, \" -e 'end tell'\") ;\n\t\t}\n\t\telse if ( tfd_kdialogPresent() )\n\t\t{\n\t\t\t\tif (aTitle&&!strcmp(aTitle,\"tinyfd_query\")){strcpy(tinyfd_response,\"kdialog\");return (char *)1;}\n\t\t\t\tlWasKdialog = 1 ;\n\n\t\t\t\tstrcpy( lDialogString , \"kdialog\" ) ;\n\t\t\t\t\t\t\t\tif ( (tfd_kdialogPresent() == 2) && tfd_xpropPresent() )\n\t\t\t\t{\n\t\t\t\t\t\tstrcat(lDialogString, \" --attach=$(xprop -root 32x '\\t$0' _NET_ACTIVE_WINDOW | cut -f 2)\"); /* contribution: Paul Rouget */\n\t\t\t\t}\n\t\t\t\tstrcat( lDialogString , \" --getopenfilename \" ) ;\n\n\t\t\t\tif ( aDefaultPathAndOrFile && strlen(aDefaultPathAndOrFile) )\n\t\t\t\t{\n                    if ( aDefaultPathAndOrFile[0] != '/' )\n                    {\n                        strcat(lDialogString, lLastDirectory) ;\n                        strcat(lDialogString , \"/\" ) ;\n                    }\n                    strcat(lDialogString, \"\\\"\") ;\n                    strcat(lDialogString, aDefaultPathAndOrFile ) ;\n                    strcat(lDialogString , \"\\\"\" ) ;\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n                    strcat(lDialogString, lLastDirectory) ;\n                    strcat(lDialogString , \"/\" ) ;\n\t\t\t\t}\n\n\t\t\t\tif ( aNumOfFilterPatterns > 0 )\n\t\t\t\t{\n\t\t\t\t\t\tstrcat(lDialogString , \" \\\"\" ) ;\n\t\t\t\t\t\t\t\t\t\t\t\tstrcat( lDialogString , aFilterPatterns[0] ) ;\n\t\t\t\t\t\tfor ( i = 1 ; i < aNumOfFilterPatterns ; i ++ )\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tstrcat( lDialogString , \" \" ) ;\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tstrcat( lDialogString , aFilterPatterns[i] ) ;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif ( aSingleFilterDescription && strlen(aSingleFilterDescription) )\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tstrcat( lDialogString , \" | \" ) ;\n\t\t\t\t\t\t\t\tstrcat( lDialogString , aSingleFilterDescription ) ;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tstrcat( lDialogString , \"\\\"\" ) ;\n\t\t\t\t}\n\t\t\t\tif ( aAllowMultipleSelects )\n\t\t\t\t{\n\t\t\t\t\t\tstrcat( lDialogString , \" --multiple --separate-output\" ) ;\n\t\t\t\t}\n\t\t\t\tif ( aTitle && strlen(aTitle) )\n\t\t\t\t{\n\t\t\t\t\t\tstrcat(lDialogString, \" --title \\\"\") ;\n\t\t\t\t\t\tstrcat(lDialogString, aTitle) ;\n\t\t\t\t\t\tstrcat(lDialogString, \"\\\"\") ;\n\t\t\t\t}\n\t\t}\n\t\telse if ( tfd_zenityPresent() || tfd_matedialogPresent() || tfd_shellementaryPresent() || tfd_qarmaPresent() )\n\t\t{\n\t\t\t\tif ( tfd_zenityPresent() )\n\t\t\t\t{\n\t\t\t\t\t\tif (aTitle&&!strcmp(aTitle,\"tinyfd_query\")){strcpy(tinyfd_response,\"zenity\");return (char *)1;}\n\t\t\t\t\t\tstrcpy( lDialogString , \"zenity\" ) ;\n\t\t\t\t\t\t\t\t\t\t\t\tif ( (tfd_zenity3Present() >= 4) && !getenv(\"SSH_TTY\") && tfd_xpropPresent() )\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tstrcat( lDialogString, \" --attach=$(sleep .01;xprop -root 32x '\\t$0' _NET_ACTIVE_WINDOW | cut -f 2)\"); /* contribution: Paul Rouget */\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\telse if ( tfd_matedialogPresent() )\n\t\t\t\t{\n\t\t\t\t\t\tif (aTitle&&!strcmp(aTitle,\"tinyfd_query\")){strcpy(tinyfd_response,\"matedialog\");return (char *)1;}\n\t\t\t\t\t\tstrcpy( lDialogString , \"matedialog\" ) ;\n\t\t\t\t}\n\t\t\t\telse if ( tfd_shellementaryPresent() )\n\t\t\t\t{\n\t\t\t\t\t\tif (aTitle&&!strcmp(aTitle,\"tinyfd_query\")){strcpy(tinyfd_response,\"shellementary\");return (char *)1;}\n\t\t\t\t\t\tstrcpy( lDialogString , \"shellementary\" ) ;\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\t\tif (aTitle&&!strcmp(aTitle,\"tinyfd_query\")){strcpy(tinyfd_response,\"qarma\");return (char *)1;}\n\t\t\t\t\t\tstrcpy( lDialogString , \"qarma\" ) ;\n\t\t\t\t\t\t\t\t\t\t\t\tif ( !getenv(\"SSH_TTY\") && tfd_xpropPresent() )\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tstrcat(lDialogString, \" --attach=$(xprop -root 32x '\\t$0' _NET_ACTIVE_WINDOW | cut -f 2)\"); /* contribution: Paul Rouget */\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tstrcat( lDialogString , \" --file-selection\" ) ;\n\n\t\t\t\tif ( aAllowMultipleSelects )\n\t\t\t\t{\n\t\t\t\t\t\tstrcat( lDialogString , \" --multiple\" ) ;\n\t\t\t\t}\n\n\t\t\t\tstrcat(lDialogString, \" --title=\\\"\") ;\n\t\t\t\tif (aTitle && strlen(aTitle)) strcat(lDialogString, aTitle) ;\n\t\t\t\tstrcat(lDialogString, \"\\\"\") ;\n\n\t\t\t\tif ( aDefaultPathAndOrFile && strlen(aDefaultPathAndOrFile) )\n\t\t\t\t{\n\t\t\t\t\t\tstrcat(lDialogString, \" --filename=\\\"\") ;\n\t\t\t\t\t\tstrcat(lDialogString, aDefaultPathAndOrFile) ;\n\t\t\t\t\t\tstrcat(lDialogString, \"\\\"\") ;\n\t\t\t\t}\n\t\t\t\tif ( aNumOfFilterPatterns > 0 )\n\t\t\t\t{\n\t\t\t\t\t\tstrcat( lDialogString , \" --file-filter='\" ) ;\n\t\t\t\t\t\tif ( aSingleFilterDescription && strlen(aSingleFilterDescription) )\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tstrcat( lDialogString , aSingleFilterDescription ) ;\n\t\t\t\t\t\t\t\tstrcat( lDialogString , \" |\" ) ;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tfor ( i = 0 ; i < aNumOfFilterPatterns ; i ++ )\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tstrcat( lDialogString , \" \" ) ;\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tstrcat( lDialogString , aFilterPatterns[i] ) ;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tstrcat( lDialogString , \"' --file-filter='All files | *'\" ) ;\n\t\t\t\t}\n\t\t\t\tif (tinyfd_silent) strcat( lDialogString , \" 2>/dev/null \");\n\t\t}\n\t\telse if (tfd_yadPresent())\n\t\t{\n\t\t   if (aTitle && !strcmp(aTitle, \"tinyfd_query\")) { strcpy(tinyfd_response, \"yad\"); return (char*)1; }\n\t\t   strcpy(lDialogString, \"yad --file\");\n\t\t   if (aAllowMultipleSelects)\n\t\t   {\n\t\t\t  strcat(lDialogString, \" --multiple\");\n\t\t   }\n\t\t   if (aTitle && strlen(aTitle))\n\t\t   {\n\t\t\t  strcat(lDialogString, \" --title=\\\"\");\n\t\t\t  strcat(lDialogString, aTitle);\n\t\t\t  strcat(lDialogString, \"\\\"\");\n\t\t   }\n\t\t   if (aDefaultPathAndOrFile && strlen(aDefaultPathAndOrFile))\n\t\t   {\n\t\t\t  strcat(lDialogString, \" --filename=\\\"\");\n\t\t\t  strcat(lDialogString, aDefaultPathAndOrFile);\n\t\t\t  strcat(lDialogString, \"\\\"\");\n\t\t   }\n\t\t   if (aNumOfFilterPatterns > 0)\n\t\t   {\n\t\t\t  strcat(lDialogString, \" --file-filter='\");\n\t\t\t  if (aSingleFilterDescription && strlen(aSingleFilterDescription))\n\t\t\t  {\n\t\t\t\t strcat(lDialogString, aSingleFilterDescription);\n\t\t\t\t strcat(lDialogString, \" |\");\n\t\t\t  }\n\t\t\t  for (i = 0; i < aNumOfFilterPatterns; i++)\n\t\t\t  {\n\t\t\t\t strcat(lDialogString, \" \");\n\t\t\t\t strcat(lDialogString, aFilterPatterns[i]);\n\t\t\t  }\n\t\t\t  strcat(lDialogString, \"' --file-filter='All files | *'\");\n\t\t   }\n\t\t   if (tinyfd_silent) strcat(lDialogString, \" 2>/dev/null \");\n\t  }\n\t  else if ( tkinter3Present( ) )\n\t\t\t\t{\n\t\t\t\t\t\tif (aTitle&&!strcmp(aTitle,\"tinyfd_query\")){strcpy(tinyfd_response,\"python3-tkinter\");return (char *)1;}\n\t\t\t\t\t\tstrcpy( lDialogString , gPython3Name ) ;\n\t\t\t\t\t\tstrcat( lDialogString ,\n\t\t\t\t\t\t\t\t\" -S -c \\\"import tkinter;from tkinter import filedialog;root=tkinter.Tk();root.withdraw();\");\n\t\t\t\t\t\tstrcat( lDialogString , \"lFiles=filedialog.askopenfilename(\");\n\t\t\t\t\t\tif ( aAllowMultipleSelects )\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tstrcat( lDialogString , \"multiple=1,\" ) ;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif ( aTitle && strlen(aTitle) )\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tstrcat(lDialogString, \"title='\") ;\n\t\t\t\t\t\t\t\tstrcat(lDialogString, aTitle) ;\n\t\t\t\t\t\t\t\tstrcat(lDialogString, \"',\") ;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif ( aDefaultPathAndOrFile && strlen(aDefaultPathAndOrFile) )\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tgetPathWithoutFinalSlash( lString , aDefaultPathAndOrFile ) ;\n\t\t\t\t\t\t\t\tif ( strlen(lString) )\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tstrcat(lDialogString, \"initialdir='\") ;\n\t\t\t\t\t\t\t\t\t\tstrcat(lDialogString, lString ) ;\n\t\t\t\t\t\t\t\t\t\tstrcat(lDialogString , \"',\" ) ;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tgetLastName( lString , aDefaultPathAndOrFile ) ;\n\t\t\t\t\t\t\t\tif ( strlen(lString) )\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tstrcat(lDialogString, \"initialfile='\") ;\n\t\t\t\t\t\t\t\t\t\tstrcat(lDialogString, lString ) ;\n\t\t\t\t\t\t\t\t\t\tstrcat(lDialogString , \"',\" ) ;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif ( ( aNumOfFilterPatterns > 1 )\n\t\t\t\t\t\t\t\t|| ( ( aNumOfFilterPatterns == 1 ) /*test because poor osx behaviour*/\n\t\t\t\t\t\t\t\t&& ( aFilterPatterns[0][strlen(aFilterPatterns[0])-1] != '*' ) ) )\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tstrcat(lDialogString , \"filetypes=(\" ) ;\n\t\t\t\t\t\t\t\tstrcat( lDialogString , \"('\" ) ;\n\t\t\t\t\t\t\t\tif ( aSingleFilterDescription && strlen(aSingleFilterDescription) )\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tstrcat( lDialogString , aSingleFilterDescription ) ;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tstrcat( lDialogString , \"',(\" ) ;\n\t\t\t\t\t\t\t\tfor ( i = 0 ; i < aNumOfFilterPatterns ; i ++ )\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tstrcat( lDialogString , \"'\" ) ;\n\t\t\t\t\t\t\t\t\t\tstrcat( lDialogString , aFilterPatterns[i] ) ;\n\t\t\t\t\t\t\t\t\t\tstrcat( lDialogString , \"',\" ) ;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tstrcat( lDialogString , \")),\" ) ;\n\t\t\t\t\t\t\t\tstrcat( lDialogString , \"('All files','*'))\" ) ;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tstrcat( lDialogString , \");\\\n\\nif not isinstance(lFiles, tuple):\\n\\tprint(lFiles)\\nelse:\\\n\\n\\tlFilesString=''\\n\\tfor lFile in lFiles:\\n\\t\\tlFilesString+=str(lFile)+'|'\\\n\\n\\tprint(lFilesString[:-1])\\n\\\"\" ) ;\n\t\t\t\t}\n\t\t\t\telse if ( tkinter2Present( ) )\n\t\t{\n\t\t\t\tif (aTitle&&!strcmp(aTitle,\"tinyfd_query\")){strcpy(tinyfd_response,\"python2-tkinter\");return (char *)1;}\n\t\t\t\t\t\t\t\tstrcpy( lDialogString , \"export PYTHONIOENCODING=utf-8;\" ) ;\n\t\t\t\t\t\t\t\tstrcat( lDialogString , gPython2Name ) ;\n\t\t\t\t\t\t\t\tif ( ! isTerminalRunning( ) && tfd_isDarwin( ) )\n\t\t\t\t{\n\t\t\t\tstrcat( lDialogString , \" -i\" ) ;  /* for osx without console */\n\t\t\t\t}\n\t\t\t\tstrcat( lDialogString ,\n\" -S -c \\\"import Tkinter,tkFileDialog;root=Tkinter.Tk();root.withdraw();\");\n\n\t\tif ( tfd_isDarwin( ) )\n\t\t{\n\t\t\t\t\t\tstrcat( lDialogString ,\n\"import os;os.system('''/usr/bin/osascript -e 'tell app \\\\\\\"Finder\\\\\\\" to set \\\nfrontmost of process \\\\\\\"Python\\\\\\\" to true' ''');\");\n\t\t\t\t}\n\t\t\t\tstrcat( lDialogString , \"lFiles=tkFileDialog.askopenfilename(\");\n\tif ( aAllowMultipleSelects )\n\t{\n\t\t\t\t\t\tstrcat( lDialogString , \"multiple=1,\" ) ;\n\t}\n\tif ( aTitle && strlen(aTitle) )\n\t{\n\t\t\t\t\t\tstrcat(lDialogString, \"title='\") ;\n\t\t\t\t\t\tstrcat(lDialogString, aTitle) ;\n\t\t\t\t\t\tstrcat(lDialogString, \"',\") ;\n\t}\n\tif ( aDefaultPathAndOrFile && strlen(aDefaultPathAndOrFile) )\n\t{\n\t\t\t\t\t\tgetPathWithoutFinalSlash( lString , aDefaultPathAndOrFile ) ;\n\t\t\t\t\t\tif ( strlen(lString) )\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tstrcat(lDialogString, \"initialdir='\") ;\n\t\t\t\t\t\t\t\tstrcat(lDialogString, lString ) ;\n\t\t\t\t\t\t\t\tstrcat(lDialogString , \"',\" ) ;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tgetLastName( lString , aDefaultPathAndOrFile ) ;\n\t\t\t\t\t\tif ( strlen(lString) )\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tstrcat(lDialogString, \"initialfile='\") ;\n\t\t\t\t\t\t\t\tstrcat(lDialogString, lString ) ;\n\t\t\t\t\t\t\t\tstrcat(lDialogString , \"',\" ) ;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif ( ( aNumOfFilterPatterns > 1 )\n\t\t\t\t\t\t|| ( ( aNumOfFilterPatterns == 1 ) /*test because poor osx behaviour*/\n\t\t\t\t\t\t\t\t&& ( aFilterPatterns[0][strlen(aFilterPatterns[0])-1] != '*' ) ) )\n\t\t\t\t{\n\t\t\t\t\t\tstrcat(lDialogString , \"filetypes=(\" ) ;\n\t\t\t\t\t\tstrcat( lDialogString , \"('\" ) ;\n\t\t\t\t\t\tif ( aSingleFilterDescription && strlen(aSingleFilterDescription) )\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tstrcat( lDialogString , aSingleFilterDescription ) ;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tstrcat( lDialogString , \"',(\" ) ;\n\t\t\t\t\t\tfor ( i = 0 ; i < aNumOfFilterPatterns ; i ++ )\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tstrcat( lDialogString , \"'\" ) ;\n\t\t\t\t\t\t\t\tstrcat( lDialogString , aFilterPatterns[i] ) ;\n\t\t\t\t\t\t\t\tstrcat( lDialogString , \"',\" ) ;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tstrcat( lDialogString , \")),\" ) ;\n\t\t\t\t\t\tstrcat( lDialogString , \"('All files','*'))\" ) ;\n\t\t\t\t}\n\t\t\t\tstrcat( lDialogString , \");\\\n\\nif not isinstance(lFiles, tuple):\\n\\tprint lFiles\\nelse:\\\n\\n\\tlFilesString=''\\n\\tfor lFile in lFiles:\\n\\t\\tlFilesString+=str(lFile)+'|'\\\n\\n\\tprint lFilesString[:-1]\\n\\\"\" ) ;\n\t\t}\n\t\telse if ( xdialogPresent() || dialogName() )\n\t\t{\n\t\t\t\tif ( xdialogPresent( ) )\n\t\t\t\t{\n\t\t\t\t\t\tif (aTitle&&!strcmp(aTitle,\"tinyfd_query\")){strcpy(tinyfd_response,\"xdialog\");return (char *)1;}\n\t\t\t\t\t\tlWasGraphicDialog = 1 ;\n\t\t\t\t\t\tstrcpy( lDialogString , \"(Xdialog \" ) ;\n\t\t\t\t}\n\t\t\t\telse if ( isTerminalRunning( ) )\n\t\t\t\t{\n\t\t\t\t\t\tif (aTitle&&!strcmp(aTitle,\"tinyfd_query\")){strcpy(tinyfd_response,\"dialog\");return (char *)0;}\n\t\t\t\t\t\tstrcpy( lDialogString , \"(dialog \" ) ;\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\t\tif (aTitle&&!strcmp(aTitle,\"tinyfd_query\")){strcpy(tinyfd_response,\"dialog\");return (char *)0;}\n\t\t\t\t\t\tlWasXterm = 1 ;\n\t\t\t\t\t\tstrcpy( lDialogString , terminalName() ) ;\n\t\t\t\t\t\tstrcat( lDialogString , \"'(\" ) ;\n\t\t\t\t\t\tstrcat( lDialogString , dialogName() ) ;\n\t\t\t\t\t\tstrcat( lDialogString , \" \" ) ;\n\t\t\t\t}\n\n\t\t\t\tif ( aTitle && strlen(aTitle) )\n\t\t\t\t{\n\t\t\t\t\t\tstrcat(lDialogString, \"--title \\\"\") ;\n\t\t\t\t\t\tstrcat(lDialogString, aTitle) ;\n\t\t\t\t\t\tstrcat(lDialogString, \"\\\" \") ;\n\t\t\t\t}\n\n\t\t\t\tif ( !xdialogPresent() && !gdialogPresent() )\n\t\t\t\t{\n\t\t\t\t\t\tstrcat(lDialogString, \"--backtitle \\\"\") ;\n\t\t\t\t\t\tstrcat(lDialogString,\n\t\t\t\t\t\t\t\t\"tab: focus | /: populate | spacebar: fill text field | ok: TEXT FIELD ONLY\") ;\n\t\t\t\t\t\tstrcat(lDialogString, \"\\\" \") ;\n\t\t\t\t}\n\n\t\t\t\tstrcat( lDialogString , \"--fselect \\\"\" ) ;\n\t\t\t\tif ( aDefaultPathAndOrFile && strlen(aDefaultPathAndOrFile) )\n\t\t\t\t{\n\t\t\t\t\t\tif ( ! strchr(aDefaultPathAndOrFile, '/') )\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tstrcat(lDialogString, \"./\") ;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tstrcat(lDialogString, aDefaultPathAndOrFile) ;\n\t\t\t\t}\n\t\t\t\telse if ( ! isTerminalRunning( ) && !lWasGraphicDialog )\n\t\t\t\t{\n\t\t\t\t\t\tstrcat(lDialogString, getenv(\"HOME\")) ;\n\t\t\t\t\t\tstrcat(lDialogString, \"/\");\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\t\tstrcat(lDialogString, \"./\") ;\n\t\t\t\t}\n\n\t\t\t\tif ( lWasGraphicDialog )\n\t\t\t\t{\n\t\t\t\t\t\tstrcat(lDialogString, \"\\\" 0 60 ) 2>&1 \") ;\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\t\tstrcat(lDialogString, \"\\\" 0 60  >/dev/tty) \") ;\n\t\t\t\t\t\tif ( lWasXterm )\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tstrcat( lDialogString ,\n\t\t\t\t\t\t\t\t\"2>/tmp/tinyfd.txt';cat /tmp/tinyfd.txt;rm /tmp/tinyfd.txt\");\n\t\t\t\t\t\t}\n\t\t\t\t\t\telse\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tstrcat(lDialogString, \"2>&1 ; clear >/dev/tty\") ;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\telse\n\t\t{\n\t\t\tif (aTitle&&!strcmp(aTitle,\"tinyfd_query\")){return tinyfd_inputBox(aTitle,NULL,NULL);}\n\t\t\t\t\t\t\t\tstrcpy(lBuff, \"Open file from \");\n\t\t\t\t\t\t\t\tstrcat(lBuff, getCurDir());\n\t\t\t\t\t\t\t\tlPointerInputBox = tinyfd_inputBox(NULL, NULL, NULL); /* obtain a pointer on the current content of tinyfd_inputBox */\n\t\t\t\t\t\t\t\tif (lPointerInputBox) strcpy(lDialogString, lPointerInputBox); /* preserve the current content of tinyfd_inputBox */\n\t\t\t\t\t\t\t\tp = tinyfd_inputBox(aTitle, lBuff, \"\");\n\t\t\t\t\t\t\t\tif ( p ) strcpy(lBuff, p); else lBuff[0] = '\\0';\n\t\t\t\t\t\t\t\tif (lPointerInputBox) strcpy(lPointerInputBox, lDialogString); /* restore its previous content to tinyfd_inputBox */\n\t\t\tif ( ! fileExists(lBuff) )\n\t\t\t{\n\t\t\t\t\t\t\t\t\t\tfree(lBuff);\n\t\t\t\t\t\t\t\t\t\tlBuff = NULL;\n\t\t\t}\n\t\t\t\t\t\t\t\telse\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\tlBuff = (char *)( realloc( lBuff, (strlen(lBuff)+1) * sizeof(char)));\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\treturn lBuff ;\n\t\t}\n\n\tif (tinyfd_verbose) printf( \"lDialogString: %s\\n\" , lDialogString ) ;\n\tif ( ! ( lIn = popen( lDialogString , \"r\" ) ) )\n\t{\n\t\t\t\tfree(lBuff);\n\t\t\t\tlBuff = NULL;\n\t\t\t\treturn NULL ;\n\t}\n\t\tlBuff[0]='\\0';\n\t\tp = lBuff;\n\t\twhile ( fgets( p , sizeof( lBuff ) , lIn ) != NULL )\n\t\t{\n\t\t\t\tp += strlen( p );\n\t\t}\n\tpclose( lIn ) ;\n\n\tif ( strlen( lBuff ) && lBuff[strlen( lBuff ) -1] == '\\n' )\n\t{\n\t\tlBuff[strlen( lBuff ) -1] = '\\0' ;\n\t}\n\t/* printf( \"strlen lBuff: %d\\n\" , strlen( lBuff ) ) ; */\n\t\tif ( lWasKdialog && aAllowMultipleSelects )\n\t\t{\n\t\t\t\tp = lBuff ;\n\t\t\t\twhile ( ( p = strchr( p , '\\n' ) ) )\n\t\t\t\t\t\t* p = '|' ;\n\t\t}\n\t\t/* printf( \"lBuff2: %s\\n\" , lBuff ) ; */\n\t\tif ( ! strlen( lBuff )  )\n\t\t{\n\t\t\t\t\t\tfree(lBuff);\n\t\t\t\t\t\tlBuff = NULL;\n\t\t\t\t\t\treturn NULL;\n\t\t}\n\t\tif ( aAllowMultipleSelects && strchr(lBuff, '|') )\n\t\t{\n            if( ! ensureFilesExist( lBuff , lBuff ) )\n            {\n                    free(lBuff);\n                    lBuff = NULL;\n                    return NULL;\n            }\n\t\t}\n\t\telse if ( !fileExists(lBuff) )\n\t\t{\n\t\t\t\t\t\tfree(lBuff);\n\t\t\t\t\t\tlBuff = NULL;\n\t\t\t\t\t\treturn NULL;\n        }\n\n        p = strrchr(lBuff, '|');\n        if ( !p ) p = lBuff ;\n        else p ++ ;\n        getPathWithoutFinalSlash( lString , p ) ;\n        /* printf( \"lString [%lu]: %s\\n\" , strlen(lString) , lString ) ; */\n        if ( strlen( lString ) && ! dirExists( lString ) )\n        {\n            return NULL ;\n        }\n        strcpy(lLastDirectory, lString) ;\n\n        lBuff = (char *)( realloc( lBuff, (strlen(lBuff)+1) * sizeof(char)));\n\n        /*printf( \"lBuff3 [%lu]: %s\\n\" , strlen(lBuff) , lBuff ) ; */\n        return lBuff ;\n}\n\n\nchar * tinyfd_selectFolderDialog(\n    char const * aTitle , /* \"\" */\n    char const * aDefaultPath ) /* \"\" */\n{\n    static char lBuff[MAX_PATH_OR_CMD] ;\n    static char lLastDirectory[MAX_PATH_OR_CMD] = \"$PWD\" ;\n\n    char lDialogString[MAX_PATH_OR_CMD] ;\n    FILE * lIn ;\n    char * p ;\n    char * lPointerInputBox ;\n    int lWasGraphicDialog = 0 ;\n    int lWasXterm = 0 ;\n    lBuff[0]='\\0';\n\n    if (tfd_quoteDetected(aTitle)) return tinyfd_selectFolderDialog(\"INVALID TITLE WITH QUOTES\", aDefaultPath);\n    if (tfd_quoteDetected(aDefaultPath)) return tinyfd_selectFolderDialog(aTitle, \"INVALID DEFAULT_PATH WITH QUOTES\");\n\n\t\tif ( osascriptPresent( ))\n\t\t{\n\t\t\t\tif (aTitle&&!strcmp(aTitle,\"tinyfd_query\")){strcpy(tinyfd_response,\"applescript\");return (char *)1;}\n\t\t\t\tstrcpy( lDialogString , \"osascript \");\n\t\t\t\tif ( ! osx9orBetter() ) strcat( lDialogString , \" -e 'tell application \\\"System Events\\\"' -e 'Activate'\");\n\t\t\t\tstrcat( lDialogString , \" -e 'try' -e 'POSIX path of ( choose folder \");\n\t\t\t\tif ( aTitle && strlen(aTitle) )\n\t\t\t\t{\n\t\t\t\tstrcat(lDialogString, \"with prompt \\\"\") ;\n\t\t\t\tstrcat(lDialogString, aTitle) ;\n\t\t\t\tstrcat(lDialogString, \"\\\" \") ;\n\t\t\t\t}\n\t\t\t\tif ( aDefaultPath && strlen(aDefaultPath) )\n\t\t\t\t{\n\t\t\t\t\t\tstrcat(lDialogString, \"default location \\\"\") ;\n\t\t\t\t\t\tstrcat(lDialogString, aDefaultPath ) ;\n\t\t\t\t\t\tstrcat(lDialogString , \"\\\" \" ) ;\n\t\t\t\t}\n\t\t\t\tstrcat( lDialogString , \")' \" ) ;\n\t\t\t\tstrcat(lDialogString, \"-e 'on error number -128' \" ) ;\n\t\t\t\tstrcat(lDialogString, \"-e 'end try'\") ;\n\t\t\t\tif ( ! osx9orBetter() ) strcat( lDialogString, \" -e 'end tell'\") ;\n\t\t}\n\t\telse if ( tfd_kdialogPresent() )\n\t\t{\n\t\t\t\tif (aTitle&&!strcmp(aTitle,\"tinyfd_query\")){strcpy(tinyfd_response,\"kdialog\");return (char *)1;}\n\t\t\t\tstrcpy( lDialogString , \"kdialog\" ) ;\n\t\t\t\t\t\t\t\tif ( (tfd_kdialogPresent() == 2) && tfd_xpropPresent() )\n\t\t\t\t{\n\t\t\t\t\t\tstrcat(lDialogString, \" --attach=$(xprop -root 32x '\\t$0' _NET_ACTIVE_WINDOW | cut -f 2)\"); /* contribution: Paul Rouget */\n\t\t\t\t}\n\t\t\t\tstrcat( lDialogString , \" --getexistingdirectory \" ) ;\n\n\t\t\t\tif ( aDefaultPath && strlen(aDefaultPath) )\n\t\t\t\t{\n\t\t\t\t\t\tif ( aDefaultPath[0] != '/' )\n\t\t\t\t\t\t{\n                            strcat(lDialogString, lLastDirectory) ;\n                            strcat(lDialogString , \"/\" ) ;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tstrcat(lDialogString, \"\\\"\") ;\n\t\t\t\t\t\tstrcat(lDialogString, aDefaultPath ) ;\n\t\t\t\t\t\tstrcat(lDialogString , \"\\\"\" ) ;\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n                        strcat(lDialogString, lLastDirectory) ;\n                        strcat(lDialogString , \"/\" ) ;\n\t\t\t\t}\n\n\t\t\t\tif ( aTitle && strlen(aTitle) )\n\t\t\t\t{\n\t\t\t\t\t\tstrcat(lDialogString, \" --title \\\"\") ;\n\t\t\t\t\t\tstrcat(lDialogString, aTitle) ;\n\t\t\t\t\t\tstrcat(lDialogString, \"\\\"\") ;\n\t\t\t\t}\n\t\t}\n\t\telse if ( tfd_zenityPresent() || tfd_matedialogPresent() || tfd_shellementaryPresent() || tfd_qarmaPresent() )\n\t\t{\n\t\t\t\tif ( tfd_zenityPresent() )\n\t\t\t\t{\n\t\t\t\t\t\tif (aTitle&&!strcmp(aTitle,\"tinyfd_query\")){strcpy(tinyfd_response,\"zenity\");return (char *)1;}\n\t\t\t\t\t\tstrcpy( lDialogString , \"zenity\" ) ;\n\t\t\t\t\t\t\t\t\t\t\t\tif ( (tfd_zenity3Present() >= 4) && !getenv(\"SSH_TTY\") && tfd_xpropPresent() )\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tstrcat( lDialogString, \" --attach=$(sleep .01;xprop -root 32x '\\t$0' _NET_ACTIVE_WINDOW | cut -f 2)\"); /* contribution: Paul Rouget */\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\telse if ( tfd_matedialogPresent() )\n\t\t\t\t{\n\t\t\t\t\t\tif (aTitle&&!strcmp(aTitle,\"tinyfd_query\")){strcpy(tinyfd_response,\"matedialog\");return (char *)1;}\n\t\t\t\t\t\tstrcpy( lDialogString , \"matedialog\" ) ;\n\t\t\t\t}\n\t\t\t\telse if ( tfd_shellementaryPresent() )\n\t\t\t\t{\n\t\t\t\t\t\tif (aTitle&&!strcmp(aTitle,\"tinyfd_query\")){strcpy(tinyfd_response,\"shellementary\");return (char *)1;}\n\t\t\t\t\t\tstrcpy( lDialogString , \"shellementary\" ) ;\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\t\tif (aTitle&&!strcmp(aTitle,\"tinyfd_query\")){strcpy(tinyfd_response,\"qarma\");return (char *)1;}\n\t\t\t\t\t\tstrcpy( lDialogString , \"qarma\" ) ;\n\t\t\t\t\t\t\t\t\t\t\t\tif ( !getenv(\"SSH_TTY\") && tfd_xpropPresent() )\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tstrcat(lDialogString, \" --attach=$(xprop -root 32x '\\t$0' _NET_ACTIVE_WINDOW | cut -f 2)\"); /* contribution: Paul Rouget */\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tstrcat( lDialogString , \" --file-selection --directory\" ) ;\n\n\t\t\t\tstrcat(lDialogString, \" --title=\\\"\") ;\n\t\t\t\tif (aTitle && strlen(aTitle)) strcat(lDialogString, aTitle) ;\n\t\t\t\tstrcat(lDialogString, \"\\\"\") ;\n\n\t\t\t\tif ( aDefaultPath && strlen(aDefaultPath) )\n\t\t\t\t{\n\t\t\t\t\t\tstrcat(lDialogString, \" --filename=\\\"\") ;\n\t\t\t\t\t\tstrcat(lDialogString, aDefaultPath) ;\n\t\t\t\t\t\tstrcat(lDialogString, \"\\\"\") ;\n\t\t\t\t}\n\t\t\t\tif (tinyfd_silent) strcat( lDialogString , \" 2>/dev/null \");\n\t\t}\n\t\telse if (tfd_yadPresent())\n\t\t{\n\t\t   if (aTitle && !strcmp(aTitle, \"tinyfd_query\")) { strcpy(tinyfd_response, \"yad\"); return (char*)1; }\n\t\t   strcpy(lDialogString, \"yad --file --directory\");\n\t\t   if (aTitle && strlen(aTitle))\n\t\t   {\n\t\t\t  strcat(lDialogString, \" --title=\\\"\");\n\t\t\t  strcat(lDialogString, aTitle);\n\t\t\t  strcat(lDialogString, \"\\\"\");\n\t\t   }\n\t\t   if (aDefaultPath && strlen(aDefaultPath))\n\t\t   {\n\t\t\t  strcat(lDialogString, \" --filename=\\\"\");\n\t\t\t  strcat(lDialogString, aDefaultPath);\n\t\t\t  strcat(lDialogString, \"\\\"\");\n\t\t   }\n\t\t   if (tinyfd_silent) strcat(lDialogString, \" 2>/dev/null \");\n\t  }\n\t  else if ( !xdialogPresent() && tkinter3Present( ) )\n\t\t\t\t{\n\t\t\t\t\t\tif (aTitle&&!strcmp(aTitle,\"tinyfd_query\")){strcpy(tinyfd_response,\"python3-tkinter\");return (char *)1;}\n\t\t\t\t\t\tstrcpy( lDialogString , gPython3Name ) ;\n\t\t\t\t\t\tstrcat( lDialogString ,\n\t\t\t\t\t\t\t\t\" -S -c \\\"import tkinter;from tkinter import filedialog;root=tkinter.Tk();root.withdraw();\");\n\t\t\t\t\t\tstrcat( lDialogString , \"res=filedialog.askdirectory(\");\n\t\t\t\t\t\tif ( aTitle && strlen(aTitle) )\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tstrcat(lDialogString, \"title='\") ;\n\t\t\t\t\t\t\t\tstrcat(lDialogString, aTitle) ;\n\t\t\t\t\t\t\t\tstrcat(lDialogString, \"',\") ;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif ( aDefaultPath && strlen(aDefaultPath) )\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tstrcat(lDialogString, \"initialdir='\") ;\n\t\t\t\t\t\t\t\tstrcat(lDialogString, aDefaultPath ) ;\n\t\t\t\t\t\t\t\tstrcat(lDialogString , \"'\" ) ;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tstrcat( lDialogString, \");\\nif not isinstance(res, tuple):\\n\\tprint(res)\\n\\\"\" ) ;\n\t\t\t\t}\n\t\t\t\telse if ( !xdialogPresent() && tkinter2Present( ) )\n\t\t{\n\t\t\t\tif (aTitle&&!strcmp(aTitle,\"tinyfd_query\")){strcpy(tinyfd_response,\"python2-tkinter\");return (char *)1;}\n\t\t\t\t\t\t\t\tstrcpy( lDialogString , \"export PYTHONIOENCODING=utf-8;\" ) ;\n\t\t\t\tstrcat( lDialogString , gPython2Name ) ;\n\t\t\t\tif ( ! isTerminalRunning( ) && tfd_isDarwin( ) )\n\t\t\t\t{\n\t\t\t\tstrcat( lDialogString , \" -i\" ) ;  /* for osx without console */\n\t\t\t\t}\n\t\tstrcat( lDialogString ,\n\" -S -c \\\"import Tkinter,tkFileDialog;root=Tkinter.Tk();root.withdraw();\");\n\n\t\tif ( tfd_isDarwin( ) )\n\t\t{\n\t\t\t\t\t\tstrcat( lDialogString ,\n\"import os;os.system('''/usr/bin/osascript -e 'tell app \\\\\\\"Finder\\\\\\\" to set \\\nfrontmost of process \\\\\\\"Python\\\\\\\" to true' ''');\");\n\t\t\t\t}\n\n\t\t\t\tstrcat( lDialogString , \"print tkFileDialog.askdirectory(\");\n\t\t\tif ( aTitle && strlen(aTitle) )\n\t\t\t{\n\t\t\t\t\t\tstrcat(lDialogString, \"title='\") ;\n\t\t\t\t\t\tstrcat(lDialogString, aTitle) ;\n\t\t\t\t\t\tstrcat(lDialogString, \"',\") ;\n\t\t\t}\n\t\tif ( aDefaultPath && strlen(aDefaultPath) )\n\t\t{\n\t\t\t\t\t\t\t\tstrcat(lDialogString, \"initialdir='\") ;\n\t\t\t\t\t\t\t\tstrcat(lDialogString, aDefaultPath ) ;\n\t\t\t\t\t\t\t\tstrcat(lDialogString , \"'\" ) ;\n\t\t\t\t}\n\t\t\t\tstrcat( lDialogString , \")\\\"\" ) ;\n\t\t}\n\t\telse if ( xdialogPresent() || dialogName() )\n\t\t{\n\t\t\t\tif ( xdialogPresent( ) )\n\t\t\t\t{\n\t\t\t\t\t\tif (aTitle&&!strcmp(aTitle,\"tinyfd_query\")){strcpy(tinyfd_response,\"xdialog\");return (char *)1;}\n\t\t\t\t\t\tlWasGraphicDialog = 1 ;\n\t\t\t\t\t\tstrcpy( lDialogString , \"(Xdialog \" ) ;\n\t\t\t\t}\n\t\t\t\telse if ( isTerminalRunning( ) )\n\t\t\t\t{\n\t\t\t\t\t\tif (aTitle&&!strcmp(aTitle,\"tinyfd_query\")){strcpy(tinyfd_response,\"dialog\");return (char *)0;}\n\t\t\t\t\t\tstrcpy( lDialogString , \"(dialog \" ) ;\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\t\tif (aTitle&&!strcmp(aTitle,\"tinyfd_query\")){strcpy(tinyfd_response,\"dialog\");return (char *)0;}\n\t\t\t\t\t\tlWasXterm = 1 ;\n\t\t\t\t\t\tstrcpy( lDialogString , terminalName() ) ;\n\t\t\t\t\t\tstrcat( lDialogString , \"'(\" ) ;\n\t\t\t\t\t\tstrcat( lDialogString , dialogName() ) ;\n\t\t\t\t\t\tstrcat( lDialogString , \" \" ) ;\n\t\t\t\t}\n\n\t\t\t\tif ( aTitle && strlen(aTitle) )\n\t\t\t\t{\n\t\t\t\t\t\tstrcat(lDialogString, \"--title \\\"\") ;\n\t\t\t\t\t\tstrcat(lDialogString, aTitle) ;\n\t\t\t\t\t\tstrcat(lDialogString, \"\\\" \") ;\n\t\t\t\t}\n\n\t\t\t\tif ( !xdialogPresent() && !gdialogPresent() )\n\t\t\t\t{\n\t\t\t\t\t\tstrcat(lDialogString, \"--backtitle \\\"\") ;\n\t\t\t\t\t\tstrcat(lDialogString,\n\t\t\t\t\t\t\t\t\"tab: focus | /: populate | spacebar: fill text field | ok: TEXT FIELD ONLY\") ;\n\t\t\t\t\t\tstrcat(lDialogString, \"\\\" \") ;\n\t\t\t\t}\n\n\t\t\t\tstrcat( lDialogString , \"--dselect \\\"\" ) ;\n\t\t\t\tif ( aDefaultPath && strlen(aDefaultPath) )\n\t\t\t\t{\n\t\t\t\t\t\tstrcat(lDialogString, aDefaultPath) ;\n\t\t\t\t\t\tensureFinalSlash(lDialogString);\n\t\t\t\t}\n\t\t\t\telse if ( ! isTerminalRunning( ) && !lWasGraphicDialog )\n\t\t\t\t{\n\t\t\t\t\t\tstrcat(lDialogString, getenv(\"HOME\")) ;\n\t\t\t\t\t\tstrcat(lDialogString, \"/\");\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\t\tstrcat(lDialogString, \"./\") ;\n\t\t\t\t}\n\n\t\t\t\tif ( lWasGraphicDialog )\n\t\t\t\t{\n\t\t\t\t\t\tstrcat(lDialogString, \"\\\" 0 60 ) 2>&1 \") ;\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\t\tstrcat(lDialogString, \"\\\" 0 60  >/dev/tty) \") ;\n\t\t\t\t\t\tif ( lWasXterm )\n\t\t\t\t\t\t{\n\t\t\t\t\t\t  strcat( lDialogString ,\n\t\t\t\t\t\t\t\t\"2>/tmp/tinyfd.txt';cat /tmp/tinyfd.txt;rm /tmp/tinyfd.txt\");\n\t\t\t\t\t\t}\n\t\t\t\t\t\telse\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tstrcat(lDialogString, \"2>&1 ; clear >/dev/tty\") ;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\t\telse\n\t\t{\n\t\t\t\tif (aTitle&&!strcmp(aTitle,\"tinyfd_query\")){return tinyfd_inputBox(aTitle,NULL,NULL);}\n\t\t\t\t\t\t\t\tstrcpy(lBuff, \"Select folder from \");\n\t\t\t\t\t\t\t\tstrcat(lBuff, getCurDir());\n\t\t\t\t\t\t\t\tlPointerInputBox = tinyfd_inputBox(NULL, NULL, NULL); /* obtain a pointer on the current content of tinyfd_inputBox */\n\t\t\t\t\t\t\t\tif (lPointerInputBox) strcpy(lDialogString, lPointerInputBox); /* preserve the current content of tinyfd_inputBox */\n\t\t\t\t\t\t\t\tp = tinyfd_inputBox(aTitle, lBuff, \"\");\n\t\t\t\t\t\t\t\tif (p) strcpy(lBuff, p); else lBuff[0] = '\\0';\n\t\t\t\t\t\t\t\tif (lPointerInputBox) strcpy(lPointerInputBox, lDialogString); /* restore its previous content to tinyfd_inputBox */\n\t\t\t\t\t\t\t\tp = lBuff;\n\n\t\t\t\tif ( !p || ! strlen( p ) || ! dirExists( p ) )\n\t\t\t\t{\n\t\t\t\t\t\treturn NULL ;\n\t\t\t\t}\n\t\t\t\treturn p ;\n\t\t}\n\tif (tinyfd_verbose) printf( \"lDialogString: %s\\n\" , lDialogString ) ;\n\tif ( ! ( lIn = popen( lDialogString , \"r\" ) ) )\n\t{\n\t\treturn NULL ;\n\t}\n    while ( fgets( lBuff , sizeof( lBuff ) , lIn ) != NULL )\n    {}\n    pclose( lIn ) ;\n\tif ( strlen( lBuff ) && lBuff[strlen( lBuff ) -1] == '\\n' )\n\t{\n\t\tlBuff[strlen( lBuff ) -1] = '\\0' ;\n\t}\n    /* printf( \"lBuff: %s\\n\" , lBuff ) ; */\n    if ( ! strlen( lBuff ) || ! dirExists( lBuff ) )\n    {\n            return NULL ;\n    }\n\n\tgetPathWithoutFinalSlash( lLastDirectory , lBuff ) ;\n\n    return lBuff ;\n}\n\n\n/* aDefaultRGB is used only if aDefaultHexRGB is absent */\n/* aDefaultRGB and aoResultRGB can be the same array */\n/* returns NULL on cancel */\n/* returns the hexcolor as a string \"#FF0000\" */\n/* aoResultRGB also contains the result */\nchar * tinyfd_colorChooser(\n\t\tchar const * aTitle , /* NULL or \"\" */\n\t\tchar const * aDefaultHexRGB , /* NULL or \"#FF0000\"*/\n\t\tunsigned char const aDefaultRGB[3] , /* { 0 , 255 , 255 } */\n\t\tunsigned char aoResultRGB[3] ) /* { 0 , 0 , 0 } */\n{\n\t\tstatic char lDefaultHexRGB[16];\n\t\tchar lBuff[128] ;\n\n\t\tchar lTmp[128] ;\n#if !((defined(__cplusplus ) && __cplusplus >= 201103L) || (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L) || defined(__clang__))\n\t\t\t\tchar * lTmp2 ;\n#endif\n\t\tchar lDialogString[MAX_PATH_OR_CMD] ;\n\t\tunsigned char lDefaultRGB[3];\n\t\tchar * p;\n\t\t\t\tchar * lPointerInputBox;\n\t\tFILE * lIn ;\n\t\tint i ;\n\t\tint lWasZenity3 = 0 ;\n\t\tint lWasOsascript = 0 ;\n\t\tint lWasXdialog = 0 ;\n\t\tlBuff[0]='\\0';\n\n\t\t\t\tif (tfd_quoteDetected(aTitle)) return tinyfd_colorChooser(\"INVALID TITLE WITH QUOTES\", aDefaultHexRGB, aDefaultRGB, aoResultRGB);\n\t\t\t\tif (tfd_quoteDetected(aDefaultHexRGB)) return tinyfd_colorChooser(aTitle, \"INVALID DEFAULT_HEX_RGB WITH QUOTES: use the GRAVE ACCENT \\\\x60 instead.\", aDefaultRGB, aoResultRGB);\n\n\t\t\t\tif (aDefaultHexRGB && (strlen(aDefaultHexRGB)==7) )\n\t\t\t\t{\n\t\t\t\t\t\tHex2RGB(aDefaultHexRGB, lDefaultRGB);\n\t\t\tstrcpy(lDefaultHexRGB, aDefaultHexRGB);\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\t\tlDefaultRGB[0] = aDefaultRGB[0];\n\t\t\t\t\t\tlDefaultRGB[1] = aDefaultRGB[1];\n\t\t\t\t\t\tlDefaultRGB[2] = aDefaultRGB[2];\n\t\t\tRGB2Hex(aDefaultRGB, lDefaultHexRGB);\n\t\t\t\t}\n\n\t\tif ( osascriptPresent( ) )\n\t\t{\n\t\t\t\tif (aTitle&&!strcmp(aTitle,\"tinyfd_query\")){strcpy(tinyfd_response,\"applescript\");return (char *)1;}\n\t\t\t\tlWasOsascript = 1 ;\n\t\t\t\tstrcpy( lDialogString , \"osascript\");\n\n\t\t\t\tif ( ! osx9orBetter() )\n\t\t\t\t{\n\t\t\t\t\t\tstrcat( lDialogString , \" -e 'tell application \\\"System Events\\\"' -e 'Activate'\");\n\t\t\t\t\t\tstrcat( lDialogString , \" -e 'try' -e 'set mycolor to choose color default color {\");\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\t\tstrcat( lDialogString ,\n\" -e 'try' -e 'tell app (path to frontmost application as Unicode text) \\\nto set mycolor to choose color default color {\");\n\t\t\t\t}\n\n\t\t\t\tsprintf(lTmp, \"%d\", 256 * lDefaultRGB[0] ) ;\n\t\t\t\tstrcat(lDialogString, lTmp ) ;\n\t\t\t\tstrcat(lDialogString, \",\" ) ;\n\t\t\t\tsprintf(lTmp, \"%d\", 256 * lDefaultRGB[1] ) ;\n\t\t\t\tstrcat(lDialogString, lTmp ) ;\n\t\t\t\tstrcat(lDialogString, \",\" ) ;\n\t\t\t\tsprintf(lTmp, \"%d\", 256 * lDefaultRGB[2] ) ;\n\t\t\t\tstrcat(lDialogString, lTmp ) ;\n\t\t\t\tstrcat(lDialogString, \"}' \" ) ;\n\t\t\t\tstrcat( lDialogString ,\n\"-e 'set mystring to ((item 1 of mycolor) div 256 as integer) as string' \" );\n\t\t\t\tstrcat( lDialogString ,\n\"-e 'repeat with i from 2 to the count of mycolor' \" );\n\t\t\t\tstrcat( lDialogString ,\n\"-e 'set mystring to mystring & \\\" \\\" & ((item i of mycolor) div 256 as integer) as string' \" );\n\t\t\t\tstrcat( lDialogString , \"-e 'end repeat' \" );\n\t\t\t\tstrcat( lDialogString , \"-e 'mystring' \");\n\t\t\t\tstrcat(lDialogString, \"-e 'on error number -128' \" ) ;\n\t\t\t\tstrcat(lDialogString, \"-e 'end try'\") ;\n\t\t\t\tif ( ! osx9orBetter() ) strcat( lDialogString, \" -e 'end tell'\") ;\n\t\t}\n\t\telse if ( tfd_kdialogPresent() )\n\t\t{\n\t\t\t\tif (aTitle&&!strcmp(aTitle,\"tinyfd_query\")){strcpy(tinyfd_response,\"kdialog\");return (char *)1;}\n\t\t\t\tstrcpy( lDialogString , \"kdialog\" ) ;\n\t\t\t\t\t\t\t\tif ( (tfd_kdialogPresent() == 2) && tfd_xpropPresent() )\n\t\t\t\t{\n\t\t\t\t\t\tstrcat(lDialogString, \" --attach=$(xprop -root 32x '\\t$0' _NET_ACTIVE_WINDOW | cut -f 2)\"); /* contribution: Paul Rouget */\n\t\t\t\t}\n\t\t\t\tsprintf( lDialogString + strlen(lDialogString) , \" --getcolor --default '%s'\" , lDefaultHexRGB ) ;\n\n\t\t\t\tif ( aTitle && strlen(aTitle) )\n\t\t\t\t{\n\t\t\t\t\t\tstrcat(lDialogString, \" --title \\\"\") ;\n\t\t\t\t\t\tstrcat(lDialogString, aTitle) ;\n\t\t\t\t\t\tstrcat(lDialogString, \"\\\"\") ;\n\t\t\t\t}\n\t\t}\n\t\telse if ( tfd_zenity3Present() || tfd_matedialogPresent() || tfd_shellementaryPresent() || tfd_qarmaPresent() )\n\t\t{\n\t\t\t\tlWasZenity3 = 1 ;\n\t\t\t\tif ( tfd_zenity3Present() )\n\t\t\t\t{\n\t\t\t\t\t\tif (aTitle&&!strcmp(aTitle,\"tinyfd_query\")){strcpy(tinyfd_response,\"zenity3\");return (char *)1;}\n\t\t\t\t\t\tstrcpy( lDialogString , \"zenity\" );\n\t\t\t\t\t\t\t\t\t\t\t\tif ( (tfd_zenity3Present() >= 4) && !getenv(\"SSH_TTY\") && tfd_xpropPresent() )\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tstrcat( lDialogString, \" --attach=$(sleep .01;xprop -root 32x '\\t$0' _NET_ACTIVE_WINDOW | cut -f 2)\"); /* contribution: Paul Rouget */\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\telse if ( tfd_matedialogPresent() )\n\t\t\t\t{\n\t\t\t\t\t\tif (aTitle&&!strcmp(aTitle,\"tinyfd_query\")){strcpy(tinyfd_response,\"matedialog\");return (char *)1;}\n\t\t\t\t\t\tstrcpy( lDialogString , \"matedialog\" ) ;\n\t\t\t\t}\n\t\t\t\telse if ( tfd_shellementaryPresent() )\n\t\t\t\t{\n\t\t\t\t\t\tif (aTitle&&!strcmp(aTitle,\"tinyfd_query\")){strcpy(tinyfd_response,\"shellementary\");return (char *)1;}\n\t\t\t\t\t\tstrcpy( lDialogString , \"shellementary\" ) ;\n\t\t\t\t}\n\t\t\t\telse\n\t\t\t\t{\n\t\t\t\t\t\tif (aTitle&&!strcmp(aTitle,\"tinyfd_query\")){strcpy(tinyfd_response,\"qarma\");return (char *)1;}\n\t\t\t\t\t\tstrcpy( lDialogString , \"qarma\" ) ;\n\t\t\t\t\t\t\t\t\t\t\t\tif ( !getenv(\"SSH_TTY\") && tfd_xpropPresent() )\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tstrcat(lDialogString, \" --attach=$(xprop -root 32x '\\t$0' _NET_ACTIVE_WINDOW | cut -f 2)\"); /* contribution: Paul Rouget */\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tstrcat( lDialogString , \" --color-selection --show-palette\" ) ;\n\t\t\t\tsprintf( lDialogString + strlen(lDialogString), \" --color=%s\" , lDefaultHexRGB ) ;\n\n\t\t\t\tstrcat(lDialogString, \" --title=\\\"\") ;\n\t\t\t\tif (aTitle && strlen(aTitle)) strcat(lDialogString, aTitle) ;\n\t\t\t\tstrcat(lDialogString, \"\\\"\") ;\n\n\t\t\t\tif (tinyfd_silent) strcat( lDialogString , \" 2>/dev/null \");\n\t\t}\n\t\telse if (tfd_yadPresent())\n\t\t{\n\t\t   if (aTitle && !strcmp(aTitle, \"tinyfd_query\")) { strcpy(tinyfd_response, \"yad\"); return (char*)1; }\n\t\t   strcpy(lDialogString, \"yad --color\");\n\t\t   sprintf(lDialogString + strlen(lDialogString), \" --init-color=%s\", lDefaultHexRGB);\n\t\t   if (aTitle && strlen(aTitle))\n\t\t   {\n\t\t\t  strcat(lDialogString, \" --title=\\\"\");\n\t\t\t  strcat(lDialogString, aTitle);\n\t\t\t  strcat(lDialogString, \"\\\"\");\n\t\t   }\n\t\t   if (tinyfd_silent) strcat(lDialogString, \" 2>/dev/null \");\n\t\t}\n\t\telse if ( xdialogPresent() )\n\t\t{\n\t\t\t\tif (aTitle&&!strcmp(aTitle,\"tinyfd_query\")){strcpy(tinyfd_response,\"xdialog\");return (char *)1;}\n\t\t\t\tlWasXdialog = 1 ;\n\t\t\t\tstrcpy( lDialogString , \"Xdialog --colorsel \\\"\" ) ;\n\t\t\t\tif ( aTitle && strlen(aTitle) )\n\t\t\t\t{\n\t\t\t\t\t\tstrcat(lDialogString, aTitle) ;\n\t\t\t\t}\n\t\t\t\tstrcat(lDialogString, \"\\\" 0 60 \") ;\n#if (defined(__cplusplus ) && __cplusplus >= 201103L) || (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L) || defined(__clang__)\n\t\t\t\t\t\t\t\tsprintf(lTmp,\"%hhu %hhu %hhu\",lDefaultRGB[0],lDefaultRGB[1],lDefaultRGB[2]);\n#else\n\t\t\t\tsprintf(lTmp,\"%hu %hu %hu\",lDefaultRGB[0],lDefaultRGB[1],lDefaultRGB[2]);\n#endif\n\t\t\t\tstrcat(lDialogString, lTmp) ;\n\t\t\t\tstrcat(lDialogString, \" 2>&1\");\n\t\t}\n\t\t\t\telse if ( tkinter3Present( ) )\n\t\t\t\t{\n\t\t\t\t\t\tif (aTitle&&!strcmp(aTitle,\"tinyfd_query\")){strcpy(tinyfd_response,\"python3-tkinter\");return (char *)1;}\n\t\t\t\t\t\tstrcpy( lDialogString , gPython3Name ) ;\n\t\t\t\t\t\tstrcat( lDialogString ,\n\t\t\t\t\t\t\t\t\" -S -c \\\"import tkinter;from tkinter import colorchooser;root=tkinter.Tk();root.withdraw();\");\n\t\t\t\t\t\tstrcat( lDialogString , \"res=colorchooser.askcolor(color='\" ) ;\n\t\t\t\t\t\tstrcat(lDialogString, lDefaultHexRGB ) ;\n\t\t\t\t\t\tstrcat(lDialogString, \"'\") ;\n\n\t\t\t\t\t\tif ( aTitle && strlen(aTitle) )\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tstrcat(lDialogString, \",title='\") ;\n\t\t\t\t\t\t\t\tstrcat(lDialogString, aTitle) ;\n\t\t\t\t\t\t\t\tstrcat(lDialogString, \"'\") ;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tstrcat( lDialogString , \");\\\n\\nif res[1] is not None:\\n\\tprint(res[1])\\\"\" ) ;\n\t\t\t\t}\n\t\t\t\telse if ( tkinter2Present( ) )\n\t\t{\n\t\t\t\tif (aTitle&&!strcmp(aTitle,\"tinyfd_query\")){strcpy(tinyfd_response,\"python2-tkinter\");return (char *)1;}\n\t\t\t\t\t\t\t\tstrcpy( lDialogString , \"export PYTHONIOENCODING=utf-8;\" ) ;\n\t\t\t\t\t\t\t\tstrcat( lDialogString , gPython2Name ) ;\n\t\t\t\t\t\t\t\tif ( ! isTerminalRunning( ) && tfd_isDarwin( ) )\n\t\t\t\t{\n\t\t\t\tstrcat( lDialogString , \" -i\" ) ;  /* for osx without console */\n\t\t\t\t}\n\n\t\t\t\tstrcat( lDialogString ,\n\" -S -c \\\"import Tkinter,tkColorChooser;root=Tkinter.Tk();root.withdraw();\");\n\n\t\t\t\tif ( tfd_isDarwin( ) )\n\t\t\t\t{\n\t\t\t\t\t\tstrcat( lDialogString ,\n\"import os;os.system('''osascript -e 'tell app \\\\\\\"Finder\\\\\\\" to set \\\nfrontmost of process \\\\\\\"Python\\\\\\\" to true' ''');\");\n\t\t\t\t}\n\n\t\t\t\tstrcat( lDialogString , \"res=tkColorChooser.askcolor(color='\" ) ;\n\t\t\t\tstrcat(lDialogString, lDefaultHexRGB ) ;\n\t\t\t\tstrcat(lDialogString, \"'\") ;\n\n\n\t\t\tif ( aTitle && strlen(aTitle) )\n\t\t\t{\n\t\t\t\t\t\tstrcat(lDialogString, \",title='\") ;\n\t\t\t\t\t\tstrcat(lDialogString, aTitle) ;\n\t\t\t\t\t\tstrcat(lDialogString, \"'\") ;\n\t\t\t}\n\t\t\t\tstrcat( lDialogString , \");\\\n\\nif res[1] is not None:\\n\\tprint res[1]\\\"\" ) ;\n\t\t}\n\t\telse\n\t\t{\n\t\t\t\tif (aTitle&&!strcmp(aTitle,\"tinyfd_query\")){return tinyfd_inputBox(aTitle,NULL,NULL);}\n\t\t\t\t\t\t\t\tlPointerInputBox = tinyfd_inputBox(NULL, NULL, NULL); /* obtain a pointer on the current content of tinyfd_inputBox */\n\t\t\t\t\t\t\t\tif (lPointerInputBox) strcpy(lDialogString, lPointerInputBox); /* preserve the current content of tinyfd_inputBox */\n\t\t\t\t\t\t\t\tp = tinyfd_inputBox(aTitle, \"Enter hex rgb color (i.e. #f5ca20)\", lDefaultHexRGB);\n\n\t\t\t\tif ( !p || (strlen(p) != 7) || (p[0] != '#') )\n\t\t\t\t{\n\t\t\t\t\t\treturn NULL ;\n\t\t\t\t}\n\t\t\t\tfor ( i = 1 ; i < 7 ; i ++ )\n\t\t\t\t{\n\t\t\t\t\t\tif ( ! isxdigit( (int) p[i] ) )\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\treturn NULL ;\n\t\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\t\t\t\t\tHex2RGB(p,aoResultRGB);\n\t\t\t\t\t\t\t\tstrcpy(lDefaultHexRGB, p);\n\t\t\t\t\t\t\t\tif (lPointerInputBox) strcpy(lPointerInputBox, lDialogString); /* restore its previous content to tinyfd_inputBox */\n\t\t\t\t\t\t\t\treturn lDefaultHexRGB;\n\t\t}\n\n\t\tif (tinyfd_verbose) printf( \"lDialogString: %s\\n\" , lDialogString ) ;\n\t\tif ( ! ( lIn = popen( lDialogString , \"r\" ) ) )\n\t\t{\n\t\t\t\treturn NULL ;\n\t}\n\t\twhile ( fgets( lBuff , sizeof( lBuff ) , lIn ) != NULL )\n\t\t{\n\t\t}\n\t\tpclose( lIn ) ;\n\tif ( ! strlen( lBuff ) )\n\t{\n\t\treturn NULL ;\n\t}\n\t\t/* printf( \"len Buff: %lu\\n\" , strlen(lBuff) ) ; */\n\t\t/* printf( \"lBuff0: %s\\n\" , lBuff ) ; */\n\tif ( lBuff[strlen( lBuff ) -1] == '\\n' )\n\t{\n\t\tlBuff[strlen( lBuff ) -1] = '\\0' ;\n\t}\n\n\t\tif ( lWasZenity3 )\n\t{\n\t\t\t\tif ( lBuff[0] == '#' )\n\t\t\t\t{\n\t\t\t\t\t\tif ( strlen(lBuff)>7 )\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tlBuff[3]=lBuff[5];\n\t\t\t\t\t\t\t\tlBuff[4]=lBuff[6];\n\t\t\t\t\t\t\t\tlBuff[5]=lBuff[9];\n\t\t\t\t\t\t\t\tlBuff[6]=lBuff[10];\n\t\t\t\t\t\t\t\tlBuff[7]='\\0';\n\t\t\t\t\t\t}\n\t\t\t\tHex2RGB(lBuff,aoResultRGB);\n\t\t\t\t}\n\t\t\t\telse if ( lBuff[3] == '(' ) {\n#if (defined(__cplusplus ) && __cplusplus >= 201103L) || (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L) || defined(__clang__)\n\tsscanf(lBuff,\"rgb(%hhu,%hhu,%hhu\", & aoResultRGB[0], & aoResultRGB[1],& aoResultRGB[2]);\n#else\n\taoResultRGB[0] = (unsigned char) strtol(lBuff+4, & lTmp2, 10 );\n\taoResultRGB[1] = (unsigned char) strtol(lTmp2+1, & lTmp2, 10 );\n\taoResultRGB[2] = (unsigned char) strtol(lTmp2+1, NULL, 10 );\n#endif\n\tRGB2Hex(aoResultRGB,lBuff);\n\t\t\t\t}\n\t\t\t\telse if ( lBuff[4] == '(' ) {\n#if (defined(__cplusplus ) && __cplusplus >= 201103L) || (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L) || defined(__clang__)\n\tsscanf(lBuff,\"rgba(%hhu,%hhu,%hhu\",  & aoResultRGB[0], & aoResultRGB[1],& aoResultRGB[2]);\n#else\n\taoResultRGB[0] = (unsigned char) strtol(lBuff+5, & lTmp2, 10 );\n\taoResultRGB[1] = (unsigned char) strtol(lTmp2+1, & lTmp2, 10 );\n\taoResultRGB[2] = (unsigned char) strtol(lTmp2+1, NULL, 10 );\n#endif\n\tRGB2Hex(aoResultRGB,lBuff);\n\t\t\t\t}\n\t}\n\telse if ( lWasOsascript || lWasXdialog )\n\t{\n\t\t\t\t/* printf( \"lBuff: %s\\n\" , lBuff ) ; */\n#if (defined(__cplusplus ) && __cplusplus >= 201103L) || (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L) || defined(__clang__)\n\tsscanf(lBuff,\"%hhu %hhu %hhu\", & aoResultRGB[0], & aoResultRGB[1],& aoResultRGB[2]);\n#else\n\taoResultRGB[0] = (unsigned char) strtol(lBuff, & lTmp2, 10 );\n\taoResultRGB[1] = (unsigned char) strtol(lTmp2+1, & lTmp2, 10 );\n\taoResultRGB[2] = (unsigned char) strtol(lTmp2+1, NULL, 10 );\n#endif\n\tRGB2Hex(aoResultRGB,lBuff);\n\t}\n\telse\n\t{\n\t\t\t\tHex2RGB(lBuff,aoResultRGB);\n\t}\n\t/* printf(\"%d %d %d\\n\", aoResultRGB[0],aoResultRGB[1],aoResultRGB[2]); */\n\t/* printf( \"lBuff: %s\\n\" , lBuff ) ; */\n\n\t\tstrcpy(lDefaultHexRGB,lBuff);\n\t\treturn lDefaultHexRGB ;\n}\n\n#endif /* _WIN32 */\n\n\n/* Modified prototypes for R */\n\nvoid tfd_messageBox(\n\tchar const * aTitle ,\n\tchar const * aMessage ,\n\tchar const * aDialogType ,\n\tchar const * aIconType ,\n\tint * aiDefaultButton )\n{\n\t* aiDefaultButton = tinyfd_messageBox( aTitle , aMessage , aDialogType , aIconType , * aiDefaultButton ) ;\n}\n\n\nvoid tfd_inputBox(\n\tchar const * aTitle ,\n\tchar const * aMessage ,\n\tchar * * aiDefaultInput )\n{\n\tchar * lReturnedInput ;\n\tif ( ! strcmp( * aiDefaultInput , \"NULL\") )  lReturnedInput = tinyfd_inputBox( aTitle , aMessage , NULL ) ;\n\telse lReturnedInput = tinyfd_inputBox( aTitle , aMessage , * aiDefaultInput ) ;\n\n\tif ( lReturnedInput ) strcpy ( * aiDefaultInput , lReturnedInput ) ;\n\telse strcpy ( * aiDefaultInput , \"NULL\" ) ;\n}\n\n\nvoid tfd_saveFileDialog(\n\tchar const * aTitle ,\n\tchar * * aiDefaultPathAndFile ,\n\tint const * aNumOfFilterPatterns ,\n\tchar const * const * aFilterPatterns ,\n\tchar const * aSingleFilterDescription )\n{\n\tchar * lSavefile ;\n\n\t/* printf( \"aFilterPatterns %s\\n\" , aFilterPatterns [0]); */\n\n\tlSavefile = tinyfd_saveFileDialog( aTitle , * aiDefaultPathAndFile , * aNumOfFilterPatterns ,\n\t\t\t\t\t\t\t\t\t\taFilterPatterns, aSingleFilterDescription ) ;\n\tif ( lSavefile ) strcpy ( * aiDefaultPathAndFile , lSavefile ) ;\n\telse strcpy ( * aiDefaultPathAndFile , \"NULL\" ) ;\n}\n\n\nvoid tfd_openFileDialog(\n\tchar const * aTitle ,\n\tchar * * aiDefaultPathAndFile ,\n\tint const * aNumOfFilterPatterns ,\n\tchar const * const * aFilterPatterns ,\n\tchar const * aSingleFilterDescription ,\n\tint const * aAllowMultipleSelects )\n{\n\tchar * lOpenfile ;\n\n\t/* printf( \"aFilterPatterns %s\\n\" , aFilterPatterns [0]); */\n\n\tlOpenfile = tinyfd_openFileDialog( aTitle , * aiDefaultPathAndFile , * aNumOfFilterPatterns ,\n\t\t\t\t\t\t\t\t\taFilterPatterns , aSingleFilterDescription , * aAllowMultipleSelects ) ;\n\n\tif ( lOpenfile ) strcpy ( * aiDefaultPathAndFile , lOpenfile ) ;\n\telse strcpy ( * aiDefaultPathAndFile , \"NULL\" ) ;\n}\n\n\nvoid tfd_selectFolderDialog(\n\tchar const * aTitle ,\n\tchar * * aiDefaultPath )\n{\n\tchar * lSelectedfolder ;\n\tlSelectedfolder = tinyfd_selectFolderDialog( aTitle, * aiDefaultPath ) ;\n\tif ( lSelectedfolder ) strcpy ( * aiDefaultPath , lSelectedfolder ) ;\n\telse strcpy ( * aiDefaultPath , \"NULL\" ) ;\n}\n\n\nvoid tfd_colorChooser(\n\tchar const * aTitle ,\n\tchar * * aiDefaultHexRGB )\n{\n\tunsigned char const aDefaultRGB [ 3 ] = {128,128,128} ;\n\tunsigned char aoResultRGB [ 3 ] =  {128,128,128} ;\n\tchar * lChosenColor ;\n\tlChosenColor = tinyfd_colorChooser( aTitle, * aiDefaultHexRGB, aDefaultRGB, aoResultRGB ) ;\n\tif ( lChosenColor ) strcpy ( * aiDefaultHexRGB , lChosenColor ) ;\n\telse strcpy ( * aiDefaultHexRGB , \"NULL\" ) ;\n}\n\n/* end of Modified prototypes for R */\n\n\n\n/*\nint main( int argc , char * argv[] )\n{\nchar const * lTmp;\nchar const * lTheSaveFileName;\nchar const * lTheOpenFileName;\nchar const * lTheSelectFolderName;\nchar const * lTheHexColor;\nchar const * lWillBeGraphicMode;\nunsigned char lRgbColor[3];\nFILE * lIn;\nchar lBuffer[1024];\nchar lString[1024];\nchar const * lFilterPatterns[2] = { \"*.txt\", \"*.text\" };\n\ntinyfd_verbose = argc - 1;\ntinyfd_silent = 1;\n\nlWillBeGraphicMode = tinyfd_inputBox(\"tinyfd_query\", NULL, NULL);\n\nstrcpy(lBuffer, \"v\");\nstrcat(lBuffer, tinyfd_version);\nif (lWillBeGraphicMode)\n{\n\tstrcat(lBuffer, \"\\ngraphic mode: \");\n}\nelse\n{\n\tstrcat(lBuffer, \"\\nconsole mode: \");\n}\nstrcat(lBuffer, tinyfd_response);\nstrcat(lBuffer, \"\\n\");\nstrcat(lBuffer, tinyfd_needs+78);\nstrcpy(lString, \"tinyfiledialogs\");\ntinyfd_messageBox(lString, lBuffer, \"ok\", \"info\", 0);\n\ntinyfd_notifyPopup(\"the title\", \"the message\\n\\tfrom outer-space\", \"info\");\n\nif (lWillBeGraphicMode && !tinyfd_forceConsole)\n{\n\t\ttinyfd_forceConsole = ! tinyfd_messageBox(\"Hello World\",\n\t\t\t\t\"graphic dialogs [yes] / console mode [no]?\",\n\t\t\t\t\"yesno\", \"question\", 1);\n}\n\nlTmp = tinyfd_inputBox(\n\t\t\"a password box\", \"your password will be revealed\", NULL);\n\nif (!lTmp) return 1;\n\nstrcpy(lString, lTmp);\n\nlTheSaveFileName = tinyfd_saveFileDialog(\n\t\t\"let us save this password\",\n\t\t\"passwordFile.txt\",\n\t\t2,\n\t\tlFilterPatterns,\n\t\tNULL);\n\nif (!lTheSaveFileName)\n{\n\t\ttinyfd_messageBox(\n\t\t\t\t\"Error\",\n\t\t\t\t\"Save file name is NULL\",\n\t\t\t\t\"ok\",\n\t\t\t\t\"error\",\n\t\t\t\t1);\n\t\treturn 1;\n}\n\nlIn = fopen(lTheSaveFileName, \"w\");\nif (!lIn)\n{\n\t\ttinyfd_messageBox(\n\t\t\t\t\"Error\",\n\t\t\t\t\"Can not open this file in write mode\",\n\t\t\t\t\"ok\",\n\t\t\t\t\"error\",\n\t\t\t\t1);\n\t\treturn 1;\n}\nfputs(lString, lIn);\nfclose(lIn);\n\nlTheOpenFileName = tinyfd_openFileDialog(\n\t\t\"let us read the password back\",\n\t\t\"\",\n\t\t2,\n\t\tlFilterPatterns,\n\t\tNULL,\n\t\t0);\n\nif (!lTheOpenFileName)\n{\n\t\ttinyfd_messageBox(\n\t\t\t\t\"Error\",\n\t\t\t\t\"Open file name is NULL\",\n\t\t\t\t\"ok\",\n\t\t\t\t\"error\",\n\t\t\t\t1);\n\t\treturn 1;\n}\n\nlIn = fopen(lTheOpenFileName, \"r\");\n\nif (!lIn)\n{\n\t\ttinyfd_messageBox(\n\t\t\t\t\"Error\",\n\t\t\t\t\"Can not open this file in read mode\",\n\t\t\t\t\"ok\",\n\t\t\t\t\"error\",\n\t\t\t\t1);\n\t\treturn(1);\n}\nlBuffer[0] = '\\0';\nfgets(lBuffer, sizeof(lBuffer), lIn);\nfclose(lIn);\n\ntinyfd_messageBox(\"your password is\",\n\t\tlBuffer, \"ok\", \"info\", 1);\n\nlTheSelectFolderName = tinyfd_selectFolderDialog(\n\t\t\"let us just select a directory\", NULL);\n\nif (!lTheSelectFolderName)\n{\n\t\ttinyfd_messageBox(\n\t\t\t\t\"Error\",\n\t\t\t\t\"Select folder name is NULL\",\n\t\t\t\t\"ok\",\n\t\t\t\t\"error\",\n\t\t\t\t1);\n\t\treturn 1;\n}\n\ntinyfd_messageBox(\"The selected folder is\",\n\t\tlTheSelectFolderName, \"ok\", \"info\", 1);\n\nlTheHexColor = tinyfd_colorChooser(\n\t\t\"choose a nice color\",\n\t\t\"#FF0077\",\n\t\tlRgbColor,\n\t\tlRgbColor);\n\nif (!lTheHexColor)\n{\n\t\ttinyfd_messageBox(\n\t\t\t\t\"Error\",\n\t\t\t\t\"hexcolor is NULL\",\n\t\t\t\t\"ok\",\n\t\t\t\t\"error\",\n\t\t\t\t1);\n\t\treturn 1;\n}\n\ntinyfd_messageBox(\"The selected hexcolor is\",\n\t\tlTheHexColor, \"ok\", \"info\", 1);\n\n\t\ttinyfd_beep();\n\n\t\treturn 0;\n}\n*/\n\n#ifdef _MSC_VER\n#pragma warning(default:4996)\n#pragma warning(default:4100)\n#pragma warning(default:4706)\n#endif\n"
  },
  {
    "path": "external/tinyfiledialogs.h",
    "content": "/* SPDX-License-Identifier: Zlib\nCopyright (c) 2014 - 2024 Guillaume Vareille http://ysengrin.com\n\t ____________________________________________________________________\n\t|                                                                    |\n\t| 100% compatible C C++  ->  You can rename tinfiledialogs.c as .cpp |\n\t|____________________________________________________________________|\n\n********* TINY FILE DIALOGS OFFICIAL WEBSITE IS ON SOURCEFORGE *********\n  _________\n /         \\ tinyfiledialogs.h v3.18.2 [Jun 8, 2024]\n |tiny file| Unique header file created [November 9, 2014]\n | dialogs |\n \\____  ___/ http://tinyfiledialogs.sourceforge.net\n      \\|     git clone http://git.code.sf.net/p/tinyfiledialogs/code tinyfd\n ____________________________________________\n|                                            |\n|   email: tinyfiledialogs at ysengrin.com   |\n|____________________________________________|\n ________________________________________________________________________________\n|  ____________________________________________________________________________  |\n| |                                                                            | |\n| |  - in tinyfiledialogs, char is UTF-8 by default (since v3.6)               | |\n| |                                                                            | |\n| | on windows:                                                                | |\n| |  - for UTF-16, use the wchar_t functions at the bottom of the header file  | |\n| |                                                                            | |\n| |  - _wfopen() requires wchar_t                                              | |\n| |  - fopen() uses char but expects ASCII or MBCS (not UTF-8)                 | |\n| |  - if you want char to be MBCS: set tinyfd_winUtf8 to 0                    | |\n| |                                                                            | |\n| |  - alternatively, tinyfiledialogs provides                                 | |\n| |                        functions to convert between UTF-8, UTF-16 and MBCS | |\n| |____________________________________________________________________________| |\n|________________________________________________________________________________|\n\nIf you like tinyfiledialogs, please upvote my stackoverflow answer\nhttps://stackoverflow.com/a/47651444\n\n- License -\nThis software is provided 'as-is', without any express or implied\nwarranty.  In no event will the authors be held liable for any damages\narising from the use of this software.\nPermission is granted to anyone to use this software for any purpose,\nincluding commercial applications, and to alter it and redistribute it\nfreely, subject to the following restrictions:\n1. The origin of this software must not be misrepresented; you must not\nclaim that you wrote the original software.  If you use this software\nin a product, an acknowledgment in the product documentation would be\nappreciated but is not required.\n2. Altered source versions must be plainly marked as such, and must not be\nmisrepresented as being the original software.\n3. This notice may not be removed or altered from any source distribution.\n\n     __________________________________________\n    |  ______________________________________  |\n    | |                                      | |\n    | | DO NOT USE USER INPUT IN THE DIALOGS | |\n    | |______________________________________| |\n    |__________________________________________|\n*/\n\n#ifndef TINYFILEDIALOGS_H\n#define TINYFILEDIALOGS_H\n\n#ifdef\t__cplusplus\nextern \"C\" {\n#endif\n\n/******************************************************************************************************/\n/**************************************** UTF-8 on Windows ********************************************/\n/******************************************************************************************************/\n#ifdef _WIN32\n/* On windows, if you want to use UTF-8 ( instead of the UTF-16/wchar_t functions at the end of this file )\nMake sure your code is really prepared for UTF-8 (on windows, functions like fopen() expect MBCS and not UTF-8) */\nextern int tinyfd_winUtf8; /* on windows char strings can be 1:UTF-8(default) or 0:MBCS */\n/* for MBCS change this to 0, in tinyfiledialogs.c or in your code */\n\n/* Here are some functions to help you convert between UTF-16 UTF-8 MBSC */\nchar * tinyfd_utf8toMbcs(char const * aUtf8string);\nchar * tinyfd_utf16toMbcs(wchar_t const * aUtf16string);\nwchar_t * tinyfd_mbcsTo16(char const * aMbcsString);\nchar * tinyfd_mbcsTo8(char const * aMbcsString);\nwchar_t * tinyfd_utf8to16(char const * aUtf8string);\nchar * tinyfd_utf16to8(wchar_t const * aUtf16string);\n#endif\n/******************************************************************************************************/\n/******************************************************************************************************/\n/******************************************************************************************************/\n\n/************* 3 funtions for C# (you don't need this in C or C++) : */\nchar const * tinyfd_getGlobalChar(char const * aCharVariableName); /* returns NULL on error */\nint tinyfd_getGlobalInt(char const * aIntVariableName); /* returns -1 on error */\nint tinyfd_setGlobalInt(char const * aIntVariableName, int aValue); /* returns -1 on error */\n/* aCharVariableName: \"tinyfd_version\" \"tinyfd_needs\" \"tinyfd_response\"\n   aIntVariableName : \"tinyfd_verbose\" \"tinyfd_silent\" \"tinyfd_allowCursesDialogs\"\n\t\t\t\t      \"tinyfd_forceConsole\" \"tinyfd_assumeGraphicDisplay\" \"tinyfd_winUtf8\"\n**************/\n\nextern char tinyfd_version[8]; /* contains tinyfd current version number */\nextern char tinyfd_needs[]; /* info about requirements */\nextern int tinyfd_verbose; /* 0 (default) or 1 : on unix, prints the command line calls */\nextern int tinyfd_silent; /* 1 (default) or 0 : on unix, hide errors and warnings from called dialogs */\n\n/** Curses dialogs are difficult to use and counter-intuitive.\nOn windows they are only ascii and still uses the unix backslash ! **/\nextern int tinyfd_allowCursesDialogs; /* 0 (default) or 1 */\n\nextern int tinyfd_forceConsole;  /* 0 (default) or 1 */\n/* for unix & windows: 0 (graphic mode) or 1 (console mode).\n0: try to use a graphic solution, if it fails then it uses console mode.\n1: forces all dialogs into console mode even when an X server is present.\n   if enabled, it can use the package Dialog or dialog.exe.\n   on windows it only make sense for console applications */\n\nextern int tinyfd_assumeGraphicDisplay; /* 0 (default) or 1  */\n/* some systems don't set the environment variable DISPLAY even when a graphic display is present.\nset this to 1 to tell tinyfiledialogs to assume the existence of a graphic display */\n\nextern char tinyfd_response[1024];\n/* if you pass \"tinyfd_query\" as aTitle,\nthe functions will not display the dialogs\nbut will return 0 for console mode, 1 for graphic mode.\ntinyfd_response is then filled with the retain solution.\npossible values for tinyfd_response are (all lowercase)\nfor graphic mode:\n  windows_wchar windows applescript kdialog zenity zenity3 yad matedialog\n  shellementary qarma python2-tkinter python3-tkinter python-dbus\n  perl-dbus gxmessage gmessage xmessage xdialog gdialog dunst\nfor console mode:\n  dialog whiptail basicinput no_solution */\n\nvoid tinyfd_beep(void);\n\nint tinyfd_notifyPopup(\n\tchar const * aTitle, /* NULL or \"\" */\n\tchar const * aMessage, /* NULL or \"\" may contain \\n \\t */\n\tchar const * aIconType); /* \"info\" \"warning\" \"error\" */\n\t\t/* return has only meaning for tinyfd_query */\n\nint tinyfd_messageBox(\n\tchar const * aTitle , /* NULL or \"\" */\n\tchar const * aMessage , /* NULL or \"\" may contain \\n \\t */\n\tchar const * aDialogType , /* \"ok\" \"okcancel\" \"yesno\" \"yesnocancel\" */\n\tchar const * aIconType , /* \"info\" \"warning\" \"error\" \"question\" */\n\tint aDefaultButton ) ;\n\t\t/* 0 for cancel/no , 1 for ok/yes , 2 for no in yesnocancel */\n\nchar * tinyfd_inputBox(\n\tchar const * aTitle , /* NULL or \"\" */\n\tchar const * aMessage , /* NULL or \"\" (\\n and \\t have no effect) */\n\tchar const * aDefaultInput ) ;  /* NULL = passwordBox, \"\" = inputbox */\n\t\t/* returns NULL on cancel */\n\nchar * tinyfd_saveFileDialog(\n\tchar const * aTitle , /* NULL or \"\" */\n\tchar const * aDefaultPathAndOrFile , /* NULL or \"\" , ends with / to set only a directory */\n\tint aNumOfFilterPatterns , /* 0  (1 in the following example) */\n\tchar const * const * aFilterPatterns , /* NULL or char const * lFilterPatterns[1]={\"*.txt\"} */\n\tchar const * aSingleFilterDescription ) ; /* NULL or \"text files\" */\n\t\t/* returns NULL on cancel */\n\nchar * tinyfd_openFileDialog(\n\tchar const * aTitle, /* NULL or \"\" */\n\tchar const * aDefaultPathAndOrFile, /* NULL or \"\" , ends with / to set only a directory */\n\tint aNumOfFilterPatterns , /* 0 (2 in the following example) */\n\tchar const * const * aFilterPatterns, /* NULL or char const * lFilterPatterns[2]={\"*.png\",\"*.jpg\"}; */\n\tchar const * aSingleFilterDescription, /* NULL or \"image files\" */\n\tint aAllowMultipleSelects ) ; /* 0 or 1 */\n\t\t/* in case of multiple files, the separator is | */\n\t\t/* returns NULL on cancel */\n\nchar * tinyfd_selectFolderDialog(\n\tchar const * aTitle, /* NULL or \"\" */\n\tchar const * aDefaultPath); /* NULL or \"\" */\n\t\t/* returns NULL on cancel */\n\nchar * tinyfd_colorChooser(\n\tchar const * aTitle, /* NULL or \"\" */\n\tchar const * aDefaultHexRGB, /* NULL or \"\" or \"#FF0000\" */\n\tunsigned char const aDefaultRGB[3] , /* unsigned char lDefaultRGB[3] = { 0 , 128 , 255 }; */\n\tunsigned char aoResultRGB[3] ) ; /* unsigned char lResultRGB[3]; */\n\t\t/* aDefaultRGB is used only if aDefaultHexRGB is absent */\n\t\t/* aDefaultRGB and aoResultRGB can be the same array */\n\t\t/* returns NULL on cancel */\n\t\t/* returns the hexcolor as a string \"#FF0000\" */\n\t\t/* aoResultRGB also contains the result */\n\n\n/************ WINDOWS ONLY SECTION ************************/\n#ifdef _WIN32\n\n/* windows only - utf-16 version */\nint tinyfd_notifyPopupW(\n\twchar_t const * aTitle, /* NULL or L\"\" */\n\twchar_t const * aMessage, /* NULL or L\"\" may contain \\n \\t */\n\twchar_t const * aIconType); /* L\"info\" L\"warning\" L\"error\" */\n\n/* windows only - utf-16 version */\nint tinyfd_messageBoxW(\n\twchar_t const * aTitle, /* NULL or L\"\" */\n\twchar_t const * aMessage, /* NULL or L\"\" may contain \\n \\t */\n\twchar_t const * aDialogType, /* L\"ok\" L\"okcancel\" L\"yesno\" */\n\twchar_t const * aIconType, /* L\"info\" L\"warning\" L\"error\" L\"question\" */\n\tint aDefaultButton ); /* 0 for cancel/no , 1 for ok/yes */\n\t\t/* returns 0 for cancel/no , 1 for ok/yes */\n\n/* windows only - utf-16 version */\nwchar_t * tinyfd_inputBoxW(\n\twchar_t const * aTitle, /* NULL or L\"\" */\n\twchar_t const * aMessage, /* NULL or L\"\" (\\n nor \\t not respected) */\n\twchar_t const * aDefaultInput); /* NULL passwordBox, L\"\" inputbox */\n\n/* windows only - utf-16 version */\nwchar_t * tinyfd_saveFileDialogW(\n\twchar_t const * aTitle, /* NULL or L\"\" */\n\twchar_t const * aDefaultPathAndOrFile, /* NULL or L\"\" , ends with / to set only a directory */\n\tint aNumOfFilterPatterns, /* 0 (1 in the following example) */\n\twchar_t const * const * aFilterPatterns, /* NULL or wchar_t const * lFilterPatterns[1]={L\"*.txt\"} */\n\twchar_t const * aSingleFilterDescription); /* NULL or L\"text files\" */\n\t\t/* returns NULL on cancel */\n\n/* windows only - utf-16 version */\nwchar_t * tinyfd_openFileDialogW(\n\twchar_t const * aTitle, /* NULL or L\"\" */\n\twchar_t const * aDefaultPathAndOrFile, /* NULL or L\"\" , ends with / to set only a directory */\n\tint aNumOfFilterPatterns , /* 0 (2 in the following example) */\n\twchar_t const * const * aFilterPatterns, /* NULL or wchar_t const * lFilterPatterns[2]={L\"*.png\",\"*.jpg\"} */\n\twchar_t const * aSingleFilterDescription, /* NULL or L\"image files\" */\n\tint aAllowMultipleSelects ) ; /* 0 or 1 */\n\t\t/* in case of multiple files, the separator is | */\n\t\t/* returns NULL on cancel */\n\n/* windows only - utf-16 version */\nwchar_t * tinyfd_selectFolderDialogW(\n\twchar_t const * aTitle, /* NULL or L\"\" */\n\twchar_t const * aDefaultPath); /* NULL or L\"\" */\n\t\t/* returns NULL on cancel */\n\n/* windows only - utf-16 version */\nwchar_t * tinyfd_colorChooserW(\n\twchar_t const * aTitle, /* NULL or L\"\" */\n\twchar_t const * aDefaultHexRGB, /* NULL or L\"#FF0000\" */\n\tunsigned char const aDefaultRGB[3], /* unsigned char lDefaultRGB[3] = { 0 , 128 , 255 }; */\n\tunsigned char aoResultRGB[3]); /* unsigned char lResultRGB[3]; */\n\t\t/* returns the hexcolor as a string L\"#FF0000\" */\n\t\t/* aoResultRGB also contains the result */\n\t\t/* aDefaultRGB is used only if aDefaultHexRGB is NULL */\n\t\t/* aDefaultRGB and aoResultRGB can be the same array */\n\t\t/* returns NULL on cancel */\n\n#endif /*_WIN32 */\n\n#ifdef\t__cplusplus\n} /*extern \"C\"*/\n#endif\n\n#endif /* TINYFILEDIALOGS_H */\n\n/*\n ________________________________________________________________________________\n|  ____________________________________________________________________________  |\n| |                                                                            | |\n| | on windows:                                                                | |\n| |  - for UTF-16, use the wchar_t functions at the bottom of the header file  | |\n| |  - _wfopen() requires wchar_t                                              | |\n| |                                                                            | |\n| |  - in tinyfiledialogs, char is UTF-8 by default (since v3.6)               | |\n| |  - but fopen() expects MBCS (not UTF-8)                                    | |\n| |  - if you want char to be MBCS: set tinyfd_winUtf8 to 0                    | |\n| |                                                                            | |\n| |  - alternatively, tinyfiledialogs provides                                 | |\n| |                        functions to convert between UTF-8, UTF-16 and MBCS | |\n| |____________________________________________________________________________| |\n|________________________________________________________________________________|\n\n- This is not for ios nor android (it works in termux though).\n- The files can be renamed with extension \".cpp\" as the code is 100% compatible C C++\n  (just comment out << extern \"C\" >> in the header file)\n- Windows is fully supported from XP to 10 (maybe even older versions)\n- C# & LUA via dll, see files in the folder EXTRAS\n- OSX supported from 10.4 to latest (maybe even older versions)\n- Do not use \" and ' as the dialogs will be displayed with a warning\n  instead of the title, message, etc...\n- There's one file filter only, it may contain several patterns.\n- If no filter description is provided,\n  the list of patterns will become the description.\n- On windows link against Comdlg32.lib and Ole32.lib\n  (on windows the no linking claim is a lie)\n- On unix: it tries command line calls, so no such need (NO LINKING).\n- On unix you need one of the following:\n  applescript, kdialog, zenity, matedialog, shellementary, qarma, yad,\n  python (2 or 3)/tkinter/python-dbus (optional), Xdialog\n  or curses dialogs (opens terminal if running without console).\n- One of those is already included on most (if not all) desktops.\n- In the absence of those it will use gdialog, gxmessage or whiptail\n  with a textinputbox. If nothing is found, it switches to basic console input,\n  it opens a console if needed (requires xterm + bash).\n- for curses dialogs you must set tinyfd_allowCursesDialogs=1\n- You can query the type of dialog that will be used (pass \"tinyfd_query\" as aTitle)\n- String memory is preallocated statically for all the returned values.\n- File and path names are tested before return, they should be valid.\n- tinyfd_forceConsole=1; at run time, forces dialogs into console mode.\n- On windows, console mode only make sense for console applications.\n- On windows, console mode is not implemented for wchar_T UTF-16.\n- Mutiple selects are not possible in console mode.\n- The package dialog must be installed to run in curses dialogs in console mode.\n  It is already installed on most unix systems.\n- On osx, the package dialog can be installed via\n  http://macappstore.org/dialog or http://macports.org\n- On windows, for curses dialogs console mode,\n  dialog.exe should be copied somewhere on your executable path.\n  It can be found at the bottom of the following page:\n  http://andrear.altervista.org/home/cdialog.php\n*/\n"
  },
  {
    "path": "scrap.desktop",
    "content": "[Desktop Entry]\nName=Scrap\nExec=AppRun\nIcon=scrap\nType=Application\nCategories=Development;\n"
  },
  {
    "path": "scrap.rc",
    "content": "scrap_icon ICON \"extras/icon.ico\"\n"
  },
  {
    "path": "src/ast.c",
    "content": "// Scrap is a project that allows anyone to build software using simple, block based interface.\n//\n// Copyright (C) 2024-2026 Grisshink\n// \n// This software is provided 'as-is', without any express or implied\n// warranty.  In no event will the authors be held liable for any damages\n// arising from the use of this software.\n// \n// Permission is granted to anyone to use this software for any purpose,\n// including commercial applications, and to alter it and redistribute it\n// freely, subject to the following restrictions:\n// \n// 1. The origin of this software must not be misrepresented; you must not\n//    claim that you wrote the original software. If you use this software\n//    in a product, an acknowledgment in the product documentation would be\n//    appreciated but is not required.\n// 2. Altered source versions must be plainly marked as such, and must not be\n//    misrepresented as being the original software.\n// 3. This notice may not be removed or altered from any source distribution.\n\n#include \"ast.h\"\n#include \"vec.h\"\n\n#include <assert.h>\n#include <libintl.h>\n#include <string.h>\n\nconst char* type_to_str(DataType type) {\n    switch (type) {\n    case DATA_TYPE_NOTHING:\n        return \"nothing\";\n    case DATA_TYPE_INTEGER:\n        return \"integer\";\n    case DATA_TYPE_FLOAT:\n        return \"float\";\n    case DATA_TYPE_STRING:\n        return \"str\";\n    case DATA_TYPE_LITERAL:\n        return \"literal\";\n    case DATA_TYPE_BOOL:\n        return \"bool\";\n    case DATA_TYPE_LIST:\n        return \"list\";\n    case DATA_TYPE_COLOR:\n        return \"color\";\n    case DATA_TYPE_ANY:\n        return \"any\";\n    case DATA_TYPE_BLOCKDEF:\n        return \"blockdef\";\n    case DATA_TYPE_UNKNOWN:\n        return \"unknown\";\n    }\n    assert(false && \"Unhandled type_to_str\");\n}\n\nBlock block_new(Blockdef* blockdef) {\n    Block block;\n    block.blockdef = blockdef;\n    block.arguments = vector_create();\n    block.parent = NULL;\n    blockdef->ref_count++;\n\n    for (size_t i = 0; i < vector_size(blockdef->inputs); i++) {\n        Argument* arg;\n\n        switch (blockdef->inputs[i].type) {\n        case INPUT_ARGUMENT:\n            arg = vector_add_dst(&block.arguments);\n            arg->input_id = i;\n\n            switch (blockdef->inputs[i].data.arg.constr) {\n            case BLOCKCONSTR_UNLIMITED:\n                arg->type = ARGUMENT_TEXT;\n                break;\n            case BLOCKCONSTR_STRING:\n                arg->type = ARGUMENT_CONST_STRING;\n                break;\n            default:\n                assert(false && \"Unimplemented argument constraint\");\n                break;\n            }\n\n            arg->data.text = vector_create();\n            for (char* pos = blockdef->inputs[i].data.arg.text; *pos; pos++) {\n                vector_add(&arg->data.text, *pos);\n            }\n            vector_add(&arg->data.text, 0);\n            break;\n        case INPUT_DROPDOWN:\n            arg = vector_add_dst(&block.arguments);\n            arg->input_id = i;\n            arg->type = ARGUMENT_CONST_STRING;\n\n            size_t list_len = 0;\n            char** list = blockdef->inputs[i].data.drop.list(&block, &list_len);\n            if (!list || list_len == 0) break;\n\n            arg->data.text = vector_create();\n            for (char* pos = list[0]; *pos; pos++) {\n                vector_add(&arg->data.text, *pos);\n            }\n            vector_add(&arg->data.text, 0);\n            break;\n        case INPUT_BLOCKDEF_EDITOR:\n            arg = vector_add_dst(&block.arguments);\n            arg->input_id = i;\n            arg->type = ARGUMENT_BLOCKDEF;\n            arg->data.blockdef = blockdef_new(\"custom\", BLOCKTYPE_NORMAL, blockdef->color, NULL);\n            arg->data.blockdef->ref_count++;\n            blockdef_add_text(arg->data.blockdef, gettext(\"My block\"));\n            break;\n        case INPUT_COLOR:\n            arg = vector_add_dst(&block.arguments);\n            arg->input_id = i;\n            arg->type = ARGUMENT_COLOR;\n            arg->data.color = blockdef->inputs[i].data.color;\n            break;\n        case INPUT_TEXT_DISPLAY:\n        case INPUT_IMAGE_DISPLAY:\n            break;\n        default:\n            assert(false && \"Unhandled add input argument\");\n            break;\n        }\n    }\n    return block;\n}\n\nBlock block_copy(Block* block, Block* parent) {\n    if (!block->arguments) return *block;\n\n    Block new;\n    new.blockdef = block->blockdef;\n    new.parent = parent;\n    new.arguments = vector_create();\n    new.blockdef->ref_count++;\n\n    for (size_t i = 0; i < vector_size(block->arguments); i++) {\n        Argument* arg = vector_add_dst((Argument**)&new.arguments);\n        arg->type = block->arguments[i].type;\n        arg->input_id = block->arguments[i].input_id;\n        static_assert(ARGUMENT_LAST == 5, \"Exhaustive argument type in block_copy\");\n        switch (block->arguments[i].type) {\n        case ARGUMENT_CONST_STRING:\n        case ARGUMENT_TEXT:\n            arg->data.text = vector_copy(block->arguments[i].data.text);\n            break;\n        case ARGUMENT_BLOCK:\n            arg->data.block = block_copy(&block->arguments[i].data.block, &new);\n            break;\n        case ARGUMENT_BLOCKDEF:\n            arg->data.blockdef = blockdef_copy(block->arguments[i].data.blockdef);\n            arg->data.blockdef->ref_count++;\n            break;\n        case ARGUMENT_COLOR:\n            arg->data.color = block->arguments[i].data.color;\n            break;\n        default:\n            assert(false && \"Unimplemented argument copy\");\n            break;\n        }\n    }\n\n    for (size_t i = 0; i < vector_size(new.arguments); i++) {\n        if (new.arguments[i].type != ARGUMENT_BLOCK) continue;\n        block_update_parent_links(&new.arguments[i].data.block);\n    }\n\n    return new;\n}\n\nvoid block_free(Block* block) {\n    blockdef_free(block->blockdef);\n    if (block->arguments) {\n        for (size_t i = 0; i < vector_size(block->arguments); i++) {\n            static_assert(ARGUMENT_LAST == 5, \"Exhaustive argument type in block_free\");\n            switch (block->arguments[i].type) {\n            case ARGUMENT_CONST_STRING:\n            case ARGUMENT_TEXT:\n                vector_free(block->arguments[i].data.text);\n                break;\n            case ARGUMENT_BLOCK:\n                block_free(&block->arguments[i].data.block);\n                break;\n            case ARGUMENT_BLOCKDEF:\n                blockdef_free(block->arguments[i].data.blockdef);\n                break;\n            case ARGUMENT_COLOR:\n                break;\n            default:\n                assert(false && \"Unimplemented argument free\");\n                break;\n            }\n        }\n        vector_free((Argument*)block->arguments);\n    }\n}\n\nvoid block_update_all_links(Block* block) {\n    for (size_t i = 0; i < vector_size(block->arguments); i++) {\n        if (block->arguments[i].type != ARGUMENT_BLOCK) continue;\n        block->arguments[i].data.block.parent = block;\n        block_update_all_links(&block->arguments[i].data.block);\n    }\n}\n\nvoid block_update_parent_links(Block* block) {\n    for (size_t i = 0; i < vector_size(block->arguments); i++) {\n        if (block->arguments[i].type != ARGUMENT_BLOCK) continue;\n        block->arguments[i].data.block.parent = block;\n    }\n}\n\nBlockChain blockchain_new(void) {\n    BlockChain chain;\n    chain.x = 0;\n    chain.y = 0;\n    chain.blocks = vector_create();\n    chain.width = 0;\n    chain.height = 0;\n\n    return chain;\n}\n\nBlockChain blockchain_copy_single(BlockChain* chain, size_t pos) {\n    assert(pos < vector_size(chain->blocks) || pos == 0);\n\n    BlockChain new;\n    new.x = chain->x;\n    new.y = chain->y;\n    new.blocks = vector_create();\n\n    BlockdefType block_type = chain->blocks[pos].blockdef->type;\n    if (block_type == BLOCKTYPE_END) return new;\n    if (block_type != BLOCKTYPE_CONTROL) {\n        vector_add(&new.blocks, block_copy(&chain->blocks[pos], NULL));\n        blockchain_update_parent_links(&new);\n        return new;\n    }\n\n    int layer = 0;\n    for (size_t i = pos; i < vector_size(chain->blocks) && layer >= 0; i++) {\n        block_type = chain->blocks[i].blockdef->type;\n        vector_add(&new.blocks, block_copy(&chain->blocks[i], NULL));\n        if (block_type == BLOCKTYPE_CONTROL && i != pos) {\n            layer++;\n        } else if (block_type == BLOCKTYPE_END) {\n            layer--;\n        }\n    }\n\n    blockchain_update_parent_links(&new);\n    return new;\n}\n\nBlockChain blockchain_copy(BlockChain* chain, size_t pos) {\n    assert(pos < vector_size(chain->blocks) || pos == 0);\n\n    BlockChain new;\n    new.x = chain->x;\n    new.y = chain->y;\n    new.blocks = vector_create();\n\n    int pos_layer = 0;\n    for (size_t i = 0; i < pos; i++) {\n        BlockdefType block_type = chain->blocks[i].blockdef->type;\n        if (block_type == BLOCKTYPE_CONTROL) {\n            pos_layer++;\n        } else if (block_type == BLOCKTYPE_END) {\n            pos_layer--;\n            if (pos_layer < 0) pos_layer = 0;\n        }\n    }\n    int current_layer = pos_layer;\n\n    vector_reserve(&new.blocks, vector_size(chain->blocks) - pos);\n    for (vec_size_t i = pos; i < vector_size(chain->blocks); i++) {\n        BlockdefType block_type = chain->blocks[i].blockdef->type;\n        if ((block_type == BLOCKTYPE_END || (block_type == BLOCKTYPE_CONTROLEND && i != pos)) &&\n            pos_layer == current_layer &&\n            current_layer != 0) break;\n\n        vector_add(&new.blocks, block_copy(&chain->blocks[i], NULL));\n        block_update_parent_links(&new.blocks[vector_size(new.blocks) - 1]);\n\n        if (block_type == BLOCKTYPE_CONTROL) {\n            current_layer++;\n        } else if (block_type == BLOCKTYPE_END) {\n            current_layer--;\n        }\n    }\n\n    return new;\n}\n\nvoid blockchain_update_parent_links(BlockChain* chain) {\n    for (size_t i = 0; i < vector_size(chain->blocks); i++) {\n        block_update_parent_links(&chain->blocks[i]);\n    }\n}\n\nvoid blockchain_add_block(BlockChain* chain, Block block) {\n    vector_add(&chain->blocks, block);\n    blockchain_update_parent_links(chain);\n}\n\nvoid blockchain_clear_blocks(BlockChain* chain) {\n    for (size_t i = 0; i < vector_size(chain->blocks); i++) {\n        block_free(&chain->blocks[i]);\n    }\n    vector_clear(chain->blocks);\n}\n\nvoid blockchain_insert(BlockChain* dst, BlockChain* src, size_t pos) {\n    assert(pos < vector_size(dst->blocks));\n\n    vector_reserve(&dst->blocks, vector_size(dst->blocks) + vector_size(src->blocks));\n    for (ssize_t i = (ssize_t)vector_size(src->blocks) - 1; i >= 0; i--) {\n        vector_insert(&dst->blocks, pos + 1, src->blocks[i]);\n    }\n    blockchain_update_parent_links(dst);\n    vector_clear(src->blocks);\n}\n\nvoid blockchain_detach_single(BlockChain* dst, BlockChain* src, size_t pos) {\n    assert(pos < vector_size(src->blocks));\n\n    BlockdefType block_type = src->blocks[pos].blockdef->type;\n    if (block_type == BLOCKTYPE_END) return;\n    if (block_type != BLOCKTYPE_CONTROL) {\n        vector_add(&dst->blocks, src->blocks[pos]);\n        blockchain_update_parent_links(dst);\n        vector_remove(src->blocks, pos);\n        for (size_t i = pos; i < vector_size(src->blocks); i++) block_update_parent_links(&src->blocks[i]);\n        return;\n    }\n\n    int size = 0;\n    int layer = 0;\n    for (size_t i = pos; i < vector_size(src->blocks) && layer >= 0; i++) {\n        BlockdefType block_type = src->blocks[i].blockdef->type;\n        vector_add(&dst->blocks, src->blocks[i]);\n        if (block_type == BLOCKTYPE_CONTROL && i != pos) {\n            layer++;\n        } else if (block_type == BLOCKTYPE_END) {\n            layer--;\n        }\n        size++;\n    }\n\n    blockchain_update_parent_links(dst);\n    vector_erase(src->blocks, pos, size);\n    for (size_t i = pos; i < vector_size(src->blocks); i++) block_update_parent_links(&src->blocks[i]);\n}\n\nvoid blockchain_detach(BlockChain* dst, BlockChain* src, size_t pos) {\n    assert(pos < vector_size(src->blocks));\n\n    int pos_layer = 0;\n    for (size_t i = 0; i < pos; i++) {\n        BlockdefType block_type = src->blocks[i].blockdef->type;\n        if (block_type == BLOCKTYPE_CONTROL) {\n            pos_layer++;\n        } else if (block_type == BLOCKTYPE_END) {\n            pos_layer--;\n            if (pos_layer < 0) pos_layer = 0;\n        }\n    }\n\n    int current_layer = pos_layer;\n    int layer_size = 0;\n\n    vector_reserve(&dst->blocks, vector_size(dst->blocks) + vector_size(src->blocks) - pos);\n    for (size_t i = pos; i < vector_size(src->blocks); i++) {\n        BlockdefType block_type = src->blocks[i].blockdef->type;\n        if ((block_type == BLOCKTYPE_END || (block_type == BLOCKTYPE_CONTROLEND && i != pos)) && pos_layer == current_layer && current_layer != 0) break;\n        vector_add(&dst->blocks, src->blocks[i]);\n        if (block_type == BLOCKTYPE_CONTROL) {\n            current_layer++;\n        } else if (block_type == BLOCKTYPE_END) {\n            current_layer--;\n        }\n        layer_size++;\n    }\n    blockchain_update_parent_links(dst);\n    vector_erase(src->blocks, pos, layer_size);\n    blockchain_update_parent_links(src);\n}\n\nvoid blockchain_free(BlockChain* chain) {\n    blockchain_clear_blocks(chain);\n    vector_free(chain->blocks);\n}\n\nvoid argument_set_block(Argument* block_arg, Block block) {\n    if (block_arg->type == ARGUMENT_TEXT || block_arg->type == ARGUMENT_CONST_STRING) vector_free(block_arg->data.text);\n    block_arg->type = ARGUMENT_BLOCK;\n    block_arg->data.block = block;\n\n    block_update_parent_links(&block_arg->data.block);\n}\n\nvoid argument_set_const_string(Argument* block_arg, char* text) {\n    assert(block_arg->type == ARGUMENT_CONST_STRING);\n\n    block_arg->type = ARGUMENT_CONST_STRING;\n    vector_clear(block_arg->data.text);\n\n    for (char* pos = text; *pos; pos++) {\n        vector_add(&block_arg->data.text, *pos);\n    }\n    vector_add(&block_arg->data.text, 0);\n}\n\nvoid argument_set_text(Argument* block_arg, char* text) {\n    block_arg->type = ARGUMENT_TEXT;\n    block_arg->data.text = vector_create();\n\n    for (char* pos = text; *pos; pos++) {\n        vector_add(&block_arg->data.text, *pos);\n    }\n    vector_add(&block_arg->data.text, 0);\n}\n\nvoid argument_set_color(Argument* block_arg, BlockdefColor color) {\n    block_arg->type = ARGUMENT_COLOR;\n    block_arg->data.color = color;\n}\n\nBlockdef* blockdef_new(const char* id, BlockdefType type, BlockdefColor color, void* func) {\n    assert(id != NULL);\n    Blockdef* blockdef = malloc(sizeof(Blockdef));\n    blockdef->id = strcpy(malloc((strlen(id) + 1) * sizeof(char)), id);\n    blockdef->color = color;\n    blockdef->type = type;\n    blockdef->ref_count = 0;\n    blockdef->inputs = vector_create();\n    blockdef->func = func;\n\n    return blockdef;\n}\n\nBlockdef* blockdef_copy(Blockdef* blockdef) {\n    Blockdef* new = malloc(sizeof(Blockdef));\n    new->id = strcpy(malloc((strlen(blockdef->id) + 1) * sizeof(char)), blockdef->id);\n    new->color = blockdef->color;\n    new->type = blockdef->type;\n    new->ref_count = 0;\n    new->inputs = vector_create();\n    new->func = blockdef->func;\n\n    for (size_t i = 0; i < vector_size(blockdef->inputs); i++) {\n        Input* input = vector_add_dst(&new->inputs);\n        input->type = blockdef->inputs[i].type;\n        switch (blockdef->inputs[i].type) {\n        case INPUT_TEXT_DISPLAY:\n            input->data = (InputData) {\n                .text = vector_copy(blockdef->inputs[i].data.text),\n            };\n            break;\n        case INPUT_ARGUMENT:\n            input->data = (InputData) {\n                .arg = {\n                    .blockdef = blockdef_copy(blockdef->inputs[i].data.arg.blockdef),\n                    .text = blockdef->inputs[i].data.arg.text,\n                    .constr = blockdef->inputs[i].data.arg.constr,\n                },\n            };\n            break;\n        case INPUT_IMAGE_DISPLAY:\n            input->data = (InputData) {\n                .image = blockdef->inputs[i].data.image,\n            };\n            break;\n        case INPUT_DROPDOWN:\n            input->data = (InputData) {\n                .drop = {\n                    .source = blockdef->inputs[i].data.drop.source,\n                    .list = blockdef->inputs[i].data.drop.list,\n                },\n            };\n            break;\n        case INPUT_BLOCKDEF_EDITOR:\n            input->data = (InputData) {0};\n            break;\n        default:\n            assert(false && \"Unimplemented input copy\");\n            break;\n        }\n    }\n\n    return new;\n}\n\nvoid blockdef_add_text(Blockdef* blockdef, const char* text) {\n    Input* input = vector_add_dst(&blockdef->inputs);\n    input->type = INPUT_TEXT_DISPLAY;\n    input->data = (InputData) {\n        .text = vector_create(),\n    };\n\n    for (size_t i = 0; text[i]; i++) vector_add(&input->data.text, text[i]);\n    vector_add(&input->data.text, 0);\n}\n\nvoid blockdef_add_argument(Blockdef* blockdef, char* defualt_data, const char* hint_text, InputArgumentConstraint constraint) {\n    Input* input = vector_add_dst(&blockdef->inputs);\n    input->type = INPUT_ARGUMENT;\n    input->data = (InputData) {\n        .arg = {\n            .blockdef = blockdef_new(\"custom_arg\", BLOCKTYPE_NORMAL, blockdef->color, NULL),\n            .text = defualt_data,\n            .constr = constraint,\n            .hint_text = hint_text,\n        },\n    };\n    input->data.arg.blockdef->ref_count++;\n}\n\nvoid blockdef_add_blockdef_editor(Blockdef* blockdef) {\n    Input* input = vector_add_dst(&blockdef->inputs);\n    input->type = INPUT_BLOCKDEF_EDITOR;\n    input->data = (InputData) {0};\n}\n\nvoid blockdef_add_dropdown(Blockdef* blockdef, InputDropdownSource dropdown_source, ListAccessor accessor) {\n    Input* input = vector_add_dst(&blockdef->inputs);\n    input->type = INPUT_DROPDOWN;\n    input->data = (InputData) {\n        .drop = {\n            .source = dropdown_source,\n            .list = accessor,\n        },\n    };\n}\n\nvoid blockdef_add_color_input(Blockdef* blockdef, BlockdefColor color) {\n    Input* input = vector_add_dst(&blockdef->inputs);\n    input->type = INPUT_COLOR;\n    input->data = (InputData) {\n        .color = color,\n    };\n}\n\nvoid blockdef_add_image(Blockdef* blockdef, BlockdefImage image) {\n    Input* input = vector_add_dst(&blockdef->inputs);\n    input->type = INPUT_IMAGE_DISPLAY;\n    input->data = (InputData) {\n        .image = image,\n    };\n}\n\nvoid blockdef_set_id(Blockdef* blockdef, const char* new_id) {\n    free((void*)blockdef->id);\n    blockdef->id = strcpy(malloc((strlen(new_id) + 1) * sizeof(char)), new_id);\n}\n\nvoid blockdef_delete_input(Blockdef* blockdef, size_t input) {\n    assert(input < vector_size(blockdef->inputs));\n\n    switch (blockdef->inputs[input].type) {\n    case INPUT_TEXT_DISPLAY:\n        vector_free(blockdef->inputs[input].data.text);\n        break;\n    case INPUT_ARGUMENT:\n        blockdef_free(blockdef->inputs[input].data.arg.blockdef);\n        break;\n    default:\n        assert(false && \"Unimplemented input delete\");\n        break;\n    }\n    vector_remove(blockdef->inputs, input);\n}\n\nvoid blockdef_free(Blockdef* blockdef) {\n    blockdef->ref_count--;\n    if (blockdef->ref_count > 0) return;\n    for (vec_size_t i = 0; i < vector_size(blockdef->inputs); i++) {\n        switch (blockdef->inputs[i].type) {\n        case INPUT_TEXT_DISPLAY:\n            vector_free(blockdef->inputs[i].data.text);\n            break;\n        case INPUT_ARGUMENT:\n            blockdef_free(blockdef->inputs[i].data.arg.blockdef);\n            break;\n        default:\n            break;\n        }\n    }\n    vector_free(blockdef->inputs);\n    free((void*)blockdef->id);\n    free(blockdef);\n}\n"
  },
  {
    "path": "src/ast.h",
    "content": "// Scrap is a project that allows anyone to build software using simple, block based interface.\n//\n// Copyright (C) 2024-2026 Grisshink\n// \n// This software is provided 'as-is', without any express or implied\n// warranty.  In no event will the authors be held liable for any damages\n// arising from the use of this software.\n// \n// Permission is granted to anyone to use this software for any purpose,\n// including commercial applications, and to alter it and redistribute it\n// freely, subject to the following restrictions:\n// \n// 1. The origin of this software must not be misrepresented; you must not\n//    claim that you wrote the original software. If you use this software\n//    in a product, an acknowledgment in the product documentation would be\n//    appreciated but is not required.\n// 2. Altered source versions must be plainly marked as such, and must not be\n//    misrepresented as being the original software.\n// 3. This notice may not be removed or altered from any source distribution.\n\n#ifndef AST_H\n#define AST_H\n\n#include <stddef.h>\n#include <stdbool.h>\n\ntypedef struct BlockdefColor BlockdefColor;\ntypedef struct BlockdefImage BlockdefImage;\n\ntypedef enum ArgumentType ArgumentType;\ntypedef union ArgumentData ArgumentData;\ntypedef struct Argument Argument;\ntypedef struct Block Block;\n\ntypedef enum InputArgumentConstraint InputArgumentConstraint;\ntypedef enum InputDropdownSource InputDropdownSource;\ntypedef struct InputArgument InputArgument;\ntypedef struct InputDropdown InputDropdown;\ntypedef enum InputType InputType;\ntypedef union InputData InputData;\ntypedef struct Input Input;\n\ntypedef enum BlockdefType BlockdefType;\ntypedef struct Blockdef Blockdef;\n\ntypedef struct BlockChain BlockChain;\n\ntypedef char** (*ListAccessor)(Block* block, size_t* list_len);\n\ntypedef enum {\n    DATA_TYPE_UNKNOWN = 0,\n    DATA_TYPE_NOTHING,\n    DATA_TYPE_INTEGER,\n    DATA_TYPE_FLOAT,\n    DATA_TYPE_LITERAL, // Literal string, stored in global memory\n    DATA_TYPE_STRING, // Pointer to a string type, managed by the current memory allocator (GC)\n    DATA_TYPE_BOOL,\n    DATA_TYPE_LIST,\n    DATA_TYPE_ANY,\n    DATA_TYPE_BLOCKDEF,\n    DATA_TYPE_COLOR,\n} DataType;\n\nstruct BlockdefColor {\n    unsigned char r, g, b, a;\n};\n\nstruct BlockdefImage {\n    void* image_ptr;\n    BlockdefColor image_color;\n};\n\nenum InputArgumentConstraint {\n    BLOCKCONSTR_UNLIMITED, // Can put anything as argument\n    BLOCKCONSTR_STRING, // Can only put strings as argument\n};\n\nstruct InputArgument {\n    Blockdef* blockdef;\n    InputArgumentConstraint constr;\n    char* text;\n    const char* hint_text;\n};\n\nenum InputDropdownSource {\n    DROPDOWN_SOURCE_LISTREF,\n};\n\nstruct InputDropdown {\n    InputDropdownSource source;\n    ListAccessor list;\n};\n\nunion InputData {\n    char* text;\n    BlockdefImage image;\n    InputArgument arg;\n    InputDropdown drop;\n    BlockdefColor color;\n};\n\nenum InputType {\n    INPUT_TEXT_DISPLAY,\n    INPUT_ARGUMENT,\n    INPUT_DROPDOWN,\n    INPUT_BLOCKDEF_EDITOR,\n    INPUT_IMAGE_DISPLAY,\n    INPUT_COLOR,\n};\n\nstruct Input {\n    InputType type;\n    InputData data;\n};\n\nenum BlockdefType {\n    BLOCKTYPE_NORMAL,\n    BLOCKTYPE_CONTROL,\n    BLOCKTYPE_CONTROLEND,\n    BLOCKTYPE_END,\n    BLOCKTYPE_HAT,\n};\n\nstruct Blockdef {\n    const char* id;\n    int ref_count;\n    BlockdefColor color;\n    BlockdefType type;\n    Input* inputs;\n    void* func;\n};\n\nstruct Block {\n    Blockdef* blockdef;\n    Argument* arguments;\n    Block* parent;\n};\n\nunion ArgumentData {\n    char* text;\n    BlockdefColor color;\n    Block block;\n    Blockdef* blockdef;\n};\n\nenum ArgumentType {\n    ARGUMENT_TEXT = 0,\n    ARGUMENT_BLOCK,\n    ARGUMENT_CONST_STRING,\n    ARGUMENT_BLOCKDEF,\n    ARGUMENT_COLOR,\n    // Must be last in enum\n    ARGUMENT_LAST,\n};\n\nstruct Argument {\n    int input_id;\n    ArgumentType type;\n    ArgumentData data;\n};\n\nstruct BlockChain {\n    int x, y;\n    int width, height;\n    Block* blocks;\n};\n\nBlockdef* blockdef_new(const char* id, BlockdefType type, BlockdefColor color, void* func);\nBlockdef* blockdef_copy(Blockdef* blockdef);\nvoid blockdef_add_text(Blockdef* blockdef, const char* text);\nvoid blockdef_add_argument(Blockdef* blockdef, char* defualt_data, const char* hint_text, InputArgumentConstraint constraint);\nvoid blockdef_add_dropdown(Blockdef* blockdef, InputDropdownSource dropdown_source, ListAccessor accessor);\nvoid blockdef_add_color_input(Blockdef* blockdef, BlockdefColor color);\nvoid blockdef_add_image(Blockdef* blockdef, BlockdefImage image);\nvoid blockdef_add_blockdef_editor(Blockdef* blockdef);\nvoid blockdef_delete_input(Blockdef* blockdef, size_t input);\nvoid blockdef_set_id(Blockdef* blockdef, const char* new_id);\nvoid blockdef_free(Blockdef* blockdef);\n\nBlockChain blockchain_new(void);\nBlockChain blockchain_copy(BlockChain* chain, size_t ind);\nBlockChain blockchain_copy_single(BlockChain* chain, size_t pos);\nvoid blockchain_add_block(BlockChain* chain, Block block);\nvoid blockchain_clear_blocks(BlockChain* chain);\nvoid blockchain_insert(BlockChain* dst, BlockChain* src, size_t pos);\nvoid blockchain_update_parent_links(BlockChain* chain);\n// Splits off blockchain src in two at specified pos, placing lower half into blockchain dst\nvoid blockchain_detach(BlockChain* dst, BlockChain* src, size_t pos);\nvoid blockchain_detach_single(BlockChain* dst, BlockChain* src, size_t pos);\nvoid blockchain_free(BlockChain* chain);\n\nBlock block_new(Blockdef* blockdef);\nBlock block_copy(Block* block, Block* parent);\nvoid block_update_parent_links(Block* block);\nvoid block_update_all_links(Block* block);\nvoid block_free(Block* block);\n\nvoid argument_set_block(Argument* block_arg, Block block);\nvoid argument_set_const_string(Argument* block_arg, char* text);\nvoid argument_set_text(Argument* block_arg, char* text);\nvoid argument_set_color(Argument* block_arg, BlockdefColor color);\n\nconst char* type_to_str(DataType type);\n\n#endif // AST_H\n"
  },
  {
    "path": "src/blocks.c",
    "content": "// Scrap is a project that allows anyone to build software using simple, block based interface.\n//\n// Copyright (C) 2024-202 Grisshink\n// \n// This software is provided 'as-is', without any express or implied\n// warranty.  In no event will the authors be held liable for any damages\n// arising from the use of this software.\n// \n// Permission is granted to anyone to use this software for any purpose,\n// including commercial applications, and to alter it and redistribute it\n// freely, subject to the following restrictions:\n// \n// 1. The origin of this software must not be misrepresented; you must not\n//    claim that you wrote the original software. If you use this software\n//    in a product, an acknowledgment in the product documentation would be\n//    appreciated but is not required.\n// 2. Altered source versions must be plainly marked as such, and must not be\n//    misrepresented as being the original software.\n// 3. This notice may not be removed or altered from any source distribution.\n\n#include \"term.h\"\n#include \"scrap.h\"\n#include \"vec.h\"\n#include \"util.h\"\n#include \"std.h\"\n\n#include <unistd.h>\n#include <stdlib.h>\n#include <string.h>\n#include <math.h>\n#include <assert.h>\n#include <libintl.h>\n\n#define MATH_LIST_LEN 10\n#define TERM_COLOR_LIST_LEN 8\n\ntypedef struct {\n    char* str;\n    size_t len;\n    size_t cap;\n} String;\n\ntypedef double (*MathFunc)(double);\n\nchar* block_math_list[MATH_LIST_LEN] = {\n    \"sqrt\", \"round\", \"floor\", \"ceil\",\n    \"sin\", \"cos\", \"tan\",\n    \"asin\", \"acos\", \"atan\",\n};\n\nchar** math_list_access(Block* block, size_t* list_len) {\n    (void) block;\n    *list_len = MATH_LIST_LEN;\n    return block_math_list;\n}\n\n#ifdef USE_INTERPRETER\n\nstatic MathFunc block_math_func_list[MATH_LIST_LEN] = {\n    sqrt, round, floor, ceil,\n    sin, cos, tan,\n    asin, acos, atan,\n};\n\n#include \"std.h\"\n\nbool block_do_nothing(Exec* exec, Block* block, int argc, AnyValue* argv, AnyValue* return_val, ControlState control_state) {\n    (void) control_state;\n    (void) block;\n    (void) exec;\n    (void) argc;\n    (void) argv;\n    *return_val = DATA_NOTHING;\n    return true;\n}\n\nbool block_noop(Exec* exec, Block* block, int argc, AnyValue* argv, AnyValue* return_val, ControlState control_state) {\n    (void) control_state;\n    (void) block;\n    (void) exec;\n    (void) argc;\n    (void) argv;\n    *return_val = DATA_NOTHING;\n    return true;\n}\n\nbool block_on_start(Exec* exec, Block* block, int argc, AnyValue* argv, AnyValue* return_val, ControlState control_state) {\n    (void) control_state;\n    (void) block;\n    (void) exec;\n    (void) argc;\n    (void) argv;\n    *return_val = DATA_NOTHING;\n    return true;\n}\n\nbool block_define_block(Exec* exec, Block* block, int argc, AnyValue* argv, AnyValue* return_val, ControlState control_state) {\n    (void) control_state;\n    (void) block;\n    (void) exec;\n    (void) argc;\n    (void) argv;\n    *return_val = DATA_NOTHING;\n    return true;\n}\n\nbool block_loop(Exec* exec, Block* block, int argc, AnyValue* argv, AnyValue* return_val, ControlState control_state) {\n    (void) block;\n    (void) argc;\n    (void) argv;\n\n    if (control_state == CONTROL_STATE_BEGIN) {\n        control_stack_push_data(exec->chain_stack[exec->chain_stack_len - 1].running_ind, size_t);\n    } else if (control_state == CONTROL_STATE_END) {\n        control_stack_pop_data(exec->chain_stack[exec->chain_stack_len - 1].running_ind, size_t);\n        control_stack_push_data(exec->chain_stack[exec->chain_stack_len - 1].running_ind, size_t);\n    }\n\n    *return_val = DATA_BOOL(1);\n    return true;\n}\n\nbool block_if(Exec* exec, Block* block, int argc, AnyValue* argv, AnyValue* return_val, ControlState control_state) {\n    (void) block;\n\n    if (control_state == CONTROL_STATE_BEGIN) {\n        if (argc < 1 || !std_bool_from_any(&argv[0])) {\n            exec_set_skip_block(exec);\n            control_stack_push_data((int)0, int);\n        } else {\n            control_stack_push_data((int)1, int);\n        }\n        *return_val = DATA_NOTHING;\n    } else if (control_state == CONTROL_STATE_END) {\n        int is_success = 0;\n        control_stack_pop_data(is_success, int);\n        *return_val = DATA_BOOL(is_success);\n    } else {\n        exec_set_error(exec, block, \"Invalid control state\");\n        return false;\n    }\n\n    return true;\n}\n\nbool block_else_if(Exec* exec, Block* block, int argc, AnyValue* argv, AnyValue* return_val, ControlState control_state) {\n    (void) block;\n\n    if (control_state == CONTROL_STATE_BEGIN) {\n        if (argc < 2 || std_bool_from_any(&argv[0])) {\n            exec_set_skip_block(exec);\n            control_stack_push_data((int)1, int);\n        } else {\n            int condition = std_bool_from_any(&argv[1]);\n            if (!condition) exec_set_skip_block(exec);\n            control_stack_push_data(condition, int);\n        }\n        *return_val = DATA_NOTHING;\n    } else if (control_state == CONTROL_STATE_END) {\n        int is_success = 0;\n        control_stack_pop_data(is_success, int);\n        *return_val = DATA_BOOL(is_success);\n    } else {\n        exec_set_error(exec, block, \"Invalid control state\");\n        return false;\n    }\n    return true;\n}\n\nbool block_else(Exec* exec, Block* block, int argc, AnyValue* argv, AnyValue* return_val, ControlState control_state) {\n    (void) block;\n\n    if (control_state == CONTROL_STATE_BEGIN) {\n        if (argc < 1 || std_bool_from_any(&argv[0])) {\n            exec_set_skip_block(exec);\n        }\n        *return_val = DATA_NOTHING;\n    } else if (control_state == CONTROL_STATE_END) {\n        *return_val = DATA_BOOL(1);\n    } else {\n        exec_set_error(exec, block, \"Invalid control state\");\n        return false;\n    }\n\n    return true;\n}\n\n// Visualization of control stack (stack grows downwards):\n// - loop block index\n// - cycles left to loop\n// - 1 <- indicator for end block to do looping\n//\n// If the loop should not loop then the stack will look like this:\n// - 0 <- indicator for end block that it should stop immediately\nbool block_repeat(Exec* exec, Block* block, int argc, AnyValue* argv, AnyValue* return_val, ControlState control_state) {\n    (void) block;\n\n    if (control_state == CONTROL_STATE_BEGIN) {\n        int cycles = argc < 1 ? 0 : std_integer_from_any(&argv[0]);\n        if (cycles <= 0) {\n            exec_set_skip_block(exec);\n            control_stack_push_data((int)0, int); // This indicates the end block that it should NOT loop\n            *return_val = DATA_NOTHING;\n            return true;\n        }\n        control_stack_push_data(exec->chain_stack[exec->chain_stack_len - 1].running_ind, size_t);\n        control_stack_push_data(cycles - 1, int);\n        control_stack_push_data((int)1, int); // This indicates the end block that it should loop\n    } else if (control_state == CONTROL_STATE_END) {\n        int should_loop = 0;\n        control_stack_pop_data(should_loop, int);\n        if (!should_loop) {\n            *return_val = DATA_BOOL(0);\n            return true;\n        }\n\n        int left = -1;\n        control_stack_pop_data(left, int);\n        if (left <= 0) {\n            size_t bin;\n            control_stack_pop_data(bin, size_t);\n            (void) bin; // Cleanup stack\n            *return_val = DATA_BOOL(1);\n            return true;\n        }\n\n        control_stack_pop_data(exec->chain_stack[exec->chain_stack_len - 1].running_ind, size_t);\n        control_stack_push_data(exec->chain_stack[exec->chain_stack_len - 1].running_ind, size_t);\n        control_stack_push_data(left - 1, int);\n        control_stack_push_data((int)1, int);\n    } else {\n        exec_set_error(exec, block, \"Invalid control state\");\n        return false;\n    }\n\n    *return_val = DATA_NOTHING;\n    return true;\n}\n\nbool block_while(Exec* exec, Block* block, int argc, AnyValue* argv, AnyValue* return_val, ControlState control_state) {\n    if (control_state == CONTROL_STATE_BEGIN) {\n        if (argc < 1 || !std_bool_from_any(&argv[0])) {\n            exec_set_skip_block(exec);\n        }\n        control_stack_push_data(exec->chain_stack[exec->chain_stack_len - 1].running_ind, size_t);\n    } else if (control_state == CONTROL_STATE_END) {\n        AnyValue out_val;\n        if (!evaluate_argument(exec, &block->arguments[0], &out_val)) return false;\n\n        if (vector_size(block->arguments) < 1 || !std_bool_from_any(&out_val)) {\n            size_t bin;\n            control_stack_pop_data(bin, size_t);\n            (void) bin;\n            *return_val = DATA_BOOL(1);\n            return true;\n        }\n\n        control_stack_pop_data(exec->chain_stack[exec->chain_stack_len - 1].running_ind, size_t);\n        control_stack_push_data(exec->chain_stack[exec->chain_stack_len - 1].running_ind, size_t);\n    } else {\n        exec_set_error(exec, block, \"Invalid control state\");\n        return false;\n    }\n\n    *return_val = DATA_NOTHING;\n    return true;\n}\n\nbool block_sleep(Exec* exec, Block* block, int argc, AnyValue* argv, AnyValue* return_val, ControlState control_state) {\n    (void) control_state;\n    (void) block;\n    (void) exec;\n    (void) argc;\n    int usecs = std_integer_from_any(&argv[0]);\n    if (usecs < 0) {\n        *return_val = DATA_INTEGER(0);\n        return true;\n    }\n\n    struct timespec sleep_time = {0};\n    sleep_time.tv_sec = usecs / 1000000;\n    sleep_time.tv_nsec = (usecs % 1000000) * 1000;\n\n    if (nanosleep(&sleep_time, &sleep_time) == -1) {\n        *return_val = DATA_INTEGER(0);\n        return true;\n    }\n\n    *return_val = DATA_INTEGER(usecs);\n    return true;\n}\n\nbool block_declare_var(Exec* exec, Block* block, int argc, AnyValue* argv, AnyValue* return_val, ControlState control_state) {\n    (void) control_state;\n    (void) argc;\n\n    if (argv[0].type != DATA_TYPE_LITERAL) {\n        exec_set_error(exec, block, gettext(\"Invalid data type %s, expected %s\"), type_to_str(argv[0].type), type_to_str(DATA_TYPE_LITERAL));\n        return false;\n    }\n    if (block->parent) {\n        exec_set_error(exec, block, gettext(\"Variable declarations are not allowed inside an expression\"));\n        return false;\n    }\n\n    Variable* var = variable_stack_push_var(exec, argv[0].data.literal_val, argv[1]);\n    if (!var) {\n        exec_set_error(exec, block, gettext(\"Cannot declare variable with empty name\"));\n        return false;\n    }\n\n    if (argv[1].type == DATA_TYPE_LIST || argv[1].type == DATA_TYPE_ANY || argv[1].type == DATA_TYPE_STRING) {\n        gc_add_root(&exec->gc, &var->value_ptr);\n    }\n\n    *return_val = argv[1];\n    return true;\n}\n\nbool block_get_var(Exec* exec, Block* block, int argc, AnyValue* argv, AnyValue* return_val, ControlState control_state) {\n    (void) control_state;\n    (void) block;\n    (void) argc;\n\n    if (argv[0].type != DATA_TYPE_LITERAL) {\n        exec_set_error(exec, block, gettext(\"Invalid data type %s, expected %s\"), type_to_str(argv[0].type), type_to_str(DATA_TYPE_LITERAL));\n        return false;\n    }\n    \n    char* var_name = argv[0].data.literal_val;\n    Variable* var = variable_stack_get_variable(exec, var_name);\n    if (!var) {\n        exec_set_error(exec, block, gettext(\"Variable with name \\\"%s\\\" does not exist in the current scope\"), var_name);\n        return false;\n    }\n    *return_val = var->value;\n    return true;\n}\n\nbool block_set_var(Exec* exec, Block* block, int argc, AnyValue* argv, AnyValue* return_val, ControlState control_state) {\n    (void) control_state;\n    (void) block;\n    (void) argc;\n\n    if (argv[0].type != DATA_TYPE_LITERAL) {\n        exec_set_error(exec, block, gettext(\"Invalid data type %s, expected %s\"), type_to_str(argv[0].type), type_to_str(DATA_TYPE_LITERAL));\n        return false;\n    }\n\n    char* var_name = argv[0].data.literal_val;\n    Variable* var = variable_stack_get_variable(exec, var_name);\n    if (!var) {\n        exec_set_error(exec, block, gettext(\"Variable with name \\\"%s\\\" does not exist in the current scope\"), var_name);\n        return false;\n    }\n\n    var->value = argv[1];\n\n    *return_val = var->value;\n    return true;\n}\n\nbool block_create_list(Exec* exec, Block* block, int argc, AnyValue* argv, AnyValue* return_val, ControlState control_state) {\n    (void) control_state;\n    (void) block;\n    (void) exec;\n    (void) argc;\n    (void) argv;\n\n    *return_val = DATA_LIST(std_list_new(&exec->gc));\n    return true;\n}\n\nbool block_list_add(Exec* exec, Block* block, int argc, AnyValue* argv, AnyValue* return_val, ControlState control_state) {\n    (void) control_state;\n    (void) argc;\n    if (argv[0].type != DATA_TYPE_LIST) {\n        exec_set_error(exec, block, gettext(\"Invalid data type %s, expected %s\"), type_to_str(argv[0].type), type_to_str(DATA_TYPE_LIST));\n        return false;\n    }\n\n    std_list_add_any(&exec->gc, argv[0].data.list_val, argv[1]);\n    *return_val = DATA_NOTHING;\n    return true;\n}\n\nbool block_list_get(Exec* exec, Block* block, int argc, AnyValue* argv, AnyValue* return_val, ControlState control_state) {\n    (void) control_state;\n    (void) block;\n    (void) argc;\n    if (argv[0].type != DATA_TYPE_LIST) {\n        exec_set_error(exec, block, gettext(\"Invalid data type %s, expected %s\"), type_to_str(argv[0].type), type_to_str(DATA_TYPE_LIST));\n        return false;\n    }\n\n    List* list = argv[0].data.list_val;\n    int index = std_integer_from_any(&argv[1]);\n\n    if (index >= list->size || index < 0) {\n        *return_val = DATA_NOTHING;\n        return true;\n    }\n\n    *return_val = list->values[index];\n    return true;\n}\n\nbool block_list_length(Exec* exec, Block* block, int argc, AnyValue* argv, AnyValue* return_val, ControlState control_state) {\n    (void) control_state;\n    (void) block;\n    (void) exec;\n    (void) argc;\n    if (argv[0].type != DATA_TYPE_LIST) {\n        exec_set_error(exec, block, gettext(\"Invalid data type %s, expected %s\"), type_to_str(argv[0].type), type_to_str(DATA_TYPE_LIST));\n        return false;\n    }\n\n    *return_val = DATA_INTEGER(argv[0].data.list_val->size);\n    return true;\n}\n\nbool block_list_set(Exec* exec, Block* block, int argc, AnyValue* argv, AnyValue* return_val, ControlState control_state) {\n    (void) control_state;\n    (void) block;\n    (void) exec;\n    (void) argc;\n    if (argv[0].type != DATA_TYPE_LIST) {\n        exec_set_error(exec, block, gettext(\"Invalid data type %s, expected %s\"), type_to_str(argv[0].type), type_to_str(DATA_TYPE_LIST));\n        return false;\n    }\n\n    int index = std_integer_from_any(&argv[1]);\n    if (index >= argv[0].data.list_val->size || index < 0) {\n        exec_set_error(exec, block, gettext(\"Tried to access index %d for list of length %d\"), index, argv[0].data.list_val->size);\n        return false;\n    }\n\n    argv[0].data.list_val->values[index] = argv[2];\n    *return_val = DATA_NOTHING;\n    return true;\n}\n\nbool block_print(Exec* exec, Block* block, int argc, AnyValue* argv, AnyValue* return_val, ControlState control_state) {\n    (void) exec;\n    (void) block;\n    (void) control_state;\n    (void) argc;\n\n    int bytes_sent = 0;\n    switch (argv[0].type) {\n    case DATA_TYPE_INTEGER:\n        bytes_sent = term_print_integer(argv[0].data.integer_val);\n        break;\n    case DATA_TYPE_BOOL:\n        bytes_sent = term_print_str(argv[0].data.integer_val ? \"true\" : \"false\");\n        break;\n    case DATA_TYPE_LITERAL:\n        bytes_sent = term_print_str(argv[0].data.literal_val);\n        break;\n    case DATA_TYPE_STRING:\n        bytes_sent = term_print_str(argv[0].data.str_val->str);\n        break;\n    case DATA_TYPE_FLOAT:\n        bytes_sent = term_print_float(argv[0].data.float_val);\n        break;\n    case DATA_TYPE_LIST:\n        bytes_sent = term_print_str(\"*LIST (\");\n        bytes_sent += term_print_integer(argv[0].data.list_val->size);\n        bytes_sent += term_print_str(\")*\");\n        break;\n    case DATA_TYPE_NOTHING:\n        break;\n    case DATA_TYPE_COLOR:\n        bytes_sent = term_print_color(CONVERT_COLOR(argv[0].data.color_val, TermColor));\n        break;\n    case DATA_TYPE_UNKNOWN:\n    case DATA_TYPE_ANY:\n    case DATA_TYPE_BLOCKDEF:\n        exec_set_error(exec, block, gettext(\"Cannot print type %s\"), type_to_str(argv[0].type));\n        return false;\n    }\n    *return_val = DATA_INTEGER(bytes_sent);\n    return true;\n}\n\nbool block_println(Exec* exec, Block* block, int argc, AnyValue* argv, AnyValue* return_val, ControlState control_state) {\n    if (!block_print(exec, block, argc, argv, return_val, control_state)) return false;\n    term_print_str(\"\\n\");\n    return_val->data.integer_val++;\n    return true;\n}\n\nbool block_cursor_x(Exec* exec, Block* block, int argc, AnyValue* argv, AnyValue* return_val, ControlState control_state) {\n    (void) control_state;\n    (void) block;\n    (void) exec;\n    (void) argv;\n    (void) argc;\n\n    mutex_lock(&term.lock);\n    int cur_x = 0;\n    if (term.char_w != 0) cur_x = term.cursor_pos % term.char_w;\n    mutex_unlock(&term.lock);\n    *return_val = DATA_INTEGER(cur_x);\n    return true;\n}\n\nbool block_cursor_y(Exec* exec, Block* block, int argc, AnyValue* argv, AnyValue* return_val, ControlState control_state) {\n    (void) control_state;\n    (void) block;\n    (void) exec;\n    (void) argv;\n    (void) argc;\n\n    mutex_lock(&term.lock);\n    int cur_y = 0;\n    if (term.char_w != 0) cur_y = term.cursor_pos / term.char_w;\n    mutex_unlock(&term.lock);\n\n    *return_val = DATA_INTEGER(cur_y);\n    return true;\n}\n\nbool block_cursor_max_x(Exec* exec, Block* block, int argc, AnyValue* argv, AnyValue* return_val, ControlState control_state) {\n    (void) control_state;\n    (void) block;\n    (void) exec;\n    (void) argv;\n    (void) argc;\n\n    mutex_lock(&term.lock);\n    int cur_max_x = term.char_w;\n    mutex_unlock(&term.lock);\n\n    *return_val = DATA_INTEGER(cur_max_x);\n    return true;\n}\n\nbool block_cursor_max_y(Exec* exec, Block* block, int argc, AnyValue* argv, AnyValue* return_val, ControlState control_state) {\n    (void) control_state;\n    (void) block;\n    (void) exec;\n    (void) argv;\n    (void) argc;\n\n    mutex_lock(&term.lock);\n    int cur_max_y = term.char_h;\n    mutex_unlock(&term.lock);\n\n    *return_val = DATA_INTEGER(cur_max_y);\n    return true;\n}\n\nbool block_set_cursor(Exec* exec, Block* block, int argc, AnyValue* argv, AnyValue* return_val, ControlState control_state) {\n    (void) control_state;\n    (void) block;\n    (void) exec;\n    (void) argc;\n\n    mutex_lock(&term.lock);\n    int x = CLAMP(std_integer_from_any(&argv[0]), 0, term.char_w - 1);\n    int y = CLAMP(std_integer_from_any(&argv[1]), 0, term.char_h - 1);\n    term.cursor_pos = x + y * term.char_w;\n    mutex_unlock(&term.lock);\n\n    *return_val = DATA_NOTHING;\n    return true;\n}\n\nbool block_set_fg_color(Exec* exec, Block* block, int argc, AnyValue* argv, AnyValue* return_val, ControlState control_state) {\n    (void) control_state;\n    (void) block;\n    (void) exec;\n    (void) argc;\n    term_set_fg_color(CONVERT_COLOR(std_color_from_any(&argv[0]), TermColor));\n    *return_val = DATA_NOTHING;\n    return true;\n}\n\nbool block_set_bg_color(Exec* exec, Block* block, int argc, AnyValue* argv, AnyValue* return_val, ControlState control_state) {\n    (void) control_state;\n    (void) block;\n    (void) exec;\n    (void) argc;\n    term_set_bg_color(CONVERT_COLOR(std_color_from_any(&argv[0]), TermColor));\n    *return_val = DATA_NOTHING;\n    return true;\n}\n\nbool block_reset_color(Exec* exec, Block* block, int argc, AnyValue* argv, AnyValue* return_val, ControlState control_state) {\n    (void) control_state;\n    (void) block;\n    (void) exec;\n    (void) argv;\n    (void) argc;\n    term_set_fg_color(CONVERT_COLOR(WHITE, TermColor));\n    term_set_bg_color(CONVERT_COLOR(BLACK, TermColor));\n\n    *return_val = DATA_NOTHING;\n    return true;\n}\n\nbool block_term_clear(Exec* exec, Block* block, int argc, AnyValue* argv, AnyValue* return_val, ControlState control_state) {\n    (void) control_state;\n    (void) block;\n    (void) exec;\n    (void) argv;\n    (void) argc;\n    term_clear();\n\n    *return_val = DATA_NOTHING;\n    return true;\n}\n\nbool block_term_set_clear(Exec* exec, Block* block, int argc, AnyValue* argv, AnyValue* return_val, ControlState control_state) {\n    (void) control_state;\n    (void) block;\n    (void) exec;\n    (void) argc;\n    term_set_clear_color(CONVERT_COLOR(std_color_from_any(&argv[0]), TermColor));\n    *return_val = DATA_NOTHING;\n    return true;\n}\n\nbool block_input(Exec* exec, Block* block, int argc, AnyValue* argv, AnyValue* return_val, ControlState control_state) {\n    (void) control_state;\n    (void) block;\n    (void) argv;\n    (void) argc;\n    *return_val = DATA_STRING(std_term_get_input(&exec->gc));\n    return true;\n}\n\nbool block_get_char(Exec* exec, Block* block, int argc, AnyValue* argv, AnyValue* return_val, ControlState control_state) {\n    (void) control_state;\n    (void) block;\n    (void) argv;\n    (void) argc;\n\n    *return_val = DATA_STRING(std_term_get_char(&exec->gc));\n    return true;\n}\n\nbool block_random(Exec* exec, Block* block, int argc, AnyValue* argv, AnyValue* return_val, ControlState control_state) {\n    (void) control_state;\n    (void) block;\n    (void) exec;\n    (void) argc;\n\n    int min = std_integer_from_any(&argv[0]);\n    int max = std_integer_from_any(&argv[1]);\n    if (min > max) {\n        int temp = min;\n        min = max;\n        max = temp;\n    }\n    int val = GetRandomValue(min, max);\n    *return_val = DATA_INTEGER(val);\n    return true;\n}\n\nbool block_join(Exec* exec, Block* block, int argc, AnyValue* argv, AnyValue* return_val, ControlState control_state) {\n    (void) control_state;\n    (void) argc;\n    (void) block;\n\n    *return_val = DATA_STRING(std_string_join(&exec->gc, std_string_from_any(&exec->gc, &argv[0]), std_string_from_any(&exec->gc, &argv[1])));\n    return true;\n}\n\nbool block_ord(Exec* exec, Block* block, int argc, AnyValue* argv, AnyValue* return_val, ControlState control_state) {\n    (void) control_state;\n    (void) block;\n    (void) exec;\n    (void) argc;\n\n    const char* str = std_any_string_from_any(&exec->gc, &argv[0]);\n    int codepoint_size;\n    int codepoint = GetCodepoint(str, &codepoint_size);\n    *return_val = DATA_INTEGER(codepoint);\n    return true;\n}\n\nbool block_chr(Exec* exec, Block* block, int argc, AnyValue* argv, AnyValue* return_val, ControlState control_state) {\n    (void) control_state;\n    (void) block;\n    (void) argc;\n    *return_val = DATA_STRING(std_string_chr(&exec->gc, std_integer_from_any(&argv[0])));\n    return true;\n}\n\nbool block_letter_in(Exec* exec, Block* block, int argc, AnyValue* argv, AnyValue* return_val, ControlState control_state) {\n    (void) control_state;\n    (void) block;\n    (void) argc;\n    *return_val = DATA_STRING(std_string_letter_in(&exec->gc, std_integer_from_any(&argv[0]), std_string_from_any(&exec->gc, &argv[1])));\n    return true;\n}\n\nbool block_substring(Exec* exec, Block* block, int argc, AnyValue* argv, AnyValue* return_val, ControlState control_state) {\n    (void) control_state;\n    (void) block;\n    (void) argc;\n    *return_val = DATA_STRING(std_string_substring(&exec->gc, std_integer_from_any(&argv[0]), std_integer_from_any(&argv[1]), std_string_from_any(&exec->gc, &argv[2])));\n    return true;\n}\n\nbool block_length(Exec* exec, Block* block, int argc, AnyValue* argv, AnyValue* return_val, ControlState control_state) {\n    (void) control_state;\n    (void) block;\n    (void) argc;\n    *return_val = DATA_INTEGER(std_string_length(std_string_from_any(&exec->gc, &argv[0])));\n    return true;\n}\n\nbool block_unix_time(Exec* exec, Block* block, int argc, AnyValue* argv, AnyValue* return_val, ControlState control_state) {\n    (void) control_state;\n    (void) block;\n    (void) exec;\n    (void) argc;\n    (void) argv;\n    *return_val = DATA_INTEGER(time(NULL));\n    return true;\n}\n\nbool block_convert_int(Exec* exec, Block* block, int argc, AnyValue* argv, AnyValue* return_val, ControlState control_state) {\n    (void) control_state;\n    (void) block;\n    (void) exec;\n    (void) argc;\n    *return_val = DATA_INTEGER(std_integer_from_any(&argv[0]));\n    return true;\n}\n\nbool block_convert_float(Exec* exec, Block* block, int argc, AnyValue* argv, AnyValue* return_val, ControlState control_state) {\n    (void) control_state;\n    (void) block;\n    (void) exec;\n    (void) argc;\n    *return_val = DATA_FLOAT(std_float_from_any(&argv[0]));\n    return true;\n}\n\nbool block_convert_str(Exec* exec, Block* block, int argc, AnyValue* argv, AnyValue* return_val, ControlState control_state) {\n    (void) control_state;\n    (void) block;\n    (void) argc;\n    *return_val = DATA_STRING(std_string_from_any(&exec->gc, &argv[0]));\n    return true;\n}\n\nbool block_convert_bool(Exec* exec, Block* block, int argc, AnyValue* argv, AnyValue* return_val, ControlState control_state) {\n    (void) control_state;\n    (void) block;\n    (void) exec;\n    (void) argc;\n    *return_val = DATA_BOOL(std_bool_from_any(&argv[0]));\n    return true;\n}\n\nbool block_convert_color(Exec* exec, Block* block, int argc, AnyValue* argv, AnyValue* return_val, ControlState control_state) {\n    (void) control_state;\n    (void) block;\n    (void) exec;\n    (void) argc;\n    *return_val = DATA_COLOR(std_color_from_any(&argv[0]));\n    return true;\n}\n\nbool block_typeof(Exec* exec, Block* block, int argc, AnyValue* argv, AnyValue* return_val, ControlState control_state) {\n    (void) control_state;\n    (void) block;\n    (void) exec;\n    (void) argc;\n    *return_val = DATA_LITERAL((char*)type_to_str(argv[0].type));\n    return true;\n}\n\nbool block_plus(Exec* exec, Block* block, int argc, AnyValue* argv, AnyValue* return_val, ControlState control_state) {\n    (void) control_state;\n    (void) block;\n    (void) exec;\n    (void) argc;\n    if (argv[0].type == DATA_TYPE_FLOAT) {\n        *return_val = DATA_FLOAT(argv[0].data.float_val + std_float_from_any(&argv[1]));\n    } else {\n        *return_val = DATA_INTEGER(std_integer_from_any(&argv[0]) + std_integer_from_any(&argv[1]));\n    }\n    return true;\n}\n\nbool block_minus(Exec* exec, Block* block, int argc, AnyValue* argv, AnyValue* return_val, ControlState control_state) {\n    (void) control_state;\n    (void) block;\n    (void) exec;\n    (void) argc;\n    if (argv[0].type == DATA_TYPE_FLOAT) {\n        *return_val = DATA_FLOAT(argv[0].data.float_val - std_float_from_any(&argv[1]));\n    } else {\n        *return_val = DATA_INTEGER(std_integer_from_any(&argv[0]) - std_integer_from_any(&argv[1]));\n    }\n    return true;\n}\n\nbool block_mult(Exec* exec, Block* block, int argc, AnyValue* argv, AnyValue* return_val, ControlState control_state) {\n    (void) control_state;\n    (void) block;\n    (void) exec;\n    (void) argc;\n    if (argv[0].type == DATA_TYPE_FLOAT) {\n        *return_val = DATA_FLOAT(argv[0].data.float_val * std_float_from_any(&argv[1]));\n    } else {\n        *return_val = DATA_INTEGER(std_integer_from_any(&argv[0]) * std_integer_from_any(&argv[1]));\n    }\n    return true;\n}\n\nbool block_div(Exec* exec, Block* block, int argc, AnyValue* argv, AnyValue* return_val, ControlState control_state) {\n    (void) control_state;\n    (void) block;\n    (void) exec;\n    (void) argc;\n\n    if (argv[0].type == DATA_TYPE_FLOAT) {\n        *return_val = DATA_FLOAT(argv[0].data.float_val / std_float_from_any(&argv[1]));\n    } else {\n        int divisor = std_integer_from_any(&argv[1]);\n        if (divisor == 0) {\n            exec_set_error(exec, block, gettext(\"Division by zero\"));\n            return false;\n        }\n        *return_val = DATA_INTEGER(std_integer_from_any(&argv[0]) / divisor);\n    }\n    return true;\n}\n\nbool block_pow(Exec* exec, Block* block, int argc, AnyValue* argv, AnyValue* return_val, ControlState control_state) {\n    (void) control_state;\n    (void) block;\n    (void) exec;\n    (void) argc;\n    if (argv[0].type == DATA_TYPE_FLOAT) {\n        *return_val = DATA_FLOAT(pow(argv[0].data.float_val, std_float_from_any(&argv[1])));\n        return true;\n    }\n\n    int base = std_integer_from_any(&argv[0]);\n    unsigned int exp = std_integer_from_any(&argv[1]);\n    if (!exp) {\n        *return_val = DATA_INTEGER(1);\n        return true;\n    }\n\n    int result = 1;\n    while (exp) {\n        if (exp & 1) result *= base;\n        exp >>= 1;\n        base *= base;\n    }\n    *return_val = DATA_INTEGER(result);\n    return true;\n}\n\nbool block_math(Exec* exec, Block* block, int argc, AnyValue* argv, AnyValue* return_val, ControlState control_state) {\n    (void) control_state;\n    (void) block;\n    (void) exec;\n    (void) argc;\n    if (argv[0].type != DATA_TYPE_LITERAL) {\n        exec_set_error(exec, block, gettext(\"Invalid data type %s, expected %s\"), type_to_str(argv[0].type), type_to_str(DATA_TYPE_LITERAL));\n        return false;\n    }\n\n    for (int i = 0; i < MATH_LIST_LEN; i++) {\n        if (strcmp(argv[0].data.literal_val, block_math_list[i])) continue;\n        *return_val = DATA_FLOAT(block_math_func_list[i](std_float_from_any(&argv[1])));\n        return true;\n    }\n\n    exec_set_error(exec, block, gettext(\"Invalid argument %s\"), argv[0].data.literal_val);\n    return false;\n}\n\nbool block_pi(Exec* exec, Block* block, int argc, AnyValue* argv, AnyValue* return_val, ControlState control_state) {\n    (void) control_state;\n    (void) block;\n    (void) exec;\n    (void) argc;\n    (void) argv;\n    *return_val = DATA_FLOAT(M_PI);\n    return true;\n}\n\nbool block_bit_not(Exec* exec, Block* block, int argc, AnyValue* argv, AnyValue* return_val, ControlState control_state) {\n    (void) control_state;\n    (void) block;\n    (void) exec;\n    (void) argc;\n    *return_val = DATA_INTEGER(~std_integer_from_any(&argv[0]));\n    return true;\n}\n\nbool block_bit_and(Exec* exec, Block* block, int argc, AnyValue* argv, AnyValue* return_val, ControlState control_state) {\n    (void) control_state;\n    (void) block;\n    (void) exec;\n    (void) argc;\n    *return_val = DATA_INTEGER(std_integer_from_any(&argv[0]) & std_integer_from_any(&argv[1]));\n    return true;\n}\n\nbool block_bit_xor(Exec* exec, Block* block, int argc, AnyValue* argv, AnyValue* return_val, ControlState control_state) {\n    (void) control_state;\n    (void) block;\n    (void) exec;\n    (void) argc;\n    *return_val = DATA_INTEGER(std_integer_from_any(&argv[0]) ^ std_integer_from_any(&argv[1]));\n    return true;\n}\n\nbool block_bit_or(Exec* exec, Block* block, int argc, AnyValue* argv, AnyValue* return_val, ControlState control_state) {\n    (void) control_state;\n    (void) block;\n    (void) exec;\n    (void) argc;\n    *return_val = DATA_INTEGER(std_integer_from_any(&argv[0]) | std_integer_from_any(&argv[1]));\n    return true;\n}\n\nbool block_rem(Exec* exec, Block* block, int argc, AnyValue* argv, AnyValue* return_val, ControlState control_state) {\n    (void) control_state;\n    (void) block;\n    (void) exec;\n    (void) argc;\n    if (argv[0].type == DATA_TYPE_FLOAT) {\n        *return_val = DATA_FLOAT(fmod(argv[0].data.float_val, std_float_from_any(&argv[1])));\n    } else {\n        *return_val = DATA_INTEGER(std_integer_from_any(&argv[0]) % std_integer_from_any(&argv[1]));\n    }\n    return true;\n}\n\nbool block_less(Exec* exec, Block* block, int argc, AnyValue* argv, AnyValue* return_val, ControlState control_state) {\n    (void) control_state;\n    (void) block;\n    (void) exec;\n    (void) argc;\n\n    if (argv[0].type == DATA_TYPE_FLOAT) {\n        *return_val = DATA_BOOL(argv[0].data.float_val < std_float_from_any(&argv[1]));\n    } else {\n        *return_val = DATA_BOOL(std_integer_from_any(&argv[0]) < std_integer_from_any(&argv[1]));\n    }\n    return true;\n}\n\nbool block_less_eq(Exec* exec, Block* block, int argc, AnyValue* argv, AnyValue* return_val, ControlState control_state) {\n    (void) control_state;\n    (void) block;\n    (void) exec;\n    (void) argc;\n    if (argv[0].type == DATA_TYPE_FLOAT) {\n        *return_val = DATA_BOOL(argv[0].data.float_val <= std_float_from_any(&argv[1]));\n    } else {\n        *return_val = DATA_BOOL(std_integer_from_any(&argv[0]) <= std_integer_from_any(&argv[1]));\n    }\n    return true;\n}\n\nbool block_more(Exec* exec, Block* block, int argc, AnyValue* argv, AnyValue* return_val, ControlState control_state) {\n    (void) control_state;\n    (void) block;\n    (void) exec;\n    (void) argc;\n    if (argv[0].type == DATA_TYPE_FLOAT) {\n        *return_val = DATA_BOOL(argv[0].data.float_val > std_float_from_any(&argv[1]));\n    } else {\n        *return_val = DATA_BOOL(std_integer_from_any(&argv[0]) > std_integer_from_any(&argv[1]));\n    }\n    return true;\n}\n\nbool block_more_eq(Exec* exec, Block* block, int argc, AnyValue* argv, AnyValue* return_val, ControlState control_state) {\n    (void) control_state;\n    (void) block;\n    (void) exec;\n    (void) argc;\n    if (argv[0].type == DATA_TYPE_FLOAT) {\n        *return_val = DATA_BOOL(argv[0].data.float_val >= std_float_from_any(&argv[1]));\n    } else {\n        *return_val = DATA_BOOL(std_integer_from_any(&argv[0]) >= std_integer_from_any(&argv[1]));\n    }\n    return true;\n}\n\nbool block_not(Exec* exec, Block* block, int argc, AnyValue* argv, AnyValue* return_val, ControlState control_state) {\n    (void) control_state;\n    (void) block;\n    (void) exec;\n    (void) argc;\n    *return_val = DATA_BOOL(!std_bool_from_any(&argv[0]));\n    return true;\n}\n\nbool block_and(Exec* exec, Block* block, int argc, AnyValue* argv, AnyValue* return_val, ControlState control_state) {\n    (void) control_state;\n    (void) block;\n    (void) exec;\n    (void) argc;\n    *return_val = DATA_BOOL(std_bool_from_any(&argv[0]) && std_bool_from_any(&argv[1]));\n    return true;\n}\n\nbool block_or(Exec* exec, Block* block, int argc, AnyValue* argv, AnyValue* return_val, ControlState control_state) {\n    (void) control_state;\n    (void) block;\n    (void) exec;\n    (void) argc;\n    *return_val = DATA_BOOL(std_bool_from_any(&argv[0]) || std_bool_from_any(&argv[1]));\n    return true;\n}\n\nbool block_true(Exec* exec, Block* block, int argc, AnyValue* argv, AnyValue* return_val, ControlState control_state) {\n    (void) control_state;\n    (void) block;\n    (void) exec;\n    (void) argc;\n    (void) argv;\n    *return_val = DATA_BOOL(1);\n    return true;\n}\n\nbool block_false(Exec* exec, Block* block, int argc, AnyValue* argv, AnyValue* return_val, ControlState control_state) {\n    (void) control_state;\n    (void) block;\n    (void) exec;\n    (void) argc;\n    (void) argv;\n    *return_val = DATA_BOOL(0);\n    return true;\n}\n\nbool block_eq(Exec* exec, Block* block, int argc, AnyValue* argv, AnyValue* return_val, ControlState control_state) {\n    (void) control_state;\n    (void) block;\n    (void) exec;\n    (void) argc;\n    if ((argv[0].type != DATA_TYPE_LITERAL && argv[0].type != DATA_TYPE_STRING) || \n        (argv[1].type != DATA_TYPE_LITERAL && argv[1].type != DATA_TYPE_STRING)) {\n        if (argv[0].type != argv[1].type) {\n            *return_val = DATA_BOOL(0);\n            return true;\n        }\n    }\n\n    switch (argv[0].type) {\n    case DATA_TYPE_BOOL:\n    case DATA_TYPE_INTEGER:\n    case DATA_TYPE_COLOR:\n        *return_val = DATA_BOOL(argv[0].data.integer_val == argv[1].data.integer_val);\n        break;\n    case DATA_TYPE_FLOAT:\n        *return_val = DATA_BOOL(argv[0].data.float_val == argv[1].data.float_val);\n        break;\n    case DATA_TYPE_LITERAL:\n        *return_val = DATA_BOOL(!strcmp(argv[0].data.literal_val, argv[1].data.literal_val));\n        break;\n    case DATA_TYPE_STRING:\n        *return_val = DATA_BOOL(std_string_is_eq(argv[0].data.str_val, argv[1].data.str_val));\n        break;\n    case DATA_TYPE_NOTHING:\n        *return_val = DATA_BOOL(1);\n        break;\n    case DATA_TYPE_LIST:\n        *return_val = DATA_BOOL(argv[0].data.list_val == argv[1].data.list_val);\n        break;\n    case DATA_TYPE_UNKNOWN:\n    case DATA_TYPE_BLOCKDEF:\n    case DATA_TYPE_ANY:\n        exec_set_error(exec, block, gettext(\"Cannot compare type %s\"), type_to_str(argv[0].type));\n        return false;\n    }\n    return true;\n}\n\nbool block_not_eq(Exec* exec, Block* block, int argc, AnyValue* argv, AnyValue* return_val, ControlState control_state) {\n    if (!block_eq(exec, block, argc, argv, return_val, control_state)) return false;\n    return_val->data.integer_val = !return_val->data.integer_val;\n    return true;\n}\n\nbool block_exec_custom(Exec* exec, Block* block, int argc, AnyValue* argv, AnyValue* return_val, ControlState control_state) {\n    (void) control_state;\n    for (size_t i = 0; i < vector_size(exec->defined_functions); i++) {\n        if (block->blockdef == exec->defined_functions[i].blockdef) {\n            if (!exec_run_chain(exec, exec->defined_functions[i].run_chain, argc, argv, return_val)) return false;\n\n            if (return_val->type == DATA_TYPE_LIST || return_val->type == DATA_TYPE_ANY || return_val->type == DATA_TYPE_STRING) {\n                gc_add_temp_root(&exec->gc, (void*)return_val->data.literal_val);\n            }\n            return true;\n        }\n    }\n\n    exec_set_error(exec, block, gettext(\"Unknown function id \\\"%s\\\"\"), block->blockdef->id);\n    return false;\n}\n\n// Checks the arguments and returns the value from custom_argv at index if all conditions are met\nbool block_custom_arg(Exec* exec, Block* block, int argc, AnyValue* argv, AnyValue* return_val, ControlState control_state) {\n    (void) argc;\n    (void) argv;\n    (void) control_state;\n    for (size_t i = 0; i < vector_size(exec->defined_functions); i++) {\n        for (size_t j = 0; j < vector_size(exec->defined_functions[i].args); j++) {\n            DefineArgument arg = exec->defined_functions[i].args[j];\n            if (arg.blockdef == block->blockdef) {\n                *return_val = exec->chain_stack[exec->chain_stack_len - 1].custom_argv[arg.arg_ind];\n                return true;\n            }\n        }\n    }\n\n    exec_set_error(exec, block, gettext(\"Unknown argument id \\\"%s\\\"\"), block->blockdef->id);\n    return false;\n}\n\n// Modifies the internal state of the current code chain so that it returns early with the data written to .return_arg\nbool block_return(Exec* exec, Block* block, int argc, AnyValue* argv, AnyValue* return_val, ControlState control_state) {\n    (void) control_state;\n    (void) block;\n    (void) argc;\n    exec->chain_stack[exec->chain_stack_len - 1].return_arg = argv[0];\n    exec->chain_stack[exec->chain_stack_len - 1].is_returning = true;\n    *return_val = DATA_NOTHING;\n    return true;\n}\n\nbool block_gc_collect(Exec* exec, Block* block, int argc, AnyValue* argv, AnyValue* return_val, ControlState control_state) {\n    (void) control_state;\n    (void) block;\n    (void) argc;\n    (void) argv;\n    gc_collect(&exec->gc);\n    *return_val = DATA_NOTHING;\n    return true;\n}\n\n#else\n\n#define MIN_ARG_COUNT(count) \\\n    if (argc < count) { \\\n        scrap_log(LOG_ERROR, \"[LLVM] Not enough arguments! Expected: %d or more, Got: %d\", count, argc); \\\n        return false; \\\n    }\n\nLLVMValueRef arg_to_value(Exec* exec, Block* block, FuncArg arg) {\n    switch (arg.type) {\n    case DATA_TYPE_LITERAL:\n        return CONST_STRING_LITERAL(arg.data.str);\n    case DATA_TYPE_LIST:\n    case DATA_TYPE_STRING:\n    case DATA_TYPE_NOTHING:\n    case DATA_TYPE_INTEGER:\n    case DATA_TYPE_FLOAT:\n    case DATA_TYPE_BOOL:\n    case DATA_TYPE_COLOR:\n    case DATA_TYPE_ANY:\n        return arg.data.value;\n    case DATA_TYPE_BLOCKDEF:\n    case DATA_TYPE_UNKNOWN:\n        exec_set_error(exec, block, gettext(\"Cannot represent %s as LLVM value\"), type_to_str(arg.type));\n        return NULL;\n    }\n    assert(false && \"Unhandled arg_to_value\");\n}\n\nLLVMValueRef arg_to_bool(Exec* exec, Block* block, FuncArg arg) {\n    switch (arg.type) {\n    case DATA_TYPE_LITERAL:\n        return CONST_BOOLEAN(*arg.data.str != 0);\n    case DATA_TYPE_STRING: ;\n        LLVMValueRef first_char = LLVMBuildLoad2(exec->builder, LLVMInt8Type(), build_call(exec, \"std_string_get_data\", arg.data.value), \"bool_cast\");\n        return LLVMBuildICmp(exec->builder, LLVMIntNE, first_char, LLVMConstInt(LLVMInt8Type(), 0, true), \"bool_cast\");\n    case DATA_TYPE_LIST:\n    case DATA_TYPE_NOTHING:\n        return CONST_BOOLEAN(0);\n    case DATA_TYPE_INTEGER:\n        return LLVMBuildICmp(exec->builder, LLVMIntNE, arg.data.value, CONST_INTEGER(0), \"bool_cast\");\n    case DATA_TYPE_BOOL:\n        return arg.data.value;\n    case DATA_TYPE_FLOAT:\n        return LLVMBuildFCmp(exec->builder, LLVMRealONE, arg.data.value, CONST_FLOAT(0.0), \"bool_cast\");\n    case DATA_TYPE_ANY:\n        return build_call(exec, \"std_bool_from_any\", arg.data.value);\n    case DATA_TYPE_BLOCKDEF:\n    case DATA_TYPE_UNKNOWN:\n    case DATA_TYPE_COLOR:\n        exec_set_error(exec, block, gettext(\"Cannot cast type %s into %s\"), type_to_str(arg.type), type_to_str(DATA_TYPE_BOOL));\n        return NULL;\n    }\n    assert(false && \"Unhandled cast to bool\");\n}\n\nLLVMValueRef arg_to_integer(Exec* exec, Block* block, FuncArg arg) {\n    switch (arg.type) {\n    case DATA_TYPE_LITERAL:\n        return CONST_INTEGER(atoi(arg.data.str));\n    case DATA_TYPE_STRING:\n        return build_call(exec, \"atoi\", build_call(exec, \"std_string_get_data\", arg.data.value));\n    case DATA_TYPE_LIST:\n    case DATA_TYPE_NOTHING:\n        return CONST_INTEGER(0);\n    case DATA_TYPE_INTEGER:\n    case DATA_TYPE_COLOR:\n        return arg.data.value;\n    case DATA_TYPE_BOOL:\n        return LLVMBuildZExt(exec->builder, arg.data.value, LLVMInt32Type(), \"int_cast\");\n    case DATA_TYPE_FLOAT:\n        return LLVMBuildFPToSI(exec->builder, arg.data.value, LLVMInt32Type(), \"int_cast\");\n    case DATA_TYPE_ANY:\n        return build_call(exec, \"std_integer_from_any\", arg.data.value);\n    case DATA_TYPE_BLOCKDEF:\n    case DATA_TYPE_UNKNOWN:\n        exec_set_error(exec, block, gettext(\"Cannot cast type %s into %s\"), type_to_str(arg.type), type_to_str(DATA_TYPE_INTEGER));\n        return NULL;\n    }\n    assert(false && \"Unhandled cast to integer\");\n}\n\nLLVMValueRef arg_to_float(Exec* exec, Block* block, FuncArg arg) {\n    switch (arg.type) {\n    case DATA_TYPE_LITERAL:\n        return CONST_FLOAT(atof(arg.data.str));\n    case DATA_TYPE_STRING:\n        return build_call(exec, \"atof\", build_call(exec, \"std_string_get_data\", arg.data.value));\n    case DATA_TYPE_LIST:\n    case DATA_TYPE_NOTHING:\n        return CONST_FLOAT(0.0);\n    case DATA_TYPE_INTEGER:\n        return LLVMBuildSIToFP(exec->builder, arg.data.value, LLVMDoubleType(), \"float_cast\");\n    case DATA_TYPE_BOOL:\n        return LLVMBuildUIToFP(exec->builder, arg.data.value, LLVMDoubleType(), \"float_cast\");\n    case DATA_TYPE_FLOAT:\n        return arg.data.value;\n    case DATA_TYPE_ANY:\n        return build_call(exec, \"std_float_from_any\", arg.data.value);\n    case DATA_TYPE_COLOR:\n        return LLVMBuildSIToFP(exec->builder, arg.data.value, LLVMDoubleType(), \"float_cast\");\n    case DATA_TYPE_BLOCKDEF:\n    case DATA_TYPE_UNKNOWN:\n        exec_set_error(exec, block, gettext(\"Cannot cast type %s into %s\"), type_to_str(arg.type), type_to_str(DATA_TYPE_FLOAT));\n        return NULL;\n    }\n    assert(false && \"Unhandled cast to float\");\n}\n\nLLVMValueRef arg_to_any_string(Exec* exec, Block* block, FuncArg arg) {\n    switch (arg.type) {\n    case DATA_TYPE_LITERAL:\n        return CONST_STRING_LITERAL(arg.data.str);\n    case DATA_TYPE_NOTHING:\n        return CONST_STRING_LITERAL(\"nothing\");\n    case DATA_TYPE_STRING:\n        return arg.data.value;\n    case DATA_TYPE_INTEGER:\n        return build_call(exec, \"std_string_from_integer\", CONST_GC, arg.data.value);\n    case DATA_TYPE_BOOL:\n        return build_call(exec, \"std_string_from_bool\", CONST_GC, arg.data.value);\n    case DATA_TYPE_FLOAT:\n        return build_call(exec, \"std_string_from_float\", CONST_GC, arg.data.value);\n    case DATA_TYPE_ANY:\n        return build_call(exec, \"std_string_from_any\", CONST_GC, arg.data.value);\n    case DATA_TYPE_LIST:\n        return build_call(exec, \"std_string_from_literal\", CONST_GC, CONST_STRING_LITERAL(\"\"), CONST_INTEGER(0));\n    case DATA_TYPE_COLOR:\n        return build_call(exec, \"std_string_from_color\", CONST_GC, arg.data.value);\n    case DATA_TYPE_BLOCKDEF:\n    case DATA_TYPE_UNKNOWN:\n        exec_set_error(exec, block, gettext(\"Cannot cast type %s into any string\"), type_to_str(arg.type));\n        return NULL;\n    }\n    assert(false && \"Unhandled cast to any string\");\n}\n\nLLVMValueRef arg_to_string_ref(Exec* exec, Block* block, FuncArg arg) {\n    switch (arg.type) {\n    case DATA_TYPE_LITERAL:\n        return build_call(exec, \"std_string_from_literal\", CONST_GC, CONST_STRING_LITERAL(arg.data.str), CONST_INTEGER(strlen(arg.data.str)));\n    case DATA_TYPE_NOTHING:\n        return build_call(exec, \"std_string_from_literal\", CONST_GC, CONST_STRING_LITERAL(\"nothing\"), CONST_INTEGER(sizeof(\"nothing\") - 1));\n    case DATA_TYPE_LIST:\n        return build_call(exec, \"std_string_from_literal\", CONST_GC, CONST_STRING_LITERAL(\"\"), CONST_INTEGER(0));\n    case DATA_TYPE_STRING:\n        return arg.data.value;\n    case DATA_TYPE_INTEGER:\n        return build_call(exec, \"std_string_from_integer\", CONST_GC, arg.data.value);\n    case DATA_TYPE_BOOL:\n        return build_call(exec, \"std_string_from_bool\", CONST_GC, arg.data.value);\n    case DATA_TYPE_FLOAT:\n        return build_call(exec, \"std_string_from_float\", CONST_GC, arg.data.value);\n    case DATA_TYPE_ANY:\n        return build_call(exec, \"std_string_from_any\", CONST_GC, arg.data.value);\n    case DATA_TYPE_COLOR:\n        return build_call(exec, \"std_string_from_color\", CONST_GC, arg.data.value);\n    case DATA_TYPE_UNKNOWN:\n    case DATA_TYPE_BLOCKDEF:\n        exec_set_error(exec, block, gettext(\"Cannot cast type %s into %s\"), type_to_str(arg.type), type_to_str(DATA_TYPE_STRING));\n        return NULL;\n    }\n    assert(false && \"Unhandled cast to string ref\");\n}\n\nLLVMValueRef arg_to_color(Exec* exec, Block* block, FuncArg arg) {\n    switch (arg.type) {\n    case DATA_TYPE_LITERAL: ;\n        StdColor col = std_parse_color(arg.data.str);\n        return CONST_INTEGER(*(int*)&col);\n    case DATA_TYPE_STRING:\n        return build_call(exec, \"std_parse_color\", build_call(exec, \"std_string_get_data\", arg.data.value));\n    case DATA_TYPE_LIST:\n    case DATA_TYPE_NOTHING:\n        return CONST_INTEGER(0);\n    case DATA_TYPE_INTEGER:\n    case DATA_TYPE_COLOR:\n        return arg.data.value;\n    case DATA_TYPE_BOOL:\n        return LLVMBuildSelect(exec->builder, arg.data.value, CONST_INTEGER(0xffffffff), CONST_INTEGER(0xff000000), \"color_cast\");\n    case DATA_TYPE_FLOAT:\n        return LLVMBuildFPToSI(exec->builder, arg.data.value, LLVMInt32Type(), \"color_cast\");\n    case DATA_TYPE_ANY:\n        return build_call(exec, \"std_color_from_any\", arg.data.value);\n    case DATA_TYPE_BLOCKDEF:\n    case DATA_TYPE_UNKNOWN:\n        exec_set_error(exec, block, gettext(\"Cannot cast type %s into %s\"), type_to_str(arg.type), type_to_str(DATA_TYPE_COLOR));\n        return NULL;\n    }\n    assert(false && \"Unhandled cast to color\");\n}\n\nLLVMValueRef arg_to_list(Exec* exec, Block* block, FuncArg arg) {\n    switch (arg.type) {\n    case DATA_TYPE_BOOL:\n    case DATA_TYPE_NOTHING:\n    case DATA_TYPE_INTEGER:\n    case DATA_TYPE_STRING:\n    case DATA_TYPE_FLOAT:\n    case DATA_TYPE_LITERAL:\n    case DATA_TYPE_UNKNOWN:\n    case DATA_TYPE_BLOCKDEF:\n    case DATA_TYPE_COLOR:\n        exec_set_error(exec, block, gettext(\"Cannot cast type %s into %s\"), type_to_str(arg.type), type_to_str(DATA_TYPE_LIST));\n        return NULL;\n    case DATA_TYPE_LIST:\n        return arg.data.value;\n    case DATA_TYPE_ANY:\n        return build_call(exec, \"std_list_from_any\", CONST_GC, arg.data.value);\n    }\n    assert(false && \"Unhandled cast to string ref\");\n}\n\nLLVMValueRef arg_to_any(Exec* exec, Block* block, FuncArg arg) {\n    switch (arg.type) {\n    case DATA_TYPE_NOTHING:\n        return build_call_count(exec, \"std_any_from_value\", 2, CONST_GC, CONST_INTEGER(arg.type));\n    case DATA_TYPE_BOOL:\n    case DATA_TYPE_INTEGER:\n    case DATA_TYPE_STRING:\n    case DATA_TYPE_FLOAT:\n    case DATA_TYPE_LITERAL:\n    case DATA_TYPE_LIST:\n    case DATA_TYPE_COLOR:\n        return build_call_count(exec, \"std_any_from_value\", 3, CONST_GC, CONST_INTEGER(arg.type), arg_to_value(exec, block, arg));\n    case DATA_TYPE_ANY:\n        return arg.data.value;\n    case DATA_TYPE_UNKNOWN:\n    case DATA_TYPE_BLOCKDEF:\n        exec_set_error(exec, block, gettext(\"Cannot cast type %s into %s\"), type_to_str(arg.type), type_to_str(DATA_TYPE_ANY));\n        return NULL;\n    }\n    assert(false && \"Unhandled cast to string ref\");\n}\n\nFuncArg arg_cast(Exec* exec, Block* block, FuncArg arg, DataType cast_to_type) {\n    switch (cast_to_type) {\n    case DATA_TYPE_LITERAL:\n        if (arg.type == DATA_TYPE_LITERAL) return arg;\n        assert(false && \"Attempted to cast LLVM value to string literal\");\n    case DATA_TYPE_STRING:\n        return DATA_STRING(arg_to_string_ref(exec, block, arg));\n    case DATA_TYPE_INTEGER:\n        return DATA_INTEGER(arg_to_integer(exec, block, arg));\n    case DATA_TYPE_BOOL:\n        return DATA_BOOLEAN(arg_to_bool(exec, block, arg));\n    case DATA_TYPE_FLOAT:\n        return DATA_FLOAT(arg_to_float(exec, block, arg));\n    case DATA_TYPE_LIST:\n        return DATA_LIST(arg_to_list(exec, block, arg));\n    case DATA_TYPE_ANY:\n        return DATA_ANY(arg_to_any(exec, block, arg));\n    case DATA_TYPE_COLOR:\n        return DATA_COLOR(arg_to_color(exec, block, arg));\n    case DATA_TYPE_NOTHING:\n    case DATA_TYPE_UNKNOWN:\n    case DATA_TYPE_BLOCKDEF:\n        exec_set_error(exec, block, gettext(\"Cannot cast type %s into %s\"), type_to_str(arg.type), type_to_str(cast_to_type));\n        return DATA_UNKNOWN;\n    }\n    assert(false && \"Unhandled cast to value typed\");\n}\n\nbool block_return(Exec* exec, Block* block, int argc, FuncArg* argv, FuncArg* return_val, ControlState control_state) {\n    (void) control_state;\n    (void) block;\n    MIN_ARG_COUNT(1);\n\n    LLVMBasicBlockRef current = LLVMGetInsertBlock(exec->builder);\n    LLVMBasicBlockRef return_block = LLVMInsertBasicBlock(current, \"return\");\n    LLVMBasicBlockRef return_after_block = LLVMInsertBasicBlock(current, \"return_after\");\n\n    LLVMMoveBasicBlockAfter(return_after_block, current);\n    LLVMMoveBasicBlockAfter(return_block, current);\n\n    LLVMBuildBr(exec->builder, return_block);\n\n    LLVMPositionBuilderAtEnd(exec->builder, return_block);\n\n    build_call(exec, \"gc_root_restore\", CONST_GC);\n\n    LLVMTypeRef data_type = LLVMTypeOf(argv[0].data.value);\n    if (data_type == LLVMPointerType(LLVMInt8Type(), 0)) {\n        build_call(exec, \"gc_add_temp_root\", CONST_GC, argv[0].data.value);\n    }\n    \n    LLVMValueRef custom_return = arg_to_any(exec, block, argv[0]);\n    if (!custom_return) return false;\n\n    exec->gc_dirty = false;\n    LLVMBuildRet(exec->builder, custom_return);\n\n    LLVMPositionBuilderAtEnd(exec->builder, return_after_block);\n    \n    *return_val = DATA_NOTHING;\n    return true;\n}\n\nbool block_custom_arg(Exec* exec, Block* block, int argc, FuncArg* argv, FuncArg* return_val, ControlState control_state) {\n    (void) control_state;\n    (void) argc;\n    (void) argv;\n    DefineFunction* func;\n    DefineArgument* arg = get_custom_argument(exec, block->blockdef, &func);\n    if (!arg) {\n        exec_set_error(exec, block, gettext(\"Could not find function definition for argument\"));\n        return false;\n    }\n    \n    if (LLVMGetBasicBlockParent(LLVMGetInsertBlock(exec->builder)) != func->func) {\n        exec_set_error(exec, block, gettext(\"Function argument block used outside of function\"));\n        return false;\n    }\n\n    *return_val = DATA_ANY(arg->arg);\n    return true;\n}\n\nbool block_exec_custom(Exec* exec, Block* block, int argc, FuncArg* argv, FuncArg* return_val, ControlState control_state) {\n    (void) control_state;\n    if (argc > 32) {\n        exec_set_error(exec, block, gettext(\"Too many parameters passed into function. Got %d/32\"), argc);\n        return false;\n    }\n    \n    DefineFunction* define = define_function(exec, block->blockdef);\n    \n    LLVMTypeRef func_type = LLVMGlobalGetValueType(define->func);\n    LLVMValueRef func_param_list[32];\n\n    for (int i = 0; i < argc; i++) {\n        func_param_list[i] = arg_to_any(exec, block, argv[i]);\n        if (!func_param_list[i]) return false;\n    }\n\n    exec->gc_dirty = true;\n    if (exec->gc_block_stack_len > 0) {\n        exec->gc_block_stack[exec->gc_block_stack_len - 1].required = true;\n    }\n    *return_val = DATA_ANY(LLVMBuildCall2(exec->builder, func_type, define->func, func_param_list, argc, \"\"));\n    return true;\n}\n\nbool block_not_eq(Exec* exec, Block* block, int argc, FuncArg* argv, FuncArg* return_val, ControlState control_state) {\n    (void) control_state;\n    (void) block;\n    MIN_ARG_COUNT(2);\n    if (argv[0].type == DATA_TYPE_LITERAL && argv[1].type == DATA_TYPE_LITERAL) {\n        *return_val = DATA_BOOLEAN(CONST_BOOLEAN(!!strcmp(argv[0].data.str, argv[1].data.str)));\n        return true;\n    }\n\n    FuncArg left;\n    FuncArg right;\n\n    if (argv[0].type == DATA_TYPE_LITERAL) {\n        left = arg_cast(exec, block, argv[0], argv[1].type);\n        if (!left.data.value) return false;\n        right = argv[1];\n    } else if (argv[1].type == DATA_TYPE_LITERAL) {\n        left = argv[0];\n        right = arg_cast(exec, block, argv[1], argv[0].type);\n        if (!right.data.value) return false;\n    } else if (argv[0].type != argv[1].type) {\n        *return_val = DATA_BOOLEAN(CONST_BOOLEAN(1));\n        return true;\n    } else {\n        left = argv[0];\n        right = argv[1];\n    }\n\n    switch (left.type) {\n    case DATA_TYPE_NOTHING:\n        *return_val = DATA_BOOLEAN(CONST_BOOLEAN(0));\n        break;\n    case DATA_TYPE_STRING: ;\n        LLVMValueRef eq_return = build_call(exec, \"std_string_is_eq\", left.data.value, right.data.value);\n        *return_val = DATA_BOOLEAN(LLVMBuildXor(exec->builder, eq_return, CONST_BOOLEAN(1), \"string_neq\"));\n        break;\n    case DATA_TYPE_INTEGER:\n    case DATA_TYPE_COLOR:\n        *return_val = DATA_BOOLEAN(LLVMBuildICmp(exec->builder, LLVMIntNE, left.data.value, right.data.value, \"int_neq\"));\n        break;\n    case DATA_TYPE_BOOL:\n        *return_val = DATA_BOOLEAN(LLVMBuildXor(exec->builder, left.data.value, right.data.value, \"bool_neq\"));\n        break;\n    case DATA_TYPE_FLOAT:\n        *return_val = DATA_BOOLEAN(LLVMBuildFCmp(exec->builder, LLVMRealONE, left.data.value, right.data.value, \"float_neq\"));\n        break;\n    case DATA_TYPE_LIST:\n        // Compare list pointers\n        *return_val = DATA_BOOLEAN(LLVMBuildICmp(exec->builder, LLVMIntNE, left.data.value, right.data.value, \"list_neq\"));\n        break;\n    case DATA_TYPE_ANY: ;\n        LLVMValueRef eq_any_return = build_call(exec, \"std_any_is_eq\", left.data.value, right.data.value);\n        *return_val = DATA_BOOLEAN(LLVMBuildXor(exec->builder, eq_any_return, CONST_BOOLEAN(1), \"any_neq\"));\n        break;\n    case DATA_TYPE_UNKNOWN:\n    case DATA_TYPE_LITERAL:\n    case DATA_TYPE_BLOCKDEF:\n        exec_set_error(exec, block, gettext(\"Cannot compare type %s\"), type_to_str(left.type));\n        return false;\n    }\n\n    return true;\n}\n\nbool block_eq(Exec* exec, Block* block, int argc, FuncArg* argv, FuncArg* return_val, ControlState control_state) {\n    (void) control_state;\n    (void) block;\n    MIN_ARG_COUNT(2);\n    if (argv[0].type == DATA_TYPE_LITERAL && argv[1].type == DATA_TYPE_LITERAL) {\n        *return_val = DATA_BOOLEAN(CONST_BOOLEAN(!strcmp(argv[0].data.str, argv[1].data.str)));\n        return true;\n    }\n\n    FuncArg left;\n    FuncArg right;\n\n    if (argv[0].type == DATA_TYPE_LITERAL) {\n        left = arg_cast(exec, block, argv[0], argv[1].type);\n        if (!left.data.value) return false;\n        right = argv[1];\n    } else if (argv[1].type == DATA_TYPE_LITERAL) {\n        left = argv[0];\n        right = arg_cast(exec, block, argv[1], argv[0].type);\n        if (!right.data.value) return false;\n    } else if (argv[0].type != argv[1].type) {\n        *return_val = DATA_BOOLEAN(CONST_BOOLEAN(0));\n        return true;\n    } else {\n        left = argv[0];\n        right = argv[1];\n    }\n\n    switch (left.type) {\n    case DATA_TYPE_NOTHING:\n        *return_val = DATA_BOOLEAN(CONST_BOOLEAN(1));\n        break;\n    case DATA_TYPE_STRING:\n        *return_val = DATA_BOOLEAN(build_call(exec, \"std_string_is_eq\", left.data.value, right.data.value));\n        break;\n    case DATA_TYPE_BOOL:\n    case DATA_TYPE_INTEGER:\n    case DATA_TYPE_COLOR:\n        *return_val = DATA_BOOLEAN(LLVMBuildICmp(exec->builder, LLVMIntEQ, left.data.value, right.data.value, \"int_eq\"));\n        break;\n    case DATA_TYPE_FLOAT:\n        *return_val = DATA_BOOLEAN(LLVMBuildFCmp(exec->builder, LLVMRealOEQ, left.data.value, right.data.value, \"float_eq\"));\n        break;\n    case DATA_TYPE_LIST:\n        // Compare list pointers\n        *return_val = DATA_BOOLEAN(LLVMBuildICmp(exec->builder, LLVMIntEQ, left.data.value, right.data.value, \"list_eq\"));\n        break;\n    case DATA_TYPE_ANY:\n        *return_val = DATA_BOOLEAN(build_call(exec, \"std_any_is_eq\", left.data.value, right.data.value));\n        break;\n    case DATA_TYPE_UNKNOWN:\n    case DATA_TYPE_LITERAL:\n    case DATA_TYPE_BLOCKDEF:\n        exec_set_error(exec, block, gettext(\"Cannot compare type %s\"), type_to_str(left.type));\n        return false;\n    }\n\n    return true;\n}\n\nbool block_false(Exec* exec, Block* block, int argc, FuncArg* argv, FuncArg* return_val, ControlState control_state) {\n    (void) control_state;\n    (void) block;\n    (void) exec;\n    (void) argc;\n    (void) argv;\n    *return_val = DATA_BOOLEAN(CONST_BOOLEAN(0));\n    return true;\n}\n\nbool block_true(Exec* exec, Block* block, int argc, FuncArg* argv, FuncArg* return_val, ControlState control_state) {\n    (void) control_state;\n    (void) block;\n    (void) exec;\n    (void) argc;\n    (void) argv;\n    *return_val = DATA_BOOLEAN(CONST_BOOLEAN(1));\n    return true;\n}\n\nbool block_or(Exec* exec, Block* block, int argc, FuncArg* argv, FuncArg* return_val, ControlState control_state) {\n    (void) control_state;\n    (void) block;\n    MIN_ARG_COUNT(2);\n    LLVMValueRef left = arg_to_bool(exec, block, argv[0]);\n    if (!left) return false;\n    LLVMValueRef right = arg_to_bool(exec, block, argv[1]);\n    if (!right) return false;\n    *return_val = DATA_BOOLEAN(LLVMBuildOr(exec->builder, left, right, \"bool_or\"));\n    return true;\n}\n\nbool block_and(Exec* exec, Block* block, int argc, FuncArg* argv, FuncArg* return_val, ControlState control_state) {\n    (void) control_state;\n    (void) block;\n    MIN_ARG_COUNT(2);\n    LLVMValueRef left = arg_to_bool(exec, block, argv[0]);\n    if (!left) return false;\n    LLVMValueRef right = arg_to_bool(exec, block, argv[1]);\n    if (!right) return false;\n    *return_val = DATA_BOOLEAN(LLVMBuildAnd(exec->builder, left, right, \"bool_and\"));\n    return true;\n}\n\nbool block_not(Exec* exec, Block* block, int argc, FuncArg* argv, FuncArg* return_val, ControlState control_state) {\n    (void) control_state;\n    (void) block;\n    MIN_ARG_COUNT(1);\n    LLVMValueRef value = arg_to_bool(exec, block, argv[0]);\n    if (!value) return false;\n    *return_val = DATA_BOOLEAN(LLVMBuildXor(exec->builder, value, CONST_BOOLEAN(1), \"not\"));\n    return true;\n}\n\nbool block_more_eq(Exec* exec, Block* block, int argc, FuncArg* argv, FuncArg* return_val, ControlState control_state) {\n    (void) control_state;\n    (void) block;\n    MIN_ARG_COUNT(2);\n    if (argv[0].type != DATA_TYPE_FLOAT) {\n        LLVMValueRef left = arg_to_integer(exec, block, argv[0]);\n        if (!left) return false;\n        LLVMValueRef right = arg_to_integer(exec, block, argv[1]);\n        if (!right) return false;\n        *return_val = DATA_BOOLEAN(LLVMBuildICmp(exec->builder, LLVMIntSGE, left, right, \"int_more_or_eq\"));\n    } else {\n        LLVMValueRef right = arg_to_float(exec, block, argv[1]);\n        if (!right) return false;\n        *return_val = DATA_BOOLEAN(LLVMBuildFCmp(exec->builder, LLVMRealOGE, argv[0].data.value, right, \"float_more_or_eq\"));\n    }\n    return true;\n}\n\nbool block_more(Exec* exec, Block* block, int argc, FuncArg* argv, FuncArg* return_val, ControlState control_state) {\n    (void) control_state;\n    (void) block;\n    MIN_ARG_COUNT(2);\n    if (argv[0].type != DATA_TYPE_FLOAT) {\n        LLVMValueRef left = arg_to_integer(exec, block, argv[0]);\n        if (!left) return false;\n        LLVMValueRef right = arg_to_integer(exec, block, argv[1]);\n        if (!right) return false;\n        *return_val = DATA_BOOLEAN(LLVMBuildICmp(exec->builder, LLVMIntSGT, left, right, \"int_more\"));\n    } else {\n        LLVMValueRef right = arg_to_float(exec, block, argv[1]);\n        if (!right) return false;\n        *return_val = DATA_BOOLEAN(LLVMBuildFCmp(exec->builder, LLVMRealOGT, argv[0].data.value, right, \"float_more\"));\n    }\n    return true;\n}\n\nbool block_less_eq(Exec* exec, Block* block, int argc, FuncArg* argv, FuncArg* return_val, ControlState control_state) {\n    (void) control_state;\n    (void) block;\n    MIN_ARG_COUNT(2);\n    if (argv[0].type != DATA_TYPE_FLOAT) {\n        LLVMValueRef left = arg_to_integer(exec, block, argv[0]);\n        if (!left) return false;\n        LLVMValueRef right = arg_to_integer(exec, block, argv[1]);\n        if (!right) return false;\n        *return_val = DATA_BOOLEAN(LLVMBuildICmp(exec->builder, LLVMIntSLE, left, right, \"int_less_or_eq\"));\n    } else {\n        LLVMValueRef right = arg_to_float(exec, block, argv[1]);\n        if (!right) return false;\n        *return_val = DATA_BOOLEAN(LLVMBuildFCmp(exec->builder, LLVMRealOLE, argv[0].data.value, right, \"float_less_or_eq\"));\n    }\n    return true;\n}\n\nbool block_less(Exec* exec, Block* block, int argc, FuncArg* argv, FuncArg* return_val, ControlState control_state) {\n    (void) control_state;\n    (void) block;\n    MIN_ARG_COUNT(2);\n    if (argv[0].type != DATA_TYPE_FLOAT) {\n        LLVMValueRef left = arg_to_integer(exec, block, argv[0]);\n        if (!left) return false;\n        LLVMValueRef right = arg_to_integer(exec, block, argv[1]);\n        if (!right) return false;\n        *return_val = DATA_BOOLEAN(LLVMBuildICmp(exec->builder, LLVMIntSLT, left, right, \"int_less\"));\n    } else {\n        LLVMValueRef right = arg_to_float(exec, block, argv[1]);\n        if (!right) return false;\n        *return_val = DATA_BOOLEAN(LLVMBuildFCmp(exec->builder, LLVMRealOLT, argv[0].data.value, right, \"float_less\"));\n    }\n    return true;\n}\n\nbool block_bit_or(Exec* exec, Block* block, int argc, FuncArg* argv, FuncArg* return_val, ControlState control_state) {\n    (void) control_state;\n    (void) block;\n    MIN_ARG_COUNT(2);\n    LLVMValueRef left = arg_to_integer(exec, block, argv[0]);\n    if (!left) return false;\n    LLVMValueRef right = arg_to_integer(exec, block, argv[1]);\n    if (!right) return false;\n    *return_val = DATA_INTEGER(LLVMBuildOr(exec->builder, left, right, \"or\"));\n    return true;\n}\n\nbool block_bit_xor(Exec* exec, Block* block, int argc, FuncArg* argv, FuncArg* return_val, ControlState control_state) {\n    (void) control_state;\n    (void) block;\n    MIN_ARG_COUNT(2);\n    LLVMValueRef left = arg_to_integer(exec, block, argv[0]);\n    if (!left) return false;\n    LLVMValueRef right = arg_to_integer(exec, block, argv[1]);\n    if (!right) return false;\n    *return_val = DATA_INTEGER(LLVMBuildXor(exec->builder, left, right, \"xor\"));\n    return true;\n}\n\nbool block_bit_and(Exec* exec, Block* block, int argc, FuncArg* argv, FuncArg* return_val, ControlState control_state) {\n    (void) control_state;\n    (void) block;\n    MIN_ARG_COUNT(2);\n    LLVMValueRef left = arg_to_integer(exec, block, argv[0]);\n    if (!left) return false;\n    LLVMValueRef right = arg_to_integer(exec, block, argv[1]);\n    if (!right) return false;\n    *return_val = DATA_INTEGER(LLVMBuildAnd(exec->builder, left, right, \"and\"));\n    return true;\n}\n\nbool block_bit_not(Exec* exec, Block* block, int argc, FuncArg* argv, FuncArg* return_val, ControlState control_state) {\n    (void) control_state;\n    (void) block;\n    MIN_ARG_COUNT(1);\n    LLVMValueRef integer_val = arg_to_integer(exec, block, argv[0]);\n    if (!integer_val) return false;\n\n    LLVMValueRef add_op = LLVMBuildAdd(exec->builder, integer_val, CONST_INTEGER(1), \"\");\n    *return_val = DATA_INTEGER(LLVMBuildNeg(exec->builder, add_op, \"bit_not\"));\n    return true;\n}\n\nbool block_pi(Exec* exec, Block* block, int argc, FuncArg* argv, FuncArg* return_val, ControlState control_state) {\n    (void) control_state;\n    (void) block;\n    (void) exec;\n    (void) argc;\n    (void) argv;\n    *return_val = DATA_FLOAT(CONST_FLOAT(M_PI));\n    return true;\n}\n\nbool block_math(Exec* exec, Block* block, int argc, FuncArg* argv, FuncArg* return_val, ControlState control_state) {\n    (void) control_state;\n    (void) block;\n    MIN_ARG_COUNT(2);\n    if (argv[0].type != DATA_TYPE_LITERAL) return false;\n\n    LLVMValueRef value = arg_to_float(exec, block, argv[1]);\n    if (!value) return false;\n\n    for (int i = 0; i < MATH_LIST_LEN; i++) {\n        if (strcmp(argv[0].data.str, block_math_list[i])) continue;\n        *return_val = DATA_FLOAT(build_call(exec, argv[0].data.str, value));\n        return true;\n    }\n\n    return false;\n}\n\nbool block_pow(Exec* exec, Block* block, int argc, FuncArg* argv, FuncArg* return_val, ControlState control_state) {\n    (void) control_state;\n    (void) block;\n    MIN_ARG_COUNT(2);\n\n    if (argv[0].type != DATA_TYPE_FLOAT) {\n        LLVMValueRef left = arg_to_integer(exec, block, argv[0]);\n        if (!left) return false;\n        LLVMValueRef right = arg_to_integer(exec, block, argv[1]);\n        if (!right) return false;\n\n        *return_val = DATA_INTEGER(build_call(exec, \"std_int_pow\", left, right));\n    } else {\n        LLVMValueRef right = arg_to_float(exec, block, argv[1]);\n        if (!right) return false;\n        *return_val = DATA_FLOAT(build_call(exec, \"pow\", argv[0].data.value, right));\n    }\n\n    return true;\n}\n\nbool block_rem(Exec* exec, Block* block, int argc, FuncArg* argv, FuncArg* return_val, ControlState control_state) {\n    (void) control_state;\n    (void) block;\n    MIN_ARG_COUNT(2);\n    if (argv[0].type != DATA_TYPE_FLOAT) {\n        LLVMValueRef left = arg_to_integer(exec, block, argv[0]);\n        if (!left) return false;\n        LLVMValueRef right = arg_to_integer(exec, block, argv[1]);\n        if (!right) return false;\n\n        if (!LLVMIsConstant(right)) {\n            LLVMBasicBlockRef current_branch = LLVMGetInsertBlock(exec->builder);\n            LLVMBasicBlockRef non_zero_branch = LLVMInsertBasicBlock(current_branch, \"non_zero_cond\");\n            LLVMBasicBlockRef zero_branch = LLVMInsertBasicBlock(current_branch, \"zero_cond\");\n            LLVMBasicBlockRef phi_branch = LLVMInsertBasicBlock(current_branch, \"cond_after\");\n\n            LLVMMoveBasicBlockAfter(phi_branch, current_branch);\n            LLVMMoveBasicBlockAfter(zero_branch, current_branch);\n            LLVMMoveBasicBlockAfter(non_zero_branch, current_branch);\n\n            LLVMValueRef condition = LLVMBuildICmp(exec->builder, LLVMIntEQ, right, CONST_INTEGER(0), \"\");\n            LLVMBuildCondBr(exec->builder, condition, zero_branch, non_zero_branch);\n\n            LLVMPositionBuilderAtEnd(exec->builder, non_zero_branch);\n            LLVMValueRef out = LLVMBuildSRem(exec->builder, left, right, \"\");\n            LLVMBuildBr(exec->builder, phi_branch);\n\n            LLVMPositionBuilderAtEnd(exec->builder, zero_branch);\n            LLVMBuildBr(exec->builder, phi_branch);\n\n            LLVMPositionBuilderAtEnd(exec->builder, phi_branch);\n\n            *return_val = DATA_INTEGER(LLVMBuildPhi(exec->builder, LLVMInt32Type(), \"div\"));\n\n            LLVMValueRef vals[] = { CONST_INTEGER(0), out };\n            LLVMBasicBlockRef blocks[] = { zero_branch, non_zero_branch };\n            LLVMAddIncoming(return_val->data.value, vals, blocks, ARRLEN(blocks));\n        } else {\n            *return_val = DATA_INTEGER(LLVMBuildSRem(exec->builder, left, right, \"rem\"));\n        }\n    } else {\n        LLVMValueRef right = arg_to_float(exec, block, argv[1]);\n        if (!right) return false;\n        *return_val = DATA_FLOAT(LLVMBuildFRem(exec->builder, argv[0].data.value, right, \"rem\"));\n    }\n\n    if (LLVMIsPoison(return_val->data.value)) {\n        exec_set_error(exec, block, gettext(\"Division by zero\"));\n        return false;\n    }\n    return true;\n}\n\nbool block_div(Exec* exec, Block* block, int argc, FuncArg* argv, FuncArg* return_val, ControlState control_state) {\n    (void) control_state;\n    (void) block;\n    MIN_ARG_COUNT(2);\n    if (argv[0].type != DATA_TYPE_FLOAT) {\n        LLVMValueRef left = arg_to_integer(exec, block, argv[0]);\n        if (!left) return false;\n        LLVMValueRef right = arg_to_integer(exec, block, argv[1]);\n        if (!right) return false;\n\n        if (!LLVMIsConstant(right)) {\n            LLVMBasicBlockRef current_branch = LLVMGetInsertBlock(exec->builder);\n            LLVMBasicBlockRef non_zero_branch = LLVMInsertBasicBlock(current_branch, \"non_zero_cond\");\n            LLVMBasicBlockRef zero_branch = LLVMInsertBasicBlock(current_branch, \"zero_cond\");\n            LLVMBasicBlockRef phi_branch = LLVMInsertBasicBlock(current_branch, \"cond_after\");\n\n            LLVMMoveBasicBlockAfter(phi_branch, current_branch);\n            LLVMMoveBasicBlockAfter(zero_branch, current_branch);\n            LLVMMoveBasicBlockAfter(non_zero_branch, current_branch);\n\n            LLVMValueRef condition = LLVMBuildICmp(exec->builder, LLVMIntEQ, right, CONST_INTEGER(0), \"\");\n            LLVMBuildCondBr(exec->builder, condition, zero_branch, non_zero_branch);\n\n            LLVMPositionBuilderAtEnd(exec->builder, non_zero_branch);\n            LLVMValueRef out = LLVMBuildSDiv(exec->builder, left, right, \"\");\n            LLVMBuildBr(exec->builder, phi_branch);\n\n            LLVMPositionBuilderAtEnd(exec->builder, zero_branch);\n            LLVMBuildBr(exec->builder, phi_branch);\n\n            LLVMPositionBuilderAtEnd(exec->builder, phi_branch);\n\n            *return_val = DATA_INTEGER(LLVMBuildPhi(exec->builder, LLVMInt32Type(), \"div\"));\n\n            LLVMValueRef vals[] = { CONST_INTEGER(0), out };\n            LLVMBasicBlockRef blocks[] = { zero_branch, non_zero_branch };\n            LLVMAddIncoming(return_val->data.value, vals, blocks, ARRLEN(blocks));\n        } else {\n            *return_val = DATA_INTEGER(LLVMBuildSDiv(exec->builder, left, right, \"div\"));\n        }\n    } else {\n        LLVMValueRef right = arg_to_float(exec, block, argv[1]);\n        if (!right) return false;\n        *return_val = DATA_FLOAT(LLVMBuildFDiv(exec->builder, argv[0].data.value, right, \"div\"));\n    }\n\n    if (LLVMIsPoison(return_val->data.value)) {\n        exec_set_error(exec, block, gettext(\"Division by zero\"));\n        return false;\n    }\n    return true;\n}\n\nbool block_mult(Exec* exec, Block* block, int argc, FuncArg* argv, FuncArg* return_val, ControlState control_state) {\n    (void) control_state;\n    (void) block;\n    MIN_ARG_COUNT(2);\n    if (argv[0].type != DATA_TYPE_FLOAT) {\n        LLVMValueRef left = arg_to_integer(exec, block, argv[0]);\n        if (!left) return false;\n        LLVMValueRef right = arg_to_integer(exec, block, argv[1]);\n        if (!right) return false;\n        *return_val = DATA_INTEGER(LLVMBuildMul(exec->builder, left, right, \"mul\"));\n    } else {\n        LLVMValueRef right = arg_to_float(exec, block, argv[1]);\n        if (!right) return false;\n        *return_val = DATA_FLOAT(LLVMBuildFMul(exec->builder, argv[0].data.value, right, \"mul\"));\n    }\n    return true;\n}\n\nbool block_minus(Exec* exec, Block* block, int argc, FuncArg* argv, FuncArg* return_val, ControlState control_state) {\n    (void) control_state;\n    (void) block;\n    MIN_ARG_COUNT(2);\n    if (argv[0].type != DATA_TYPE_FLOAT) {\n        LLVMValueRef left = arg_to_integer(exec, block, argv[0]);\n        if (!left) return false;\n        LLVMValueRef right = arg_to_integer(exec, block, argv[1]);\n        if (!right) return false;\n        *return_val = DATA_INTEGER(LLVMBuildSub(exec->builder, left, right, \"sub\"));\n    } else {\n        LLVMValueRef right = arg_to_float(exec, block, argv[1]);\n        if (!right) return false;\n        *return_val = DATA_FLOAT(LLVMBuildFSub(exec->builder, argv[0].data.value, right, \"sub\"));\n    }\n    return true;\n}\n\nbool block_plus(Exec* exec, Block* block, int argc, FuncArg* argv, FuncArg* return_val, ControlState control_state) {\n    (void) control_state;\n    (void) block;\n    MIN_ARG_COUNT(2);\n    if (argv[0].type != DATA_TYPE_FLOAT) {\n        LLVMValueRef left = arg_to_integer(exec, block, argv[0]);\n        if (!left) return false;\n        LLVMValueRef right = arg_to_integer(exec, block, argv[1]);\n        if (!right) return false;\n        *return_val = DATA_INTEGER(LLVMBuildAdd(exec->builder, left, right, \"add\"));\n    } else {\n        LLVMValueRef right = arg_to_float(exec, block, argv[1]);\n        if (!right) return false;\n        *return_val = DATA_FLOAT(LLVMBuildFAdd(exec->builder, argv[0].data.value, right, \"add\"));\n    }\n    return true;\n}\n\n// TODO: Make this block evaluate arguments lazily\nbool block_typeof(Exec* exec, Block* block, int argc, FuncArg* argv, FuncArg* return_val, ControlState control_state) {\n    (void) control_state;\n    (void) block;\n    (void) exec;\n    (void) argc;\n    *return_val = (FuncArg) {\n        .type = DATA_TYPE_LITERAL,\n        .data = (FuncArgData) {\n            .str = (char*)type_to_str(argv[0].type),\n        },\n    };\n    return true;\n}\n\nbool block_convert_color(Exec* exec, Block* block, int argc, FuncArg* argv, FuncArg* return_val, ControlState control_state) {\n    (void) control_state;\n    (void) block;\n    MIN_ARG_COUNT(1);\n    LLVMValueRef value = arg_to_color(exec, block, argv[0]);\n    if (!value) return false;\n    *return_val = DATA_COLOR(value);\n    return true;\n}\n\nbool block_convert_bool(Exec* exec, Block* block, int argc, FuncArg* argv, FuncArg* return_val, ControlState control_state) {\n    (void) control_state;\n    (void) block;\n    MIN_ARG_COUNT(1);\n    LLVMValueRef value = arg_to_bool(exec, block, argv[0]);\n    if (!value) return false;\n    *return_val = DATA_BOOLEAN(value);\n    return true;\n}\n\nbool block_convert_str(Exec* exec, Block* block, int argc, FuncArg* argv, FuncArg* return_val, ControlState control_state) {\n    (void) control_state;\n    (void) block;\n    MIN_ARG_COUNT(1);\n    LLVMValueRef value = arg_to_string_ref(exec, block, argv[0]);\n    if (!value) return false;\n    *return_val = DATA_STRING(value);\n    return true;\n}\n\nbool block_convert_float(Exec* exec, Block* block, int argc, FuncArg* argv, FuncArg* return_val, ControlState control_state) {\n    (void) control_state;\n    (void) block;\n    MIN_ARG_COUNT(1);\n    LLVMValueRef value = arg_to_float(exec, block, argv[0]);\n    if (!value) return false;\n    *return_val = DATA_FLOAT(value);\n    return true;\n}\n\nbool block_convert_int(Exec* exec, Block* block, int argc, FuncArg* argv, FuncArg* return_val, ControlState control_state) {\n    (void) control_state;\n    (void) block;\n    MIN_ARG_COUNT(1);\n    LLVMValueRef value = arg_to_integer(exec, block, argv[0]);\n    if (!value) return false;\n    *return_val = DATA_INTEGER(value);\n    return true;\n}\n\nbool block_unix_time(Exec* exec, Block* block, int argc, FuncArg* argv, FuncArg* return_val, ControlState control_state) {\n    (void) control_state;\n    (void) block;\n    (void) argc;\n    (void) argv;\n    *return_val = DATA_INTEGER(build_call(exec, \"time\", LLVMConstPointerNull(LLVMPointerType(LLVMVoidType(), 0))));\n    return true;\n}\n\nbool block_length(Exec* exec, Block* block, int argc, FuncArg* argv, FuncArg* return_val, ControlState control_state) {\n    (void) control_state;\n    (void) block;\n    MIN_ARG_COUNT(1);\n    LLVMValueRef str = arg_to_string_ref(exec, block, argv[0]);\n    if (!str) return false;\n\n    *return_val = DATA_INTEGER(build_call(exec, \"std_string_length\", str));\n    return true;\n}\n\nbool block_substring(Exec* exec, Block* block, int argc, FuncArg* argv, FuncArg* return_val, ControlState control_state) {\n    (void) control_state;\n    (void) block;\n    MIN_ARG_COUNT(3);\n    LLVMValueRef begin = arg_to_integer(exec, block, argv[0]);\n    if (!begin) return false;\n\n    LLVMValueRef end = arg_to_integer(exec, block, argv[1]);\n    if (!end) return false;\n\n    LLVMValueRef str = arg_to_string_ref(exec, block, argv[2]);\n    if (!str) return false;\n\n    *return_val = DATA_STRING(build_call(exec, \"std_string_substring\", CONST_GC, begin, end, str));\n    return true;\n}\n\nbool block_letter_in(Exec* exec, Block* block, int argc, FuncArg* argv, FuncArg* return_val, ControlState control_state) {\n    (void) control_state;\n    (void) block;\n    MIN_ARG_COUNT(2);\n    LLVMValueRef target = arg_to_integer(exec, block, argv[0]);\n    if (!target) return false;\n\n    LLVMValueRef str = arg_to_string_ref(exec, block, argv[1]);\n    if (!str) return false;\n\n    *return_val = DATA_STRING(build_call(exec, \"std_string_letter_in\", CONST_GC, target, str));\n    return true;\n}\n\nbool block_chr(Exec* exec, Block* block, int argc, FuncArg* argv, FuncArg* return_val, ControlState control_state) {\n    (void) control_state;\n    (void) block;\n    MIN_ARG_COUNT(1);\n    LLVMValueRef value = arg_to_integer(exec, block, argv[0]);\n    if (!value) return false;\n\n    *return_val = DATA_STRING(build_call(exec, \"std_string_chr\", CONST_GC, value));\n    return true;\n}\n\nbool block_ord(Exec* exec, Block* block, int argc, FuncArg* argv, FuncArg* return_val, ControlState control_state) {\n    (void) control_state;\n    (void) block;\n    MIN_ARG_COUNT(1);\n    LLVMValueRef str = arg_to_any_string(exec, block, argv[0]);\n    if (!str) return false;\n\n    *return_val = DATA_INTEGER(build_call(exec, \"std_string_ord\", str));\n    return true;\n}\n\nbool block_join(Exec* exec, Block* block, int argc, FuncArg* argv, FuncArg* return_val, ControlState control_state) {\n    (void) control_state;\n    (void) block;\n    MIN_ARG_COUNT(2);\n    LLVMValueRef left = arg_to_string_ref(exec, block, argv[0]);\n    if (!left) return false;\n    LLVMValueRef right = arg_to_string_ref(exec, block, argv[1]);\n    if (!right) return false;\n\n    *return_val = DATA_STRING(build_call(exec, \"std_string_join\", CONST_GC, left, right));\n    return true;\n}\n\nbool block_random(Exec* exec, Block* block, int argc, FuncArg* argv, FuncArg* return_val, ControlState control_state) {\n    (void) control_state;\n    (void) block;\n    MIN_ARG_COUNT(2);\n\n    exec->build_random = true;\n\n    LLVMValueRef min = arg_to_integer(exec, block, argv[0]);\n    if (!min) return false;\n    LLVMValueRef max = arg_to_integer(exec, block, argv[1]);\n    if (!max) return false;\n\n    *return_val = DATA_INTEGER(build_call(exec, \"std_get_random\", min, max));\n    return true;\n}\n\nbool block_get_char(Exec* exec, Block* block, int argc, FuncArg* argv, FuncArg* return_val, ControlState control_state) {\n    (void) control_state;\n    (void) block;\n    (void) argc;\n    (void) argv;\n    *return_val = DATA_STRING(build_call(exec, \"std_term_get_char\", CONST_GC));\n    return true;\n}\n\nbool block_input(Exec* exec, Block* block, int argc, FuncArg* argv, FuncArg* return_val, ControlState control_state) {\n    (void) control_state;\n    (void) block;\n    (void) argc;\n    (void) argv;\n    *return_val = DATA_STRING(build_call(exec, \"std_term_get_input\", CONST_GC));\n    return true;\n}\n\nbool block_term_set_clear(Exec* exec, Block* block, int argc, FuncArg* argv, FuncArg* return_val, ControlState control_state) {\n    (void) control_state;\n    (void) block;\n    (void) argc;\n\n    LLVMValueRef color = arg_to_color(exec, block, argv[0]);\n    if (!color) return false;\n\n    build_call(exec, \"std_term_set_clear_color\", color);\n    *return_val = DATA_NOTHING;\n    return true;\n}\n\nbool block_term_clear(Exec* exec, Block* block, int argc, FuncArg* argv, FuncArg* return_val, ControlState control_state) {\n    (void) control_state;\n    (void) block;\n    (void) argc;\n    (void) argv;\n    build_call(exec, \"std_term_clear\");\n    *return_val = DATA_NOTHING;\n    return true;\n}\n\nbool block_reset_color(Exec* exec, Block* block, int argc, FuncArg* argv, FuncArg* return_val, ControlState control_state) {\n    (void) control_state;\n    (void) block;\n    (void) argc;\n    (void) argv;\n    // For some reason gcc throws a warning in release mode with constant color struct so it is passed as integer\n    build_call(exec, \"std_term_set_fg_color\", CONST_INTEGER(0xffffffff));\n    build_call(exec, \"std_term_set_bg_color\", CONST_INTEGER(*(int*)&(BLACK)));\n    *return_val = DATA_NOTHING;\n    return true;\n}\n\nbool block_set_bg_color(Exec* exec, Block* block, int argc, FuncArg* argv, FuncArg* return_val, ControlState control_state) {\n    (void) control_state;\n    (void) block;\n    MIN_ARG_COUNT(1);\n\n    LLVMValueRef color = arg_to_color(exec, block, argv[0]);\n    if (!color) return false;\n\n    build_call(exec, \"std_term_set_bg_color\", color);\n    *return_val = DATA_NOTHING;\n    return true;\n}\n\nbool block_set_fg_color(Exec* exec, Block* block, int argc, FuncArg* argv, FuncArg* return_val, ControlState control_state) {\n    (void) control_state;\n    (void) block;\n    MIN_ARG_COUNT(1);\n\n    LLVMValueRef color = arg_to_color(exec, block, argv[0]);\n    if (!color) return false;\n\n    build_call(exec, \"std_term_set_fg_color\", color);\n    *return_val = DATA_NOTHING;\n    return true;\n}\n\nbool block_set_cursor(Exec* exec, Block* block, int argc, FuncArg* argv, FuncArg* return_val, ControlState control_state) {\n    (void) control_state;\n    (void) block;\n    MIN_ARG_COUNT(2);\n    LLVMValueRef x = arg_to_integer(exec, block, argv[0]);\n    if (!x) return false;\n    LLVMValueRef y = arg_to_integer(exec, block, argv[1]);\n    if (!y) return false;\n\n    build_call(exec, \"std_term_set_cursor\", x, y);\n    *return_val = DATA_NOTHING;\n    return true;\n}\n\nbool block_cursor_max_y(Exec* exec, Block* block, int argc, FuncArg* argv, FuncArg* return_val, ControlState control_state) {\n    (void) control_state;\n    (void) block;\n    (void) argc;\n    (void) argv;\n    *return_val = DATA_INTEGER(build_call(exec, \"std_term_cursor_max_y\"));\n    return true;\n}\n\nbool block_cursor_max_x(Exec* exec, Block* block, int argc, FuncArg* argv, FuncArg* return_val, ControlState control_state) {\n    (void) control_state;\n    (void) block;\n    (void) argc;\n    (void) argv;\n    *return_val = DATA_INTEGER(build_call(exec, \"std_term_cursor_max_x\"));\n    return true;\n}\n\nbool block_cursor_y(Exec* exec, Block* block, int argc, FuncArg* argv, FuncArg* return_val, ControlState control_state) {\n    (void) control_state;\n    (void) block;\n    (void) argc;\n    (void) argv;\n    *return_val = DATA_INTEGER(build_call(exec, \"std_term_cursor_y\"));\n    return true;\n}\n\nbool block_cursor_x(Exec* exec, Block* block, int argc, FuncArg* argv, FuncArg* return_val, ControlState control_state) {\n    (void) control_state;\n    (void) block;\n    (void) argc;\n    (void) argv;\n    *return_val = DATA_INTEGER(build_call(exec, \"std_term_cursor_x\"));\n    return true;\n}\n\nbool block_print(Exec* exec, Block* block, int argc, FuncArg* argv, FuncArg* return_val, ControlState control_state) {\n    (void) control_state;\n    (void) block;\n    MIN_ARG_COUNT(1);\n\n    switch (argv[0].type) {\n    case DATA_TYPE_LITERAL:\n        *return_val = DATA_INTEGER(*argv[0].data.str\n                                   ? build_call(exec, \"std_term_print_str\", CONST_STRING_LITERAL(argv[0].data.str))\n                                   : CONST_INTEGER(0));\n        return true;\n    case DATA_TYPE_STRING:\n        *return_val = DATA_INTEGER(build_call(exec, \"std_term_print_str\", build_call(exec, \"std_string_get_data\", argv[0].data.value)));\n        return true;\n    case DATA_TYPE_NOTHING:\n        *return_val = DATA_INTEGER(CONST_INTEGER(0));\n        return true;\n    case DATA_TYPE_INTEGER:\n        *return_val = DATA_INTEGER(build_call(exec, \"std_term_print_integer\", argv[0].data.value));\n        return true;\n    case DATA_TYPE_BOOL:\n        *return_val = DATA_INTEGER(build_call(exec, \"std_term_print_bool\", argv[0].data.value));\n        return true;\n    case DATA_TYPE_FLOAT:\n        *return_val = DATA_INTEGER(build_call(exec, \"std_term_print_float\", argv[0].data.value));\n        return true;\n    case DATA_TYPE_LIST:\n        *return_val = DATA_INTEGER(build_call(exec, \"std_term_print_list\", argv[0].data.value));\n        return true;\n    case DATA_TYPE_ANY:\n        *return_val = DATA_INTEGER(build_call(exec, \"std_term_print_any\", argv[0].data.value));\n        return true;\n    case DATA_TYPE_COLOR:\n        *return_val = DATA_INTEGER(build_call(exec, \"std_term_print_color\", argv[0].data.value));\n        return true;\n    case DATA_TYPE_UNKNOWN:\n    case DATA_TYPE_BLOCKDEF:\n        exec_set_error(exec, block, gettext(\"Invalid type %s in print function\"), type_to_str(argv[0].type));\n        return false;\n    }\n\n    exec_set_error(exec, block, gettext(\"Unhandled type %s in print function\"), type_to_str(argv[0].type));\n    return false;\n}\n\nbool block_println(Exec* exec, Block* block, int argc, FuncArg* argv, FuncArg* return_val, ControlState control_state) {\n    (void) control_state;\n    (void) block;\n    MIN_ARG_COUNT(1);\n    block_print(exec, block, argc, argv, return_val, control_state);\n    build_call(exec, \"std_term_print_str\", CONST_STRING_LITERAL(\"\\n\"));\n    *return_val = DATA_INTEGER(LLVMBuildAdd(exec->builder, return_val->data.value, CONST_INTEGER(1), \"add\"));\n    return true;\n}\n\nbool block_list_length(Exec* exec, Block* block, int argc, FuncArg* argv, FuncArg* return_val, ControlState control_state) {\n    (void) control_state;\n    MIN_ARG_COUNT(1);\n\n    LLVMValueRef list = arg_to_list(exec, block, argv[0]);\n    if (!list) return false;\n\n    *return_val = DATA_INTEGER(build_call(exec, \"std_list_length\", list));\n    return true;\n}\n\nbool block_list_set(Exec* exec, Block* block, int argc, FuncArg* argv, FuncArg* return_val, ControlState control_state) {\n    (void) control_state;\n    MIN_ARG_COUNT(3);\n\n    LLVMValueRef list = arg_to_list(exec, block, argv[0]);\n    if (!list) return false;\n    LLVMValueRef index = arg_to_integer(exec, block, argv[1]);\n    if (!index) return false;\n\n    if (argv[2].type == DATA_TYPE_NOTHING) {\n        build_call_count(exec, \"std_list_set\", 3, list, index, CONST_INTEGER(argv[2].type));\n    } else {\n        build_call_count(exec, \"std_list_set\", 4, list, index, CONST_INTEGER(argv[2].type), arg_to_value(exec, block, argv[2]));\n    }\n    *return_val = DATA_NOTHING;\n    return true;\n}\n\nbool block_list_get(Exec* exec, Block* block, int argc, FuncArg* argv, FuncArg* return_val, ControlState control_state) {\n    (void) control_state;\n    (void) block;\n    MIN_ARG_COUNT(2);\n\n    LLVMValueRef list = arg_to_list(exec, block, argv[0]);\n    if (!list) return false;\n    LLVMValueRef index = arg_to_integer(exec, block, argv[1]);\n    if (!index) return false;\n\n    *return_val = DATA_ANY(build_call(exec, \"std_list_get\", CONST_GC, list, index));\n    return true;\n}\n\nbool block_list_add(Exec* exec, Block* block, int argc, FuncArg* argv, FuncArg* return_val, ControlState control_state) {\n    (void) control_state;\n    (void) block;\n    MIN_ARG_COUNT(2);\n\n    LLVMValueRef list = arg_to_list(exec, block, argv[0]);\n    if (!list) return false;\n\n    if (argv[1].type == DATA_TYPE_NOTHING) {\n        build_call_count(exec, \"std_list_add\", 3, CONST_GC, list, CONST_INTEGER(argv[1].type));\n    } else {\n        build_call_count(exec, \"std_list_add\", 4, CONST_GC, list, CONST_INTEGER(argv[1].type), arg_to_value(exec, block, argv[1]));\n    }\n    *return_val = DATA_NOTHING;\n    return true;\n}\n\nbool block_create_list(Exec* exec, Block* block, int argc, FuncArg* argv, FuncArg* return_val, ControlState control_state) {\n    (void) control_state;\n    (void) block;\n    (void) argc;\n    (void) argv;\n    *return_val = DATA_LIST(build_call(exec, \"std_list_new\", CONST_GC));\n    return true;\n}\n\nbool block_gc_collect(Exec* exec, Block* block, int argc, FuncArg* argv, FuncArg* return_val, ControlState control_state) {\n    (void) control_state;\n    (void) block;\n    (void) argc;\n    (void) argv;\n    build_call(exec, \"gc_collect\", CONST_GC);\n    *return_val = DATA_NOTHING;\n    return true;\n}\n\nbool block_set_var(Exec* exec, Block* block, int argc, FuncArg* argv, FuncArg* return_val, ControlState control_state) {\n    (void) control_state;\n    (void) block;\n    MIN_ARG_COUNT(2);\n    if (argv[0].type != DATA_TYPE_LITERAL) {\n        exec_set_error(exec, block, gettext(\"Invalid data type %s, expected %s\"), type_to_str(argv[0].type), type_to_str(DATA_TYPE_LITERAL));\n        return false;\n    }\n\n    Variable* var = variable_get(exec, argv[0].data.str);\n    if (!var) {\n        exec_set_error(exec, block, gettext(\"Variable with name \\\"%s\\\" does not exist in the current scope\"), argv[0].data.str);\n        return false;\n    }\n\n    if (argv[1].type != var->value.type) {\n        exec_set_error(exec, block, gettext(\"Assign to variable \\\"%s\\\" of type %s with incompatible type %s\"), argv[0].data.str, type_to_str(var->value.type), type_to_str(argv[1].type));\n        return false;\n    }\n\n    if (var->value.type == DATA_TYPE_LITERAL) {\n        var->value = argv[1];\n        *return_val = argv[1];\n        return true;\n    }\n\n    LLVMBuildStore(exec->builder, argv[1].data.value, var->value.data.value);\n    *return_val = argv[1];\n    return true;\n}\n\nbool block_get_var(Exec* exec, Block* block, int argc, FuncArg* argv, FuncArg* return_val, ControlState control_state) {\n    (void) control_state;\n    (void) block;\n    MIN_ARG_COUNT(1);\n    if (argv[0].type != DATA_TYPE_LITERAL) {\n        exec_set_error(exec, block, gettext(\"Invalid data type %s, expected %s\"), type_to_str(argv[0].type), type_to_str(DATA_TYPE_LITERAL));\n        return false;\n    }\n\n    Variable* var = variable_get(exec, argv[0].data.str);\n    if (!var) {\n        exec_set_error(exec, block, gettext(\"Variable with name \\\"%s\\\" does not exist in the current scope\"), argv[0].data.str);\n        return false;\n    }\n\n    if (var->value.type == DATA_TYPE_LITERAL) {\n        *return_val = var->value;\n        return true;\n    }\n\n    *return_val = (FuncArg) {\n        .type = var->value.type,\n        .data = (FuncArgData) {\n            .value = LLVMBuildLoad2(exec->builder, var->type, var->value.data.value, \"get_var\"),\n        },\n    };\n    return true;\n}\n\nbool block_declare_var(Exec* exec, Block* block, int argc, FuncArg* argv, FuncArg* return_val, ControlState control_state) {\n    (void) control_state;\n    (void) block;\n    MIN_ARG_COUNT(2);\n\n    if (block->parent) {\n        exec_set_error(exec, block, gettext(\"Variable declarations are not allowed inside an argument\"));\n        return false;\n    }\n    \n    if (argv[0].type != DATA_TYPE_LITERAL) {\n        exec_set_error(exec, block, gettext(\"Invalid data type %s, expected %s\"), type_to_str(argv[0].type), type_to_str(DATA_TYPE_LITERAL));\n        return false;\n    }\n\n    if (*argv[0].data.str == 0) {\n        exec_set_error(exec, block, gettext(\"Cannot declare variable with empty name\"));\n        return false;\n    }\n\n    if (argv[1].type == DATA_TYPE_NOTHING) {\n        exec_set_error(exec, block, gettext(\"Cannot declare a variable with zero sized type (i.e. Nothing)\"));\n        return false;\n    }\n\n    LLVMValueRef func_current = LLVMGetBasicBlockParent(LLVMGetInsertBlock(exec->builder));\n    LLVMValueRef func_main = LLVMGetNamedFunction(exec->module, MAIN_NAME);\n\n    if (argv[1].type == DATA_TYPE_LITERAL) {\n        Variable var = (Variable) {\n            .type = LLVMVoidType(),\n            .value = argv[1],\n            .name = argv[0].data.str,\n        };\n        if (exec->control_stack_len == 0 && func_current == func_main) {\n            global_variable_add(exec, var);\n        } else {\n            if (!variable_stack_push(exec, block, var)) return false;\n        }\n        *return_val = argv[1];\n        return true;\n    }\n\n    LLVMTypeRef data_type = LLVMTypeOf(argv[1].data.value);\n\n    Variable var = (Variable) {\n        .type = data_type,\n        .value = (FuncArg) {\n            .type = argv[1].type,\n            .data = (FuncArgData) {\n                .value = NULL,\n            },\n        },\n        .name = argv[0].data.str,\n    };\n\n    if (exec->control_stack_len == 0 && func_current == func_main) {\n        var.value.data.value = LLVMAddGlobal(exec->module, data_type, argv[0].data.str);\n        LLVMSetInitializer(var.value.data.value, LLVMConstNull(LLVMTypeOf(argv[1].data.value)));\n        global_variable_add(exec, var);\n    } else {\n        var.value.data.value = LLVMBuildAlloca(exec->builder, data_type, argv[0].data.str);\n        variable_stack_push(exec, block, var);\n    }\n\n    LLVMBuildStore(exec->builder, argv[1].data.value, var.value.data.value);\n    if (data_type == LLVMPointerType(LLVMInt8Type(), 0)) {\n        build_call(exec, \"gc_add_root\", CONST_GC, var.value.data.value);\n    }\n    *return_val = argv[1];\n    return true;\n}\n\nbool block_sleep(Exec* exec, Block* block, int argc, FuncArg* argv, FuncArg* return_val, ControlState control_state) {\n    (void) control_state;\n    (void) block;\n    MIN_ARG_COUNT(1);\n    LLVMValueRef usecs = arg_to_integer(exec, block, argv[0]);\n    if (!usecs) return false;\n\n    *return_val = DATA_INTEGER(build_call(exec, \"std_sleep\", usecs));\n    return true;\n}\n\nbool block_while(Exec* exec, Block* block, int argc, FuncArg* argv, FuncArg* return_val, ControlState control_state) {\n    (void) block;\n\n    if (control_state == CONTROL_STATE_BEGIN) {\n        MIN_ARG_COUNT(1);\n\n        LLVMValueRef condition = arg_to_bool(exec, block, argv[0]);\n        if (!condition) return false;\n\n        LLVMBasicBlockRef control_block = LLVMGetInsertBlock(exec->builder);\n        LLVMBasicBlockRef while_body_branch = LLVMInsertBasicBlock(control_block, \"while\");\n        LLVMBasicBlockRef while_end_branch = LLVMInsertBasicBlock(control_block, \"while_end\");\n\n        LLVMMoveBasicBlockAfter(while_end_branch, control_block);\n        LLVMMoveBasicBlockAfter(while_body_branch, control_block);\n\n        LLVMBuildCondBr(exec->builder, condition, while_body_branch, while_end_branch);\n\n        LLVMPositionBuilderAtEnd(exec->builder, while_body_branch);\n        if (!build_gc_root_begin(exec, block)) return false;\n\n        control_data_stack_push_data(control_block, LLVMBasicBlockRef);\n        control_data_stack_push_data(while_end_branch, LLVMBasicBlockRef);\n    } else if (control_state == CONTROL_STATE_END) {\n        LLVMBasicBlockRef control_block, while_end_branch;\n        control_data_stack_pop_data(while_end_branch, LLVMBasicBlockRef);\n        control_data_stack_pop_data(control_block, LLVMBasicBlockRef);\n\n        if (!build_gc_root_end(exec, block)) return false;\n        LLVMBuildBr(exec->builder, control_block);\n\n        LLVMPositionBuilderAtEnd(exec->builder, while_end_branch);\n\n        *return_val = DATA_BOOLEAN(CONST_BOOLEAN(1));\n    } else {\n        exec_set_error(exec, block, \"Invalid control state\");\n        return false;\n    }\n\n    return true;\n}\n\nbool block_repeat(Exec* exec, Block* block, int argc, FuncArg* argv, FuncArg* return_val, ControlState control_state) {\n    (void) block;\n\n    if (control_state == CONTROL_STATE_BEGIN) {\n        MIN_ARG_COUNT(1);\n\n        LLVMValueRef counter = arg_to_integer(exec, block, argv[0]);\n        if (!counter) return false;\n\n        LLVMBasicBlockRef current = LLVMGetInsertBlock(exec->builder);\n        LLVMBasicBlockRef repeat_branch = LLVMInsertBasicBlock(current, \"repeat\");\n        LLVMBasicBlockRef repeat_body_branch = LLVMInsertBasicBlock(current, \"repeat_body\");\n        LLVMBasicBlockRef repeat_end_branch = LLVMInsertBasicBlock(current, \"repeat_end\");\n\n        LLVMMoveBasicBlockAfter(repeat_end_branch, current);\n        LLVMMoveBasicBlockAfter(repeat_body_branch, current);\n        LLVMMoveBasicBlockAfter(repeat_branch, current);\n\n        LLVMBuildBr(exec->builder, repeat_branch);\n        LLVMPositionBuilderAtEnd(exec->builder, repeat_branch);\n\n        LLVMValueRef phi_node = LLVMBuildPhi(exec->builder, LLVMInt32Type(), \"repeat_phi\");\n        LLVMValueRef index = LLVMBuildSub(exec->builder, phi_node, CONST_INTEGER(1), \"repeat_index_sub\");\n        LLVMValueRef index_test = LLVMBuildICmp(exec->builder, LLVMIntSLT, index, CONST_INTEGER(0), \"repeat_loop_check\");\n        LLVMBuildCondBr(exec->builder, index_test, repeat_end_branch, repeat_body_branch);\n\n        LLVMValueRef vals[] = { counter };\n        LLVMBasicBlockRef blocks[] = { current };\n        LLVMAddIncoming(phi_node, vals, blocks, ARRLEN(blocks));\n\n        LLVMPositionBuilderAtEnd(exec->builder, repeat_body_branch);\n        if (!build_gc_root_begin(exec, block)) return false;\n\n        control_data_stack_push_data(phi_node, LLVMValueRef);\n        control_data_stack_push_data(vals[0], LLVMValueRef);\n        control_data_stack_push_data(index, LLVMValueRef);\n        control_data_stack_push_data(repeat_branch, LLVMBasicBlockRef);\n        control_data_stack_push_data(repeat_end_branch, LLVMBasicBlockRef);\n    } else if (control_state == CONTROL_STATE_END) {\n        LLVMBasicBlockRef current = LLVMGetInsertBlock(exec->builder);\n        LLVMBasicBlockRef loop_end, loop;\n        LLVMValueRef phi_node, index, start_index;\n        control_data_stack_pop_data(loop_end, LLVMBasicBlockRef);\n        control_data_stack_pop_data(loop, LLVMBasicBlockRef);\n        control_data_stack_pop_data(index, LLVMValueRef);\n        control_data_stack_pop_data(start_index, LLVMValueRef);\n        control_data_stack_pop_data(phi_node, LLVMValueRef);\n\n        if (!build_gc_root_end(exec, block)) return false;\n        LLVMBuildBr(exec->builder, loop);\n\n        LLVMValueRef vals[] = { index };\n        LLVMBasicBlockRef blocks[] = { current };\n        LLVMAddIncoming(phi_node, vals, blocks, ARRLEN(blocks));\n\n        LLVMPositionBuilderAtEnd(exec->builder, loop_end);\n\n        *return_val = DATA_BOOLEAN(LLVMBuildICmp(exec->builder, LLVMIntSGT, start_index, CONST_INTEGER(0), \"\"));\n    } else {\n        exec_set_error(exec, block, \"Invalid control state\");\n        return false;\n    }\n\n    return true;\n}\n\nbool block_else(Exec* exec, Block* block, int argc, FuncArg* argv, FuncArg* return_val, ControlState control_state) {\n    (void) block;\n\n    if (control_state == CONTROL_STATE_BEGIN) {\n        MIN_ARG_COUNT(1);\n\n        LLVMValueRef value = arg_to_bool(exec, block, argv[0]);\n        if (!value) return false;\n\n        LLVMBasicBlockRef current_branch = LLVMGetInsertBlock(exec->builder);\n        LLVMBasicBlockRef else_branch = LLVMInsertBasicBlock(current_branch, \"else\");\n        LLVMBasicBlockRef end_branch = LLVMInsertBasicBlock(current_branch, \"end_else\");\n\n        LLVMMoveBasicBlockAfter(end_branch, current_branch);\n        LLVMMoveBasicBlockAfter(else_branch, current_branch);\n\n        LLVMBuildCondBr(exec->builder, value, end_branch, else_branch);\n\n        LLVMPositionBuilderAtEnd(exec->builder, else_branch);\n        if (!build_gc_root_begin(exec, block)) return false;\n\n        control_data_stack_push_data(end_branch, LLVMBasicBlockRef);\n    } else if (control_state == CONTROL_STATE_END) {\n        LLVMBasicBlockRef end_branch;\n        control_data_stack_pop_data(end_branch, LLVMBasicBlockRef);\n\n        if (!build_gc_root_end(exec, block)) return false;\n        LLVMBuildBr(exec->builder, end_branch);\n        LLVMPositionBuilderAtEnd(exec->builder, end_branch);\n        *return_val = DATA_BOOLEAN(CONST_BOOLEAN(1));\n    } else {\n        exec_set_error(exec, block, \"Invalid control state\");\n        return false;\n    }\n\n    return true;\n}\n\nbool block_else_if(Exec* exec, Block* block, int argc, FuncArg* argv, FuncArg* return_val, ControlState control_state) {\n    (void) block;\n\n    if (control_state == CONTROL_STATE_BEGIN) {\n        MIN_ARG_COUNT(2);\n\n        LLVMValueRef prev_val = arg_to_bool(exec, block, argv[0]);\n        if (!prev_val) return false;\n        LLVMValueRef condition = arg_to_bool(exec, block, argv[1]);\n        if (!condition) return false;\n\n        LLVMBasicBlockRef current_branch = LLVMGetInsertBlock(exec->builder);\n        LLVMBasicBlockRef else_if_check_branch = LLVMInsertBasicBlock(current_branch, \"else_if_check\");\n        LLVMBasicBlockRef else_if_branch = LLVMInsertBasicBlock(current_branch, \"else_if\");\n        LLVMBasicBlockRef else_if_fail_branch = LLVMInsertBasicBlock(current_branch, \"else_if_fail\");\n        LLVMBasicBlockRef end_branch = LLVMInsertBasicBlock(current_branch, \"end_else_if\");\n\n        LLVMMoveBasicBlockAfter(end_branch, current_branch);\n        LLVMMoveBasicBlockAfter(else_if_fail_branch, current_branch);\n        LLVMMoveBasicBlockAfter(else_if_branch, current_branch);\n        LLVMMoveBasicBlockAfter(else_if_check_branch, current_branch);\n\n        LLVMBuildCondBr(exec->builder, prev_val, end_branch, else_if_check_branch);\n        control_data_stack_push_data(current_branch, LLVMBasicBlockRef);\n\n        LLVMPositionBuilderAtEnd(exec->builder, else_if_check_branch);\n        LLVMBuildCondBr(exec->builder, condition, else_if_branch, else_if_fail_branch);\n\n        LLVMPositionBuilderAtEnd(exec->builder, else_if_fail_branch);\n        LLVMBuildBr(exec->builder, end_branch);\n\n        LLVMPositionBuilderAtEnd(exec->builder, else_if_branch);\n        if (!build_gc_root_begin(exec, block)) return false;\n\n        control_data_stack_push_data(else_if_fail_branch, LLVMBasicBlockRef);\n        control_data_stack_push_data(end_branch, LLVMBasicBlockRef);\n    } else if (control_state == CONTROL_STATE_END) {\n        LLVMBasicBlockRef else_if_branch = LLVMGetInsertBlock(exec->builder);\n        LLVMBasicBlockRef top_branch, fail_branch, end_branch;\n        control_data_stack_pop_data(end_branch, LLVMBasicBlockRef);\n        control_data_stack_pop_data(fail_branch, LLVMBasicBlockRef);\n        control_data_stack_pop_data(top_branch, LLVMBasicBlockRef);\n\n        if (!build_gc_root_end(exec, block)) return false;\n        LLVMBuildBr(exec->builder, end_branch);\n\n        LLVMPositionBuilderAtEnd(exec->builder, end_branch);\n        *return_val = DATA_BOOLEAN(LLVMBuildPhi(exec->builder, LLVMInt1Type(), \"\"));\n\n        LLVMValueRef vals[] = { CONST_BOOLEAN(1), CONST_BOOLEAN(1), CONST_BOOLEAN(0) };\n        LLVMBasicBlockRef blocks[] = { top_branch, else_if_branch, fail_branch };\n        LLVMAddIncoming(return_val->data.value, vals, blocks, ARRLEN(blocks));\n    } else {\n        exec_set_error(exec, block, \"Invalid control state\");\n        return false;\n    }\n\n    return true;\n}\n\nbool block_if(Exec* exec, Block* block, int argc, FuncArg* argv, FuncArg* return_val, ControlState control_state) {\n    (void) block;\n\n    if (control_state == CONTROL_STATE_BEGIN) {\n        MIN_ARG_COUNT(1);\n\n        LLVMValueRef condition = arg_to_bool(exec, block, argv[0]);\n        if (!condition) return false;\n\n        LLVMBasicBlockRef current_branch = LLVMGetInsertBlock(exec->builder);\n        LLVMBasicBlockRef then_branch = LLVMInsertBasicBlock(current_branch, \"if_cond\");\n        // This is needed for a phi block to determine if this condition has failed. The result of this phi node is then passed into a C-end block\n        LLVMBasicBlockRef fail_branch = LLVMInsertBasicBlock(current_branch, \"if_fail\");\n        LLVMBasicBlockRef end_branch = LLVMInsertBasicBlock(current_branch, \"end_if\");\n\n        LLVMMoveBasicBlockAfter(end_branch, current_branch);\n        LLVMMoveBasicBlockAfter(fail_branch, current_branch);\n        LLVMMoveBasicBlockAfter(then_branch, current_branch);\n\n        LLVMBuildCondBr(exec->builder, condition, then_branch, fail_branch);\n\n        LLVMPositionBuilderAtEnd(exec->builder, fail_branch);\n        LLVMBuildBr(exec->builder, end_branch);\n\n        LLVMPositionBuilderAtEnd(exec->builder, then_branch);\n        if (!build_gc_root_begin(exec, block)) return false;\n\n        control_data_stack_push_data(fail_branch, LLVMBasicBlockRef);\n        control_data_stack_push_data(end_branch, LLVMBasicBlockRef);\n    } else if (control_state == CONTROL_STATE_END) {\n        LLVMBasicBlockRef then_branch = LLVMGetInsertBlock(exec->builder);\n        LLVMBasicBlockRef fail_branch, end_branch;\n        control_data_stack_pop_data(end_branch, LLVMBasicBlockRef);\n        control_data_stack_pop_data(fail_branch, LLVMBasicBlockRef);\n\n        if (!build_gc_root_end(exec, block)) return false;\n        LLVMBuildBr(exec->builder, end_branch);\n\n        LLVMPositionBuilderAtEnd(exec->builder, end_branch);\n        *return_val = DATA_BOOLEAN(LLVMBuildPhi(exec->builder, LLVMInt1Type(), \"\"));\n\n        LLVMValueRef vals[] = { CONST_BOOLEAN(1), CONST_BOOLEAN(0) };\n        LLVMBasicBlockRef blocks[] = { then_branch, fail_branch };\n        LLVMAddIncoming(return_val->data.value, vals, blocks, ARRLEN(blocks));\n    } else {\n        exec_set_error(exec, block, \"Invalid control state\");\n        return false;\n    }\n\n    return true;\n}\n\nbool block_loop(Exec* exec, Block* block, int argc, FuncArg* argv, FuncArg* return_val, ControlState control_state) {\n    (void) block;\n    (void) argc;\n    (void) argv;\n\n    if (control_state == CONTROL_STATE_BEGIN) {\n        LLVMBasicBlockRef current = LLVMGetInsertBlock(exec->builder);\n        LLVMBasicBlockRef loop = LLVMInsertBasicBlock(current, \"loop\");\n        LLVMBasicBlockRef loop_end = LLVMInsertBasicBlock(current, \"loop_end\");\n\n        LLVMMoveBasicBlockAfter(loop_end, current);\n        LLVMMoveBasicBlockAfter(loop, current);\n\n        LLVMBuildBr(exec->builder, loop);\n        LLVMPositionBuilderAtEnd(exec->builder, loop);\n        if (!build_gc_root_begin(exec, block)) return false;\n\n        control_data_stack_push_data(loop, LLVMBasicBlockRef);\n        control_data_stack_push_data(loop_end, LLVMBasicBlockRef);\n    } else if (control_state == CONTROL_STATE_END) {\n        LLVMBasicBlockRef loop;\n        LLVMBasicBlockRef loop_end;\n        control_data_stack_pop_data(loop_end, LLVMBasicBlockRef);\n        control_data_stack_pop_data(loop, LLVMBasicBlockRef);\n\n        if (!build_gc_root_end(exec, block)) return false;\n        LLVMBuildBr(exec->builder, loop);\n        LLVMPositionBuilderAtEnd(exec->builder, loop_end);\n        *return_val = DATA_BOOLEAN(CONST_BOOLEAN(0));\n    } else {\n        exec_set_error(exec, block, \"Invalid control state\");\n        return false;\n    }\n\n    return true;\n}\n\nbool block_do_nothing(Exec* exec, Block* block, int argc, FuncArg* argv, FuncArg* return_val, ControlState control_state) {\n    (void) block;\n    (void) argc;\n    (void) argv;\n\n    if (control_state == CONTROL_STATE_BEGIN) {\n        if (!build_gc_root_begin(exec, block)) return false;\n    } else if (control_state == CONTROL_STATE_END) {\n        if (!build_gc_root_end(exec, block)) return false;\n    } else {\n        exec_set_error(exec, block, \"Invalid control state\");\n        return false;\n    }\n\n    *return_val = DATA_NOTHING;\n    return true;\n}\n\nbool block_noop(Exec* exec, Block* block, int argc, FuncArg* argv, FuncArg* return_val, ControlState control_state) {\n    (void) control_state;\n    (void) block;\n    (void) exec;\n    (void) argc;\n    (void) argv;\n    *return_val = DATA_NOTHING;\n    return true;\n}\n\nbool block_define_block(Exec* exec, Block* block, int argc, FuncArg* argv, FuncArg* return_val, ControlState control_state) {\n    (void) control_state;\n    (void) block;\n    MIN_ARG_COUNT(1);\n\n    if (argv[0].type != DATA_TYPE_BLOCKDEF) {\n        exec_set_error(exec, block, gettext(\"Invalid data type %s, expected %s\"), type_to_str(argv[0].type), type_to_str(DATA_TYPE_BLOCKDEF));\n        return false;\n    }\n\n    DefineFunction* define = define_function(exec, argv[0].data.blockdef);\n\n    LLVMBasicBlockRef entry = LLVMAppendBasicBlock(define->func, \"entry\");\n    LLVMPositionBuilderAtEnd(exec->builder, entry);\n\n    exec->gc_value = LLVMBuildLoad2(exec->builder, LLVMInt64Type(), LLVMGetNamedGlobal(exec->module, \"gc\"), \"get_gc\");\n\n    build_call(exec, \"gc_root_save\", CONST_GC);\n\n    if (!build_gc_root_begin(exec, NULL)) return false;\n\n    *return_val = DATA_NOTHING;\n    return true;\n}\n\nbool block_on_start(Exec* exec, Block* block, int argc, FuncArg* argv, FuncArg* return_val, ControlState control_state) {\n    (void) control_state;\n    (void) block;\n    (void) argc;\n    (void) argv;\n    LLVMValueRef main_func = LLVMGetNamedFunction(exec->module, MAIN_NAME);\n    LLVMBasicBlockRef last_block = LLVMGetLastBasicBlock(main_func);\n    LLVMPositionBuilderAtEnd(exec->builder, last_block);\n    *return_val = DATA_NOTHING;\n    return true;\n}\n\n#endif // USE_INTERPRETER\n\n// Creates and registers blocks (commands) for the Vm/Exec virtual machine\nvoid register_blocks(Vm* vm) {\n    BlockCategory cat;\n    cat = block_category_new(gettext(\"Control\"),  (Color) CATEGORY_CONTROL_COLOR);\n    BlockCategory* cat_control = block_category_register(cat);\n    cat = block_category_new(gettext(\"Terminal\"), (Color) CATEGORY_TERMINAL_COLOR);\n    BlockCategory* cat_terminal = block_category_register(cat);\n    cat = block_category_new(gettext(\"Math\"),     (Color) CATEGORY_MATH_COLOR);\n    BlockCategory* cat_math = block_category_register(cat);\n    cat = block_category_new(gettext(\"Logic\"),    (Color) CATEGORY_LOGIC_COLOR);\n    BlockCategory* cat_logic = block_category_register(cat);\n    cat = block_category_new(gettext(\"Data\"),     (Color) CATEGORY_DATA_COLOR);\n    BlockCategory* cat_data = block_category_register(cat);\n    cat = block_category_new(gettext(\"Misc.\"),    (Color) CATEGORY_MISC_COLOR);\n    BlockCategory* cat_misc = block_category_register(cat);\n\n    BlockdefImage term_img = (BlockdefImage) {\n        .image_ptr = &assets.textures.icon_term,\n        .image_color = (BlockdefColor) { 0xff, 0xff, 0xff, 0xff },\n    };\n\n    BlockdefImage list_img = (BlockdefImage) {\n        .image_ptr = &assets.textures.icon_list,\n        .image_color = (BlockdefColor) { 0xff, 0xff, 0xff, 0xff },\n    };\n\n    Blockdef* sc_end = blockdef_new(\"end\", BLOCKTYPE_END, (BlockdefColor) { 0x77, 0x77, 0x77, 0xff }, block_noop);\n    blockdef_add_text(sc_end, gettext(\"End\"));\n    blockdef_register(vm, sc_end);\n\n    Blockdef* on_start = blockdef_new(\"on_start\", BLOCKTYPE_HAT, (BlockdefColor) { 0xff, 0x77, 0x00, 0xFF }, block_on_start);\n    blockdef_add_text(on_start, gettext(\"When\"));\n    blockdef_add_image(on_start, (BlockdefImage) { .image_ptr = &assets.textures.button_run, .image_color = (BlockdefColor) { 0x60, 0xff, 0x00, 0xff } });\n    blockdef_add_text(on_start, gettext(\"clicked\"));\n    blockdef_register(vm, on_start);\n    block_category_add_blockdef(cat_control, on_start);\n\n    block_category_add_label(cat_control, gettext(\"Conditionals\"), (Color) CATEGORY_CONTROL_COLOR);\n\n    Blockdef* sc_if = blockdef_new(\"if\", BLOCKTYPE_CONTROL, (BlockdefColor) CATEGORY_CONTROL_COLOR, block_if);\n    blockdef_add_text(sc_if, gettext(\"If\"));\n    blockdef_add_argument(sc_if, \"\", gettext(\"cond.\"), BLOCKCONSTR_UNLIMITED);\n    blockdef_add_text(sc_if, gettext(\", then\"));\n    blockdef_register(vm, sc_if);\n    block_category_add_blockdef(cat_control, sc_if);\n\n    Blockdef* sc_else_if = blockdef_new(\"else_if\", BLOCKTYPE_CONTROLEND, (BlockdefColor) CATEGORY_CONTROL_COLOR, block_else_if);\n    blockdef_add_text(sc_else_if, gettext(\"Else if\"));\n    blockdef_add_argument(sc_else_if, \"\", gettext(\"cond.\"), BLOCKCONSTR_UNLIMITED);\n    blockdef_add_text(sc_else_if, gettext(\", then\"));\n    blockdef_register(vm, sc_else_if);\n    block_category_add_blockdef(cat_control, sc_else_if);\n\n    Blockdef* sc_else = blockdef_new(\"else\", BLOCKTYPE_CONTROLEND, (BlockdefColor) CATEGORY_CONTROL_COLOR, block_else);\n    blockdef_add_text(sc_else, gettext(\"Else\"));\n    blockdef_register(vm, sc_else);\n    block_category_add_blockdef(cat_control, sc_else);\n\n    block_category_add_label(cat_control, gettext(\"Loops\"), (Color) CATEGORY_CONTROL_COLOR);\n\n    Blockdef* sc_loop = blockdef_new(\"loop\", BLOCKTYPE_CONTROL, (BlockdefColor) CATEGORY_CONTROL_COLOR, block_loop);\n    blockdef_add_text(sc_loop, gettext(\"Loop\"));\n    blockdef_register(vm, sc_loop);\n    block_category_add_blockdef(cat_control, sc_loop);\n\n    Blockdef* sc_repeat = blockdef_new(\"repeat\", BLOCKTYPE_CONTROL, (BlockdefColor) CATEGORY_CONTROL_COLOR, block_repeat);\n    blockdef_add_text(sc_repeat, gettext(\"Repeat\"));\n    blockdef_add_argument(sc_repeat, \"10\", \"0\", BLOCKCONSTR_UNLIMITED);\n    blockdef_add_text(sc_repeat, gettext(\"times\"));\n    blockdef_register(vm, sc_repeat);\n    block_category_add_blockdef(cat_control, sc_repeat);\n\n    Blockdef* sc_while = blockdef_new(\"while\", BLOCKTYPE_CONTROL, (BlockdefColor) CATEGORY_CONTROL_COLOR, block_while);\n    blockdef_add_text(sc_while, gettext(\"While\"));\n    blockdef_add_argument(sc_while, \"\", gettext(\"cond.\"), BLOCKCONSTR_UNLIMITED);\n    blockdef_register(vm, sc_while);\n    block_category_add_blockdef(cat_control, sc_while);\n\n    block_category_add_label(cat_control, gettext(\"Functions\"), (Color) { 0x99, 0x00, 0xff, 0xff });\n\n    Blockdef* sc_define_block = blockdef_new(\"define_block\", BLOCKTYPE_HAT, (BlockdefColor) { 0x99, 0x00, 0xff, 0xff }, block_define_block);\n    blockdef_add_image(sc_define_block, (BlockdefImage) { .image_ptr = &assets.textures.icon_special, .image_color = (BlockdefColor) { 0xff, 0xff, 0xff, 0xff } });\n    blockdef_add_text(sc_define_block, gettext(\"Define\"));\n    blockdef_add_blockdef_editor(sc_define_block);\n    blockdef_register(vm, sc_define_block);\n    block_category_add_blockdef(cat_control, sc_define_block);\n\n    Blockdef* sc_return = blockdef_new(\"return\", BLOCKTYPE_NORMAL, (BlockdefColor) { 0x99, 0x00, 0xff, 0xff }, block_return);\n    blockdef_add_image(sc_return, (BlockdefImage) { .image_ptr = &assets.textures.icon_special, .image_color = (BlockdefColor) { 0xff, 0xff, 0xff, 0xff } });\n    blockdef_add_text(sc_return, gettext(\"Return\"));\n    blockdef_add_argument(sc_return, \"\", gettext(\"any\"), BLOCKCONSTR_UNLIMITED);\n    blockdef_register(vm, sc_return);\n    block_category_add_blockdef(cat_control, sc_return);\n\n    block_category_add_label(cat_terminal, gettext(\"Input/Output\"), (Color) CATEGORY_TERMINAL_COLOR);\n\n    Blockdef* sc_print = blockdef_new(\"print\", BLOCKTYPE_NORMAL, (BlockdefColor) CATEGORY_TERMINAL_COLOR, block_print);\n    blockdef_add_image(sc_print, term_img);\n    blockdef_add_text(sc_print, gettext(\"Print\"));\n    blockdef_add_argument(sc_print, gettext(\"Hello, scrap!\"), gettext(\"Abc\"), BLOCKCONSTR_UNLIMITED);\n    blockdef_register(vm, sc_print);\n    block_category_add_blockdef(cat_terminal, sc_print);\n\n    Blockdef* sc_println = blockdef_new(\"println\", BLOCKTYPE_NORMAL, (BlockdefColor) CATEGORY_TERMINAL_COLOR, block_println);\n    blockdef_add_image(sc_println, term_img);\n    blockdef_add_text(sc_println, gettext(\"Print line\"));\n    blockdef_add_argument(sc_println, gettext(\"Hello, scrap!\"), gettext(\"Abc\"), BLOCKCONSTR_UNLIMITED);\n    blockdef_register(vm, sc_println);\n    block_category_add_blockdef(cat_terminal, sc_println);\n\n    Blockdef* sc_input = blockdef_new(\"input\", BLOCKTYPE_NORMAL, (BlockdefColor) CATEGORY_TERMINAL_COLOR, block_input);\n    blockdef_add_image(sc_input, term_img);\n    blockdef_add_text(sc_input, gettext(\"Get input\"));\n    blockdef_register(vm, sc_input);\n    block_category_add_blockdef(cat_terminal, sc_input);\n\n    Blockdef* sc_char = blockdef_new(\"get_char\", BLOCKTYPE_NORMAL, (BlockdefColor) CATEGORY_TERMINAL_COLOR, block_get_char);\n    blockdef_add_image(sc_char, term_img);\n    blockdef_add_text(sc_char, gettext(\"Get char\"));\n    blockdef_register(vm, sc_char);\n    block_category_add_blockdef(cat_terminal, sc_char);\n\n    block_category_add_label(cat_terminal, gettext(\"Cursor\"), (Color) CATEGORY_TERMINAL_COLOR);\n\n    Blockdef* sc_set_cursor = blockdef_new(\"set_cursor\", BLOCKTYPE_NORMAL, (BlockdefColor) CATEGORY_TERMINAL_COLOR, block_set_cursor);\n    blockdef_add_image(sc_set_cursor, term_img);\n    blockdef_add_text(sc_set_cursor, gettext(\"Set cursor X:\"));\n    blockdef_add_argument(sc_set_cursor, \"0\", \"0\", BLOCKCONSTR_UNLIMITED);\n    blockdef_add_text(sc_set_cursor, gettext(\"Y:\"));\n    blockdef_add_argument(sc_set_cursor, \"0\", \"0\", BLOCKCONSTR_UNLIMITED);\n    blockdef_register(vm, sc_set_cursor);\n    block_category_add_blockdef(cat_terminal, sc_set_cursor);\n\n    Blockdef* sc_cursor_x = blockdef_new(\"cursor_x\", BLOCKTYPE_NORMAL, (BlockdefColor) CATEGORY_TERMINAL_COLOR, block_cursor_x);\n    blockdef_add_image(sc_cursor_x, term_img);\n    blockdef_add_text(sc_cursor_x, gettext(\"Cursor X\"));\n    blockdef_register(vm, sc_cursor_x);\n    block_category_add_blockdef(cat_terminal, sc_cursor_x);\n\n    Blockdef* sc_cursor_y = blockdef_new(\"cursor_y\", BLOCKTYPE_NORMAL, (BlockdefColor) CATEGORY_TERMINAL_COLOR, block_cursor_y);\n    blockdef_add_image(sc_cursor_y, term_img);\n    blockdef_add_text(sc_cursor_y, gettext(\"Cursor Y\"));\n    blockdef_register(vm, sc_cursor_y);\n    block_category_add_blockdef(cat_terminal, sc_cursor_y);\n\n    Blockdef* sc_cursor_max_x = blockdef_new(\"cursor_max_x\", BLOCKTYPE_NORMAL, (BlockdefColor) CATEGORY_TERMINAL_COLOR, block_cursor_max_x);\n    blockdef_add_image(sc_cursor_max_x, term_img);\n    blockdef_add_text(sc_cursor_max_x, gettext(\"Terminal width\"));\n    blockdef_register(vm, sc_cursor_max_x);\n    block_category_add_blockdef(cat_terminal, sc_cursor_max_x);\n\n    Blockdef* sc_cursor_max_y = blockdef_new(\"cursor_max_y\", BLOCKTYPE_NORMAL, (BlockdefColor) CATEGORY_TERMINAL_COLOR, block_cursor_max_y);\n    blockdef_add_image(sc_cursor_max_y, term_img);\n    blockdef_add_text(sc_cursor_max_y, gettext(\"Terminal height\"));\n    blockdef_register(vm, sc_cursor_max_y);\n    block_category_add_blockdef(cat_terminal, sc_cursor_max_y);\n\n    block_category_add_label(cat_terminal, gettext(\"Colors\"), (Color) CATEGORY_TERMINAL_COLOR);\n\n    Blockdef* sc_set_fg_color = blockdef_new(\"set_fg_color\", BLOCKTYPE_NORMAL, (BlockdefColor) CATEGORY_TERMINAL_COLOR, block_set_fg_color);\n    blockdef_add_image(sc_set_fg_color, term_img);\n    blockdef_add_text(sc_set_fg_color, gettext(\"Set text color\"));\n    blockdef_add_color_input(sc_set_fg_color, (BlockdefColor) { 0xff, 0xff, 0xff, 0xff });\n    blockdef_register(vm, sc_set_fg_color);\n    block_category_add_blockdef(cat_terminal, sc_set_fg_color);\n\n    Blockdef* sc_set_bg_color = blockdef_new(\"set_bg_color\", BLOCKTYPE_NORMAL, (BlockdefColor) CATEGORY_TERMINAL_COLOR, block_set_bg_color);\n    blockdef_add_image(sc_set_bg_color, term_img);\n    blockdef_add_text(sc_set_bg_color, gettext(\"Set background color\"));\n    blockdef_add_color_input(sc_set_bg_color, (BlockdefColor) { 0x30, 0x30, 0x30, 0xff });\n    blockdef_register(vm, sc_set_bg_color);\n    block_category_add_blockdef(cat_terminal, sc_set_bg_color);\n\n    Blockdef* sc_reset_color = blockdef_new(\"reset_color\", BLOCKTYPE_NORMAL, (BlockdefColor) CATEGORY_TERMINAL_COLOR, block_reset_color);\n    blockdef_add_image(sc_reset_color, term_img);\n    blockdef_add_text(sc_reset_color, gettext(\"Reset color\"));\n    blockdef_register(vm, sc_reset_color);\n    block_category_add_blockdef(cat_terminal, sc_reset_color);\n\n    Blockdef* sc_term_clear = blockdef_new(\"term_clear\", BLOCKTYPE_NORMAL, (BlockdefColor) CATEGORY_TERMINAL_COLOR, block_term_clear);\n    blockdef_add_image(sc_term_clear, term_img);\n    blockdef_add_text(sc_term_clear, gettext(\"Clear terminal\"));\n    blockdef_register(vm, sc_term_clear);\n    block_category_add_blockdef(cat_terminal, sc_term_clear);\n\n    Blockdef* sc_term_set_clear = blockdef_new(\"term_set_clear\", BLOCKTYPE_NORMAL, (BlockdefColor) CATEGORY_TERMINAL_COLOR, block_term_set_clear);\n    blockdef_add_image(sc_term_set_clear, term_img);\n    blockdef_add_text(sc_term_set_clear, gettext(\"Set clear color\"));\n    blockdef_add_color_input(sc_term_set_clear, (BlockdefColor) { 0x00, 0x00, 0x00, 0xff });\n    blockdef_register(vm, sc_term_set_clear);\n    block_category_add_blockdef(cat_terminal, sc_term_set_clear);\n\n    Blockdef* sc_plus = blockdef_new(\"plus\", BLOCKTYPE_NORMAL, (BlockdefColor) CATEGORY_MATH_COLOR, block_plus);\n    blockdef_add_argument(sc_plus, \"9\", \"0\", BLOCKCONSTR_UNLIMITED);\n    blockdef_add_text(sc_plus, \"+\");\n    blockdef_add_argument(sc_plus, \"10\", \"0\", BLOCKCONSTR_UNLIMITED);\n    blockdef_register(vm, sc_plus);\n    block_category_add_blockdef(cat_math, sc_plus);\n\n    Blockdef* sc_minus = blockdef_new(\"minus\", BLOCKTYPE_NORMAL, (BlockdefColor) CATEGORY_MATH_COLOR, block_minus);\n    blockdef_add_argument(sc_minus, \"9\", \"0\", BLOCKCONSTR_UNLIMITED);\n    blockdef_add_text(sc_minus, \"-\");\n    blockdef_add_argument(sc_minus, \"10\", \"0\", BLOCKCONSTR_UNLIMITED);\n    blockdef_register(vm, sc_minus);\n    block_category_add_blockdef(cat_math, sc_minus);\n\n    Blockdef* sc_mult = blockdef_new(\"mult\", BLOCKTYPE_NORMAL, (BlockdefColor) CATEGORY_MATH_COLOR, block_mult);\n    blockdef_add_argument(sc_mult, \"9\", \"0\", BLOCKCONSTR_UNLIMITED);\n    blockdef_add_text(sc_mult, \"*\");\n    blockdef_add_argument(sc_mult, \"10\", \"0\", BLOCKCONSTR_UNLIMITED);\n    blockdef_register(vm, sc_mult);\n    block_category_add_blockdef(cat_math, sc_mult);\n\n    Blockdef* sc_div = blockdef_new(\"div\", BLOCKTYPE_NORMAL, (BlockdefColor) CATEGORY_MATH_COLOR, block_div);\n    blockdef_add_argument(sc_div, \"39\", \"0\", BLOCKCONSTR_UNLIMITED);\n    blockdef_add_text(sc_div, \"/\");\n    blockdef_add_argument(sc_div, \"5\", \"0\", BLOCKCONSTR_UNLIMITED);\n    blockdef_register(vm, sc_div);\n    block_category_add_blockdef(cat_math, sc_div);\n\n    Blockdef* sc_rem = blockdef_new(\"rem\", BLOCKTYPE_NORMAL, (BlockdefColor) CATEGORY_MATH_COLOR, block_rem);\n    blockdef_add_argument(sc_rem, \"39\", \"0\", BLOCKCONSTR_UNLIMITED);\n    blockdef_add_text(sc_rem, \"%\");\n    blockdef_add_argument(sc_rem, \"5\", \"0\", BLOCKCONSTR_UNLIMITED);\n    blockdef_register(vm, sc_rem);\n    block_category_add_blockdef(cat_math, sc_rem);\n\n    Blockdef* sc_pow = blockdef_new(\"pow\", BLOCKTYPE_NORMAL, (BlockdefColor) CATEGORY_MATH_COLOR, block_pow);\n    blockdef_add_text(sc_pow, gettext(\"Pow\"));\n    blockdef_add_argument(sc_pow, \"5\", \"0\", BLOCKCONSTR_UNLIMITED);\n    blockdef_add_argument(sc_pow, \"5\", \"0\", BLOCKCONSTR_UNLIMITED);\n    blockdef_register(vm, sc_pow);\n    block_category_add_blockdef(cat_math, sc_pow);\n\n    Blockdef* sc_math = blockdef_new(\"math\", BLOCKTYPE_NORMAL, (BlockdefColor) CATEGORY_MATH_COLOR, block_math);\n    blockdef_add_dropdown(sc_math, DROPDOWN_SOURCE_LISTREF, math_list_access);\n    blockdef_add_argument(sc_math, \"\", \"0.0\", BLOCKCONSTR_UNLIMITED);\n    blockdef_register(vm, sc_math);\n    block_category_add_blockdef(cat_math, sc_math);\n\n    Blockdef* sc_pi = blockdef_new(\"pi\", BLOCKTYPE_NORMAL, (BlockdefColor) CATEGORY_MATH_COLOR, block_pi);\n    blockdef_add_image(sc_pi, (BlockdefImage) { .image_ptr = &assets.textures.icon_pi, .image_color = (BlockdefColor) { 0xff, 0xff, 0xff, 0xff } });\n    blockdef_register(vm, sc_pi);\n    block_category_add_blockdef(cat_math, sc_pi);\n\n    block_category_add_label(cat_logic, gettext(\"Comparisons\"), (Color) CATEGORY_LOGIC_COLOR);\n\n    Blockdef* sc_less = blockdef_new(\"less\", BLOCKTYPE_NORMAL, (BlockdefColor) CATEGORY_LOGIC_COLOR, block_less);\n    blockdef_add_argument(sc_less, \"9\", \"0\", BLOCKCONSTR_UNLIMITED);\n    blockdef_add_text(sc_less, \"<\");\n    blockdef_add_argument(sc_less, \"11\", \"0\", BLOCKCONSTR_UNLIMITED);\n    blockdef_register(vm, sc_less);\n    block_category_add_blockdef(cat_logic, sc_less);\n\n    Blockdef* sc_less_eq = blockdef_new(\"less_eq\", BLOCKTYPE_NORMAL, (BlockdefColor) CATEGORY_LOGIC_COLOR, block_less_eq);\n    blockdef_add_argument(sc_less_eq, \"9\", \"0\", BLOCKCONSTR_UNLIMITED);\n    blockdef_add_text(sc_less_eq, \"<=\");\n    blockdef_add_argument(sc_less_eq, \"11\", \"0\", BLOCKCONSTR_UNLIMITED);\n    blockdef_register(vm, sc_less_eq);\n    block_category_add_blockdef(cat_logic, sc_less_eq);\n\n    Blockdef* sc_eq = blockdef_new(\"eq\", BLOCKTYPE_NORMAL, (BlockdefColor) CATEGORY_LOGIC_COLOR, block_eq);\n    blockdef_add_argument(sc_eq, \"\", gettext(\"any\"), BLOCKCONSTR_UNLIMITED);\n    blockdef_add_text(sc_eq, \"=\");\n    blockdef_add_argument(sc_eq, \"\", gettext(\"any\"), BLOCKCONSTR_UNLIMITED);\n    blockdef_register(vm, sc_eq);\n    block_category_add_blockdef(cat_logic, sc_eq);\n\n    Blockdef* sc_not_eq = blockdef_new(\"not_eq\", BLOCKTYPE_NORMAL, (BlockdefColor) CATEGORY_LOGIC_COLOR, block_not_eq);\n    blockdef_add_argument(sc_not_eq, \"\", gettext(\"any\"), BLOCKCONSTR_UNLIMITED);\n    blockdef_add_text(sc_not_eq, \"!=\");\n    blockdef_add_argument(sc_not_eq, \"\", gettext(\"any\"), BLOCKCONSTR_UNLIMITED);\n    blockdef_register(vm, sc_not_eq);\n    block_category_add_blockdef(cat_logic, sc_not_eq);\n\n    Blockdef* sc_more_eq = blockdef_new(\"more_eq\", BLOCKTYPE_NORMAL, (BlockdefColor) CATEGORY_LOGIC_COLOR, block_more_eq);\n    blockdef_add_argument(sc_more_eq, \"9\", \"0\", BLOCKCONSTR_UNLIMITED);\n    blockdef_add_text(sc_more_eq, \">=\");\n    blockdef_add_argument(sc_more_eq, \"11\", \"0\", BLOCKCONSTR_UNLIMITED);\n    blockdef_register(vm, sc_more_eq);\n    block_category_add_blockdef(cat_logic, sc_more_eq);\n\n    Blockdef* sc_more = blockdef_new(\"more\", BLOCKTYPE_NORMAL, (BlockdefColor) CATEGORY_LOGIC_COLOR, block_more);\n    blockdef_add_argument(sc_more, \"9\", \"0\", BLOCKCONSTR_UNLIMITED);\n    blockdef_add_text(sc_more, \">\");\n    blockdef_add_argument(sc_more, \"11\", \"0\", BLOCKCONSTR_UNLIMITED);\n    blockdef_register(vm, sc_more);\n    block_category_add_blockdef(cat_logic, sc_more);\n\n    block_category_add_label(cat_logic, gettext(\"Boolean math\"), (Color) CATEGORY_LOGIC_COLOR);\n\n    Blockdef* sc_not = blockdef_new(\"not\", BLOCKTYPE_NORMAL, (BlockdefColor) CATEGORY_LOGIC_COLOR, block_not);\n    blockdef_add_text(sc_not, gettext(\"Not\"));\n    blockdef_add_argument(sc_not, \"\", gettext(\"cond.\"), BLOCKCONSTR_UNLIMITED);\n    blockdef_register(vm, sc_not);\n    block_category_add_blockdef(cat_logic, sc_not);\n\n    Blockdef* sc_and = blockdef_new(\"and\", BLOCKTYPE_NORMAL, (BlockdefColor) CATEGORY_LOGIC_COLOR, block_and);\n    blockdef_add_argument(sc_and, \"\", gettext(\"cond.\"), BLOCKCONSTR_UNLIMITED);\n    blockdef_add_text(sc_and, gettext(\"and\"));\n    blockdef_add_argument(sc_and, \"\", gettext(\"cond.\"), BLOCKCONSTR_UNLIMITED);\n    blockdef_register(vm, sc_and);\n    block_category_add_blockdef(cat_logic, sc_and);\n\n    Blockdef* sc_or = blockdef_new(\"or\", BLOCKTYPE_NORMAL, (BlockdefColor) CATEGORY_LOGIC_COLOR, block_or);\n    blockdef_add_argument(sc_or, \"\", gettext(\"cond.\"), BLOCKCONSTR_UNLIMITED);\n    blockdef_add_text(sc_or, gettext(\"or\"));\n    blockdef_add_argument(sc_or, \"\", gettext(\"cond.\"), BLOCKCONSTR_UNLIMITED);\n    blockdef_register(vm, sc_or);\n    block_category_add_blockdef(cat_logic, sc_or);\n\n    Blockdef* sc_true = blockdef_new(\"true\", BLOCKTYPE_NORMAL, (BlockdefColor) CATEGORY_LOGIC_COLOR, block_true);\n    blockdef_add_text(sc_true, gettext(\"True\"));\n    blockdef_register(vm, sc_true);\n    block_category_add_blockdef(cat_logic, sc_true);\n\n    Blockdef* sc_false = blockdef_new(\"false\", BLOCKTYPE_NORMAL, (BlockdefColor) CATEGORY_LOGIC_COLOR, block_false);\n    blockdef_add_text(sc_false, gettext(\"False\"));\n    blockdef_register(vm, sc_false);\n    block_category_add_blockdef(cat_logic, sc_false);\n\n    block_category_add_label(cat_logic, gettext(\"Bitwise math\"), (Color) CATEGORY_LOGIC_COLOR);\n\n    Blockdef* sc_bit_not = blockdef_new(\"bit_not\", BLOCKTYPE_NORMAL, (BlockdefColor) CATEGORY_LOGIC_COLOR, block_bit_not);\n    blockdef_add_text(sc_bit_not, \"~\");\n    blockdef_add_argument(sc_bit_not, \"39\", \"0\", BLOCKCONSTR_UNLIMITED);\n    blockdef_register(vm, sc_bit_not);\n    block_category_add_blockdef(cat_logic, sc_bit_not);\n\n    Blockdef* sc_bit_and = blockdef_new(\"bit_and\", BLOCKTYPE_NORMAL, (BlockdefColor) CATEGORY_LOGIC_COLOR, block_bit_and);\n    blockdef_add_argument(sc_bit_and, \"39\", \"0\", BLOCKCONSTR_UNLIMITED);\n    blockdef_add_text(sc_bit_and, \"&\");\n    blockdef_add_argument(sc_bit_and, \"5\", \"0\", BLOCKCONSTR_UNLIMITED);\n    blockdef_register(vm, sc_bit_and);\n    block_category_add_blockdef(cat_logic, sc_bit_and);\n\n    Blockdef* sc_bit_or = blockdef_new(\"bit_or\", BLOCKTYPE_NORMAL, (BlockdefColor) CATEGORY_LOGIC_COLOR, block_bit_or);\n    blockdef_add_argument(sc_bit_or, \"39\", \"0\", BLOCKCONSTR_UNLIMITED);\n    blockdef_add_text(sc_bit_or, \"|\");\n    blockdef_add_argument(sc_bit_or, \"5\", \"0\", BLOCKCONSTR_UNLIMITED);\n    blockdef_register(vm, sc_bit_or);\n    block_category_add_blockdef(cat_logic, sc_bit_or);\n\n    Blockdef* sc_bit_xor = blockdef_new(\"bit_xor\", BLOCKTYPE_NORMAL, (BlockdefColor) CATEGORY_LOGIC_COLOR, block_bit_xor);\n    blockdef_add_argument(sc_bit_xor, \"39\", \"0\", BLOCKCONSTR_UNLIMITED);\n    blockdef_add_text(sc_bit_xor, \"^\");\n    blockdef_add_argument(sc_bit_xor, \"5\", \"0\", BLOCKCONSTR_UNLIMITED);\n    blockdef_register(vm, sc_bit_xor);\n    block_category_add_blockdef(cat_logic, sc_bit_xor);\n\n    block_category_add_label(cat_misc, gettext(\"System\"), (Color) CATEGORY_MISC_COLOR);\n\n    Blockdef* sc_sleep = blockdef_new(\"sleep\", BLOCKTYPE_NORMAL, (BlockdefColor) CATEGORY_MISC_COLOR, block_sleep);\n    blockdef_add_text(sc_sleep, gettext(\"Sleep\"));\n    blockdef_add_argument(sc_sleep, \"\", \"0\", BLOCKCONSTR_UNLIMITED);\n    blockdef_add_text(sc_sleep, gettext(\"μs\"));\n    blockdef_register(vm, sc_sleep);\n    block_category_add_blockdef(cat_misc, sc_sleep);\n\n    Blockdef* sc_random = blockdef_new(\"random\", BLOCKTYPE_NORMAL, (BlockdefColor) CATEGORY_MISC_COLOR, block_random);\n    blockdef_add_text(sc_random, gettext(\"Random from\"));\n    blockdef_add_argument(sc_random, \"0\", \"0\", BLOCKCONSTR_UNLIMITED);\n    blockdef_add_text(sc_random, gettext(\"to\"));\n    blockdef_add_argument(sc_random, \"10\", \"0\", BLOCKCONSTR_UNLIMITED);\n    blockdef_register(vm, sc_random);\n    block_category_add_blockdef(cat_misc, sc_random);\n\n    Blockdef* sc_unix_time = blockdef_new(\"unix_time\", BLOCKTYPE_NORMAL, (BlockdefColor) CATEGORY_MISC_COLOR, block_unix_time);\n    blockdef_add_text(sc_unix_time, gettext(\"Time since 1970\"));\n    blockdef_register(vm, sc_unix_time);\n    block_category_add_blockdef(cat_misc, sc_unix_time);\n\n    block_category_add_label(cat_misc, gettext(\"Type casting\"), (Color) CATEGORY_MISC_COLOR);\n\n    Blockdef* sc_int = blockdef_new(\"convert_int\", BLOCKTYPE_NORMAL, (BlockdefColor) CATEGORY_MISC_COLOR, block_convert_int);\n    blockdef_add_text(sc_int, gettext(\"Int\"));\n    blockdef_add_argument(sc_int, \"\", gettext(\"any\"), BLOCKCONSTR_UNLIMITED);\n    blockdef_register(vm, sc_int);\n    block_category_add_blockdef(cat_misc, sc_int);\n\n    Blockdef* sc_float = blockdef_new(\"convert_float\", BLOCKTYPE_NORMAL, (BlockdefColor) CATEGORY_MISC_COLOR, block_convert_float);\n    blockdef_add_text(sc_float, gettext(\"Float\"));\n    blockdef_add_argument(sc_float, \"\", gettext(\"any\"), BLOCKCONSTR_UNLIMITED);\n    blockdef_register(vm, sc_float);\n    block_category_add_blockdef(cat_misc, sc_float);\n\n    Blockdef* sc_str = blockdef_new(\"convert_str\", BLOCKTYPE_NORMAL, (BlockdefColor) CATEGORY_MISC_COLOR, block_convert_str);\n    blockdef_add_text(sc_str, gettext(\"Str\"));\n    blockdef_add_argument(sc_str, \"\", gettext(\"any\"), BLOCKCONSTR_UNLIMITED);\n    blockdef_register(vm, sc_str);\n    block_category_add_blockdef(cat_misc, sc_str);\n\n    Blockdef* sc_bool = blockdef_new(\"convert_bool\", BLOCKTYPE_NORMAL, (BlockdefColor) CATEGORY_MISC_COLOR, block_convert_bool);\n    blockdef_add_text(sc_bool, gettext(\"Bool\"));\n    blockdef_add_argument(sc_bool, \"\", gettext(\"any\"), BLOCKCONSTR_UNLIMITED);\n    blockdef_register(vm, sc_bool);\n    block_category_add_blockdef(cat_misc, sc_bool);\n\n    Blockdef* sc_color = blockdef_new(\"convert_color\", BLOCKTYPE_NORMAL, (BlockdefColor) CATEGORY_MISC_COLOR, block_convert_color);\n    blockdef_add_text(sc_color, gettext(\"Color\"));\n    blockdef_add_color_input(sc_color, (BlockdefColor) { 0x00, 0xff, 0x00, 0xff });\n    blockdef_register(vm, sc_color);\n    block_category_add_blockdef(cat_misc, sc_color);\n\n    Blockdef* sc_typeof = blockdef_new(\"typeof\", BLOCKTYPE_NORMAL, (BlockdefColor) CATEGORY_MISC_COLOR, block_typeof);\n    blockdef_add_text(sc_typeof, gettext(\"Type of\"));\n    blockdef_add_argument(sc_typeof, \"\", gettext(\"any\"), BLOCKCONSTR_UNLIMITED);\n    blockdef_register(vm, sc_typeof);\n    block_category_add_blockdef(cat_misc, sc_typeof);\n\n    block_category_add_label(cat_misc, gettext(\"Doing nothing\"), (Color) { 0x77, 0x77, 0x77, 0xff });\n\n    Blockdef* sc_nothing = blockdef_new(\"nothing\", BLOCKTYPE_NORMAL, (BlockdefColor) { 0x77, 0x77, 0x77, 0xff }, block_noop);\n    blockdef_add_text(sc_nothing, gettext(\"Nothing\"));\n    blockdef_register(vm, sc_nothing);\n    block_category_add_blockdef(cat_misc, sc_nothing);\n\n    Blockdef* sc_do_nothing = blockdef_new(\"do_nothing\", BLOCKTYPE_CONTROL, (BlockdefColor) { 0x77, 0x77, 0x77, 0xff }, block_do_nothing);\n    blockdef_add_text(sc_do_nothing, gettext(\"Do nothing\"));\n    blockdef_register(vm, sc_do_nothing);\n    block_category_add_blockdef(cat_misc, sc_do_nothing);\n\n    Blockdef* sc_comment = blockdef_new(\"comment\", BLOCKTYPE_NORMAL, (BlockdefColor) { 0x77, 0x77, 0x77, 0xff }, block_noop);\n    blockdef_add_text(sc_comment, \"//\");\n    blockdef_add_argument(sc_comment, \"\", gettext(\"any\"), BLOCKCONSTR_UNLIMITED);\n    blockdef_register(vm, sc_comment);\n    block_category_add_blockdef(cat_misc, sc_comment);\n\n#ifdef DEBUG\n    block_category_add_label(cat_misc, gettext(\"Debug blocks\"), (Color) { 0xa0, 0x70, 0x00, 0xff });\n\n    Blockdef* sc_gc_collect = blockdef_new(\"gc_collect\", BLOCKTYPE_NORMAL, (BlockdefColor) { 0xa0, 0x70, 0x00, 0xff }, block_gc_collect);\n    blockdef_add_text(sc_gc_collect, gettext(\"Collect garbage\"));\n    blockdef_register(vm, sc_gc_collect);\n    block_category_add_blockdef(cat_misc, sc_gc_collect);\n#endif\n\n    block_category_add_label(cat_data, gettext(\"Variables\"), (Color) CATEGORY_DATA_COLOR);\n\n    Blockdef* sc_decl_var = blockdef_new(\"decl_var\", BLOCKTYPE_NORMAL, (BlockdefColor) CATEGORY_DATA_COLOR, block_declare_var);\n    blockdef_add_image(sc_decl_var, (BlockdefImage) { .image_ptr = &assets.textures.icon_variable, .image_color = (BlockdefColor) { 0xff, 0xff, 0xff, 0xff } });\n    blockdef_add_text(sc_decl_var, gettext(\"Declare\"));\n    blockdef_add_argument(sc_decl_var, gettext(\"my variable\"), gettext(\"Abc\"), BLOCKCONSTR_STRING);\n    blockdef_add_text(sc_decl_var, \"=\");\n    blockdef_add_argument(sc_decl_var, \"\", gettext(\"any\"), BLOCKCONSTR_UNLIMITED);\n    blockdef_register(vm, sc_decl_var);\n    block_category_add_blockdef(cat_data, sc_decl_var);\n\n    Blockdef* sc_get_var = blockdef_new(\"get_var\", BLOCKTYPE_NORMAL, (BlockdefColor) CATEGORY_DATA_COLOR, block_get_var);\n    blockdef_add_image(sc_get_var, (BlockdefImage) { .image_ptr = &assets.textures.icon_variable, .image_color = (BlockdefColor) { 0xff, 0xff, 0xff, 0xff } });\n    blockdef_add_argument(sc_get_var, gettext(\"my variable\"), gettext(\"Abc\"), BLOCKCONSTR_STRING);\n    blockdef_register(vm, sc_get_var);\n    block_category_add_blockdef(cat_data, sc_get_var);\n\n    Blockdef* sc_set_var = blockdef_new(\"set_var\", BLOCKTYPE_NORMAL, (BlockdefColor) CATEGORY_DATA_COLOR, block_set_var);\n    blockdef_add_image(sc_set_var, (BlockdefImage) { .image_ptr = &assets.textures.icon_variable, .image_color = (BlockdefColor) { 0xff, 0xff, 0xff, 0xff } });\n    blockdef_add_text(sc_set_var, gettext(\"Set\"));\n    blockdef_add_argument(sc_set_var, gettext(\"my variable\"), gettext(\"Abc\"), BLOCKCONSTR_STRING);\n    blockdef_add_text(sc_set_var, \"=\");\n    blockdef_add_argument(sc_set_var, \"\", gettext(\"any\"), BLOCKCONSTR_UNLIMITED);\n    blockdef_register(vm, sc_set_var);\n    block_category_add_blockdef(cat_data, sc_set_var);\n\n    block_category_add_label(cat_data, gettext(\"Strings\"), (Color) CATEGORY_STRING_COLOR);\n\n    Blockdef* sc_join = blockdef_new(\"join\", BLOCKTYPE_NORMAL, (BlockdefColor) CATEGORY_STRING_COLOR, block_join);\n    blockdef_add_text(sc_join, gettext(\"Join\"));\n    blockdef_add_argument(sc_join, gettext(\"left and \"), gettext(\"Abc\"), BLOCKCONSTR_UNLIMITED);\n    blockdef_add_argument(sc_join, gettext(\"right\"), gettext(\"Abc\"), BLOCKCONSTR_UNLIMITED);\n    blockdef_register(vm, sc_join);\n    block_category_add_blockdef(cat_data, sc_join);\n\n    Blockdef* sc_letter_in = blockdef_new(\"letter_in\", BLOCKTYPE_NORMAL, (BlockdefColor) CATEGORY_STRING_COLOR, block_letter_in);\n    blockdef_add_text(sc_letter_in, gettext(\"Letter\"));\n    blockdef_add_argument(sc_letter_in, \"1\", \"0\", BLOCKCONSTR_UNLIMITED);\n    blockdef_add_text(sc_letter_in, gettext(\"in\"));\n    blockdef_add_argument(sc_letter_in, gettext(\"string\"), gettext(\"Abc\"), BLOCKCONSTR_UNLIMITED);\n    blockdef_register(vm, sc_letter_in);\n    block_category_add_blockdef(cat_data, sc_letter_in);\n\n    Blockdef* sc_substring = blockdef_new(\"substring\", BLOCKTYPE_NORMAL, (BlockdefColor) CATEGORY_STRING_COLOR, block_substring);\n    blockdef_add_text(sc_substring, gettext(\"Substring\"));\n    blockdef_add_argument(sc_substring, \"2\", \"0\", BLOCKCONSTR_UNLIMITED);\n    blockdef_add_text(sc_substring, gettext(\"to\"));\n    blockdef_add_argument(sc_substring, \"4\", \"0\", BLOCKCONSTR_UNLIMITED);\n    blockdef_add_text(sc_substring, gettext(\"in\"));\n    blockdef_add_argument(sc_substring, gettext(\"string\"), gettext(\"Abc\"), BLOCKCONSTR_UNLIMITED);\n    blockdef_register(vm, sc_substring);\n    block_category_add_blockdef(cat_data, sc_substring);\n\n    Blockdef* sc_length = blockdef_new(\"length\", BLOCKTYPE_NORMAL, (BlockdefColor) CATEGORY_STRING_COLOR, block_length);\n    blockdef_add_text(sc_length, gettext(\"Length\"));\n    blockdef_add_argument(sc_length, gettext(\"string\"), gettext(\"Abc\"), BLOCKCONSTR_UNLIMITED);\n    blockdef_register(vm, sc_length);\n    block_category_add_blockdef(cat_data, sc_length);\n\n    Blockdef* sc_ord = blockdef_new(\"ord\", BLOCKTYPE_NORMAL, (BlockdefColor) CATEGORY_STRING_COLOR, block_ord);\n    blockdef_add_text(sc_ord, gettext(\"Ord\"));\n    blockdef_add_argument(sc_ord, \"A\", gettext(\"Abc\"), BLOCKCONSTR_UNLIMITED);\n    blockdef_register(vm, sc_ord);\n    block_category_add_blockdef(cat_data, sc_ord);\n\n    Blockdef* sc_chr = blockdef_new(\"chr\", BLOCKTYPE_NORMAL, (BlockdefColor) CATEGORY_STRING_COLOR, block_chr);\n    blockdef_add_text(sc_chr, gettext(\"Chr\"));\n    blockdef_add_argument(sc_chr, \"65\", \"0\", BLOCKCONSTR_UNLIMITED);\n    blockdef_register(vm, sc_chr);\n    block_category_add_blockdef(cat_data, sc_chr);\n\n    block_category_add_label(cat_data, gettext(\"Lists\"), (Color) { 0xff, 0x44, 0x00, 0xff });\n\n    Blockdef* sc_create_list = blockdef_new(\"create_list\", BLOCKTYPE_NORMAL, (BlockdefColor) { 0xff, 0x44, 0x00, 0xff }, block_create_list);\n    blockdef_add_image(sc_create_list, list_img);\n    blockdef_add_text(sc_create_list, gettext(\"Empty list\"));\n    blockdef_register(vm, sc_create_list);\n    block_category_add_blockdef(cat_data, sc_create_list);\n\n    Blockdef* sc_list_add = blockdef_new(\"list_add\", BLOCKTYPE_NORMAL, (BlockdefColor) { 0xff, 0x44, 0x00, 0xff }, block_list_add);\n    blockdef_add_image(sc_list_add, list_img);\n    blockdef_add_text(sc_list_add, gettext(\"Add\"));\n    blockdef_add_argument(sc_list_add, \"\", gettext(\"list\"), BLOCKCONSTR_UNLIMITED);\n    blockdef_add_text(sc_list_add, gettext(\"value\"));\n    blockdef_add_argument(sc_list_add, \"\", gettext(\"any\"), BLOCKCONSTR_UNLIMITED);\n    blockdef_register(vm, sc_list_add);\n    block_category_add_blockdef(cat_data, sc_list_add);\n\n    Blockdef* sc_list_get = blockdef_new(\"list_get\", BLOCKTYPE_NORMAL, (BlockdefColor) { 0xff, 0x44, 0x00, 0xff }, block_list_get);\n    blockdef_add_image(sc_list_get, list_img);\n    blockdef_add_argument(sc_list_get, \"\", gettext(\"list\"), BLOCKCONSTR_UNLIMITED);\n    blockdef_add_text(sc_list_get, gettext(\"at\"));\n    blockdef_add_argument(sc_list_get, \"0\", \"0\", BLOCKCONSTR_UNLIMITED);\n    blockdef_register(vm, sc_list_get);\n    block_category_add_blockdef(cat_data, sc_list_get);\n\n    Blockdef* sc_list_set = blockdef_new(\"list_set\", BLOCKTYPE_NORMAL, (BlockdefColor) { 0xff, 0x44, 0x00, 0xff }, block_list_set);\n    blockdef_add_image(sc_list_set, list_img);\n    blockdef_add_text(sc_list_set, gettext(\"Set\"));\n    blockdef_add_argument(sc_list_set, \"\", gettext(\"list\"), BLOCKCONSTR_UNLIMITED);\n    blockdef_add_text(sc_list_set, gettext(\"at\"));\n    blockdef_add_argument(sc_list_set, \"0\", \"0\", BLOCKCONSTR_UNLIMITED);\n    blockdef_add_text(sc_list_set, \"=\");\n    blockdef_add_argument(sc_list_set, \"\", gettext(\"any\"), BLOCKCONSTR_UNLIMITED);\n    blockdef_register(vm, sc_list_set);\n    block_category_add_blockdef(cat_data, sc_list_set);\n\n    Blockdef* sc_list_len = blockdef_new(\"list_length\", BLOCKTYPE_NORMAL, (BlockdefColor) { 0xff, 0x44, 0x00, 0xff }, block_list_length);\n    blockdef_add_image(sc_list_len, list_img);\n    blockdef_add_text(sc_list_len, gettext(\"Length\"));\n    blockdef_add_argument(sc_list_len, \"\", gettext(\"list\"), BLOCKCONSTR_UNLIMITED);\n    blockdef_register(vm, sc_list_len);\n    block_category_add_blockdef(cat_data, sc_list_len);\n}\n"
  },
  {
    "path": "src/compiler.c",
    "content": "// Scrap is a project that allows anyone to build software using simple, block based interface.\n//\n// Copyright (C) 2024-2026 Grisshink\n// \n// This software is provided 'as-is', without any express or implied\n// warranty.  In no event will the authors be held liable for any damages\n// arising from the use of this software.\n// \n// Permission is granted to anyone to use this software for any purpose,\n// including commercial applications, and to alter it and redistribute it\n// freely, subject to the following restrictions:\n// \n// 1. The origin of this software must not be misrepresented; you must not\n//    claim that you wrote the original software. If you use this software\n//    in a product, an acknowledgment in the product documentation would be\n//    appreciated but is not required.\n// 2. Altered source versions must be plainly marked as such, and must not be\n//    misrepresented as being the original software.\n// 3. This notice may not be removed or altered from any source distribution.\n\n#include \"scrap.h\"\n#include \"term.h\"\n#include \"vec.h\"\n#include \"std.h\"\n\n#include <sys/stat.h>\n#include <llvm-c/Analysis.h>\n#include <assert.h>\n#include <string.h>\n#include <time.h>\n#include <stdio.h>\n#include <math.h>\n#include <unistd.h>\n#include <errno.h>\n#include <libintl.h>\n\n#ifndef _WIN32\n#include <glob.h>\n#endif\n\n#ifdef _WIN32\n#define TARGET_TRIPLE \"x86_64-w64-windows-gnu\"\n#else\n#define TARGET_TRIPLE \"x86_64-pc-linux-gnu\"\n#endif\n\nstatic bool compile_program(Exec* exec);\nstatic bool run_program(Exec* exec);\nstatic bool build_program(Exec* exec);\nstatic void free_defined_functions(Exec* exec);\n\nExec exec_new(Thread* thread, CompilerMode mode) {\n    Exec exec = (Exec) {\n        .code = NULL,\n        .thread = thread,\n        .current_error_block = NULL,\n        .current_mode = mode,\n    };\n    exec.current_error[0] = 0;\n    return exec;\n}\n\nvoid exec_free(Exec* exec) {\n    (void) exec;\n}\n\nvoid exec_cleanup(void* e) {\n    Exec* exec = e;\n    \n    switch (exec->current_state) {\n    case STATE_NONE:\n        break;\n    case STATE_COMPILE:\n        LLVMDisposeModule(exec->module);\n        LLVMDisposeBuilder(exec->builder);\n        vector_free(exec->gc_dirty_funcs);\n        vector_free(exec->compile_func_list);\n        vector_free(exec->global_variables);\n        free_defined_functions(exec);\n        break;\n    case STATE_PRE_EXEC:\n        LLVMDisposeModule(exec->module);\n        vector_free(exec->compile_func_list);\n        break;\n    case STATE_EXEC:\n        gc_free(&exec->gc);\n        LLVMDisposeExecutionEngine(exec->engine);\n        break;\n    }\n}\n\nbool exec_run(void* e) {\n    Exec* exec = e;\n    exec->current_state = STATE_NONE;\n\n    if (!compile_program(exec)) return false;\n\n    if (exec->current_mode == COMPILER_MODE_JIT) {\n        if (!run_program(exec)) return false;\n    } else {\n        if (!build_program(exec)) return false;\n    }\n\n    return true;\n}\n\nstatic void exec_handle_running_state(Exec* exec) {\n    if (exec->thread->state != THREAD_STATE_STOPPING) return;\n    longjmp(exec->run_jump_buf, 1);\n}\n\nvoid exec_set_error(Exec* exec, Block* block, const char* fmt, ...) {\n    exec->current_error_block = block;\n    va_list va;\n    va_start(va, fmt);\n    vsnprintf(exec->current_error, MAX_ERROR_LEN, fmt, va);\n    va_end(va);\n    scrap_log(LOG_ERROR, \"[EXEC] %s\", exec->current_error);\n}\n\nstatic bool control_stack_push(Exec* exec, Block* block) {\n    if (exec->control_stack_len >= VM_CONTROL_STACK_SIZE) {\n        exec_set_error(exec, block, gettext(\"Control stack overflow\"));\n        return false;\n    }\n    exec->control_stack[exec->control_stack_len++] = block;\n    return true;\n}\n\nstatic Block* control_stack_pop(Exec* exec) {\n    if (exec->control_stack_len == 0) {\n        exec_set_error(exec, NULL, gettext(\"Control stack underflow\"));\n        return NULL;\n    }\n    return exec->control_stack[--exec->control_stack_len];\n}\n\nvoid global_variable_add(Exec* exec, Variable variable) {\n    vector_add(&exec->global_variables, variable);\n}\n\nbool variable_stack_push(Exec* exec, Block* block, Variable variable) {\n    if (exec->variable_stack_len >= VM_CONTROL_STACK_SIZE) {\n        exec_set_error(exec, block, gettext(\"Variable stack overflow\"));\n        return false;\n    }\n    exec->variable_stack[exec->variable_stack_len++] = variable;\n    return true;\n}\n\nVariable* variable_get(Exec* exec, const char* var_name) {\n    for (ssize_t i = exec->variable_stack_len - 1; i >= 0; i--) {\n        if (!strcmp(var_name, exec->variable_stack[i].name)) return &exec->variable_stack[i];\n    }\n    for (ssize_t i = vector_size(exec->global_variables) - 1; i >= 0; i--) {\n        if (!strcmp(var_name, exec->global_variables[i].name)) return &exec->global_variables[i];\n    }\n    return NULL;\n}\n\nstatic bool variable_stack_frame_push(Exec* exec) {\n    if (exec->variable_stack_frames_len >= VM_CONTROL_STACK_SIZE) {\n        exec_set_error(exec, NULL, gettext(\"Variable stack overflow\"));\n        return false;\n    }\n    VariableStackFrame frame;\n    frame.base_size = exec->variable_stack_len;\n\n    frame.base_stack = build_call(exec, \"llvm.stacksave.p0\");\n\n    exec->variable_stack_frames[exec->variable_stack_frames_len++] = frame;\n    return true;\n}\n\nstatic bool variable_stack_frame_pop(Exec* exec) {\n    if (exec->variable_stack_frames_len == 0) {\n        exec_set_error(exec, NULL, gettext(\"Variable stack underflow\"));\n        return false;\n    }\n    VariableStackFrame frame = exec->variable_stack_frames[--exec->variable_stack_frames_len];\n\n    build_call(exec, \"llvm.stackrestore.p0\", frame.base_stack);\n\n    exec->variable_stack_len = frame.base_size;\n    return true;\n}\n\nstatic bool evaluate_block(Exec* exec, Block* block, FuncArg* return_val, ControlState control_state, FuncArg input_val) {\n    if (!block->blockdef) {\n        exec_set_error(exec, block, gettext(\"Tried to compile block without definition\"));\n        return false;\n    }\n    if (!block->blockdef->func) {\n        exec_set_error(exec, block, gettext(\"Tried to compile block \\\"%s\\\" without implementation\"), block->blockdef->id);\n        return false;\n    }\n\n    BlockCompileFunc compile_block = block->blockdef->func;\n    FuncArg* args = vector_create();\n    FuncArg* arg;\n\n    if (control_state == CONTROL_STATE_BEGIN) {\n        LLVMBasicBlockRef current = LLVMGetInsertBlock(exec->builder);\n        LLVMBasicBlockRef control_block = LLVMInsertBasicBlock(current, \"control_block\");\n        LLVMMoveBasicBlockAfter(control_block, current);\n\n        LLVMBuildBr(exec->builder, control_block);\n        LLVMPositionBuilderAtEnd(exec->builder, control_block);\n\n        variable_stack_frame_push(exec);\n    } else if (control_state == CONTROL_STATE_END) {\n        if (exec->current_mode == COMPILER_MODE_JIT) build_call(exec, \"test_cancel\", CONST_EXEC);\n        variable_stack_frame_pop(exec);\n    }\n\n    if (block->blockdef->type == BLOCKTYPE_CONTROLEND && control_state == CONTROL_STATE_BEGIN) {\n        vector_add(&args, input_val);\n    }\n\n    if (control_state != CONTROL_STATE_END) {\n        for (size_t i = 0; i < vector_size(block->arguments); i++) {\n            FuncArg block_return;\n            static_assert(ARGUMENT_LAST == 5, \"Exhaustive argument type in evaluate_block\");\n            switch (block->arguments[i].type) {\n            case ARGUMENT_TEXT:\n            case ARGUMENT_CONST_STRING:\n                arg = vector_add_dst(&args);\n                arg->type = DATA_TYPE_LITERAL;\n                arg->data.str = block->arguments[i].data.text;\n                break;\n            case ARGUMENT_BLOCK:\n                if (!evaluate_block(exec, &block->arguments[i].data.block, &block_return, CONTROL_STATE_NORMAL, DATA_NOTHING)) {\n                    scrap_log(LOG_ERROR, \"[LLVM] While compiling block id: \\\"%s\\\" (argument #%d) (at block %p)\", block->blockdef->id, i + 1, block);\n                    vector_free(args);\n                    return false;\n                }\n                vector_add(&args, block_return);\n                break;\n            case ARGUMENT_BLOCKDEF:\n                arg = vector_add_dst(&args);\n                arg->type = DATA_TYPE_BLOCKDEF;\n                arg->data.blockdef = block->arguments[i].data.blockdef;\n                break;\n            case ARGUMENT_COLOR:\n                arg = vector_add_dst(&args);\n                arg->type = DATA_TYPE_COLOR;\n                arg->data.value = CONST_INTEGER(*(int*)&block->arguments[i].data.color);\n                break;\n            default:\n                assert(false && \"Unimplemented argument type in evaluate_block\");\n            }\n        }\n    }\n\n    if (control_state == CONTROL_STATE_BEGIN) {\n        control_data_stack_push_data(exec->gc_dirty, bool);\n    }\n\n    if (!compile_block(exec, block, vector_size(args), args, return_val, control_state)) {\n        vector_free(args);\n        scrap_log(LOG_ERROR, \"[LLVM] Got error while compiling block id: \\\"%s\\\" (at block %p)\", block->blockdef->id, block);\n        return false;\n    }\n\n    if (control_state == CONTROL_STATE_END) {\n        control_data_stack_pop_data(exec->gc_dirty, bool);\n    }\n\n    if (!block->parent && exec->gc_dirty) {\n        build_call(exec, \"gc_flush\", CONST_GC);\n        exec->gc_dirty = false;\n    }\n\n    vector_free(args);\n    return true;\n}\n\nstatic bool evaluate_chain(Exec* exec, BlockChain* chain) {\n    if (vector_size(chain->blocks) == 0 || chain->blocks[0].blockdef->type != BLOCKTYPE_HAT) return true;\n\n    exec->variable_stack_len = 0;\n    exec->variable_stack_frames_len = 0;\n\n    for (size_t i = 0; i < vector_size(chain->blocks); i++) {\n        FuncArg block_return;\n        Block* exec_block = &chain->blocks[i];\n        ControlState control_state = chain->blocks[i].blockdef->type == BLOCKTYPE_CONTROL ? CONTROL_STATE_BEGIN : CONTROL_STATE_NORMAL;\n\n        if (chain->blocks[i].blockdef->type == BLOCKTYPE_END || chain->blocks[i].blockdef->type == BLOCKTYPE_CONTROLEND) {\n            exec_block = control_stack_pop(exec);\n            if (!exec_block) return false;\n            control_state = CONTROL_STATE_END;\n        }\n\n        if (!evaluate_block(exec, exec_block, &block_return, control_state, DATA_NOTHING)) return false;\n\n        if (chain->blocks[i].blockdef->type == BLOCKTYPE_CONTROLEND) {\n            FuncArg bin;\n            control_state = CONTROL_STATE_BEGIN;\n            if (!evaluate_block(exec, &chain->blocks[i], &bin, control_state, block_return)) return false;\n        }\n\n        if (chain->blocks[i].blockdef->type == BLOCKTYPE_CONTROL || chain->blocks[i].blockdef->type == BLOCKTYPE_CONTROLEND) {\n            if (!control_stack_push(exec, &chain->blocks[i])) return false;\n        }\n    }\n\n    return true;\n}\n\nDefineArgument* get_custom_argument(Exec* exec, Blockdef* blockdef, DefineFunction** func) {\n    for (size_t i = 0; i < vector_size(exec->defined_functions); i++) {\n        for (size_t j = 0; j < vector_size(exec->defined_functions[i].args); j++) {\n            if (exec->defined_functions[i].args[j].blockdef == blockdef) {\n                *func = &exec->defined_functions[i];\n                return &exec->defined_functions[i].args[j];\n            }\n        }\n    }\n    return NULL;\n}\n\nstatic void vector_add_str(char** vec, const char* str) {\n    for (const char* str_val = str; *str_val; str_val++) vector_add(vec, *str_val);\n}\n\nDefineFunction* define_function(Exec* exec, Blockdef* blockdef) {\n    for (size_t i = 0; i < vector_size(exec->defined_functions); i++) {\n        if (exec->defined_functions[i].blockdef == blockdef) {\n            return &exec->defined_functions[i];\n        }\n    }\n\n    LLVMTypeRef func_params[32];\n    Blockdef* func_params_blockdefs[32];\n    unsigned int func_params_count = 0;\n\n    char* func_name = vector_create();\n    vector_add_str(&func_name, blockdef->id);\n    vector_add(&func_name, ' ');\n\n    for (size_t i = 0; i < vector_size(blockdef->inputs); i++) {\n        switch (blockdef->inputs[i].type) {\n        case INPUT_TEXT_DISPLAY:\n            vector_add_str(&func_name, blockdef->inputs[i].data.text);\n            vector_add(&func_name, ' ');\n            break;\n        case INPUT_BLOCKDEF_EDITOR:\n        case INPUT_COLOR:\n        case INPUT_DROPDOWN:\n            vector_add_str(&func_name, \"[] \");\n            break;\n        case INPUT_IMAGE_DISPLAY:\n            vector_add_str(&func_name, \"img \");\n            break;\n        case INPUT_ARGUMENT:\n            func_params[func_params_count] = LLVMPointerType(LLVMInt8Type(), 0);\n            func_params_blockdefs[func_params_count] = blockdef->inputs[i].data.arg.blockdef;\n            func_params_count++;\n            vector_add_str(&func_name, \"[] \");\n            break;\n        }\n    }\n    func_name[vector_size(func_name) - 1] = 0;\n\n    LLVMTypeRef func_type = LLVMFunctionType(LLVMPointerType(LLVMInt8Type(), 0), func_params, func_params_count, false);\n    LLVMValueRef func = LLVMAddFunction(exec->module, func_name, func_type);\n\n    vector_free(func_name);\n\n    DefineFunction* define = vector_add_dst(&exec->defined_functions);\n    define->blockdef = blockdef;\n    define->func = func;\n    define->args = vector_create();\n\n    LLVMValueRef func_params_values[32];\n    LLVMGetParams(func, func_params_values);\n\n    for (unsigned int i = 0; i < func_params_count; i++) {\n        DefineArgument* arg = vector_add_dst(&define->args);\n        arg->blockdef = func_params_blockdefs[i];\n        arg->arg = func_params_values[i];\n    }\n\n    return define;\n}\n\nLLVMValueRef build_gc_root_begin(Exec* exec, Block* block) {\n    if (exec->gc_block_stack_len >= VM_CONTROL_STACK_SIZE) {\n        exec_set_error(exec, block, \"Gc stack overflow\");\n        return NULL;\n    }\n\n    LLVMValueRef root_begin = build_call(exec, \"gc_root_begin\", CONST_GC);\n    GcBlock gc_block = (GcBlock) {\n        .root_begin = root_begin,\n        .required = false,\n    };\n    exec->gc_block_stack[exec->gc_block_stack_len++] = gc_block;\n\n    return root_begin;\n}\n\nLLVMValueRef build_gc_root_end(Exec* exec, Block* block) {\n    if (exec->gc_block_stack_len == 0) {\n        exec_set_error(exec, block, \"Gc stack underflow\");\n        return NULL;\n    }\n\n    GcBlock gc_block = exec->gc_block_stack[--exec->gc_block_stack_len];\n    if (!gc_block.required) {\n        LLVMInstructionEraseFromParent(gc_block.root_begin);\n        return (LLVMValueRef)-1;\n    }\n\n    return build_call(exec, \"gc_root_end\", CONST_GC);\n}\n\nstatic LLVMValueRef get_function(Exec* exec, const char* func_name) {\n    LLVMValueRef func = LLVMGetNamedFunction(exec->module, func_name);\n    if (func) return func;\n    for (size_t i = 0; i < vector_size(exec->compile_func_list); i++) {\n        if (!strcmp(exec->compile_func_list[i].name, func_name)) {\n            func = LLVMAddFunction(exec->module, func_name, exec->compile_func_list[i].type);\n            if (exec->compile_func_list[i].dynamic) vector_add(&exec->gc_dirty_funcs, func);\n            return func;\n        }\n    }\n    exec_set_error(exec, NULL, gettext(\"Function with name \\\"%s\\\" does not exist\"), func_name);\n    return NULL;\n}\n\nstatic LLVMValueRef build_call_va(Exec* exec, const char* func_name, LLVMValueRef func, LLVMTypeRef func_type, size_t func_param_count, va_list va) {\n    for (size_t i = 0; i < vector_size(exec->gc_dirty_funcs); i++) {\n        if (func != exec->gc_dirty_funcs[i]) continue;\n        exec->gc_dirty = true;\n        if (exec->gc_block_stack_len > 0) {\n            exec->gc_block_stack[exec->gc_block_stack_len - 1].required = true;\n        }\n    }\n\n    // Should be enough for all functions\n    assert(func_param_count <= 32);\n    LLVMValueRef func_param_list[32];\n\n    for (unsigned int i = 0; i < func_param_count; i++) {\n        func_param_list[i] = va_arg(va, LLVMValueRef);\n    }\n\n    if (LLVMGetTypeKind(LLVMGetReturnType(func_type)) == LLVMVoidTypeKind) {\n        return LLVMBuildCall2(exec->builder, func_type, func, func_param_list, func_param_count, \"\");\n    } else {\n        return LLVMBuildCall2(exec->builder, func_type, func, func_param_list, func_param_count, func_name);\n    }\n}\n\nLLVMValueRef build_call_count(Exec* exec, const char* func_name, size_t func_param_count, ...) {\n    LLVMValueRef func = get_function(exec, func_name);\n    LLVMTypeRef func_type = LLVMGlobalGetValueType(func);\n    LLVMValueRef out;\n\n    va_list va;\n    va_start(va, func_param_count);\n    out = build_call_va(exec, func_name, func, func_type, func_param_count, va);\n    va_end(va);\n\n    return out;\n}\n\nLLVMValueRef build_call(Exec* exec, const char* func_name, ...) {\n    LLVMValueRef func = get_function(exec, func_name);\n    LLVMTypeRef func_type = LLVMGlobalGetValueType(func);\n    unsigned int func_param_count = LLVMCountParamTypes(func_type);\n    LLVMValueRef out;\n\n    va_list va;\n    va_start(va, func_name);\n    out = build_call_va(exec, func_name, func, func_type, func_param_count, va);\n    va_end(va);\n\n    return out;\n}\n\n// Dynamic means the func calls gc_malloc at some point. This is needed for gc.root_temp_chunks cleanup\nstatic void add_function(Exec* exec, const char* name, LLVMTypeRef return_type, LLVMTypeRef* params, size_t params_len, void* func, bool dynamic, bool variadic) {\n    CompileFunction* comp_func = vector_add_dst(&exec->compile_func_list);\n    comp_func->func = func;\n    comp_func->name = name;\n    comp_func->type = LLVMFunctionType(return_type, params, params_len, variadic);\n    comp_func->dynamic = dynamic;\n}\n\nstatic LLVMValueRef register_globals(Exec* exec) {\n    LLVMTypeRef print_func_params[] = { LLVMPointerType(LLVMInt8Type(), 0) };\n    add_function(exec, \"std_term_print_str\", LLVMInt32Type(), print_func_params, ARRLEN(print_func_params), std_term_print_str, false, false);\n\n    LLVMTypeRef print_integer_func_params[] = { LLVMInt32Type() };\n    add_function(exec, \"std_term_print_integer\", LLVMInt32Type(), print_integer_func_params, ARRLEN(print_integer_func_params), std_term_print_integer, false, false);\n\n    LLVMTypeRef print_float_func_params[] = { LLVMDoubleType() };\n    add_function(exec, \"std_term_print_float\", LLVMInt32Type(), print_float_func_params, ARRLEN(print_float_func_params), std_term_print_float, false, false);\n\n    LLVMTypeRef print_bool_func_params[] = { LLVMInt1Type() };\n    add_function(exec, \"std_term_print_bool\", LLVMInt32Type(), print_bool_func_params, ARRLEN(print_bool_func_params), std_term_print_bool, false, false);\n\n    LLVMTypeRef print_list_func_params[] = { LLVMPointerType(LLVMInt8Type(), 0) };\n    add_function(exec, \"std_term_print_list\", LLVMInt32Type(), print_list_func_params, ARRLEN(print_list_func_params), std_term_print_list, false, false);\n\n    LLVMTypeRef print_color_func_params[] = { LLVMInt32Type() };\n    add_function(exec, \"std_term_print_color\", LLVMInt32Type(), print_color_func_params, ARRLEN(print_color_func_params), std_term_print_color, false, false);\n\n    LLVMTypeRef print_any_func_params[] = { LLVMPointerType(LLVMInt8Type(), 0) };\n    add_function(exec, \"std_term_print_any\", LLVMInt32Type(), print_any_func_params, ARRLEN(print_any_func_params), std_term_print_any, false, false);\n\n    LLVMTypeRef string_literal_func_params[] = { LLVMInt64Type(), LLVMPointerType(LLVMInt8Type(), 0), LLVMInt32Type() };\n    add_function(exec, \"std_string_from_literal\", LLVMPointerType(LLVMInt8Type(), 0), string_literal_func_params, ARRLEN(string_literal_func_params), std_string_from_literal, true, false);\n\n    LLVMTypeRef string_integer_func_params[] = { LLVMInt64Type(), LLVMInt32Type() };\n    add_function(exec, \"std_string_from_integer\", LLVMPointerType(LLVMInt8Type(), 0), string_integer_func_params, ARRLEN(string_integer_func_params), std_string_from_integer, true, false);\n\n    LLVMTypeRef string_bool_func_params[] = { LLVMInt64Type(), LLVMInt1Type() };\n    add_function(exec, \"std_string_from_bool\", LLVMPointerType(LLVMInt8Type(), 0), string_bool_func_params, ARRLEN(string_bool_func_params), std_string_from_bool, true, false);\n\n    LLVMTypeRef string_float_func_params[] = { LLVMInt64Type(), LLVMDoubleType() };\n    add_function(exec, \"std_string_from_float\", LLVMPointerType(LLVMInt8Type(), 0), string_float_func_params, ARRLEN(string_float_func_params), std_string_from_float, true, false);\n\n    LLVMTypeRef string_color_func_params[] = { LLVMInt64Type(), LLVMInt32Type() };\n    add_function(exec, \"std_string_from_color\", LLVMPointerType(LLVMInt8Type(), 0), string_color_func_params, ARRLEN(string_color_func_params), std_string_from_color, true, false);\n\n    LLVMTypeRef string_any_func_params[] = { LLVMInt64Type(), LLVMPointerType(LLVMInt8Type(), 0) };\n    add_function(exec, \"std_string_from_any\", LLVMPointerType(LLVMInt8Type(), 0), string_any_func_params, ARRLEN(string_any_func_params), std_string_from_any, true, false);\n\n    LLVMTypeRef string_get_data_func_params[] = { LLVMPointerType(LLVMInt8Type(), 0) };\n    add_function(exec, \"std_string_get_data\", LLVMPointerType(LLVMInt8Type(), 0), string_get_data_func_params, ARRLEN(string_get_data_func_params), std_string_get_data, false, false);\n\n    LLVMTypeRef integer_any_func_params[] = { LLVMPointerType(LLVMInt8Type(), 0) };\n    add_function(exec, \"std_integer_from_any\", LLVMInt32Type(), integer_any_func_params, ARRLEN(integer_any_func_params), std_integer_from_any, false, false);\n\n    LLVMTypeRef float_any_func_params[] = { LLVMPointerType(LLVMInt8Type(), 0) };\n    add_function(exec, \"std_float_from_any\", LLVMDoubleType(), float_any_func_params, ARRLEN(float_any_func_params), std_float_from_any, false, false);\n\n    LLVMTypeRef bool_any_func_params[] = { LLVMPointerType(LLVMInt8Type(), 0) };\n    add_function(exec, \"std_bool_from_any\", LLVMInt1Type(), bool_any_func_params, ARRLEN(bool_any_func_params), std_bool_from_any, false, false);\n\n    LLVMTypeRef color_any_func_params[] = { LLVMPointerType(LLVMInt8Type(), 0) };\n    add_function(exec, \"std_color_from_any\", LLVMInt32Type(), color_any_func_params, ARRLEN(color_any_func_params), std_color_from_any, false, false);\n\n    LLVMTypeRef parse_color_func_params[] = { LLVMPointerType(LLVMInt8Type(), 0) };\n    add_function(exec, \"std_parse_color\", LLVMInt32Type(), parse_color_func_params, ARRLEN(parse_color_func_params), std_parse_color, false, false);\n\n    LLVMTypeRef list_any_func_params[] = { LLVMInt64Type(), LLVMPointerType(LLVMInt8Type(), 0) };\n    add_function(exec, \"std_list_from_any\", LLVMPointerType(LLVMInt8Type(), 0), list_any_func_params, ARRLEN(list_any_func_params), std_list_from_any, true, false);\n\n    LLVMTypeRef any_cast_func_params[] = { LLVMInt64Type(), LLVMInt32Type() };\n    add_function(exec, \"std_any_from_value\", LLVMPointerType(LLVMInt8Type(), 0), any_cast_func_params, ARRLEN(any_cast_func_params), std_any_from_value, true, true);\n\n    LLVMTypeRef string_length_func_params[] = { LLVMPointerType(LLVMInt8Type(), 0) };\n    add_function(exec, \"std_string_length\", LLVMInt32Type(), string_length_func_params, ARRLEN(string_length_func_params), std_string_length, false, false);\n\n    LLVMTypeRef string_join_func_params[] = { LLVMInt64Type(), LLVMPointerType(LLVMInt8Type(), 0), LLVMPointerType(LLVMInt8Type(), 0) };\n    add_function(exec, \"std_string_join\", LLVMPointerType(LLVMInt8Type(), 0), string_join_func_params, ARRLEN(string_join_func_params), std_string_join, true, false);\n\n    LLVMTypeRef string_ord_func_params[] = { LLVMPointerType(LLVMInt8Type(), 0) };\n    add_function(exec, \"std_string_ord\", LLVMInt32Type(), string_ord_func_params, ARRLEN(string_ord_func_params), std_string_ord, false, false);\n\n    LLVMTypeRef string_chr_func_params[] = { LLVMInt64Type(), LLVMInt32Type() };\n    add_function(exec, \"std_string_chr\", LLVMPointerType(LLVMInt8Type(), 0), string_chr_func_params, ARRLEN(string_chr_func_params), std_string_chr, true, false);\n\n    LLVMTypeRef string_letter_in_func_params[] = { LLVMInt64Type(), LLVMInt32Type(), LLVMPointerType(LLVMInt8Type(), 0) };\n    add_function(exec, \"std_string_letter_in\", LLVMPointerType(LLVMInt8Type(), 0), string_letter_in_func_params, ARRLEN(string_letter_in_func_params), std_string_letter_in, true, false);\n\n    LLVMTypeRef string_substring_func_params[] = { LLVMInt64Type(), LLVMInt32Type(), LLVMInt32Type(), LLVMPointerType(LLVMInt8Type(), 0) };\n    add_function(exec, \"std_string_substring\", LLVMPointerType(LLVMInt8Type(), 0), string_substring_func_params, ARRLEN(string_substring_func_params), std_string_substring, true, false);\n\n    LLVMTypeRef string_eq_func_params[] = { LLVMPointerType(LLVMInt8Type(), 0), LLVMPointerType(LLVMInt8Type(), 0) };\n    add_function(exec, \"std_string_is_eq\", LLVMInt1Type(), string_eq_func_params, ARRLEN(string_eq_func_params), std_string_is_eq, false, false);\n\n    LLVMTypeRef any_eq_func_params[] = { LLVMPointerType(LLVMInt8Type(), 0), LLVMPointerType(LLVMInt8Type(), 0) };\n    add_function(exec, \"std_any_is_eq\", LLVMInt1Type(), any_eq_func_params, ARRLEN(any_eq_func_params), std_any_is_eq, false, false);\n\n    LLVMTypeRef sleep_func_params[] = { LLVMInt32Type() };\n    add_function(exec, \"std_sleep\", LLVMInt32Type(), sleep_func_params, ARRLEN(sleep_func_params), std_sleep, false, false);\n\n    LLVMTypeRef random_func_params[] = { LLVMInt32Type(), LLVMInt32Type() };\n    add_function(exec, \"std_get_random\", LLVMInt32Type(), random_func_params, ARRLEN(random_func_params), std_get_random, false, false);\n\n    LLVMTypeRef set_seed_func_params[] = { LLVMInt32Type() };\n    add_function(exec, \"std_set_random_seed\", LLVMVoidType(), set_seed_func_params, ARRLEN(set_seed_func_params), std_set_random_seed, false, false);\n\n    LLVMTypeRef atoi_func_params[] = { LLVMPointerType(LLVMInt8Type(), 0) };\n    add_function(exec, \"atoi\", LLVMInt32Type(), atoi_func_params, ARRLEN(atoi_func_params), atoi, false, false);\n\n    LLVMTypeRef atof_func_params[] = { LLVMPointerType(LLVMInt8Type(), 0) };\n    add_function(exec, \"atof\", LLVMDoubleType(), atof_func_params, ARRLEN(atof_func_params), atof, false, false);\n\n    LLVMTypeRef int_pow_func_params[] = { LLVMInt32Type(), LLVMInt32Type() };\n    add_function(exec, \"std_int_pow\", LLVMInt32Type(), int_pow_func_params, ARRLEN(int_pow_func_params), std_int_pow, false, false);\n\n    LLVMTypeRef time_func_params[] = { LLVMPointerType(LLVMVoidType(), 0) };\n    add_function(exec, \"time\", LLVMInt32Type(), time_func_params, ARRLEN(time_func_params), time, false, false);\n\n    LLVMTypeRef sin_func_params[] = { LLVMDoubleType() };\n    add_function(exec, \"sin\", LLVMDoubleType(), sin_func_params, ARRLEN(sin_func_params), sin, false, false);\n\n    LLVMTypeRef cos_func_params[] = { LLVMDoubleType() };\n    add_function(exec, \"cos\", LLVMDoubleType(), cos_func_params, ARRLEN(cos_func_params), cos, false, false);\n\n    LLVMTypeRef tan_func_params[] = { LLVMDoubleType() };\n    add_function(exec, \"tan\", LLVMDoubleType(), tan_func_params, ARRLEN(tan_func_params), tan, false, false);\n\n    LLVMTypeRef asin_func_params[] = { LLVMDoubleType() };\n    add_function(exec, \"asin\", LLVMDoubleType(), asin_func_params, ARRLEN(asin_func_params), asin, false, false);\n\n    LLVMTypeRef acos_func_params[] = { LLVMDoubleType() };\n    add_function(exec, \"acos\", LLVMDoubleType(), acos_func_params, ARRLEN(acos_func_params), acos, false, false);\n\n    LLVMTypeRef atan_func_params[] = { LLVMDoubleType() };\n    add_function(exec, \"atan\", LLVMDoubleType(), atan_func_params, ARRLEN(atan_func_params), atan, false, false);\n\n    LLVMTypeRef sqrt_func_params[] = { LLVMDoubleType() };\n    add_function(exec, \"sqrt\", LLVMDoubleType(), sqrt_func_params, ARRLEN(sqrt_func_params), sqrt, false, false);\n\n    LLVMTypeRef round_func_params[] = { LLVMDoubleType() };\n    add_function(exec, \"round\", LLVMDoubleType(), round_func_params, ARRLEN(round_func_params), round, false, false);\n\n    LLVMTypeRef floor_func_params[] = { LLVMDoubleType() };\n    add_function(exec, \"floor\", LLVMDoubleType(), floor_func_params, ARRLEN(floor_func_params), floor, false, false);\n\n    LLVMTypeRef pow_func_params[] = { LLVMDoubleType(), LLVMDoubleType() };\n    add_function(exec, \"pow\", LLVMDoubleType(), pow_func_params, ARRLEN(pow_func_params), pow, false, false);\n    \n    LLVMTypeRef get_char_func_params[] = { LLVMInt64Type() };\n    add_function(exec, \"std_term_get_char\", LLVMPointerType(LLVMInt8Type(), 0), get_char_func_params, ARRLEN(get_char_func_params), std_term_get_char, true, false);\n\n    LLVMTypeRef get_input_func_params[] = { LLVMInt64Type() };\n    add_function(exec, \"std_term_get_input\", LLVMPointerType(LLVMInt8Type(), 0), get_input_func_params, ARRLEN(get_input_func_params), std_term_get_input, true, false);\n\n    LLVMTypeRef set_clear_color_func_params[] = { LLVMInt32Type() };\n    add_function(exec, \"std_term_set_clear_color\", LLVMVoidType(), set_clear_color_func_params, ARRLEN(set_clear_color_func_params), std_term_set_clear_color, false, false);\n\n    LLVMTypeRef set_fg_color_func_params[] = { LLVMInt32Type() };\n    add_function(exec, \"std_term_set_fg_color\", LLVMVoidType(), set_fg_color_func_params, ARRLEN(set_fg_color_func_params), std_term_set_fg_color, false, false);\n\n    LLVMTypeRef set_bg_color_func_params[] = { LLVMInt32Type() };\n    add_function(exec, \"std_term_set_bg_color\", LLVMVoidType(), set_bg_color_func_params, ARRLEN(set_bg_color_func_params), std_term_set_bg_color, false, false);\n\n    LLVMTypeRef set_cursor_func_params[] = { LLVMInt32Type(), LLVMInt32Type() };\n    add_function(exec, \"std_term_set_cursor\", LLVMVoidType(), set_cursor_func_params, ARRLEN(set_cursor_func_params), std_term_set_cursor, false, false);\n\n    add_function(exec, \"std_term_cursor_x\", LLVMInt32Type(), NULL, 0, std_term_cursor_x, false, false);\n    add_function(exec, \"std_term_cursor_y\", LLVMInt32Type(), NULL, 0, std_term_cursor_y, false, false);\n    add_function(exec, \"std_term_cursor_max_x\", LLVMInt32Type(), NULL, 0, std_term_cursor_max_x, false, false);\n    add_function(exec, \"std_term_cursor_max_y\", LLVMInt32Type(), NULL, 0, std_term_cursor_max_y, false, false);\n\n    add_function(exec, \"std_term_clear\", LLVMVoidType(), NULL, 0, std_term_clear, false, false);\n\n    LLVMTypeRef list_new_func_params[] = { LLVMInt64Type() };\n    add_function(exec, \"std_list_new\", LLVMPointerType(LLVMInt8Type(), 0), list_new_func_params, ARRLEN(list_new_func_params), std_list_new, true, false);\n\n    LLVMTypeRef list_add_func_params[] = { LLVMInt64Type(), LLVMPointerType(LLVMInt8Type(), 0), LLVMInt32Type() };\n    add_function(exec, \"std_list_add\", LLVMVoidType(), list_add_func_params, ARRLEN(list_add_func_params), std_list_add, true, true);\n\n    LLVMTypeRef list_get_func_params[] = { LLVMInt64Type(), LLVMPointerType(LLVMInt8Type(), 0), LLVMInt32Type() };\n    add_function(exec, \"std_list_get\", LLVMPointerType(LLVMInt8Type(), 0), list_get_func_params, ARRLEN(list_get_func_params), std_list_get, true, false);\n\n    LLVMTypeRef list_set_func_params[] = { LLVMPointerType(LLVMInt8Type(), 0), LLVMInt32Type(), LLVMInt32Type() };\n    add_function(exec, \"std_list_set\", LLVMPointerType(LLVMInt8Type(), 0), list_set_func_params, ARRLEN(list_set_func_params), std_list_set, false, true);\n\n    LLVMTypeRef list_length_func_params[] = { LLVMPointerType(LLVMInt8Type(), 0) };\n    add_function(exec, \"std_list_length\", LLVMInt32Type(), list_length_func_params, ARRLEN(list_length_func_params), std_list_length, false, false);\n\n    LLVMTypeRef ceil_func_params[] = { LLVMDoubleType() };\n    add_function(exec, \"ceil\", LLVMDoubleType(), ceil_func_params, ARRLEN(ceil_func_params), ceil, false, false);\n\n    LLVMTypeRef test_cancel_func_params[] = { LLVMInt64Type() };\n    add_function(exec, \"test_cancel\", LLVMVoidType(), test_cancel_func_params, ARRLEN(test_cancel_func_params), exec_handle_running_state, false, false);\n\n    LLVMTypeRef stack_save_func_type = LLVMFunctionType(LLVMPointerType(LLVMVoidType(), 0), NULL, 0, false);\n    LLVMAddFunction(exec->module, \"llvm.stacksave.p0\", stack_save_func_type);\n\n    LLVMTypeRef stack_restore_func_params[] = { LLVMPointerType(LLVMVoidType(), 0) };\n    LLVMTypeRef stack_restore_func_type = LLVMFunctionType(LLVMVoidType(), stack_restore_func_params, ARRLEN(stack_restore_func_params), false);\n    LLVMAddFunction(exec->module, \"llvm.stackrestore.p0\", stack_restore_func_type);\n\n    LLVMTypeRef gc_root_begin_func_params[] = { LLVMInt64Type() };\n    add_function(exec, \"gc_root_begin\", LLVMVoidType(), gc_root_begin_func_params, ARRLEN(gc_root_begin_func_params), gc_root_begin, false, false);\n\n    LLVMTypeRef gc_root_end_func_params[] = { LLVMInt64Type() };\n    add_function(exec, \"gc_root_end\", LLVMVoidType(), gc_root_end_func_params, ARRLEN(gc_root_end_func_params), gc_root_end, false, false);\n    \n    LLVMTypeRef gc_flush_func_params[] = { LLVMInt64Type() };\n    add_function(exec, \"gc_flush\", LLVMVoidType(), gc_flush_func_params, ARRLEN(gc_flush_func_params), gc_flush, false, false);\n\n    LLVMTypeRef gc_add_root_func_params[] = { LLVMInt64Type(), LLVMPointerType(LLVMInt8Type(), 0) };\n    add_function(exec, \"gc_add_root\", LLVMVoidType(), gc_add_root_func_params, ARRLEN(gc_add_root_func_params), gc_add_root, false, false);\n\n    LLVMTypeRef gc_add_temp_root_func_params[] = { LLVMInt64Type(), LLVMPointerType(LLVMInt8Type(), 0) };\n    add_function(exec, \"gc_add_temp_root\", LLVMVoidType(), gc_add_temp_root_func_params, ARRLEN(gc_add_temp_root_func_params), gc_add_temp_root, false, false);\n\n    LLVMTypeRef gc_collect_func_params[] = { LLVMInt64Type() };\n    add_function(exec, \"gc_collect\", LLVMVoidType(), gc_collect_func_params, ARRLEN(gc_collect_func_params), gc_collect, false, false);\n\n    LLVMTypeRef gc_root_save_func_params[] = { LLVMInt64Type() };\n    add_function(exec, \"gc_root_save\", LLVMVoidType(), gc_root_save_func_params, ARRLEN(gc_root_save_func_params), gc_root_save, false, false);\n\n    LLVMTypeRef gc_root_restore_func_params[] = { LLVMInt64Type() };\n    add_function(exec, \"gc_root_restore\", LLVMVoidType(), gc_root_restore_func_params, ARRLEN(gc_root_restore_func_params), gc_root_restore, false, false);\n\n    LLVMAddGlobal(exec->module, LLVMInt64Type(), \"gc\");\n\n    LLVMTypeRef main_func_type = LLVMFunctionType(LLVMVoidType(), NULL, 0, false);\n    LLVMValueRef main_func = LLVMAddFunction(exec->module, MAIN_NAME, main_func_type);\n\n    return main_func;\n}\n\nstatic void free_defined_functions(Exec* exec) {\n    for (size_t i = 0; i < vector_size(exec->defined_functions); i++) {\n        vector_free(exec->defined_functions[i].args);\n    }\n    vector_free(exec->defined_functions);\n}\n\nstatic bool compile_program(Exec* exec) {\n    exec->compile_func_list = vector_create();\n    exec->global_variables = vector_create();\n    exec->gc_block_stack_len = 0;\n    exec->control_stack_len = 0;\n    exec->control_data_stack_len = 0;\n    exec->variable_stack_len = 0;\n    exec->variable_stack_frames_len = 0;\n    exec->build_random = false;\n    exec->gc_dirty = false;\n    exec->gc_dirty_funcs = vector_create();\n    exec->defined_functions = vector_create();\n    exec->current_state = STATE_COMPILE;\n\n    exec->module = LLVMModuleCreateWithName(\"scrap_module\");\n    LLVMSetTarget(exec->module, TARGET_TRIPLE);\n\n    LLVMValueRef main_func = register_globals(exec);\n    LLVMBasicBlockRef entry = LLVMAppendBasicBlock(main_func, \"entry\");\n\n    exec->builder = LLVMCreateBuilder();\n    LLVMPositionBuilderAtEnd(exec->builder, entry);\n\n    exec->gc_value = LLVMBuildLoad2(exec->builder, LLVMInt64Type(), LLVMGetNamedGlobal(exec->module, \"gc\"), \"get_gc\");\n\n    if (!build_gc_root_begin(exec, NULL)) return false;\n\n    for (size_t i = 0; i < vector_size(exec->code); i++) {\n        if (strcmp(exec->code[i].blocks[0].blockdef->id, \"on_start\")) continue;\n        if (!evaluate_chain(exec, &exec->code[i])) {\n            return false;\n        }\n    }\n\n    if (!build_gc_root_end(exec, NULL)) return false;\n    LLVMBuildRetVoid(exec->builder);\n\n    for (size_t i = 0; i < vector_size(exec->code); i++) {\n        if (!strcmp(exec->code[i].blocks[0].blockdef->id, \"on_start\")) continue;\n        if (!evaluate_chain(exec, &exec->code[i])) {\n            return false;\n        }\n\n        if (vector_size(exec->code[i].blocks) != 0 && exec->code[i].blocks[0].blockdef->type == BLOCKTYPE_HAT) {\n            if (!build_gc_root_end(exec, NULL)) return false;\n            build_call(exec, \"gc_root_restore\", CONST_GC);\n            LLVMValueRef val = build_call_count(exec, \"std_any_from_value\", 2, CONST_GC, CONST_INTEGER(DATA_TYPE_NOTHING));\n            LLVMBuildRet(exec->builder, val);\n        }\n    }\n\n    if (exec->build_random) {\n        LLVMBasicBlockRef random_block = LLVMInsertBasicBlock(entry, \"rand_init\");\n        LLVMPositionBuilderAtEnd(exec->builder, random_block);\n        LLVMValueRef time_val = build_call(exec, \"time\", LLVMConstPointerNull(LLVMPointerType(LLVMVoidType(), 0)));\n        build_call(exec, \"std_set_random_seed\", time_val);\n        LLVMBuildBr(exec->builder, entry);\n    }\n\n    char *error = NULL;\n    if (LLVMVerifyModule(exec->module, LLVMReturnStatusAction , &error)) {\n        exec_set_error(exec, NULL, gettext(\"Failed to build module: %s\"), error);\n        return false;\n    }\n    LLVMDisposeMessage(error);\n\n    LLVMDumpModule(exec->module);\n\n    LLVMDisposeBuilder(exec->builder);\n    vector_free(exec->gc_dirty_funcs);\n    vector_free(exec->global_variables);\n    free_defined_functions(exec);\n\n    return true;\n}\n\nstatic void vector_append(char** vec, const char* str) {\n    if (vector_size(*vec) > 0 && (*vec)[vector_size(*vec) - 1] == 0) vector_pop(*vec);\n    for (size_t i = 0; str[i]; i++) vector_add(vec, str[i]);\n    vector_add(vec, 0);\n}\n\nstatic bool file_exists(char* path) {\n    struct stat s;\n    if (stat(path, &s)) return false;\n    return S_ISREG(s.st_mode);\n}\n\n#ifndef _WIN32\nstatic char* find_path_glob(char* search_path, int file_len) {\n\tglob_t glob_buf;\n\tif (glob(search_path, 0, NULL, &glob_buf)) return NULL;\n\n    char* path = glob_buf.gl_pathv[0];\n    size_t len = strlen(path);\n\n    path[len - file_len] = 0;\n\n    char* out = vector_create();\n    vector_append(&out, path);\n    globfree(&glob_buf);\n    return out;\n}\n\nstatic char* find_crt(void) {\n    char* out;\n    if (file_exists(\"/usr/lib/crt1.o\")) {\n        out = vector_create();\n        vector_append(&out, \"/usr/lib/\");\n        return out;\n    }\n    if (file_exists(\"/usr/lib64/crt1.o\")) {\n        out = vector_create();\n        vector_append(&out, \"/usr/lib64/\");\n        return out;\n    }\n\n    out = find_path_glob(\"/usr/lib/x86_64*linux*/crt1.o\", sizeof(\"crt1.o\") - 1);\n    if (out) return out;\n    return find_path_glob(\"/usr/lib64/x86_64*linux*/crt1.o\", sizeof(\"crt1.o\") - 1);\n}\n\nstatic char* find_crt_begin(void) {\n    char* out = find_path_glob(\"/usr/lib/gcc/x86_64*linux*/*/crtbegin.o\", sizeof(\"crtbegin.o\") - 1);\n    if (out) return out;\n    return find_path_glob(\"/usr/lib64/gcc/x86_64*linux*/*/crtbegin.o\", sizeof(\"crtbegin.o\") - 1);\n}\n#endif\n\nstatic bool build_program(Exec* exec) {\n    exec->current_state = STATE_PRE_EXEC;\n\n    if (LLVMInitializeNativeTarget()) {\n        exec_set_error(exec, NULL, \"[LLVM] Native target initialization failed\");\n        return false;\n    }\n    if (LLVMInitializeNativeAsmParser()) {\n        exec_set_error(exec, NULL, \"[LLVM] Native asm parser initialization failed\");\n        return false;\n    }\n    if (LLVMInitializeNativeAsmPrinter()) {\n        exec_set_error(exec, NULL, \"[LLVM] Native asm printer initialization failed\");\n        return false;\n    }\n\n    char *error = NULL;\n\n    LLVMTargetRef target;\n\n    if (LLVMGetTargetFromTriple(TARGET_TRIPLE, &target, &error)) {\n        exec_set_error(exec, NULL, \"[LLVM] Failed to get target: %s\", error);\n        LLVMDisposeMessage(error);\n        return false;\n    }\n\n    LLVMTargetMachineOptionsRef machine_opts = LLVMCreateTargetMachineOptions();\n    LLVMTargetMachineOptionsSetCodeGenOptLevel(machine_opts, LLVMCodeGenLevelDefault);\n    LLVMTargetMachineOptionsSetRelocMode(machine_opts, LLVMRelocPIC);\n\n    LLVMTargetMachineRef machine = LLVMCreateTargetMachineWithOptions(target, TARGET_TRIPLE, machine_opts);\n    if (!machine) {\n        LLVMDisposeTargetMachineOptions(machine_opts);\n        exec_set_error(exec, NULL, \"[LLVM] Failed to create target machine\");\n        return false;\n    }\n    LLVMDisposeTargetMachineOptions(machine_opts);\n    \n    if (LLVMTargetMachineEmitToFile(machine, exec->module, \"output.o\", LLVMObjectFile, &error)) {\n        exec_set_error(exec, NULL, \"[LLVM] Failed to save to file: %s\", error);\n        LLVMDisposeTargetMachine(machine);\n        LLVMDisposeMessage(error);\n        return false;\n    }\n    LLVMDisposeTargetMachine(machine);\n    scrap_log(LOG_INFO, \"Built object file successfully\");\n\n    char link_error[1024];\n    char* command = vector_create();\n#ifdef _WIN32\n    // Command for linking on Windows. This thing requires gcc, which is not ideal :/\n    vector_append(&command, TextFormat(\"x86_64-w64-mingw32-gcc.exe -static -o %s.exe output.o -L. -L%s -lscrapstd-win -lm\", project_config.executable_name, GetApplicationDirectory()));\n#else\n    char* crt_dir = find_crt();\n    if (!crt_dir) {\n        exec_set_error(exec, NULL, \"Could not find crt files for linking\");\n        vector_free(command);\n        return false;\n    }\n\n    char* crt_begin_dir = find_crt_begin();\n\n    scrap_log(LOG_INFO, \"Crt dir: %s\", crt_dir);\n    if (crt_begin_dir) {\n        scrap_log(LOG_INFO, \"Crtbegin dir: %s\", crt_begin_dir);\n    } else {\n        scrap_log(LOG_WARNING, \"Crtbegin dir is not found!\");\n    }\n\n    vector_append(&command, TextFormat(\"%s \", project_config.linker_name));\n    vector_append(&command, \"-dynamic-linker /lib64/ld-linux-x86-64.so.2 \");\n    vector_append(&command, \"-pie \");\n    vector_append(&command, TextFormat(\"-o %s \", project_config.executable_name));\n\n    vector_append(&command, TextFormat(\"%scrti.o %sScrt1.o %scrtn.o \", crt_dir, crt_dir, crt_dir));\n    if (crt_begin_dir) vector_append(&command, TextFormat(\"%scrtbeginS.o %scrtendS.o \", crt_begin_dir, crt_begin_dir));\n\n    vector_append(&command, \"output.o \");\n    vector_append(&command, TextFormat(\"-L. -L%s -lscrapstd -L/usr/lib -L/lib -L/usr/local/lib -lm -lc\", GetApplicationDirectory()));\n\n    scrap_log(LOG_INFO, \"Full command: \\\"%s\\\"\", command);\n#endif\n    \n    bool res = spawn_process(command, link_error, 1024);\n    if (res) {\n        scrap_log(LOG_INFO, \"Linked successfully\");\n    } else {\n        exec_set_error(exec, NULL, link_error);\n    }\n\n    vector_free(command);\n#ifndef _WIN32\n    vector_free(crt_dir);\n    if (crt_begin_dir) vector_free(crt_begin_dir);\n#endif\n    return res;\n}\n\nstatic bool run_program(Exec* exec) {\n    exec->current_state = STATE_PRE_EXEC;\n\n    if (LLVMInitializeNativeTarget()) {\n        exec_set_error(exec, NULL, \"[LLVM] Native target initialization failed\");\n        return false;\n    }\n    if (LLVMInitializeNativeAsmParser()) {\n        exec_set_error(exec, NULL, \"[LLVM] Native asm parser initialization failed\");\n        return false;\n    }\n    if (LLVMInitializeNativeAsmPrinter()) {\n        exec_set_error(exec, NULL, \"[LLVM] Native asm printer initialization failed\");\n        return false;\n    }\n    LLVMLinkInMCJIT();\n\n    char *error = NULL;\n    if (LLVMCreateExecutionEngineForModule(&exec->engine, exec->module, &error)) {\n        exec_set_error(exec, NULL, \"[LLVM] Failed to create execution engine: %s\", error);\n        LLVMDisposeMessage(error);\n        return false;\n    }\n\n    for (size_t i = 0; i < vector_size(exec->compile_func_list); i++) {\n        LLVMValueRef func = LLVMGetNamedFunction(exec->module, exec->compile_func_list[i].name);\n        if (!func) continue;\n        LLVMAddGlobalMapping(exec->engine, func, exec->compile_func_list[i].func);\n    }\n\n    vector_free(exec->compile_func_list);\n\n    exec->gc = gc_new(MIN_MEMORY_LIMIT, MAX_MEMORY_LIMIT);\n    Gc* gc_ref = &exec->gc;\n    LLVMAddGlobalMapping(exec->engine, LLVMGetNamedGlobal(exec->module, \"gc\"), &gc_ref);\n\n    // For some weird reason calling pthread_exit() inside LLVM results in segfault, so we avoid that by using setjmp. \n    // This unfortunately leaks a little bit of memory inside LLVMRunFunction() though :P\n    if (setjmp(exec->run_jump_buf)) {\n        thread_exit(exec->thread, false);\n    } else {\n        memcpy(exec->gc.run_jump_buf, exec->run_jump_buf, sizeof(exec->gc.run_jump_buf));\n        exec->current_state = STATE_EXEC;\n        LLVMGenericValueRef val = LLVMRunFunction(exec->engine, LLVMGetNamedFunction(exec->module, \"llvm_main\"), 0, NULL);\n        LLVMDisposeGenericValue(val);\n    }\n\n    return true;\n}\n"
  },
  {
    "path": "src/compiler.h",
    "content": "// Scrap is a project that allows anyone to build software using simple, block based interface.\n//\n// Copyright (C) 2024-2026 Grisshink\n// \n// This software is provided 'as-is', without any express or implied\n// warranty.  In no event will the authors be held liable for any damages\n// arising from the use of this software.\n// \n// Permission is granted to anyone to use this software for any purpose,\n// including commercial applications, and to alter it and redistribute it\n// freely, subject to the following restrictions:\n// \n// 1. The origin of this software must not be misrepresented; you must not\n//    claim that you wrote the original software. If you use this software\n//    in a product, an acknowledgment in the product documentation would be\n//    appreciated but is not required.\n// 2. Altered source versions must be plainly marked as such, and must not be\n//    misrepresented as being the original software.\n// 3. This notice may not be removed or altered from any source distribution.\n\n#ifndef COMPILER_H\n#define COMPILER_H\n\n#include \"gc.h\"\n#include \"ast.h\"\n#include \"thread.h\"\n\n#include <llvm-c/Core.h>\n#include <llvm-c/ExecutionEngine.h>\n#include <setjmp.h>\n\n#define VM_ARG_STACK_SIZE 1024\n#define VM_CONTROL_STACK_SIZE 1024\n#define VM_CONTROL_DATA_STACK_SIZE 32768\n#define VM_VARIABLE_STACK_SIZE 1024\n\n#define control_data_stack_push_data(data, type) \\\n    if (exec->control_data_stack_len + sizeof(type) > VM_CONTROL_DATA_STACK_SIZE) { \\\n        scrap_log(LOG_ERROR, \"[LLVM] Control stack overflow\"); \\\n        return false; \\\n    } \\\n    *(type *)(exec->control_data_stack + exec->control_data_stack_len) = (data); \\\n    exec->control_data_stack_len += sizeof(type);\n\n#define control_data_stack_pop_data(data, type) \\\n    if (sizeof(type) > exec->control_data_stack_len) { \\\n        scrap_log(LOG_ERROR, \"[LLVM] Control stack underflow\"); \\\n        return false; \\\n    } \\\n    exec->control_data_stack_len -= sizeof(type); \\\n    data = *(type*)(exec->control_data_stack + exec->control_data_stack_len);\n\ntypedef enum {\n    CONTROL_STATE_NORMAL = 0,\n    CONTROL_STATE_BEGIN,\n    CONTROL_STATE_END,\n} ControlState;\n\ntypedef union {\n    LLVMValueRef value;\n    const char* str;\n    Blockdef* blockdef;\n} FuncArgData;\n\ntypedef struct {\n    DataType type;\n    FuncArgData data;\n} FuncArg;\n\ntypedef struct {\n    FuncArg value;\n    LLVMTypeRef type;\n    const char* name;\n} Variable;\n\ntypedef struct {\n    size_t base_size;\n    LLVMValueRef base_stack;\n} VariableStackFrame;\n\ntypedef struct {\n    const char* name;\n    void* func;\n    LLVMTypeRef type;\n    bool dynamic;\n} CompileFunction;\n\ntypedef struct {\n    Blockdef* blockdef;\n    LLVMValueRef arg;\n} DefineArgument;\n\ntypedef struct {\n    Blockdef* blockdef;\n    LLVMValueRef func;\n    DefineArgument* args;\n} DefineFunction;\n\ntypedef enum {\n    STATE_NONE,\n    STATE_COMPILE,\n    STATE_PRE_EXEC,\n    STATE_EXEC,\n} CompilerState;\n\ntypedef enum {\n    COMPILER_MODE_JIT = 0,\n    COMPILER_MODE_BUILD,\n} CompilerMode;\n\ntypedef struct {\n    LLVMValueRef root_begin;\n    bool required;\n} GcBlock;\n\ntypedef struct {\n    BlockChain* code;\n    LLVMModuleRef module;\n    LLVMBuilderRef builder;\n    LLVMExecutionEngineRef engine;\n\n    Block* control_stack[VM_CONTROL_STACK_SIZE];\n    size_t control_stack_len;\n\n    GcBlock gc_block_stack[VM_CONTROL_STACK_SIZE];\n    size_t gc_block_stack_len;\n\n    unsigned char control_data_stack[VM_CONTROL_DATA_STACK_SIZE];\n    size_t control_data_stack_len;\n\n    Variable variable_stack[VM_VARIABLE_STACK_SIZE];\n    size_t variable_stack_len;\n\n    Variable* global_variables;\n\n    VariableStackFrame variable_stack_frames[VM_VARIABLE_STACK_SIZE];\n    size_t variable_stack_frames_len;\n\n    CompileFunction* compile_func_list;\n    DefineFunction* defined_functions;\n\n    Gc gc;\n    LLVMValueRef gc_value;\n\n    CompilerState current_state;\n\n    char current_error[MAX_ERROR_LEN];\n    Block* current_error_block;\n\n    bool build_random;\n\n    // Needed for compiler to determine if some block uses gc_malloc so we could call gc_flush afterwards\n    bool gc_dirty;\n    LLVMValueRef* gc_dirty_funcs;\n\n    jmp_buf run_jump_buf;\n\n    Thread* thread;\n    CompilerMode current_mode;\n} Exec;\n\n#define MAIN_NAME \"llvm_main\"\n\n#define CONST_NOTHING LLVMConstPointerNull(LLVMVoidType())\n#define CONST_INTEGER(val) LLVMConstInt(LLVMInt32Type(), val, true)\n#define CONST_BOOLEAN(val) LLVMConstInt(LLVMInt1Type(), val, false)\n#define CONST_FLOAT(val) LLVMConstReal(LLVMDoubleType(), val)\n#define CONST_STRING_LITERAL(val) LLVMBuildGlobalStringPtr(exec->builder, val, \"\")\n#define CONST_GC exec->gc_value\n#define CONST_EXEC LLVMConstInt(LLVMInt64Type(), (unsigned long long)exec, false)\n\n#define _DATA(t, val) (FuncArg) { \\\n    .type = t, \\\n    .data = (FuncArgData) { \\\n        .value = val, \\\n    }, \\\n}\n\n#define DATA_BOOLEAN(val) _DATA(DATA_TYPE_BOOL, val)\n#define DATA_STRING(val) _DATA(DATA_TYPE_STRING, val)\n#define DATA_INTEGER(val) _DATA(DATA_TYPE_INTEGER, val)\n#define DATA_FLOAT(val) _DATA(DATA_TYPE_FLOAT, val)\n#define DATA_LIST(val) _DATA(DATA_TYPE_LIST, val)\n#define DATA_COLOR(val) _DATA(DATA_TYPE_COLOR, val)\n#define DATA_ANY(val) _DATA(DATA_TYPE_ANY, val)\n#define DATA_UNKNOWN _DATA(DATA_TYPE_UNKNOWN, NULL)\n#define DATA_NOTHING _DATA(DATA_TYPE_NOTHING, CONST_NOTHING)\n\ntypedef bool (*BlockCompileFunc)(Exec* exec, Block* block, int argc, FuncArg* argv, FuncArg* return_val, ControlState control_state);\n\nExec exec_new(Thread* thread, CompilerMode mode);\nbool exec_run(void* e);\nvoid exec_cleanup(void* e);\nvoid exec_free(Exec* exec);\nvoid exec_set_error(Exec* exec, Block* block, const char* fmt, ...);\n\nbool variable_stack_push(Exec* exec, Block* block, Variable variable);\nVariable* variable_get(Exec* exec, const char* var_name);\nvoid global_variable_add(Exec* exec, Variable variable);\n\nLLVMValueRef build_gc_root_begin(Exec* exec, Block* block);\nLLVMValueRef build_gc_root_end(Exec* exec, Block* block);\nLLVMValueRef build_call(Exec* exec, const char* func_name, ...);\nLLVMValueRef build_call_count(Exec* exec, const char* func_name, size_t func_param_count, ...);\n\nDefineFunction* define_function(Exec* exec, Blockdef* blockdef);\nDefineArgument* get_custom_argument(Exec* exec, Blockdef* blockdef, DefineFunction** func);\n\n#endif // COMPILER_H\n"
  },
  {
    "path": "src/config.h",
    "content": "// Scrap is a project that allows anyone to build software using simple, block based interface.\n//\n// Copyright (C) 2024-2026 Grisshink\n// \n// This software is provided 'as-is', without any express or implied\n// warranty.  In no event will the authors be held liable for any damages\n// arising from the use of this software.\n// \n// Permission is granted to anyone to use this software for any purpose,\n// including commercial applications, and to alter it and redistribute it\n// freely, subject to the following restrictions:\n// \n// 1. The origin of this software must not be misrepresented; you must not\n//    claim that you wrote the original software. If you use this software\n//    in a product, an acknowledgment in the product documentation would be\n//    appreciated but is not required.\n// 2. Altered source versions must be plainly marked as such, and must not be\n//    misrepresented as being the original software.\n// 3. This notice may not be removed or altered from any source distribution.\n\n#define SCRAP_SAVE_VERSION 4\n\n#define EDITOR_DEFAULT_PROJECT_NAME \"project.scrp\"\n\n#define DROP_TEX_WIDTH ((float)(config.ui_size - BLOCK_OUTLINE_SIZE * 4) / (float)drop_tex.height * (float)drop_tex.width)\n#define FONT_PATH_MAX_SIZE 256\n#define FONT_SYMBOLS_MAX_SIZE 1024\n#define ACTION_BAR_MAX_SIZE 128\n\n#define ELEMENT_GAP ((float)config.ui_size * 0.25)\n\n#define SHADOW_DISTANCE floorf(1.66 * (float)config.ui_size / 32.0)\n#define BLOCK_OUTLINE_SIZE (2.0 * (float)config.ui_size / 32.0)\n#define BLOCK_TEXT_SIZE floorf((float)config.ui_size * 0.6)\n#define BLOCK_IMAGE_SIZE (config.ui_size - BLOCK_OUTLINE_SIZE * 4)\n#define BLOCK_PADDING (5.0 * (float)config.ui_size / 32.0)\n#define BLOCK_STRING_PADDING (10.0 * (float)config.ui_size / 32.0)\n#define BLOCK_CONTROL_INDENT (16.0 * (float)config.ui_size / 32.0)\n#define BLOCK_GHOST_OPACITY 0x99\n#define BLOCK_ARG_OPACITY 0xdd\n\n#define PANEL_BACKGROUND_COLOR { 0x10, 0x10, 0x10, 0xff }\n\n#define TEXT_SELECTION_COLOR { 0x00, 0x60, 0xff, 0x80 }\n\n#define DATA_PATH \"data/\"\n#define LOCALE_PATH \"locale/\"\n#define CONFIG_PATH \"config.txt\"\n#define CONFIG_FOLDER_NAME \"scrap\"\n\n#define LICENSE_URL \"https://github.com/Grisshink/scrap/blob/main/LICENSE\"\n\n#define CODEPOINT_REGION_COUNT 3\n\n#define DEBUG_BUFFER_LINES 32\n#define DEBUG_BUFFER_LINE_SIZE 256\n\n#define CATEGORY_CONTROL_COLOR { 0xff, 0x99, 0x00, 0xff }\n#define CATEGORY_TERMINAL_COLOR { 0x00, 0xaa, 0x44, 0xff }\n#define CATEGORY_MATH_COLOR { 0x00, 0xcc, 0x77, 0xff }\n#define CATEGORY_LOGIC_COLOR { 0x77, 0xcc, 0x44, 0xff }\n#define CATEGORY_STRING_COLOR { 0xff, 0x00, 0x99, 0xff }\n#define CATEGORY_MISC_COLOR { 0x00, 0x99, 0xff, 0xff }\n#define CATEGORY_DATA_COLOR { 0xff, 0x77, 0x00, 0xff }\n\n#define UNIMPLEMENTED_BLOCK_COLOR { 0x66, 0x66, 0x66, 0xff }\n\n#define MAX_ERROR_LEN 512\n\n#define MIN_MEMORY_LIMIT 4194304 // 4 MB\n#define MAX_MEMORY_LIMIT 4294967296 // 4 GB\n"
  },
  {
    "path": "src/gc.c",
    "content": "// Scrap is a project that allows anyone to build software using simple, block based interface.\n//\n// Copyright (C) 2024-2026 Grisshink\n// \n// This software is provided 'as-is', without any express or implied\n// warranty.  In no event will the authors be held liable for any damages\n// arising from the use of this software.\n// \n// Permission is granted to anyone to use this software for any purpose,\n// including commercial applications, and to alter it and redistribute it\n// freely, subject to the following restrictions:\n// \n// 1. The origin of this software must not be misrepresented; you must not\n//    claim that you wrote the original software. If you use this software\n//    in a product, an acknowledgment in the product documentation would be\n//    appreciated but is not required.\n// 2. Altered source versions must be plainly marked as such, and must not be\n//    misrepresented as being the original software.\n// 3. This notice may not be removed or altered from any source distribution.\n\n#include <assert.h>\n#include <stdio.h>\n#include <time.h>\n#include <stdarg.h>\n\n#include \"gc.h\"\n#include \"std.h\"\n\n#ifdef STANDALONE_STD\n\n#define EXIT exit(1)\n#define TRACE_LOG(loglevel, ...) fprintf(stderr, __VA_ARGS__)\n\n#ifdef _WIN32\n#include <windows.h>\n#endif // _WIN32\n\n#else\n\n#include \"util.h\"\n#define EXIT longjmp(gc->run_jump_buf, 1)\n#define TRACE_LOG(loglevel, ...) scrap_log(loglevel, __VA_ARGS__)\n\n#endif // STANDALONE_STD\n\n#define MIN(x, y) ((x) < (y) ? (x) : (y))\n#define MAX_TEXT_BUFFER_LENGTH 512\n\nstatic void gc_mark_refs(Gc* gc, GcChunkData* chunk);\n\nstatic const char *text_format(const char *text, ...) {\n    static char buffer[MAX_TEXT_BUFFER_LENGTH] = {0};\n    buffer[0] = 0;\n\n    va_list args;\n    va_start(args, text);\n    int requiredByteCount = vsnprintf(buffer, MAX_TEXT_BUFFER_LENGTH, text, args);\n    va_end(args);\n\n    // If requiredByteCount is larger than the MAX_TEXT_BUFFER_LENGTH, then overflow occured\n    if (requiredByteCount >= MAX_TEXT_BUFFER_LENGTH) {\n        // Inserting \"...\" at the end of the string to mark as truncated\n        char *truncBuffer = buffer + MAX_TEXT_BUFFER_LENGTH - 4; // Adding 4 bytes = \"...\\0\"\n        sprintf(truncBuffer, \"...\");\n    }\n\n    return buffer;\n}\n\nGc gc_new(size_t memory_min, size_t memory_max) {\n    return (Gc) {\n        .chunks = vector_create(),\n        .roots_stack = vector_create(),\n        .roots_bases = vector_create(),\n        .root_chunks = vector_create(),\n        .root_temp_chunks = vector_create(),\n        .memory_used = 0,\n        .memory_allocated = memory_min,\n        .memory_max = memory_max,\n    };\n}\n\nvoid gc_free(Gc* gc) {\n#ifdef DEBUG\n    TRACE_LOG(LOG_INFO, \"[GC] gc_free: used %zu bytes, allocated %zu chunks\", gc->memory_used, vector_size(gc->chunks));\n#endif\n    for (size_t i = 0; i < vector_size(gc->chunks); i++) {\n        free(gc->chunks[i].ptr);\n    }\n    vector_free(gc->roots_bases);\n    vector_free(gc->root_chunks);\n    vector_free(gc->root_temp_chunks);\n    vector_free(gc->roots_stack);\n    vector_free(gc->chunks);\n    gc->memory_max = 0;\n    gc->memory_allocated = 0;\n    gc->memory_used = 0;\n}\n\nstatic void gc_mark_any(Gc* gc, AnyValue* any) {\n    GcChunkData* chunk_inner;\n\n    if (any->type == DATA_TYPE_LIST) {\n        chunk_inner = ((GcChunkData*)any->data.list_val) - 1;\n        if (!chunk_inner->marked) {\n            chunk_inner->marked = 1;\n            gc_mark_refs(gc, chunk_inner);\n        }\n    } else if (any->type == DATA_TYPE_STRING) {\n        chunk_inner = ((GcChunkData*)any->data.str_val) - 1;\n        chunk_inner->marked = 1;\n    }\n}\n\nstatic void gc_mark_refs(Gc* gc, GcChunkData* chunk) {\n    switch (chunk->data_type) {\n    case DATA_TYPE_LIST: ;\n        List* list = (List*)chunk->data;\n        if (!list->values) break;\n\n        GcChunkData* list_values_chunk = ((GcChunkData*)list->values) - 1;\n        list_values_chunk->marked = 1;\n        \n        for (long i = 0; i < list->size; i++) {\n            gc_mark_any(gc, &list->values[i]);\n        }\n        break;\n    case DATA_TYPE_ANY:\n        gc_mark_any(gc, (AnyValue*)chunk->data);\n        break;\n    default:\n        break;\n    }\n}\n\nvoid gc_collect(Gc* gc) {\n#ifdef DEBUG\n#if defined(_WIN32) && defined(STANDALONE_STD)\n    long t;\n    long t_freq;\n\n    QueryPerformanceCounter((LARGE_INTEGER*)&t);\n    QueryPerformanceFrequency((LARGE_INTEGER*)&t_freq);\n#else\n    struct timespec t;\n    clock_gettime(CLOCK_MONOTONIC, &t);\n#endif // defined(_WIN32) && defined(STANDALONE_STD)\n#endif // DEBUG\n\n    // Mark roots\n    for (size_t i = 0; i < vector_size(gc->root_chunks); i++) {\n        GcChunkData* chunk = (*gc->root_chunks[i]) - 1;\n        if (chunk->marked) continue;\n        chunk->marked = 1;\n        gc_mark_refs(gc, chunk);\n    }\n\n    for (size_t i = 0; i < vector_size(gc->root_temp_chunks); i++) {\n        if (gc->root_temp_chunks[i]->marked) continue;\n        gc->root_temp_chunks[i]->marked = 1;\n        gc_mark_refs(gc, gc->root_temp_chunks[i]);\n    }\n\n    // Find unmarked chunks\n    size_t memory_freed = 0;\n    size_t chunks_deleted = 0;\n    for (int i = vector_size(gc->chunks) - 1; i >= 0; i--) {\n        if (gc->chunks[i].ptr->marked) {\n            gc->chunks[i].ptr->marked = false;\n            continue;\n        }\n        free(gc->chunks[i].ptr);\n        memory_freed += gc->chunks[i].len;\n        chunks_deleted++;\n        vector_remove(gc->chunks, i);\n    }\n\n    for (size_t i = 0; i < vector_size(gc->root_chunks); i++) {\n        GcChunkData* chunk = (*gc->root_chunks[i]) - 1;\n        chunk->marked = 0;\n    }\n\n    gc->memory_used -= memory_freed;\n\n#ifdef DEBUG\n#if defined(_WIN32) && defined(STANDALONE_STD)\n    long end_time;\n    QueryPerformanceCounter((LARGE_INTEGER*)&end_time);\n\n    double gc_time = (double)(end_time - t) * 1e+6 / (double)t_freq;\n#else\n    struct timespec end_time;\n    clock_gettime(CLOCK_MONOTONIC, &end_time);\n\n    double gc_time = (end_time.tv_sec - t.tv_sec) * 1e+6 + (end_time.tv_nsec - t.tv_nsec) * 1e-3;\n#endif // defined(_WIN32) && defined(STANDALONE_STD)\n    TRACE_LOG(LOG_INFO, \"[GC] gc_collect: freed %zu bytes, deleted %zu chunks, time: %.2fus\", memory_freed, chunks_deleted, gc_time);\n#endif // DEBUG\n}\n\nvoid* gc_malloc(Gc* gc, size_t size, DataType data_type) {\n    assert(vector_size(gc->roots_stack) > 0);\n\n    if (size > gc->memory_max) {\n        std_term_print_str(text_format(\"*[GC] Memory limit exeeded! Tried to allocate %zu bytes past maximum memory limit in gc (%zu bytes)*\", size, gc->memory_max));\n        EXIT;\n    }\n\n    if (gc->memory_used + size > gc->memory_allocated) gc_collect(gc);\n    if (gc->memory_used + size > gc->memory_allocated) {\n        gc->memory_allocated = MIN(gc->memory_allocated * 2, gc->memory_max);\n#ifdef DEBUG\n        TRACE_LOG(LOG_WARNING, \"[GC] gc_malloc: raising memory limit to %zu bytes\", gc->memory_allocated);\n#endif\n    }\n    if (gc->memory_used + size > gc->memory_allocated) {\n        std_term_print_str(text_format(\"*[GC] Memory limit exeeded! Tried to allocate %zu bytes in gc with %zu bytes free*\", size, gc->memory_max - gc->memory_used));\n        EXIT;\n    }\n\n    GcChunkData* chunk_data = malloc(size + sizeof(GcChunkData));\n    if (chunk_data == NULL) return NULL;\n\n    chunk_data->marked = 0;\n    chunk_data->data_type = data_type;\n    GcChunk chunk = (GcChunk) {\n        .ptr = chunk_data,\n        .len = size,\n    };\n\n    vector_add(&gc->chunks, chunk);\n    vector_add(&gc->root_temp_chunks, chunk.ptr);\n    gc->memory_used += size;\n\n    return chunk_data->data;\n}\n\nvoid gc_root_begin(Gc* gc) {\n    if (vector_size(gc->roots_stack) > 1024) {\n        std_term_print_str(\"*[GC] Root stack overflow!*\");\n        EXIT;\n    }\n\n    GcRoot* root = vector_add_dst(&gc->roots_stack);\n    root->chunks_base = vector_size(gc->root_chunks);\n    root->temp_chunks_base = vector_size(gc->root_temp_chunks);\n}\n\nvoid gc_root_end(Gc* gc) {\n    assert(vector_size(gc->roots_stack) > 0);\n    vector_get_header(gc->root_chunks)->size = gc->roots_stack[vector_size(gc->roots_stack) - 1].chunks_base;\n    vector_get_header(gc->root_temp_chunks)->size = gc->roots_stack[vector_size(gc->roots_stack) - 1].temp_chunks_base;\n    vector_pop(gc->roots_stack);\n}\n\nvoid gc_add_root(Gc* gc, void* stack_ptr) {\n    vector_add(&gc->root_chunks, stack_ptr);\n}\n\nvoid gc_add_temp_root(Gc* gc, void* ptr) {\n    GcChunkData* chunk = ((GcChunkData*)ptr) - 1;\n    vector_add(&gc->root_temp_chunks, chunk);\n}\n\nvoid gc_root_save(Gc* gc) {\n    if (vector_size(gc->roots_bases) > 1024) {\n        std_term_print_str(\"*[GC] Root stack overflow!*\");\n        EXIT;\n    }\n\n    vector_add(&gc->roots_bases, vector_size(gc->roots_stack));\n}\n\nvoid gc_root_restore(Gc* gc) {\n    if (vector_size(gc->roots_bases) == 0) {\n        std_term_print_str(\"*[GC] Root stack underflow!*\");\n        EXIT;\n    }\n\n    size_t* size = &vector_get_header(gc->roots_stack)->size;\n    size_t prev_size = *size;\n    *size = gc->roots_bases[vector_size(gc->roots_bases) - 1];\n    vector_pop(gc->roots_bases);\n\n    if (*size == 0) {\n        vector_clear(gc->root_chunks);\n        vector_clear(gc->root_temp_chunks);\n    } else if (*size < prev_size) {\n        vector_get_header(gc->root_chunks)->size = gc->roots_stack[*size].chunks_base;\n        vector_get_header(gc->root_temp_chunks)->size = gc->roots_stack[*size].temp_chunks_base;\n    }\n}\n\nvoid gc_flush(Gc* gc) {\n    vector_get_header(gc->root_temp_chunks)->size = gc->roots_stack[vector_size(gc->roots_stack) - 1].temp_chunks_base;\n}\n"
  },
  {
    "path": "src/gc.h",
    "content": "// Scrap is a project that allows anyone to build software using simple, block based interface.\n//\n// Copyright (C) 2024-2026 Grisshink\n// \n// This software is provided 'as-is', without any express or implied\n// warranty.  In no event will the authors be held liable for any damages\n// arising from the use of this software.\n// \n// Permission is granted to anyone to use this software for any purpose,\n// including commercial applications, and to alter it and redistribute it\n// freely, subject to the following restrictions:\n// \n// 1. The origin of this software must not be misrepresented; you must not\n//    claim that you wrote the original software. If you use this software\n//    in a product, an acknowledgment in the product documentation would be\n//    appreciated but is not required.\n// 2. Altered source versions must be plainly marked as such, and must not be\n//    misrepresented as being the original software.\n// 3. This notice may not be removed or altered from any source distribution.\n\n#ifndef SCRAP_GC_H\n#define SCRAP_GC_H\n\n#include <stddef.h>\n#ifndef STANDALONE_STD\n#include <setjmp.h>\n#endif\n\n#include \"ast.h\"\n#include \"vec.h\"\n\ntypedef struct {\n    unsigned char marked;\n    DataType data_type;\n    unsigned char data[];\n} GcChunkData;\n\ntypedef struct {\n    GcChunkData* ptr;\n    size_t len;\n} GcChunk;\n\ntypedef struct {\n    size_t chunks_base;\n    size_t temp_chunks_base;\n} GcRoot;\n\ntypedef struct {\n    GcChunk* chunks;\n    size_t* roots_bases;\n    GcRoot* roots_stack;\n    // NOTE: This variable stores a list of stack addresses pointing at gc_malloc'd memory, \n    // so you need to offset a pointer by -1 before dereferencing GcChunkData\n    GcChunkData*** root_chunks;\n    GcChunkData** root_temp_chunks;\n    size_t memory_used;\n    size_t memory_allocated;\n    size_t memory_max;\n#ifndef STANDALONE_STD\n    jmp_buf run_jump_buf;\n#endif\n} Gc;\n\nGc gc_new(size_t memory_min, size_t memory_max);\nvoid gc_free(Gc* gc);\n\nvoid gc_root_begin(Gc* gc);\nvoid gc_root_end(Gc* gc);\nvoid* gc_malloc(Gc* gc, size_t size, DataType data_type);\nvoid gc_collect(Gc* gc);\nvoid gc_flush(Gc* gc);\nvoid gc_add_root(Gc* gc, void* ptr);\nvoid gc_add_temp_root(Gc* gc, void* ptr);\nvoid gc_root_save(Gc* gc);\nvoid gc_root_restore(Gc* gc);\n\n#endif // SCRAP_GC_H\n"
  },
  {
    "path": "src/interpreter.c",
    "content": "// Scrap is a project that allows anyone to build software using simple, block based interface.\n//\n// Copyright (C) 2024-2026 Grisshink\n// \n// This software is provided 'as-is', without any express or implied\n// warranty.  In no event will the authors be held liable for any damages\n// arising from the use of this software.\n// \n// Permission is granted to anyone to use this software for any purpose,\n// including commercial applications, and to alter it and redistribute it\n// freely, subject to the following restrictions:\n// \n// 1. The origin of this software must not be misrepresented; you must not\n//    claim that you wrote the original software. If you use this software\n//    in a product, an acknowledgment in the product documentation would be\n//    appreciated but is not required.\n// 2. Altered source versions must be plainly marked as such, and must not be\n//    misrepresented as being the original software.\n// 3. This notice may not be removed or altered from any source distribution.\n\n#include \"scrap.h\"\n#include \"ast.h\"\n#include \"vec.h\"\n\n#include <stdlib.h>\n#include <string.h>\n#include <stdio.h>\n#include <libintl.h>\n#include <assert.h>\n\nvoid arg_stack_push_arg(Exec* exec, AnyValue data);\nvoid arg_stack_undo_args(Exec* exec, size_t count);\nvoid variable_stack_pop_layer(Exec* exec);\nvoid variable_stack_cleanup(Exec* exec);\nvoid chain_stack_push(Exec* exec, ChainStackData data);\nvoid chain_stack_pop(Exec* exec);\nbool exec_block(Exec* exec, Block* block, AnyValue* block_return, ControlState control_state, AnyValue control_arg);\n\nvoid define_function(Exec* exec, Blockdef* blockdef, BlockChain* chain) {\n    DefineFunction* func = vector_add_dst(&exec->defined_functions);\n    func->blockdef = blockdef;\n    func->run_chain = chain;\n    func->args = vector_create();\n\n    int arg_ind = 0;\n    for (size_t i = 0; i < vector_size(blockdef->inputs); i++) {\n        if (blockdef->inputs[i].type != INPUT_ARGUMENT) continue;\n\n        DefineArgument* arg = vector_add_dst(&func->args);\n        arg->blockdef = blockdef->inputs[i].data.arg.blockdef;\n        arg->arg_ind = arg_ind++;\n    }\n}\n\nExec exec_new(Thread* thread) {\n    Exec exec = (Exec) {\n        .code = NULL,\n        .arg_stack_len = 0,\n        .control_stack_len = 0,\n        .thread = thread,\n        .current_error_block = NULL,\n    };\n    exec.current_error[0] = 0;\n    return exec;\n}\n\nvoid exec_free(Exec* exec) {\n    (void) exec;\n}\n\nbool exec_run(void* e) {\n    Exec* exec = e;\n    exec->arg_stack_len = 0;\n    exec->control_stack_len = 0;\n    exec->chain_stack_len = 0;\n    exec->running_chain = NULL;\n    exec->defined_functions = vector_create();\n    exec->gc = gc_new(MIN_MEMORY_LIMIT, MAX_MEMORY_LIMIT);\n\n    SetRandomSeed(time(NULL));\n\n    for (size_t i = 0; i < vector_size(exec->code); i++) {\n        Block* block = &exec->code[i].blocks[0];\n        if (strcmp(block->blockdef->id, \"define_block\")) continue;\n\n        for (size_t j = 0; j < vector_size(block->arguments); j++) {\n            if (block->arguments[j].type != ARGUMENT_BLOCKDEF) continue;\n            define_function(exec, block->arguments[j].data.blockdef, &exec->code[i]);\n        }\n    }\n\n    for (size_t i = 0; i < vector_size(exec->code); i++) {\n        Block* block = &exec->code[i].blocks[0];\n        if (block->blockdef->type != BLOCKTYPE_HAT) continue;\n        bool cont = false;\n        for (size_t j = 0; j < vector_size(block->arguments); j++) {\n            if (block->arguments[j].type == ARGUMENT_BLOCKDEF) {\n                cont = true;\n                break;\n            }\n        }\n        if (cont) continue;\n        AnyValue bin;\n        if (!exec_run_chain(exec, &exec->code[i], -1, NULL, &bin)) {\n            exec->running_chain = NULL;\n            return false;\n        }\n        exec->running_chain = NULL;\n    }\n\n    return true;\n}\n\nvoid exec_cleanup(void* e) {\n    Exec* exec = e;\n    for (size_t i = 0; i < vector_size(exec->defined_functions); i++) {\n        vector_free(exec->defined_functions[i].args);\n    }\n    vector_free(exec->defined_functions);\n    variable_stack_cleanup(exec);\n    arg_stack_undo_args(exec, exec->arg_stack_len);\n    gc_free(&exec->gc);\n}\n\nvoid exec_set_error(Exec* exec, Block* block, const char* fmt, ...) {\n    exec->current_error_block = block;\n    va_list va;\n    va_start(va, fmt);\n    vsnprintf(exec->current_error, MAX_ERROR_LEN, fmt, va);\n    va_end(va);\n    scrap_log(LOG_ERROR, \"[EXEC] %s\", exec->current_error);\n}\n\nbool evaluate_argument(Exec* exec, Argument* arg, AnyValue* return_val) {\n    static_assert(ARGUMENT_LAST == 5, \"Exhaustive argument type in evaluate_argument\");\n    switch (arg->type) {\n    case ARGUMENT_TEXT:\n    case ARGUMENT_CONST_STRING:\n        *return_val = DATA_LITERAL(arg->data.text);\n        return true;\n    case ARGUMENT_BLOCK:\n        if (!exec_block(exec, &arg->data.block, return_val, CONTROL_STATE_NORMAL, (AnyValue) {0})) {\n            return false;\n        }\n        return true;\n    case ARGUMENT_BLOCKDEF:\n        return true;\n    case ARGUMENT_COLOR:\n        *return_val = DATA_COLOR(CONVERT_COLOR(arg->data.color, StdColor));\n        return true;\n    default:\n        assert(false && \"Unimplemented argument type in evaluate_argument\");\n    }\n    return false;\n}\n\nbool exec_block(Exec* exec, Block* block, AnyValue* block_return, ControlState control_state, AnyValue control_arg) {\n    if (!block->blockdef) {\n        exec_set_error(exec, block, gettext(\"Tried to execute block without definition\"));\n        return false;\n    }\n    if (!block->blockdef->func) {\n        exec_set_error(exec, block, gettext(\"Tried to execute block \\\"%s\\\" without implementation\"), block->blockdef->id);\n        return false;\n    }\n\n    BlockFunc execute_block = block->blockdef->func;\n\n    int stack_begin = exec->arg_stack_len;\n\n    if (block->blockdef->type == BLOCKTYPE_CONTROLEND && control_state == CONTROL_STATE_BEGIN) {\n        arg_stack_push_arg(exec, control_arg);\n    }\n\n    size_t last_temps = vector_size(exec->gc.root_temp_chunks);\n\n    if (control_state != CONTROL_STATE_END) {\n        for (vec_size_t i = 0; i < vector_size(block->arguments); i++) {\n            AnyValue arg;\n            if (!evaluate_argument(exec, &block->arguments[i], &arg)) {\n                scrap_log(LOG_ERROR, \"[VM] From block id: \\\"%s\\\" (at block %p)\", block->blockdef->id, &block);\n                return false;\n            }\n            arg_stack_push_arg(exec, arg);\n        }\n    }\n\n    if (!execute_block(exec, block, exec->arg_stack_len - stack_begin, exec->arg_stack + stack_begin, block_return, control_state)) {\n        scrap_log(LOG_ERROR, \"[VM] Error from block id: \\\"%s\\\" (at block %p)\", block->blockdef->id, &block);\n        return false;\n    }\n\n    arg_stack_undo_args(exec, exec->arg_stack_len - stack_begin);\n\n    if (!block->parent && vector_size(exec->gc.root_temp_chunks) > last_temps) gc_flush(&exec->gc);\n\n    return true;\n}\n\n#define BLOCKDEF chain->blocks[i].blockdef\nbool exec_run_chain(Exec* exec, BlockChain* chain, int argc, AnyValue* argv, AnyValue* return_val) {\n    size_t base_len = exec->control_stack_len;\n    chain_stack_push(exec, (ChainStackData) {\n        .skip_block = false,\n        .layer = 0,\n        .running_ind = 0,\n        .custom_argc = argc,\n        .custom_argv = argv,\n        .is_returning = false,\n        .return_arg = (AnyValue) {0},\n    });\n\n    gc_root_begin(&exec->gc);\n    gc_root_save(&exec->gc);\n\n    exec->running_chain = chain;\n    AnyValue block_return;\n    for (size_t i = 0; i < vector_size(chain->blocks); i++) {\n        ChainStackData* chain_data = &exec->chain_stack[exec->chain_stack_len - 1];\n\n        if (chain_data->skip_block) {\n            int layer = chain_data->layer;\n            while (i < vector_size(chain->blocks)) {\n                if (BLOCKDEF->type == BLOCKTYPE_END || BLOCKDEF->type == BLOCKTYPE_CONTROLEND) {\n                    layer--;\n                    if (layer < chain_data->layer) break;\n                }\n                if (BLOCKDEF->type == BLOCKTYPE_CONTROL || BLOCKDEF->type == BLOCKTYPE_CONTROLEND) {\n                    layer++;\n                }\n                i++;\n            }\n            chain_data->skip_block = false;\n        }\n\n        thread_handle_stopping_state(exec->thread);\n\n        size_t block_ind = i;\n        chain_data->running_ind = i;\n        ControlState control_state = BLOCKDEF->type == BLOCKTYPE_CONTROL ? CONTROL_STATE_BEGIN : CONTROL_STATE_NORMAL;\n        if (chain_data->is_returning) break;\n\n        if (BLOCKDEF->type == BLOCKTYPE_END || BLOCKDEF->type == BLOCKTYPE_CONTROLEND) {\n            if (chain_data->layer == 0) continue;\n            variable_stack_pop_layer(exec);\n            chain_data->layer--;\n            control_stack_pop_data(block_ind, size_t);\n            control_stack_pop_data(block_return, AnyValue);\n            gc_root_end(&exec->gc);\n            control_state = CONTROL_STATE_END;\n        }\n        \n        if (!exec_block(exec, &chain->blocks[block_ind], &block_return, control_state, (AnyValue){0})) {\n            chain_stack_pop(exec);\n            return false;\n        }\n        exec->running_chain = chain;\n        if (chain_data->running_ind != i) i = chain_data->running_ind;\n\n        if (BLOCKDEF->type == BLOCKTYPE_CONTROLEND && block_ind != i) {\n            control_state = CONTROL_STATE_BEGIN;\n            if (!exec_block(exec, &chain->blocks[i], &block_return, control_state, block_return)) {\n                chain_stack_pop(exec);\n                return false;\n            }\n            if (chain_data->running_ind != i) i = chain_data->running_ind;\n        }\n\n        if (BLOCKDEF->type == BLOCKTYPE_CONTROL || BLOCKDEF->type == BLOCKTYPE_CONTROLEND) {\n            control_stack_push_data(block_return, AnyValue);\n            control_stack_push_data(i, size_t);\n            gc_root_begin(&exec->gc);\n            chain_data->layer++;\n        }\n    }\n    gc_root_restore(&exec->gc);\n    gc_root_end(&exec->gc);\n\n    *return_val = exec->chain_stack[exec->chain_stack_len - 1].return_arg;\n    while (exec->chain_stack[exec->chain_stack_len - 1].layer >= 0) {\n        variable_stack_pop_layer(exec);\n        exec->chain_stack[exec->chain_stack_len - 1].layer--;\n    }\n    exec->control_stack_len = base_len;\n    chain_stack_pop(exec);\n    return true;\n}\n#undef BLOCKDEF\n\nvoid exec_set_skip_block(Exec* exec) {\n    exec->chain_stack[exec->chain_stack_len - 1].skip_block = true;\n}\n\nVariable* variable_stack_push_var(Exec* exec, const char* name, AnyValue arg) {\n    if (exec->variable_stack_len >= VM_VARIABLE_STACK_SIZE) {\n        scrap_log(LOG_ERROR, \"[VM] Variable stack overflow\");\n        thread_exit(exec->thread, false);\n    }\n    if (*name == 0) return NULL;\n    Variable var;\n    var.name = name;\n    var.chunk_header.marked = 0;\n    var.chunk_header.data_type = DATA_TYPE_ANY;\n    var.value_ptr = &exec->variable_stack[exec->variable_stack_len].value;\n    var.value = arg;\n    var.chain_layer = exec->chain_stack_len - 1;\n    var.layer = exec->chain_stack[var.chain_layer].layer;\n    exec->variable_stack[exec->variable_stack_len++] = var;\n    return &exec->variable_stack[exec->variable_stack_len - 1];\n}\n\nvoid variable_stack_pop_layer(Exec* exec) {\n    size_t count = 0;\n    for (int i = exec->variable_stack_len - 1; i >= 0 &&\n                                               exec->variable_stack[i].layer == exec->chain_stack[exec->chain_stack_len - 1].layer &&\n                                               exec->variable_stack[i].chain_layer == exec->chain_stack_len - 1; i--) {\n        count++;\n    }\n    exec->variable_stack_len -= count;\n}\n\nvoid variable_stack_cleanup(Exec* exec) {\n    exec->variable_stack_len = 0;\n}\n\nVariable* variable_stack_get_variable(Exec* exec, const char* name) {\n    for (int i = exec->variable_stack_len - 1; i >= 0; i--) {\n        if (exec->variable_stack[i].chain_layer != exec->chain_stack_len - 1) break;\n        if (!strcmp(exec->variable_stack[i].name, name)) return &exec->variable_stack[i];\n    }\n    if (exec->chain_stack_len > 0) {\n        for (size_t i = 0; i < exec->variable_stack_len; i++) {\n            if (exec->variable_stack[i].layer != 0 || exec->variable_stack[i].chain_layer != 0) break;\n            if (!strcmp(exec->variable_stack[i].name, name)) return &exec->variable_stack[i];\n        }\n    }\n    return NULL;\n}\n\nvoid chain_stack_push(Exec* exec, ChainStackData data) {\n    if (exec->chain_stack_len >= VM_CHAIN_STACK_SIZE) {\n        scrap_log(LOG_ERROR, \"[VM] Chain stack overflow\");\n        thread_exit(exec->thread, false);\n    }\n    exec->chain_stack[exec->chain_stack_len++] = data;\n}\n\nvoid chain_stack_pop(Exec* exec) {\n    if (exec->chain_stack_len == 0) {\n        scrap_log(LOG_ERROR, \"[VM] Chain stack underflow\");\n        thread_exit(exec->thread, false);\n    }\n    exec->chain_stack_len--;\n}\n\nvoid arg_stack_push_arg(Exec* exec, AnyValue arg) {\n    if (exec->arg_stack_len >= VM_ARG_STACK_SIZE) {\n        scrap_log(LOG_ERROR, \"[VM] Arg stack overflow\");\n        thread_exit(exec->thread, false);\n    }\n    exec->arg_stack[exec->arg_stack_len++] = arg;\n}\n\nvoid arg_stack_undo_args(Exec* exec, size_t count) {\n    if (count > exec->arg_stack_len) {\n        scrap_log(LOG_ERROR, \"[VM] Arg stack underflow\");\n        thread_exit(exec->thread, false);\n    }\n    exec->arg_stack_len -= count;\n}\n"
  },
  {
    "path": "src/interpreter.h",
    "content": "// Scrap is a project that allows anyone to build software using simple, block based interface.\n//\n// Copyright (C) 2024-2026 Grisshink\n// \n// This software is provided 'as-is', without any express or implied\n// warranty.  In no event will the authors be held liable for any damages\n// arising from the use of this software.\n// \n// Permission is granted to anyone to use this software for any purpose,\n// including commercial applications, and to alter it and redistribute it\n// freely, subject to the following restrictions:\n// \n// 1. The origin of this software must not be misrepresented; you must not\n//    claim that you wrote the original software. If you use this software\n//    in a product, an acknowledgment in the product documentation would be\n//    appreciated but is not required.\n// 2. Altered source versions must be plainly marked as such, and must not be\n//    misrepresented as being the original software.\n// 3. This notice may not be removed or altered from any source distribution.\n\n#ifndef INTERPRETER_H\n#define INTERPRETER_H\n\n#include \"raylib.h\"\n#include \"ast.h\"\n#include \"std.h\"\n#include \"thread.h\"\n\n#define VM_ARG_STACK_SIZE 1024\n#define VM_CONTROL_STACK_SIZE 32768\n#define VM_VARIABLE_STACK_SIZE 1024\n#define VM_CHAIN_STACK_SIZE 1024\n\ntypedef struct Variable Variable;\ntypedef struct Exec Exec;\ntypedef struct ChainStackData ChainStackData;\n\ntypedef enum {\n    CONTROL_STATE_NORMAL = 0,\n    CONTROL_STATE_BEGIN,\n    CONTROL_STATE_END,\n} ControlState;\n\ntypedef bool (*BlockFunc)(Exec* exec, Block* block, int argc, AnyValue* argv, AnyValue* return_val, ControlState control_state);\n\nstruct Variable {\n    const char* name;\n    // This is a pretty hacky way to make gc think this area of memory is allocated with\n    // gc_malloc even though it is not. The data_type field in header should be set to\n    // DATA_TYPE_ANY so that gc could check the potential heap references inside the any\n    // value. This essentially allows interpreter to change variable type without\n    // invalidating gc root pointers.\n    AnyValue* value_ptr;\n    GcChunkData chunk_header;\n    AnyValue value;\n\n    size_t chain_layer;\n    int layer;\n};\n\nstruct ChainStackData {\n    bool skip_block;\n    int layer;\n    size_t running_ind;\n    int custom_argc;\n    AnyValue* custom_argv;\n    bool is_returning;\n    AnyValue return_arg;\n};\n\ntypedef struct {\n    Blockdef* blockdef;\n    int arg_ind;\n} DefineArgument;\n\ntypedef struct {\n    Blockdef* blockdef;\n    BlockChain* run_chain;\n    DefineArgument* args;\n} DefineFunction;\n\nstruct Exec {\n    BlockChain* code;\n\n    AnyValue arg_stack[VM_ARG_STACK_SIZE];\n    size_t arg_stack_len;\n\n    unsigned char control_stack[VM_CONTROL_STACK_SIZE];\n    size_t control_stack_len;\n\n    Variable variable_stack[VM_VARIABLE_STACK_SIZE];\n    size_t variable_stack_len;\n\n    ChainStackData chain_stack[VM_CHAIN_STACK_SIZE];\n    size_t chain_stack_len;\n\n    DefineFunction* defined_functions;\n\n    char current_error[MAX_ERROR_LEN];\n    Block* current_error_block;\n\n    Thread* thread;\n    BlockChain* running_chain;\n\n    Gc gc;\n};\n\n#define control_stack_push_data(data, type) do { \\\n    if (exec->control_stack_len + sizeof(type) > VM_CONTROL_STACK_SIZE) { \\\n        scrap_log(LOG_ERROR, \"[VM] Control stack overflow\"); \\\n        thread_exit(exec->thread, false); \\\n    } \\\n    *(type *)(exec->control_stack + exec->control_stack_len) = (data); \\\n    exec->control_stack_len += sizeof(type); \\\n} while (0)\n\n#define control_stack_pop_data(data, type) do { \\\n    if (sizeof(type) > exec->control_stack_len) { \\\n        scrap_log(LOG_ERROR, \"[VM] Control stack underflow\"); \\\n        thread_exit(exec->thread, false); \\\n    } \\\n    exec->control_stack_len -= sizeof(type); \\\n    data = *(type*)(exec->control_stack + exec->control_stack_len); \\\n} while (0)\n\n#define DATA_NOTHING (AnyValue) { \\\n    .type = DATA_TYPE_NOTHING, \\\n    .data = (AnyValueData) {0}, \\\n}\n\n#define DATA_INTEGER(val) (AnyValue) { \\\n    .type = DATA_TYPE_INTEGER, \\\n    .data = (AnyValueData) { .integer_val = (val) }, \\\n}\n\n#define DATA_FLOAT(val) (AnyValue) { \\\n    .type = DATA_TYPE_FLOAT, \\\n    .data = (AnyValueData) { .float_val = (val) }, \\\n}\n\n#define DATA_BOOL(val) (AnyValue) { \\\n    .type = DATA_TYPE_BOOL, \\\n    .data = (AnyValueData) { .integer_val = (val) }, \\\n}\n\n#define DATA_LITERAL(val) (AnyValue) { \\\n    .type = DATA_TYPE_LITERAL, \\\n    .data = (AnyValueData) { .literal_val = (val) }, \\\n}\n\n#define DATA_STRING(val) (AnyValue) { \\\n    .type = DATA_TYPE_STRING, \\\n    .data = (AnyValueData) { .str_val = (val) }, \\\n}\n\n#define DATA_LIST(val) (AnyValue) { \\\n    .type = DATA_TYPE_LIST, \\\n    .data = (AnyValueData) { .list_val = (val) }, \\\n}\n\n#define DATA_COLOR(val) (AnyValue) { \\\n    .type = DATA_TYPE_COLOR, \\\n    .data = (AnyValueData) { .color_val = (val) }, \\\n}\n\nExec exec_new(Thread* thread);\nbool exec_run(void* e);\nvoid exec_cleanup(void* e);\nvoid exec_free(Exec* exec);\nbool exec_run_chain(Exec* exec, BlockChain* chain, int argc, AnyValue* argv, AnyValue* return_val);\nvoid exec_set_skip_block(Exec* exec);\nvoid exec_set_error(Exec* exec, Block* block, const char* fmt, ...);\n\nbool evaluate_argument(Exec* exec, Argument* arg, AnyValue* return_val);\n\nVariable* variable_stack_push_var(Exec* exec, const char* name, AnyValue arg);\nVariable* variable_stack_get_variable(Exec* exec, const char* name);\n\n#endif // INTERPRETER_H\n"
  },
  {
    "path": "src/platform.c",
    "content": "// Scrap is a project that allows anyone to build software using simple, block based interface.\n//\n// Copyright (C) 2024-2026 Grisshink\n// \n// This software is provided 'as-is', without any express or implied\n// warranty.  In no event will the authors be held liable for any damages\n// arising from the use of this software.\n// \n// Permission is granted to anyone to use this software for any purpose,\n// including commercial applications, and to alter it and redistribute it\n// freely, subject to the following restrictions:\n// \n// 1. The origin of this software must not be misrepresented; you must not\n//    claim that you wrote the original software. If you use this software\n//    in a product, an acknowledgment in the product documentation would be\n//    appreciated but is not required.\n// 2. Altered source versions must be plainly marked as such, and must not be\n//    misrepresented as being the original software.\n// 3. This notice may not be removed or altered from any source distribution.\n\n#ifdef _WIN32\n#include <windows.h>\n#else\n#include <unistd.h>\n#include <sys/wait.h>\n#endif\n#include <stdio.h>\n#include <stdlib.h>\n#include <stdbool.h>\n#include <string.h>\n#include <errno.h>\n#include <libintl.h>\n\n#include \"term.h\"\n\nvoid scrap_set_env(const char* name, const char* value) {\n#ifdef _WIN32\n    char buf[256];\n    snprintf(buf, 256, \"%s=%s\", name, value);\n    putenv(buf);\n    SetEnvironmentVariableA(name, value);\n#else\n    setenv(name, value, false);\n#endif // _WIN32\n}\n\n#ifndef USE_INTERPRETER\n\n#ifndef _WIN32\nstatic size_t next_arg(char* cmd, size_t i, char** out_arg) {\n    *out_arg = NULL;\n\n    while (cmd[i] == ' ') i++;\n    if (cmd[i] == 0) return i;\n\n    if (cmd[i] == '\"') {\n        i++;\n        *out_arg = cmd + i;\n        while (cmd[i] != '\"' && cmd[i] != 0) i++;\n    } else {\n        *out_arg = cmd + i;\n        while (cmd[i] != ' ' && cmd[i] != 0) i++;\n    }\n\n    if (cmd[i] == 0) {\n        return i;\n    } else {\n        cmd[i] = 0;\n        return i + 1;\n    }\n}\n#endif\n\nbool spawn_process(char* command, char* error, size_t error_len) {\n#ifdef _WIN32\n    HANDLE read_pipe, write_pipe;\n    STARTUPINFO start_info = {0};\n    PROCESS_INFORMATION proc_info = {0};\n\n    SECURITY_ATTRIBUTES pipe_attrs = {0};\n    pipe_attrs.nLength = sizeof(pipe_attrs);\n    pipe_attrs.bInheritHandle = TRUE;\n\n    if (!CreatePipe(&read_pipe, &write_pipe, &pipe_attrs, 0)) {\n        snprintf(error, error_len, gettext(\"Failed to create a pipe. Error code: %ld\"), GetLastError());\n        return false;\n    }\n\n    start_info.cb = sizeof(start_info);\n    start_info.hStdError = write_pipe;\n    start_info.hStdOutput = write_pipe;\n    start_info.dwFlags = STARTF_USESTDHANDLES;\n\n    if(!CreateProcessA(\n        NULL, command,\n        NULL, NULL, // No security attributes\n        TRUE, // Allow to inherit handles\n        CREATE_NO_WINDOW, // Don't spawn cmd for a process\n        NULL, NULL, // Just give me a process\n        &start_info, // Give STARTUPINFO\n        &proc_info) // Get PROCESS_INFORMATION\n    ) {\n        long last_error = GetLastError();\n        snprintf(error, error_len, gettext(\"Failed to create a process. Error code: %ld\"), last_error);\n        if (last_error == 2) { // File not found\n            size_t i = 0;\n            while (command[i] != 0 && command[i] != ' ') i++;\n            command[i] = 0;\n            term_print_str(command);\n            term_print_str(gettext(\" is not installed on your system or not installed correctly. Please install it and add it to your PATH environment variable to be able to build executables\"));\n        }\n        CloseHandle(write_pipe);\n        CloseHandle(read_pipe);\n        return false;\n    }\n\n    CloseHandle(write_pipe);\n\n    unsigned long size = 0;\n    char buf[1024];\n\n    for (;;) {\n        if (!ReadFile(read_pipe, buf, 1024 - 1 /* Save space for null terminator */, &size, NULL)) {\n            long last_error = GetLastError();\n            if (last_error == ERROR_BROKEN_PIPE) break;\n\n            snprintf(error, error_len, gettext(\"Failed to read from pipe. Error code: %ld\"), last_error);\n            CloseHandle(proc_info.hProcess);\n            CloseHandle(proc_info.hThread);\n            return false;\n        }\n        buf[size] = 0;\n        term_print_str(buf);\n    }\n\n    WaitForSingleObject(proc_info.hProcess, INFINITE);\n\n    unsigned long exit_code;\n    if (!GetExitCodeProcess(proc_info.hProcess, &exit_code)) {\n        snprintf(error, error_len, gettext(\"Failed to get exit code. Error code: %ld\"), GetLastError());\n        CloseHandle(proc_info.hProcess);\n        CloseHandle(proc_info.hThread);\n        CloseHandle(read_pipe);\n        return false;\n    }\n\n    if (exit_code) {\n        snprintf(error, error_len, gettext(\"Linker exited with exit code: %ld\"), exit_code);\n        CloseHandle(proc_info.hProcess);\n        CloseHandle(proc_info.hThread);\n        CloseHandle(read_pipe);\n        return false;\n    }\n\n    CloseHandle(proc_info.hProcess);\n    CloseHandle(proc_info.hThread);\n    CloseHandle(read_pipe);\n#else\n    int pipefd[2];\n\n    if (pipe(pipefd) == -1) {\n        snprintf(error, error_len, gettext(\"Failed to create a pipe: %s\"), strerror(errno));\n        return false;\n    }\n\n    pid_t pid = fork();\n    if (pid == -1) {\n        snprintf(error, error_len, gettext(\"Failed to fork a process: %s\"), strerror(errno));\n        return false;\n    }\n\n    if (pid == 0) {\n        // We are newborn\n\n        // Close duplicate read end\n        if (close(pipefd[0]) == -1) {\n            perror(\"close\");\n            exit(1);\n        }\n        \n        // Replace stdout and stderr with pipe\n        if (dup2(pipefd[1], 1) == -1) {\n            perror(\"dup2\");\n            exit(1);\n        }\n\n        if (dup2(pipefd[1], 2) == -1) {\n            perror(\"dup2\");\n            exit(1);\n        }\n\n        // Parse command line\n        char* name;\n        char* args[256];\n        size_t arg_len = 1;\n\n        size_t i = 0;\n        i = next_arg(command, i, &name);\n        args[0] = name;\n        args[255] = NULL;\n\n        while (args[arg_len - 1] && arg_len < 255) i = next_arg(command, i, &args[arg_len++]);\n\n        printf(\"Name: %s\\n\", name);\n\n        printf(\"Args: \");\n        for (char** arg = args; *arg; arg++) {\n            printf(\"\\\"%s\\\", \", *arg);\n        }\n        printf(\"\\n\");\n\n        // Replace da child with linker\n        if (execvp(name, args) == -1) {\n            perror(\"execvp\");\n            exit(1);\n        }\n    } else {\n        // We are parent\n\n        // Close duplicate write end\n        if (close(pipefd[1]) == -1) {\n            perror(\"close\");\n            exit(1);\n        }\n\n        ssize_t size = 0;\n        char buf[1024];\n        while ((size = read(pipefd[0], buf, 1024 - 1 /* Save space for null terminator */))) {\n            if (size == -1) {\n                perror(\"read\");\n                exit(1);\n            }\n            buf[size] = 0;   \n            term_print_str(buf);\n        }\n\n        if (close(pipefd[0]) == -1) {\n            perror(\"close\");\n            exit(1);\n        }\n\n        // Wait for child to terminate\n        int status;\n        waitpid(pid, &status, 0);\n        \n        if (WIFEXITED(status)) {\n            int exit_code = WEXITSTATUS(status);\n            if (exit_code == 0) {\n                return true;\n            } else {\n                snprintf(error, error_len, gettext(\"Linker exited with exit code: %d\"), exit_code);\n                return false;\n            }\n        } else if (WIFSIGNALED(status)) {\n            snprintf(error, error_len, gettext(\"Linker signaled with signal number: %d\"), WTERMSIG(status));\n            return false;\n        } else {\n            snprintf(error, error_len, gettext(\"Received unknown child status :/\"));\n            return false;\n        }\n    }\n#endif\n\n    return true;\n}\n#endif // USE_INTERPRETER\n"
  },
  {
    "path": "src/render.c",
    "content": "// Scrap is a project that allows anyone to build software using simple, block based interface.\n//\n// Copyright (C) 2024-2026 Grisshink\n// \n// This software is provided 'as-is', without any express or implied\n// warranty.  In no event will the authors be held liable for any damages\n// arising from the use of this software.\n// \n// Permission is granted to anyone to use this software for any purpose,\n// including commercial applications, and to alter it and redistribute it\n// freely, subject to the following restrictions:\n// \n// 1. The origin of this software must not be misrepresented; you must not\n//    claim that you wrote the original software. If you use this software\n//    in a product, an acknowledgment in the product documentation would be\n//    appreciated but is not required.\n// 2. Altered source versions must be plainly marked as such, and must not be\n//    misrepresented as being the original software.\n// 3. This notice may not be removed or altered from any source distribution.\n\n#include \"scrap.h\"\n#include \"vec.h\"\n#include \"term.h\"\n\n#define NANOSVG_IMPLEMENTATION\n#include \"../external/nanosvg.h\"\n\n#define NANOSVGRAST_IMPLEMENTATION\n#include \"../external/nanosvgrast.h\"\n\n#include <assert.h>\n#include <math.h>\n#include <stdarg.h>\n#include <string.h>\n#include <libintl.h>\n#include <stdio.h>\n\ntypedef enum {\n    BORDER_NORMAL = 0,\n    BORDER_CONTROL,\n    BORDER_CONTROL_BODY,\n    BORDER_END,\n    BORDER_CONTROL_END,\n    BORDER_NOTCHED,\n} BorderType;\n\ntypedef enum {\n    RECT_NORMAL = 0,\n    RECT_NOTCHED,\n    RECT_TERMINAL, // Terminal rendering is handled specially as it needs to synchronize with its buffer\n} RectType;\n\ntypedef enum {\n    IMAGE_NORMAL = 0,\n    IMAGE_STRETCHED,\n} ImageType;\n\nstatic void draw_code(void);\nstatic GuiElement* draw_blockchain(BlockChain* chain, bool ghost, bool show_previews, bool editable_arguments);\nstatic void argument_on_hover(GuiElement* el);\nstatic void argument_on_render(GuiElement* el);\n\nbool rl_vec_equal(Color lhs, Color rhs) {\n    return lhs.r == rhs.r && lhs.g == rhs.g && lhs.b == rhs.b && lhs.a == rhs.a;\n}\n\nvoid actionbar_show(const char* text) {\n    scrap_log(LOG_INFO, \"[ACTION] %s\", text);\n    strncpy(editor.actionbar.text, text, sizeof(editor.actionbar.text) - 1);\n    editor.actionbar.show_time = 3.0;\n}\n\nconst char* sgettext(const char* msgid) {\n    const char* msgval = gettext(msgid);\n    if (msgval == msgid) msgval = strrchr(msgid, '|') + 1;\n    if (msgval == (void*)1) msgval = msgid;\n    return msgval;\n}\n\nstatic void draw_dots(void) {\n    int win_width = GetScreenWidth();\n    int win_height = GetScreenHeight();\n\n    for (int y = MOD(-(int)editor.camera_pos.y, config.ui_size * 2); y < win_height; y += config.ui_size * 2) {\n        for (int x = MOD(-(int)editor.camera_pos.x, config.ui_size * 2); x < win_width; x += config.ui_size * 2) {\n            DrawRectangle(x, y, 2, 2, (Color) { 0x40, 0x40, 0x40, 0xff });\n        }\n    }\n\n    if (ui.shader_time == 1.0) return;\n    if (!IsShaderValid(assets.line_shader)) return;\n\n    BeginShaderMode(assets.line_shader);\n    for (int y = MOD(-(int)editor.camera_pos.y, config.ui_size * 2); y < win_height; y += config.ui_size * 2) {\n        DrawRectangle(0, y, win_width, 2, (Color) { 0x40, 0x40, 0x40, 0xff });\n    }\n    for (int x = MOD(-(int)editor.camera_pos.x, config.ui_size * 2); x < win_width; x += config.ui_size * 2) {\n        DrawRectangle(x, 0, 2, win_height, (Color) { 0x40, 0x40, 0x40, 0xff });\n    }\n    EndShaderMode();\n}\n\nstatic void draw_term(int x, int y) {\n    mutex_lock(&term.lock);\n\n    if (term.char_w == 0 || term.char_h == 0) goto unlock_term;\n    if (!term.buffer) goto unlock_term;\n\n    Rectangle final_pos = { x, y, term.size.x, term.size.y };\n    DrawRectangleRec(final_pos, BLACK);\n\n    if (IsShaderValid(assets.line_shader)) {\n        BeginShaderMode(assets.line_shader);\n        DrawRectangleLinesEx(final_pos, 2.0, (Color) { 0x60, 0x60, 0x60, 0xff });\n        EndShaderMode();\n    }\n\n    Vector2 pos = (Vector2) { final_pos.x, final_pos.y };\n    for (int y = 0; y < term.char_h; y++) {\n        pos.x = final_pos.x;\n        for (int x = 0; x < term.char_w; x++) {\n            TerminalChar buffer_char = term.buffer[x + y*term.char_w];\n            if (!rl_vec_equal(CONVERT_COLOR(buffer_char.bg_color, Color), BLACK)) {\n                DrawRectangle(pos.x, pos.y, term.char_size.x, term.font_size, CONVERT_COLOR(buffer_char.bg_color, Color));\n            }\n            pos.x += term.char_size.x;\n        }\n        pos.y += term.font_size;\n    }\n\n    pos = (Vector2) { final_pos.x, final_pos.y };\n    for (int y = 0; y < term.char_h; y++) {\n        pos.x = final_pos.x;\n        for (int x = 0; x < term.char_w; x++) {\n            TerminalChar buffer_char = term.buffer[x + y*term.char_w];\n            if (!rl_vec_equal(CONVERT_COLOR(buffer_char.fg_color, Color), CONVERT_COLOR(buffer_char.bg_color, Color))) {\n                DrawTextEx(assets.fonts.font_mono, buffer_char.ch, pos, term.font_size, 0.0, CONVERT_COLOR(buffer_char.fg_color, Color));\n            }\n            pos.x += term.char_size.x;\n        }\n        pos.y += term.font_size;\n    }\n    if (fmod(GetTime(), 1.0) <= 0.5) {\n        Vector2 cursor_pos = (Vector2) {\n            final_pos.x + (term.cursor_pos % term.char_w) * term.char_size.x,\n            final_pos.y + (term.cursor_pos / term.char_w) * term.font_size,\n        };\n        DrawRectangle(cursor_pos.x, cursor_pos.y, BLOCK_OUTLINE_SIZE, term.font_size, CONVERT_COLOR(term.cursor_fg_color, Color));\n    }\n\nunlock_term:\n    mutex_unlock(&term.lock);\n}\n\nvoid prerender_font_shadow(Font* font) {\n    SetTextureFilter(font->texture, TEXTURE_FILTER_POINT);\n    Image font_img = LoadImageFromTexture(font->texture);\n    Image render_img = ImageCopy(font_img);\n    ImageClearBackground(&render_img, BLANK);\n    ImageDraw(\n        &render_img,\n        font_img,\n        (Rectangle) { 0, 0, font_img.width, font_img.height },\n        (Rectangle) { SHADOW_DISTANCE, SHADOW_DISTANCE, font_img.width, font_img.height },\n        (Color) { 0x00, 0x00, 0x00, 0x88 }\n    );\n    ImageDraw(\n        &render_img,\n        font_img,\n        (Rectangle) { 0, 0, font_img.width, font_img.height },\n        (Rectangle) { 0, 0, font_img.width, font_img.height },\n        WHITE\n    );\n    UnloadTexture(font->texture);\n    font->texture = LoadTextureFromImage(render_img);\n    SetTextureFilter(font->texture, TEXTURE_FILTER_BILINEAR);\n\n    UnloadImage(font_img);\n    UnloadImage(render_img);\n}\n\nstatic void blockdef_on_hover(GuiElement* el) {\n    if (ui.hover.is_panel_edit_mode) return;\n    if (gui_window_is_shown()) return;\n    ui.hover.editor.part = EDITOR_BLOCKDEF;\n    ui.hover.editor.blockdef = el->custom_data;\n}\n\nstatic void blockdef_input_on_hover(GuiElement* el) {\n    if (ui.hover.is_panel_edit_mode) return;\n    if (gui_window_is_shown()) return;\n    ui.hover.editor.blockchain = ui.hover.editor.prev_blockchain;\n    if (el->draw_type != DRAWTYPE_UNKNOWN) return;\n    el->draw_type = DRAWTYPE_BORDER;\n    el->color = (GuiColor) { 0xa0, 0xa0, 0xa0, 0xff };\n    el->data.border_width = BLOCK_OUTLINE_SIZE;\n    el->draw_subtype = GUI_SUBTYPE_DEFAULT;\n}\n\nstatic void editor_del_button_on_hover(GuiElement* el) {\n    if (ui.hover.is_panel_edit_mode) return;\n    if (gui_window_is_shown()) return;\n    if (ui.hover.button.handler) return;\n    el->draw_type = DRAWTYPE_RECT;\n    el->draw_subtype = GUI_SUBTYPE_DEFAULT;\n    el->color = (GuiColor) { 0xff, 0xff, 0xff, 0x80 };\n    ui.hover.editor.blockdef_input = (size_t)el->custom_data;\n    ui.hover.button.handler = handle_editor_del_arg_button;\n}\n\nstatic void editor_button_on_hover(GuiElement* el) {\n    if (ui.hover.is_panel_edit_mode) return;\n    if (gui_window_is_shown()) return;\n    if (ui.hover.button.handler) return;\n    el->draw_type = DRAWTYPE_RECT;\n    el->draw_subtype = GUI_SUBTYPE_DEFAULT;\n    el->color = (GuiColor) { 0xff, 0xff, 0xff, 0x80 };\n    ui.hover.button.handler = el->custom_data;\n}\n\nstatic void editor_color_on_hover(GuiElement* el) {\n    if (ui.hover.is_panel_edit_mode) return;\n    if (gui_window_is_shown()) return;\n    if (ui.hover.button.handler) return;\n\n    el->draw_type = DRAWTYPE_BORDER;\n    el->draw_subtype = GUI_SUBTYPE_DEFAULT;\n    el->color = (GuiColor) { 0xa0, 0xa0, 0xa0, 0xff };\n    el->data.border_width = BLOCK_OUTLINE_SIZE;\n    ui.hover.button.handler = handle_editor_color_button;\n}\n\nstatic void draw_editor_button(Texture2D* texture, ButtonClickHandler handler) {\n    gui_element_begin(gui);\n        gui_set_rect(gui, (GuiColor) { 0xff, 0xff, 0xff, 0x40 });\n        gui_on_hover(gui, editor_button_on_hover);\n        gui_set_custom_data(gui, handler);\n\n        gui_image(gui, texture, BLOCK_IMAGE_SIZE, GUI_WHITE);\n    gui_element_end(gui);\n}\n\nvoid input_on_hover(GuiElement* el) {\n    if (ui.hover.button.handler) return;\n    if (ui.hover.is_panel_edit_mode) return;\n\n    ui.hover.input_info = *(InputHoverInfo*)gui_get_state(el);\n    ui.hover.input_info.rel_pos = (Vector2) { \n        gui->mouse_x - el->abs_x - ui.hover.input_info.rel_pos.x, \n        gui->mouse_y - el->abs_y - ui.hover.input_info.rel_pos.y,\n    };\n}\n\nstatic const int input_cursor_scrolloff = 5;\n\nvoid input_selection_on_render(GuiElement* el) {\n    if (!el->parent) return;\n    if (!el->parent->scroll_value) return;\n\n    int cursor_pos = ui.hover.select_input_cursor > ui.hover.select_input_mark ? el->x + el->w : el->x;\n\n    int right_off_x = cursor_pos + input_cursor_scrolloff - el->parent->w;\n    int left_off_x  = -cursor_pos + input_cursor_scrolloff;\n\n    if (left_off_x > 0) {\n        *el->parent->scroll_value += left_off_x;\n        ui.render_surface_redraw_next = true;\n    } else if (right_off_x > 0) {\n        *el->parent->scroll_value -= right_off_x;\n        ui.render_surface_redraw_next = true;\n    }\n}\n\nvoid input_cursor_on_render(GuiElement* el) {\n    if (!el->parent) return;\n    if (!el->parent->scroll_value) return;\n\n    int right_off_x = el->x + el->w + input_cursor_scrolloff - el->parent->w;\n    int left_off_x  = -el->x + input_cursor_scrolloff;\n\n    if (left_off_x > 0) {\n        *el->parent->scroll_value += left_off_x;\n        ui.render_surface_redraw_next = true;\n    } else if (right_off_x > 0) {\n        *el->parent->scroll_value -= right_off_x;\n        ui.render_surface_redraw_next = true;\n    }\n}\n\nvoid draw_input_text(Font* font, char** input, const char* hint, unsigned short font_size, GuiColor font_color) {\n    if (ui.hover.select_input == input) {\n        if (ui.hover.select_input_cursor == ui.hover.select_input_mark) ui.hover.select_input_mark = -1;\n\n        if (ui.hover.select_input_mark == -1) {\n            gui_text_slice(gui, font, *input, ui.hover.select_input_cursor, font_size, font_color);\n            gui_element_begin(gui);\n                gui_set_rect(gui, font_color);\n                gui_set_min_size(gui, BLOCK_OUTLINE_SIZE, BLOCK_TEXT_SIZE);\n                gui_on_render(gui, input_cursor_on_render);\n            gui_element_end(gui);\n\n            gui_text(gui, font, *input + ui.hover.select_input_cursor, font_size, font_color);\n        } else {\n            int select_start = MIN(ui.hover.select_input_cursor, ui.hover.select_input_mark),\n                select_end   = MAX(ui.hover.select_input_cursor, ui.hover.select_input_mark);\n            gui_text_slice(gui, font, *input, select_start, font_size, font_color);\n\n            gui_element_begin(gui);\n                gui_set_rect(gui, (GuiColor) TEXT_SELECTION_COLOR);\n                gui_text_slice(gui, font, *input + select_start, select_end - select_start, font_size, font_color);\n                gui_on_render(gui, input_selection_on_render);\n            gui_element_end(gui);\n\n            gui_text(gui, font, *input + select_end, font_size, font_color);\n        }\n    } else {\n        if (**input == 0) {\n            gui_text(gui, font, hint, font_size, (GuiColor) { font_color.r, font_color.g, font_color.b, font_color.a * 0.3 });\n        } else {\n            gui_text(gui, font, *input, font_size, font_color);\n        }\n    }\n}\n\nstatic void argument_input_on_hover(GuiElement* el) {\n    if (el->custom_data) {\n        argument_on_hover(el);\n    } else {\n        blockdef_input_on_hover(el);\n    }\n    input_on_hover(el);\n}\n\nstatic void draw_argument_input(Argument* arg, char** input, const char* hint, bool can_hover, bool editable, GuiColor font_color, GuiColor bg_color) {\n    gui_element_begin(gui);\n        gui_set_rect(gui, bg_color);\n\n        gui_element_begin(gui);\n            if (editable) {\n                if ((arg && ui.hover.editor.select_argument == arg) || ui.hover.select_input == input) {\n                    gui_set_border(gui, (GuiColor) { 0x30, 0x30, 0x30, 0xff }, BLOCK_OUTLINE_SIZE);\n                    if (arg) gui_on_render(gui, argument_on_render);\n                }\n                InputHoverInfo info = (InputHoverInfo) {\n                    .input = input,\n                    .rel_pos = (Vector2) { BLOCK_STRING_PADDING / 2, 0 },\n                    .font = &assets.fonts.font_cond_shadow,\n                    .font_size = BLOCK_TEXT_SIZE,\n                };\n                gui_set_state(gui, &info, sizeof(info));\n                gui_set_custom_data(gui, arg);\n                if (can_hover) gui_on_hover(gui, argument_input_on_hover);\n            }\n\n            gui_set_direction(gui, DIRECTION_HORIZONTAL);\n            gui_set_align(gui, ALIGN_CENTER, ALIGN_CENTER);\n            gui_set_padding(gui, BLOCK_STRING_PADDING / 2, 0);\n            gui_set_min_size(gui, config.ui_size - BLOCK_OUTLINE_SIZE * 4, config.ui_size - BLOCK_OUTLINE_SIZE * 4);\n\n            draw_input_text(&assets.fonts.font_cond, input, hint, BLOCK_TEXT_SIZE, font_color);\n        gui_element_end(gui);\n    gui_element_end(gui);\n}\n\nstatic void draw_blockdef(Blockdef* blockdef, bool editing) {\n    bool collision = ui.hover.editor.prev_blockdef == blockdef;\n    Color color = CONVERT_COLOR(blockdef->color, Color);\n    Color block_color = ColorBrightness(color, collision ? 0.3 : 0.0);\n    Color dropdown_color = ColorBrightness(color, collision ? 0.0 : -0.3);\n    Color outline_color = ColorBrightness(color, collision ? 0.5 : -0.2);\n\n    gui_element_begin(gui);\n        gui_set_direction(gui, DIRECTION_HORIZONTAL);\n        gui_set_rect(gui, CONVERT_COLOR(block_color, GuiColor));\n        gui_set_custom_data(gui, blockdef);\n        gui_on_hover(gui, blockdef_on_hover);\n\n    gui_element_begin(gui);\n        gui_set_direction(gui, DIRECTION_HORIZONTAL);\n        gui_set_border(gui, CONVERT_COLOR(outline_color, GuiColor), BLOCK_OUTLINE_SIZE);\n        gui_set_align(gui, ALIGN_LEFT, ALIGN_CENTER);\n        gui_set_min_size(gui, 0, config.ui_size);\n        gui_set_padding(gui, BLOCK_OUTLINE_SIZE * 2, BLOCK_OUTLINE_SIZE * 2);\n        gui_set_gap(gui, BLOCK_PADDING);\n\n    for (size_t i = 0; i < vector_size(blockdef->inputs); i++) {\n        Input* input = &blockdef->inputs[i];\n\n        if (ui.hover.editor.edit_blockdef == blockdef) {\n            gui_element_begin(gui);\n                gui_set_direction(gui, DIRECTION_HORIZONTAL);\n                gui_set_rect(gui, CONVERT_COLOR(dropdown_color, GuiColor));\n                gui_set_align(gui, ALIGN_LEFT, ALIGN_CENTER);\n                gui_set_padding(gui, BLOCK_PADDING, BLOCK_PADDING);\n                gui_set_gap(gui, BLOCK_PADDING);\n        }\n\n        switch (input->type) {\n        case INPUT_TEXT_DISPLAY:\n            if (editing) {\n                draw_argument_input(NULL, &input->data.text, \"\", true, true, GUI_BLACK, GUI_WHITE);\n            } else {\n                gui_text(gui, &assets.fonts.font_cond_shadow, input->data.text, BLOCK_TEXT_SIZE, GUI_WHITE);\n            }\n            break;\n        case INPUT_IMAGE_DISPLAY:\n            gui_image(gui, input->data.image.image_ptr, BLOCK_IMAGE_SIZE, GUI_WHITE);\n            break;\n        case INPUT_ARGUMENT:\n            input->data.arg.blockdef->color = blockdef->color;\n            draw_blockdef(input->data.arg.blockdef, editing);\n            break;\n        default:\n            gui_text(gui, &assets.fonts.font_cond_shadow, \"NODEF\", BLOCK_TEXT_SIZE, GUI_WHITE);\n            break;\n        }\n\n        if (ui.hover.editor.edit_blockdef == blockdef) {\n                gui_element_begin(gui);\n                    gui_set_rect(gui, (GuiColor) { 0xff, 0xff, 0xff, 0x40 });\n                    gui_on_hover(gui, editor_del_button_on_hover);\n                    gui_set_custom_data(gui, (void*)i);\n\n                    gui_image(gui, &assets.textures.button_del_arg, BLOCK_IMAGE_SIZE, GUI_WHITE);\n                gui_element_end(gui);\n            gui_element_end(gui);\n        }\n    }\n\n    gui_element_end(gui);\n    gui_element_end(gui);\n}\n\nstatic void block_on_hover(GuiElement* el) {\n    if (ui.hover.button.handler) return;\n    if (ui.hover.is_panel_edit_mode) return;\n    if (gui_window_is_shown()) return;\n    ui.hover.editor.block = el->custom_data;\n    ui.hover.editor.blockchain = ui.hover.editor.prev_blockchain;\n    if (!ui.hover.editor.block->parent) ui.hover.editor.parent_argument = NULL;\n}\n\nstatic void argument_on_render(GuiElement* el) {\n    ui.hover.editor.select_block_pos = (Vector2) { el->abs_x, el->abs_y };\n}\n\nstatic void block_on_render(GuiElement* el) {\n    ui.hover.editor.select_block_pos = (Vector2) { el->abs_x, el->abs_y };\n    ui.hover.editor.select_valid = true;\n}\n\nstatic void block_argument_on_hover(GuiElement* el) {\n    if (ui.hover.button.handler) return;\n    if (ui.hover.is_panel_edit_mode) return;\n    ui.hover.editor.parent_argument = el->custom_data;\n    ui.hover.editor.blockchain = ui.hover.editor.prev_blockchain;\n}\n\nstatic void argument_on_hover(GuiElement* el) {\n    if (ui.hover.button.handler) return;\n    if (ui.hover.is_panel_edit_mode) return;\n    if (gui_window_is_shown()) return;\n    ui.hover.editor.argument = el->custom_data;\n    ui.hover.editor.blockchain = ui.hover.editor.prev_blockchain;\n    if (el->draw_type != DRAWTYPE_UNKNOWN) return;\n    el->draw_type = DRAWTYPE_BORDER;\n    el->draw_subtype = GUI_SUBTYPE_DEFAULT;\n    el->color = (GuiColor) { 0xa0, 0xa0, 0xa0, 0xff };\n    el->data.border_width = BLOCK_OUTLINE_SIZE;\n}\n\nstatic void draw_block(Block* block, bool highlight, bool can_hover, bool ghost, bool editable) {\n    bool collision = ui.hover.editor.prev_block == block || highlight;\n    Color color = CONVERT_COLOR(block->blockdef->color, Color);\n    if (!block->blockdef->func) color = (Color) UNIMPLEMENTED_BLOCK_COLOR;\n    if (!thread_is_running(&vm.thread) && block == vm.compile_error_block) {\n        double animation = fmod(-GetTime(), 1.0) * 0.5 + 1.0;\n        color = (Color) { 0xff * animation, 0x20 * animation, 0x20 * animation, 0xff };\n    }\n    if (ghost) color.a = BLOCK_GHOST_OPACITY;\n\n    Color block_color = collision ? ColorBrightness(color, 0.3) : color;\n    Color dropdown_color = collision ? color : ColorBrightness(color, -0.3);\n    Color outline_color;\n    if (ui.hover.editor.select_block == block) {\n        outline_color = ColorBrightness(color, 0.7);\n    } else {\n        outline_color = ColorBrightness(color, collision ? 0.5 : -0.2);\n    }\n\n    gui_element_begin(gui);\n        gui_set_direction(gui, DIRECTION_HORIZONTAL);\n        gui_set_rect(gui, CONVERT_COLOR(block_color, GuiColor));\n        gui_set_custom_data(gui, block);\n        if (block->blockdef->type == BLOCKTYPE_HAT) gui_set_draw_subtype(gui, RECT_NOTCHED);\n        if (can_hover) gui_on_hover(gui, block_on_hover);\n        if (ui.hover.editor.select_block == block) gui_on_render(gui, block_on_render);\n\n    gui_element_begin(gui);\n        gui_set_direction(gui, DIRECTION_VERTICAL);\n        gui_set_border(gui, CONVERT_COLOR(outline_color, GuiColor), BLOCK_OUTLINE_SIZE);\n        gui_set_align(gui, ALIGN_LEFT, ALIGN_CENTER);\n        gui_set_padding(gui, BLOCK_OUTLINE_SIZE * 2, BLOCK_OUTLINE_SIZE * 2);\n        gui_set_min_size(gui, 0, config.ui_size);\n        gui_set_gap(gui, BLOCK_PADDING);\n        if (block->blockdef->type == BLOCKTYPE_CONTROL) {\n            gui_set_draw_subtype(gui, BORDER_CONTROL);\n        } else if (block->blockdef->type == BLOCKTYPE_CONTROLEND) {\n            gui_set_draw_subtype(gui, BORDER_CONTROL_END);\n        } else if (block->blockdef->type == BLOCKTYPE_HAT) {\n            gui_set_draw_subtype(gui, BORDER_NOTCHED);\n        }\n\n    gui_element_begin(gui);\n        gui_set_direction(gui, DIRECTION_HORIZONTAL);\n        gui_set_gap(gui, BLOCK_PADDING);\n        gui_set_align(gui, ALIGN_LEFT, ALIGN_CENTER);\n\n    size_t arg_id = 0;\n    Input* inputs = block->blockdef->inputs;\n    size_t inputs_size = vector_size(inputs);\n\n    Argument default_argument = {\n        .input_id = 0,\n        .data = (ArgumentData) {\n            .text = \"\",\n        },\n    };\n\n    GuiElement* block_element;\n\n    for (size_t i = 0; i < inputs_size; i++) {\n        Input* input = &inputs[i];\n        Argument* arg = block->arguments ? &block->arguments[arg_id] : NULL;\n\n        switch (input->type) {\n        case INPUT_TEXT_DISPLAY:\n            gui_text(gui, &assets.fonts.font_cond_shadow, input->data.text, BLOCK_TEXT_SIZE, (GuiColor) { 0xff, 0xff, 0xff, ghost ? BLOCK_GHOST_OPACITY : 0xff });\n            break;\n        case INPUT_IMAGE_DISPLAY: ;\n            GuiColor img_color = CONVERT_COLOR(input->data.image.image_color, GuiColor);\n            if (ghost) img_color.a = BLOCK_GHOST_OPACITY;\n            gui_image(gui, input->data.image.image_ptr, BLOCK_IMAGE_SIZE, img_color);\n            break;\n        case INPUT_ARGUMENT:\n            if (!arg) {\n                arg = &default_argument;\n                arg->type = ARGUMENT_TEXT;\n            }\n\n            switch (arg->type) {\n            case ARGUMENT_CONST_STRING:\n                draw_argument_input(\n                    arg,\n                    &arg->data.text,\n                    input->data.arg.hint_text,\n                    can_hover,\n                    editable,\n                    (GuiColor) { 0xff, 0xff, 0xff, ghost ? BLOCK_GHOST_OPACITY : 0xff },\n                    CONVERT_COLOR(dropdown_color, GuiColor)\n                );\n                break;\n            case ARGUMENT_TEXT:\n                draw_argument_input(\n                    arg,\n                    &arg->data.text,\n                    input->data.arg.hint_text,\n                    can_hover,\n                    editable,\n                    (GuiColor) { 0x00, 0x00, 0x00, ghost ? BLOCK_GHOST_OPACITY : 0xff },\n                    (GuiColor) { 0xff, 0xff, 0xff, ghost ? BLOCK_GHOST_OPACITY : BLOCK_ARG_OPACITY }\n                );\n                break;\n            case ARGUMENT_BLOCK:\n                block_element = gui_element_begin(gui);\n                    if (can_hover) gui_on_hover(gui, block_argument_on_hover);\n                    gui_set_custom_data(gui, arg);\n\n                    draw_block(&arg->data.block, highlight, can_hover, ghost, editable);\n                gui_element_end(gui);\n                if (block_element->w > 500 && i + 1 < inputs_size) {\n                    gui_element_end(gui);\n                    gui_element_begin(gui);\n                        gui_set_direction(gui, DIRECTION_HORIZONTAL);\n                        gui_set_gap(gui, BLOCK_PADDING);\n                        gui_set_align(gui, ALIGN_LEFT, ALIGN_CENTER);\n                }\n                break;\n            default:\n                gui_text(gui, &assets.fonts.font_cond_shadow, \"NODEF\", BLOCK_TEXT_SIZE, (GuiColor) { 0xff, 0x00, 0x00, 0xff });\n                break;\n            }\n            arg_id++;\n            break;\n        case INPUT_DROPDOWN:\n            if (!arg) {\n                arg = &default_argument;\n                arg->type = ARGUMENT_CONST_STRING;\n            }\n\n            assert(arg->type == ARGUMENT_CONST_STRING);\n            gui_element_begin(gui);\n                gui_set_rect(gui, CONVERT_COLOR(dropdown_color, GuiColor));\n\n                if (ui.dropdown.ref_object == arg) {\n                    ui.dropdown.element = gui_get_element(gui);\n                }\n\n                gui_element_begin(gui);\n                    gui_set_min_size(gui, 0, config.ui_size - BLOCK_OUTLINE_SIZE * 4);\n                    gui_set_align(gui, ALIGN_LEFT, ALIGN_CENTER);\n                    gui_set_padding(gui, BLOCK_STRING_PADDING / 2, 0);\n                    gui_set_direction(gui, DIRECTION_HORIZONTAL);\n                    if (editable) {\n                        if (ui.hover.editor.select_argument == arg) gui_set_border(gui, (GuiColor) { 0x30, 0x30, 0x30, 0xff }, BLOCK_OUTLINE_SIZE);\n                        if (can_hover) gui_on_hover(gui, argument_on_hover);\n                        gui_set_custom_data(gui, arg);\n                    }\n\n                    gui_text(gui, &assets.fonts.font_cond_shadow, arg->data.text, BLOCK_TEXT_SIZE, GUI_WHITE);\n                    gui_image(gui, &assets.textures.dropdown, BLOCK_IMAGE_SIZE, GUI_WHITE);\n\n                gui_element_end(gui);\n            gui_element_end(gui);\n            arg_id++;\n            break;\n        case INPUT_BLOCKDEF_EDITOR:\n            if (!arg) {\n                arg_id++;\n                break;\n            }\n\n            assert(arg->type == ARGUMENT_BLOCKDEF);\n            gui_element_begin(gui);\n                gui_set_direction(gui, DIRECTION_HORIZONTAL);\n                gui_set_rect(gui, CONVERT_COLOR(dropdown_color, GuiColor));\n                gui_set_align(gui, ALIGN_LEFT, ALIGN_CENTER);\n                gui_set_gap(gui, BLOCK_PADDING);\n                gui_set_custom_data(gui, arg);\n                if (can_hover) gui_on_hover(gui, argument_on_hover);\n\n                draw_blockdef(arg->data.blockdef, ui.hover.editor.edit_blockdef == arg->data.blockdef);\n\n                if (editable) {\n                    if (ui.hover.editor.edit_blockdef == arg->data.blockdef) {\n                        draw_editor_button(&assets.textures.button_add_arg, handle_editor_add_arg_button);\n                        draw_editor_button(&assets.textures.button_add_text, handle_editor_add_text_button);\n\n                        gui_element_begin(gui);\n                            if (can_hover) gui_on_hover(gui, editor_color_on_hover);\n\n                            if (ui.dropdown.ref_object == &arg->data.blockdef->color) {\n                                ui.dropdown.element = gui_get_element(gui);\n                                gui_set_border(gui, (GuiColor) { 0x30, 0x30, 0x30, 0xff }, BLOCK_OUTLINE_SIZE);\n                                gui_on_render(gui, argument_on_render);\n                            }\n\n                            gui_element_begin(gui);\n                                gui_set_fixed(gui, BLOCK_IMAGE_SIZE, BLOCK_IMAGE_SIZE);\n                                gui_set_rect(gui, CONVERT_COLOR(arg->data.blockdef->color, GuiColor));\n                            gui_element_end(gui);\n                        gui_element_end(gui);\n\n                        draw_editor_button(&assets.textures.button_close, handle_editor_close_button);\n                    } else {\n                        draw_editor_button(&assets.textures.button_edit, handle_editor_edit_button);\n                    }\n                    gui_spacer(gui, 0, 0);\n                }\n\n            gui_element_end(gui);\n            arg_id++;\n            break;\n        case INPUT_COLOR:\n            if (!arg) {\n                arg_id++;\n                break;\n            }\n\n            if (arg->type == ARGUMENT_TEXT || arg->type == ARGUMENT_CONST_STRING) {\n                const struct {\n                    char* text;\n                    Color color;\n                } color_map[] = {\n                    { \"black\",  BLACK                              },\n                    { \"red\",    RED                                },\n                    { \"yellow\", YELLOW                             },\n                    { \"green\",  GREEN                              },\n                    { \"blue\",   BLUE                               },\n                    { \"purple\", PURPLE                             },\n                    { \"cyan\",   (Color) { 0x00, 0xff, 0xff, 0xff } },\n                    { \"white\",  WHITE                              },\n                };\n\n                for (size_t i = 0; i < ARRLEN(color_map); i++) {\n                    if (!strcmp(arg->data.text, color_map[i].text)) {\n                        vector_free(arg->data.text);\n                        argument_set_color(arg, CONVERT_COLOR(color_map[i].color, BlockdefColor));\n                        break;\n                    }\n                }\n\n                if (arg->type != ARGUMENT_COLOR) {\n                    vector_free(arg->data.text);\n                    argument_set_color(arg, (BlockdefColor) { 0x00, 0x00, 0x00, 0xff });\n                }\n            }\n\n            switch (arg->type) {\n            case ARGUMENT_COLOR:\n                gui_element_begin(gui);\n                    if (editable) {\n                        if (ui.hover.editor.select_argument == arg) {\n                            gui_set_border(gui, (GuiColor) { 0x30, 0x30, 0x30, 0xff }, BLOCK_OUTLINE_SIZE);\n                            gui_on_render(gui, argument_on_render);\n                        }\n                        gui_set_custom_data(gui, arg);\n                        if (can_hover) gui_on_hover(gui, argument_on_hover);\n                    }\n\n                    if (ui.dropdown.ref_object == arg) {\n                        ui.dropdown.element = gui_get_element(gui);\n                    }\n\n                    gui_element_begin(gui);\n                        gui_set_fixed(gui, BLOCK_IMAGE_SIZE, BLOCK_IMAGE_SIZE);\n                        gui_set_rect(gui, CONVERT_COLOR(arg->data.color, GuiColor));\n                    gui_element_end(gui);\n                gui_element_end(gui);\n                break;\n            case ARGUMENT_BLOCK:\n                block_element = gui_element_begin(gui);\n                    if (can_hover) gui_on_hover(gui, block_argument_on_hover);\n                    gui_set_custom_data(gui, arg);\n\n                    draw_block(&arg->data.block, highlight, can_hover, ghost, editable);\n                gui_element_end(gui);\n                if (block_element->w > 500 && i + 1 < inputs_size) {\n                    gui_element_end(gui);\n                    gui_element_begin(gui);\n                        gui_set_direction(gui, DIRECTION_HORIZONTAL);\n                        gui_set_gap(gui, BLOCK_PADDING);\n                        gui_set_align(gui, ALIGN_LEFT, ALIGN_CENTER);\n                }\n                break;\n            default:\n                assert(false && \"Invalid argument type in color input\");\n                break;\n            }\n\n            arg_id++;\n            break;\n        default:\n            gui_text(gui, &assets.fonts.font_cond_shadow, \"NODEF\", BLOCK_TEXT_SIZE, (GuiColor) { 0xff, 0x00, 0x00, 0xff });\n            break;\n        }\n    }\n\n    gui_element_end(gui);\n    gui_element_end(gui);\n    gui_element_end(gui);\n}\n\nstatic void tab_button_add_on_hover(GuiElement* el) {\n    if (gui_window_is_shown()) return;\n    if (ui.hover.button.handler) return;\n    if (el->draw_type == DRAWTYPE_RECT) return;\n    el->draw_type = DRAWTYPE_RECT;\n    el->draw_subtype = GUI_SUBTYPE_DEFAULT;\n    el->color = (GuiColor) { 0x40, 0x40, 0x40, 0xff };\n    ui.hover.button.handler = handle_add_tab_button;\n    ui.hover.button.data = el->custom_data;\n}\n\nstatic void tab_button_on_hover(GuiElement* el) {\n    if (gui_window_is_shown()) return;\n    if (ui.hover.button.handler) return;\n    if (el->draw_type == DRAWTYPE_RECT) return;\n    el->draw_type = DRAWTYPE_RECT;\n    el->draw_subtype = GUI_SUBTYPE_DEFAULT;\n    el->color = (GuiColor) { 0x40, 0x40, 0x40, 0xff };\n    ui.hover.button.handler = handle_tab_button;\n    ui.hover.button.data = el->custom_data;\n}\n\nstatic void button_on_hover(GuiElement* el) {\n    if (ui.hover.is_panel_edit_mode) return;\n    if (gui_window_is_shown()) return;\n    if (ui.hover.button.handler) return;\n    if (el->draw_type == DRAWTYPE_RECT) return;\n    el->draw_type = DRAWTYPE_RECT;\n    el->draw_subtype = GUI_SUBTYPE_DEFAULT;\n    el->color = (GuiColor) { 0x40, 0x40, 0x40, 0xff };\n    ui.hover.button.handler = el->custom_data;\n}\n\nstatic void panel_editor_button_on_hover(GuiElement* el) {\n    if (!ui.hover.is_panel_edit_mode) return;\n    if (ui.hover.button.handler) return;\n\n    Color color = ColorBrightness(CONVERT_COLOR(el->color, Color), -0.13);\n    el->color = CONVERT_COLOR(color, GuiColor);\n    ui.hover.button.handler = el->custom_data;\n}\n\nstatic void draw_panel_editor_button(const char* text, int size, GuiColor color, ButtonClickHandler handler) {\n    gui_element_begin(gui);\n        gui_set_direction(gui, DIRECTION_HORIZONTAL);\n        gui_set_align(gui, ALIGN_LEFT, ALIGN_CENTER);\n        gui_set_min_size(gui, 0, size);\n        gui_set_padding(gui, config.ui_size * 0.3, 0);\n        gui_set_rect(gui, color);\n        gui_on_hover(gui, panel_editor_button_on_hover);\n        gui_set_custom_data(gui, handler);\n\n        gui_text(gui, &assets.fonts.font_cond, text, BLOCK_TEXT_SIZE, GUI_BLACK);\n    gui_element_end(gui);\n}\n\nstatic GuiElement* draw_button(const char* text, Texture2D* icon, int size, bool selected, GuiHandler on_hover, void* custom_data) {\n    GuiElement* el;\n    gui_element_begin(gui);\n        gui_set_direction(gui, DIRECTION_HORIZONTAL);\n        gui_set_align(gui, ALIGN_LEFT, ALIGN_CENTER);\n        gui_set_min_size(gui, 0, size);\n        gui_set_padding(gui, config.ui_size * 0.3, 0);\n        gui_set_gap(gui, ELEMENT_GAP/2);\n        if (selected) gui_set_rect(gui, GUI_WHITE);\n        gui_on_hover(gui, on_hover);\n        gui_set_custom_data(gui, custom_data);\n        el = gui_get_element(gui);\n\n        if (icon) gui_image(gui, icon, BLOCK_IMAGE_SIZE, selected ? GUI_BLACK : GUI_WHITE);\n        if (text) gui_text(gui, &assets.fonts.font_cond, text, BLOCK_TEXT_SIZE, selected ? GUI_BLACK : GUI_WHITE);\n    gui_element_end(gui);\n    return el;\n}\n\nstatic void draw_top_bar(void) {\n    const int top_bar_size = config.ui_size * 1.2;\n    gui_element_begin(gui);\n        gui_set_grow(gui, DIRECTION_HORIZONTAL);\n        gui_set_direction(gui, DIRECTION_HORIZONTAL);\n        gui_set_rect(gui, (GuiColor) { 0x30, 0x30, 0x30, 0xff });\n        gui_set_min_size(gui, 0, top_bar_size);\n        gui_set_align(gui, ALIGN_LEFT, ALIGN_CENTER);\n\n        gui_spacer(gui, 5, 0);\n        gui_image(gui, &assets.textures.icon_logo, config.ui_size, CONVERT_COLOR(WHITE, GuiColor));\n        gui_spacer(gui, 10, 0);\n        gui_text(gui, &assets.fonts.font_eb, gettext(\"Scrap\"), config.ui_size * 0.8, CONVERT_COLOR(WHITE, GuiColor));\n        gui_spacer(gui, 10, 0);\n\n        GuiElement* el = draw_button(gettext(\"File\"), &assets.textures.icon_file, top_bar_size, false, button_on_hover, handle_file_button_click);\n        if (ui.dropdown.handler == handle_file_menu_click) ui.dropdown.element = el;\n        draw_button(gettext(\"Settings\"), &assets.textures.icon_settings, top_bar_size, false, button_on_hover, handle_settings_button_click);\n        draw_button(gettext(\"About\"), &assets.textures.icon_about, top_bar_size, false, button_on_hover, handle_about_button_click);\n    gui_element_end(gui);\n}\n\nstatic void draw_tab_bar(void) {\n    const int tab_bar_size = config.ui_size;\n    gui_element_begin(gui);\n        gui_set_grow(gui, DIRECTION_HORIZONTAL);\n        gui_set_direction(gui, DIRECTION_HORIZONTAL);\n        gui_set_rect(gui, (GuiColor) { 0x2b, 0x2b, 0x2b, 0xff });\n        gui_set_min_size(gui, 0, tab_bar_size);\n        gui_set_align(gui, ALIGN_LEFT, ALIGN_CENTER);\n\n        if (ui.hover.is_panel_edit_mode && ui.hover.panels.mouse_panel != PANEL_NONE) {\n            draw_button(\"+\", NULL, tab_bar_size, false, tab_button_add_on_hover, (void*)0);\n        }\n        for (size_t i = 0; i < vector_size(editor.tabs); i++) {\n            draw_button(gettext(editor.tabs[i].name), NULL, tab_bar_size, editor.current_tab == (int)i, tab_button_on_hover, (void*)i);\n            if (ui.hover.is_panel_edit_mode && ui.hover.panels.mouse_panel != PANEL_NONE) {\n                draw_button(\"+\", NULL, tab_bar_size, false, tab_button_add_on_hover, (void*)(i + 1));\n            }\n        }\n\n        gui_grow(gui, DIRECTION_HORIZONTAL);\n        gui_text(gui, &assets.fonts.font_cond, editor.project_name, BLOCK_TEXT_SIZE, (GuiColor) { 0x80, 0x80, 0x80, 0xff });\n        if (editor.project_modified) gui_text(gui, &assets.fonts.font_cond, \"*\", BLOCK_TEXT_SIZE, (GuiColor) { 0x80, 0x80, 0x80, 0xff });\n        gui_grow(gui, DIRECTION_HORIZONTAL);\n\n#ifndef USE_INTERPRETER\n        gui_element_begin(gui);\n            gui_on_hover(gui, button_on_hover);\n            gui_set_custom_data(gui, handle_build_button_click);\n\n            gui_image(gui, &assets.textures.button_build, tab_bar_size, (GuiColor) { 0xff, 0x99, 0x00, 0xff });\n        gui_element_end(gui);\n\n        gui_spacer(gui, config.ui_size * 0.2, 0);\n#endif\n\n        gui_element_begin(gui);\n            gui_on_hover(gui, button_on_hover);\n            gui_set_custom_data(gui, handle_stop_button_click);\n\n            if (vm.thread.state == THREAD_STATE_STOPPING) {\n                gui_set_rect(gui, GUI_WHITE);\n                gui_image(gui, &assets.textures.button_stop, tab_bar_size, GUI_BLACK);\n            } else {\n                gui_image(gui, &assets.textures.button_stop, tab_bar_size, (GuiColor) { 0xff, 0x40, 0x30, 0xff });\n            }\n        gui_element_end(gui);\n\n        gui_spacer(gui, config.ui_size * 0.2, 0);\n\n        gui_element_begin(gui);\n            gui_on_hover(gui, button_on_hover);\n            gui_set_custom_data(gui, handle_run_button_click);\n\n            if (thread_is_running(&vm.thread)) {\n                gui_set_rect(gui, GUI_WHITE);\n                gui_image(gui, &assets.textures.button_run, tab_bar_size, GUI_BLACK);\n            } else {\n                gui_image(gui, &assets.textures.button_run, tab_bar_size, (GuiColor) { 0x60, 0xff, 0x00, 0xff });\n            }\n        gui_element_end(gui);\n    gui_element_end(gui);\n}\n\nstatic void blockchain_on_hover(GuiElement* el) {\n    if (ui.hover.is_panel_edit_mode) return;\n    ui.hover.editor.prev_blockchain = el->custom_data;\n}\n\nstatic void draw_block_preview(void) {\n    if (vector_size(editor.mouse_blockchains) != 1) return;\n    if (ui.hover.editor.prev_argument != NULL) return;\n    if (editor.mouse_blockchains[0].blocks[0].blockdef->type == BLOCKTYPE_HAT) return;\n\n    draw_blockchain(&editor.mouse_blockchains[0], true, false, false);\n}\n\nstatic GuiElement* draw_blockchain(BlockChain* chain, bool ghost, bool show_previews, bool editable_arguments) {\n    int layer = 0;\n    GuiElement* el = gui_element_begin(gui);\n        gui_set_direction(gui, DIRECTION_VERTICAL);\n        gui_on_hover(gui, blockchain_on_hover);\n        gui_set_custom_data(gui, chain);\n\n    for (size_t i = 0; i < vector_size(chain->blocks); i++) {\n        Blockdef* blockdef = chain->blocks[i].blockdef;\n\n        if (blockdef->type == BLOCKTYPE_END) {\n            gui_element_end(gui);\n            gui_element_end(gui);\n\n            GuiElement* el = gui_get_element(gui);\n\n            Block* block = el->custom_data;\n\n            bool collision = ui.hover.editor.prev_block == &chain->blocks[i];\n            Color color = CONVERT_COLOR(block->blockdef->color, Color);\n            if (ghost) color.a = BLOCK_GHOST_OPACITY;\n            Color block_color = ColorBrightness(color, collision ? 0.3 : 0.0);\n            Color outline_color;\n            if (ui.hover.editor.select_block == &chain->blocks[i]) {\n                outline_color = ColorBrightness(color, 0.7);\n            } else {\n                outline_color = ColorBrightness(color, collision ? 0.5 : -0.2);\n            }\n\n            gui_element_begin(gui);\n                gui_set_min_size(gui, editor.blockchain_render_layer_widths[vector_size(editor.blockchain_render_layer_widths) - 1], config.ui_size);\n                gui_set_rect(gui, CONVERT_COLOR(block_color, GuiColor));\n                gui_on_hover(gui, block_on_hover);\n                if (ui.hover.editor.select_block == &chain->blocks[i]) gui_on_render(gui, block_on_render);\n                gui_set_custom_data(gui, &chain->blocks[i]);\n\n                gui_element_begin(gui);\n                    gui_set_grow(gui, DIRECTION_VERTICAL);\n                    gui_set_grow(gui, DIRECTION_HORIZONTAL);\n                    gui_set_border(gui, CONVERT_COLOR(outline_color, GuiColor), BLOCK_OUTLINE_SIZE);\n                    gui_set_draw_subtype(gui, BORDER_END);\n                gui_element_end(gui);\n            gui_element_end(gui);\n\n            vector_pop(editor.blockchain_render_layer_widths);\n            layer--;\n            gui_element_end(gui);\n        } else if (blockdef->type == BLOCKTYPE_CONTROLEND) {\n            if (layer > 0) {\n                gui_element_end(gui);\n                gui_element_end(gui);\n                gui_element_end(gui);\n                layer--;\n            }\n            if (vector_size(editor.blockchain_render_layer_widths) > 0) vector_pop(editor.blockchain_render_layer_widths);\n            gui_element_begin(gui);\n                gui_set_direction(gui, DIRECTION_VERTICAL);\n                gui_set_custom_data(gui, &chain->blocks[i]);\n        } else if (blockdef->type == BLOCKTYPE_CONTROL) {\n            gui_element_begin(gui);\n                gui_set_direction(gui, DIRECTION_VERTICAL);\n                gui_set_custom_data(gui, &chain->blocks[i]);\n        }\n\n        if (blockdef->type != BLOCKTYPE_END) {\n            draw_block(&chain->blocks[i], false, true, ghost, editable_arguments);\n        }\n\n        if (blockdef->type == BLOCKTYPE_CONTROL || blockdef->type == BLOCKTYPE_CONTROLEND) {\n            layer++;\n\n            GuiElement* el = gui_get_element(gui);\n            vector_add(&editor.blockchain_render_layer_widths, el->w);\n\n            bool collision = ui.hover.editor.prev_block == &chain->blocks[i];\n            Color color = CONVERT_COLOR(blockdef->color, Color);\n            if (ghost) color.a = BLOCK_GHOST_OPACITY;\n            Color block_color = ColorBrightness(color, collision ? 0.3 : 0.0);\n            Color outline_color;\n            if (ui.hover.editor.select_block == &chain->blocks[i]) {\n                outline_color = ColorBrightness(color, 0.7);\n            } else {\n                outline_color = ColorBrightness(color, collision ? 0.5 : -0.2);\n            }\n\n            gui_element_begin(gui);\n                gui_set_direction(gui, DIRECTION_HORIZONTAL);\n\n                gui_element_begin(gui);\n                    gui_set_grow(gui, DIRECTION_VERTICAL);\n                    gui_set_min_size(gui, BLOCK_CONTROL_INDENT, config.ui_size / 2);\n                    gui_set_rect(gui, CONVERT_COLOR(block_color, GuiColor));\n                    gui_on_hover(gui, block_on_hover);\n                    gui_set_custom_data(gui, &chain->blocks[i]);\n\n                    gui_element_begin(gui);\n                        gui_set_grow(gui, DIRECTION_VERTICAL);\n                        gui_set_grow(gui, DIRECTION_HORIZONTAL);\n                        gui_set_border(gui, CONVERT_COLOR(outline_color, GuiColor), BLOCK_OUTLINE_SIZE);\n                        gui_set_draw_subtype(gui, BORDER_CONTROL_BODY);\n                    gui_element_end(gui);\n                gui_element_end(gui);\n\n                gui_element_begin(gui);\n                    gui_set_direction(gui, DIRECTION_VERTICAL);\n\n                    if (ui.hover.editor.prev_block == &chain->blocks[i] && show_previews) {\n                        draw_block_preview();\n                    }\n        } else {\n            if (ui.hover.editor.prev_block == &chain->blocks[i] && show_previews) {\n                draw_block_preview();\n            }\n        }\n    }\n\n    while (layer > 0) {\n        gui_element_end(gui);\n        gui_element_end(gui);\n        gui_element_end(gui);\n        layer--;\n    }\n\n    gui_element_end(gui);\n    return el;\n}\n\nstatic void category_on_hover(GuiElement* el) {\n    if (ui.hover.is_panel_edit_mode) return;\n    if (gui_window_is_shown()) return;\n    if (ui.hover.button.handler) return;\n\n    el->color.a = 0x80;\n    ui.hover.button.handler = handle_category_click;\n    ui.hover.category = el->custom_data;\n}\n\nstatic void draw_category(BlockCategory* category) {\n    GuiColor color = CONVERT_COLOR(category->color, GuiColor);\n    color.a = 0x40;\n\n    gui_element_begin(gui);\n        gui_set_scissor(gui);\n        gui_set_grow(gui, DIRECTION_HORIZONTAL);\n        gui_set_rect(gui, color);\n        gui_on_hover(gui, category_on_hover);\n        gui_set_custom_data(gui, category);\n\n        color.a = 0xff;\n        gui_element_begin(gui);\n            gui_set_grow(gui, DIRECTION_HORIZONTAL);\n            if (category == editor.palette.current_category) gui_set_border(gui, color, BLOCK_OUTLINE_SIZE);\n            gui_set_direction(gui, DIRECTION_HORIZONTAL);\n            gui_set_padding(gui, ELEMENT_GAP, 0);\n            gui_set_min_size(gui, 0, config.ui_size);\n            gui_set_align(gui, ALIGN_LEFT, ALIGN_CENTER);\n            gui_set_gap(gui, ELEMENT_GAP);\n\n            gui_element_begin(gui);\n                gui_set_min_size(gui, ELEMENT_GAP * 2, ELEMENT_GAP * 2);\n                gui_set_rect(gui, color);\n            gui_element_end(gui);\n\n            gui_text(gui, &assets.fonts.font_cond_shadow, category->name, BLOCK_TEXT_SIZE, GUI_WHITE);\n        gui_element_end(gui);\n    gui_element_end(gui);\n}\n\nstatic void draw_block_categories(void) {\n    gui_element_begin(gui);\n        gui_set_grow(gui, DIRECTION_VERTICAL);\n        gui_set_grow(gui, DIRECTION_HORIZONTAL);\n        gui_set_rect(gui, (GuiColor) PANEL_BACKGROUND_COLOR);\n        gui_set_padding(gui, ELEMENT_GAP, ELEMENT_GAP);\n        gui_set_gap(gui, ELEMENT_GAP);\n        gui_set_scroll(gui, &ui.categories_scroll);\n        gui_set_scissor(gui);\n\n        BlockCategory* cat = editor.palette.categories_start;\n        while (cat) {\n            gui_element_begin(gui);\n                gui_set_direction(gui, DIRECTION_HORIZONTAL);\n                gui_set_grow(gui, DIRECTION_HORIZONTAL);\n                gui_set_gap(gui, ELEMENT_GAP);\n\n                draw_category(cat);\n                cat = cat->next;\n                if (cat) {\n                    draw_category(cat);\n                    cat = cat->next;\n                } else {\n                    gui_grow(gui, DIRECTION_HORIZONTAL);\n                }\n            gui_element_end(gui);\n        }\n    gui_element_end(gui);\n}\n\nstatic void draw_block_palette(void) {\n    gui_element_begin(gui);\n        gui_set_grow(gui, DIRECTION_VERTICAL);\n        gui_set_grow(gui, DIRECTION_HORIZONTAL);\n        gui_set_rect(gui, (GuiColor) PANEL_BACKGROUND_COLOR);\n        gui_set_padding(gui, ELEMENT_GAP, ELEMENT_GAP);\n        gui_set_gap(gui, ELEMENT_GAP);\n        gui_set_scroll(gui, &editor.palette.scroll_amount);\n        gui_set_scroll_scaling(gui, config.ui_size * 4);\n        gui_set_scissor(gui);\n\n        BlockCategory* cat = editor.palette.current_category;\n        if (cat) {\n            for (size_t i = 0; i < vector_size(cat->items); i++) {\n                switch (cat->items[i].type) {\n                case CATEGORY_ITEM_CHAIN:\n                    draw_blockchain(&cat->items[i].data.chain, false, false, false);\n                    break;\n                case CATEGORY_ITEM_LABEL:\n                    if (i != 0) gui_spacer(gui, 0, config.ui_size * 0.1);\n                    gui_element_begin(gui);\n                        gui_set_direction(gui, DIRECTION_HORIZONTAL);\n                        gui_set_gap(gui, BLOCK_OUTLINE_SIZE * 4);\n                        gui_set_align(gui, ALIGN_LEFT, ALIGN_CENTER);\n                        // gui_set_min_size(gui, 0, config.ui_size);\n\n                        gui_element_begin(gui);\n                            gui_set_fixed(gui, BLOCK_OUTLINE_SIZE * 2, config.ui_size * 0.75);\n                            gui_set_rect(gui, CONVERT_COLOR(cat->items[i].data.label.color, GuiColor));\n                        gui_element_end(gui);\n\n                        gui_text(gui, &assets.fonts.font_cond_shadow, cat->items[i].data.label.text, BLOCK_TEXT_SIZE, GUI_WHITE);\n                    gui_element_end(gui);\n                    break;\n                }\n            }\n        } else {\n            gui_set_align(gui, ALIGN_CENTER, ALIGN_CENTER);\n            gui_text(gui, &assets.fonts.font_cond_shadow, \"No category currently selected\", BLOCK_TEXT_SIZE, GUI_WHITE);\n        }\n    gui_element_end(gui);\n}\n\nstatic void spectrum_on_hover(GuiElement* el) {\n    (void) el;\n    ui.dropdown.as.color_picker.hover_part = COLOR_PICKER_SPECTRUM;\n}\n\nstatic void spectrum_on_render(GuiElement* el) {\n    if (IsMouseButtonDown(MOUSE_BUTTON_LEFT) && ui.dropdown.as.color_picker.select_part == COLOR_PICKER_SPECTRUM) {\n        ui.dropdown.as.color_picker.color.hue = CLAMP((gui->mouse_y - el->parent->abs_y) / el->parent->h * 360.0, 0.0, 360.0);\n        editor.project_modified = true;\n    }\n    el->y = (ui.dropdown.as.color_picker.color.hue / 360.0) * el->parent->h - el->h / 2.0;\n}\n\nstatic void color_picker_on_hover(GuiElement* el) {\n    (void) el;\n    ui.hover.button.handler = handle_color_picker_click;\n}\n\nstatic void color_picker_sv_on_hover(GuiElement* el) {\n    (void) el;\n    ui.dropdown.as.color_picker.hover_part = COLOR_PICKER_SV;\n}\n\nstatic void color_picker_sv_on_render(GuiElement* el) {\n    if (IsMouseButtonDown(MOUSE_BUTTON_LEFT) && ui.dropdown.as.color_picker.select_part == COLOR_PICKER_SV) {\n        ui.dropdown.as.color_picker.color.saturation = CLAMP((gui->mouse_x - el->parent->abs_x) / el->parent->w, 0.0, 1.0);\n        ui.dropdown.as.color_picker.color.value = CLAMP(1.0 - (gui->mouse_y - el->parent->abs_y) / el->parent->h, 0.0, 1.0);\n        editor.project_modified = true;\n    }\n\n    el->x = ui.dropdown.as.color_picker.color.saturation * el->parent->w - el->w / 2.0;\n    el->y = (1 - ui.dropdown.as.color_picker.color.value) * el->parent->h - el->h / 2.0;\n}\n\nstatic void draw_color_picker(void) {\n    Color col = ColorFromHSV(\n        ui.dropdown.as.color_picker.color.hue,\n        ui.dropdown.as.color_picker.color.saturation,\n        ui.dropdown.as.color_picker.color.value\n    );\n\n    Color col_hue = ColorFromHSV(\n        ui.dropdown.as.color_picker.color.hue,\n        1.0,\n        1.0\n    );\n\n    *ui.dropdown.as.color_picker.edit_color = col;\n\n    gui_element_begin(gui);\n        gui_set_rect(gui, (GuiColor) { 0x20, 0x20, 0x20, 0xff });\n        gui_on_hover(gui, color_picker_on_hover);\n\n        gui_element_begin(gui);\n            gui_set_border(gui, (GuiColor) { 0x40, 0x40, 0x40, 0xff }, 2);\n            gui_set_gap(gui, config.ui_size * 0.25);\n            gui_set_padding(gui, config.ui_size * 0.25, config.ui_size * 0.25);\n            gui_set_direction(gui, DIRECTION_HORIZONTAL);\n\n            gui_element_begin(gui);\n                gui_set_rect(gui, CONVERT_COLOR(col_hue, GuiColor));\n                gui_set_fixed(gui, config.ui_size * 8.0, config.ui_size * 8.0);\n                gui_set_shader(gui, &assets.gradient_shader);\n                gui_on_hover(gui, color_picker_sv_on_hover);\n\n                gui_element_begin(gui);\n                    gui_set_border(gui, (GuiColor) { 0xff - col.r, 0xff - col.g, 0xff - col.b, 0xff }, 2);\n                    gui_set_floating(gui);\n                    gui_set_position(gui, -config.ui_size * 0.125, -config.ui_size * 0.125);\n                    gui_set_fixed(gui, config.ui_size * 0.25, config.ui_size * 0.25);\n                    gui_on_render(gui, color_picker_sv_on_render);\n                gui_element_end(gui);\n            gui_element_end(gui);\n\n            gui_element_begin(gui);\n                gui_set_image(gui, &assets.textures.spectrum, 0, GUI_WHITE);\n                gui_set_min_size(gui, config.ui_size * 0.75, 0);\n                gui_set_grow(gui, DIRECTION_VERTICAL);\n                gui_set_draw_subtype(gui, IMAGE_STRETCHED);\n                gui_on_hover(gui, spectrum_on_hover);\n\n                gui_element_begin(gui);\n                    gui_set_border(gui, (GuiColor) { 0xff - col_hue.r, 0xff - col_hue.g, 0xff - col_hue.b, 0xff }, 2);\n                    gui_set_floating(gui);\n                    gui_set_position(gui, -config.ui_size * 0.125, -config.ui_size * 0.125);\n                    gui_set_fixed(gui, config.ui_size, config.ui_size * 0.25);\n                    gui_on_render(gui, spectrum_on_render);\n                gui_element_end(gui);\n            gui_element_end(gui);\n\n            gui_element_begin(gui);\n                gui_set_gap(gui, config.ui_size * 0.25);\n\n                gui_element_begin(gui);\n                    gui_set_rect(gui, (GuiColor) { 0x30, 0x30, 0x30, 0xff });\n                    gui_set_padding(gui, config.ui_size * 0.2, config.ui_size * 0.2);\n\n                    snprintf(ui.dropdown.as.color_picker.color_hex, 10, \"#%02x%02x%02x%02x\", col.r, col.g, col.b, col.a);\n                    gui_text(gui, &assets.fonts.font_cond, ui.dropdown.as.color_picker.color_hex, BLOCK_TEXT_SIZE, GUI_WHITE);\n                gui_element_end(gui);\n\n                gui_element_begin(gui);\n                    gui_set_border(gui, (GuiColor) { 0x40, 0x40, 0x40, 0xff }, 2);\n                    gui_element_begin(gui);\n                        gui_set_fixed(gui, config.ui_size, config.ui_size);\n                        gui_set_rect(gui, CONVERT_COLOR(col, GuiColor));\n                    gui_element_end(gui);\n                gui_element_end(gui);\n            gui_element_end(gui);\n\n        gui_element_end(gui);\n\n    gui_element_end(gui);\n}\n\nstatic void code_area_on_render(GuiElement* el) {\n    ui.hover.panels.code_panel_bounds = (Rectangle) { el->abs_x, el->abs_y, el->w, el->h };\n}\n\nstatic void draw_code_area(void) {\n    gui_element_begin(gui);\n        gui_set_grow(gui, DIRECTION_HORIZONTAL);\n        gui_set_grow(gui, DIRECTION_VERTICAL);\n        gui_set_direction(gui, DIRECTION_VERTICAL);\n        gui_set_padding(gui, 0, 0);\n        gui_set_align(gui, ALIGN_RIGHT, ALIGN_TOP);\n        gui_set_scissor(gui);\n        gui_on_render(gui, code_area_on_render);\n\n        draw_code();\n\n        gui_element_begin(gui);\n            gui_set_floating(gui);\n            gui_set_position(gui, 0, 0);\n            gui_set_padding(gui, config.ui_size * 0.2, config.ui_size * 0.2);\n            for (int i = 0; i < DEBUG_BUFFER_LINES; i++) {\n                if (*editor.debug_buffer[i]) gui_text(gui, &assets.fonts.font_cond, editor.debug_buffer[i], config.ui_size * 0.5, (GuiColor) { 0xff, 0xff, 0xff, 0x60 });\n            }\n\n            gui_spacer(gui, 0, config.ui_size * 0.5);\n        gui_element_end(gui);\n\n        if (!thread_is_running(&vm.thread) && vector_size(vm.compile_error) > 0) {\n            gui_element_begin(gui);\n                gui_set_direction(gui, DIRECTION_HORIZONTAL);\n                gui_set_align(gui, ALIGN_LEFT, ALIGN_CENTER);\n                gui_set_gap(gui, config.ui_size * 0.5);\n                gui_set_padding(gui, config.ui_size * 0.4, config.ui_size * 0.4);\n                gui_set_rect(gui, (GuiColor) { 0x00, 0x00, 0x00, 0x80 });\n\n                double animation = (fmod(-GetTime(), 1.0) * 0.5 + 1.0) * 255.0;\n                gui_element_begin(gui);\n                    gui_set_rect(gui, (GuiColor) { 0xff, 0x20, 0x20, animation });\n                    gui_set_fixed(gui, config.ui_size, config.ui_size);\n                    gui_set_direction(gui, DIRECTION_VERTICAL);\n                    gui_set_align(gui, ALIGN_CENTER, ALIGN_CENTER);\n\n                    gui_text(gui, &assets.fonts.font_eb, \"!\", config.ui_size, GUI_WHITE);\n                gui_element_end(gui);\n\n                gui_element_begin(gui);\n                    gui_set_direction(gui, DIRECTION_VERTICAL);\n\n                    gui_text(gui, &assets.fonts.font_cond, gettext(\"Got compiler error!\"), config.ui_size * 0.6, (GuiColor) { 0xff, 0x33, 0x33, 0xff });\n                    for (size_t i = 0; i < vector_size(vm.compile_error); i++) {\n                        gui_text(gui, &assets.fonts.font_cond, vm.compile_error[i], config.ui_size * 0.6, GUI_WHITE);\n                    }\n\n                    gui_spacer(gui, 0, config.ui_size * 0.5);\n\n                    gui_element_begin(gui);\n                        gui_set_direction(gui, DIRECTION_HORIZONTAL);   \n                        gui_set_gap(gui, config.ui_size * 0.5);\n\n                        if (vm.compile_error_block) {\n                            gui_element_begin(gui);\n                                gui_set_border(gui, (GuiColor) { 0x40, 0x40, 0x40, 0xff }, BLOCK_OUTLINE_SIZE);\n                                draw_button(gettext(\"Jump to block\"), NULL, config.ui_size, false, button_on_hover, handle_jump_to_block_button_click);\n                            gui_element_end(gui);\n                        }\n\n                        gui_element_begin(gui);\n                            gui_set_border(gui, (GuiColor) { 0x40, 0x40, 0x40, 0xff }, BLOCK_OUTLINE_SIZE);\n                            draw_button(gettext(\"Close\"), NULL, config.ui_size, false, button_on_hover, handle_error_window_close_button_click);\n                        gui_element_end(gui);\n                    gui_element_end(gui);\n                gui_element_end(gui);\n            gui_element_end(gui);\n        } else {\n            gui_spacer(gui, 0, config.ui_size * 1.5);\n        }\n\n        if (editor.actionbar.show_time > 0) {\n            gui_element_begin(gui);\n                gui_set_grow(gui, DIRECTION_HORIZONTAL);\n                gui_set_direction(gui, DIRECTION_VERTICAL);\n                gui_set_align(gui, ALIGN_CENTER, ALIGN_TOP);\n                \n                Color color = YELLOW;\n                color.a = editor.actionbar.show_time / 3.0 * 255.0;\n                gui_text(gui, &assets.fonts.font_eb, editor.actionbar.text, config.ui_size * 0.8, CONVERT_COLOR(color, GuiColor));\n            gui_element_end(gui);\n        }\n    gui_element_end(gui);\n}\n\nstatic void draw_split_preview(PanelTree* panel) {\n    if (!ui.hover.is_panel_edit_mode) return;\n    if (ui.hover.panels.prev_panel != panel) return;\n\n    if (ui.hover.panels.mouse_panel == PANEL_NONE) {\n        gui_element_begin(gui);\n            gui_set_floating(gui);\n            gui_set_position(gui, 0, 0);\n            gui_set_grow(gui, DIRECTION_HORIZONTAL);\n            gui_set_grow(gui, DIRECTION_VERTICAL);\n            gui_set_rect(gui, (GuiColor) { 0x00, 0xff, 0xff, 0x20 });\n\n            gui_element_begin(gui);\n                gui_set_grow(gui, DIRECTION_HORIZONTAL);\n                gui_set_grow(gui, DIRECTION_VERTICAL);\n                gui_set_border(gui, (GuiColor) { 0x00, 0xff, 0xff, 0x80 }, BLOCK_OUTLINE_SIZE);\n            gui_element_end(gui);\n        gui_element_end(gui);\n        return;\n    }\n\n    if (ui.hover.panels.panel_side == SPLIT_SIDE_NONE) return;\n\n    gui_element_begin(gui);\n        gui_set_floating(gui);\n        gui_set_position(gui, 0, 0);\n        gui_set_grow(gui, DIRECTION_HORIZONTAL);\n        gui_set_grow(gui, DIRECTION_VERTICAL);\n\n        if (ui.hover.panels.panel_side == SPLIT_SIDE_LEFT || ui.hover.panels.panel_side == SPLIT_SIDE_RIGHT) gui_set_direction(gui, DIRECTION_HORIZONTAL);\n\n        if (ui.hover.panels.panel_side == SPLIT_SIDE_BOTTOM) gui_grow(gui, DIRECTION_VERTICAL);\n        if (ui.hover.panels.panel_side == SPLIT_SIDE_RIGHT) gui_grow(gui, DIRECTION_HORIZONTAL);\n\n        gui_element_begin(gui);\n            gui_set_grow(gui, DIRECTION_VERTICAL);\n            gui_set_grow(gui, DIRECTION_HORIZONTAL);\n            gui_set_rect(gui, (GuiColor) { 0x00, 0xff, 0xff, 0x20 });\n\n            gui_element_begin(gui);\n                gui_set_grow(gui, DIRECTION_VERTICAL);\n                gui_set_grow(gui, DIRECTION_HORIZONTAL);\n                gui_set_border(gui, (GuiColor) { 0x00, 0xff, 0xff, 0x80 }, BLOCK_OUTLINE_SIZE);\n            gui_element_end(gui);\n        gui_element_end(gui);\n\n        if (ui.hover.panels.panel_side == SPLIT_SIDE_TOP) gui_grow(gui, DIRECTION_VERTICAL);\n        if (ui.hover.panels.panel_side == SPLIT_SIDE_LEFT) gui_grow(gui, DIRECTION_HORIZONTAL);\n    gui_element_end(gui);\n}\n\nstatic void draw_term_panel(void) {\n    gui_element_begin(gui);\n        gui_set_grow(gui, DIRECTION_HORIZONTAL);\n        gui_set_grow(gui, DIRECTION_VERTICAL);\n        gui_set_padding(gui, ELEMENT_GAP, ELEMENT_GAP);\n        gui_set_rect(gui, (GuiColor) PANEL_BACKGROUND_COLOR);\n\n        gui_element_begin(gui);\n            gui_set_grow(gui, DIRECTION_HORIZONTAL);\n            gui_set_grow(gui, DIRECTION_VERTICAL);\n            gui_set_rect(gui, GUI_WHITE);\n            gui_set_draw_subtype(gui, RECT_TERMINAL);\n        gui_element_end(gui);\n    gui_element_end(gui);\n}\n\nstatic void panel_on_hover(GuiElement* el) {\n    ui.hover.panels.panel = el->custom_data;\n    ui.hover.panels.panel_size = (Rectangle) { el->abs_x, el->abs_y, el->w, el->h };\n\n    if (ui.hover.panels.panel->type == PANEL_SPLIT) return;\n\n    int mouse_x = gui->mouse_x - el->abs_x;\n    int mouse_y = gui->mouse_y - el->abs_y;\n\n    bool is_top_right = mouse_y < ((float)el->h / (float)el->w) * mouse_x;\n    bool is_top_left = mouse_y < -((float)el->h / (float)el->w * mouse_x) + el->h;\n\n    if (is_top_right) {\n        if (is_top_left) {\n            ui.hover.panels.panel_side = SPLIT_SIDE_TOP;\n        } else {\n            ui.hover.panels.panel_side = SPLIT_SIDE_RIGHT;\n        }\n    } else {\n        if (is_top_left) {\n            ui.hover.panels.panel_side = SPLIT_SIDE_LEFT;\n        } else {\n            ui.hover.panels.panel_side = SPLIT_SIDE_BOTTOM;\n        }\n    }\n}\n\nstatic void draw_panel(PanelTree* panel) {\n    if (panel->type != PANEL_SPLIT && !panel->parent) {\n        gui_element_begin(gui);\n            gui_set_grow(gui, DIRECTION_VERTICAL);\n            gui_set_grow(gui, DIRECTION_HORIZONTAL);\n            gui_on_hover(gui, panel_on_hover);\n            gui_set_custom_data(gui, panel);\n    }\n\n    switch (panel->type) {\n    case PANEL_NONE:\n        assert(false && \"Attempt to render panel with type PANEL_NONE\");\n        break;\n    case PANEL_BLOCK_PALETTE:\n        draw_block_palette();\n        break;\n    case PANEL_CODE:\n        draw_code_area();\n        break;\n    case PANEL_TERM:\n        draw_term_panel();\n        break;\n    case PANEL_BLOCK_CATEGORIES:\n        draw_block_categories();\n        break;\n    case PANEL_SPLIT:\n        gui_element_begin(gui);\n            gui_set_grow(gui, DIRECTION_VERTICAL);\n            gui_set_grow(gui, DIRECTION_HORIZONTAL);\n            gui_set_direction(gui, panel->direction);\n            gui_on_hover(gui, panel_on_hover);\n            gui_set_custom_data(gui, panel);\n\n            gui_element_begin(gui);\n                if (panel->direction == DIRECTION_VERTICAL) {\n                    gui_set_percent_size(gui, panel->split_percent, DIRECTION_VERTICAL);\n                    gui_set_grow(gui, DIRECTION_HORIZONTAL);\n                } else {\n                    gui_set_grow(gui, DIRECTION_VERTICAL);\n                    gui_set_percent_size(gui, panel->split_percent, DIRECTION_HORIZONTAL);\n                }\n\n                if (panel->left->type != PANEL_SPLIT) {\n                    gui_on_hover(gui, panel_on_hover);\n                    gui_set_custom_data(gui, panel->left);\n                }\n\n                draw_panel(panel->left);\n            gui_element_end(gui);\n\n            if (ui.hover.is_panel_edit_mode) {\n                gui_element_begin(gui);\n                    if (panel->direction == DIRECTION_HORIZONTAL) {\n                        gui_set_grow(gui, DIRECTION_VERTICAL);\n                    } else {\n                        gui_set_grow(gui, DIRECTION_HORIZONTAL);\n                    }\n                    gui_set_min_size(gui, 10, 10);\n                    gui_set_rect(gui, (GuiColor) { 0xff, 0xff, 0xff, ui.hover.panels.drag_panel == panel ? 0x20 : ui.hover.panels.prev_panel == panel ? 0x80 : 0x40 });\n                gui_element_end(gui);\n            }\n\n            gui_element_begin(gui);\n                gui_set_grow(gui, DIRECTION_VERTICAL);\n                gui_set_grow(gui, DIRECTION_HORIZONTAL);\n\n                if (panel->right->type != PANEL_SPLIT) {\n                    gui_on_hover(gui, panel_on_hover);\n                    gui_set_custom_data(gui, panel->right);\n                }\n\n                draw_panel(panel->right);\n            gui_element_end(gui);\n        gui_element_end(gui);\n        break;\n    }\n    if (panel->type != PANEL_SPLIT) draw_split_preview(panel);\n    if (panel->type != PANEL_SPLIT && !panel->parent) gui_element_end(gui);\n}\n\nstatic void draw_code(void) {\n    for (size_t i = 0; i < vector_size(editor.code); i++) {\n        Vector2 chain_pos = (Vector2) {\n            editor.code[i].x * config.ui_size / 32.0 - editor.camera_pos.x,\n            editor.code[i].y * config.ui_size / 32.0 - editor.camera_pos.y,\n        };\n        Rectangle code_size = ui.hover.panels.code_panel_bounds;\n        if (&editor.code[i] != ui.hover.editor.select_blockchain) {\n            if (chain_pos.x > code_size.width || chain_pos.y > code_size.height) continue;\n            if (editor.code[i].width > 0 && editor.code[i].height > 0 &&\n                (chain_pos.x + editor.code[i].width < 0 || chain_pos.y + editor.code[i].height < 0)) continue;\n        }\n        GuiElement* el = gui_element_begin(gui);\n            gui_set_floating(gui);\n            gui_set_position(gui, chain_pos.x, chain_pos.y);\n\n            draw_blockchain(&editor.code[i], false, config.show_blockchain_previews, true);\n        gui_element_end(gui);\n        editor.code[i].width = el->w;\n        editor.code[i].height = el->h;\n    }\n}\n\nstatic void list_dropdown_on_hover(GuiElement* el) {\n    assert(ui.dropdown.type == DROPDOWN_LIST);\n\n    el->draw_type = DRAWTYPE_RECT;\n    el->draw_subtype = GUI_SUBTYPE_DEFAULT;\n    el->color = (GuiColor) { 0x40, 0x40, 0x40, 0xff };\n    // Double cast to avoid warning. In our case this operation is safe because el->custom_data currently stores a value of type int\n    ui.dropdown.as.list.select_ind = (int)(size_t)el->custom_data;\n\n    ui.hover.button.handler = ui.dropdown.handler;\n}\n\nstatic void draw_list_dropdown(void) {\n    const int max_list_size = 10;\n\n    gui_element_begin(gui);\n        gui_set_rect(gui, (GuiColor) { 0x40, 0x40, 0x40, 0xff });\n        gui_set_gap(gui, 2);\n        gui_set_padding(gui, 2, 2);\n        if (ui.dropdown.as.list.len > max_list_size) {\n            gui_set_scissor(gui);\n            gui_set_fixed(gui, ui.dropdown.element->w + 5, max_list_size * (config.ui_size + 2) + 4);\n            gui_set_scroll(gui, &ui.dropdown.as.list.scroll);\n            gui_set_scroll_scaling(gui, (config.ui_size + 2) * 2);\n        } else {\n            gui_set_min_size(gui, ui.dropdown.element->w, 0);\n        }\n\n        for (int i = 0; i < ui.dropdown.as.list.len; i++) {\n            gui_element_begin(gui);\n                gui_set_grow(gui, DIRECTION_HORIZONTAL);\n                gui_set_direction(gui, DIRECTION_HORIZONTAL);\n                gui_set_align(gui, ALIGN_LEFT, ALIGN_CENTER);\n                gui_set_min_size(gui, 0, config.ui_size);\n                gui_set_padding(gui, config.ui_size * 0.3, 0);\n                gui_set_rect(gui, (GuiColor) { 0x2b, 0x2b, 0x2b, 0xff });\n                gui_on_hover(gui, list_dropdown_on_hover);\n                gui_set_custom_data(gui, (void*)(size_t)i);\n\n                const char* list_value = sgettext(ui.dropdown.as.list.data[i]);\n                gui_text(gui, &assets.fonts.font_cond, list_value, BLOCK_TEXT_SIZE, GUI_WHITE);\n            gui_element_end(gui);\n        }\n    gui_element_end(gui);\n}\n\nstatic void dropdown_on_render(GuiElement* el) {\n    int off_x = MAX(el->abs_x + el->w - gui->win_w, 0);\n    int off_y = MAX(el->abs_y + el->h - gui->win_h, 0);\n    el->x -= off_x;\n    el->abs_x -= off_x;\n\n    el->y -= off_y;\n    el->abs_y -= off_y;\n}\n\nstatic void draw_dropdown(void) {\n    if (!ui.dropdown.shown) return;\n    ui.hover.button.handler = handle_dropdown_close;\n\n    if (!ui.dropdown.element) {\n        scrap_log(LOG_WARNING, \"[DROPDOWN] Anchor is not set or gone\");\n        handle_dropdown_close();\n        return;\n    }\n\n    gui_element_begin(gui);\n        gui_set_floating(gui);\n        gui_set_parent_anchor(gui, ui.dropdown.element);\n        gui_set_position(gui, 0, ui.dropdown.element->h);\n        gui_on_render(gui, dropdown_on_render);\n\n        switch (ui.dropdown.type) {\n        case DROPDOWN_COLOR_PICKER:\n            draw_color_picker();\n            break;\n        case DROPDOWN_LIST:\n            draw_list_dropdown();\n            break;\n        default:\n            assert(false && \"Unhandled dropdown type\");\n            break;\n        }\n    gui_element_end(gui);\n}\n\nstatic void search_on_hover(GuiElement* el) {\n    el->color = (GuiColor) { 0x40, 0x40, 0x40, 0xff };\n    ui.hover.editor.blockdef = el->custom_data;\n}\n\nstatic void draw_search_list(void) {\n    gui_element_begin(gui);\n        gui_set_floating(gui);\n        gui_set_position(gui, editor.search_list_pos.x, editor.search_list_pos.y);\n        gui_set_rect(gui, (GuiColor) { 0x40, 0x40, 0x40, 0xff });\n        gui_set_gap(gui, BLOCK_OUTLINE_SIZE);\n        gui_set_padding(gui, BLOCK_OUTLINE_SIZE, BLOCK_OUTLINE_SIZE);\n\n        gui_element_begin(gui);\n            gui_set_rect(gui, (GuiColor) { 0x2b, 0x2b, 0x2b, 0xff });\n            gui_set_grow(gui, DIRECTION_HORIZONTAL);\n            gui_set_padding(gui, BLOCK_OUTLINE_SIZE, 0);\n            gui_set_min_size(gui, 0, config.ui_size);\n            gui_set_direction(gui, DIRECTION_HORIZONTAL);\n            gui_set_align(gui, ALIGN_LEFT, ALIGN_CENTER);\n\n            draw_input_text(&assets.fonts.font_cond, &editor.search_list_search, \"Search...\", BLOCK_TEXT_SIZE, GUI_WHITE);\n        gui_element_end(gui);\n\n        gui_element_begin(gui);\n            gui_set_fixed(gui, 0, config.ui_size * 5);\n            gui_set_fit(gui, DIRECTION_HORIZONTAL);\n            gui_set_gap(gui, BLOCK_OUTLINE_SIZE);\n            gui_set_scissor(gui);\n            gui_set_scroll(gui, &ui.search_list_scroll);\n\n            for (size_t i = 0; i < vector_size(editor.search_list); i++) {\n                gui_element_begin(gui);\n                    gui_set_rect(gui, (GuiColor) { 0x2b, 0x2b, 0x2b, 0xff });\n                    gui_set_grow(gui, DIRECTION_HORIZONTAL);\n                    gui_on_hover(gui, search_on_hover);\n                    gui_set_custom_data(gui, editor.search_list[i]);\n\n                    Block dummy_block = {\n                        .blockdef = editor.search_list[i],\n                        .arguments = NULL,\n                        .parent = NULL,\n                    };\n                    draw_block(&dummy_block, ui.hover.editor.prev_blockdef == editor.search_list[i], false, false, false);\n                gui_element_end(gui);\n            }\n\n        gui_element_end(gui);\n    gui_element_end(gui);\n}\n\nstatic void panel_editor_on_hover(GuiElement* el) {\n    (void) el;\n    if (!ui.hover.is_panel_edit_mode) return;\n    ui.hover.panels.panel = NULL;\n}\n\nvoid scrap_gui_process(void) {\n    gui_begin(gui);\n        draw_top_bar();\n        draw_tab_bar();\n        GuiElement* tab_bar_anchor = NULL;\n\n        if (ui.hover.is_panel_edit_mode) {\n            gui_element_begin(gui);\n                tab_bar_anchor = gui_get_element(gui);\n            gui_element_end(gui);\n        }\n\n        draw_panel(editor.tabs[editor.current_tab].root_panel);\n        draw_window();\n\n        gui_element_begin(gui);\n            gui_set_floating(gui);\n            gui_set_position(gui, gui->mouse_x, gui->mouse_y);\n            gui_set_gap(gui, config.ui_size);\n\n            int x_i = 0,\n                x_i_max = ceil(sqrt(vector_size(editor.mouse_blockchains))),\n                y_max = 0;\n\n            gui_element_begin(gui);\n                gui_set_direction(gui, DIRECTION_HORIZONTAL);\n                gui_set_gap(gui, config.ui_size);\n\n                for (size_t i = 0; i < vector_size(editor.mouse_blockchains); i++) {\n                    GuiElement* el = draw_blockchain(&editor.mouse_blockchains[i], false, false, true);\n                    editor.mouse_blockchains[i].width  = el->w;\n                    editor.mouse_blockchains[i].height = el->h;\n\n                    x_i++;\n                    y_max = MAX(y_max, editor.mouse_blockchains[i].height);\n\n                    if (x_i >= x_i_max) {\n                        gui_element_end(gui);\n                        gui_element_begin(gui);\n                            gui_set_direction(gui, DIRECTION_HORIZONTAL);\n                            gui_set_gap(gui, config.ui_size);\n                        y_max = 0;\n                        x_i = 0;\n                    }\n                }\n            gui_element_end(gui);\n        gui_element_end(gui);\n\n        if (ui.hover.select_input == &editor.search_list_search) {\n            draw_search_list();\n        } else {\n            editor.search_list_pos = (Vector2) { gui->mouse_x, gui->mouse_y };\n        }\n\n        if (ui.hover.is_panel_edit_mode) {\n            if (ui.hover.panels.mouse_panel != PANEL_NONE) {\n                gui_element_begin(gui);\n                    gui_set_floating(gui);\n                    gui_set_fixed(gui, gui->win_w * 0.3, gui->win_h * 0.3);\n                    gui_set_position(gui, gui->mouse_x, gui->mouse_y);\n\n                    PanelTree panel = (PanelTree) {\n                        .type = ui.hover.panels.mouse_panel,\n                        .parent = NULL,\n                        .left = NULL,\n                        .right = NULL,\n                    };\n                    draw_panel(&panel);\n                gui_element_end(gui);\n            }\n\n            gui_element_begin(gui);\n                gui_set_floating(gui);\n                gui_set_grow(gui, DIRECTION_HORIZONTAL);\n                gui_set_position(gui, 0, 0);\n                gui_set_parent_anchor(gui, tab_bar_anchor);\n                gui_set_align(gui, ALIGN_CENTER, ALIGN_TOP);\n                gui_set_padding(gui, 0, config.ui_size);\n\n                gui_element_begin(gui);\n                    gui_set_padding(gui, config.ui_size * 0.3, config.ui_size * 0.3);\n                    gui_set_rect(gui, (GuiColor) { 0x00, 0x00, 0x00, 0x80 });\n                    gui_set_align(gui, ALIGN_CENTER, ALIGN_TOP);\n                    gui_on_hover(gui, panel_editor_on_hover);\n\n                    gui_text(gui, &assets.fonts.font_eb, gettext(\"Panel edit mode\"), config.ui_size * 0.8, GUI_WHITE);\n\n                    gui_spacer(gui, 0, config.ui_size * 0.25);\n\n                    gui_text(gui, &assets.fonts.font_cond_shadow, gettext(\"Click on panels to reposition them\"), BLOCK_TEXT_SIZE, GUI_WHITE);\n                    gui_text(gui, &assets.fonts.font_cond_shadow, gettext(\"Drag panel edges to resize them\"), BLOCK_TEXT_SIZE, GUI_WHITE);\n\n                    gui_spacer(gui, 0, config.ui_size * 0.25);\n\n                    gui_element_begin(gui);\n                        gui_set_direction(gui, DIRECTION_HORIZONTAL);\n                        gui_set_gap(gui, config.ui_size * 0.25);\n\n                        draw_panel_editor_button(gettext(\"Save\"), config.ui_size, (GuiColor) { 0x40, 0xff, 0x40, 0xff }, handle_panel_editor_save_button);\n                        draw_panel_editor_button(gettext(\"Done\"), config.ui_size, (GuiColor) { 0x80, 0x80, 0x80, 0xff }, handle_panel_editor_cancel_button);\n                    gui_element_end(gui);\n                gui_element_end(gui);\n            gui_element_end(gui);\n        }\n\n        draw_dropdown();\n    gui_end(gui);\n}\n\n// Adopted from Raylib 5.0\nbool svg_load(const char* file_name, size_t width, size_t height, Image* out_image) {\n    if (!file_name) return false;\n\n    // Bug in Raylib 5.0:\n    // LoadFileData() does not return null-terminated string which nsvgParse expects, so\n    // i am using nsvgParseFromFile() here instead\n    NSVGimage* svg = nsvgParseFromFile(file_name, \"px\", 96.0);\n    if (!svg) {\n        scrap_log(LOG_WARNING, \"[SVG] Could not load \\\"%s\\\"\", file_name);\n        return false;\n    }\n    unsigned char* image_data = malloc(width * height * 4);\n\n    float scale_width  = width  / svg->width,\n          scale_height = height / svg->height,\n          scale        = MAX(scale_width, scale_height);\n\n    int offset_x = 0,\n        offset_y = 0;\n\n    if (scale_height > scale_width) {\n        offset_y = (height - svg->height * scale) / 2;\n    } else {\n        offset_x = (width - svg->width * scale) / 2;\n    }\n\n    NSVGrasterizer *rast = nsvgCreateRasterizer();\n    nsvgRasterize(rast, svg, offset_x, offset_y, scale, image_data, width, height, width*4);\n\n    out_image->data    = image_data;\n    out_image->width   = width;\n    out_image->height  = height;\n    out_image->mipmaps = 1;\n    out_image->format  = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8;\n\n    nsvgDeleteRasterizer(rast);\n    nsvgDelete(svg);\n    return true;\n}\n\n// Draw order for render_border_control()\n//\n//           1\n//   +---------------+\n// 4 |               | 2\n//   +     +---------+\n//             3\n//\nstatic void render_border_control(GuiDrawCommand cmd) {\n    unsigned short border_w = cmd.data.border_width;\n    Color color = CONVERT_COLOR(cmd.color, Color);\n\n    /* 1 */ DrawRectangle(cmd.pos_x, cmd.pos_y, cmd.width, border_w, color);\n    /* 2 */ DrawRectangle(cmd.pos_x + cmd.width - border_w, cmd.pos_y, border_w, cmd.height, color);\n    /* 3 */ DrawRectangle(cmd.pos_x + BLOCK_CONTROL_INDENT - border_w, cmd.pos_y + cmd.height - border_w, cmd.width - BLOCK_CONTROL_INDENT, border_w, color);\n    /* 4 */ DrawRectangle(cmd.pos_x, cmd.pos_y, border_w, cmd.height, color);\n}\n\n// Draw order for render_border_control_body()\n//\n//   +     +\n// 1 |     | 2\n//   +     +\n//\nstatic void render_border_control_body(GuiDrawCommand cmd) {\n    unsigned short border_w = cmd.data.border_width;\n    Color color = CONVERT_COLOR(cmd.color, Color);\n\n    /* 1 */ DrawRectangle(cmd.pos_x, cmd.pos_y, border_w, cmd.height, color);\n    /* 2 */ DrawRectangle(cmd.pos_x + cmd.width - border_w, cmd.pos_y, border_w, cmd.height, color);\n}\n\n// Draw order for render_border_control_end()\n//\n//              1\n//   +     +---------+\n// 4 |               | 2\n//   +     +---------+\n//              3\n//\nstatic void render_border_control_end(GuiDrawCommand cmd) {\n    unsigned short border_w = cmd.data.border_width;\n    Color color = CONVERT_COLOR(cmd.color, Color);\n\n    /* 1 */ DrawRectangle(cmd.pos_x + BLOCK_CONTROL_INDENT - border_w, cmd.pos_y, cmd.width - BLOCK_CONTROL_INDENT, border_w, color);\n    /* 2 */ DrawRectangle(cmd.pos_x + cmd.width - border_w, cmd.pos_y, border_w, cmd.height, color);\n    /* 3 */ DrawRectangle(cmd.pos_x + BLOCK_CONTROL_INDENT - border_w, cmd.pos_y + cmd.height - border_w, cmd.width - BLOCK_CONTROL_INDENT, border_w, color);\n    /* 4 */ DrawRectangle(cmd.pos_x, cmd.pos_y, border_w, cmd.height, color);\n}\n\n// Draw order for render_border_end()\n//\n//              1\n//   +     +---------+\n// 4 |               | 2\n//   +---------------+\n//           3\nstatic void render_border_end(GuiDrawCommand cmd) {\n    unsigned short border_w = cmd.data.border_width;\n    Color color = CONVERT_COLOR(cmd.color, Color);\n\n    /* 1 */ DrawRectangle(cmd.pos_x + BLOCK_CONTROL_INDENT - border_w, cmd.pos_y, cmd.width - BLOCK_CONTROL_INDENT, border_w, color);\n    /* 2 */ DrawRectangle(cmd.pos_x + cmd.width - border_w, cmd.pos_y, border_w, cmd.height, color);\n    /* 3 */ DrawRectangle(cmd.pos_x, cmd.pos_y + cmd.height - border_w, cmd.width, border_w, color);\n    /* 4 */ DrawRectangle(cmd.pos_x, cmd.pos_y, border_w, cmd.height, color);\n}\n\n// Draw order for render_border_notched() and render_rect_notched()\n//\n//           1\n//   +--------------+ 2\n//   |               +\n// 5 |               | 3\n//   +---------------+\n//           4\nstatic void render_border_notched(GuiDrawCommand cmd) {\n    unsigned short border_w = cmd.data.border_width;\n    Color color = CONVERT_COLOR(cmd.color, Color);\n    int notch_size = config.ui_size / 4;\n\n    /* 1 */ DrawRectangle(cmd.pos_x, cmd.pos_y, cmd.width - notch_size, border_w, color);\n    /* 2 */ DrawRectanglePro((Rectangle) {\n        cmd.pos_x + cmd.width - notch_size,\n        cmd.pos_y,\n        sqrtf((notch_size * notch_size) * 2),\n        border_w,\n    }, (Vector2) {0}, 45.0, color);\n    /* 3 */ DrawRectangle(cmd.pos_x + cmd.width - border_w, cmd.pos_y + notch_size, border_w, cmd.height - notch_size, color);\n    /* 4 */ DrawRectangle(cmd.pos_x, cmd.pos_y + cmd.height - border_w, cmd.width, border_w, color);\n    /* 5 */ DrawRectangle(cmd.pos_x, cmd.pos_y, border_w, cmd.height, color);\n}\n\nstatic void render_rect_notched(GuiDrawCommand cmd) {\n    Color color = CONVERT_COLOR(cmd.color, Color);\n    int notch_size = config.ui_size / 4;\n\n    DrawRectangle(cmd.pos_x, cmd.pos_y, cmd.width - notch_size, cmd.height, color);\n    DrawRectangle(cmd.pos_x, cmd.pos_y + notch_size, cmd.width, cmd.height - notch_size, color);\n    DrawTriangle(\n        (Vector2) { cmd.pos_x + cmd.width - notch_size - 1, cmd.pos_y },\n        (Vector2) { cmd.pos_x + cmd.width - notch_size - 1, cmd.pos_y + notch_size },\n        (Vector2) { cmd.pos_x + cmd.width, cmd.pos_y + notch_size },\n        color\n    );\n}\n\nstatic void draw_text_slice(Font font, const char *text, float pos_x, float pos_y, unsigned int text_size, float font_size, Color color) {\n    if (font.texture.id == 0) return;\n\n    Vector2 pos = (Vector2) { pos_x, pos_y };\n    int codepoint, index;\n    float scale_factor = font_size / font.baseSize;\n\n    for (unsigned int i = 0; i < text_size;) {\n        if (!text[i]) break;\n        int next = 0;\n        codepoint = GetCodepointNext(&text[i], &next);\n        index = search_glyph(font, codepoint);\n        i += next;\n\n        if (codepoint != ' ') DrawTextCodepoint(font, codepoint, pos, font_size, color);\n\n        if (font.glyphs[index].advanceX != 0) {\n            pos.x += font.glyphs[index].advanceX * scale_factor;\n        } else {\n            pos.y += font.recs[index].width * scale_factor + font.glyphs[index].offsetX;\n        }\n    }\n}\n\nstatic void scrap_gui_render(void) {\n#ifdef DEBUG\n    bool show_bounds = IsKeyDown(KEY_F4);\n#endif\n    GuiDrawCommand command;\n    GUI_GET_COMMANDS(gui, command) {\n        Texture2D* image = command.data.image;\n\n        switch (command.type) {\n        case DRAWTYPE_UNKNOWN:\n            assert(false && \"Got unknown draw type\");\n            break;\n        case DRAWTYPE_BORDER:\n            switch (command.subtype) {\n            case BORDER_NORMAL:\n                DrawRectangleLinesEx(\n                    (Rectangle) { floor(command.pos_x), floor(command.pos_y), command.width, command.height },\n                    command.data.border_width,\n                    CONVERT_COLOR(command.color, Color)\n                );\n                break;\n            case BORDER_CONTROL:\n                render_border_control(command);\n                break;\n            case BORDER_CONTROL_BODY:\n                render_border_control_body(command);\n                break;\n            case BORDER_END:\n                render_border_end(command);\n                break;\n            case BORDER_CONTROL_END:\n                render_border_control_end(command);\n                break;\n            case BORDER_NOTCHED:\n                render_border_notched(command);\n                break;\n            default:\n                assert(false && \"Unhandled draw border type\");\n                break;\n            }\n            break;\n        case DRAWTYPE_RECT:\n            switch (command.subtype) {\n            case RECT_NORMAL:\n                DrawRectangle(command.pos_x, command.pos_y, command.width, command.height, CONVERT_COLOR(command.color, Color));\n                break;\n            case RECT_NOTCHED:\n                render_rect_notched(command);\n                break;\n            case RECT_TERMINAL:\n                term_resize(command.width, command.height);\n                draw_term(command.pos_x, command.pos_y);\n                break;\n            default:\n                assert(false && \"Unhandled draw rect type\");\n                break;\n            }\n            break;\n        case DRAWTYPE_TEXT:\n            draw_text_slice(\n                *(Font*)command.data.text.font,\n                command.data.text.text,\n                command.pos_x,\n                command.pos_y,\n                command.data.text.text_size,\n                command.height,\n                CONVERT_COLOR(command.color, Color)\n            );\n            break;\n        case DRAWTYPE_IMAGE:\n            switch (command.subtype) {\n            case IMAGE_NORMAL:\n                DrawTextureEx(\n                    *image,\n                    (Vector2) { command.pos_x + SHADOW_DISTANCE, command.pos_y + SHADOW_DISTANCE },\n                    0.0,\n                    (float)command.height / (float)image->height,\n                    (Color) { 0x00, 0x00, 0x00, 0x80 }\n                );\n                DrawTextureEx(\n                    *image,\n                    (Vector2) { command.pos_x, command.pos_y},\n                    0.0,\n                    (float)command.height / (float)image->height,\n                    CONVERT_COLOR(command.color, Color)\n                );\n                break;\n            case IMAGE_STRETCHED:\n                DrawTexturePro(\n                    *image,\n                    (Rectangle) { 0, 0, image->width, image->height },\n                    (Rectangle) { command.pos_x, command.pos_y, command.width, command.height },\n                    (Vector2) {0},\n                    0.0,\n                    CONVERT_COLOR(command.color, Color)\n                );\n                break;\n            }\n            break;\n        case DRAWTYPE_SCISSOR_SET:\n            BeginScissorMode(command.pos_x, command.pos_y, command.width, command.height);\n            break;\n        case DRAWTYPE_SCISSOR_RESET:\n            EndScissorMode();\n            break;\n        case DRAWTYPE_SHADER_BEGIN:\n            BeginShaderMode(*(Shader*)command.data.shader);\n            break;\n        case DRAWTYPE_SHADER_END:\n            EndShaderMode();\n            break;\n        default:\n            assert(false && \"Unimplemented command render\");\n            break;\n        }\n#ifdef DEBUG\n        if (show_bounds) DrawRectangleLinesEx((Rectangle) { command.pos_x, command.pos_y, command.width, command.height }, 1.0, (Color) { 0xff, 0x00, 0xff, 0x40 });\n#endif\n    }\n}\n\nstatic void print_debug(int* num, char* fmt, ...) {\n    va_list va;\n    va_start(va, fmt);\n    vsnprintf(editor.debug_buffer[(*num)++], DEBUG_BUFFER_LINE_SIZE, fmt, va);\n    va_end(va);\n}\n\nstatic void write_debug_buffer(void) {\n    int i = 0;\n    print_debug(&i, \"Scrap \" SCRAP_VERSION);\n    print_debug(&i, \"FPS: %d, Frame time: %.3f\", GetFPS(), GetFrameTime());\n\n#ifdef DEBUG\n    if (!editor.show_debug) return;\n    print_debug(&i, \"UI time: %.3f\", ui.ui_time);\n    print_debug(&i, \"Elements: %zu, Drawn: %zu\", gui->elements_count, gui->command_list.size);\n    print_debug(&i, \"Mem: %.3f/%.3f MiB\", (double)gui->arena->pos / (1024.0 * 1024.0), (double)gui->arena->reserve_size / (1024.0 * 1024.0));\n    print_debug(&i, \" \");\n\n    print_debug(&i, \"Button handler: %p\", ui.hover.button.handler);\n    print_debug(&i, \"Block: %p, Parent: %p, Parent Arg: %p\", ui.hover.editor.block, ui.hover.editor.block ? ui.hover.editor.block->parent : NULL, ui.hover.editor.parent_argument);\n    print_debug(&i, \"Argument: %p\", ui.hover.editor.argument);\n    print_debug(&i, \"BlockChain: %p\", ui.hover.editor.blockchain);\n    print_debug(&i, \"Select block: %p, arg: %p, chain: %p\", ui.hover.editor.select_block, ui.hover.editor.select_argument, ui.hover.editor.select_blockchain);\n    print_debug(&i, \"Select block pos: (%.3f, %.3f)\", ui.hover.editor.select_block_pos.x, ui.hover.editor.select_block_pos.y);\n    print_debug(&i, \"Select block bounds Pos: (%.3f, %.3f), Size: (%.3f, %.3f)\", ui.hover.panels.code_panel_bounds.x, ui.hover.panels.code_panel_bounds.y, ui.hover.panels.code_panel_bounds.width, ui.hover.panels.code_panel_bounds.height);\n    print_debug(&i, \"Category: %p\", ui.hover.category);\n    print_debug(&i, \"Mouse chains: %zu, Time: %.3f, Pos: (%d, %d), Click: (%d, %d)\", vector_size(editor.mouse_blockchains), ui.hover.time_at_last_pos, GetMouseX(), GetMouseY(), (int)ui.hover.mouse_click_pos.x, (int)ui.hover.mouse_click_pos.y);\n    print_debug(&i, \"Camera: (%.3f, %.3f), Click: (%.3f, %.3f)\", editor.camera_pos.x, editor.camera_pos.y, editor.camera_click_pos.x, editor.camera_click_pos.y);\n    print_debug(&i, \"Drag cancelled: %d\", ui.hover.drag_cancelled);\n    print_debug(&i, \"Editor: %d, Editing: %p, Blockdef: %p, input: %zu\", ui.hover.editor.part, ui.hover.editor.edit_blockdef, ui.hover.editor.blockdef, ui.hover.editor.blockdef_input);\n    print_debug(&i, \"Slider: %p, min: %d, max: %d\", ui.hover.hover_slider.value, ui.hover.hover_slider.min, ui.hover.hover_slider.max);\n    print_debug(&i, \"Input: %p, Select: %p, Pos: (%.3f, %.3f), ind: (%d, %d)\", ui.hover.input_info.input, ui.hover.select_input, ui.hover.input_info.rel_pos.x, ui.hover.input_info.rel_pos.y, ui.hover.select_input_cursor, ui.hover.select_input_mark);\n    print_debug(&i, \"Panel: %p, side: %d\", ui.hover.panels.panel, ui.hover.panels.panel_side);\n    print_debug(&i, \"Part: %d, Select: %d\", ui.dropdown.as.color_picker.hover_part, ui.dropdown.as.color_picker.select_part);\n    print_debug(&i, \"Anchor: %p, Ref: %p\", ui.dropdown.element, ui.dropdown.ref_object);\n#endif\n}\n\nvoid scrap_gui_process_render(void) {\n    if (!ui.render_surface_needs_redraw) return;\n    ui.render_surface_needs_redraw = false;\n\n    ClearBackground(GetColor(0x202020ff));\n    draw_dots();\n\n    for (int i = 0; i < DEBUG_BUFFER_LINES; i++) editor.debug_buffer[i][0] = 0;\n    write_debug_buffer();\n    scrap_gui_render();\n\n    if (vm.start_timeout == 0) {\n        term_restart();\n        clear_compile_error();\n#ifdef USE_INTERPRETER\n        vm.exec = exec_new(&vm.thread);\n#else\n        vm.exec = exec_new(&vm.thread, vm.start_mode);\n#endif\n        vm.exec.code = editor.code;\n        if (!thread_start(vm.exec.thread, &vm.exec)) {\n            actionbar_show(gettext(\"Start failed!\"));\n        } else {\n            actionbar_show(gettext(\"Started successfully!\"));\n        }\n    }\n}\n"
  },
  {
    "path": "src/save.c",
    "content": "// Scrap is a project that allows anyone to build software using simple, block based interface.\n//\n// Copyright (C) 2024-2026 Grisshink\n// \n// This software is provided 'as-is', without any express or implied\n// warranty.  In no event will the authors be held liable for any damages\n// arising from the use of this software.\n// \n// Permission is granted to anyone to use this software for any purpose,\n// including commercial applications, and to alter it and redistribute it\n// freely, subject to the following restrictions:\n// \n// 1. The origin of this software must not be misrepresented; you must not\n//    claim that you wrote the original software. If you use this software\n//    in a product, an acknowledgment in the product documentation would be\n//    appreciated but is not required.\n// 2. Altered source versions must be plainly marked as such, and must not be\n//    misrepresented as being the original software.\n// 3. This notice may not be removed or altered from any source distribution.\n\n#include \"scrap.h\"\n#include \"vec.h\"\n\n#include <string.h>\n#include <stdlib.h>\n#include <assert.h>\n#include <stdio.h>\n#include <libintl.h>\n#include <math.h>\n\n#include \"../external/cfgpath.h\"\n\n#define STR(v) #v\n\n#define SHARED_DIR_BUF_LEN 512\n#define LOCALE_DIR_BUF_LEN 768\n\n#define INT_TO_COLOR(v) ((BlockdefColor) { \\\n    ((v) >> 0 ) & 255, \\\n    ((v) >> 8 ) & 255, \\\n    ((v) >> 16) & 255, \\\n    ((v) >> 24) & 255, \\\n})\n\ntypedef struct {\n    void* ptr;\n    size_t size;\n    size_t capacity;\n} SaveData;\n\nconst int codepoint_regions[CODEPOINT_REGION_COUNT][2] = {\n    { 0x20, 0x7e }, // All printable ASCII chars\n    { 0x3bc, 0x3bc }, // Letter μ\n    { 0x400, 0x4ff }, // Cyrillic letters\n};\nint codepoint_start_ranges[CODEPOINT_REGION_COUNT] = {0};\n\nchar* language_list[5] = {\n    \"LanguageList|System\",\n    \"English [en]\",\n    \"Russian [ru]\",\n    \"Kazakh [kk]\",\n    \"Ukrainian [uk]\",\n};\n\nchar scrap_ident[] = \"SCRAP\";\nconst char** save_block_ids = NULL;\nBlockdef** save_blockdefs = NULL;\nstatic unsigned int ver = 0;\n\nint save_find_id(const char* id);\nvoid save_code(const char* file_path, ProjectConfig* config, BlockChain* code);\nBlockChain* load_code(const char* file_path, ProjectConfig* out_config);\nvoid save_block(SaveData* save, Block* block);\nbool load_block(SaveData* save, Block* block);\nvoid save_blockdef(SaveData* save, Blockdef* blockdef);\nBlockdef* load_blockdef(SaveData* save);\n\nconst char* language_to_code(Language lang) {\n    switch (lang) {\n        case LANG_SYSTEM: return \"system\";\n        case LANG_EN: return \"en\";\n        case LANG_RU: return \"ru\";\n        case LANG_KK: return \"kk\";\n        case LANG_UK: return \"uk\";\n    }\n    assert(false && \"Unreachable\");\n}\n\nLanguage code_to_language(const char* code) {\n    if (!strcmp(code, \"en\")) {\n        return LANG_EN;\n    } else if (!strcmp(code, \"ru\")) {\n        return LANG_RU;\n    } else if (!strcmp(code, \"kk\")) {\n        return LANG_KK;\n    } else if (!strcmp(code, \"uk\")) {\n        return LANG_UK;\n    } else {\n        return LANG_SYSTEM;\n    }\n}\n\nconst char* get_shared_dir_path(void) {\n    static char out_path[SHARED_DIR_BUF_LEN] = {0};\n    if (*out_path) return out_path;\n\n#ifndef _WIN32\n    snprintf(out_path, SHARED_DIR_BUF_LEN, \"%sdata\", GetApplicationDirectory());\n    if (DirectoryExists(out_path)) {\n        snprintf(out_path, SHARED_DIR_BUF_LEN, \"%s\", GetApplicationDirectory());\n        goto end;\n    }\n\n    snprintf(out_path, SHARED_DIR_BUF_LEN, \"%s../share/scrap/\", GetApplicationDirectory());\n    if (DirectoryExists(out_path)) goto end;\n\n    snprintf(out_path, SHARED_DIR_BUF_LEN, \"/usr/share/scrap/\");\n    if (DirectoryExists(out_path)) goto end;\n\n    snprintf(out_path, SHARED_DIR_BUF_LEN, \"/usr/local/share/scrap/\");\n    if (DirectoryExists(out_path)) goto end;\n#endif\n\n    snprintf(out_path, SHARED_DIR_BUF_LEN, \"%s\", GetApplicationDirectory());\n\nend:\n    scrap_log(LOG_INFO, \"Using \\\"%s\\\" as shared directory path\", out_path);\n    return out_path;\n}\n\nconst char* get_locale_path(void) {\n    static char out_path[LOCALE_DIR_BUF_LEN] = {0};\n    if (*out_path) return out_path;\n\n#ifndef _WIN32\n    snprintf(out_path, LOCALE_DIR_BUF_LEN, \"%slocale\", GetApplicationDirectory());\n    if (DirectoryExists(out_path)) goto end;\n#endif\n\n    const char* shared_path = get_shared_dir_path();\n    if (!strcmp(shared_path, GetApplicationDirectory())) {\n        snprintf(out_path, LOCALE_DIR_BUF_LEN, \"%slocale\", shared_path);\n    } else {\n        snprintf(out_path, LOCALE_DIR_BUF_LEN, \"%s../locale\", shared_path);\n    }\n\nend:\n    scrap_log(LOG_INFO, \"Using \\\"%s\\\" as locale directory path\", out_path);\n    return out_path;\n}\n\nconst char* into_shared_dir_path(const char* path) {\n    return TextFormat(\"%s%s\", get_shared_dir_path(), path);\n}\n\n// Returns the absolute path to the font, converting the relative path to a path inside the data directory\nconst char* get_font_path(char* font_path) {\n    return font_path[0] != '/' && font_path[1] != ':' ? into_shared_dir_path(font_path) : font_path;\n}\n\nvoid reload_fonts(void) {\n    int* codepoints = vector_create();\n    for (int i = 0; i < CODEPOINT_REGION_COUNT; i++) {\n        codepoint_start_ranges[i] = vector_size(codepoints);\n        for (int j = codepoint_regions[i][0]; j <= codepoint_regions[i][1]; j++) {\n            vector_add(&codepoints, j);\n        }\n    }\n\n    int codepoints_count = vector_size(codepoints);\n    if (IsFontValid(assets.fonts.font_cond)) UnloadFont(assets.fonts.font_cond);\n    assets.fonts.font_cond = LoadFontEx(get_font_path(config.font_path), config.ui_size, codepoints, codepoints_count);\n    SetTextureFilter(assets.fonts.font_cond.texture, TEXTURE_FILTER_BILINEAR);\n\n    if (IsFontValid(assets.fonts.font_cond_shadow)) UnloadFont(assets.fonts.font_cond_shadow);\n    assets.fonts.font_cond_shadow = LoadFontEx(get_font_path(config.font_path), BLOCK_TEXT_SIZE, codepoints, codepoints_count);\n    SetTextureFilter(assets.fonts.font_cond_shadow.texture, TEXTURE_FILTER_BILINEAR);\n\n    if (IsFontValid(assets.fonts.font_eb)) UnloadFont(assets.fonts.font_eb);\n    assets.fonts.font_eb = LoadFontEx(get_font_path(config.font_bold_path), config.ui_size * 0.8, codepoints, codepoints_count);\n    SetTextureFilter(assets.fonts.font_eb.texture, TEXTURE_FILTER_BILINEAR);\n\n    if (IsFontValid(assets.fonts.font_mono)) UnloadFont(assets.fonts.font_mono);\n    assets.fonts.font_mono = LoadFontEx(get_font_path(config.font_mono_path), config.ui_size, codepoints, codepoints_count);\n    SetTextureFilter(assets.fonts.font_mono.texture, TEXTURE_FILTER_BILINEAR);\n\n    vector_free(codepoints);\n\n    prerender_font_shadow(&assets.fonts.font_cond_shadow);\n}\n\nvoid vector_set_string(char** vec, char* str) {\n    vector_clear(*vec);\n    for (char* i = str; *i; i++) vector_add(vec, *i);\n    vector_add(vec, 0);\n}\n\nvoid config_new(Config* config) {\n    config->font_path = vector_create();\n    config->font_bold_path = vector_create();\n    config->font_mono_path = vector_create();\n}\n\nvoid config_free(Config* config) {\n    vector_free(config->font_path);\n    vector_free(config->font_bold_path);\n    vector_free(config->font_mono_path);\n}\n\nvoid config_copy(Config* dst, Config* src) {\n    dst->ui_size = src->ui_size;\n    dst->fps_limit = src->fps_limit;\n    dst->language = src->language;\n    dst->block_size_threshold = src->block_size_threshold;\n    dst->font_path = vector_copy(src->font_path);\n    dst->font_bold_path = vector_copy(src->font_bold_path);\n    dst->font_mono_path = vector_copy(src->font_mono_path);\n    dst->show_blockchain_previews = src->show_blockchain_previews;\n}\n\nvoid set_default_config(Config* config) {\n    config->ui_size = 32;\n    config->fps_limit = 60;\n    config->block_size_threshold = 1000;\n    config->language = LANG_SYSTEM;\n    vector_set_string(&config->font_path, DATA_PATH \"nk57-cond.otf\");\n    vector_set_string(&config->font_bold_path, DATA_PATH \"nk57-eb.otf\");\n    vector_set_string(&config->font_mono_path, DATA_PATH \"nk57.otf\");\n    config->show_blockchain_previews = true;\n}\n\nvoid project_config_new(ProjectConfig* config) {\n    config->executable_name = vector_create();\n    config->linker_name = vector_create();\n}\n\nvoid project_config_free(ProjectConfig* config) {\n    vector_free(config->executable_name);\n    vector_free(config->linker_name);\n}\n\nvoid project_config_set_default(ProjectConfig* config) {\n    vector_set_string(&config->executable_name, \"project\");\n    vector_set_string(&config->linker_name, \"ld\");\n}\n\nvoid apply_config(Config* dst, Config* src) {\n    dst->fps_limit = src->fps_limit; SetTargetFPS(dst->fps_limit);\n    dst->block_size_threshold = src->block_size_threshold;\n\n    editor.camera_pos.x *= (float)src->ui_size / dst->ui_size;\n    editor.camera_pos.y *= (float)src->ui_size / dst->ui_size;\n\n    dst->ui_size = src->ui_size;\n\n    vector_free(dst->font_path);\n    vector_free(dst->font_bold_path);\n    vector_free(dst->font_mono_path);\n\n    dst->font_path = vector_copy(src->font_path);\n    dst->font_bold_path = vector_copy(src->font_bold_path);\n    dst->font_mono_path = vector_copy(src->font_mono_path);\n\n    reload_fonts();\n\n    dst->show_blockchain_previews = src->show_blockchain_previews;\n}\n\nvoid save_panel_config(char* file_str, int* cursor, PanelTree* panel) {\n    switch (panel->type) {\n    case PANEL_NONE:\n        *cursor += sprintf(file_str + *cursor, \"PANEL_NONE \");\n        break;\n    case PANEL_CODE:\n        *cursor += sprintf(file_str + *cursor, \"PANEL_CODE \");\n        break;\n    case PANEL_TERM:\n        *cursor += sprintf(file_str + *cursor, \"PANEL_TERM \");\n        break;\n    case PANEL_BLOCK_PALETTE:\n        *cursor += sprintf(file_str + *cursor, \"PANEL_BLOCK_PALETTE \");\n        break;\n    case PANEL_SPLIT:\n        *cursor += sprintf(\n            file_str + *cursor,\n            \"PANEL_SPLIT %s %f \",\n            panel->direction == DIRECTION_HORIZONTAL ? \"DIRECTION_HORIZONTAL\" : \"DIRECTION_VERTICAL\",\n            panel->split_percent\n        );\n        save_panel_config(file_str, cursor, panel->left);\n        save_panel_config(file_str, cursor, panel->right);\n        break;\n    case PANEL_BLOCK_CATEGORIES:\n        *cursor += sprintf(file_str + *cursor, \"PANEL_BLOCK_CATEGORIES \");\n        break;\n    }\n}\n\nstatic char* read_panel_token(char** str, bool* is_eof) {\n    if (*is_eof) return NULL;\n    while (**str == ' ' || **str == '\\0') {\n        (*str)++;\n        if (**str == '\\0') return NULL;\n    }\n\n    char* out = *str;\n    while (**str != ' ' && **str != '\\0') (*str)++;\n    if (**str == '\\0') *is_eof = true;\n    **str = '\\0';\n\n    return out;\n}\n\nPanelTree* load_panel_config(char** config) {\n    bool is_eof = false;\n\n    char* name = read_panel_token(config, &is_eof);\n    if (!name) return NULL;\n\n    if (!strcmp(name, \"PANEL_SPLIT\")) {\n        char* direction = read_panel_token(config, &is_eof);\n        if (!direction) return NULL;\n        char* split_percent = read_panel_token(config, &is_eof);\n        if (!split_percent) return NULL;\n\n        GuiElementDirection dir;\n        if (!strcmp(direction, \"DIRECTION_HORIZONTAL\")) {\n            dir = DIRECTION_HORIZONTAL;\n        } else if (!strcmp(direction, \"DIRECTION_VERTICAL\")) {\n            dir = DIRECTION_VERTICAL;\n        } else {\n            return NULL;\n        }\n\n        float percent = CLAMP(atof(split_percent), 0.0, 1.0);\n\n        PanelTree* left = load_panel_config(config);\n        if (!left) return NULL;\n        PanelTree* right = load_panel_config(config);\n        if (!right) {\n            panel_delete(left);\n            return NULL;\n        }\n\n        PanelTree* panel = malloc(sizeof(PanelTree));\n        panel->type = PANEL_SPLIT;\n        panel->direction = dir;\n        panel->parent = NULL;\n        panel->split_percent = percent;\n        panel->left = left;\n        panel->right = right;\n\n        left->parent = panel;\n        right->parent = panel;\n\n        return panel;\n    } else if (!strcmp(name, \"PANEL_NONE\")) {\n        return panel_new(PANEL_NONE);\n    } else if (!strcmp(name, \"PANEL_CODE\")) {\n        return panel_new(PANEL_CODE);\n    } else if (!strcmp(name, \"PANEL_TERM\")) {\n        return panel_new(PANEL_TERM);\n    } else if (!strcmp(name, \"PANEL_SIDEBAR\")) { // Legacy panel name\n        return panel_new(PANEL_BLOCK_PALETTE);\n    } else if (!strcmp(name, \"PANEL_BLOCK_PALETTE\")) {\n        return panel_new(PANEL_BLOCK_PALETTE);\n    } else if (!strcmp(name, \"PANEL_BLOCK_CATEGORIES\")) {\n        return panel_new(PANEL_BLOCK_CATEGORIES);\n    }\n\n    scrap_log(LOG_ERROR, \"Unknown panel type: %s\", name);\n    return NULL;\n}\n\nvoid save_config(Config* config) {\n    char* file_str = malloc(sizeof(char) * 32768);\n    file_str[0] = 0;\n    int cursor = 0;\n\n    cursor += sprintf(file_str + cursor, \"LANGUAGE=%s\\n\", language_to_code(config->language));\n    cursor += sprintf(file_str + cursor, \"UI_SIZE=%u\\n\", config->ui_size);\n    cursor += sprintf(file_str + cursor, \"FPS_LIMIT=%u\\n\", config->fps_limit);\n    cursor += sprintf(file_str + cursor, \"BLOCK_SIZE_THRESHOLD=%u\\n\", config->block_size_threshold);\n    cursor += sprintf(file_str + cursor, \"FONT_PATH=%s\\n\", config->font_path);\n    cursor += sprintf(file_str + cursor, \"FONT_BOLD_PATH=%s\\n\", config->font_bold_path);\n    cursor += sprintf(file_str + cursor, \"FONT_MONO_PATH=%s\\n\", config->font_mono_path);\n    cursor += sprintf(file_str + cursor, \"SHOW_BLOCKCHAIN_PREVIEWS=%u\\n\", config->show_blockchain_previews);\n    for (size_t i = 0; i < vector_size(editor.tabs); i++) {\n        cursor += sprintf(file_str + cursor, \"CONFIG_TAB_%s=\", editor.tabs[i].name);\n        save_panel_config(file_str, &cursor, editor.tabs[i].root_panel);\n        cursor += sprintf(file_str + cursor, \"\\n\");\n    }\n\n    char config_path[MAX_PATH + 10];\n    get_user_config_folder(config_path, ARRLEN(config_path), CONFIG_FOLDER_NAME);\n    strcat(config_path, CONFIG_PATH);\n\n    SaveFileText(config_path, file_str);\n    free(file_str);\n}\n\nPanelTree* find_panel_in_all_tabs(PanelType panel_type) {\n    for (size_t i = 0; i < vector_size(editor.tabs); i++) {\n        PanelTree* panel = find_panel(editor.tabs[i].root_panel, panel_type);\n        if (panel) return panel;\n    }\n    return NULL;\n}\n\nvoid add_missing_panels(void) {\n    PanelTree* categories = find_panel_in_all_tabs(PANEL_BLOCK_CATEGORIES);\n    if (categories) return;\n\n    PanelTree* palette = find_panel_in_all_tabs(PANEL_BLOCK_PALETTE);\n    if (!palette) {\n        scrap_log(LOG_ERROR, \"Failed to insert missing panel PANEL_BLOCK_CATEGORIES: panel PANEL_BLOCK_PALETTE is missing\");\n        return;\n    }\n    panel_split(palette, SPLIT_SIDE_TOP, PANEL_BLOCK_CATEGORIES, 0.35);\n}\n\nvoid load_config(Config* config) {\n    delete_all_tabs();\n\n    char config_path[MAX_PATH + 10];\n    get_user_config_folder(config_path, ARRLEN(config_path), CONFIG_FOLDER_NAME);\n    strcat(config_path, CONFIG_PATH);\n\n    char* file = LoadFileText(config_path);\n    if (!file) {\n        init_panels();\n        editor.current_tab = 0;\n        return;\n    }\n    int cursor = 0;\n\n    bool has_lines = true;\n    while (has_lines) {\n        char* field = &file[cursor];\n        while(file[cursor] != '=' && file[cursor] != '\\n' && file[cursor] != '\\0') cursor++;\n        if (file[cursor] == '\\n') {\n            cursor++;\n            continue;\n        };\n        if (file[cursor] == '\\0') break;\n        file[cursor++] = '\\0';\n\n        char* value = &file[cursor];\n        int value_size = 0;\n        while(file[cursor] != '\\n' && file[cursor] != '\\0') {\n            cursor++;\n            value_size++;\n        }\n        (void) value_size;\n        if (file[cursor] == '\\0') has_lines = false;\n        file[cursor++] = '\\0';\n\n        if (!strcmp(field, \"UI_SIZE\")) {\n            int val = atoi(value);\n            config->ui_size = val ? val : config->ui_size;\n        } else if (!strcmp(field, \"FPS_LIMIT\")) {\n            int val = atoi(value);\n            config->fps_limit = val ? val : config->fps_limit;\n        } else if (!strcmp(field, \"BLOCK_SIZE_THRESHOLD\")) {\n            int val = atoi(value);\n            config->block_size_threshold = val ? val : config->block_size_threshold;\n        } else if (!strcmp(field, \"FONT_PATH\")) {\n            vector_set_string(&config->font_path, value);\n        } else if (!strcmp(field, \"FONT_BOLD_PATH\")) {\n            vector_set_string(&config->font_bold_path, value);\n        } else if (!strcmp(field, \"FONT_MONO_PATH\")) {\n            vector_set_string(&config->font_mono_path, value);\n        } else if (!strcmp(field, \"SHOW_BLOCKCHAIN_PREVIEWS\")) {\n            config->show_blockchain_previews = atoi(value) != 0;\n        } else if (!strncmp(field, \"CONFIG_TAB_\", sizeof(\"CONFIG_TAB_\") - 1)) {\n            char* panel_value = value;\n            tab_new(field + sizeof(\"CONFIG_TAB_\") - 1, load_panel_config(&panel_value));\n        } else if (!strcmp(field, \"LANGUAGE\")) {\n            Language lang = code_to_language(value);\n            config->language = lang;\n        } else {\n            scrap_log(LOG_WARNING, \"Unknown key: %s\", field);\n        }\n    }\n\n    add_missing_panels();\n    if (vector_size(editor.tabs) == 0) init_panels();\n    if (editor.current_tab >= (int)vector_size(editor.tabs)) editor.current_tab = vector_size(editor.tabs) - 1;\n\n    UnloadFileText(file);\n}\n\n#define save_add(save, data) save_add_item(save, &data, sizeof(data))\n\nvoid* save_read_item(SaveData* save, size_t data_size) {\n    if (save->size + data_size > save->capacity) {\n        scrap_log(LOG_ERROR, \"[LOAD] Unexpected EOF reading data\");\n        return NULL;\n    }\n    void* ptr = save->ptr + save->size;\n    save->size += data_size;\n    return ptr;\n}\n\nbool save_read_varint(SaveData* save, unsigned int* out) {\n    *out = 0;\n    int pos = 0;\n    unsigned char* chunk = NULL;\n    do {\n        chunk = save_read_item(save, sizeof(unsigned char));\n        if (!chunk) return false;\n        *out |= (*chunk & 0x7f) << pos;\n        pos += 7;\n    } while ((*chunk & 0x80) == 0);\n    return true;\n}\n\nvoid* save_read_array(SaveData* save, size_t data_size, unsigned int* array_len) {\n    if (!save_read_varint(save, array_len)) return NULL;\n    return save_read_item(save, data_size * *array_len);\n}\n\nvoid save_add_item(SaveData* save, const void* data, size_t data_size) {\n    if (save->size + data_size > save->capacity) {\n        save->capacity = save->capacity > 0 ? save->capacity * 2 : 256;\n        save->ptr = realloc(save->ptr, save->capacity);\n    }\n\n    memcpy(save->ptr + save->size, data, data_size);\n    save->size += data_size;\n}\n\nvoid save_add_varint(SaveData* save, unsigned int data) {\n    unsigned char varint = 0;\n    do {\n        varint = data & 0x7f;\n        data >>= 7;\n        varint |= (data == 0) << 7;\n        save_add(save, varint);\n    } while (data);\n}\n\nvoid save_add_array(SaveData* save, const void* array, int array_size, size_t data_size) {\n    save_add_varint(save, array_size);\n    for (int i = 0; i < array_size; i++) save_add_item(save, array + data_size * i, data_size);\n}\n\nvoid free_save(SaveData* save) {\n    free(save->ptr);\n    save->size = 0;\n    save->capacity = 0;\n}\n\nvoid save_blockdef_input(SaveData* save, Input* input) {\n    save_add_varint(save, input->type);\n    switch (input->type) {\n    case INPUT_TEXT_DISPLAY:\n        save_add_array(save, input->data.text, vector_size(input->data.text), sizeof(input->data.text[0]));\n        break;\n    case INPUT_ARGUMENT:\n        save_add_varint(save, input->data.arg.constr);\n        save_blockdef(save, input->data.arg.blockdef);\n        break;\n    default:\n        assert(false && \"Unimplemented input save\");\n        break;\n    }\n}\n\nvoid save_blockdef(SaveData* save, Blockdef* blockdef) {\n    save_add_array(save, blockdef->id, strlen(blockdef->id) + 1, sizeof(blockdef->id[0]));\n    save_add(save, blockdef->color);\n    save_add_varint(save, blockdef->type);\n\n    int input_count = vector_size(blockdef->inputs);\n    save_add_varint(save, input_count);\n    for (int i = 0; i < input_count; i++) save_blockdef_input(save, &blockdef->inputs[i]);\n}\n\nvoid save_block_arguments(SaveData* save, Argument* arg) {\n    save_add_varint(save, arg->input_id);\n    save_add_varint(save, arg->type);\n\n    int string_id;\n    static_assert(ARGUMENT_LAST == 5, \"Exhaustive argument type in save_block_arguments\");\n    switch (arg->type) {\n    case ARGUMENT_TEXT:\n    case ARGUMENT_CONST_STRING:\n        string_id = save_find_id(arg->data.text);\n        assert(string_id != -1);\n        save_add_varint(save, string_id);\n        break;\n    case ARGUMENT_BLOCK:\n        save_block(save, &arg->data.block);\n        break;\n    case ARGUMENT_BLOCKDEF:\n        string_id = save_find_id(arg->data.blockdef->id);\n        assert(string_id != -1);\n        save_add_varint(save, string_id);\n        break;\n    case ARGUMENT_COLOR:\n        save_add_varint(save, *(int*)&arg->data.color);\n        break;\n    default:\n        assert(false && \"Unimplemented argument save\");\n        break;\n    }\n}\n\nvoid save_block(SaveData* save, Block* block) {\n    assert(block->blockdef->id != NULL);\n\n    int arg_count = vector_size(block->arguments);\n\n    int string_id = save_find_id(block->blockdef->id);\n    assert(string_id != -1);\n    save_add_varint(save, string_id);\n    save_add_varint(save, arg_count);\n    for (int i = 0; i < arg_count; i++) save_block_arguments(save, &block->arguments[i]);\n}\n\nvoid save_blockchain(SaveData* save, BlockChain* chain) {\n    int blocks_count = vector_size(chain->blocks);\n\n    save_add(save, chain->x);\n    save_add(save, chain->y);\n    save_add_varint(save, blocks_count);\n    for (int i = 0; i < blocks_count; i++) save_block(save, &chain->blocks[i]);\n}\n\nvoid rename_blockdef(Blockdef* blockdef, int id) {\n    blockdef_set_id(blockdef, TextFormat(\"custom%d\", id));\n    int arg_id = 0;\n    for (size_t i = 0; i < vector_size(blockdef->inputs); i++) {\n        if (blockdef->inputs[i].type != INPUT_ARGUMENT) continue;\n        blockdef_set_id(blockdef->inputs[i].data.arg.blockdef, TextFormat(\"custom%d_arg%d\", id, arg_id++));\n    }\n}\n\nint save_find_id(const char* id) {\n    for (size_t i = 0; i < vector_size(save_block_ids); i++) {\n        if (!strcmp(save_block_ids[i], id)) return i;\n    }\n    return -1;\n}\n\nvoid save_add_id(const char* id) {\n    if (save_find_id(id) != -1) return;\n    vector_add(&save_block_ids, id);\n}\n\nvoid block_collect_ids(Block* block) {\n    save_add_id(block->blockdef->id);\n    for (size_t i = 0; i < vector_size(block->arguments); i++) {\n        static_assert(ARGUMENT_LAST == 5, \"Exhaustive argument type in block_collect_ids\");\n        switch (block->arguments[i].type) {\n        case ARGUMENT_TEXT:\n        case ARGUMENT_CONST_STRING:\n            save_add_id(block->arguments[i].data.text);\n            break;\n        case ARGUMENT_BLOCK:\n            block_collect_ids(&block->arguments[i].data.block);\n            break;\n        case ARGUMENT_BLOCKDEF:\n            save_add_id(block->arguments[i].data.blockdef->id);\n            break;\n        case ARGUMENT_COLOR:\n            break;\n        default:\n            assert(false && \"Unimplemented argument save id\");\n            break;\n        }\n    }\n}\n\nvoid collect_all_code_ids(BlockChain* code) {\n    for (size_t i = 0; i < vector_size(code); i++) {\n        BlockChain* chain = &code[i];\n        for (size_t j = 0; j < vector_size(chain->blocks); j++) {\n            block_collect_ids(&chain->blocks[j]);\n        }\n    }\n}\n\nvoid save_code(const char* file_path, ProjectConfig* config, BlockChain* code) {\n    (void) config;\n    SaveData save = {0};\n    ver = SCRAP_SAVE_VERSION;\n    int chains_count = vector_size(code);\n\n    Blockdef** blockdefs = vector_create();\n    save_block_ids = vector_create();\n\n    int id = 0;\n    for (int i = 0; i < chains_count; i++) {\n        Block* block = &code[i].blocks[0];\n        for (size_t j = 0; j < vector_size(block->arguments); j++) {\n            if (block->arguments[j].type != ARGUMENT_BLOCKDEF) continue;\n            rename_blockdef(block->arguments[j].data.blockdef, id++);\n            vector_add(&blockdefs, block->arguments[j].data.blockdef);\n        }\n    }\n\n    collect_all_code_ids(code);\n\n    save_add_varint(&save, ver);\n    save_add_array(&save, scrap_ident, ARRLEN(scrap_ident), sizeof(scrap_ident[0]));\n\n    save_add_varint(&save, vector_size(save_block_ids));\n    for (size_t i = 0; i < vector_size(save_block_ids); i++) {\n        save_add_array(&save, save_block_ids[i], strlen(save_block_ids[i]) + 1, sizeof(save_block_ids[i][0]));\n    }\n\n    save_add_varint(&save, id);\n    for (size_t i = 0; i < vector_size(blockdefs); i++) save_blockdef(&save, blockdefs[i]);\n\n    save_add_varint(&save, chains_count);\n    for (int i = 0; i < chains_count; i++) save_blockchain(&save, &code[i]);\n\n    SaveFileData(file_path, save.ptr, save.size);\n    scrap_log(LOG_INFO, \"%zu bytes written into %s\", save.size, file_path);\n\n    vector_free(save_block_ids);\n    vector_free(blockdefs);\n    free_save(&save);\n}\n\nBlockdef* find_blockdef(Blockdef** blockdefs, const char* id) {\n    for (size_t i = 0; i < vector_size(blockdefs); i++) {\n        if (!strcmp(id, blockdefs[i]->id)) return blockdefs[i];\n    }\n    return NULL;\n}\n\nbool load_blockdef_input(SaveData* save, Input* input) {\n    InputType type;\n    if (!save_read_varint(save, (unsigned int*)&type)) return false;\n    input->type = type;\n\n    unsigned int text_len;\n    InputArgumentConstraint constr;\n    char* text;\n\n    switch (input->type) {\n    case INPUT_TEXT_DISPLAY:\n        text = save_read_array(save, sizeof(char), &text_len);\n        if (!text) return false;\n        if (text[text_len - 1] != 0) return false;\n\n        input->data.text = vector_create();\n\n        for (char* str = text; *str; str++) vector_add(&input->data.text, *str);\n        vector_add(&input->data.text, 0);\n        break;\n    case INPUT_ARGUMENT:\n        if (!save_read_varint(save, (unsigned int*)&constr)) return false;\n\n        Blockdef* blockdef = load_blockdef(save);\n        if (!blockdef) return false;\n\n        input->data.arg.text = \"\";\n        input->data.arg.hint_text = gettext(\"any\");\n        input->data.arg.constr = constr;\n        input->data.arg.blockdef = blockdef;\n        input->data.arg.blockdef->ref_count++;\n        input->data.arg.blockdef->func = block_custom_arg;\n        vector_add(&save_blockdefs, input->data.arg.blockdef);\n        break;\n    default:\n        scrap_log(LOG_ERROR, \"[LOAD] Unimplemented input load\");\n        return false;\n        break;\n    }\n    return true;\n}\n\nBlockdef* load_blockdef(SaveData* save) {\n    unsigned int id_len;\n    char* id = save_read_array(save, sizeof(char), &id_len);\n    if (!id) return NULL;\n    if (id_len == 0) return false;\n    if (id[id_len - 1] != 0) return false;\n\n    BlockdefColor* color = save_read_item(save, sizeof(BlockdefColor));\n    if (!color) return NULL;\n\n    BlockdefType type;\n    if (!save_read_varint(save, (unsigned int*)&type)) return NULL;\n\n    if (ver < 3) {\n        // Deprecated: Arg ids are now not needed for blockdefs\n        int arg_id;\n        if (!save_read_varint(save, (unsigned int*)&arg_id)) return NULL;\n    }\n\n    unsigned int input_count;\n    if (!save_read_varint(save, &input_count)) return NULL;\n\n    Blockdef* blockdef = malloc(sizeof(Blockdef));\n    blockdef->id = strcpy(malloc(id_len * sizeof(char)), id);\n    blockdef->color = *color;\n    blockdef->type = type;\n    blockdef->ref_count = 0;\n    blockdef->inputs = vector_create();\n    blockdef->func = block_exec_custom;\n\n    for (unsigned int i = 0; i < input_count; i++) {\n        Input input;\n        if (!load_blockdef_input(save, &input)) {\n            blockdef_free(blockdef);\n            return NULL;\n        }\n        vector_add(&blockdef->inputs, input);\n    }\n\n    return blockdef;\n}\n\nbool load_block_argument(SaveData* save, Argument* arg) {\n    unsigned int input_id;\n    if (!save_read_varint(save, &input_id)) return false;\n\n    ArgumentType arg_type;\n    if (!save_read_varint(save, (unsigned int*)&arg_type)) return false;\n\n    arg->type = arg_type;\n    arg->input_id = input_id;\n\n    unsigned int text_id;\n    Block block;\n    unsigned int blockdef_id;\n    unsigned int color_int;\n\n    static_assert(ARGUMENT_LAST == 5, \"Exhaustive argument type in load_block_argument\");\n    switch (arg_type) {\n    case ARGUMENT_TEXT:\n    case ARGUMENT_CONST_STRING:\n        if (!save_read_varint(save, &text_id)) return false;\n\n        arg->data.text = vector_create();\n        for (char* str = (char*)save_block_ids[text_id]; *str; str++) vector_add(&arg->data.text, *str);\n        vector_add(&arg->data.text, 0);\n        break;\n    case ARGUMENT_BLOCK:\n        if (!load_block(save, &block)) return false;\n\n        arg->data.block = block;\n        break;\n    case ARGUMENT_BLOCKDEF:\n        if (!save_read_varint(save, &blockdef_id)) return false;\n        if (blockdef_id >= vector_size(save_block_ids)) {\n            scrap_log(LOG_ERROR, \"[LOAD] Out of bounds read of save_block_id at %u\", blockdef_id);\n            return false;\n        }\n\n        Blockdef* blockdef = find_blockdef(save_blockdefs, save_block_ids[blockdef_id]);\n        if (!blockdef) return false;\n\n        arg->data.blockdef = blockdef;\n        arg->data.blockdef->ref_count++;\n        break;\n    case ARGUMENT_COLOR:\n        if (!save_read_varint(save, &color_int)) return false;\n        arg->data.color = INT_TO_COLOR(color_int);\n        break;\n    default:\n        scrap_log(LOG_ERROR, \"[LOAD] Unimplemented argument load\");\n        return false;\n    }\n    return true;\n}\n\nbool load_block(SaveData* save, Block* block) {\n    unsigned int block_id;\n    if (!save_read_varint(save, &block_id)) return false;\n\n    bool unknown_blockdef = false;\n    Blockdef* blockdef = NULL;\n    blockdef = find_blockdef(save_blockdefs, save_block_ids[block_id]);\n    if (!blockdef) {\n        blockdef = find_blockdef(vm.blockdefs, save_block_ids[block_id]);\n        if (!blockdef) {\n            scrap_log(LOG_WARNING, \"[LOAD] No blockdef matched id: %s\", save_block_ids[block_id]);\n            unknown_blockdef = true;\n\n            blockdef = blockdef_new(save_block_ids[block_id], BLOCKTYPE_NORMAL, (BlockdefColor) { 0x66, 0x66, 0x66, 0xff }, NULL);\n            blockdef_add_text(blockdef, TextFormat(gettext(\"UNKNOWN %s\"), save_block_ids[block_id]));\n        }\n    }\n\n    unsigned int arg_count;\n    if (!save_read_varint(save, &arg_count)) return false;\n\n    block->blockdef = blockdef;\n    block->arguments = vector_create();\n    block->parent = NULL;\n    blockdef->ref_count++;\n\n    for (unsigned int i = 0; i < arg_count; i++) {\n        Argument arg;\n        if (!load_block_argument(save, &arg)) {\n            block_free(block);\n            return false;\n        }\n        vector_add(&block->arguments, arg);\n        if (unknown_blockdef) {\n            blockdef_add_argument(blockdef, \"\", \"\", BLOCKCONSTR_UNLIMITED);\n        }\n    }\n\n    return true;\n}\n\nbool load_blockchain(SaveData* save, BlockChain* chain) {\n    int pos_x, pos_y;\n    if (ver == 1) {\n        struct { float x; float y; }* pos = save_read_item(save, sizeof(struct { float x; float y; }));\n        if (!pos) return false;\n        pos_x = pos->x;\n        pos_y = pos->y;\n    } else {\n        int* pos = save_read_item(save, sizeof(int));\n        if (!pos) return false;\n        pos_x = *pos;\n\n        pos = save_read_item(save, sizeof(int));\n        if (!pos) return false;\n        pos_y = *pos;\n    }\n\n    unsigned int blocks_count;\n    if (!save_read_varint(save, &blocks_count)) return false;\n\n    *chain = blockchain_new();\n    chain->x = pos_x;\n    chain->y = pos_y;\n\n    for (unsigned int i = 0; i < blocks_count; i++) {\n        Block block;\n        if (!load_block(save, &block)) {\n            blockchain_free(chain);\n            return false;\n        }\n        blockchain_add_block(chain, block);\n        block_update_all_links(&chain->blocks[vector_size(chain->blocks) - 1]);\n    }\n\n    return true;\n}\n\nBlockChain* load_code(const char* file_path, ProjectConfig* out_config) {\n    ProjectConfig config;\n    project_config_new(&config);\n    project_config_set_default(&config);\n\n    BlockChain* code = vector_create();\n    save_blockdefs = vector_create();\n    save_block_ids = vector_create();\n\n    int save_size;\n    void* file_data = LoadFileData(file_path, &save_size);\n    if (!file_data) goto load_fail;\n    scrap_log(LOG_INFO, \"%zu bytes read from %s\", save_size, file_path);\n\n    SaveData save;\n    save.ptr = file_data;\n    save.size = 0;\n    save.capacity = save_size;\n\n    if (!save_read_varint(&save, &ver)) goto load_fail;\n    if (ver < 1 || ver > SCRAP_SAVE_VERSION) {\n        scrap_log(LOG_ERROR, \"[LOAD] Unsupported version %d. Current scrap build expects save versions from 1 to \" STR(SCRAP_SAVE_VERSION), ver);\n        goto load_fail;\n    }\n\n    unsigned int ident_len;\n    char* ident = save_read_array(&save, sizeof(char), &ident_len);\n    if (!ident) goto load_fail;\n    if (ident_len == 0) goto load_fail;\n\n    if (ident[ident_len - 1] != 0 || ident_len != sizeof(scrap_ident) || strncmp(ident, scrap_ident, sizeof(scrap_ident))) {\n        scrap_log(LOG_ERROR, \"[LOAD] Not valid scrap save\");\n        goto load_fail;\n    }\n\n    unsigned int block_ids_len;\n    if (!save_read_varint(&save, &block_ids_len)) goto load_fail;\n    for (unsigned int i = 0; i < block_ids_len; i++) {\n        unsigned int id_len;\n        char* id = save_read_array(&save, sizeof(char), &id_len);\n        if (!id) goto load_fail;\n        if (id_len == 0) goto load_fail;\n        if (id[id_len - 1] != 0) goto load_fail;\n\n        vector_add(&save_block_ids, id);\n    }\n\n    unsigned int custom_block_len;\n    if (!save_read_varint(&save, &custom_block_len)) goto load_fail;\n    for (unsigned int i = 0; i < custom_block_len; i++) {\n        Blockdef* blockdef = load_blockdef(&save);\n        if (!blockdef) goto load_fail;\n        vector_add(&save_blockdefs, blockdef);\n    }\n\n    unsigned int code_len;\n    if (!save_read_varint(&save, &code_len)) goto load_fail;\n\n    for (unsigned int i = 0; i < code_len; i++) {\n        BlockChain chain;\n        if (!load_blockchain(&save, &chain)) goto load_fail;\n        vector_add(&code, chain);\n    }\n\n    unsigned int len;\n    char* executable_name = save_read_array(&save, sizeof(char), &len);\n    if (executable_name) vector_set_string(&config.executable_name, executable_name);\n\n    char* linker_name = save_read_array(&save, sizeof(char), &len);\n    if (linker_name) vector_set_string(&config.linker_name, linker_name);\n\n    *out_config = config;\n\n    UnloadFileData(file_data);\n    vector_free(save_block_ids);\n    vector_free(save_blockdefs);\n    return code;\n\nload_fail:\n    if (file_data) UnloadFileData(file_data);\n    for (size_t i = 0; i < vector_size(code); i++) blockchain_free(&code[i]);\n    project_config_free(&config);\n    vector_free(code);\n    vector_free(save_block_ids);\n    vector_free(save_blockdefs);\n    return NULL;\n}\n"
  },
  {
    "path": "src/scrap-runtime.c",
    "content": "// Scrap is a project that allows anyone to build software using simple, block based interface.\n//\n// Copyright (C) 2024-2026 Grisshink\n// \n// This software is provided 'as-is', without any express or implied\n// warranty.  In no event will the authors be held liable for any damages\n// arising from the use of this software.\n// \n// Permission is granted to anyone to use this software for any purpose,\n// including commercial applications, and to alter it and redistribute it\n// freely, subject to the following restrictions:\n// \n// 1. The origin of this software must not be misrepresented; you must not\n//    claim that you wrote the original software. If you use this software\n//    in a product, an acknowledgment in the product documentation would be\n//    appreciated but is not required.\n// 2. Altered source versions must be plainly marked as such, and must not be\n//    misrepresented as being the original software.\n// 3. This notice may not be removed or altered from any source distribution.\n\n#include \"gc.h\"\n#include \"config.h\"\n\n#ifdef _WIN32\n#include <windows.h>\n#endif\n\nGc* gc;\n\nvoid llvm_main(void);\n\nint main(void) {\n#ifdef _WIN32\n    SetConsoleOutputCP(65001);\n#endif\n    Gc _gc = gc_new(MIN_MEMORY_LIMIT, MAX_MEMORY_LIMIT);\n    gc = &_gc;\n    llvm_main();\n    gc_free(gc);\n    return 0;\n}\n"
  },
  {
    "path": "src/scrap.c",
    "content": "// Scrap is a project that allows anyone to build software using simple, block based interface.\n//\n// Copyright (C) 2024-2026 Grisshink\n// \n// This software is provided 'as-is', without any express or implied\n// warranty.  In no event will the authors be held liable for any damages\n// arising from the use of this software.\n// \n// Permission is granted to anyone to use this software for any purpose,\n// including commercial applications, and to alter it and redistribute it\n// freely, subject to the following restrictions:\n// \n// 1. The origin of this software must not be misrepresented; you must not\n//    claim that you wrote the original software. If you use this software\n//    in a product, an acknowledgment in the product documentation would be\n//    appreciated but is not required.\n// 2. Altered source versions must be plainly marked as such, and must not be\n//    misrepresented as being the original software.\n// 3. This notice may not be removed or altered from any source distribution.\n\n#define SCRVM_IMPLEMENTATION\n#include \"term.h\"\n#include \"scrap.h\"\n#include \"vec.h\"\n#include \"util.h\"\n#include \"rlgl.h\"\n\n#include <math.h>\n#include <libintl.h>\n#include <locale.h>\n#include <assert.h>\n#include <string.h>\n#include <stdio.h>\n\n#define KiB(n) ((size_t)(n) << 10)\n#define MiB(n) ((size_t)(n) << 20)\n#define GiB(n) ((size_t)(n) << 30)\n\n// Global Variables\n\nConfig config;\nProjectConfig project_config;\n\nAssets assets;\n\nVm vm;\nGui* gui = NULL;\nGui gui_val;\n\nEditor editor;\nUI ui;\n\nconst char* line_shader_vertex =\n    \"#version 330\\n\"\n    \"in vec3 vertexPosition;\\n\"\n    \"in vec4 vertexColor;\\n\"\n    \"out vec2 fragCoord;\\n\"\n    \"out vec4 fragColor;\\n\"\n    \"uniform mat4 mvp;\\n\"\n    \"void main() {\\n\"\n    \"    vec4 pos = mvp * vec4(vertexPosition, 1.0);\\n\"\n    \"    fragCoord = pos.xy;\\n\"\n    \"    fragColor = vertexColor;\\n\"\n    \"    gl_Position = pos;\\n\"\n    \"}\";\n\nconst char* line_shader_fragment =\n    \"#version 330\\n\"\n    \"in vec2 fragCoord;\\n\"\n    \"in vec4 fragColor;\\n\"\n    \"out vec4 finalColor;\\n\"\n    \"uniform float time = 0.0;\\n\"\n    \"void main() {\\n\"\n    \"    vec2 coord = (fragCoord + 1.0) * 0.5;\\n\"\n    \"    coord.y = 1.0 - coord.y;\\n\"\n    \"    float pos = time * 4.0 - 1.0;\\n\"\n    \"    float diff = clamp(1.0 - abs(coord.x + coord.y - pos), 0.0, 1.0);\\n\"\n    \"    finalColor = vec4(fragColor.xyz, pow(diff, 2.0));\\n\"\n    \"}\";\n\nconst char* gradient_shader_vertex =\n    \"#version 330\\n\"\n    \"in vec3 vertexPosition;\\n\"\n    \"in vec2 vertexTexCoord;\\n\"\n    \"in vec4 vertexColor;\\n\"\n    \"out vec2 fragCoord;\\n\"\n    \"out vec4 fragColor;\\n\"\n    \"uniform mat4 mvp;\\n\"\n    \"void main() {\\n\"\n    \"    vec4 pos = mvp * vec4(vertexPosition, 1.0);\\n\"\n    \"    fragCoord = vec2(vertexTexCoord.x, 1.0 - vertexTexCoord.y);\\n\"\n    \"    fragColor = vertexColor;\\n\"\n    \"    gl_Position = pos;\\n\"\n    \"}\";\n\nconst char* gradient_shader_fragment =\n    \"#version 330\\n\"\n    \"in vec2 fragCoord;\\n\"\n    \"in vec4 fragColor;\\n\"\n    \"out vec4 finalColor;\\n\"\n    \"void main() {\\n\"\n    \"    vec4 left = mix(vec4(0.0, 0.0, 0.0, 1.0), vec4(1.0, 1.0, 1.0, 1.0), fragCoord.y);\\n\"\n    \"    vec4 right = mix(vec4(0.0, 0.0, 0.0, 1.0), fragColor, fragCoord.y);\\n\"\n    \"    finalColor = mix(left, right, fragCoord.x);\\n\"\n    \"}\";\n\nImage setup(void) {\n    SetExitKey(KEY_NULL);\n\n    ui.render_surface = LoadRenderTexture(GetScreenWidth(), GetScreenHeight());\n    SetTextureWrap(ui.render_surface.texture, TEXTURE_WRAP_MIRROR_REPEAT);\n\n    assets.textures.dropdown = LoadTexture(into_shared_dir_path(DATA_PATH \"drop.png\"));\n    SetTextureFilter(assets.textures.dropdown, TEXTURE_FILTER_BILINEAR);\n\n    assets.textures.spectrum = LoadTexture(into_shared_dir_path(DATA_PATH \"spectrum.png\"));\n    SetTextureFilter(assets.textures.spectrum, TEXTURE_FILTER_BILINEAR);\n\n    Image window_icon;\n    svg_load(into_shared_dir_path(DATA_PATH \"logo.svg\"), config.ui_size, config.ui_size, &window_icon);\n    assets.textures.icon_logo = LoadTextureFromImage(window_icon);\n    SetTextureFilter(assets.textures.icon_logo, TEXTURE_FILTER_BILINEAR);\n\n    void* image_load_paths[] = {\n        &assets.textures.button_add_arg,     \"add_arg.svg\",\n        &assets.textures.button_add_text,    \"add_text.svg\",\n        &assets.textures.button_arrow_left,  \"arrow_left.svg\",\n        &assets.textures.button_arrow_right, \"arrow_right.svg\",\n        &assets.textures.button_build,       \"build.svg\",\n        &assets.textures.button_close,       \"close.svg\",\n        &assets.textures.button_del_arg,     \"del_arg.svg\",\n        &assets.textures.button_edit,        \"edit.svg\",\n        &assets.textures.button_run,         \"run.svg\",\n        &assets.textures.button_stop,        \"stop.svg\",\n        &assets.textures.icon_about,         \"about.svg\",\n        &assets.textures.icon_file,          \"file.svg\",\n        &assets.textures.icon_folder,        \"folder.svg\",\n        &assets.textures.icon_list,          \"list.svg\",\n        &assets.textures.icon_pi,            \"pi_symbol.svg\",\n        &assets.textures.icon_settings,      \"settings.svg\",\n        &assets.textures.icon_special,       \"special.svg\",\n        &assets.textures.icon_term,          \"term.svg\",\n        &assets.textures.icon_variable,      \"variable_symbol.svg\",\n        &assets.textures.icon_warning,       \"warning.svg\",\n        NULL,\n    };\n\n    for (int i = 0; image_load_paths[i]; i += 2) {\n        Image svg_img;\n        if (!svg_load(TextFormat(\"%s\" DATA_PATH \"%s\", get_shared_dir_path(), image_load_paths[i + 1]), config.ui_size, config.ui_size, &svg_img)) {\n            continue;\n        }\n\n        Texture2D* texture = image_load_paths[i];\n        *texture = LoadTextureFromImage(svg_img);\n        SetTextureFilter(*texture, TEXTURE_FILTER_BILINEAR);\n        UnloadImage(svg_img);\n    }\n\n    reload_fonts();\n\n    assets.line_shader = LoadShaderFromMemory(line_shader_vertex, line_shader_fragment);\n    ui.shader_time_loc = GetShaderLocation(assets.line_shader, \"time\");\n\n    assets.gradient_shader = LoadShaderFromMemory(gradient_shader_vertex, gradient_shader_fragment);\n\n    strncpy(editor.project_name, EDITOR_DEFAULT_PROJECT_NAME, 1024);\n    editor.blockchain_select_counter = -1;\n\n    ui.render_surface_needs_redraw = true;\n\n    vm = vm_new();\n    register_blocks(&vm);\n\n    editor.show_debug = true;\n    editor.mouse_blockchains = vector_create();\n    editor.code = vector_create();\n\n    editor.search_list = vector_create();\n    editor.search_list_search = vector_create();\n    vector_add(&editor.search_list_search, 0);\n    update_search();\n\n    term_init(term_measure_text, &assets.fonts.font_mono, config.ui_size * 0.6);\n\n    // This fixes incorrect texture coordinates in gradient shader\n    Texture2D texture = {\n        .id = rlGetTextureIdDefault(),\n        .width = 1,\n        .height = 1,\n        .mipmaps = 1,\n        .format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8,\n    };\n    SetShapesTexture(texture, (Rectangle){ 0.0f, 0.0f, 1.0f, 1.0f });\n\n    gui_val = gui_new(GiB(1));\n    gui = &gui_val;\n    gui_set_measure_text_func(gui, scrap_gui_measure_text);\n    gui_set_measure_image_func(gui, scrap_gui_measure_image);\n    gui_update_window_size(gui, GetScreenWidth(), GetScreenHeight());\n    init_gui_window();\n\n    editor.blockchain_render_layer_widths = vector_create();\n\n    return window_icon;\n}\n\nvoid cleanup(void) {\n    term_free();\n\n    for (size_t i = 0; i < vector_size(editor.mouse_blockchains); i++) blockchain_free(&editor.mouse_blockchains[i]);\n    vector_free(editor.mouse_blockchains);\n    for (vec_size_t i = 0; i < vector_size(editor.code); i++) blockchain_free(&editor.code[i]);\n    vector_free(editor.code);\n    vm_free(&vm);\n\n    vector_free(editor.blockchain_render_layer_widths);\n    gui_free(gui);\n\n    delete_all_tabs();\n    vector_free(editor.tabs);\n\n    vector_free(editor.search_list_search);\n    vector_free(editor.search_list);\n    vector_free(vm.compile_error);\n\n    unregister_categories();\n\n    project_config_free(&project_config);\n    config_free(&config);\n    config_free(&window_config);\n\n    CloseWindow();\n}\n\n// Main function: Initializes configurations, sets up window, processes input, renders GUI, and cleans up resources on exit\nint main(void) {\n    SetTraceLogCallback(scrap_log_va);\n    config_new(&config);\n    config_new(&window_config);\n    project_config_new(&project_config);\n    project_config_set_default(&project_config);\n\n    editor.tabs = vector_create();\n    set_default_config(&config);\n    load_config(&config);\n\n    if (config.language != LANG_SYSTEM) {\n#ifdef _WIN32\n        scrap_set_env(\"LANG\", language_to_code(config.language));\n#else\n        scrap_set_env(\"LANGUAGE\", language_to_code(config.language));\n#endif\n    }\n    setlocale(LC_MESSAGES, \"\");\n    textdomain(\"scrap\");\n    bindtextdomain(\"scrap\", get_locale_path());\n#ifdef _WIN32\n    bind_textdomain_codeset(\"scrap\", \"UTF-8\");\n#endif\n\n    SetConfigFlags(FLAG_MSAA_4X_HINT | FLAG_WINDOW_RESIZABLE);\n    InitWindow(800, 600, \"Scrap\");\n    //SetWindowState(FLAG_VSYNC_HINT);\n    SetTargetFPS(config.fps_limit);\n\n    Image icon = setup();\n    SetWindowIcon(icon);\n    // SetWindowIcon() copies the icon so we can safely unload it\n    UnloadImage(icon);\n\n    ui.scrap_running = true;\n    while (ui.scrap_running) {\n        if (WindowShouldClose()) {\n            if (!editor.project_modified) {\n                ui.scrap_running = false;\n                break;\n            } else {\n                gui_window_show(draw_save_confirmation_window);\n            }\n        }\n\n        vm_handle_running_thread();\n\n        scrap_gui_process_ui();\n\n        BeginTextureMode(ui.render_surface);\n            scrap_gui_process_render();\n        EndTextureMode();\n\n        BeginDrawing();\n            Texture2D texture = ui.render_surface.texture;\n\n            DrawTexturePro(\n                texture,\n                (Rectangle) {\n#ifdef ARABIC_MODE\n                    // Flip texture upside down and also mirror it ;)\n                    texture.width, texture.height,\n#else\n                    // Render everything just below the texture. This texture has wrapping mode set to TEXTURE_WRAP_MIRROR_REPEAT,\n                    // so this will have the effect of flipping the texture upside down\n                    0, texture.height,\n#endif\n                    texture.width, texture.height,\n                },\n                (Rectangle) {\n                    0, 0,\n                    texture.width, texture.height,\n                },\n                (Vector2) {0}, // Origin at 0,0\n                0.0, // No rotation\n                WHITE // No tint\n            );\n        EndDrawing();\n    }\n\n    cleanup();\n    return 0;\n}\n"
  },
  {
    "path": "src/scrap.h",
    "content": "// Scrap is a project that allows anyone to build software using simple, block based interface.\n//\n// Copyright (C) 2024-2026 Grisshink\n// \n// This software is provided 'as-is', without any express or implied\n// warranty.  In no event will the authors be held liable for any damages\n// arising from the use of this software.\n// \n// Permission is granted to anyone to use this software for any purpose,\n// including commercial applications, and to alter it and redistribute it\n// freely, subject to the following restrictions:\n// \n// 1. The origin of this software must not be misrepresented; you must not\n//    claim that you wrote the original software. If you use this software\n//    in a product, an acknowledgment in the product documentation would be\n//    appreciated but is not required.\n// 2. Altered source versions must be plainly marked as such, and must not be\n//    misrepresented as being the original software.\n// 3. This notice may not be removed or altered from any source distribution.\n\n#ifndef SCRAP_H\n#define SCRAP_H\n\n#include <time.h>\n\n#include \"ast.h\"\n#include \"raylib.h\"\n#include \"config.h\"\n#include \"scrap_gui.h\"\n#include \"util.h\"\n#include \"term.h\"\n\ntypedef struct Vm Vm;\n\n#ifdef USE_INTERPRETER\n#include \"interpreter.h\"\n#else\n#include \"compiler.h\"\n#endif\n\ntypedef struct PanelTree PanelTree;\ntypedef struct BlockCategory BlockCategory;\n\ntypedef enum {\n    LANG_SYSTEM = 0,\n    LANG_EN,\n    LANG_RU,\n    LANG_KK,\n    LANG_UK,\n} Language;\n\ntypedef struct {\n    int ui_size;\n    int fps_limit;\n    int block_size_threshold;\n    Language language;\n    char* font_path;\n    char* font_bold_path;\n    char* font_mono_path;\n    bool show_blockchain_previews;\n} Config;\n\ntypedef struct {\n    char* executable_name;\n    char* linker_name;\n} ProjectConfig;\n\ntypedef bool (*ButtonClickHandler)(void);\n\ntypedef enum {\n    SPLIT_SIDE_NONE = 0,\n    SPLIT_SIDE_TOP,\n    SPLIT_SIDE_BOTTOM,\n    SPLIT_SIDE_LEFT,\n    SPLIT_SIDE_RIGHT,\n} SplitSide;\n\ntypedef struct {\n    SplitSide side;\n} SplitPreview;\n\ntypedef enum {\n    PANEL_NONE = 0,\n    PANEL_SPLIT,\n    PANEL_BLOCK_PALETTE,\n    PANEL_CODE,\n    PANEL_TERM,\n    PANEL_BLOCK_CATEGORIES,\n} PanelType;\n\nstruct PanelTree {\n    PanelType type;\n    GuiElementDirection direction;\n    struct PanelTree* parent;\n    float split_percent;\n    struct PanelTree* left; // Becomes top when direction is DIRECTION_VERTICAL\n    struct PanelTree* right; // Becomes bottom when direction is DIRECTION_VERTICAL\n};\n\ntypedef enum {\n    EDITOR_NONE,\n    EDITOR_BLOCKDEF,\n    EDITOR_EDIT,\n    EDITOR_ADD_ARG,\n    EDITOR_DEL_ARG,\n    EDITOR_ADD_TEXT,\n} EditorHoverPart;\n\ntypedef struct {\n    BlockChain* prev_blockchain;\n    BlockChain* blockchain;\n\n    Block* prev_block;\n    Block* block;\n    Argument* argument;\n    Argument* prev_argument;\n    Argument* parent_argument;\n\n    Block* select_block;\n    Argument* select_argument;\n    BlockChain* select_blockchain;\n    Vector2 select_block_pos;\n    bool select_valid;\n\n    EditorHoverPart part;\n    Blockdef* edit_blockdef;\n    Block* edit_block;\n    Blockdef* prev_blockdef;\n    Blockdef* blockdef;\n    size_t blockdef_input;\n} EditorHoverInfo;\n\ntypedef enum {\n    DROPDOWN_LIST,\n    DROPDOWN_COLOR_PICKER,\n} DropdownType;\n\ntypedef struct {\n    float hue, saturation, value;\n} HSV;\n\ntypedef enum {\n    COLOR_PICKER_NONE = 0,\n    COLOR_PICKER_SV,\n    COLOR_PICKER_SPECTRUM,\n} ColorPickerPartType;\n\ntypedef struct {\n    char** data;\n    int len;\n\n    int select_ind;\n    int scroll;\n} ListDropdown;\n\ntypedef struct {\n    ColorPickerPartType hover_part, select_part;\n    HSV color;\n    Color* edit_color;\n    char color_hex[10];\n} ColorPickerDropdown;\n\ntypedef struct {\n    bool shown;\n    void* ref_object;\n    GuiElement* element;\n    ButtonClickHandler handler;\n\n    DropdownType type;\n    union {\n        ListDropdown list;\n        ColorPickerDropdown color_picker;\n    } as;\n} Dropdown;\n\ntypedef struct {\n    int* value;\n    char** list;\n    int list_len;\n} DropdownData;\n\ntypedef struct {\n    int min;\n    int max;\n    int* value;\n    char value_str[16]; // Used to store value as string as gui does not store strings\n} SliderHoverInfo;\n\ntypedef struct {\n    char** input;\n    Vector2 rel_pos;\n    Font* font;\n    float font_size;\n} InputHoverInfo;\n\ntypedef enum {\n    CATEGORY_ITEM_CHAIN,\n    CATEGORY_ITEM_LABEL,\n} BlockCategoryItemType;\n\ntypedef struct {\n    BlockCategoryItemType type;\n    union {\n        BlockChain chain;\n        struct {\n            const char* text;\n            Color color;\n        } label;\n    } data;\n} BlockCategoryItem;\n\nstruct BlockCategory {\n    const char* name;\n    Color color;\n    BlockCategoryItem* items;\n\n    BlockCategory* next;\n    BlockCategory* prev;\n};\n\ntypedef struct {\n    PanelTree* panel;\n    Rectangle panel_size;\n\n    PanelTree* drag_panel;\n    Rectangle drag_panel_size;\n\n    PanelType mouse_panel;\n    PanelTree* prev_panel;\n    SplitSide panel_side;\n\n    Rectangle code_panel_bounds;\n} PanelHoverInfo;\n\ntypedef struct {\n    ButtonClickHandler handler;\n    void* data;\n} ButtonHoverInfo;\n\ntypedef struct {\n    bool is_panel_edit_mode;\n    bool drag_cancelled;\n\n    BlockCategory* category;\n\n    InputHoverInfo input_info;\n    char** select_input;\n    int select_input_cursor;\n    int select_input_mark;\n\n    Vector2 mouse_click_pos;\n    float time_at_last_pos;\n\n    EditorHoverInfo editor;\n    PanelHoverInfo panels;\n    ButtonHoverInfo button;\n\n    SliderHoverInfo hover_slider;\n    SliderHoverInfo dragged_slider;\n    int slider_last_val;\n\n    DropdownData settings_dropdown_data;\n    int* select_settings_dropdown_value;\n} HoverInfo;\n\ntypedef struct {\n    Texture2D button_add_arg;\n    Texture2D button_add_text;\n    Texture2D button_arrow_left;\n    Texture2D button_arrow_right;\n    Texture2D button_build;\n    Texture2D button_close;\n    Texture2D button_del_arg;\n    Texture2D button_edit;\n    Texture2D button_run;\n    Texture2D button_stop;\n    Texture2D dropdown;\n    Texture2D icon_about;\n    Texture2D icon_file;\n    Texture2D icon_folder;\n    Texture2D icon_list;\n    Texture2D icon_logo;\n    Texture2D icon_pi;\n    Texture2D icon_settings;\n    Texture2D icon_special;\n    Texture2D icon_term;\n    Texture2D icon_variable;\n    Texture2D icon_warning;\n    Texture2D spectrum;\n} TextureList;\n\ntypedef struct {\n    Font font_cond;\n    Font font_cond_shadow;\n    Font font_eb;\n    Font font_mono;\n} Fonts;\n\ntypedef struct {\n    Fonts fonts;\n    TextureList textures;\n    Shader line_shader;\n    Shader gradient_shader;\n} Assets;\n\ntypedef struct {\n    char* name;\n    PanelTree* root_panel;\n} Tab;\n\ntypedef struct {\n    float show_time;\n    char text[ACTION_BAR_MAX_SIZE];\n} ActionBar;\n\ntypedef struct {\n    Vector2 min_pos;\n    Vector2 max_pos;\n} BlockCode;\n\ntypedef struct {\n    int scroll_amount;\n    BlockCategory* current_category;\n    BlockCategory* categories_start;\n    BlockCategory* categories_end;\n} BlockPalette;\n\ntypedef void (*WindowGuiRenderFunc)(void);\n\ntypedef struct {\n    char project_name[1024];\n    bool project_modified;\n\n    Tab* tabs;\n\n    Vector2 camera_pos;\n    Vector2 camera_click_pos;\n\n    BlockChain* code;\n    BlockPalette palette;\n\n    char* search_list_search;\n    Blockdef** search_list;\n    Vector2 search_list_pos;\n\n    ActionBar actionbar;\n    BlockChain* mouse_blockchains;\n    SplitPreview split_preview;\n    int* blockchain_render_layer_widths;\n    int current_tab;\n    int blockchain_select_counter;\n\n    char debug_buffer[DEBUG_BUFFER_LINES][DEBUG_BUFFER_LINE_SIZE];\n    bool show_debug;\n} Editor;\n\ntypedef struct {\n    bool scrap_running;\n\n    RenderTexture2D render_surface;\n    bool render_surface_needs_redraw;\n    bool render_surface_redraw_next;\n\n    int shader_time_loc;\n    float shader_time;\n\n    HoverInfo hover;\n\n    int categories_scroll;\n    int search_list_scroll;\n\n    Dropdown dropdown;\n\n#ifdef DEBUG\n    double ui_time;\n#endif\n} UI;\n\nstruct Vm {\n    Blockdef** blockdefs;\n    size_t end_blockdef;\n\n    Thread thread;\n\n    Exec exec;\n    char** compile_error;\n    Block* compile_error_block;\n    BlockChain* compile_error_blockchain;\n\n    int start_timeout; // = -1;\n#ifndef USE_INTERPRETER\n    CompilerMode start_mode; // = COMPILER_MODE_JIT;\n#endif\n};\n\nextern Config config;\nextern Config window_config;\nextern ProjectConfig project_config;\n\nextern Assets assets;\n\nextern Vm vm;\nextern Gui* gui;\n\nextern Editor editor;\nextern UI ui;\n\nextern char* language_list[5];\nextern const int codepoint_regions[CODEPOINT_REGION_COUNT][2];\nextern int codepoint_start_ranges[CODEPOINT_REGION_COUNT];\n\n// scrap.c\n// Nothing...\n\n// render.c\nvoid actionbar_show(const char* text);\nvoid process_render(void);\nvoid prerender_font_shadow(Font* font);\nvoid scrap_gui_process_render(void);\nvoid scrap_gui_process(void);\nbool svg_load(const char* file_name, size_t width, size_t height, Image* out_image);\nconst char* sgettext(const char* msgid);\nvoid input_on_hover(GuiElement* el);\nvoid draw_input_text(Font* font, char** input, const char* hint, unsigned short font_size, GuiColor font_color);\n\n// input.c\nvoid scrap_gui_process_ui(void);\n\nPanelTree* find_panel(PanelTree* root, PanelType panel);\nvoid update_search(void);\nvoid show_list_dropdown(char** list, int list_len, void* ref_object, ButtonClickHandler handler);\n\nGuiMeasurement scrap_gui_measure_image(void* image, unsigned short size);\nGuiMeasurement scrap_gui_measure_text(void* font, const char* text, unsigned int text_size, unsigned short font_size);\nTermVec term_measure_text(void* font, const char* text, unsigned int text_size, unsigned short font_size);\nint search_glyph(Font font, int codepoint);\n\nsize_t tab_new(char* name, PanelTree* root_panel);\nvoid delete_all_tabs(void);\n\nvoid init_panels(void);\nPanelTree* panel_new(PanelType type);\nvoid panel_split(PanelTree* panel, SplitSide side, PanelType new_panel_type, float split_percent);\nvoid panel_delete(PanelTree* panel);\n\nbool save_project(void);\n\nbool handle_file_button_click(void);\nbool handle_settings_button_click(void);\nbool handle_about_button_click(void);\nbool handle_run_button_click(void);\nbool handle_build_button_click(void);\nbool handle_stop_button_click(void);\nbool handle_code_tab_click(void);\nbool handle_output_tab_click(void);\nbool handle_dropdown_close(void);\nbool handle_file_menu_click(void);\nbool handle_editor_close_button(void);\nbool handle_editor_edit_button(void);\nbool handle_editor_add_arg_button(void);\nbool handle_editor_add_text_button(void);\nbool handle_editor_del_arg_button(void);\nbool handle_panel_editor_save_button(void);\nbool handle_panel_editor_cancel_button(void);\nbool handle_tab_button(void);\nbool handle_add_tab_button(void);\nbool handle_category_click(void);\nbool handle_jump_to_block_button_click(void);\nbool handle_error_window_close_button_click(void);\nbool handle_color_picker_click(void);\nbool handle_editor_color_button(void);\n\n// save.c\nvoid config_new(Config* config);\nvoid config_free(Config* config);\nvoid set_default_config(Config* config);\nvoid apply_config(Config* dst, Config* src);\nvoid save_config(Config* config);\nvoid load_config(Config* config);\nvoid config_copy(Config* dst, Config* src);\n\nvoid save_code(const char* file_path, ProjectConfig* config, BlockChain* code);\nBlockChain* load_code(const char* file_path, ProjectConfig* out_config);\n\nvoid project_config_new(ProjectConfig* config);\nvoid project_config_free(ProjectConfig* config);\nvoid project_config_set_default(ProjectConfig* config);\n\nconst char* language_to_code(Language lang);\nLanguage code_to_language(const char* code);\n\nconst char* get_locale_path(void);\nconst char* get_shared_dir_path(void);\nconst char* into_shared_dir_path(const char* path);\n\nvoid reload_fonts(void);\n\n// window.c\nvoid init_gui_window(void);\nvoid gui_window_show(WindowGuiRenderFunc func);\nvoid gui_window_hide(void);\nvoid gui_window_hide_immediate(void);\nWindowGuiRenderFunc gui_window_get_render_func(void);\nbool gui_window_is_shown(void);\nvoid handle_window(void);\nvoid draw_window(void);\n\nvoid draw_settings_window(void);\nvoid draw_project_settings_window(void);\nvoid draw_about_window(void);\nvoid draw_save_confirmation_window(void);\n\n// blocks.c\nvoid register_blocks(Vm* vm);\n\n#ifdef USE_INTERPRETER\nbool block_custom_arg(Exec* exec, Block* block, int argc, AnyValue* argv, AnyValue* return_val, ControlState control_state);\nbool block_exec_custom(Exec* exec, Block* block, int argc, AnyValue* argv, AnyValue* return_val, ControlState control_state);\n#else\nbool block_custom_arg(Exec* exec, Block* block, int argc, FuncArg* argv, FuncArg* return_val, ControlState control_state);\nbool block_exec_custom(Exec* exec, Block* block, int argc, FuncArg* argv, FuncArg* return_val, ControlState control_state);\n#endif\n\n// vm.c\nBlockCategory block_category_new(const char* name, Color color);\nBlockCategory* block_category_register(BlockCategory category);\nvoid block_category_add_blockdef(BlockCategory* category, Blockdef* blockdef);\nvoid block_category_add_label(BlockCategory* category, const char* label, Color color);\n\nsize_t blockdef_register(Vm* vm, Blockdef* blockdef);\nvoid blockdef_unregister(Vm* vm, size_t block_id);\n\nvoid unregister_categories(void);\n\nVm vm_new(void);\nvoid vm_free(Vm* vm);\n#ifdef USE_INTERPRETER\nbool vm_start(void);\n#else\nbool vm_start(CompilerMode mode);\n#endif\nbool vm_stop(void);\nvoid vm_handle_running_thread(void);\n\nvoid clear_compile_error(void);\nBlock block_new_ms(Blockdef* blockdef);\n\n// platform.c\nvoid scrap_set_env(const char* name, const char* value);\n\n#ifndef USE_INTERPRETER\nbool spawn_process(char* command, char* error, size_t error_len);\n#endif\n\n#endif // SCRAP_H\n"
  },
  {
    "path": "src/scrap_gui.c",
    "content": "// Scrap is a project that allows anyone to build software using simple, block based interface.\n//\n// Copyright (C) 2024-2026 Grisshink\n// \n// This software is provided 'as-is', without any express or implied\n// warranty.  In no event will the authors be held liable for any damages\n// arising from the use of this software.\n// \n// Permission is granted to anyone to use this software for any purpose,\n// including commercial applications, and to alter it and redistribute it\n// freely, subject to the following restrictions:\n// \n// 1. The origin of this software must not be misrepresented; you must not\n//    claim that you wrote the original software. If you use this software\n//    in a product, an acknowledgment in the product documentation would be\n//    appreciated but is not required.\n// 2. Altered source versions must be plainly marked as such, and must not be\n//    misrepresented as being the original software.\n// 3. This notice may not be removed or altered from any source distribution.\n\n#include \"scrap_gui.h\"\n\n#include <assert.h>\n#include <stdbool.h>\n#include <string.h>\n#include <stdio.h>\n#include <stdarg.h>\n\n#define KiB(n) ((size_t)(n) << 10)\n\n#define GUI_ALIGN_UP_POW2(n, p) (((size_t)(n) + ((size_t)(p) - 1)) & (~((size_t)(p) - 1)))\n#define GUI_ARENA_BASE_POS (sizeof(GuiMemArena))\n#define GUI_ARENA_ALIGN (sizeof(void*))\n\n#define MAX(x, y) ((x) > (y) ? (x) : (y))\n#define MIN(x, y) ((x) < (y) ? (x) : (y))\n#define CLAMP(x, min, max) (MIN(MAX(min, x), max))\n\n#define SIZING_X(el) (GuiElementSizing)(el->sizing & 0x0f)\n#define SIZING_Y(el) (GuiElementSizing)((el->sizing >> 4) & 0x0f)\n#define ANCHOR_X(el) (GuiAlignmentType)(el->anchor & 0x0f)\n#define ANCHOR_Y(el) (GuiAlignmentType)((el->anchor >> 4) & 0x0f)\n#define NEED_RESIZE(el) ((el->flags >> 5) & 1)\n#define SCISSOR(el) ((el->flags >> 4) & 1)\n#define FLOATING(el) ((el->flags >> 3) & 1)\n#define ALIGN_X(el) (GuiAlignmentType)((el->flags >> 6) & 3)\n#define ALIGN_Y(el) (GuiAlignmentType)((el->flags >> 1) & 3)\n#define DIRECTION(el) (GuiElementDirection)(el->flags & 1)\n\n#define SET_SIZING_X(el, size) (el->sizing = (el->sizing & 0xf0) | size)\n#define SET_SIZING_Y(el, size) (el->sizing = (el->sizing & 0x0f) | (size << 4))\n#define SET_NEED_RESIZE(el, resize) (el->flags = (el->flags & ~(1 << 5)) | ((resize & 1) << 5))\n#define SET_SCISSOR(el, scissor) (el->flags = (el->flags & ~(1 << 4)) | ((scissor & 1) << 4))\n#define SET_FLOATING(el, floating) (el->flags = (el->flags & ~(1 << 3)) | ((floating & 1) << 3))\n#define SET_ALIGN_X(el, ali) (el->flags = (el->flags & 0x3f) | (ali << 6))\n#define SET_ALIGN_Y(el, ali) (el->flags = (el->flags & 0xf9) | (ali << 1))\n#define SET_DIRECTION(el, dir) (el->flags = (el->flags & 0xfe) | dir)\n#define SET_ANCHOR(el, x, y) (el->anchor = x | (y << 4))\n\nstatic void gui_render(Gui* gui, GuiElement* el);\nstatic void flush_command_batch(Gui* gui);\n\nstatic unsigned long gui_plat_get_pagesize(void);\nstatic void* gui_plat_mem_reserve(size_t size);\nstatic bool gui_plat_mem_commit(void* ptr, size_t size);\nstatic bool gui_plat_mem_release(void* ptr, size_t size);\n\nstatic bool inside_scissor(GuiDrawBounds rect, GuiBounds scissor) {\n    return (rect.x + rect.w > scissor.x) && (rect.x < scissor.x + scissor.w) &&\n           (rect.y + rect.h > scissor.y) && (rect.y < scissor.y + scissor.h);\n}\n\nstatic bool mouse_inside(Gui* gui, GuiBounds rect) {\n    return ((gui->mouse_x > rect.x) && (gui->mouse_x < rect.x + rect.w) &&\n            (gui->mouse_y > rect.y) && (gui->mouse_y < rect.y + rect.h));\n}\n\nGui gui_new(size_t arena_size) {\n    return (Gui) {\n        .arena = gui_arena_new(MAX(arena_size, KiB(512)), KiB(512)),\n    };\n}\n\nvoid gui_free(Gui* gui) {\n    gui_arena_free(gui->arena);\n}\n\nvoid gui_begin(Gui* gui) {\n    gui_arena_clear(gui->arena);\n    gui->command_list_iter = 0;\n    gui->command_list_last_batch = 0;\n    gui->elements_count = 0;\n    gui->current_element = NULL;\n    gui->command_list = (GuiDrawCommandList) {0};\n    gui->aux_command_list = (GuiDrawCommandList) {0};\n    gui->scissor_stack = (GuiScissorStack) {0};\n\n    gui->root_element = gui_element_begin(gui);\n    gui_set_fixed(gui, gui->win_w, gui->win_h);\n}\n\nvoid gui_end(Gui* gui) {\n    gui_element_end(gui);\n    gui_render(gui, gui->root_element);\n    flush_command_batch(gui);\n}\n\nvoid gui_set_measure_text_func(Gui* gui, GuiMeasureTextSliceFunc measure_text) {\n    gui->measure_text = measure_text;\n}\n\nvoid gui_set_measure_image_func(Gui* gui, GuiMeasureImageFunc measure_image) {\n    gui->measure_image = measure_image;\n}\n\nvoid gui_update_mouse_pos(Gui* gui, short mouse_x, short mouse_y) {\n    gui->mouse_x = mouse_x;\n    gui->mouse_y = mouse_y;\n}\n\nvoid gui_update_mouse_scroll(Gui* gui, int mouse_scroll) {\n    gui->mouse_scroll = mouse_scroll;\n}\n\nvoid gui_update_window_size(Gui* gui, unsigned short win_w, unsigned short win_h) {\n    gui->win_w = win_w;\n    gui->win_h = win_h;\n}\n\nstatic bool is_command_lesseq(GuiDrawCommand* left, GuiDrawCommand* right) {\n    if (left->type != right->type) return left->type <= right->type;\n    \n    switch (left->type) {\n    case DRAWTYPE_IMAGE:\n        return left->data.image <= right->data.image;\n    case DRAWTYPE_TEXT:\n        return left->data.text.font <= right->data.text.font;\n    default:\n        return true; // Equal\n    }\n}\n\nstatic void merge(GuiDrawCommandList list, GuiDrawCommandList aux_list, int start, int middle, int end) {\n    int left_pos  = start,\n        right_pos = middle;\n\n    for (int i = start; i < end; i++) {\n        if (left_pos < middle && (right_pos >= end || is_command_lesseq(&list.items[left_pos], &list.items[right_pos]))) {\n            aux_list.items[i] = list.items[left_pos++];\n        } else {\n            aux_list.items[i] = list.items[right_pos++];\n        }\n    }\n}\n\nstatic void split_and_merge_commands(GuiDrawCommandList list, GuiDrawCommandList aux_list, int start, int end) {\n    if (end - start <= 1) return;\n    int middle = (start + end) / 2;\n\n    split_and_merge_commands(aux_list, list, start,  middle);\n    split_and_merge_commands(aux_list, list, middle, end);\n    merge(list, aux_list, start, middle, end);\n}\n\nstatic void sort_commands(GuiDrawCommandList list, GuiDrawCommandList aux_list, int start, int end) {\n    for (int i = start; i < end; i++) aux_list.items[i] = list.items[i];\n    split_and_merge_commands(aux_list, list, start, end);\n}\n\nstatic void flush_command_batch(Gui* gui) {\n    if (gui->command_list_last_batch >= gui->command_list.size) return;\n    // Skip sorting unwanted elements\n    while (gui->command_list.items[gui->command_list_last_batch].type > 4) gui->command_list_last_batch++;\n\n    sort_commands(gui->command_list, gui->aux_command_list, gui->command_list_last_batch, gui->command_list.size);\n    gui->command_list_last_batch = gui->command_list.size;\n}\n\n\nstatic void new_draw_command(Gui* gui, GuiDrawBounds bounds, GuiDrawType draw_type, unsigned char draw_subtype, GuiDrawData data, GuiColor color) {\n    GuiDrawCommand command = {\n        .pos_x   = bounds.x,\n        .pos_y   = bounds.y,\n        .width   = bounds.w,\n        .height  = bounds.h,\n        .type    = draw_type,\n        .data    = data,\n        .color   = color,\n        .subtype = draw_subtype,\n    };\n    gui_arena_append(gui->arena, gui->command_list, command);\n    gui_arena_append(gui->arena, gui->aux_command_list, command);\n}\n\nstatic GuiBounds scissor_rect(GuiBounds rect, GuiBounds scissor) {\n    if (rect.x < scissor.x) {\n        rect.w = MAX(0, rect.w - (scissor.x - rect.x));\n        rect.x = scissor.x;\n    }\n    if (rect.y < scissor.y) {\n        rect.h = MAX(0, rect.h - (scissor.y - rect.y));\n        rect.y = scissor.y;\n    }\n    if (rect.x + rect.w > scissor.x + scissor.w) {\n        rect.w = MAX(0, rect.w - ((rect.x + rect.w) - (scissor.x + scissor.w)));\n    }\n    if (rect.y + rect.h > scissor.y + scissor.h) {\n        rect.h = MAX(0, rect.h - ((rect.y + rect.h) - (scissor.y + scissor.h)));\n    }\n\n    return rect;\n}\n\nstatic void gui_get_anchor_pos(GuiElement* el, float* anchor_x, float* anchor_y) {\n    switch (ANCHOR_X(el)) {\n    case ALIGN_CENTER: *anchor_x = (float)el->w * el->scaling / 2; break;\n    case ALIGN_RIGHT:  *anchor_x = (float)el->w * el->scaling; break;\n    default: break;\n    }\n    switch (ANCHOR_Y(el)) {\n    case ALIGN_CENTER: *anchor_y = (float)el->h * el->scaling / 2; break;\n    case ALIGN_RIGHT:  *anchor_y = (float)el->h * el->scaling; break;\n    default: break;\n    }\n}\n\nstatic void gui_render(Gui* gui, GuiElement* el) {\n    GuiBounds prev_scissor = gui->scissor_stack.size > 0 ? gui->scissor_stack.items[gui->scissor_stack.size - 1] : (GuiBounds) { 0, 0, gui->win_w, gui->win_h };\n    bool hover = false;\n\n    float parent_pos_x   = 0,\n          parent_pos_y   = 0,\n          parent_scaling = 1.0;\n    if (el->parent) {\n        if (el->parent_anchor) {\n            parent_pos_x = el->parent_anchor->abs_x;\n            parent_pos_y = el->parent_anchor->abs_y;\n        } else {\n            parent_pos_x = el->parent->abs_x;\n            parent_pos_y = el->parent->abs_y;\n        }\n        parent_scaling = el->parent->scaling;\n    }\n\n    float anchor_x = 0,\n          anchor_y = 0;\n    gui_get_anchor_pos(el, &anchor_x, &anchor_y);\n\n    el->abs_x = ((float)el->x - anchor_x) * parent_scaling + parent_pos_x;\n    el->abs_y = ((float)el->y - anchor_y) * parent_scaling + parent_pos_y;\n\n    if (mouse_inside(gui, scissor_rect((GuiBounds) {\n        (el->x - anchor_x) * parent_scaling + parent_pos_x,\n        (el->y - anchor_y) * parent_scaling + parent_pos_y,\n        el->w * el->scaling,\n        el->h * el->scaling }, prev_scissor)))\n    {\n        if (el->handle_hover) el->handle_hover(el);\n        hover = true;\n    }\n    if (el->handle_pre_render) el->handle_pre_render(el);\n\n    GuiDrawBounds el_bounds = (GuiDrawBounds) {\n        parent_pos_x + ((float)el->x - anchor_x) * parent_scaling,\n        parent_pos_y + ((float)el->y - anchor_y) * parent_scaling,\n        (float)el->w * el->scaling,\n        (float)el->h * el->scaling,\n    };\n\n    if (SCISSOR(el) || FLOATING(el) || el->shader) flush_command_batch(gui);\n\n    GuiBounds scissor = prev_scissor;\n    if (SCISSOR(el)) {\n        scissor = scissor_rect(prev_scissor, (GuiBounds) { el_bounds.x, el_bounds.y, el_bounds.w, el_bounds.h });\n        if (scissor.w > 0 && scissor.h > 0) {\n            new_draw_command(gui, (GuiDrawBounds) { scissor.x, scissor.y, scissor.w, scissor.h }, DRAWTYPE_SCISSOR_SET, GUI_SUBTYPE_DEFAULT, (GuiDrawData) {0}, (GuiColor) {0});\n        }\n        gui_arena_append(gui->arena, gui->scissor_stack, scissor);\n    }\n    if (el->shader) new_draw_command(gui, el_bounds, DRAWTYPE_SHADER_BEGIN, GUI_SUBTYPE_DEFAULT, (GuiDrawData) { .shader = el->shader }, (GuiColor) {0});\n\n    if (el->draw_type != DRAWTYPE_UNKNOWN && inside_scissor(el_bounds, scissor)) {\n        new_draw_command(gui, el_bounds, el->draw_type, el->draw_subtype, el->data, el->color);\n    }\n\n    if (el->shader) {\n        flush_command_batch(gui);\n        new_draw_command(gui, el_bounds, DRAWTYPE_SHADER_END, GUI_SUBTYPE_DEFAULT, (GuiDrawData) { .shader = el->shader }, (GuiColor) {0});\n    }\n\n    for (GuiElement* iter = el->child_elements_begin; iter; iter = iter->next) {\n        gui_render(gui, iter);\n    }\n\n    if (el->scroll_value) {\n        int el_size = DIRECTION(el) == DIRECTION_HORIZONTAL ? el->w : el->h;\n        int content_size = DIRECTION(el) == DIRECTION_HORIZONTAL ? el->cursor_x : el->cursor_y;\n        int max = content_size - el_size;\n\n        if (max > 0) {\n            flush_command_batch(gui);\n            GuiDrawCommand command;\n            command.type = DRAWTYPE_RECT;\n            command.subtype = GUI_SUBTYPE_DEFAULT;\n            command.color = (GuiColor) { 0xff, 0xff, 0xff, 0x80 };\n\n            float scroll_size = (float)el_size / ((float)content_size / (float)el_size);\n            float scroll_pos = (-(float)*el->scroll_value / (float)max) * ((float)el_size - scroll_size);\n            if (DIRECTION(el) == DIRECTION_HORIZONTAL) {\n                command.width = scroll_size * el->scaling;\n                command.height = 5 * el->scaling;\n                command.pos_x = el_bounds.x + scroll_pos * parent_scaling;\n                command.pos_y = el_bounds.y + el_bounds.h - command.height;\n            } else {\n                command.width = 5 * el->scaling;\n                command.height = scroll_size * el->scaling;\n                command.pos_x = el_bounds.x + el_bounds.w - command.width;\n                command.pos_y = el_bounds.y + scroll_pos * parent_scaling;\n            }\n\n            gui_arena_append(gui->arena, gui->command_list, command);\n            gui_arena_append(gui->arena, gui->aux_command_list, command);\n        }\n\n        if (hover) *el->scroll_value += gui->mouse_scroll * el->scroll_scaling;\n        if (*el->scroll_value < -max) *el->scroll_value = -max;\n        if (*el->scroll_value > 0) *el->scroll_value = 0;\n    }\n\n    if (FLOATING(el) || SCISSOR(el)) flush_command_batch(gui);\n\n    if (SCISSOR(el)) {\n        gui->scissor_stack.size--;\n        if (gui->scissor_stack.size == 0) {\n            new_draw_command(gui, el_bounds, DRAWTYPE_SCISSOR_RESET, GUI_SUBTYPE_DEFAULT, (GuiDrawData) {0}, (GuiColor) {0});\n        } else {\n            if (prev_scissor.w > 0 && prev_scissor.h > 0) {\n                new_draw_command(gui, (GuiDrawBounds) { prev_scissor.x, prev_scissor.y, prev_scissor.w, prev_scissor.h }, DRAWTYPE_SCISSOR_SET, GUI_SUBTYPE_DEFAULT, (GuiDrawData) {0}, (GuiColor) {0});\n            }\n        }\n    }\n}\n\nstatic GuiElement* gui_element_new(Gui* gui) {\n    GuiElement* el = gui_arena_alloc(gui->arena, sizeof(GuiElement));\n    memset(el, 0, sizeof(*el));\n\n    el->scaling = 1.0;\n    el->size_percentage = 1.0;\n    el->scroll_scaling = 64;\n    gui->elements_count++;\n    return el;\n}\n\nGuiElement* gui_element_begin(Gui* gui) {\n    GuiElement* parent = gui->current_element;\n    GuiElement* el = gui_element_new(gui);\n    gui->current_element = el;\n    el->parent = parent;\n\n    if (parent) {\n        if (!parent->child_elements_end) {\n            parent->child_elements_begin = el;\n            parent->child_elements_end = el;\n        } else {\n            parent->child_elements_end->next = el;\n            el->prev = parent->child_elements_end;\n            parent->child_elements_end = el;\n        }\n        el->scaling = parent->scaling;\n        el->x = parent->cursor_x;\n        el->y = parent->cursor_y;\n    }\n    return el;\n}\n\nstatic void gui_element_offset(GuiElement* el, int offset_x, int offset_y) {\n    for (GuiElement* iter = el->child_elements_begin; iter; iter = iter->next) {\n        iter->x += offset_x;\n        iter->y += offset_y;\n    }\n}\n\nstatic void gui_element_realign(GuiElement* el) {\n    int align_div;\n\n    if (ALIGN_X(el) != ALIGN_TOP) {\n        align_div = ALIGN_X(el) == ALIGN_CENTER ? 2 : 1;\n        for (GuiElement* iter = el->child_elements_begin; iter; iter = iter->next) {\n            if (FLOATING(iter)) continue;\n            if (DIRECTION(el) == DIRECTION_VERTICAL) {\n                iter->x = (el->w - iter->w) / align_div;\n            } else {\n                iter->x += MAX(0, (el->w - el->pad_w + el->gap - el->cursor_x) / align_div);\n            }\n        }\n    }\n\n    if (ALIGN_Y(el) != ALIGN_TOP) {\n        align_div = ALIGN_Y(el) == ALIGN_CENTER ? 2 : 1;\n        for (GuiElement* iter = el->child_elements_begin; iter; iter = iter->next) {\n            if (FLOATING(iter)) continue;\n            if (DIRECTION(el) == DIRECTION_VERTICAL) {\n                iter->y += MAX(0, (el->h - el->pad_h + el->gap - el->cursor_y) / align_div);\n            } else {\n                iter->y = (el->h - iter->h) / align_div;\n            }\n        }\n    }\n\n    if (el->scroll_value) {\n        if (DIRECTION(el) == DIRECTION_HORIZONTAL) {\n            gui_element_offset(el, *el->scroll_value, 0);\n        } else {\n            gui_element_offset(el, 0, *el->scroll_value);\n        }\n    }\n}\n\nstatic void gui_element_resize(Gui* gui, GuiElement* el, unsigned short new_w, unsigned short new_h) {\n    el->w = new_w;\n    el->h = new_h;\n\n    int left_w = el->w - el->pad_w * 2 + el->gap;\n    int left_h = el->h - el->pad_h * 2 + el->gap;\n    int grow_elements = 0;\n\n    for (GuiElement* iter = el->child_elements_begin; iter; iter = iter->next) {\n        if (FLOATING(iter)) continue;\n\n        if (DIRECTION(el) == DIRECTION_VERTICAL) {\n            if (SIZING_Y(iter) == SIZING_GROW) {\n                grow_elements++;\n            } else if (SIZING_Y(iter) == SIZING_PERCENT) {\n                left_h -= el->h * iter->size_percentage;\n            } else {\n                left_h -= iter->h;\n            }\n            left_h -= el->gap;\n        } else {\n            if (SIZING_X(iter) == SIZING_GROW) {\n                grow_elements++;\n            } else if (SIZING_X(iter) == SIZING_PERCENT) {\n                left_w -= el->w * iter->size_percentage;\n            } else {\n                left_w -= iter->w;\n            }\n            left_w -= el->gap;\n        }\n    }\n\n    if (left_w < 0) left_w = 0;\n    if (left_h < 0) left_h = 0;\n\n    el->cursor_x = el->pad_w;\n    el->cursor_y = el->pad_h;\n\n    for (GuiElement* iter = el->child_elements_begin; iter; iter = iter->next) {\n        bool is_floating = FLOATING(iter);\n        if (!is_floating) {\n            iter->x = el->cursor_x;\n            iter->y = el->cursor_y;\n        }\n\n        int size_w = iter->w;\n        int size_h = iter->h;\n        GuiElementSizing sizing_x = SIZING_X(iter);\n        GuiElementSizing sizing_y = SIZING_Y(iter);\n        if (sizing_x == SIZING_PERCENT) size_w = el->w * iter->size_percentage;\n        if (sizing_y == SIZING_PERCENT) size_h = el->h * iter->size_percentage;\n\n        if (DIRECTION(el) == DIRECTION_VERTICAL) {\n            if (sizing_x == SIZING_GROW) size_w = el->w - el->pad_w * 2;\n            if (sizing_y == SIZING_GROW) size_h = left_h / grow_elements;\n            if (sizing_x == SIZING_GROW || sizing_y == SIZING_GROW || sizing_x == SIZING_PERCENT || sizing_y == SIZING_PERCENT) gui_element_resize(gui, iter, size_w, size_h);\n            if (!is_floating) el->cursor_y += iter->h + el->gap;\n        } else {\n            if (sizing_x == SIZING_GROW) size_w = left_w / grow_elements;\n            if (sizing_y == SIZING_GROW) size_h = el->h - el->pad_h * 2;\n            if (sizing_x == SIZING_GROW || sizing_y == SIZING_GROW || sizing_x == SIZING_PERCENT || sizing_y == SIZING_PERCENT) gui_element_resize(gui, iter, size_w, size_h);\n            if (!is_floating) el->cursor_x += iter->w + el->gap;\n        }\n    }\n    if (DIRECTION(el) == DIRECTION_HORIZONTAL) {\n        el->cursor_x += el->pad_w - el->gap;\n    } else {\n        el->cursor_y += el->pad_h - el->gap;\n    }\n\n    gui_element_realign(el);\n}\n\nstatic void gui_element_advance(GuiElement* el, GuiMeasurement ms) {\n    if (!el) return;\n\n    if (DIRECTION(el) == DIRECTION_HORIZONTAL) {\n        el->cursor_x += ms.w + el->gap;\n        if (SIZING_X(el) != SIZING_FIXED) el->w = MAX(el->w, el->cursor_x + el->pad_w);\n        if (SIZING_Y(el) != SIZING_FIXED) el->h = MAX(el->h, ms.h + el->pad_h * 2);\n    } else {\n        el->cursor_y += ms.h + el->gap;\n        if (SIZING_X(el) != SIZING_FIXED) el->w = MAX(el->w, ms.w + el->pad_w * 2);\n        if (SIZING_Y(el) != SIZING_FIXED) el->h = MAX(el->h, el->cursor_y + el->pad_h);\n    }\n}\n\nvoid gui_element_end(Gui* gui) {\n    GuiElement* el     = gui->current_element;\n    GuiElement* parent = gui->current_element->parent;\n\n    if (DIRECTION(el) == DIRECTION_VERTICAL) {\n        el->h = MAX(el->min_h, el->h - el->gap);\n    } else {\n        el->w = MAX(el->min_w, el->w - el->gap);\n    }\n\n    if (!FLOATING(el)) gui_element_advance(parent, (GuiMeasurement) { el->w, el->h });\n\n    GuiElementSizing sizing_x = SIZING_X(el),\n                     sizing_y = SIZING_Y(el);\n    bool has_defined_size = sizing_x != SIZING_GROW && sizing_x != SIZING_PERCENT &&\n                            sizing_y != SIZING_GROW && sizing_y != SIZING_PERCENT;\n\n    if (!has_defined_size) SET_NEED_RESIZE(parent, 1);\n\n    if (has_defined_size && NEED_RESIZE(el) && (sizing_x == SIZING_FIXED || sizing_y == SIZING_FIXED || sizing_x == SIZING_FIT || sizing_y == SIZING_FIT)) {\n        gui_element_resize(gui, el, el->w, el->h);\n    } else {\n        gui_element_realign(el);\n    }\n\n    gui->current_element = parent;\n}\n\nGuiElement* gui_get_element(Gui* gui) {\n    return gui->current_element;\n}\n\nvoid gui_on_hover(Gui* gui, GuiHandler handler) {\n    GuiElement* el = gui->current_element;\n    el->handle_hover = handler;\n}\n\nvoid gui_on_render(Gui* gui, GuiHandler handler) {\n    GuiElement* el = gui->current_element;\n    el->handle_pre_render = handler;\n}\n\nvoid gui_set_anchor(Gui* gui, GuiAlignmentType anchor_x, GuiAlignmentType anchor_y) {\n    GuiElement* el = gui->current_element;\n    SET_ANCHOR(el, anchor_x, anchor_y);\n}\n\nvoid gui_set_parent_anchor(Gui* gui, GuiElement* anchor) {\n    GuiElement* el = gui->current_element;\n    el->parent_anchor = anchor;\n}\n\nvoid gui_set_shader(Gui* gui, void* shader) {\n    GuiElement* el = gui->current_element;\n    el->shader = shader;\n}\n\nvoid gui_set_scroll_scaling(Gui* gui, int scroll_scaling) {\n    GuiElement* el = gui->current_element;\n    el->scroll_scaling = scroll_scaling;\n}\n\nvoid gui_set_scroll(Gui* gui, int* scroll_value) {\n    GuiElement* el = gui->current_element;\n    el->scroll_value = scroll_value;\n}\n\nvoid gui_set_scissor(Gui* gui) {\n    GuiElement* el = gui->current_element;\n    SET_SCISSOR(el, 1);\n}\n\nvoid gui_scale_element(Gui* gui, float scaling) {\n    GuiElement* el = gui->current_element;\n    el->scaling *= scaling;\n}\n\nvoid* gui_set_state(Gui* gui, void* state, unsigned short state_len) {\n    GuiElement* el = gui->current_element;\n    if (el->custom_state) return el->custom_state;\n\n    void* el_state = gui_arena_alloc(gui->arena, state_len);\n\n    memcpy(el_state, state, state_len);\n    el->custom_state = el_state;\n    return el->custom_state;\n}\n\nvoid* gui_get_state(GuiElement* el) {\n    return el->custom_state;\n}\n\nvoid gui_set_floating(Gui* gui) {\n    GuiElement* el = gui->current_element;\n    SET_FLOATING(el, 1);\n}\n\nvoid gui_set_position(Gui* gui, int x, int y) {\n    GuiElement* el = gui->current_element;\n    el->x = x;\n    el->y = y;\n}\n\nvoid gui_set_custom_data(Gui* gui, void* custom_data) {\n    GuiElement* el = gui->current_element;\n    el->custom_data = custom_data;\n}\n\nvoid gui_set_fixed(Gui* gui, unsigned short w, unsigned short h) {\n    GuiElement* el = gui->current_element;\n    el->sizing = SIZING_FIXED | (SIZING_FIXED << 4); // This sets both dimensions to SIZING_FIXED\n    el->w = w;\n    el->h = h;\n}\n\nvoid gui_set_fit(Gui* gui, GuiElementDirection direction) {\n    GuiElement* el = gui->current_element;\n    if (direction == DIRECTION_VERTICAL) {\n        SET_SIZING_Y(el, SIZING_FIT);\n    } else {\n        SET_SIZING_X(el, SIZING_FIT);\n    }\n}\n\nvoid gui_set_padding(Gui* gui, unsigned short pad_w, unsigned short pad_h) {\n    GuiElement* el = gui->current_element;\n    el->pad_w = pad_w;\n    el->pad_h = pad_h;\n    el->w = MAX(el->w, el->pad_w * 2);\n    el->h = MAX(el->h, el->pad_h * 2);\n    el->cursor_x = pad_w;\n    el->cursor_y = pad_h;\n}\n\nvoid gui_set_gap(Gui* gui, unsigned short gap) {\n    GuiElement* el = gui->current_element;\n    el->gap = gap;\n}\n\nvoid gui_set_grow(Gui* gui, GuiElementDirection direction) {\n    GuiElement* el = gui->current_element;\n    if (direction == DIRECTION_VERTICAL) {\n        SET_SIZING_Y(el, SIZING_GROW);\n        el->h = 0;\n    } else {\n        SET_SIZING_X(el, SIZING_GROW);\n        el->w = 0;\n    }\n}\n\nvoid gui_set_percent_size(Gui* gui, float percentage, GuiElementDirection direction) {\n    GuiElement* el = gui->current_element;\n    el->size_percentage = percentage;\n    if (direction == DIRECTION_VERTICAL) {\n        SET_SIZING_Y(el, SIZING_PERCENT);\n        el->h = 0;\n    } else {\n        SET_SIZING_X(el, SIZING_PERCENT);\n        el->w = 0;\n    }\n}\n\nvoid gui_set_draw_subtype(Gui* gui, unsigned char subtype) {\n    GuiElement* el = gui->current_element;\n    el->draw_subtype = subtype;\n}\n\nvoid gui_set_direction(Gui* gui, GuiElementDirection direction) {\n    GuiElement* el = gui->current_element;\n    SET_DIRECTION(el, direction);\n}\n\nvoid gui_set_rect(Gui* gui, GuiColor color) {\n    GuiElement* el = gui->current_element;\n    el->draw_type = DRAWTYPE_RECT;\n    el->color = color;\n}\n\nvoid gui_set_border(Gui* gui, GuiColor color, unsigned int border_width) {\n    GuiElement* el = gui->current_element;\n    el->draw_type = DRAWTYPE_BORDER;\n    el->color = color;\n    el->data.border_width = border_width;\n}\n\nvoid gui_set_text_slice(Gui* gui, void* font, const char* text, unsigned int text_size, unsigned short font_size, GuiColor color) {\n    if (text_size == 0) return;\n    GuiElement* el = gui->current_element;\n    GuiMeasurement text_bounds = gui->measure_text(font, text, text_size, font_size);\n    el->draw_type = DRAWTYPE_TEXT;\n    el->color = color;\n    el->data.text.text = text;\n    el->data.text.font = font;\n    el->data.text.text_size = text_size;\n    el->w = text_bounds.w;\n    el->h = text_bounds.h;\n}\n\ninline void gui_set_text(Gui* gui, void* font, const char* text, unsigned short font_size, GuiColor color) {\n    gui_set_text_slice(gui, font, text, strlen(text), font_size, color);\n}\n\nvoid gui_set_image(Gui* gui, void* image, unsigned short size, GuiColor color) {\n    GuiElement* el = gui->current_element;\n    GuiMeasurement image_size = gui->measure_image(image, size);\n    el->draw_type = DRAWTYPE_IMAGE;\n    el->color = color;\n    el->data.image = image;\n    el->w = image_size.w;\n    el->h = image_size.h;\n}\n\nvoid gui_set_align(Gui* gui, GuiAlignmentType align_x, GuiAlignmentType align_y) {\n    GuiElement* el = gui->current_element;\n    SET_ALIGN_X(el, align_x);\n    SET_ALIGN_Y(el, align_y);\n}\n\nvoid gui_set_min_size(Gui* gui, unsigned short min_w, unsigned short min_h) {\n    GuiElement* el = gui->current_element;\n    el->w = MAX(el->w, min_w);\n    el->h = MAX(el->h, min_h);\n    el->min_w = min_w;\n    el->min_h = min_h;\n}\n\ninline void gui_text_slice(Gui* gui, void* font, const char* text, unsigned int text_size, unsigned short font_size, GuiColor color) {\n    if (text_size == 0) return;\n    gui_element_begin(gui);\n    gui_set_text_slice(gui, font, text, text_size, font_size, color);\n    gui_element_end(gui);\n}\n\ninline void gui_text(Gui* gui, void* font, const char* text, unsigned short size, GuiColor color) {\n    gui_element_begin(gui);\n    gui_set_text(gui, font, text, size, color);\n    gui_element_end(gui);\n}\n\ninline void gui_image(Gui* gui, void* image, unsigned short size, GuiColor color) {\n    gui_element_begin(gui);\n    gui_set_image(gui, image, size, color);\n    gui_element_end(gui);\n}\n\ninline void gui_grow(Gui* gui, GuiElementDirection direction) {\n    gui_element_begin(gui);\n    gui_set_grow(gui, direction);\n    gui_element_end(gui);\n}\n\ninline void gui_spacer(Gui* gui, unsigned short w, unsigned short h) {\n    gui_element_begin(gui);\n    gui_set_min_size(gui, w, h);\n    gui_element_end(gui);\n}\n\nGuiMemArena* gui_arena_new(size_t reserve_size, size_t commit_size) {\n    unsigned long pagesize = gui_plat_get_pagesize();\n\n    reserve_size = GUI_ALIGN_UP_POW2(reserve_size, pagesize);\n    commit_size = GUI_ALIGN_UP_POW2(commit_size, pagesize);\n\n    GuiMemArena* arena = gui_plat_mem_reserve(reserve_size);\n\n    if (!gui_plat_mem_commit(arena, commit_size)) return NULL;\n\n    arena->reserve_size = reserve_size;\n    arena->commit_size = commit_size;\n    arena->pos = GUI_ARENA_BASE_POS;\n    arena->commit_pos = commit_size;\n\n    return arena;\n}\n\nvoid gui_arena_free(GuiMemArena* arena) {\n    gui_plat_mem_release(arena, arena->reserve_size);\n}\n\nconst char* gui_arena_sprintf(GuiMemArena* arena, size_t max_size, const char* fmt, ...) {\n    char* str = gui_arena_alloc(arena, max_size);\n\n    va_list va;\n    va_start(va, fmt);\n    int size = vsnprintf(str, max_size, fmt, va);\n    va_end(va);\n\n    gui_arena_pop(arena, max_size - size - 1);\n    return str;\n}\n\nvoid* gui_arena_alloc(GuiMemArena* arena, size_t size) {\n    size_t pos_aligned = GUI_ALIGN_UP_POW2(arena->pos, GUI_ARENA_ALIGN);\n    size_t new_pos = pos_aligned + size;\n\n    assert(new_pos <= arena->reserve_size);\n\n    if (new_pos > arena->commit_pos) {\n        size_t new_commit_pos = new_pos;\n        new_commit_pos += arena->commit_size - 1;\n        new_commit_pos -= new_commit_pos % arena->commit_size;\n        new_commit_pos = MIN(new_commit_pos, arena->reserve_size);\n\n        unsigned char* mem = (unsigned char*)arena + arena->commit_pos;\n        size_t commit_size = new_commit_pos - arena->commit_pos;\n\n        bool arena_memory_committed = gui_plat_mem_commit(mem, commit_size);\n        assert(arena_memory_committed);\n\n        arena->commit_pos = new_commit_pos;\n    }\n\n    arena->pos = new_pos;\n\n    unsigned char* out = (unsigned char*)arena + pos_aligned;\n\n    return out;\n}\n\nvoid* gui_arena_realloc(GuiMemArena* arena, void* ptr, size_t old_size, size_t new_size) {\n    if (!ptr || old_size == 0) return gui_arena_alloc(arena, new_size);\n\n    void* ret = gui_arena_alloc(arena, new_size);\n    memcpy(ret, ptr, old_size);\n    return ret;\n}\n\nvoid gui_arena_pop(GuiMemArena* arena, size_t size) {\n    size = MIN(size, arena->pos - GUI_ARENA_BASE_POS);\n    arena->pos -= size;\n}\n\nvoid gui_arena_pop_to(GuiMemArena* arena, size_t pos) {\n    size_t size = pos < arena->pos ? arena->pos - pos : 0;\n    gui_arena_pop(arena, size);\n}\n\nvoid gui_arena_clear(GuiMemArena* arena) {\n    gui_arena_pop_to(arena, GUI_ARENA_BASE_POS);\n}\n\n#ifdef _WIN32\n\n#include <windows.h>\n\nstatic unsigned long gui_plat_get_pagesize(void) {\n    SYSTEM_INFO sysinfo = { 0 };\n    GetSystemInfo(&sysinfo);\n\n    return sysinfo.dwPageSize;\n}\n\nstatic void* gui_plat_mem_reserve(size_t size) {\n    return VirtualAlloc(NULL, size, MEM_RESERVE, PAGE_READWRITE);\n}\n\nstatic bool gui_plat_mem_commit(void* ptr, size_t size) {\n    void* ret = VirtualAlloc(ptr, size, MEM_COMMIT, PAGE_READWRITE);\n    return ret != NULL;\n}\n\nstatic bool gui_plat_mem_release(void* ptr, size_t size) {\n    return VirtualFree(ptr, size, MEM_RELEASE);\n}\n\n#else\n\n#include <unistd.h>\n#include <sys/mman.h>\n\nstatic unsigned long gui_plat_get_pagesize(void) {\n    return sysconf(_SC_PAGESIZE);\n}\n\nstatic void* gui_plat_mem_reserve(size_t size) {\n    void* out = mmap(NULL, size, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);\n    if (out == MAP_FAILED) return NULL;\n    return out;\n}\n\nstatic bool gui_plat_mem_commit(void* ptr, size_t size) {\n    return !mprotect(ptr, size, PROT_READ | PROT_WRITE);\n}\n\nstatic bool gui_plat_mem_release(void* ptr, size_t size) {\n    return !munmap(ptr, size);\n}\n\n#endif // _WIN32\n\n"
  },
  {
    "path": "src/scrap_gui.h",
    "content": "// Scrap is a project that allows anyone to build software using simple, block based interface.\n//\n// Copyright (C) 2024-2026 Grisshink\n// \n// This software is provided 'as-is', without any express or implied\n// warranty.  In no event will the authors be held liable for any damages\n// arising from the use of this software.\n// \n// Permission is granted to anyone to use this software for any purpose,\n// including commercial applications, and to alter it and redistribute it\n// freely, subject to the following restrictions:\n// \n// 1. The origin of this software must not be misrepresented; you must not\n//    claim that you wrote the original software. If you use this software\n//    in a product, an acknowledgment in the product documentation would be\n//    appreciated but is not required.\n// 2. Altered source versions must be plainly marked as such, and must not be\n//    misrepresented as being the original software.\n// 3. This notice may not be removed or altered from any source distribution.\n\n#ifndef SCRAP_GUI_H\n#define SCRAP_GUI_H\n\n#include <stddef.h>\n\n#define ELEMENT_STACK_SIZE 32768\n#define COMMAND_STACK_SIZE 4096\n#define STATE_STACK_SIZE 32768\ntypedef struct Gui Gui;\ntypedef struct GuiElement GuiElement;\n\ntypedef struct {\n    unsigned short w, h;\n} GuiMeasurement;\n\ntypedef struct {\n    int x, y;\n    unsigned short w, h;\n} GuiBounds;\n\ntypedef struct {\n    float x, y, w, h;\n} GuiDrawBounds;\n\ntypedef struct {\n    unsigned char r, g, b, a;\n} GuiColor;\n\ntypedef struct {\n    void* font;\n    const char* text;\n    unsigned int text_size;\n} GuiTextData;\n\ntypedef enum {\n    DRAWTYPE_UNKNOWN = 0,\n    DRAWTYPE_RECT = 1,\n    DRAWTYPE_BORDER = 2,\n    DRAWTYPE_IMAGE = 3,\n    DRAWTYPE_TEXT = 4,\n    DRAWTYPE_SCISSOR_SET,\n    DRAWTYPE_SCISSOR_RESET,\n    DRAWTYPE_SHADER_BEGIN,\n    DRAWTYPE_SHADER_END,\n} GuiDrawType;\n\ntypedef union {\n    GuiTextData text;\n    void* image;\n    void* custom_data;\n    void* shader;\n    unsigned int border_width;\n} GuiDrawData;\n\ntypedef struct {\n    unsigned char type; // DrawType\n    unsigned char subtype;\n    float pos_x, pos_y;\n    float width, height;\n    GuiColor color;\n    GuiDrawData data;\n} GuiDrawCommand;\n\ntypedef enum {\n    ALIGN_LEFT = 0,\n    ALIGN_CENTER,\n    ALIGN_RIGHT,\n    ALIGN_TOP = 0,\n    ALIGN_BOTTOM = 2,\n} GuiAlignmentType;\n\ntypedef enum {\n    SIZING_FIT = 0,\n    SIZING_FIXED,\n    SIZING_GROW,\n    SIZING_PERCENT,\n} GuiElementSizing;\n\ntypedef enum {\n    DIRECTION_VERTICAL = 0,\n    DIRECTION_HORIZONTAL,\n} GuiElementDirection;\n\ntypedef void (*GuiHandler)(GuiElement* el);\n\nstruct GuiElement {\n    int x, y;\n    unsigned short w, h;\n    unsigned short min_w, min_h;\n    float abs_x, abs_y;\n    int cursor_x, cursor_y;\n    unsigned short pad_w, pad_h;\n    unsigned short gap;\n    float scaling;\n    float size_percentage;\n    unsigned char draw_type; // DrawType\n    // Custom draw type, interpretation of this is defined by user\n    // NOTE: Scrap gui always interprets 0 as no subtype, so don't do custom\n    // rendering with this subtype value to not break things in unexpected way\n    unsigned char draw_subtype;\n    GuiDrawData data;\n    GuiColor color;\n    // Sizing layout:\n    // YYYYXXXX\n    // Where:\n    //   X - ElementSizing for element width\n    //   Y - ElementSizing for element height\n    unsigned char sizing;\n    // Anchor layout:\n    // YYYYXXXX\n    // Where:\n    //   X - GuiAlignmentType for horizontal alignment\n    //   Y - GuiAlignmentType for vertical alignment\n    unsigned char anchor;\n    // Flags layout:\n    // XXGSFYYD\n    // Where:\n    //   D - GuiElementDirection\n    //   X - GuiAlignmentType for horizontal alignment\n    //   Y - GuiAlignmentType for vertical alignment\n    //   F - Is floating element\n    //   S - Is scissoring on\n    //   G - Does this element need to be resized? (internal flag)\n    //   0 - Unused\n    unsigned char flags;\n    GuiHandler handle_hover;\n    GuiHandler handle_pre_render;\n    int* scroll_value;\n    short scroll_scaling;\n    void* custom_data;\n    void* custom_state;\n    void* shader;\n\n    GuiElement* child_elements_begin;\n    GuiElement* child_elements_end;\n\n    GuiElement* parent;\n    GuiElement* parent_anchor;\n\n    GuiElement* prev;\n    GuiElement* next;\n};\n\ntypedef GuiMeasurement (*GuiMeasureTextSliceFunc)(void* font, const char* text, unsigned int text_size, unsigned short font_size);\ntypedef GuiMeasurement (*GuiMeasureImageFunc)(void* image, unsigned short size);\n\ntypedef struct {\n    size_t reserve_size, commit_size,\n           pos, commit_pos;\n} GuiMemArena;\n\ntypedef struct {\n    GuiDrawCommand* items;\n    size_t size, capacity;\n} GuiDrawCommandList;\n\ntypedef struct {\n    GuiBounds* items;\n    size_t size, capacity;\n} GuiScissorStack;\n\nstruct Gui {\n    GuiMemArena* arena;\n\n    size_t elements_count;\n\n    GuiDrawCommandList command_list;\n    GuiDrawCommandList aux_command_list;\n    size_t command_list_iter,\n           command_list_last_batch;\n\n    GuiScissorStack scissor_stack;\n\n    GuiMeasureTextSliceFunc measure_text;\n    GuiMeasureImageFunc measure_image;\n\n    GuiElement *root_element,\n               *current_element;\n\n    unsigned short win_w, win_h;\n    short mouse_x, mouse_y;\n    int mouse_scroll;\n};\n\n#define GUI_GET_COMMANDS(gui, command) for ( \\\n    gui->command_list_iter = 0; \\\n    command = gui->command_list.items[gui->command_list_iter], gui->command_list_iter < gui->command_list.size; \\\n    gui->command_list_iter++ \\\n)\n\n#define GUI_BLACK (GuiColor) { 0x00, 0x00, 0x00, 0xff }\n#define GUI_WHITE (GuiColor) { 0xff, 0xff, 0xff, 0xff }\n#define GUI_SUBTYPE_DEFAULT 0\n\nGui gui_new(size_t arena_size);\nvoid gui_free(Gui* gui);\nvoid gui_begin(Gui* gui);\nvoid gui_end(Gui* gui);\nvoid gui_update_window_size(Gui* gui, unsigned short win_w, unsigned short win_h);\nvoid gui_update_mouse_pos(Gui* gui, short mouse_x, short mouse_y);\nvoid gui_update_mouse_scroll(Gui* gui, int mouse_scroll);\nvoid gui_set_measure_text_func(Gui* gui, GuiMeasureTextSliceFunc measure_text);\nvoid gui_set_measure_image_func(Gui* gui, GuiMeasureImageFunc measure_image);\n\nGuiElement* gui_element_begin(Gui* gui);\nvoid gui_element_end(Gui* gui);\n\nvoid gui_set_fixed(Gui* gui, unsigned short w, unsigned short h);\nvoid gui_set_fit(Gui* gui, GuiElementDirection direction);\nvoid gui_set_grow(Gui* gui, GuiElementDirection diection);\nvoid gui_set_percent_size(Gui* gui, float percentage, GuiElementDirection direction);\nvoid gui_set_draw_subtype(Gui* gui, unsigned char subtype);\nvoid gui_set_rect(Gui* gui, GuiColor color);\nvoid gui_set_direction(Gui* gui, GuiElementDirection direction);\nvoid gui_set_border(Gui* gui, GuiColor color, unsigned int border_width);\nvoid gui_set_text_slice(Gui* gui, void* font, const char* text, unsigned int text_size, unsigned short font_size, GuiColor color);\nvoid gui_set_text(Gui* gui, void* font, const char* text, unsigned short size, GuiColor color);\nvoid gui_set_image(Gui* gui, void* image, unsigned short size, GuiColor color);\nvoid gui_set_min_size(Gui* gui, unsigned short min_w, unsigned short min_h);\nvoid gui_set_align(Gui* gui, GuiAlignmentType align_x, GuiAlignmentType align_y);\nvoid gui_set_padding(Gui* gui, unsigned short pad_w, unsigned short pad_h);\nvoid gui_set_gap(Gui* gui, unsigned short gap);\nvoid gui_set_custom_data(Gui* gui, void* custom_data);\nvoid gui_set_floating(Gui* gui);\nvoid gui_set_scissor(Gui* gui);\nvoid gui_set_position(Gui* gui, int x, int y);\nvoid gui_set_anchor(Gui* gui, GuiAlignmentType anchor_x, GuiAlignmentType anchor_y);\nvoid gui_set_parent_anchor(Gui* gui, GuiElement* parent_anchor);\nvoid gui_set_scroll(Gui* gui, int* scroll_value);\nvoid gui_set_scroll_scaling(Gui* gui, int scroll_scaling);\nvoid gui_set_shader(Gui* gui, void* shader);\nvoid gui_scale_element(Gui* gui, float scaling);\nvoid* gui_set_state(Gui* gui, void* state, unsigned short state_len);\nvoid* gui_get_state(GuiElement* el);\nGuiElement* gui_get_element(Gui* gui);\n\nvoid gui_on_hover(Gui* gui, GuiHandler handler);\nvoid gui_on_render(Gui* gui, GuiHandler handler);\n\nvoid gui_text_slice(Gui* gui, void* font, const char* text, unsigned int text_size, unsigned short font_size, GuiColor color);\nvoid gui_text(Gui* gui, void* font, const char* text, unsigned short size, GuiColor color);\nvoid gui_image(Gui* gui, void* image, unsigned short size, GuiColor color);\nvoid gui_grow(Gui* gui, GuiElementDirection direction);\nvoid gui_spacer(Gui* gui, unsigned short w, unsigned short h);\n\n#define gui_arena_append(_arena, _list, _val) do { \\\n    if ((_list).size >= (_list).capacity) { \\\n        size_t _old_cap = (_list).capacity * sizeof(*(_list).items); \\\n        if ((_list).capacity == 0) (_list).capacity = 32; \\\n        else (_list).capacity *= 2; \\\n        (_list).items = gui_arena_realloc(_arena, (_list).items, _old_cap, (_list).capacity * sizeof(*(_list).items)); \\\n    } \\\n    (_list).items[(_list).size++] = (_val); \\\n} while (0)\n\nGuiMemArena* gui_arena_new(size_t reserve_size, size_t commit_size);\nvoid gui_arena_free(GuiMemArena* arena);\nvoid* gui_arena_alloc(GuiMemArena* arena, size_t size);\nvoid* gui_arena_realloc(GuiMemArena* arena, void* ptr, size_t old_size, size_t new_size);\nconst char* gui_arena_sprintf(GuiMemArena* arena, size_t max_size, const char* fmt, ...);\nvoid gui_arena_pop(GuiMemArena* arena, size_t size);\nvoid gui_arena_pop_to(GuiMemArena* arena, size_t pos);\nvoid gui_arena_clear(GuiMemArena* arena);\n\n#endif // SCRAP_GUI_H\n"
  },
  {
    "path": "src/std.c",
    "content": "// Scrap is a project that allows anyone to build software using simple, block based interface.\n//\n// Copyright (C) 2024-2026 Grisshink\n// \n// This software is provided 'as-is', without any express or implied\n// warranty.  In no event will the authors be held liable for any damages\n// arising from the use of this software.\n// \n// Permission is granted to anyone to use this software for any purpose,\n// including commercial applications, and to alter it and redistribute it\n// freely, subject to the following restrictions:\n// \n// 1. The origin of this software must not be misrepresented; you must not\n//    claim that you wrote the original software. If you use this software\n//    in a product, an acknowledgment in the product documentation would be\n//    appreciated but is not required.\n// 2. Altered source versions must be plainly marked as such, and must not be\n//    misrepresented as being the original software.\n// 3. This notice may not be removed or altered from any source distribution.\n\n#include <stdarg.h>\n#include <string.h>\n#include <stdio.h>\n#include <time.h>\n\n#include \"std.h\"\n#include \"util.h\"\n\n#ifdef _WIN32\n#include <windows.h>\n#else\n#include <sys/ioctl.h>\n#endif\n\n// Explicitly including rprand.h here as this translation unit will soon need \n// to compile as standalone library, which means we should not depend on \n// raylib in any way\n#define RPRAND_IMPLEMENTATION\n#define RPRANDAPI static __attribute__ ((unused))\n#include \"../external/rprand.h\"\n\n// NOTE: Shamelessly stolen from raylib codebase ;)\n// Get next codepoint in a UTF-8 encoded text, scanning until '\\0' is found\n// When an invalid UTF-8 byte is encountered we exit as soon as possible and a '?'(0x3f) codepoint is returned\n// Total number of bytes processed are returned as a parameter\n// NOTE: The standard says U+FFFD should be returned in case of errors\n// but that character is not supported by the default font in raylib\nstatic int get_codepoint(const char *text, int *codepoint_size) {\n/*\n    UTF-8 specs from https://www.ietf.org/rfc/rfc3629.txt\n\n    Char. number range  |        UTF-8 octet sequence\n      (hexadecimal)    |              (binary)\n    --------------------+---------------------------------------------\n    0000 0000-0000 007F | 0xxxxxxx\n    0000 0080-0000 07FF | 110xxxxx 10xxxxxx\n    0000 0800-0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx\n    0001 0000-0010 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx\n*/\n    // NOTE: on decode errors we return as soon as possible\n\n    int codepoint = 0x3f;   // Codepoint (defaults to '?')\n    int octet = (unsigned char)(text[0]); // The first UTF8 octet\n    *codepoint_size = 1;\n\n    if (octet <= 0x7f) {\n        // Only one octet (ASCII range x00-7F)\n        codepoint = text[0];\n    } else if ((octet & 0xe0) == 0xc0) {\n        // Two octets\n\n        // [0]xC2-DF    [1]UTF8-tail(x80-BF)\n        unsigned char octet1 = text[1];\n\n        if ((octet1 == '\\0') || ((octet1 >> 6) != 2)) {\n            // Unexpected sequence\n            *codepoint_size = 2; \n            return codepoint;\n        }\n\n        if ((octet >= 0xc2) && (octet <= 0xdf)) {\n            codepoint = ((octet & 0x1f) << 6) | (octet1 & 0x3f);\n            *codepoint_size = 2;\n        }\n    } else if ((octet & 0xf0) == 0xe0) {\n        // Three octets\n        unsigned char octet1 = text[1];\n        unsigned char octet2 = '\\0';\n\n        if ((octet1 == '\\0') || ((octet1 >> 6) != 2)) { \n            // Unexpected sequence\n            *codepoint_size = 2; \n            return codepoint; \n        }\n\n        octet2 = text[2];\n\n        if ((octet2 == '\\0') || ((octet2 >> 6) != 2)) { \n            // Unexpected sequence\n            *codepoint_size = 3; \n            return codepoint; \n        }\n\n        // [0]xE0    [1]xA0-BF       [2]UTF8-tail(x80-BF)\n        // [0]xE1-EC [1]UTF8-tail    [2]UTF8-tail(x80-BF)\n        // [0]xED    [1]x80-9F       [2]UTF8-tail(x80-BF)\n        // [0]xEE-EF [1]UTF8-tail    [2]UTF8-tail(x80-BF)\n\n        if (((octet == 0xe0) && !((octet1 >= 0xa0) && (octet1 <= 0xbf))) ||\n            ((octet == 0xed) && !((octet1 >= 0x80) && (octet1 <= 0x9f)))) { \n            *codepoint_size = 2; \n            return codepoint; \n        }\n\n        if ((octet >= 0xe0) && (octet <= 0xef)) {\n            codepoint = ((octet & 0xf) << 12) | ((octet1 & 0x3f) << 6) | (octet2 & 0x3f);\n            *codepoint_size = 3;\n        }\n    } else if ((octet & 0xf8) == 0xf0) {\n        // Four octets\n        if (octet > 0xf4) return codepoint;\n\n        unsigned char octet1 = text[1];\n        unsigned char octet2 = '\\0';\n        unsigned char octet3 = '\\0';\n\n        if ((octet1 == '\\0') || ((octet1 >> 6) != 2)) { \n            // Unexpected sequence\n            *codepoint_size = 2; \n            return codepoint; \n        }\n\n        octet2 = text[2];\n\n        if ((octet2 == '\\0') || ((octet2 >> 6) != 2)) { \n            // Unexpected sequence\n            *codepoint_size = 3; \n            return codepoint; \n        }\n\n        octet3 = text[3];\n\n        if ((octet3 == '\\0') || ((octet3 >> 6) != 2)) { \n            // Unexpected sequence\n            *codepoint_size = 4; \n            return codepoint; \n        }\n\n        // [0]xF0       [1]x90-BF       [2]UTF8-tail  [3]UTF8-tail\n        // [0]xF1-F3    [1]UTF8-tail    [2]UTF8-tail  [3]UTF8-tail\n        // [0]xF4       [1]x80-8F       [2]UTF8-tail  [3]UTF8-tail\n\n        if (((octet == 0xf0) && !((octet1 >= 0x90) && (octet1 <= 0xbf))) ||\n            ((octet == 0xf4) && !((octet1 >= 0x80) && (octet1 <= 0x8f)))) { \n            // Unexpected sequence\n            *codepoint_size = 2; \n            return codepoint; \n        }\n\n        if (octet >= 0xf0) {\n            codepoint = ((octet & 0x7) << 18) | ((octet1 & 0x3f) << 12) | ((octet2 & 0x3f) << 6) | (octet3 & 0x3f);\n            *codepoint_size = 4;\n        }\n    }\n\n    if (codepoint > 0x10ffff) codepoint = 0x3f;     // Codepoints after U+10ffff are invalid\n\n    return codepoint;\n}\n\n// NOTE: Shamelessly stolen from raylib codebase ;)\n// Encode codepoint into utf8 text (char array length returned as parameter)\n// NOTE: It uses a static array to store UTF-8 bytes\nconst char *codepoint_to_utf8(int codepoint, int *utf8_size) {\n    static char utf8[6] = { 0 };\n    int size = 0;   // Byte size of codepoint\n\n    if (codepoint <= 0x7f) {\n        utf8[0] = (char)codepoint;\n        size = 1;\n    } else if (codepoint <= 0x7ff) {\n        utf8[0] = (char)(((codepoint >> 6) & 0x1f) | 0xc0);\n        utf8[1] = (char)((codepoint & 0x3f) | 0x80);\n        size = 2;\n    } else if (codepoint <= 0xffff) {\n        utf8[0] = (char)(((codepoint >> 12) & 0x0f) | 0xe0);\n        utf8[1] = (char)(((codepoint >>  6) & 0x3f) | 0x80);\n        utf8[2] = (char)((codepoint & 0x3f) | 0x80);\n        size = 3;\n    } else if (codepoint <= 0x10ffff) {\n        utf8[0] = (char)(((codepoint >> 18) & 0x07) | 0xf0);\n        utf8[1] = (char)(((codepoint >> 12) & 0x3f) | 0x80);\n        utf8[2] = (char)(((codepoint >>  6) & 0x3f) | 0x80);\n        utf8[3] = (char)((codepoint & 0x3f) | 0x80);\n        size = 4;\n    }\n\n    *utf8_size = size;\n\n    return utf8;\n}\n\nstatic int leading_ones(unsigned char byte) {\n    int out = 0;\n    while (byte & 0x80) {\n        out++;\n        byte <<= 1;\n    }\n    return out;\n}\n\nint std_int_pow(int base, int exp) {\n    if (exp == 0) return 1;\n\n    int result = 1;\n    while (exp) {\n        if (exp & 1) result *= base;\n        exp >>= 1;\n        base *= base;\n    }\n    return result;\n}\n\nStdColor std_parse_color(const char* value) {\n    if (*value == '#') value++;\n    unsigned char r = 0x00, g = 0x00, b = 0x00, a = 0xff;\n    sscanf(value, \"%02hhx%02hhx%02hhx%02hhx\", &r, &g, &b, &a);\n    return (StdColor) { r, g, b, a };\n}\n\nList* std_list_new(Gc* gc) {\n    List* list = gc_malloc(gc, sizeof(List), DATA_TYPE_LIST);\n    list->size = 0;\n    list->capacity = 0;\n    list->values = NULL;\n    return list;\n}\n\nstatic AnyValue std_get_any(DataType data_type, va_list va) {\n    AnyValueData data;\n\n    switch (data_type) {\n    case DATA_TYPE_BOOL:\n    case DATA_TYPE_INTEGER:\n        data.integer_val = va_arg(va, int);\n        break;\n    case DATA_TYPE_FLOAT:\n        data.float_val = va_arg(va, double);\n        break;\n    case DATA_TYPE_STRING:\n        data.str_val = va_arg(va, StringHeader*);\n        break;\n    case DATA_TYPE_LITERAL:\n        data.literal_val = va_arg(va, char*);\n        break;\n    case DATA_TYPE_LIST:\n        data.list_val = va_arg(va, List*);\n        break;\n    case DATA_TYPE_ANY:\n        return *va_arg(va, AnyValue*);\n    case DATA_TYPE_COLOR:\n        data.color_val = va_arg(va, StdColor);\n        break;\n    default:\n        data = (AnyValueData) {0};\n        break;\n    }\n\n    return (AnyValue) {\n        .type = data_type,\n        .data = data,\n    };\n}\n\nvoid std_list_add(Gc* gc, List* list, DataType data_type, ...) {\n    AnyValue any;\n    \n    va_list va;\n    va_start(va, data_type);\n    any = std_get_any(data_type, va);\n    va_end(va);\n\n    std_list_add_any(gc, list, any);\n}\n\nvoid std_list_add_any(Gc* gc, List* list, AnyValue any) {\n    if (!list->values) {\n        list->values = gc_malloc(gc, sizeof(AnyValue), 0);\n        list->capacity = 1;\n    }\n\n    if (list->size >= list->capacity) {\n        AnyValue* new_list = gc_malloc(gc, sizeof(AnyValue) * list->size * 2, 0);\n        memcpy(new_list, list->values, sizeof(AnyValue) * list->size);\n        list->values = new_list;\n        list->capacity = list->size * 2;\n    }\n\n    list->values[list->size++] = any;\n}\n\nvoid std_list_set(List* list, int index, DataType data_type, ...) {\n    if (index >= list->size || index < 0) return;\n\n    AnyValue any;\n\n    va_list va;\n    va_start(va, data_type);\n    any = std_get_any(data_type, va);\n    va_end(va);\n\n    list->values[index] = any;\n}\n\nAnyValue* std_list_get(Gc* gc, List* list, int index) {\n    AnyValue* out = gc_malloc(gc, sizeof(AnyValue), DATA_TYPE_ANY);\n    *out = (AnyValue) { .type = DATA_TYPE_NOTHING };\n\n    if (index >= list->size || index < 0) return out;\n\n    *out = list->values[index];\n    return out;\n}\n\nint std_list_length(List* list) {\n    return list->size;\n}\n\nAnyValue* std_any_from_value(Gc* gc, DataType data_type, ...) {\n    AnyValue any;\n\n    va_list va;\n    va_start(va, data_type);\n    if (data_type == DATA_TYPE_ANY) {\n        return va_arg(va, AnyValue*);\n    } else {\n        any = std_get_any(data_type, va);\n    }\n    va_end(va);\n\n    AnyValue* value = gc_malloc(gc, sizeof(AnyValue), DATA_TYPE_ANY);\n    *value = any;\n    return value;\n}\n\nStringHeader* std_string_from_literal(Gc* gc, const char* literal, unsigned int size) {\n    StringHeader* out_str = gc_malloc(gc, sizeof(StringHeader) + size + 1, DATA_TYPE_STRING); // Don't forget null terminator. It is not included in size\n    memcpy(out_str->str, literal, size);\n    out_str->size = size;\n    out_str->capacity = size;\n    out_str->str[size] = 0;\n    return out_str;\n}\n\nchar* std_string_get_data(StringHeader* str) {\n    return str->str;\n}\n\nStringHeader* std_string_letter_in(Gc* gc, int target, StringHeader* input_str) {\n    int pos = 0;\n    if (target <= 0) return std_string_from_literal(gc, \"\", 0);\n    for (char* str = input_str->str; *str; str++) {\n        // Increment pos only on the beginning of multibyte char\n        if ((*str & 0x80) == 0 || (*str & 0x40) != 0) pos++;\n\n        if (pos == target) {\n            int codepoint_size;\n            get_codepoint(str, &codepoint_size);\n            return std_string_from_literal(gc, str, codepoint_size);\n        }\n    }\n\n    return std_string_from_literal(gc, \"\", 0);\n}\n\nStringHeader* std_string_substring(Gc* gc, int begin, int end, StringHeader* input_str) {\n    if (begin <= 0) begin = 1;\n    if (end <= 0) return std_string_from_literal(gc, \"\", 0);\n    if (begin > end) return std_string_from_literal(gc, \"\", 0);\n\n    char* substr_start = NULL;\n    int substr_len = 0;\n\n    int pos = 0;\n    for (char* str = input_str->str; *str; str++) {\n        // Increment pos only on the beginning of multibyte char\n        if ((*str & 0x80) == 0 || (*str & 0x40) != 0) pos++;\n        if (substr_start) substr_len++;\n\n        if (pos == begin && !substr_start) {\n            substr_start = str;\n            substr_len = 1;\n        }\n        if (pos == end) {\n            if (!substr_start) return std_string_from_literal(gc, \"\", 0);\n            int codepoint_size;\n            get_codepoint(str, &codepoint_size);\n            substr_len += codepoint_size - 1;\n\n            return std_string_from_literal(gc, substr_start, substr_len);\n        }\n    }\n\n    if (substr_start) return std_string_from_literal(gc, substr_start, substr_len);\n    return std_string_from_literal(gc, \"\", 0);\n}\n\nStringHeader* std_string_join(Gc* gc, StringHeader* left, StringHeader* right) {\n    StringHeader* out_str = gc_malloc(gc, sizeof(StringHeader) + left->size + right->size + 1, DATA_TYPE_STRING);\n    memcpy(out_str->str, left->str, left->size);\n    memcpy(out_str->str + left->size, right->str, right->size);\n    out_str->size = left->size + right->size;\n    out_str->capacity = out_str->size;\n    out_str->str[out_str->size] = 0;\n    return out_str;\n}\n\nint std_string_length(StringHeader* str) {\n    int len = 0;\n    char* cur = str->str;\n    while (*cur) {\n        int mb_size = leading_ones(*cur);\n        if (mb_size == 0) mb_size = 1;\n        cur += mb_size;\n        len++;\n    }\n    return len;\n}\n\nbool std_string_is_eq(StringHeader* left, StringHeader* right) {\n    if (left->size != right->size) return false;\n    for (unsigned int i = 0; i < left->size; i++) {\n        if (left->str[i] != right->str[i]) return false;\n    }\n    return true;\n}\n\nStringHeader* std_string_chr(Gc* gc, int value) {\n    int text_size;\n    const char* text = codepoint_to_utf8(value, &text_size);\n    return std_string_from_literal(gc, text, text_size);\n}\n\nint std_string_ord(StringHeader* str) {\n    int codepoint_size;\n    int codepoint = get_codepoint(str->str, &codepoint_size);\n    (void) codepoint_size;\n    return codepoint;\n}\n\nStringHeader* std_string_from_integer(Gc* gc, int value) {\n    char str[20];\n    unsigned int len = snprintf(str, 20, \"%d\", value);\n    return std_string_from_literal(gc, str, len);\n}\n\nStringHeader* std_string_from_bool(Gc* gc, bool value) {\n    return value ? std_string_from_literal(gc, \"true\", 4) : std_string_from_literal(gc, \"false\", 5);\n}\n\nStringHeader* std_string_from_float(Gc* gc, double value) {\n    char str[20];\n    unsigned int len = snprintf(str, 20, \"%f\", value);\n    return std_string_from_literal(gc, str, len);\n}\n\nStringHeader* std_string_from_color(Gc* gc, StdColor value) {\n    char str[20];\n    unsigned int len = snprintf(str, 20, \"#%02x%02x%02x%02x\", value.r, value.g, value.b, value.a);\n    return std_string_from_literal(gc, str, len);\n}\n\nchar* std_any_string_from_any(Gc* gc, AnyValue* value) {\n    if (value->type == DATA_TYPE_LITERAL) return value->data.literal_val;\n    return std_string_from_any(gc, value)->str;\n}\n\nStringHeader* std_string_from_any(Gc* gc, AnyValue* value) {\n    if (!value) return std_string_from_literal(gc, \"\", 0);\n    char str[32];\n    int size;\n\n    switch (value->type) {\n    case DATA_TYPE_INTEGER:\n        return std_string_from_integer(gc, value->data.integer_val);\n    case DATA_TYPE_FLOAT:\n        return std_string_from_float(gc, value->data.float_val);\n    case DATA_TYPE_LITERAL:\n        return std_string_from_literal(gc, value->data.literal_val, strlen(value->data.literal_val));\n    case DATA_TYPE_STRING:\n        return value->data.str_val;\n    case DATA_TYPE_BOOL:\n        return std_string_from_bool(gc, value->data.integer_val);\n    case DATA_TYPE_LIST:\n        size = snprintf(str, 32, \"*LIST (%lu)*\", value->data.list_val->size);\n        return std_string_from_literal(gc, str, size);\n    case DATA_TYPE_COLOR:\n        size = snprintf(str, 10, \"#%02x%02x%02x%02x\", value->data.color_val.r, value->data.color_val.g, value->data.color_val.b, value->data.color_val.a);\n        return std_string_from_literal(gc, str, size);\n    default:\n        return std_string_from_literal(gc, \"\", 0);\n    }\n}\n\nint std_integer_from_any(AnyValue* value) {\n    if (!value) return 0;\n\n    switch (value->type) {\n    case DATA_TYPE_BOOL:\n    case DATA_TYPE_INTEGER:\n        return value->data.integer_val;\n    case DATA_TYPE_FLOAT:\n        return (int)value->data.float_val;\n    case DATA_TYPE_STRING:\n        return atoi(value->data.str_val->str);\n    case DATA_TYPE_LITERAL:\n        return atoi(value->data.literal_val);\n    case DATA_TYPE_COLOR:\n        return *(int*)&value->data.color_val;\n    default:\n        return 0;\n    }\n}\n\ndouble std_float_from_any(AnyValue* value) {\n    if (!value) return 0;\n\n    switch (value->type) {\n    case DATA_TYPE_BOOL:\n    case DATA_TYPE_INTEGER:\n        return (double)value->data.integer_val;\n    case DATA_TYPE_FLOAT:\n        return value->data.float_val;\n    case DATA_TYPE_STRING:\n        return atof(value->data.str_val->str);\n    case DATA_TYPE_LITERAL:\n        return atof(value->data.literal_val);\n    case DATA_TYPE_COLOR:\n        return *(int*)&value->data.color_val;\n    default:\n        return 0;\n    }\n}\n\nint std_bool_from_any(AnyValue* value) {\n    if (!value) return 0;\n\n    switch (value->type) {\n    case DATA_TYPE_BOOL:\n    case DATA_TYPE_INTEGER:\n        return value->data.integer_val != 0;\n    case DATA_TYPE_FLOAT:\n        return value->data.float_val != 0.0;\n    case DATA_TYPE_STRING:\n        return value->data.str_val->size > 0;\n    case DATA_TYPE_LITERAL:\n        return *value->data.literal_val != 0;\n    case DATA_TYPE_COLOR:\n        return *(int*)&value->data.color_val != 0;\n    default:\n        return 0;\n    }\n}\n\n#define INT_TO_COLOR(v) ((StdColor) { \\\n    ((v) >> 0 ) & 255, \\\n    ((v) >> 8 ) & 255, \\\n    ((v) >> 16) & 255, \\\n    ((v) >> 24) & 255, \\\n})\n\nStdColor std_color_from_any(AnyValue* value) {\n    if (!value) return (StdColor) { 0x00, 0x00, 0x00, 0xff };\n\n    switch (value->type) {\n    case DATA_TYPE_BOOL:\n        return value->data.integer_val ? (StdColor) { 0xff, 0xff, 0xff, 0xff } : (StdColor) { 0x00, 0x00, 0x00, 0xff };\n    case DATA_TYPE_INTEGER:\n        return INT_TO_COLOR(value->data.integer_val);\n    case DATA_TYPE_FLOAT:\n        int int_val = value->data.float_val;\n        return INT_TO_COLOR(int_val);\n    case DATA_TYPE_STRING:\n        return std_parse_color(value->data.str_val->str);\n    case DATA_TYPE_LITERAL:\n        return std_parse_color(value->data.literal_val);\n    case DATA_TYPE_COLOR:\n        return value->data.color_val;\n    default:\n        return (StdColor) { 0x00, 0x00, 0x00, 0xff };\n    }\n}\n\nList* std_list_from_any(Gc* gc, AnyValue* value) {\n    if (!value) return 0;\n\n    switch (value->type) {\n    case DATA_TYPE_LIST:\n        return value->data.list_val;\n    default:\n        return std_list_new(gc);\n    }\n}\n\nbool std_any_is_eq(AnyValue* left, AnyValue* right) {\n    if (left->type != right->type) return false;\n\n    switch (left->type) {\n    case DATA_TYPE_NOTHING:\n        return true;\n    case DATA_TYPE_LITERAL:\n        return !strcmp(left->data.literal_val, right->data.literal_val);\n    case DATA_TYPE_STRING:\n        return std_string_is_eq(left->data.str_val, right->data.str_val);\n    case DATA_TYPE_INTEGER:\n    case DATA_TYPE_BOOL:\n        return left->data.integer_val == right->data.integer_val;\n    case DATA_TYPE_FLOAT:\n        return left->data.float_val == right->data.float_val;\n    case DATA_TYPE_LIST:\n        return left->data.list_val == right->data.list_val;\n    case DATA_TYPE_COLOR:\n        return memcmp(&left->data.color_val, &right->data.color_val, sizeof(left->data.color_val));\n    default:\n        return false;\n    }\n}\n\nint std_sleep(int usecs) {\n    if (usecs < 0) return 0;\n#ifdef _WIN32\n    Sleep(usecs / 1000);\n#else\n    struct timespec sleep_time = {0};\n    sleep_time.tv_sec = usecs / 1000000;\n    sleep_time.tv_nsec = (usecs % 1000000) * 1000;\n\n    if (nanosleep(&sleep_time, &sleep_time) == -1) return 0;\n#endif\n    return usecs;\n}\n\nvoid std_set_random_seed(int seed) {\n    rprand_set_seed(seed);\n}\n\nint std_get_random(int min, int max) {\n    if (min > max) {\n        return rprand_get_value(max, min);\n    } else {\n        return rprand_get_value(min, max);\n    }\n}\n\nint std_term_print_any(AnyValue* any) {\n    if (!any) return 0;\n\n    switch (any->type) {\n    case DATA_TYPE_STRING:\n        return std_term_print_str(any->data.str_val->str);\n    case DATA_TYPE_LITERAL:\n        return std_term_print_str(any->data.literal_val);\n    case DATA_TYPE_NOTHING:\n        return 0;\n    case DATA_TYPE_INTEGER:\n        return std_term_print_integer(any->data.integer_val);\n    case DATA_TYPE_BOOL:\n        return std_term_print_bool(any->data.integer_val);\n    case DATA_TYPE_FLOAT:\n        return std_term_print_float(any->data.float_val);\n    case DATA_TYPE_LIST:\n        return std_term_print_list(any->data.list_val);\n    case DATA_TYPE_COLOR:\n        return std_term_print_color(any->data.color_val);\n    default:\n        return 0;\n    }\n}\n\n#ifdef STANDALONE_STD\n\nstatic int cursor_x = 0;\nstatic int cursor_y = 0;\nstatic Color clear_color = {0};\nstatic Color bg_color = {0};\n\nvoid test_cancel(void) {}\n\nint std_term_print_str(const char* str) {\n    int len = printf(\"%s\", str);\n    fflush(stdout);\n    return len;\n}\n\nint std_term_print_integer(int value) {\n    int len = printf(\"%d\", value);\n    fflush(stdout);\n    return len;\n}\n\nint std_term_print_float(double value) {\n    int len = printf(\"%f\", value);\n    fflush(stdout);\n    return len;\n}\n\nint std_term_print_bool(bool value) {\n    int len = printf(\"%s\", value ? \"true\" : \"false\");\n    fflush(stdout);\n    return len;\n}\n\nint std_term_print_color(StdColor value) {\n    int len = printf(\"[Color: #%02x%02x%02x%02x]\", value.r, value.g, value.b, value.a);\n    fflush(stdout);\n    return len;\n}\n\nvoid std_term_set_fg_color(Color color) {\n    // ESC[38;2;⟨r⟩;⟨g⟩;⟨b⟩m Select RGB foreground color\n    printf(\"\\033[38;2;%d;%d;%dm\", color.r, color.g, color.b);\n    fflush(stdout);\n}\n\nvoid std_term_set_bg_color(Color color) {\n    // ESC[48;2;⟨r⟩;⟨g⟩;⟨b⟩m Select RGB background color\n    printf(\"\\033[48;2;%d;%d;%dm\", color.r, color.g, color.b);\n    bg_color = color;\n    fflush(stdout);\n}\n\nvoid std_term_set_clear_color(Color color) {\n    clear_color = color;\n}\n\nint std_term_cursor_max_y(void) {\n#ifdef _WIN32\n    CONSOLE_SCREEN_BUFFER_INFO csbi;\n    if (!GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &csbi)) return 0;\n    return csbi.srWindow.Bottom - csbi.srWindow.Top + 1;\n#else\n    struct winsize w;\n    if (ioctl(0, TIOCGWINSZ, &w)) return 0;\n    return w.ws_row;\n#endif\n}\n\nint std_term_cursor_max_x(void) {\n#ifdef _WIN32\n    CONSOLE_SCREEN_BUFFER_INFO csbi;\n    if (!GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &csbi)) return 0;\n    return csbi.srWindow.Right - csbi.srWindow.Left + 1;\n#else\n    struct winsize w;\n    if (ioctl(0, TIOCGWINSZ, &w)) return 0;\n    return w.ws_col;\n#endif\n}\n\nint std_term_cursor_x(void) {\n    return cursor_x;\n}\n\nint std_term_cursor_y(void) {\n    return cursor_y;\n}\n\nvoid std_term_clear(void) {\n    // ESC[48;2;⟨r⟩;⟨g⟩;⟨b⟩m Select RGB background color\n    printf(\"\\033[48;2;%d;%d;%dm\", clear_color.r, clear_color.g, clear_color.b);\n    printf(\"\\033[2J\");\n    // ESC[48;2;⟨r⟩;⟨g⟩;⟨b⟩m Select RGB background color\n    printf(\"\\033[48;2;%d;%d;%dm\", bg_color.r, bg_color.g, bg_color.b);\n    fflush(stdout);\n}\n\nvoid std_term_set_cursor(int x, int y) {\n    cursor_x = x;\n    cursor_y = y;\n    printf(\"\\033[%d;%dH\", y + 1, x + 1);\n    fflush(stdout);\n}\n\nStringHeader* std_term_get_char(Gc* gc) {\n    char input[10];\n    input[0] = (char)getchar();\n    if (input[0] == '\\n') return std_string_from_literal(gc, \"\", 0);\n\n    int mb_size = leading_ones(input[0]);\n\n    if (mb_size == 0) mb_size = 1;\n    for (int i = 1; i < mb_size && i < 10; i++) input[i] = (char)getchar();\n    input[mb_size] = 0;\n\n    return std_string_from_literal(gc, input, mb_size);\n}\n\nStringHeader* std_term_get_input(Gc* gc) {\n    char* string_buf = vector_create();\n    char last_char = 0;\n    char buf[256];\n    \n    while (last_char != '\\n') {\n        if (!fgets(buf, 256, stdin)) {\n            vector_free(string_buf);\n            return std_string_from_literal(gc, \"\", 0);\n        }\n\n        int size = strlen(buf);\n        last_char = buf[size - 1];\n        if (last_char == '\\n') buf[--size] = 0;\n\n        for (char* str = buf; *str; str++) vector_add(&string_buf, *str);\n    }\n\n    StringHeader* out_string = std_string_from_literal(gc, string_buf, vector_size(string_buf));\n    vector_free(string_buf);\n\n    return out_string;\n}\n\nint std_term_print_list(List* list) {\n    int len = printf(\"*LIST (%lu)*\", list->size);\n    fflush(stdout);\n    return len;\n}\n\n#else\n\nint std_term_print_str(const char* str) {\n    return term_print_str(str);\n}\n\nint std_term_print_integer(int value) {\n    return term_print_integer(value);\n}\n\nint std_term_print_float(double value) {\n    return term_print_float(value);\n}\n\nint std_term_print_bool(bool value) {\n    return term_print_bool(value);\n}\n\nint std_term_print_color(StdColor value) {\n    return term_print_color(CONVERT_COLOR(value, TermColor));\n}\n\nvoid std_term_set_fg_color(TermColor color) {\n    return term_set_fg_color(color);\n}\n\nvoid std_term_set_bg_color(TermColor color) {\n    return term_set_bg_color(color);\n}\n\nvoid std_term_set_clear_color(TermColor color) {\n    return term_set_clear_color(color);\n}\n\nvoid std_term_clear(void) {\n    term_clear();\n}\n\nStringHeader* std_term_get_char(Gc* gc) {\n    char input[10];\n    input[0] = term_input_get_char();\n    int mb_size = leading_ones(input[0]);\n\n    if (mb_size == 0) mb_size = 1;\n    for (int i = 1; i < mb_size && i < 10; i++) input[i] = term_input_get_char();\n    input[mb_size] = 0;\n\n    return std_string_from_literal(gc, input, mb_size);\n}\n\nvoid std_term_set_cursor(int x, int y) {\n    mutex_lock(&term.lock);\n    x = CLAMP(x, 0, term.char_w - 1);\n    y = CLAMP(y, 0, term.char_h - 1);\n    term.cursor_pos = x + y * term.char_w;\n    mutex_unlock(&term.lock);\n}\n\nint std_term_cursor_x(void) {\n    mutex_lock(&term.lock);\n    int cur_x = 0;\n    if (term.char_w != 0) cur_x = term.cursor_pos % term.char_w;\n    mutex_unlock(&term.lock);\n    return cur_x;\n}\n\nint std_term_cursor_y(void) {\n    mutex_lock(&term.lock);\n    int cur_y = 0;\n    if (term.char_w != 0) cur_y = term.cursor_pos / term.char_w;\n    mutex_unlock(&term.lock);\n    return cur_y;\n}\n\nint std_term_cursor_max_x(void) {\n    mutex_lock(&term.lock);\n    int cur_max_x = term.char_w;\n    mutex_unlock(&term.lock);\n    return cur_max_x;\n}\n\nint std_term_cursor_max_y(void) {\n    mutex_lock(&term.lock);\n    int cur_max_y = term.char_h;\n    mutex_unlock(&term.lock);\n    return cur_max_y;\n}\n\nStringHeader* std_term_get_input(Gc* gc) {\n    char input_char = 0;\n    char* string_buf = vector_create();\n\n    while (input_char != '\\n') {\n        char input[256];\n        int i = 0;\n        for (; i < 255 && input_char != '\\n'; i++) input[i] = (input_char = term_input_get_char());\n        if (input[i - 1] == '\\n') input[i - 1] = 0;\n        input[i] = 0;\n\n        for (char* str = input; *str; str++) vector_add(&string_buf, *str);\n    }\n\n    StringHeader* out_string = std_string_from_literal(gc, string_buf, vector_size(string_buf));\n    vector_free(string_buf);\n\n    return out_string;\n}\n\nint std_term_print_list(List* list) {\n    char converted[32];\n    snprintf(converted, 32, \"*LIST (%lu)*\", list->size);\n    return term_print_str(converted);\n}\n\n#endif\n"
  },
  {
    "path": "src/std.h",
    "content": "// Scrap is a project that allows anyone to build software using simple, block based interface.\n//\n// Copyright (C) 2024-2026 Grisshink\n// \n// This software is provided 'as-is', without any express or implied\n// warranty.  In no event will the authors be held liable for any damages\n// arising from the use of this software.\n// \n// Permission is granted to anyone to use this software for any purpose,\n// including commercial applications, and to alter it and redistribute it\n// freely, subject to the following restrictions:\n// \n// 1. The origin of this software must not be misrepresented; you must not\n//    claim that you wrote the original software. If you use this software\n//    in a product, an acknowledgment in the product documentation would be\n//    appreciated but is not required.\n// 2. Altered source versions must be plainly marked as such, and must not be\n//    misrepresented as being the original software.\n// 3. This notice may not be removed or altered from any source distribution.\n\n#ifndef SCRAP_STD_H\n#define SCRAP_STD_H\n\n#include \"gc.h\"\n\n#include <stdbool.h>\n\ntypedef struct {\n    unsigned int size;\n    unsigned int capacity;\n    char str[];\n} StringHeader;\n\ntypedef struct {\n    unsigned char r, g, b, a;\n} StdColor;\n\ntypedef struct AnyValue AnyValue;\ntypedef struct List List;\n\ntypedef union {\n    char* literal_val;\n    StringHeader* str_val;\n    int integer_val;\n    double float_val;\n    List* list_val;\n    AnyValue* any_val;\n    StdColor color_val;\n} AnyValueData;\n\nstruct AnyValue {\n    DataType type;\n    AnyValueData data;\n};\n\nstruct List {\n    long size;\n    long capacity;\n    AnyValue* values;\n};\n\n// Math\nint std_int_pow(int base, int exp);\n\n// List operations\nList* std_list_new(Gc* gc);\nvoid std_list_add_any(Gc* gc, List* list, AnyValue any);\nvoid std_list_add(Gc* gc, List* list, DataType data_type, ...);\nvoid std_list_set(List* list, int index, DataType data_type, ...);\nAnyValue* std_list_get(Gc* gc, List* list, int index);\nint std_list_length(List* list);\n\n// Any operations\nAnyValue* std_any_from_value(Gc* gc, DataType data_type, ...);\nint std_integer_from_any(AnyValue* value);\ndouble std_float_from_any(AnyValue* value);\nint std_bool_from_any(AnyValue* value);\nList* std_list_from_any(Gc* gc, AnyValue* value);\nStdColor std_color_from_any(AnyValue* value);\nStringHeader* std_string_from_any(Gc* gc, AnyValue* value);\nchar* std_any_string_from_any(Gc* gc, AnyValue* value);\nbool std_any_is_eq(AnyValue* left, AnyValue* right);\n\n// String operations\nStringHeader* std_string_from_literal(Gc* gc, const char* literal, unsigned int size);\nStringHeader* std_string_from_integer(Gc* gc, int value);\nStringHeader* std_string_from_bool(Gc* gc, bool value);\nStringHeader* std_string_from_float(Gc* gc, double value);\nStringHeader* std_string_from_color(Gc* gc, StdColor value);\nchar* std_string_get_data(StringHeader* str);\n\nint std_string_length(StringHeader* str);\nStringHeader* std_string_letter_in(Gc* gc, int target, StringHeader* input_str);\nStringHeader* std_string_substring(Gc* gc, int begin, int end, StringHeader* input_str);\nStringHeader* std_string_join(Gc* gc, StringHeader* left, StringHeader* right);\nbool std_string_is_eq(StringHeader* left, StringHeader* right);\nStringHeader* std_string_chr(Gc* gc, int value);\nint std_string_ord(StringHeader* str);\n\n// Terminal control\nStringHeader* std_term_get_char(Gc* gc);\nvoid std_term_set_cursor(int x, int y);\nint std_term_cursor_x(void);\nint std_term_cursor_y(void);\nint std_term_cursor_max_x(void);\nint std_term_cursor_max_y(void);\nStringHeader* std_term_get_input(Gc* gc);\nint std_term_print_list(List* list);\nint std_term_print_any(AnyValue* any);\nint std_term_print_str(const char* str);\nint std_term_print_integer(int value);\nint std_term_print_float(double value);\nint std_term_print_bool(bool value);\nint std_term_print_color(StdColor value);\nvoid std_term_clear(void);\n\n#ifdef STANDALONE_STD\ntypedef struct {\n    unsigned char r, g, b, a;\n} Color;\n\nvoid std_term_set_fg_color(Color color);\nvoid std_term_set_bg_color(Color color);\nvoid std_term_set_clear_color(Color color);\n#else\n// TODO: Remove this dependency by doing stdio\n#include \"term.h\"\n\nvoid std_term_set_fg_color(TermColor color);\nvoid std_term_set_bg_color(TermColor color);\nvoid std_term_set_clear_color(TermColor color);\n#endif\n\n// Misc\nint std_sleep(int usecs);\nint std_get_random(int min, int max);\nvoid std_set_random_seed(int seed);\nStdColor std_parse_color(const char* value);\n\n#endif // SCRAP_STD_H\n"
  },
  {
    "path": "src/term.c",
    "content": "// Scrap is a project that allows anyone to build software using simple, block based interface.\n//\n// Copyright (C) 2024-2026 Grisshink\n// \n// This software is provided 'as-is', without any express or implied\n// warranty.  In no event will the authors be held liable for any damages\n// arising from the use of this software.\n// \n// Permission is granted to anyone to use this software for any purpose,\n// including commercial applications, and to alter it and redistribute it\n// freely, subject to the following restrictions:\n// \n// 1. The origin of this software must not be misrepresented; you must not\n//    claim that you wrote the original software. If you use this software\n//    in a product, an acknowledgment in the product documentation would be\n//    appreciated but is not required.\n// 2. Altered source versions must be plainly marked as such, and must not be\n//    misrepresented as being the original software.\n// 3. This notice may not be removed or altered from any source distribution.\n\n#include \"term.h\"\n#include \"util.h\"\n\n#include <stdlib.h>\n#include <string.h>\n#include <assert.h>\n#include <stdio.h>\n\n#define TERM_WHITE (TermColor) { 0xff, 0xff, 0xff, 0xff }\n#define TERM_BLACK (TermColor) { 0x00, 0x00, 0x00, 0xff }\n\nTerminal term = {0};\n\nint leading_ones(unsigned char byte) {\n    int out = 0;\n    while (byte & 0x80) {\n        out++;\n        byte <<= 1;\n    }\n    return out;\n}\n\nvoid term_init(MeasureTextSliceFunc measure_text, void* font, unsigned short font_size) {\n    sem_init(&term.input_sem, 0, 0);\n    term.lock = mutex_new();\n    term.is_buffer_dirty = true;\n    term.cursor_fg_color = TERM_WHITE;\n    term.cursor_bg_color = TERM_BLACK;\n    term.measure_text = measure_text;\n    term.font = font;\n    term.font_size = font_size;\n\n    term_resize(0, 0);\n}\n\nvoid term_restart(void) {\n    sem_destroy(&term.input_sem);\n    sem_init(&term.input_sem, 0, 0);\n    term.buf_start = 0;\n    term.buf_end = 0;\n    term.cursor_fg_color = TERM_WHITE;\n    term.cursor_bg_color = TERM_BLACK;\n    term.clear_color = TERM_BLACK;\n    term_clear();\n}\n\nvoid term_free(void) {\n    mutex_free(&term.lock);\n    sem_destroy(&term.input_sem);\n}\n\nvoid term_input_put_char(char ch) {\n    mutex_lock(&term.lock);\n    term.input_buf[term.buf_end] = ch;\n    term.buf_end = (term.buf_end + 1) % TERM_INPUT_BUF_SIZE;\n    mutex_unlock(&term.lock);\n    sem_post(&term.input_sem);\n}\n\nchar term_input_get_char(void) {\n    sem_wait(&term.input_sem);\n    mutex_lock(&term.lock);\n    int out = term.input_buf[term.buf_start];\n    term.buf_start = (term.buf_start + 1) % TERM_INPUT_BUF_SIZE;\n    mutex_unlock(&term.lock);\n    return out;\n}\n\nvoid term_scroll_down(void) {\n    mutex_lock(&term.lock);\n    memmove(term.buffer, term.buffer + term.char_w, term.char_w * (term.char_h - 1) * sizeof(*term.buffer));\n    for (int i = term.char_w * (term.char_h - 1); i < term.char_w * term.char_h; i++) {\n        strncpy(term.buffer[i].ch, \" \", ARRLEN(term.buffer[i].ch));\n        term.buffer[i].fg_color = TERM_WHITE;\n        term.buffer[i].bg_color = term.clear_color;\n    }\n    mutex_unlock(&term.lock);\n}\n\nvoid term_set_fg_color(TermColor color) {\n    mutex_lock(&term.lock);\n    term.cursor_fg_color = color;\n    mutex_unlock(&term.lock);\n}\n\nvoid term_set_bg_color(TermColor color) {\n    mutex_lock(&term.lock);\n    term.cursor_bg_color = color;\n    mutex_unlock(&term.lock);\n}\n\nvoid term_set_clear_color(TermColor color) {\n    mutex_lock(&term.lock);\n    term.clear_color = color;\n    mutex_unlock(&term.lock);\n}\n\nint term_print_str(const char* str) {\n    int len = 0;\n    assert(term.buffer != NULL);\n\n    mutex_lock(&term.lock);\n    if (*str) term.is_buffer_dirty = true;\n    while (*str) {\n        if (term.cursor_pos >= term.char_w * term.char_h) {\n            term.cursor_pos = term.char_w * term.char_h - term.char_w;\n            term_scroll_down();\n        }\n        if (*str == '\\t') {\n            term_print_str(\"    \");\n            str++;\n            continue;\n        }\n        if (*str == '\\n') {\n            term.cursor_pos += term.char_w;\n            term.cursor_pos -= term.cursor_pos % term.char_w;\n            str++;\n            if (term.cursor_pos >= term.char_w * term.char_h) {\n                term.cursor_pos -= term.char_w;\n                term_scroll_down();\n            }\n            continue;\n        }\n        if (*str == '\\r') {\n            term.cursor_pos -= term.cursor_pos % term.char_w;\n            str++;\n            continue;\n        }\n\n        int mb_size = leading_ones(*str);\n        if (mb_size == 0) mb_size = 1;\n        int i = 0;\n        for (; i < mb_size; i++) term.buffer[term.cursor_pos].ch[i] = str[i];\n        term.buffer[term.cursor_pos].ch[i] = 0;\n        term.buffer[term.cursor_pos].fg_color = term.cursor_fg_color;\n        term.buffer[term.cursor_pos].bg_color = term.cursor_bg_color;\n\n        str += mb_size;\n        term.cursor_pos++;\n        len++;\n    }\n    mutex_unlock(&term.lock);\n\n    return len;\n}\n\nint term_print_integer(int value) {\n    char converted[12];\n    snprintf(converted, 12, \"%d\", value);\n    return term_print_str(converted);\n}\n\nint term_print_float(double value) {\n    char converted[20];\n    snprintf(converted, 20, \"%f\", value);\n    return term_print_str(converted);\n}\n\nint term_print_bool(bool value) {\n    return term_print_str(value ? \"true\" : \"false\");\n}\n\nint term_print_color(TermColor value) {\n    char converted[30];\n    snprintf(converted, 30, \"[Color: #%02x%02x%02x%02x]\", value.r, value.g, value.b, value.a);\n    return term_print_str(converted);\n}\n\nvoid term_clear(void) {\n    mutex_lock(&term.lock);\n    for (int i = 0; i < term.char_w * term.char_h; i++) {\n        strncpy(term.buffer[i].ch, \" \", ARRLEN(term.buffer[i].ch));\n        term.buffer[i].fg_color = TERM_WHITE;\n        term.buffer[i].bg_color = term.clear_color;\n    }\n    term.cursor_pos = 0;\n    mutex_unlock(&term.lock);\n}\n\nvoid term_resize(float screen_w, float screen_h) {\n    mutex_lock(&term.lock);\n    term.size = (TermVec) { screen_w, screen_h };\n\n    term.char_size = term.measure_text(term.font, \"A\", 1, term.font_size);\n    TermVec new_buffer_size = { term.size.x / term.char_size.x, term.size.y / term.char_size.y };\n    int new_char_w = (int)new_buffer_size.x,\n        new_char_h = (int)new_buffer_size.y;\n\n    if (term.char_w != new_char_w || term.char_h != new_char_h) {\n        int buf_size = new_char_w * new_char_h * sizeof(*term.buffer);\n        TerminalChar* new_buffer = malloc(buf_size);\n        if (term.buffer) {\n            for (int y = 0; y < new_char_h; y++) {\n                for (int x = 0; x < new_char_w; x++) {\n                    TerminalChar* ch = &new_buffer[x + y * new_char_w];\n\n                    if (x >= term.char_w || y >= term.char_h) {\n                        strncpy(ch->ch, \" \", ARRLEN(ch->ch));\n                        ch->fg_color = TERM_WHITE;\n                        ch->bg_color = term.clear_color;\n                        continue;\n                    }\n\n                    *ch = term.buffer[x + y * term.char_w];\n                }\n            }\n\n            int term_x = term.cursor_pos % term.char_w,\n                term_y = term.cursor_pos / term.char_w;\n            if (term_x >= new_char_w) term_x = new_char_w - 1;\n            if (term_y >= new_char_h) term_y = new_char_h - 1;\n            term.cursor_pos = term_x + term_y * new_char_w;\n\n            free(term.buffer);\n\n            term.char_w = new_char_w;\n            term.char_h = new_char_h;\n            term.buffer = new_buffer;\n        } else {\n            term.char_w = new_char_w;\n            term.char_h = new_char_h;\n            term.buffer = new_buffer;\n            term_clear();\n        }\n    }\n    mutex_unlock(&term.lock);\n}\n"
  },
  {
    "path": "src/term.h",
    "content": "// Scrap is a project that allows anyone to build software using simple, block based interface.\n//\n// Copyright (C) 2024-2026 Grisshink\n// \n// This software is provided 'as-is', without any express or implied\n// warranty.  In no event will the authors be held liable for any damages\n// arising from the use of this software.\n// \n// Permission is granted to anyone to use this software for any purpose,\n// including commercial applications, and to alter it and redistribute it\n// freely, subject to the following restrictions:\n// \n// 1. The origin of this software must not be misrepresented; you must not\n//    claim that you wrote the original software. If you use this software\n//    in a product, an acknowledgment in the product documentation would be\n//    appreciated but is not required.\n// 2. Altered source versions must be plainly marked as such, and must not be\n//    misrepresented as being the original software.\n// 3. This notice may not be removed or altered from any source distribution.\n\n#ifndef TERM_H\n#define TERM_H\n\n#include \"thread.h\"\n\n#include <semaphore.h>\n#include <stdbool.h>\n\n#define TERM_INPUT_BUF_SIZE 256\n\ntypedef struct {\n    unsigned char r, g, b, a;\n} TermColor;\n\ntypedef struct {\n    char ch[5];\n    TermColor fg_color;\n    TermColor bg_color;\n} TerminalChar;\n\ntypedef struct {\n    float x, y;\n} TermVec;\n\ntypedef TermVec (*MeasureTextSliceFunc)(void* font, const char* text, unsigned int text_size, unsigned short font_size);\n\ntypedef struct {\n    MeasureTextSliceFunc measure_text;\n    void* font;\n    unsigned short font_size;\n\n    Mutex lock;\n    TermVec size;\n    int char_w, char_h;\n    int cursor_pos;\n    TermColor cursor_fg_color, cursor_bg_color;\n    TermVec char_size;\n    TerminalChar *buffer;\n    bool is_buffer_dirty;\n\n    TermColor clear_color;\n\n    sem_t input_sem;\n    char input_buf[TERM_INPUT_BUF_SIZE];\n    int buf_start;\n    int buf_end;\n} Terminal;\n\nextern Terminal term;\n\nvoid term_init(MeasureTextSliceFunc measure_text, void* font, unsigned short font_size);\nvoid term_input_put_char(char ch);\nchar term_input_get_char(void);\nvoid term_scroll_down(void);\nvoid term_set_fg_color(TermColor color);\nvoid term_set_bg_color(TermColor color);\nvoid term_set_clear_color(TermColor color);\nint term_print_str(const char* str);\nint term_print_integer(int value);\nint term_print_float(double value);\nint term_print_bool(bool value);\nint term_print_color(TermColor value);\nvoid term_clear(void);\nvoid term_resize(float screen_w, float screen_h);\nvoid term_free(void);\nvoid term_restart(void);\n\n#endif // TERM_H\n"
  },
  {
    "path": "src/thread.c",
    "content": "// Scrap is a project that allows anyone to build software using simple, block based interface.\n//\n// Copyright (C) 2024-2026 Grisshink\n// \n// This software is provided 'as-is', without any express or implied\n// warranty.  In no event will the authors be held liable for any damages\n// arising from the use of this software.\n// \n// Permission is granted to anyone to use this software for any purpose,\n// including commercial applications, and to alter it and redistribute it\n// freely, subject to the following restrictions:\n// \n// 1. The origin of this software must not be misrepresented; you must not\n//    claim that you wrote the original software. If you use this software\n//    in a product, an acknowledgment in the product documentation would be\n//    appreciated but is not required.\n// 2. Altered source versions must be plainly marked as such, and must not be\n//    misrepresented as being the original software.\n// 3. This notice may not be removed or altered from any source distribution.\n\n#include \"thread.h\"\n\nMutex mutex_new(void) {\n    Mutex mutex;\n    pthread_mutexattr_t attr;\n    pthread_mutexattr_init(&attr);\n    pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);\n    pthread_mutex_init(&mutex, &attr);\n    return mutex;\n}\n\nvoid mutex_free(Mutex* mutex) {\n    pthread_mutex_destroy(mutex);\n}\n\nvoid mutex_lock(Mutex* mutex) {\n    pthread_mutex_lock(mutex);\n}\n\nvoid mutex_unlock(Mutex* mutex) {\n    pthread_mutex_unlock(mutex);\n}\n\nThread thread_new(ThreadEntry entry_func, ThreadCleanup cleanup_func) {\n    return (Thread) {\n        .state = THREAD_STATE_NOT_RUNNING,\n        .entry = entry_func,\n        .cleanup = cleanup_func,\n    };\n}\n\nstatic void* thread_entry(void* t) {\n    Thread* thread = t;\n    thread->state = THREAD_STATE_RUNNING;\n\n    void* return_val = thread->entry(thread->entry_data) ? (void*)THREAD_RETURN_SUCCESS : (void*)THREAD_RETURN_FAILURE;\n    if (thread->cleanup) thread->cleanup(thread->entry_data);\n\n    thread->state = THREAD_STATE_DONE;\n    return return_val;\n}\n\nbool thread_start(Thread* thread, void* data) {\n    if (thread->state != THREAD_STATE_NOT_RUNNING) return false;\n\n    thread->entry_data = data;\n    thread->state = THREAD_STATE_STARTING;\n    if (pthread_create(&thread->handle, NULL, thread_entry, thread)) {\n        thread->state = THREAD_STATE_NOT_RUNNING;\n        return false;\n    }\n\n    return true;\n}\n\nbool thread_is_running(Thread* thread) {\n    return thread->state != THREAD_STATE_NOT_RUNNING;\n}\n\nvoid thread_handle_stopping_state(Thread* thread) {\n    if (thread->state != THREAD_STATE_STOPPING) return;\n    if (thread->cleanup) thread->cleanup(thread->entry_data);\n    thread->state = THREAD_STATE_DONE;\n    pthread_exit((void*)THREAD_RETURN_STOPPED);\n}\n\nvoid thread_exit(Thread* thread, bool success) {\n    thread_handle_stopping_state(thread);\n\n    if (thread->state != THREAD_STATE_RUNNING) return;\n    if (thread->cleanup) thread->cleanup(thread->entry_data);\n    thread->state = THREAD_STATE_DONE;\n    pthread_exit(success ? (void*)THREAD_RETURN_SUCCESS : (void*)THREAD_RETURN_FAILURE);\n}\n\nbool thread_stop(Thread* thread) {\n    if (thread->state != THREAD_STATE_RUNNING) return false;\n    thread->state = THREAD_STATE_STOPPING;\n    return true;\n}\n\nThreadReturnCode thread_join(Thread* thread) {\n    if (thread->state == THREAD_STATE_NOT_RUNNING) return THREAD_RETURN_FAILURE;\n\n    void* return_val;\n    if (pthread_join(thread->handle, &return_val)) return THREAD_RETURN_FAILURE;\n    thread->state = THREAD_STATE_NOT_RUNNING;\n\n    ThreadReturnCode thread_return = (ThreadReturnCode)return_val;\n    switch (thread_return) {\n    case THREAD_RETURN_SUCCESS:\n    case THREAD_RETURN_STOPPED:\n        return thread_return;\n    default:\n        return THREAD_RETURN_FAILURE;\n    }\n}\n\nThreadReturnCode thread_try_join(Thread* thread) {\n    if (thread->state != THREAD_STATE_DONE) return THREAD_RETURN_RUNNING;\n    return thread_join(thread);\n}\n"
  },
  {
    "path": "src/thread.h",
    "content": "// Scrap is a project that allows anyone to build software using simple, block based interface.\n//\n// Copyright (C) 2024-2026 Grisshink\n// \n// This software is provided 'as-is', without any express or implied\n// warranty.  In no event will the authors be held liable for any damages\n// arising from the use of this software.\n// \n// Permission is granted to anyone to use this software for any purpose,\n// including commercial applications, and to alter it and redistribute it\n// freely, subject to the following restrictions:\n// \n// 1. The origin of this software must not be misrepresented; you must not\n//    claim that you wrote the original software. If you use this software\n//    in a product, an acknowledgment in the product documentation would be\n//    appreciated but is not required.\n// 2. Altered source versions must be plainly marked as such, and must not be\n//    misrepresented as being the original software.\n// 3. This notice may not be removed or altered from any source distribution.\n\n#ifndef THREAD_H\n#define THREAD_H\n\n#include <stdbool.h>\n#include <pthread.h>\n\ntypedef enum {\n    THREAD_STATE_NOT_RUNNING = 0,\n    THREAD_STATE_STARTING,\n    THREAD_STATE_RUNNING,\n    THREAD_STATE_STOPPING,\n    THREAD_STATE_DONE,\n} ThreadState;\n\ntypedef enum {\n    THREAD_RETURN_FAILURE = 0,\n    THREAD_RETURN_SUCCESS = 1,\n    THREAD_RETURN_STOPPED,\n    THREAD_RETURN_RUNNING, // Returned from thread_try_join to signify that thread is still running\n} ThreadReturnCode;\n\n// Return value indicates if the thread was executed successfully\ntypedef bool (*ThreadEntry)(void*);\ntypedef void (*ThreadCleanup)(void*);\n\ntypedef struct {\n    ThreadState state;\n    ThreadEntry entry;\n    ThreadCleanup cleanup;\n    void* entry_data;\n    pthread_t handle;\n} Thread;\n\ntypedef pthread_mutex_t Mutex;\n\nMutex mutex_new(void);\nvoid mutex_lock(Mutex* mutex);\nvoid mutex_unlock(Mutex* mutex);\nvoid mutex_free(Mutex* mutex);\n\nThread thread_new(ThreadEntry entry_func, ThreadCleanup cleanup_func);\nbool thread_start(Thread* thread, void* data);\nbool thread_is_running(Thread* thread);\nvoid thread_handle_stopping_state(Thread* thread);\nvoid thread_exit(Thread* thread, bool success);\nbool thread_stop(Thread* thread);\nThreadReturnCode thread_join(Thread* thread);\nThreadReturnCode thread_try_join(Thread* thread);\n\n#endif // THREAD_H\n"
  },
  {
    "path": "src/ui.c",
    "content": "// Scrap is a project that allows anyone to build software using simple, block based interface.\n//\n// Copyright (C) 2024-2026 Grisshink\n// \n// This software is provided 'as-is', without any express or implied\n// warranty.  In no event will the authors be held liable for any damages\n// arising from the use of this software.\n// \n// Permission is granted to anyone to use this software for any purpose,\n// including commercial applications, and to alter it and redistribute it\n// freely, subject to the following restrictions:\n// \n// 1. The origin of this software must not be misrepresented; you must not\n//    claim that you wrote the original software. If you use this software\n//    in a product, an acknowledgment in the product documentation would be\n//    appreciated but is not required.\n// 2. Altered source versions must be plainly marked as such, and must not be\n//    misrepresented as being the original software.\n// 3. This notice may not be removed or altered from any source distribution.\n\n#include \"scrap.h\"\n#include \"term.h\"\n#include \"../external/tinyfiledialogs.h\"\n#include \"vec.h\"\n#include \"util.h\"\n\n#include <assert.h>\n#include <math.h>\n#include <wctype.h>\n#include <libintl.h>\n#include <stdio.h>\n#include <string.h>\n\ntypedef enum {\n    FILE_MENU_NEW_PROJECT = 0,\n    FILE_MENU_SAVE_PROJECT,\n    FILE_MENU_LOAD_PROJECT,\n} FileMenuInds;\n\nchar* file_menu_list[] = {\n    \"New project\",\n    \"Save project\",\n    \"Load project\",\n};\n\n// Divides the panel into two parts along the specified side with the specified split percentage\nvoid panel_split(PanelTree* panel, SplitSide side, PanelType new_panel_type, float split_percent) {\n    if (panel->type == PANEL_SPLIT) return;\n\n    PanelTree* old_panel = malloc(sizeof(PanelTree));\n    old_panel->type = panel->type;\n    old_panel->left = NULL;\n    old_panel->right = NULL;\n    old_panel->parent = panel;\n\n    PanelTree* new_panel = malloc(sizeof(PanelTree));\n    new_panel->type = new_panel_type;\n    new_panel->left = NULL;\n    new_panel->right = NULL;\n    new_panel->parent = panel;\n\n    panel->type = PANEL_SPLIT;\n\n    switch (side) {\n    case SPLIT_SIDE_TOP:\n        panel->direction = DIRECTION_VERTICAL;\n        panel->left = new_panel;\n        panel->right = old_panel;\n        panel->split_percent = split_percent;\n        break;\n    case SPLIT_SIDE_BOTTOM:\n        panel->direction = DIRECTION_VERTICAL;\n        panel->left = old_panel;\n        panel->right = new_panel;\n        panel->split_percent = 1.0 - split_percent;\n        break;\n    case SPLIT_SIDE_LEFT:\n        panel->direction = DIRECTION_HORIZONTAL;\n        panel->left = new_panel;\n        panel->right = old_panel;\n        panel->split_percent = split_percent;\n        break;\n    case SPLIT_SIDE_RIGHT:\n        panel->direction = DIRECTION_HORIZONTAL;\n        panel->left = old_panel;\n        panel->right = new_panel;\n        panel->split_percent = 1.0 - split_percent;\n        break;\n    case SPLIT_SIDE_NONE:\n        assert(false && \"Got SPLIT_SIDE_NONE\");\n        break;\n    default:\n        assert(false && \"Got unknown split side\");\n        break;\n    }\n}\n\nPanelTree* panel_new(PanelType type) {\n    PanelTree* panel = malloc(sizeof(PanelTree));\n    panel->type = type;\n    panel->left = NULL;\n    panel->right = NULL;\n    panel->parent = NULL;\n    return panel;\n}\n\n// Removes a panel and its child panels recursively, freeing memory\nvoid panel_delete(PanelTree* panel) {\n    assert(panel != NULL);\n\n    if (panel->type == PANEL_SPLIT) {\n        panel_delete(panel->left);\n        panel_delete(panel->right);\n        panel->left = NULL;\n        panel->right = NULL;\n    }\n\n    panel->type = PANEL_NONE;\n    free(panel);\n}\n\n// Removes a tab by index and frees its resources\nvoid tab_delete(size_t tab) {\n    assert(tab < vector_size(editor.tabs));\n    panel_delete(editor.tabs[tab].root_panel);\n    vector_free(editor.tabs[tab].name);\n    vector_remove(editor.tabs, tab);\n    if (editor.current_tab >= (int)vector_size(editor.tabs)) editor.current_tab = vector_size(editor.tabs) - 1;\n}\n\nvoid delete_all_tabs(void) {\n    for (ssize_t i = vector_size(editor.tabs) - 1; i >= 0; i--) tab_delete(i);\n}\n\n// Creates a new tab with the given name and panel, adding it to the list of tabs\nsize_t tab_new(char* name, PanelTree* root_panel) {\n    if (!root_panel) {\n        scrap_log(LOG_WARNING, \"Got root_panel == NULL, not adding\");\n        return -1;\n    }\n\n    Tab* tab = vector_add_dst(&editor.tabs);\n    tab->name = vector_create();\n    for (char* str = name; *str; str++) vector_add(&tab->name, *str);\n    vector_add(&tab->name, 0);\n    tab->root_panel = root_panel;\n\n    return vector_size(editor.tabs) - 1;\n}\n\n// Inserts a new tab with the given name and panel at the specified position in the list of tabs\nvoid tab_insert(char* name, PanelTree* root_panel, size_t position) {\n    if (!root_panel) {\n        scrap_log(LOG_WARNING, \"Got root_panel == NULL, not adding\");\n        return;\n    }\n\n    Tab* tab = vector_insert_dst(&editor.tabs, position);\n    tab->name = vector_create();\n    for (char* str = name; *str; str++) vector_add(&tab->name, *str);\n    vector_add(&tab->name, 0);\n    tab->root_panel = root_panel;\n}\n\n// Initializes codespace, using a default panel layout\nvoid init_panels(void) {\n    PanelTree* code_panel = panel_new(PANEL_CODE);\n    panel_split(code_panel, SPLIT_SIDE_LEFT, PANEL_BLOCK_PALETTE, 0.3);\n    panel_split(code_panel->left, SPLIT_SIDE_TOP, PANEL_BLOCK_CATEGORIES, 0.35);\n    tab_new(\"Code\", code_panel);\n    tab_new(\"Output\", panel_new(PANEL_TERM));\n}\n\nint search_glyph(Font font, int codepoint) {\n    // We assume that ASCII region is the first region, so this index should correspond to char '?' in the glyph table\n    const int fallback = 31;\n    for (int i = 0; i < CODEPOINT_REGION_COUNT; i++) {\n        if (codepoint < codepoint_regions[i][0] || codepoint > codepoint_regions[i][1]) continue;\n        int glyph = codepoint - codepoint_regions[i][0] + codepoint_start_ranges[i];\n        if (glyph >= font.glyphCount) return fallback;\n        return glyph;\n    }\n    return fallback;\n}\n\nstatic GuiMeasurement measure_slice(Font font, const char *text, unsigned int text_size, float font_size) {\n    GuiMeasurement ms = {0};\n\n    if ((font.texture.id == 0) || !text) return ms;\n\n    int codepoint = 0; // Current character\n    int index = 0; // Index position in sprite font\n\n    for (unsigned int i = 0; i < text_size;) {\n        if (!text[i]) break;\n        int next = 0;\n        codepoint = GetCodepointNext(&text[i], &next);\n        index = search_glyph(font, codepoint);\n        i += next;\n\n        if (font.glyphs[index].advanceX != 0) {\n            ms.w += font.glyphs[index].advanceX;\n        } else {\n            ms.w += font.recs[index].width + font.glyphs[index].offsetX;\n        }\n    }\n\n    ms.w *= font_size / (float)font.baseSize;\n    ms.h = font_size;\n    return ms;\n}\n\nGuiMeasurement scrap_gui_measure_image(void* image, unsigned short size) {\n    Texture2D* img = image;\n    return (GuiMeasurement) { img->width * ((float)size / (float)img->height), size };\n}\n\nGuiMeasurement scrap_gui_measure_text(void* font, const char* text, unsigned int text_size, unsigned short font_size) {\n    return measure_slice(*(Font*)font, text, text_size, font_size);\n}\n\nTermVec term_measure_text(void* font, const char* text, unsigned int text_size, unsigned short font_size) {\n    GuiMeasurement m = measure_slice(*(Font*)font, text, text_size, font_size);\n    return (TermVec) { .x = m.w, .y = m.h };\n}\n\n#ifdef DEBUG\nstatic void sanitize_block(Block* block) {\n    for (vec_size_t i = 0; i < vector_size(block->arguments); i++) {\n        if (block->arguments[i].type != ARGUMENT_BLOCK) continue;\n        if (block->arguments[i].data.block.parent != block) {\n            scrap_log(LOG_ERROR, \"Block %p detached from parent %p! (Got %p)\", &block->arguments[i].data.block, block, block->arguments[i].data.block.parent);\n            assert(false);\n            return;\n        }\n        sanitize_block(&block->arguments[i].data.block);\n    }\n}\n\nstatic void sanitize_links(void) {\n    for (vec_size_t i = 0; i < vector_size(editor.code); i++) {\n        Block* blocks = editor.code[i].blocks;\n        for (vec_size_t j = 0; j < vector_size(blocks); j++) {\n            sanitize_block(&blocks[j]);\n        }\n    }\n\n    for (size_t i = 0; i < vector_size(editor.mouse_blockchains); i++) {\n        BlockChain* chain = &editor.mouse_blockchains[i];\n        for (size_t j = 0; j < vector_size(chain->blocks); j++) {\n            sanitize_block(&chain->blocks[j]);\n        }\n    }\n}\n#endif\n\nstatic void switch_tab_to_panel(PanelType panel) {\n    for (size_t i = 0; i < vector_size(editor.tabs); i++) {\n        if (find_panel(editor.tabs[i].root_panel, panel)) {\n            if (editor.current_tab != (int)i) ui.shader_time = 0.0;\n            editor.current_tab = i;\n            ui.render_surface_needs_redraw = true;\n            return;\n        }\n    }\n}\n\nstatic void set_mark(void) {\n    if (IsKeyDown(KEY_LEFT_SHIFT) || IsKeyDown(KEY_RIGHT_SHIFT)) {\n        if (ui.hover.select_input_mark == -1) ui.hover.select_input_mark = ui.hover.select_input_cursor;\n    } else {\n        ui.hover.select_input_mark = -1;\n    }\n}\n\nstatic void copy_text(char* text, int start, int end) {\n    char* clipboard = vector_create();\n    for (int i = start; i < end; i++) vector_add(&clipboard, text[i]);\n    vector_add(&clipboard, 0);\n\n    SetClipboardText(clipboard);\n    vector_free(clipboard);\n}\n\nstatic void delete_region(char** text) {\n    if (ui.hover.select_input_mark == -1) return;\n\n    int remove_pos  = MIN(ui.hover.select_input_cursor, ui.hover.select_input_mark),\n        remove_size = ABS(ui.hover.select_input_cursor - ui.hover.select_input_mark);\n    ui.hover.select_input_mark = -1;\n    ui.hover.select_input_cursor = remove_pos;\n    vector_erase(*text, remove_pos, remove_size);\n    ui.render_surface_needs_redraw = true;\n}\n\nstatic bool edit_text(char** text) {\n    if (!text) return false;\n\n    if (IsKeyPressed(KEY_HOME)) {\n        set_mark();\n        ui.hover.select_input_cursor = 0;\n        ui.render_surface_needs_redraw = true;\n        return false;\n    }\n\n    if (IsKeyPressed(KEY_END)) {\n        set_mark();\n        ui.hover.select_input_cursor = vector_size(*text) - 1;\n        ui.render_surface_needs_redraw = true;\n        return false;\n    }\n\n    if ((IsKeyDown(KEY_LEFT_CONTROL) || IsKeyDown(KEY_RIGHT_CONTROL)) && IsKeyPressed(KEY_A)) {\n        ui.hover.select_input_cursor = 0;\n        ui.hover.select_input_mark = strlen(*text);\n        ui.render_surface_needs_redraw = true;\n        return false;\n    }\n\n    if ((IsKeyDown(KEY_LEFT_CONTROL) || IsKeyDown(KEY_RIGHT_CONTROL)) && IsKeyPressed(KEY_U)) {\n        vector_clear(*text);\n        vector_add(text, 0);\n        ui.hover.select_input_cursor = 0;\n        ui.hover.select_input_mark = -1;\n        ui.render_surface_needs_redraw = true;\n        return true;\n    }\n\n    if ((IsKeyDown(KEY_LEFT_CONTROL) || IsKeyDown(KEY_RIGHT_CONTROL)) && IsKeyPressed(KEY_C)) {\n        if (ui.hover.select_input_mark != -1) {\n            copy_text(*text, MIN(ui.hover.select_input_cursor, ui.hover.select_input_mark),\n                             MAX(ui.hover.select_input_cursor, ui.hover.select_input_mark));\n        }\n        return false;\n    }\n\n    if ((IsKeyDown(KEY_LEFT_CONTROL) || IsKeyDown(KEY_RIGHT_CONTROL)) && IsKeyPressed(KEY_V)) {\n        const char* clipboard = GetClipboardText();\n        if (clipboard) {\n            delete_region(text);\n\n            for (int i = 0; clipboard[i]; i++) {\n                if (clipboard[i] == '\\n' || clipboard[i] == '\\r') continue;\n                vector_insert(text, ui.hover.select_input_cursor++, clipboard[i]);\n            }\n            ui.render_surface_needs_redraw = true;\n            return true;\n        }\n        return false;\n    }\n\n    if ((IsKeyDown(KEY_LEFT_CONTROL) || IsKeyDown(KEY_RIGHT_CONTROL)) && IsKeyPressed(KEY_X)) {\n        if (ui.hover.select_input_mark != -1) {\n            int sel_start = MIN(ui.hover.select_input_cursor, ui.hover.select_input_mark),\n                sel_end   = MAX(ui.hover.select_input_cursor, ui.hover.select_input_mark);\n\n            copy_text(*text, sel_start, sel_end);\n            delete_region(text);\n            return true;\n        }\n        return false;\n    }\n\n    if (IsKeyPressed(KEY_LEFT) || IsKeyPressedRepeat(KEY_LEFT)) {\n        set_mark();\n        ui.hover.select_input_cursor--;\n        if (ui.hover.select_input_cursor < 0) {\n            ui.hover.select_input_cursor = 0;\n        } else {\n            while (((unsigned char)(*text)[ui.hover.select_input_cursor] >> 6) == 2) ui.hover.select_input_cursor--;\n        }\n        ui.render_surface_needs_redraw = true;\n        return false;\n    }\n\n    if (IsKeyPressed(KEY_RIGHT) || IsKeyPressedRepeat(KEY_RIGHT)) {\n        set_mark();\n        ui.hover.select_input_cursor++;\n        if (ui.hover.select_input_cursor >= (int)vector_size(*text)) {\n            ui.hover.select_input_cursor = vector_size(*text) - 1;\n        } else {\n            while (((unsigned char)(*text)[ui.hover.select_input_cursor] >> 6) == 2) ui.hover.select_input_cursor++;\n        }\n        ui.render_surface_needs_redraw = true;\n        return false;\n    }\n\n    if (IsKeyPressed(KEY_DELETE) || IsKeyPressedRepeat(KEY_DELETE)) {\n        if (vector_size(*text) <= 1 || (ui.hover.select_input_cursor == (int)vector_size(*text) - 1 && ui.hover.select_input_mark == -1)) return false;\n\n        if (ui.hover.select_input_mark != -1) {\n            delete_region(text);\n        } else {\n            int remove_pos = ui.hover.select_input_cursor;\n            int remove_size;\n            GetCodepointNext(*text + remove_pos, &remove_size);\n            vector_erase(*text, remove_pos, remove_size);\n            ui.render_surface_needs_redraw = true;\n        }\n        return true;\n    }\n\n    if (IsKeyPressed(KEY_BACKSPACE) || IsKeyPressedRepeat(KEY_BACKSPACE)) {\n        if (vector_size(*text) <= 1 || (ui.hover.select_input_cursor == 0 && ui.hover.select_input_mark == -1)) {\n            return false;\n        }\n\n        if (ui.hover.select_input_mark != -1) {\n            delete_region(text);\n        } else {\n            int remove_pos = ui.hover.select_input_cursor - 1;\n            int remove_size = 1;\n            while (((unsigned char)(*text)[remove_pos] >> 6) == 2) { // This checks if we are in the middle of UTF-8 char\n                remove_pos--;\n                remove_size++;\n            }\n            ui.hover.select_input_cursor -= remove_size;\n            vector_erase(*text, remove_pos, remove_size);\n            ui.render_surface_needs_redraw = true;\n        }\n\n        return true;\n    }\n\n    bool input_changed = false;\n    int char_val;\n    while ((char_val = GetCharPressed())) {\n        delete_region(text);\n        int utf_size = 0;\n        const char* utf_char = CodepointToUTF8(char_val, &utf_size);\n        for (int i = 0; i < utf_size; i++) {\n            vector_insert(text, ui.hover.select_input_cursor++, utf_char[i]);\n        }\n        input_changed = true;\n        ui.render_surface_needs_redraw = true;\n    }\n    return input_changed;\n}\n\nPanelTree* find_panel(PanelTree* root, PanelType panel) {\n    if (root->type == panel) return root;\n    if (root->type == PANEL_SPLIT) {\n        PanelTree* out = NULL;\n        out = find_panel(root->left, panel);\n        if (out) return out;\n        out = find_panel(root->right, panel);\n        if (out) return out;\n    }\n\n    return NULL;\n}\n\nstatic void deselect_all(void) {\n    ui.hover.editor.select_argument = NULL;\n    ui.hover.select_input = NULL;\n    if (ui.dropdown.type == DROPDOWN_LIST) ui.dropdown.as.list.scroll = 0;\n}\n\nvoid show_dropdown(DropdownType type, void* ref_object, ButtonClickHandler handler) {\n    ui.dropdown.ref_object = ref_object;\n    ui.dropdown.handler = handler;\n    ui.dropdown.shown = true;\n    ui.dropdown.type = type;\n}\n\nvoid show_list_dropdown(char** list, int list_len, void* ref_object, ButtonClickHandler handler) {\n    show_dropdown(DROPDOWN_LIST, ref_object, handler);\n    ui.dropdown.as.list.data = list;\n    ui.dropdown.as.list.len = list_len;\n    ui.dropdown.as.list.select_ind = 0;\n    ui.dropdown.as.list.scroll = 0;\n}\n\nvoid show_color_picker_dropdown(Color* edit_color, void* ref_object, ButtonClickHandler handler) {\n    assert(edit_color != NULL);\n\n    show_dropdown(DROPDOWN_COLOR_PICKER, ref_object, handler);\n\n    Vector3 hsv_vec = ColorToHSV(*edit_color);\n    HSV hsv = (HSV) { hsv_vec.x, hsv_vec.y, hsv_vec.z };\n\n    ui.dropdown.as.color_picker.hover_part  = COLOR_PICKER_NONE;\n    ui.dropdown.as.color_picker.select_part = COLOR_PICKER_NONE;\n    ui.dropdown.as.color_picker.color = hsv;\n    ui.dropdown.as.color_picker.edit_color = edit_color;\n    ui.dropdown.as.color_picker.color_hex[0] = 0;\n}\n\nbool handle_dropdown_close(void) {\n    memset(&ui.dropdown.as, 0, sizeof(ui.dropdown.as));\n    ui.dropdown.ref_object = NULL;\n    ui.dropdown.handler = NULL;\n    ui.dropdown.shown = false;\n\n    ui.hover.editor.select_block = NULL;\n    ui.hover.select_input = NULL;\n    ui.hover.editor.select_argument = NULL;\n    return true;\n}\n\nstatic char* get_basename(char* path) {\n    char* base_name = path;\n    for (char* str = path; *str; str++) {\n        if (*str == '/' || *str == '\\\\') {\n            base_name = str + 1;\n        }\n    }\n    return base_name;\n}\n\nbool save_project(void) {\n    char const* filters[] = {\"*.scrp\"};\n\n    char* path = tinyfd_saveFileDialog(NULL, editor.project_name, ARRLEN(filters), filters, \"Scrap project files (.scrp)\");\n    if (!path) return false;\n    save_code(path, &project_config, editor.code);\n\n    char* base_path = get_basename(path);\n    int i;\n    for (i = 0; base_path[i]; i++) editor.project_name[i] = base_path[i];\n    editor.project_name[i] = 0;\n    editor.project_modified = false;\n    return true;\n}\n\nvoid load_project(void) {\n    char const* filters[] = {\"*.scrp\"};\n\n    char* path = tinyfd_openFileDialog(NULL, editor.project_name, ARRLEN(filters), filters, \"Scrap project files (.scrp)\", 0);\n    if (!path) return;\n\n    ProjectConfig new_config;\n    BlockChain* chain = load_code(path, &new_config);\n    switch_tab_to_panel(PANEL_CODE);\n    if (!chain) {\n        actionbar_show(gettext(\"File load failed :(\"));\n        return;\n    }\n\n    project_config_free(&project_config);\n    project_config = new_config;\n\n    for (size_t i = 0; i < vector_size(editor.code); i++) blockchain_free(&editor.code[i]);\n    vector_free(editor.code);\n    vm.compile_error_block = NULL;\n    vm.compile_error_blockchain = NULL;\n    editor.code = chain;\n\n    editor.blockchain_select_counter = 0;\n    editor.camera_pos.x = editor.code[editor.blockchain_select_counter].x - 50;\n    editor.camera_pos.y = editor.code[editor.blockchain_select_counter].y - 50;\n\n    char* base_path = get_basename(path);\n\n    int i;\n    for (i = 0; base_path[i]; i++) editor.project_name[i] = base_path[i];\n    editor.project_name[i] = 0;\n\n    actionbar_show(gettext(\"File load succeeded!\"));\n    editor.project_modified = false;\n}\n\nbool handle_file_menu_click(void) {\n    assert(ui.dropdown.type == DROPDOWN_LIST);\n\n    switch (ui.dropdown.as.list.select_ind) {\n    case FILE_MENU_NEW_PROJECT:\n        for (size_t i = 0; i < vector_size(editor.code); i++) blockchain_free(&editor.code[i]);\n        vector_clear(editor.code);\n        switch_tab_to_panel(PANEL_CODE);\n        editor.project_modified = false;\n        break;\n    case FILE_MENU_SAVE_PROJECT:\n        save_project();\n        break;\n    case FILE_MENU_LOAD_PROJECT:\n        load_project();\n        break;\n    default:\n        printf(\"idk\\n\");\n        break;\n    }\n    return handle_dropdown_close();\n}\n\nbool handle_block_dropdown_click(void) {\n    assert(ui.dropdown.type == DROPDOWN_LIST);\n    argument_set_const_string(ui.hover.editor.select_argument, ui.dropdown.as.list.data[ui.dropdown.as.list.select_ind]);\n    return handle_dropdown_close();\n}\n\nbool handle_color_picker_click(void) {\n    ui.dropdown.as.color_picker.select_part = ui.dropdown.as.color_picker.hover_part;\n    return true;\n}\n\nbool handle_file_button_click(void) {\n    if (thread_is_running(&vm.thread)) return true;\n    show_list_dropdown(file_menu_list, ARRLEN(file_menu_list), NULL, handle_file_menu_click);\n    return true;\n}\n\nbool handle_settings_button_click(void) {\n    gui_window_show(draw_settings_window);\n    return true;\n}\n\nbool handle_about_button_click(void) {\n    gui_window_show(draw_about_window);\n    return true;\n}\n\nbool handle_run_button_click(void) {\n#ifdef USE_INTERPRETER\n    vm_start();\n#else\n    vm_start(COMPILER_MODE_JIT);\n#endif\n    return true;\n}\n\nbool handle_build_button_click(void) {\n    if (thread_is_running(&vm.thread)) return true;\n    gui_window_show(draw_project_settings_window);\n    return true;\n}\n\nbool handle_stop_button_click(void) {\n    vm_stop();\n    return true;\n}\n\nbool handle_category_click(void) {\n    editor.palette.current_category = ui.hover.category;\n    return true;\n}\n\nbool handle_jump_to_block_button_click(void) {\n    ui.hover.editor.select_block = vm.compile_error_block;\n    ui.hover.editor.select_blockchain = vm.compile_error_blockchain;\n    return true;\n}\n\nbool handle_error_window_close_button_click(void) {\n    clear_compile_error();\n    return true;\n}\n\nbool handle_tab_button(void) {\n    editor.current_tab = (int)(size_t)ui.hover.button.data;\n    ui.shader_time = 0.0;\n    return true;\n}\n\nbool handle_add_tab_button(void) {\n    char* name = \"\";\n    switch (ui.hover.panels.mouse_panel) {\n    case PANEL_NONE:\n        name = \"Unknown\";\n        break;\n    case PANEL_CODE:\n        name = \"Code\";\n        break;\n    case PANEL_BLOCK_PALETTE:\n        name = \"Block editor.palette\";\n        break;\n    case PANEL_TERM:\n        name = \"Output\";\n        break;\n    case PANEL_BLOCK_CATEGORIES:\n        name = \"Block categories\";\n        break;\n    case PANEL_SPLIT:\n        name = \"Multiple...\";\n        break;\n    }\n\n    tab_insert(name, panel_new(ui.hover.panels.mouse_panel), (int)(size_t)ui.hover.button.data);\n\n    ui.hover.panels.mouse_panel = PANEL_NONE;\n    editor.current_tab = (int)(size_t)ui.hover.button.data;\n    ui.shader_time = 0.0;\n    return true;\n}\n\nbool handle_panel_editor_save_button(void) {\n    ui.hover.is_panel_edit_mode = false;\n    save_config(&config);\n    return true;\n}\n\nbool handle_panel_editor_cancel_button(void) {\n    ui.hover.is_panel_edit_mode = false;\n    return true;\n}\n\nbool handle_editor_add_arg_button(void) {\n    Blockdef* blockdef = ui.hover.editor.argument->data.blockdef;\n    size_t last_input = vector_size(blockdef->inputs);\n    char str[32];\n\n    // TODO: Update block arguments when new argument is added\n    if (blockdef->ref_count > 1) {\n        deselect_all();\n        return true;\n    }\n    for (size_t i = 0; i < vector_size(blockdef->inputs); i++) {\n        if (blockdef->inputs[i].type != INPUT_ARGUMENT) continue;\n        if (blockdef->inputs[i].data.arg.blockdef->ref_count > 1) {\n            deselect_all();\n            return true;\n        }\n    }\n\n    blockdef_add_argument(blockdef, \"\", gettext(\"any\"), BLOCKCONSTR_UNLIMITED);\n\n    sprintf(str, \"arg%zu\", last_input);\n    Blockdef* arg_blockdef = blockdef->inputs[last_input].data.arg.blockdef;\n    blockdef_add_text(arg_blockdef, str);\n    arg_blockdef->func = block_custom_arg;\n\n    deselect_all();\n    return true;\n}\n\nbool handle_editor_add_text_button(void) {\n    Blockdef* blockdef = ui.hover.editor.argument->data.blockdef;\n    size_t last_input = vector_size(blockdef->inputs);\n    char str[32];\n\n    // TODO: Update block arguments when new argument is added\n    if (blockdef->ref_count > 1) {\n        deselect_all();\n        return true;\n    }\n    for (size_t i = 0; i < vector_size(blockdef->inputs); i++) {\n        if (blockdef->inputs[i].type != INPUT_ARGUMENT) continue;\n        if (blockdef->inputs[i].data.arg.blockdef->ref_count > 1) {\n            deselect_all();\n            return true;\n        }\n    }\n\n    sprintf(str, \"text%zu\", last_input);\n    blockdef_add_text(blockdef, str);\n\n    deselect_all();\n    return true;\n}\n\nbool handle_editor_del_arg_button(void) {\n    Blockdef* blockdef = ui.hover.editor.argument->data.blockdef;\n\n    assert(ui.hover.editor.blockdef_input != (size_t)-1);\n    if (blockdef->ref_count > 1) {\n        deselect_all();\n        return true;\n    }\n    for (size_t i = 0; i < vector_size(blockdef->inputs); i++) {\n        if (blockdef->inputs[i].type != INPUT_ARGUMENT) continue;\n        if (blockdef->inputs[i].data.arg.blockdef->ref_count > 1) {\n            deselect_all();\n            return true;\n        }\n    }\n\n    blockdef_delete_input(blockdef, ui.hover.editor.blockdef_input);\n\n    deselect_all();\n    return true;\n}\n\nbool handle_editor_edit_button(void) {\n    ui.hover.editor.edit_blockdef = ui.hover.editor.argument->data.blockdef;\n    ui.hover.editor.edit_block = ui.hover.editor.block;\n    deselect_all();\n    return true;\n}\n\nbool handle_editor_close_button(void) {\n    ui.hover.editor.edit_blockdef = NULL;\n    ui.hover.editor.edit_block = NULL;\n    deselect_all();\n    return true;\n}\n\nbool handle_editor_color_button(void) {\n    assert(ui.hover.editor.edit_blockdef != NULL);\n\n    show_color_picker_dropdown((Color*)&ui.hover.editor.edit_blockdef->color, &ui.hover.editor.edit_blockdef->color, NULL);\n    return true;\n}\n\nstatic void remove_blockdef(BlockChain* chain) {\n    for (size_t i = 0; i < vector_size(chain->blocks); i++) {\n        for (size_t j = 0; j < vector_size(chain->blocks[i].arguments); j++) {\n            Argument* arg = &chain->blocks[i].arguments[j];\n            if (arg->type != ARGUMENT_BLOCKDEF) continue;\n            arg->data.blockdef->func = NULL;\n            for (size_t k = 0; k < vector_size(arg->data.blockdef->inputs); k++) {\n                Input* input = &arg->data.blockdef->inputs[k];\n                if (input->type != INPUT_ARGUMENT) continue;\n                input->data.arg.blockdef->func = NULL;\n            }\n        }\n    }\n}\n\nstatic bool handle_block_palette_click(bool mouse_empty) {\n    if (ui.hover.editor.select_argument) {\n        deselect_all();\n        return true;\n    }\n    bool shift_down = IsKeyDown(KEY_LEFT_SHIFT) || IsKeyDown(KEY_RIGHT_SHIFT);\n    if ((mouse_empty || shift_down) && ui.hover.editor.block) {\n        // Pickup block\n        scrap_log(LOG_INFO, \"Pickup block\");\n        assert(editor.palette.current_category != NULL);\n        vector_add(&editor.mouse_blockchains, blockchain_copy(ui.hover.editor.blockchain, 0));\n        return true;\n    } else if (!mouse_empty) {\n        // Drop block\n        scrap_log(LOG_INFO, \"Drop block\");\n        if (shift_down) {\n            for (size_t i = 0; i < vector_size(editor.mouse_blockchains); i++) {\n                remove_blockdef(&editor.mouse_blockchains[i]);\n                blockchain_free(&editor.mouse_blockchains[i]);\n            }\n            vector_clear(editor.mouse_blockchains);\n        } else {\n            remove_blockdef(&editor.mouse_blockchains[0]);\n            blockchain_free(&editor.mouse_blockchains[0]);\n            vector_remove(editor.mouse_blockchains, 0);\n        }\n        return true;\n    }\n    return true;\n}\n\nstatic bool handle_blockdef_editor_click(void) {\n    // Pickup blockdef\n    scrap_log(LOG_INFO, \"Pickup blockdef\");\n\n    if (!ui.hover.editor.blockdef) return true;\n    if (ui.hover.editor.edit_blockdef == ui.hover.editor.argument->data.blockdef) return false;\n\n    vector_add(&editor.mouse_blockchains, blockchain_new());\n    blockchain_add_block(&editor.mouse_blockchains[vector_size(editor.mouse_blockchains) - 1], block_new_ms(ui.hover.editor.blockdef));\n    deselect_all();\n    return true;\n}\n\nstatic void code_put_blocks(bool single) {\n    scrap_log(LOG_INFO, \"Put block(s)\");\n\n    if (single) {\n        BlockChain* chain = &editor.mouse_blockchains[0];\n        chain->x += editor.camera_pos.x - ui.hover.panels.panel_size.x;\n        chain->y += editor.camera_pos.y - ui.hover.panels.panel_size.y;\n        chain->x /= config.ui_size / 32.0;\n        chain->y /= config.ui_size / 32.0;\n        vector_add(&editor.code, *chain);\n        vector_remove(editor.mouse_blockchains, 0);\n    } else {\n        for (size_t i = 0; i < vector_size(editor.mouse_blockchains); i++) {\n            BlockChain* chain = &editor.mouse_blockchains[i];\n            chain->x += editor.camera_pos.x - ui.hover.panels.panel_size.x;\n            chain->y += editor.camera_pos.y - ui.hover.panels.panel_size.y;\n            chain->x /= config.ui_size / 32.0;\n            chain->y /= config.ui_size / 32.0;\n            vector_add(&editor.code, *chain);\n        }\n        vector_clear(editor.mouse_blockchains);\n    }\n\n    ui.hover.editor.select_blockchain = &editor.code[vector_size(editor.code) - 1];\n    ui.hover.editor.select_block = &ui.hover.editor.select_blockchain->blocks[0];\n    editor.project_modified = true;\n}\n\nstatic void code_attach_to_argument(void) {\n    scrap_log(LOG_INFO, \"Attach to argument\");\n\n    BlockChain* chain = &editor.mouse_blockchains[0];\n\n    if (vector_size(chain->blocks) > 1) return;\n    if (chain->blocks[0].blockdef->type == BLOCKTYPE_CONTROLEND) return;\n    if (chain->blocks[0].blockdef->type == BLOCKTYPE_HAT) return;\n    if (ui.hover.editor.argument->type != ARGUMENT_TEXT && ui.hover.editor.argument->type != ARGUMENT_COLOR) return;\n\n    chain->blocks[0].parent = ui.hover.editor.block;\n    argument_set_block(ui.hover.editor.argument, chain->blocks[0]);\n\n    vector_clear(chain->blocks);\n    blockchain_free(chain);\n    vector_remove(editor.mouse_blockchains, 0);\n\n    ui.hover.editor.select_blockchain = ui.hover.editor.blockchain;\n    ui.hover.editor.select_block = &ui.hover.editor.argument->data.block;\n    ui.hover.select_input = NULL;\n    editor.project_modified = true;\n}\n\nstatic void code_copy_argument(void) {\n    scrap_log(LOG_INFO, \"Copy argument\");\n\n    vector_add(&editor.mouse_blockchains, blockchain_new());\n    blockchain_add_block(&editor.mouse_blockchains[vector_size(editor.mouse_blockchains) - 1], block_copy(ui.hover.editor.block, NULL));\n}\n\nstatic void code_swap_argument(void) {\n    scrap_log(LOG_INFO, \"Swap argument\");\n\n    BlockChain* chain = &editor.mouse_blockchains[0];\n\n    if (vector_size(chain->blocks) > 1) return;\n    if (chain->blocks[0].blockdef->type == BLOCKTYPE_CONTROLEND) return;\n    if (chain->blocks[0].blockdef->type == BLOCKTYPE_HAT) return;\n    if (ui.hover.editor.parent_argument->type != ARGUMENT_BLOCK) return;\n\n    chain->blocks[0].parent = ui.hover.editor.block->parent;\n    Block temp = chain->blocks[0];\n    chain->blocks[0] = *ui.hover.editor.block;\n    chain->blocks[0].parent = NULL;\n    block_update_parent_links(&chain->blocks[0]);\n\n    argument_set_block(ui.hover.editor.parent_argument, temp);\n\n    ui.hover.editor.select_block = &ui.hover.editor.parent_argument->data.block;\n    ui.hover.editor.select_blockchain = ui.hover.editor.blockchain;\n    editor.project_modified = true;\n}\n\nstatic void code_detach_argument(void) {\n    scrap_log(LOG_INFO, \"Detach argument\");\n\n    assert(ui.hover.editor.parent_argument != NULL);\n\n    vector_add(&editor.mouse_blockchains, blockchain_new());\n    BlockChain* chain = &editor.mouse_blockchains[vector_size(editor.mouse_blockchains) - 1];\n\n    blockchain_add_block(chain, *ui.hover.editor.block);\n    chain->blocks[0].parent = NULL;\n\n    if (ui.hover.editor.block->parent->blockdef->inputs[ui.hover.editor.parent_argument->input_id].type == INPUT_COLOR) {\n        argument_set_color(ui.hover.editor.parent_argument, (BlockdefColor) { 0xff, 0xff, 0xff, 0xff });\n    } else {\n        argument_set_text(ui.hover.editor.parent_argument, \"\");\n    }\n\n    ui.hover.editor.select_blockchain = NULL;\n    ui.hover.editor.select_block = NULL;\n    ui.hover.select_input = NULL;\n    editor.project_modified = true;\n}\n\nstatic void code_copy_blocks(bool single) {\n    scrap_log(LOG_INFO, \"Copy block(s)\");\n\n    int ind = ui.hover.editor.block - ui.hover.editor.blockchain->blocks;\n    BlockChain new_chain = single ? blockchain_copy_single(ui.hover.editor.blockchain, ind) : blockchain_copy(ui.hover.editor.blockchain, ind);\n    if (vector_size(new_chain.blocks) == 0) {\n        blockchain_free(&new_chain);\n        ui.hover.select_input = NULL;\n        return;\n    }\n    vector_add(&editor.mouse_blockchains, new_chain);\n    ui.hover.select_input = NULL;\n}\n\nstatic void code_detach_blocks(bool single) {\n    scrap_log(LOG_INFO, \"Detach block(s)\");\n\n    int ind = ui.hover.editor.block - ui.hover.editor.blockchain->blocks;\n    vector_add(&editor.mouse_blockchains, blockchain_new());\n    BlockChain* chain = &editor.mouse_blockchains[vector_size(editor.mouse_blockchains) - 1];\n\n    if (single) {\n        blockchain_detach_single(chain, ui.hover.editor.blockchain, ind);\n    } else {\n        blockchain_detach(chain, ui.hover.editor.blockchain, ind);\n    }\n    if (vector_size(chain->blocks) == 0) {\n        blockchain_free(chain);\n        vector_pop(editor.mouse_blockchains);\n    }\n\n    if (vector_size(ui.hover.editor.blockchain->blocks) == 0) {\n        blockchain_free(ui.hover.editor.blockchain);\n        vector_remove(editor.code, ui.hover.editor.blockchain - editor.code);\n        ui.hover.editor.block = NULL;\n    }\n\n    editor.project_modified = true;\n    ui.hover.select_input = NULL;\n}\n\nstatic void code_attach_block(void) {\n    scrap_log(LOG_INFO, \"Attach block\");\n\n    BlockChain* chain = &editor.mouse_blockchains[0];\n\n    if (chain->blocks[0].blockdef->type == BLOCKTYPE_HAT) return;\n\n    int ind = ui.hover.editor.block - ui.hover.editor.blockchain->blocks;\n    blockchain_insert(ui.hover.editor.blockchain, chain, ind);\n    blockchain_free(chain);\n    vector_remove(editor.mouse_blockchains, 0);\n\n    ui.hover.editor.block = &ui.hover.editor.blockchain->blocks[ind];\n    ui.hover.editor.select_block = ui.hover.editor.block + 1;\n    ui.hover.editor.select_blockchain = ui.hover.editor.blockchain;\n    editor.project_modified = true;\n}\n\nstatic bool handle_code_editor_click(bool mouse_empty) {\n    bool shift_down = IsKeyDown(KEY_LEFT_SHIFT) || IsKeyDown(KEY_RIGHT_SHIFT);\n    bool alt_down = IsKeyDown(KEY_LEFT_ALT) || IsKeyDown(KEY_RIGHT_ALT);\n    bool ctrl_down = IsKeyDown(KEY_LEFT_CONTROL) || IsKeyDown(KEY_RIGHT_CONTROL);\n\n    if (!mouse_empty && !ui.hover.editor.block) {\n        int x   = 0, \n            y   = 0,\n            x_i = 0,\n            x_i_max = ceil(sqrt(vector_size(editor.mouse_blockchains))),\n            y_max = 0;\n\n        for (size_t i = 0; i < vector_size(editor.mouse_blockchains); i++) {\n            editor.mouse_blockchains[i].x = gui->mouse_x + x;\n            editor.mouse_blockchains[i].y = gui->mouse_y + y;\n\n            x += editor.mouse_blockchains[i].width + config.ui_size;\n            x_i++;\n            y_max = MAX(y_max, editor.mouse_blockchains[i].height);\n\n            if (x_i >= x_i_max) {\n                x = 0;\n                y += y_max + config.ui_size;\n                y_max = 0;\n                x_i = 0;\n            }\n        }\n\n        code_put_blocks(!shift_down);\n        return true;\n    }\n    if (!ui.hover.editor.block || !ui.hover.editor.blockchain) return false;\n\n    if (ui.hover.editor.argument && !mouse_empty) {\n        code_attach_to_argument();\n        return true;\n    }\n\n    if (ui.hover.editor.block->parent) {\n        if (alt_down) {\n            code_copy_argument();\n            return true;\n        } \n\n        if (!mouse_empty && !shift_down) {\n            code_swap_argument();\n            return true;\n        }\n\n        code_detach_argument();\n        return true;\n    }\n\n    if (alt_down) {\n        code_copy_blocks(ctrl_down);\n        return true;\n    }\n\n    if (mouse_empty || shift_down) {\n        code_detach_blocks(ctrl_down);\n\n        ui.hover.editor.edit_blockdef = NULL;\n        ui.hover.editor.edit_block = NULL;\n        ui.hover.editor.select_blockchain = NULL;\n        ui.hover.editor.select_block = NULL;\n    } else {\n        code_attach_block();\n    }\n\n    return true;\n}\n\nstatic bool handle_editor_panel_click(void) {\n    if (!ui.hover.panels.panel) return true;\n\n    if (ui.hover.panels.panel->type == PANEL_SPLIT) {\n        ui.hover.panels.drag_panel = ui.hover.panels.panel;\n        ui.hover.panels.drag_panel_size = ui.hover.panels.panel_size;\n        return false;\n    }\n\n    if (ui.hover.panels.mouse_panel == PANEL_NONE) {\n        PanelTree* parent = ui.hover.panels.panel->parent;\n        if (!parent) {\n            if (vector_size(editor.tabs) > 1) {\n                ui.hover.panels.mouse_panel = ui.hover.panels.panel->type;\n                tab_delete(editor.current_tab);\n            }\n            return true;\n        }\n\n        ui.hover.panels.mouse_panel = ui.hover.panels.panel->type;\n        free(ui.hover.panels.panel);\n        PanelTree* other_panel = parent->left == ui.hover.panels.panel ? parent->right : parent->left;\n\n        parent->type = other_panel->type;\n        parent->split_percent = other_panel->split_percent;\n        parent->direction = other_panel->direction;\n        parent->left = other_panel->left;\n        parent->right = other_panel->right;\n        if (other_panel->type == PANEL_SPLIT) {\n            parent->left->parent = parent;\n            parent->right->parent = parent;\n        }\n        free(other_panel);\n    } else {\n        panel_split(ui.hover.panels.panel, ui.hover.panels.panel_side, ui.hover.panels.mouse_panel, 0.5);\n        ui.hover.panels.mouse_panel = PANEL_NONE;\n    }\n\n    return true;\n}\n\nstatic void get_input_ind(void) {\n    assert(ui.hover.input_info.font != NULL);\n    assert(ui.hover.input_info.input != NULL);\n\n    float width = 0.0;\n    float prev_width = 0.0;\n\n    int codepoint = 0; // Current character\n    int index = 0; // Index position in sprite font\n    int text_size = strlen(*ui.hover.input_info.input);\n    float scale_factor = ui.hover.input_info.font_size / (float)ui.hover.input_info.font->baseSize;\n\n    int prev_i = 0;\n    int i = 0;\n\n    while (i < text_size && (width * scale_factor) < ui.hover.input_info.rel_pos.x) {\n        int next = 0;\n        codepoint = GetCodepointNext(&(*ui.hover.input_info.input)[i], &next);\n        index = search_glyph(*ui.hover.input_info.font, codepoint);\n\n        prev_width = width;\n        prev_i = i;\n\n        if (ui.hover.input_info.font->glyphs[index].advanceX != 0) {\n            width += ui.hover.input_info.font->glyphs[index].advanceX;\n        } else {\n            width += ui.hover.input_info.font->recs[index].width + ui.hover.input_info.font->glyphs[index].offsetX;\n        }\n        i += next;\n    }\n    prev_width *= scale_factor;\n    width *= scale_factor;\n\n    if (width - ui.hover.input_info.rel_pos.x < ui.hover.input_info.rel_pos.x - prev_width) { // Right side of char is closer\n        ui.hover.select_input_cursor = i;\n    } else {\n        ui.hover.select_input_cursor = prev_i;\n    }\n    ui.hover.select_input_mark = -1;\n}\n\n// Return value indicates if we should cancel dragging\nstatic bool handle_mouse_click(void) {\n    ui.hover.mouse_click_pos = (Vector2) { gui->mouse_x, gui->mouse_y };\n    editor.camera_click_pos = editor.camera_pos;\n    ui.hover.dragged_slider.value = NULL;\n\n    if (ui.hover.select_input == &editor.search_list_search) {\n        if (ui.hover.editor.blockdef) {\n            vector_add(&editor.mouse_blockchains, blockchain_new());\n            BlockChain* chain = &editor.mouse_blockchains[vector_size(editor.mouse_blockchains) - 1];\n\n            blockchain_add_block(chain, block_new_ms(ui.hover.editor.blockdef));\n            if (ui.hover.editor.blockdef->type == BLOCKTYPE_CONTROL && vm.end_blockdef != (size_t)-1) {\n                blockchain_add_block(chain, block_new_ms(vm.blockdefs[vm.end_blockdef]));\n            }\n        }\n\n        ui.hover.select_input = NULL;\n        ui.hover.editor.block = NULL;\n        return true;\n    }\n\n    if (ui.hover.button.handler) return ui.hover.button.handler();\n    if (ui.hover.hover_slider.value) {\n        ui.hover.dragged_slider = ui.hover.hover_slider;\n        ui.hover.slider_last_val = *ui.hover.dragged_slider.value;\n        return false;\n    }\n    if (gui_window_is_shown()) {\n        if (ui.hover.input_info.input) get_input_ind();\n        if (ui.hover.input_info.input != ui.hover.select_input) ui.hover.select_input = ui.hover.input_info.input;\n        return true;\n    }\n    if (!ui.hover.panels.panel) return true;\n    if (ui.hover.is_panel_edit_mode) return handle_editor_panel_click();\n    if (ui.hover.panels.panel->type == PANEL_TERM) return true;\n    if (thread_is_running(&vm.thread)) return ui.hover.panels.panel->type != PANEL_CODE;\n\n    if (ui.hover.input_info.input) get_input_ind();\n    if (ui.hover.input_info.input != ui.hover.select_input) ui.hover.select_input = ui.hover.input_info.input;\n\n    bool mouse_empty = vector_size(editor.mouse_blockchains) == 0;\n\n    if (ui.hover.panels.panel->type == PANEL_BLOCK_PALETTE) return handle_block_palette_click(mouse_empty);\n\n    if (ui.hover.editor.argument && ui.hover.editor.argument->type == ARGUMENT_BLOCKDEF) {\n        if (handle_blockdef_editor_click()) return true;\n    }\n\n    if (ui.dropdown.shown && ui.dropdown.type == DROPDOWN_COLOR_PICKER) {\n        ui.dropdown.as.color_picker.select_part = COLOR_PICKER_NONE;\n    }\n\n    if (mouse_empty) {\n        if (ui.hover.editor.block && ui.hover.editor.argument) {\n            Input block_input = ui.hover.editor.block->blockdef->inputs[ui.hover.editor.argument->input_id];\n            if (block_input.type == INPUT_DROPDOWN) {\n                size_t list_len = 0;\n                char** list = block_input.data.drop.list(ui.hover.editor.block, &list_len);\n\n                show_list_dropdown(list, list_len, ui.hover.editor.argument, handle_block_dropdown_click);\n            } else if (block_input.type == INPUT_COLOR) {\n                show_color_picker_dropdown((Color*)&ui.hover.editor.argument->data.color, ui.hover.editor.argument, NULL);\n            }\n        }\n\n        if (ui.hover.editor.blockchain != ui.hover.editor.select_blockchain) {\n            ui.hover.editor.select_blockchain = ui.hover.editor.blockchain;\n            if (ui.hover.editor.select_blockchain) editor.blockchain_select_counter = ui.hover.editor.select_blockchain - editor.code;\n        }\n\n        if (ui.hover.editor.block != ui.hover.editor.select_block) {\n            ui.hover.editor.select_block = ui.hover.editor.block;\n        }\n\n        if (ui.hover.editor.argument != ui.hover.editor.select_argument) {\n            if (!ui.hover.editor.argument || ui.hover.input_info.input || ui.dropdown.shown) {\n                ui.hover.editor.select_argument = ui.hover.editor.argument;\n            }\n            if (ui.dropdown.type == DROPDOWN_LIST) ui.dropdown.as.list.scroll = 0;\n            return true;\n        }\n\n        if (ui.hover.editor.select_argument) {\n            return true;\n        }\n    }\n\n    if (ui.hover.panels.panel->type == PANEL_CODE && handle_code_editor_click(mouse_empty)) return true;\n    return ui.hover.panels.panel->type != PANEL_CODE;\n}\n\nstatic void block_next_argument() {\n    Argument* args = ui.hover.editor.select_block->arguments;\n    Argument* arg = ui.hover.editor.select_argument ? ui.hover.editor.select_argument + 1 : &args[0];\n    if (arg - args >= (int)vector_size(args)) {\n        if (ui.hover.editor.select_block->parent) {\n            Argument* parent_args = ui.hover.editor.select_block->parent->arguments;\n            for (size_t i = 0; i < vector_size(parent_args); i++) {\n                if (parent_args[i].type == ARGUMENT_BLOCK && &parent_args[i].data.block == ui.hover.editor.select_block) ui.hover.editor.select_argument = &parent_args[i];\n            }\n            ui.hover.editor.select_block = ui.hover.editor.select_block->parent;\n            block_next_argument();\n        } else {\n            ui.hover.editor.select_argument = NULL;\n        }\n        return;\n    }\n\n    if (arg->type == ARGUMENT_TEXT || arg->type == ARGUMENT_CONST_STRING) {\n        ui.hover.editor.select_argument = arg;\n    } else if (arg->type == ARGUMENT_BLOCK) {\n        ui.hover.editor.select_argument = NULL;\n        ui.hover.editor.select_block = &arg->data.block;\n    }\n}\n\nstatic void block_prev_argument() {\n    Argument* args = ui.hover.editor.select_block->arguments;\n    Argument* arg = ui.hover.editor.select_argument ? ui.hover.editor.select_argument - 1 : &args[-1];\n    if (arg - args < 0) {\n        if (ui.hover.editor.select_argument) {\n            ui.hover.editor.select_argument = NULL;\n            return;\n        }\n        if (ui.hover.editor.select_block->parent) {\n            Argument* parent_args = ui.hover.editor.select_block->parent->arguments;\n            for (size_t i = 0; i < vector_size(parent_args); i++) {\n                if (parent_args[i].type == ARGUMENT_BLOCK && &parent_args[i].data.block == ui.hover.editor.select_block) ui.hover.editor.select_argument = &parent_args[i];\n            }\n            ui.hover.editor.select_block = ui.hover.editor.select_block->parent;\n            block_prev_argument();\n        } else {\n            ui.hover.editor.select_argument = NULL;\n        }\n        return;\n    }\n\n    if (arg->type == ARGUMENT_TEXT || arg->type == ARGUMENT_CONST_STRING) {\n        ui.hover.editor.select_argument = arg;\n    } else if (arg->type == ARGUMENT_BLOCK) {\n        ui.hover.editor.select_argument = NULL;\n        ui.hover.editor.select_block = &arg->data.block;\n        while (vector_size(ui.hover.editor.select_block->arguments) != 0) {\n            arg = &ui.hover.editor.select_block->arguments[vector_size(ui.hover.editor.select_block->arguments) - 1];\n            if (arg->type == ARGUMENT_TEXT || arg->type == ARGUMENT_CONST_STRING) {\n                ui.hover.editor.select_argument = arg;\n                break;\n            } else if (arg->type == ARGUMENT_BLOCK) {\n                ui.hover.editor.select_block = &arg->data.block;\n            }\n        }\n    }\n}\n\nstatic bool handle_code_panel_key_press(void) {\n    if (ui.hover.editor.select_argument && !ui.hover.select_input) {\n        if (IsKeyPressed(KEY_ENTER) || IsKeyPressed(KEY_KP_ENTER)) {\n            ui.hover.select_input = &ui.hover.editor.select_argument->data.text;\n            ui.hover.select_input_mark = 0;\n            ui.hover.select_input_cursor = strlen(*ui.hover.select_input);\n            ui.render_surface_needs_redraw = true;\n            return true;\n        }\n    }\n\n    if (IsKeyPressed(KEY_TAB) && vector_size(editor.code) > 0) {\n        if (IsKeyDown(KEY_LEFT_SHIFT)) {\n            editor.blockchain_select_counter--;\n            if (editor.blockchain_select_counter < 0) editor.blockchain_select_counter = vector_size(editor.code) - 1;\n        } else {\n            editor.blockchain_select_counter++;\n            if ((vec_size_t)editor.blockchain_select_counter >= vector_size(editor.code)) editor.blockchain_select_counter = 0;\n        }\n\n        ui.hover.select_input = NULL;\n        ui.hover.editor.select_argument = NULL;\n        ui.hover.editor.select_block = &editor.code[editor.blockchain_select_counter].blocks[0];\n        ui.hover.editor.select_blockchain = &editor.code[editor.blockchain_select_counter];\n        editor.camera_pos.x = editor.code[editor.blockchain_select_counter].x - 50;\n        editor.camera_pos.y = editor.code[editor.blockchain_select_counter].y - 50;\n        actionbar_show(TextFormat(gettext(\"Jump to chain (%d/%d)\"), editor.blockchain_select_counter + 1, vector_size(editor.code)));\n        ui.render_surface_needs_redraw = true;\n        return true;\n    }\n\n    if (!ui.hover.editor.select_blockchain || !ui.hover.editor.select_block || ui.hover.select_input) return false;\n\n    int bounds_x = MIN(200, ui.hover.panels.code_panel_bounds.width / 2);\n    int bounds_y = MIN(200, ui.hover.panels.code_panel_bounds.height / 2);\n\n    if (ui.hover.editor.select_block_pos.x - (ui.hover.panels.code_panel_bounds.x + ui.hover.panels.code_panel_bounds.width) > -bounds_x) {\n        editor.camera_pos.x += ui.hover.editor.select_block_pos.x - (ui.hover.panels.code_panel_bounds.x + ui.hover.panels.code_panel_bounds.width) + bounds_x;\n        ui.render_surface_needs_redraw = true;\n    }\n\n    if (ui.hover.editor.select_block_pos.x - ui.hover.panels.code_panel_bounds.x < bounds_x) {\n        editor.camera_pos.x += ui.hover.editor.select_block_pos.x - ui.hover.panels.code_panel_bounds.x - bounds_x;\n        ui.render_surface_needs_redraw = true;\n    }\n\n    if (ui.hover.editor.select_block_pos.y - (ui.hover.panels.code_panel_bounds.y + ui.hover.panels.code_panel_bounds.height) > -bounds_y) {\n        editor.camera_pos.y += ui.hover.editor.select_block_pos.y - (ui.hover.panels.code_panel_bounds.y + ui.hover.panels.code_panel_bounds.height) + bounds_y;\n        ui.render_surface_needs_redraw = true;\n    }\n\n    if (ui.hover.editor.select_block_pos.y - ui.hover.panels.code_panel_bounds.y < bounds_y) {\n        editor.camera_pos.y += ui.hover.editor.select_block_pos.y - ui.hover.panels.code_panel_bounds.y - bounds_y;\n        ui.render_surface_needs_redraw = true;\n    }\n\n    if (IsKeyPressed(KEY_RIGHT) || IsKeyPressedRepeat(KEY_RIGHT)) {\n        block_next_argument();\n        ui.render_surface_needs_redraw = true;\n        return true;\n    }\n    if (IsKeyPressed(KEY_LEFT) || IsKeyPressedRepeat(KEY_LEFT)) {\n        block_prev_argument();\n        ui.render_surface_needs_redraw = true;\n        return true;\n    }\n    if (IsKeyPressed(KEY_UP) || IsKeyPressedRepeat(KEY_UP)) {\n        while (ui.hover.editor.select_block->parent) ui.hover.editor.select_block = ui.hover.editor.select_block->parent;\n        ui.hover.editor.select_block--;\n        ui.hover.editor.select_argument = NULL;\n        if (ui.hover.editor.select_block < ui.hover.editor.select_blockchain->blocks) ui.hover.editor.select_block = ui.hover.editor.select_blockchain->blocks;\n        ui.render_surface_needs_redraw = true;\n        return true;\n    }\n    if (IsKeyPressed(KEY_DOWN) || IsKeyPressedRepeat(KEY_DOWN)) {\n        while (ui.hover.editor.select_block->parent) ui.hover.editor.select_block = ui.hover.editor.select_block->parent;\n        ui.hover.editor.select_block++;\n        ui.hover.editor.select_argument = NULL;\n        if (ui.hover.editor.select_block - ui.hover.editor.select_blockchain->blocks >= (int)vector_size(ui.hover.editor.select_blockchain->blocks)) {\n            ui.hover.editor.select_block--;\n        }\n        ui.render_surface_needs_redraw = true;\n        return true;\n    }\n\n    return false;\n}\n\nstatic bool search_string(const char* str, const char* substr) {\n    if (*substr == 0) return true;\n\n    int next_ch, next_subch, cur_ch, cur_subch;\n    char* cur_substr = (char*)substr;\n    char* cur_str = (char*)str;\n\n    while (*cur_str != 0 && *cur_substr != 0) {\n        cur_ch = GetCodepointNext(cur_str, &next_ch);\n        cur_subch = GetCodepointNext(cur_substr, &next_subch);\n\n        if (towlower(cur_ch) == towlower(cur_subch)) {\n            cur_substr += next_subch;\n            cur_str += next_ch;\n        } else {\n            if (cur_substr == substr) cur_str += next_ch;\n            cur_substr = (char*)substr;\n        }\n    }\n    return *cur_substr == 0;\n}\n\nstatic bool search_blockdef(Blockdef* blockdef) {\n    if (search_string(blockdef->id, editor.search_list_search)) return true;\n    for (size_t i = 0; i < vector_size(blockdef->inputs); i++) {\n        if (blockdef->inputs[i].type != INPUT_TEXT_DISPLAY) continue;\n        if (search_string(blockdef->inputs[i].data.text, editor.search_list_search)) return true;\n    }\n    return false;\n}\n\nvoid update_search(void) {\n    vector_clear(editor.search_list);\n    for (size_t i = 0; i < vector_size(vm.blockdefs); i++) {\n        if (vm.blockdefs[i]->type == BLOCKTYPE_END) continue;\n        if (!search_blockdef(vm.blockdefs[i])) continue;\n\n        vector_add(&editor.search_list, vm.blockdefs[i]);\n    }\n}\n\nstatic void handle_key_press(void) {\n    if (vector_size(editor.mouse_blockchains) > 0) return;\n\n    if (IsKeyPressed(KEY_F5)) {\n#ifdef USE_INTERPRETER\n        vm_start();\n#else\n        vm_start(COMPILER_MODE_JIT);\n#endif\n        return;\n    }\n    if (IsKeyPressed(KEY_F6)) {\n        vm_stop();\n        return;\n    }\n    if (IsKeyPressed(KEY_S) &&\n        ui.hover.select_input != &editor.search_list_search &&\n        !ui.hover.is_panel_edit_mode &&\n        ui.hover.panels.panel &&\n        ui.hover.panels.panel->type == PANEL_CODE &&\n        !thread_is_running(&vm.thread) &&\n        !gui_window_is_shown() &&\n        !ui.hover.select_input)\n    {\n        vector_clear(editor.search_list_search);\n        vector_add(&editor.search_list_search, 0);\n        ui.hover.select_input = &editor.search_list_search;\n        ui.hover.select_input_cursor = 0;\n        ui.hover.select_input_mark = -1;\n        ui.render_surface_needs_redraw = true;\n        update_search();\n        return;\n    }\n\n    if (ui.hover.panels.panel) {\n        if (ui.hover.panels.panel->type == PANEL_TERM) {\n            if (!thread_is_running(&vm.thread)) return;\n            if (IsKeyPressed(KEY_ENTER) || IsKeyPressed(KEY_KP_ENTER)) {\n                term_input_put_char('\\n');\n                term_print_str(\"\\n\");\n                ui.render_surface_needs_redraw = true;\n                return;\n            }\n\n            int char_val;\n            while ((char_val = GetCharPressed())) {\n                int utf_size = 0;\n                const char* utf_char = CodepointToUTF8(char_val, &utf_size);\n                for (int i = 0; i < utf_size; i++) {\n                    term_input_put_char(utf_char[i]);\n                }\n                // CodepointToUTF8() returns an array, not a null terminated string, so we copy it to satisfy constraints\n                char utf_str[7];\n                memcpy(utf_str, utf_char, utf_size);\n                utf_str[utf_size] = 0;\n                term_print_str(utf_str);\n                ui.render_surface_needs_redraw = true;\n            }\n            return;\n        } else if (ui.hover.panels.panel->type == PANEL_CODE) {\n            if (handle_code_panel_key_press()) return;\n        }\n    }\n\n    if (IsKeyPressed(KEY_ESCAPE)) {\n        ui.hover.select_input = NULL;\n        ui.hover.editor.select_argument = NULL;\n        ui.render_surface_needs_redraw = true;\n        return;\n    }\n    if (ui.hover.editor.select_block && ui.hover.editor.select_argument && ui.hover.editor.select_block->blockdef->inputs[ui.hover.editor.select_argument->input_id].type == INPUT_DROPDOWN) return;\n\n    if (edit_text(ui.hover.select_input)) {\n        if (ui.hover.select_input == &editor.search_list_search) update_search();\n    }\n}\n\nstatic void handle_mouse_wheel(void) {\n    if (!ui.hover.panels.panel) return;\n    if (ui.hover.panels.panel->type != PANEL_CODE) return;\n    if (ui.hover.editor.select_argument) return;\n    if (ui.hover.is_panel_edit_mode) return;\n    if (ui.hover.select_input) return;\n    if (gui_window_is_shown()) return;\n\n    Vector2 wheel = GetMouseWheelMoveV();\n    editor.camera_pos.x -= wheel.x * config.ui_size * 2;\n    editor.camera_pos.y -= wheel.y * config.ui_size * 2;\n\n    if (wheel.x != 0 || wheel.y != 0) {\n        ui.hover.editor.select_block = NULL;\n        ui.hover.editor.select_argument = NULL;\n        ui.hover.select_input = NULL;\n        ui.hover.editor.select_blockchain = NULL;\n    }\n}\n\nstatic void handle_mouse_drag(void) {\n    if (ui.hover.drag_cancelled) return;\n\n    if (ui.hover.is_panel_edit_mode && ui.hover.panels.drag_panel && ui.hover.panels.drag_panel->type == PANEL_SPLIT) {\n        if (ui.hover.panels.drag_panel->direction == DIRECTION_HORIZONTAL) {\n            ui.hover.panels.drag_panel->split_percent = CLAMP(\n                (gui->mouse_x - ui.hover.panels.drag_panel_size.x - 5) / ui.hover.panels.drag_panel_size.width,\n                0.0,\n                1.0 - (10.0 / ui.hover.panels.drag_panel_size.width)\n            );\n        } else {\n            ui.hover.panels.drag_panel->split_percent = CLAMP(\n                (gui->mouse_y - ui.hover.panels.drag_panel_size.y - 5) / ui.hover.panels.drag_panel_size.height,\n                0.0,\n                1.0 - (10.0 / ui.hover.panels.drag_panel_size.height)\n            );\n        }\n        return;\n    }\n\n    if (ui.hover.dragged_slider.value) {\n        *ui.hover.dragged_slider.value = CLAMP(\n            ui.hover.slider_last_val + (gui->mouse_x - ui.hover.mouse_click_pos.x) / 2,\n            ui.hover.dragged_slider.min,\n            ui.hover.dragged_slider.max\n        );\n        return;\n    }\n\n    editor.camera_pos.x = editor.camera_click_pos.x - (gui->mouse_x - ui.hover.mouse_click_pos.x);\n    editor.camera_pos.y = editor.camera_click_pos.y - (gui->mouse_y - ui.hover.mouse_click_pos.y);\n}\n\nvoid scrap_gui_process_ui(void) {\n    editor.actionbar.show_time -= GetFrameTime();\n    if (editor.actionbar.show_time < 0) {\n        editor.actionbar.show_time = 0;\n    } else {\n        ui.render_surface_needs_redraw = true;\n    }\n\n    if (ui.shader_time_loc != -1) SetShaderValue(assets.line_shader, ui.shader_time_loc, &ui.shader_time, SHADER_UNIFORM_FLOAT);\n    ui.shader_time += GetFrameTime() / 2.0;\n    if (ui.shader_time >= 1.0) {\n        ui.shader_time = 1.0;\n    } else {\n        ui.render_surface_needs_redraw = true;\n    }\n\n    int prev_mouse_scroll = gui->mouse_scroll;\n    gui_update_mouse_scroll(gui, GetMouseWheelMove());\n    if (prev_mouse_scroll != gui->mouse_scroll) ui.render_surface_needs_redraw = true;\n\n    if (IsWindowResized()) {\n        ui.shader_time = 0.0;\n        gui_update_window_size(gui, GetScreenWidth(), GetScreenHeight());\n        UnloadRenderTexture(ui.render_surface);\n        ui.render_surface = LoadRenderTexture(GetScreenWidth(), GetScreenHeight());\n        SetTextureWrap(ui.render_surface.texture, TEXTURE_WRAP_MIRROR_REPEAT);\n        ui.render_surface_needs_redraw = true;\n    }\n\n    Vector2 delta = GetMouseDelta();\n    if (delta.x != 0 || delta.y != 0) ui.render_surface_needs_redraw = true;\n\n    if (GetMouseWheelMove() != 0.0) {\n        handle_mouse_wheel();\n        ui.render_surface_needs_redraw = true;\n    }\n\n#ifdef ARABIC_MODE\n    gui_update_mouse_pos(gui, gui->win_w - GetMouseX(), GetMouseY());\n#else\n    gui_update_mouse_pos(gui, GetMouseX(), GetMouseY());\n#endif\n\n    if (IsMouseButtonPressed(MOUSE_BUTTON_LEFT)) {\n        ui.hover.drag_cancelled = handle_mouse_click();\n        ui.render_surface_needs_redraw = true;\n#ifdef DEBUG\n        // This will traverse through all blocks in codebase, which is expensive in large codebase.\n        // Ideally all functions should not be broken in the first place. This helps with debugging invalid states\n        sanitize_links();\n#endif\n    } else if (IsMouseButtonPressed(MOUSE_BUTTON_MIDDLE)) {\n        ui.hover.mouse_click_pos = (Vector2) { gui->mouse_x, gui->mouse_y };\n        editor.camera_click_pos = editor.camera_pos;\n        ui.hover.editor.select_block = NULL;\n        ui.hover.editor.select_argument = NULL;\n        ui.hover.select_input = NULL;\n        ui.hover.editor.select_blockchain = NULL;\n        ui.render_surface_needs_redraw = true;\n        if (ui.dropdown.shown) handle_dropdown_close();\n    } else if (IsMouseButtonDown(MOUSE_BUTTON_MIDDLE) || IsMouseButtonDown(MOUSE_BUTTON_LEFT)) {\n        handle_mouse_drag();\n    } else {\n        ui.hover.drag_cancelled = false;\n        ui.hover.dragged_slider.value = NULL;\n        ui.hover.panels.drag_panel = NULL;\n        handle_key_press();\n    }\n\n    if (IsMouseButtonReleased(MOUSE_BUTTON_LEFT) || IsMouseButtonReleased(MOUSE_BUTTON_MIDDLE)) ui.render_surface_needs_redraw = true;\n\n    if (ui.render_surface_redraw_next) {\n        ui.render_surface_needs_redraw = true;\n        ui.render_surface_redraw_next = false;\n    }\n\n    handle_window();\n\n    if (ui.render_surface_needs_redraw) {\n        ui.hover.editor.block = NULL;\n        ui.hover.editor.argument = NULL;\n        ui.hover.input_info.input = NULL;\n        ui.hover.category = NULL;\n        ui.hover.editor.parent_argument = NULL;\n        ui.hover.editor.prev_blockchain = NULL;\n        ui.hover.editor.blockchain = NULL;\n        ui.hover.editor.part = EDITOR_NONE;\n        ui.hover.editor.blockdef = NULL;\n        ui.hover.editor.blockdef_input = -1;\n        ui.hover.button.handler = NULL;\n        ui.hover.button.data = NULL;\n        ui.hover.hover_slider.value = NULL;\n        ui.hover.panels.panel = NULL;\n        ui.hover.panels.panel_size = (Rectangle) {0};\n        ui.hover.editor.select_valid = false;\n        ui.dropdown.element = NULL;\n        if (ui.dropdown.shown && ui.dropdown.type == DROPDOWN_COLOR_PICKER) {\n            ui.dropdown.as.color_picker.hover_part = COLOR_PICKER_NONE;\n        }\n\n#ifdef DEBUG\n        Timer t = start_timer(\"gui process\");\n#endif\n        scrap_gui_process();\n#ifdef DEBUG\n        ui.ui_time = end_timer(t);\n#endif\n\n        if (vm.start_timeout >= 0) vm.start_timeout--;\n        // This fixes selecting wrong argument of a block when two blocks overlap\n        if (ui.hover.editor.block && ui.hover.editor.argument) {\n            int ind = ui.hover.editor.argument - ui.hover.editor.block->arguments;\n            if (ind < 0 || ind > (int)vector_size(ui.hover.editor.block->arguments)) ui.hover.editor.argument = NULL;\n        }\n\n        if (ui.hover.editor.select_block && !ui.hover.editor.select_valid) {\n            scrap_log(LOG_WARNING, \"Invalid selection: %p\", ui.hover.editor.select_block);\n            ui.hover.editor.select_block = NULL;\n            ui.hover.editor.select_blockchain = NULL;\n        }\n\n        if (vector_size(editor.mouse_blockchains) > 0 &&\n            // This small hack allows to transfer mouse blockchain contents between projects\n            ui.hover.button.handler != handle_file_button_click &&\n            ui.hover.button.handler != handle_file_menu_click)\n        {\n            ui.hover.button.handler = NULL;\n        }\n    }\n\n    ui.hover.editor.prev_block = ui.hover.editor.block;\n    ui.hover.editor.prev_argument = ui.hover.editor.argument;\n    ui.hover.editor.prev_blockdef = ui.hover.editor.blockdef;\n    ui.hover.panels.prev_panel = ui.hover.panels.panel;\n}\n"
  },
  {
    "path": "src/util.c",
    "content": "// Scrap is a project that allows anyone to build software using simple, block based interface.\n//\n// Copyright (C) 2024-2026 Grisshink\n// \n// This software is provided 'as-is', without any express or implied\n// warranty.  In no event will the authors be held liable for any damages\n// arising from the use of this software.\n// \n// Permission is granted to anyone to use this software for any purpose,\n// including commercial applications, and to alter it and redistribute it\n// freely, subject to the following restrictions:\n// \n// 1. The origin of this software must not be misrepresented; you must not\n//    claim that you wrote the original software. If you use this software\n//    in a product, an acknowledgment in the product documentation would be\n//    appreciated but is not required.\n// 2. Altered source versions must be plainly marked as such, and must not be\n//    misrepresented as being the original software.\n// 3. This notice may not be removed or altered from any source distribution.\n\n#include \"vec.h\"\n#include \"util.h\"\n\n#include <assert.h>\n#include <string.h>\n#include <stdio.h>\n\nTimer start_timer(const char* name) {\n    Timer timer;\n    timer.name = name;\n    clock_gettime(CLOCK_MONOTONIC, &timer.start);\n    return timer;\n}\n\ndouble end_timer(Timer timer) {\n    struct timespec end;\n    clock_gettime(CLOCK_MONOTONIC, &end);\n\n    double time_taken = (end.tv_sec - timer.start.tv_sec) * 1e+6 + (end.tv_nsec - timer.start.tv_nsec) * 1e-3;\n    return time_taken;\n}\n\n#define CSI_DARK_GRAY \"\\e[90m\"\n#define CSI_YELLOW \"\\e[93m\"\n#define CSI_RED \"\\e[91m\"\n#define CSI_RESET \"\\e[0m\"\n\nvoid scrap_log(int log_level, const char *text, ...) {\n    va_list va;\n    va_start(va, text);\n    scrap_log_va(log_level, text, va);\n    va_end(va);\n}\n\nvoid scrap_log_va(int log_level, const char *text, va_list args) {\n    switch (log_level) {\n    case LOG_TRACE:\n        printf(CSI_DARK_GRAY \"[TRACE] \");\n        break;\n    case LOG_DEBUG:\n        printf(\"[DEBUG] \");\n        break;\n    case LOG_INFO:\n        printf(\"[INFO] \");\n        break;\n    case LOG_WARNING:\n        printf(CSI_YELLOW \"[WARN] \");\n        break;\n    case LOG_ERROR:\n        printf(CSI_RED \"[ERROR] \");\n        break;\n    case LOG_FATAL:\n        printf(CSI_RED \"[FATAL] \");\n        break;\n    default:\n        printf(CSI_RED \"[UNKNOWN] \");\n        break;\n    }\n\n    vprintf(text, args);\n\n    printf(CSI_RESET \"\\n\");\n}\n"
  },
  {
    "path": "src/util.h",
    "content": "// Scrap is a project that allows anyone to build software using simple, block based interface.\n//\n// Copyright (C) 2024-2026 Grisshink\n// \n// This software is provided 'as-is', without any express or implied\n// warranty.  In no event will the authors be held liable for any damages\n// arising from the use of this software.\n// \n// Permission is granted to anyone to use this software for any purpose,\n// including commercial applications, and to alter it and redistribute it\n// freely, subject to the following restrictions:\n// \n// 1. The origin of this software must not be misrepresented; you must not\n//    claim that you wrote the original software. If you use this software\n//    in a product, an acknowledgment in the product documentation would be\n//    appreciated but is not required.\n// 2. Altered source versions must be plainly marked as such, and must not be\n//    misrepresented as being the original software.\n// 3. This notice may not be removed or altered from any source distribution.\n\n#ifndef SCRAP_UTIL_H\n#define SCRAP_UTIL_H\n\n#include <time.h>\n#include <stdarg.h>\n\n#define ARRLEN(arr) (sizeof(arr) / sizeof(arr[0]))\n\n#define ABS(x) ((x) < 0 ? -(x) : (x))\n#define MAX(x, y) ((x) > (y) ? (x) : (y))\n#define MIN(x, y) ((x) < (y) ? (x) : (y))\n#define CLAMP(x, min, max) (MIN(MAX(min, x), max))\n\n#define CONVERT_COLOR(color, type) (type) { color.r, color.g, color.b, color.a }\n\n#define MOD(x, y) (((x) % (y) + (y)) % (y))\n#define LERP(min, max, t) (((max) - (min)) * (t) + (min))\n#define UNLERP(min, max, v) (((float)(v) - (float)(min)) / ((float)(max) - (float)(min)))\n\n#define LOG_ALL 0\n#define LOG_TRACE 1\n#define LOG_DEBUG 2\n#define LOG_INFO 3\n#define LOG_WARNING 4\n#define LOG_ERROR 5\n#define LOG_FATAL 6\n#define LOG_NONE 7\n\ntypedef struct {\n    struct timespec start;\n    const char* name;\n} Timer;\n\nTimer start_timer(const char* name);\ndouble end_timer(Timer timer);\nvoid scrap_log(int log_level, const char *text, ...);\nvoid scrap_log_va(int log_level, const char *text, va_list args);\n\n#endif // SCRAP_UTIL_H\n"
  },
  {
    "path": "src/vec.c",
    "content": "/*\nBSD 3-Clause License\n\nCopyright (c) 2024, Mashpoe\nAll rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are met:\n\n1. Redistributions of source code must retain the above copyright notice, this\n   list of conditions and the following disclaimer.\n\n2. Redistributions in binary form must reproduce the above copyright notice,\n   this list of conditions and the following disclaimer in the documentation\n   and/or other materials provided with the distribution.\n\n3. Neither the name of the copyright holder nor the names of its\n   contributors may be used to endorse or promote products derived from\n   this software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\nAND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\nIMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\nDISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE\nFOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\nDAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR\nSERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER\nCAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,\nOR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n*/\n\n#include \"vec.h\"\n#include <string.h>\n#include <stdbool.h>\n\nvector_header* vector_get_header(vector vec) { return &((vector_header*)vec)[-1]; }\n\nvector vector_create(void)\n{\n\tvector_header* h = (vector_header*)malloc(sizeof(vector_header));\n\th->capacity = 0;\n\th->size = 0;\n\n\treturn &h->data;\n}\n\nvoid vector_free(vector vec) { free(vector_get_header(vec)); }\n\nvec_size_t vector_size(vector vec) { return vector_get_header(vec)->size; }\n\nvec_size_t vector_capacity(vector vec) { return vector_get_header(vec)->capacity; }\n\nvector_header* vector_realloc(vector_header* h, vec_type_t type_size)\n{\n\tvec_size_t new_capacity = (h->capacity == 0) ? 1 : h->capacity * 2;\n\tvector_header* new_h = (vector_header*)realloc(h, sizeof(vector_header) + new_capacity * type_size);\n\tnew_h->capacity = new_capacity;\n\n\treturn new_h;\n}\n\nbool vector_has_space(vector_header* h)\n{\n\treturn h->capacity - h->size > 0;\n}\n\nvoid* _vector_add_dst(vector* vec_addr, vec_type_t type_size)\n{\n\tvector_header* h = vector_get_header(*vec_addr);\n\n\tif (!vector_has_space(h))\n\t{\n\t\th = vector_realloc(h, type_size);\n\t\t*vec_addr = h->data;\n\t}\n\n\treturn &h->data[type_size * h->size++];\n}\n\nvoid* _vector_insert_dst(vector* vec_addr, vec_type_t type_size, vec_size_t pos)\n{\n\tvector_header* h = vector_get_header(*vec_addr);\n\n\tvec_size_t new_length = h->size + 1;\n\n\t// make sure there is enough room for the new element\n\tif (!vector_has_space(h))\n\t{\n\t\th = vector_realloc(h, type_size);\n\t\t*vec_addr = h->data;\n\t}\n\t// move trailing elements\n\tmemmove(&h->data[(pos + 1) * type_size],\n\t\t&h->data[pos * type_size],\n\t\t(h->size - pos) * type_size);\n\n\th->size = new_length;\n\n\treturn &h->data[pos * type_size];\n}\n\nvoid _vector_erase(vector vec, vec_type_t type_size, vec_size_t pos, vec_size_t len)\n{\n\tvector_header* h = vector_get_header(vec);\n\tmemmove(&h->data[pos * type_size],\n\t\t&h->data[(pos + len) * type_size],\n\t\t(h->size - pos - len) * type_size);\n\n\th->size -= len;\n}\n\nvoid _vector_remove(vector vec, vec_type_t type_size, vec_size_t pos)\n{\n\t_vector_erase(vec, type_size, pos, 1);\n}\n\nvoid vector_pop(vector vec) { --vector_get_header(vec)->size; }\n\nvoid vector_clear(vector vec) { vector_get_header(vec)->size = 0; }\n\nvoid _vector_reserve(vector* vec_addr, vec_type_t type_size, vec_size_t capacity)\n{\n\tvector_header* h = vector_get_header(*vec_addr);\n\tif (h->capacity >= capacity)\n\t{\n\t\treturn;\n\t}\n\n\th = (vector_header*)realloc(h, sizeof(vector_header) + capacity * type_size);\n\th->capacity = capacity;\n\t*vec_addr = &h->data;\n}\n\nvector _vector_copy(vector vec, vec_type_t type_size)\n{\n\tvector_header* h = vector_get_header(vec);\n\tsize_t alloc_size = sizeof(vector_header) + h->size * type_size;\n\tvector_header* copy_h = (vector_header*)malloc(alloc_size);\n\tmemcpy(copy_h, h, alloc_size);\n\tcopy_h->capacity = copy_h->size;\n\n\treturn &copy_h->data;\n}\n"
  },
  {
    "path": "src/vec.h",
    "content": "/*\nBSD 3-Clause License\n\nCopyright (c) 2024, Mashpoe\nAll rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are met:\n\n1. Redistributions of source code must retain the above copyright notice, this\n   list of conditions and the following disclaimer.\n\n2. Redistributions in binary form must reproduce the above copyright notice,\n   this list of conditions and the following disclaimer in the documentation\n   and/or other materials provided with the distribution.\n\n3. Neither the name of the copyright holder nor the names of its\n   contributors may be used to endorse or promote products derived from\n   this software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\nAND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\nIMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\nDISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE\nFOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\nDAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR\nSERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER\nCAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,\nOR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n*/\n\n#ifndef vec_h\n#define vec_h\n\n#ifdef __cpp_decltype\n#include <type_traits>\n#define typeof(T) std::remove_reference<std::add_lvalue_reference<decltype(T)>::type>::type\n#endif\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\n#include <stdlib.h>\n\n// generic type for internal use\ntypedef void* vector;\n// number of elements in a vector\ntypedef size_t vec_size_t;\n// number of bytes for a type\ntypedef size_t vec_type_t;\n\ntypedef struct\n{\n\tvec_size_t size;\n\tvec_size_t capacity;\n\tunsigned char data[]; \n} vector_header;\n\n// TODO: more rigorous check for typeof support with different compilers\n#if _MSC_VER == 0 || __STDC_VERSION__ >= 202311L || defined __cpp_decltype\n\n// shortcut defines\n\n// vec_addr is a vector* (aka type**)\n#define vector_add_dst(vec_addr)\\\n\t((__typeof__(*vec_addr))(\\\n\t    _vector_add_dst((vector*)vec_addr, sizeof(**vec_addr))\\\n\t))\n#define vector_insert_dst(vec_addr, pos)\\\n\t((__typeof__(*vec_addr))(\\\n\t    _vector_insert_dst((vector*)vec_addr, sizeof(**vec_addr), pos)))\n\n#define vector_add(vec_addr, value)\\\n\t(*vector_add_dst(vec_addr) = value)\n#define vector_insert(vec_addr, pos, value)\\\n\t(*vector_insert_dst(vec_addr, pos) = value)\n\n#else\n\n#define vector_add_dst(vec_addr, type)\\\n\t((type*)_vector_add_dst((vector*)vec_addr, sizeof(type)))\n#define vector_insert_dst(vec_addr, type, pos)\\\n\t((type*)_vector_insert_dst((vector*)vec_addr, sizeof(type), pos))\n\n#define vector_add(vec_addr, type, value)\\\n\t(*vector_add_dst(vec_addr, type) = value)\n#define vector_insert(vec_addr, type, pos, value)\\\n\t(*vector_insert_dst(vec_addr, type, pos) = value)\n\n#endif\n\n// vec is a vector (aka type*)\n#define vector_erase(vec, pos, len)\\\n\t(_vector_erase((vector)vec, sizeof(*vec), pos, len))\n#define vector_remove(vec, pos)\\\n\t(_vector_remove((vector)vec, sizeof(*vec), pos))\n\n#define vector_reserve(vec_addr, capacity)\\\n\t(_vector_reserve((vector*)vec_addr, sizeof(**vec_addr), capacity))\n\n#define vector_copy(vec)\\\n\t(_vector_copy((vector)vec, sizeof(*vec)))\n\nvector vector_create(void);\n\nvoid vector_free(vector vec);\n\nvoid* _vector_add_dst(vector* vec_addr, vec_type_t type_size);\n\nvoid* _vector_insert_dst(vector* vec_addr, vec_type_t type_size, vec_size_t pos);\n\nvoid _vector_erase(vector vec_addr, vec_type_t type_size, vec_size_t pos, vec_size_t len);\n\nvoid _vector_remove(vector vec_addr, vec_type_t type_size, vec_size_t pos);\n\nvoid vector_pop(vector vec);\n\nvoid vector_clear(vector vec);\n\nvoid _vector_reserve(vector* vec_addr, vec_type_t type_size, vec_size_t capacity);\n\nvector _vector_copy(vector vec, vec_type_t type_size);\n\nvec_size_t vector_size(vector vec);\n\nvec_size_t vector_capacity(vector vec);\n\nvector_header* vector_get_header(vector vec);\n\n// closing bracket for extern \"C\"\n#ifdef __cplusplus\n}\n#endif\n\n#endif /* vec_h */\n"
  },
  {
    "path": "src/vm.c",
    "content": "// Scrap is a project that allows anyone to build software using simple, block based interface.\n//\n// Copyright (C) 2024-202 Grisshink\n// \n// This software is provided 'as-is', without any express or implied\n// warranty.  In no event will the authors be held liable for any damages\n// arising from the use of this software.\n// \n// Permission is granted to anyone to use this software for any purpose,\n// including commercial applications, and to alter it and redistribute it\n// freely, subject to the following restrictions:\n// \n// 1. The origin of this software must not be misrepresented; you must not\n//    claim that you wrote the original software. If you use this software\n//    in a product, an acknowledgment in the product documentation would be\n//    appreciated but is not required.\n// 2. Altered source versions must be plainly marked as such, and must not be\n//    misrepresented as being the original software.\n// 3. This notice may not be removed or altered from any source distribution.\n\n#include \"scrap.h\"\n\n#include <stdlib.h>\n#include <assert.h>\n#include <libintl.h>\n\nstatic BlockChain* find_blockchain(Block* block) {\n    if (!block) return NULL;\n    while (block->parent) block = block->parent;\n    for (size_t i = 0; i < vector_size(editor.code); i++) {\n        if (block >= editor.code[i].blocks && block < editor.code[i].blocks + vector_size(editor.code[i].blocks)) {\n            return &editor.code[i];\n        }\n    }\n    return NULL;\n}\n\nBlock block_new_ms(Blockdef* blockdef) {\n    Block block = block_new(blockdef);\n    for (size_t i = 0; i < vector_size(block.arguments); i++) {\n        if (block.arguments[i].type != ARGUMENT_BLOCKDEF) continue;\n        block.arguments[i].data.blockdef->func = block_exec_custom;\n    }\n    return block;\n}\n\nsize_t blockdef_register(Vm* vm, Blockdef* blockdef) {\n    if (!blockdef->func) scrap_log(LOG_WARNING, \"[VM] Block \\\"%s\\\" has not defined its implementation!\", blockdef->id);\n\n    vector_add(&vm->blockdefs, blockdef);\n    blockdef->ref_count++;\n    if (blockdef->type == BLOCKTYPE_END && vm->end_blockdef == (size_t)-1) {\n        vm->end_blockdef = vector_size(vm->blockdefs) - 1;\n    }\n\n    return vector_size(vm->blockdefs) - 1;\n}\n\nvoid blockdef_unregister(Vm* vm, size_t block_id) {\n    blockdef_free(vm->blockdefs[block_id]);\n    vector_remove(vm->blockdefs, block_id);\n}\n\nBlockCategory block_category_new(const char* name, Color color) {\n    return (BlockCategory) {\n        .name = name,\n        .color = color,\n        .items = vector_create(),\n        .next = NULL,\n        .prev = NULL,\n    };\n}\n\nBlockCategory* block_category_register(BlockCategory category) {\n    BlockCategory* cat = malloc(sizeof(category));\n    assert(cat != NULL);\n    *cat = category;\n    if (!editor.palette.categories_end) {\n        editor.palette.categories_end   = cat;\n        editor.palette.categories_start = cat;\n        editor.palette.current_category = editor.palette.categories_start;\n        return cat;\n    }\n    editor.palette.categories_end->next = cat;\n    cat->prev = editor.palette.categories_end;\n    editor.palette.categories_end = cat;\n    return cat;\n}\n\nvoid block_category_unregister(BlockCategory* category) {\n    for (size_t i = 0; i < vector_size(category->items); i++) {\n        switch (category->items[i].type) {\n        case CATEGORY_ITEM_CHAIN:\n            blockchain_free(&category->items[i].data.chain);\n            break;\n        case CATEGORY_ITEM_LABEL:\n            break;\n        }\n    }\n    vector_free(category->items);\n    if (category->next) category->next->prev = NULL;\n    if (category->prev) category->prev->next = NULL;\n\n    if (editor.palette.categories_start == category) editor.palette.categories_start = category->next;\n    if (editor.palette.categories_end == category) editor.palette.categories_end = category->prev;\n    if (editor.palette.current_category == category) editor.palette.current_category = editor.palette.categories_start;\n\n    free(category);\n}\n\nvoid block_category_add_blockdef(BlockCategory* category, Blockdef* blockdef) {\n    BlockChain chain = blockchain_new();\n    blockchain_add_block(&chain, block_new_ms(blockdef));\n    if (blockdef->type == BLOCKTYPE_CONTROL && vm.end_blockdef != (size_t)-1) {\n        blockchain_add_block(&chain, block_new(vm.blockdefs[vm.end_blockdef])); }\n\n    BlockCategoryItem* item = vector_add_dst(&category->items);\n    item->type = CATEGORY_ITEM_CHAIN;\n    item->data.chain = chain;\n}\n\nvoid block_category_add_label(BlockCategory* category, const char* label, Color color) {\n    BlockCategoryItem* item = vector_add_dst(&category->items);\n    item->type = CATEGORY_ITEM_LABEL;\n    item->data.label.text = label;\n    item->data.label.color = color;\n}\n\nvoid unregister_categories(void) {\n    if (!editor.palette.categories_start) return;\n    BlockCategory* cat = editor.palette.categories_start;\n    while (cat) {\n        BlockCategory* next = cat->next;\n        block_category_unregister(cat);\n        cat = next;\n    }\n    editor.palette.categories_start = NULL;\n    editor.palette.categories_end = NULL;\n}\n\nvoid clear_compile_error(void) {\n    vm.compile_error_block = NULL;\n    vm.compile_error_blockchain = NULL;\n    for (size_t i = 0; i < vector_size(vm.compile_error); i++) vector_free(vm.compile_error[i]);\n    vector_clear(vm.compile_error);\n}\n\nVm vm_new(void) {\n    Vm vm = (Vm) {\n        .blockdefs = vector_create(),\n        .end_blockdef = -1,\n        .thread = thread_new(exec_run, exec_cleanup),\n        .exec = (Exec) {0},\n\n        .compile_error = vector_create(),\n        .compile_error_block = NULL,\n        .compile_error_blockchain = NULL,\n        .start_timeout = -1,\n#ifndef USE_INTERPRETER\n        .start_mode = COMPILER_MODE_JIT,\n#endif\n    };\n    return vm;\n}\n\nvoid vm_free(Vm* vm) {\n    if (thread_is_running(&vm->thread)) {\n        thread_stop(&vm->thread);\n        thread_join(&vm->thread);\n        exec_free(&vm->exec);\n    }\n\n    for (ssize_t i = (ssize_t)vector_size(vm->blockdefs) - 1; i >= 0 ; i--) {\n        blockdef_unregister(vm, i);\n    }\n    vector_free(vm->blockdefs);\n}\n\n#ifdef USE_INTERPRETER\nbool vm_start(void) {\n#else\nbool vm_start(CompilerMode mode) {\n#endif\n    if (thread_is_running(&vm.thread)) return false;\n\n    for (size_t i = 0; i < vector_size(editor.tabs); i++) {\n        if (find_panel(editor.tabs[i].root_panel, PANEL_TERM)) {\n#ifndef USE_INTERPRETER\n            vm.start_mode = mode;\n#endif\n            if (editor.current_tab != (int)i) {\n                ui.shader_time = 0.0;\n                // Delay vm startup until next frame. Because this handler only runs after the layout is computed and\n                // before the actual rendering begins, we need to add delay to vm startup to make sure the terminal buffer\n                // is initialized and vm does not try to write to uninitialized buffer\n                vm.start_timeout = 2;\n            } else {\n                vm.start_timeout = 1;\n            }\n            editor.current_tab = i;\n            ui.render_surface_needs_redraw = true;\n            break;\n        }\n    }\n    return true;\n}\n\nbool vm_stop(void) {\n    if (!thread_is_running(&vm.thread)) return false;\n    scrap_log(LOG_INFO, \"STOP\");\n    thread_stop(&vm.thread);\n    ui.render_surface_needs_redraw = true;\n    return true;\n}\n\nvoid vm_handle_running_thread(void) {\n    ThreadReturnCode thread_return = thread_try_join(&vm.thread);\n    if (thread_return != THREAD_RETURN_RUNNING) {\n        switch (thread_return) {\n        case THREAD_RETURN_SUCCESS:\n            actionbar_show(gettext(\"Vm executed successfully\"));\n            break;\n        case THREAD_RETURN_FAILURE:\n            actionbar_show(gettext(\"Vm shitted and died :(\"));\n            break;\n        case THREAD_RETURN_STOPPED:\n            actionbar_show(gettext(\"Vm stopped >:(\"));\n            break;\n        default:\n            break;\n        }\n\n        size_t i = 0;\n        while (vm.exec.current_error[i]) {\n            vector_add(&vm.compile_error, vector_create());\n            size_t line_len = 0;\n            while (line_len < 50 && vm.exec.current_error[i]) {\n                if (((unsigned char)vm.exec.current_error[i] >> 6) != 2) line_len++;\n                if (line_len >= 50) break;\n                vector_add(&vm.compile_error[vector_size(vm.compile_error) - 1], vm.exec.current_error[i++]);\n            }\n            vector_add(&vm.compile_error[vector_size(vm.compile_error) - 1], 0);\n        }\n        vm.compile_error_block = vm.exec.current_error_block;\n        vm.compile_error_blockchain = find_blockchain(vm.compile_error_block);\n        exec_free(&vm.exec);\n        ui.render_surface_needs_redraw = true;\n    } else if (thread_is_running(&vm.thread)) {\n        mutex_lock(&term.lock);\n        if (find_panel(editor.tabs[editor.current_tab].root_panel, PANEL_TERM) && term.is_buffer_dirty) {\n            ui.render_surface_needs_redraw = true;\n            term.is_buffer_dirty = false;\n        }\n        mutex_unlock(&term.lock);\n    } else {\n        if (vector_size(vm.compile_error) > 0) ui.render_surface_needs_redraw = true;\n    }\n}\n"
  },
  {
    "path": "src/window.c",
    "content": "// Scrap is a project that allows anyone to build software using simple, block based interface.\n//\n// Copyright (C) 2024-2026 Grisshink\n// \n// This software is provided 'as-is', without any express or implied\n// warranty.  In no event will the authors be held liable for any damages\n// arising from the use of this software.\n// \n// Permission is granted to anyone to use this software for any purpose,\n// including commercial applications, and to alter it and redistribute it\n// freely, subject to the following restrictions:\n// \n// 1. The origin of this software must not be misrepresented; you must not\n//    claim that you wrote the original software. If you use this software\n//    in a product, an acknowledgment in the product documentation would be\n//    appreciated but is not required.\n// 2. Altered source versions must be plainly marked as such, and must not be\n//    misrepresented as being the original software.\n// 3. This notice may not be removed or altered from any source distribution.\n\n#include \"scrap.h\"\n#include \"../external/tinyfiledialogs.h\"\n\n#include <stdbool.h>\n#include <stdlib.h>\n#include <math.h>\n#include <assert.h>\n#include <stdio.h>\n#include <libintl.h>\n#include <string.h>\n\ntypedef struct {\n    bool shown;\n    float animation_time;\n    float animation_ease;\n    bool is_fading;\n    bool is_hiding;\n    WindowGuiRenderFunc render;\n} WindowGui;\n\nConfig window_config;\nstatic WindowGui window = {0};\nstatic bool settings_tooltip = false;\nstatic bool settings_applied = false;\nstatic char** about_text_split = NULL;\n\nstatic void draw_button(const char* label, Texture2D* icon, ButtonClickHandler handler, void* data);\n\n// https://easings.net/#easeOutExpo\nfloat ease_out_expo(float x) {\n    return x == 1.0 ? 1.0 : 1 - powf(2.0, -10.0 * x);\n}\n\nstatic bool about_on_license_button_click(void) {\n    OpenURL(LICENSE_URL);\n    return true;\n}\n\nstatic bool window_on_close_button_click(void) {\n    gui_window_hide();\n    return true;\n}\n\nstatic void vector_append(char** vec, const char* str) {\n    if (vector_size(*vec) > 0 && (*vec)[vector_size(*vec) - 1] == 0) vector_pop(*vec);\n    for (size_t i = 0; str[i]; i++) vector_add(vec, str[i]);\n    vector_add(vec, 0);\n}\n\nstatic bool settings_on_browse_button_click(void) {\n    char const* filters[] = { \"*.ttf\", \"*.otf\" };\n    char** path_input = ui.hover.button.data;\n    char* path = tinyfd_openFileDialog(NULL, *path_input, ARRLEN(filters), filters, \"Font files\", 0);\n    if (!path) return true;\n\n    vector_clear(*path_input);\n    vector_append(path_input, path);\n\n    ui.hover.select_input_cursor = 0;\n    ui.hover.select_input_mark = -1;\n    ui.render_surface_needs_redraw = true;\n\n    return true;\n}\n\nstatic bool settings_on_left_slider_button_click(void) {\n    settings_applied = false;\n    *ui.hover.hover_slider.value = MAX(*ui.hover.hover_slider.value - 1, ui.hover.hover_slider.min);\n    return true;\n}\n\nstatic bool settings_on_right_slider_button_click(void) {\n    settings_applied = false;\n    *ui.hover.hover_slider.value = MIN(*ui.hover.hover_slider.value + 1, ui.hover.hover_slider.max);\n    return true;\n}\n\nstatic bool settings_on_dropdown_button_click(void) {\n    settings_applied = false;\n    *(int*)ui.dropdown.ref_object = ui.dropdown.as.list.select_ind;\n    return handle_dropdown_close();\n}\n\nstatic bool settings_on_dropdown_click(void) {\n    show_list_dropdown(ui.hover.settings_dropdown_data.list, ui.hover.settings_dropdown_data.list_len, ui.hover.settings_dropdown_data.value, settings_on_dropdown_button_click);\n    return true;\n}\n\nstatic bool settings_on_panel_editor_button_click(void) {\n    gui_window_hide();\n    ui.hover.is_panel_edit_mode = true;\n    ui.hover.select_input = NULL;\n    ui.hover.editor.select_argument = NULL;\n    ui.hover.editor.select_block = NULL;\n    ui.hover.editor.select_blockchain = NULL;\n    return true;\n}\n\nstatic bool settings_on_reset_button_click(void) {\n    set_default_config(&window_config);\n    settings_applied = false;\n    return true;\n}\n\nstatic bool settings_on_reset_panels_button_click(void) {\n    delete_all_tabs();\n    init_panels();\n    editor.current_tab = 0;\n    settings_applied = false;\n    return true;\n}\n\nstatic bool settings_on_apply_button_click(void) {\n    apply_config(&config, &window_config);\n    save_config(&window_config);\n    settings_applied = true;\n    return true;\n}\n\nstatic bool settings_on_toggle_button_click(void) {\n    bool* toggle_value = ui.hover.button.data;\n    *toggle_value = !*toggle_value;\n    return true;\n}\n\nstatic bool project_settings_on_build_button_click(void) {\n#ifdef USE_INTERPRETER\n    vm_start();\n#else\n    vm_start(COMPILER_MODE_BUILD);\n#endif\n    gui_window_hide();\n    return true;\n}\n\nstatic bool save_confirmation_on_yes_button_click(void) {\n    if (save_project()) {\n        ui.scrap_running = false;\n    } else {\n        gui_window_hide();\n    }\n    return true;\n}\n\nstatic bool save_confirmation_on_no_button_click(void) {\n    ui.scrap_running = false;\n    return true;\n}\n\nstatic bool save_confirmation_on_cancel_button_click(void) {\n    gui_window_hide();\n    return true;\n}\n\nvoid init_gui_window(void) {\n    window.is_fading = true;\n}\n\nbool gui_window_is_shown(void) {\n    return window.shown;\n}\n\nWindowGuiRenderFunc gui_window_get_render_func(void) {\n    return window.render;\n}\n\nvoid gui_window_show(WindowGuiRenderFunc func) {\n    config_free(&window_config); // Drop old strings and replace with new\n    config_copy(&window_config, &config);\n    window.is_fading = false;\n    window.render = func;\n    ui.shader_time = -0.2;\n    settings_applied = false;\n}\n\nvoid gui_window_hide(void) {\n    ui.hover.select_input = NULL;\n    window.is_fading = true;\n}\n\nvoid gui_window_hide_immediate(void) {\n    gui_window_hide();\n    window.is_hiding = true;\n}\n\nstatic void settings_button_on_hover(GuiElement* el) {\n    if (ui.hover.button.handler) return;\n    el->color = (GuiColor) { 0x40, 0x40, 0x40, 0xff };\n    ui.hover.button = *(ButtonHoverInfo*)gui_get_state(el);\n}\n\nstatic void close_button_on_hover(GuiElement* el) {\n    if (ui.hover.button.handler) return;\n    if (el->draw_type == DRAWTYPE_RECT) return;\n    el->draw_type = DRAWTYPE_RECT;\n    el->draw_subtype = GUI_SUBTYPE_DEFAULT;\n    el->color = (GuiColor) { 0x40, 0x40, 0x40, 0xff };\n    ui.hover.button.handler = window_on_close_button_click;\n}\n\nstatic void window_on_hover(GuiElement* el) {\n    (void) el;\n    if (!ui.dropdown.shown) ui.hover.button.handler = NULL;\n}\n\nstatic void begin_window(const char* title, Texture2D* icon, int w, int h, float scaling) {\n    ui.hover.button.handler = window_on_close_button_click;\n    gui_element_begin(gui);\n        gui_set_floating(gui);\n        gui_set_rect(gui, (GuiColor) { 0x00, 0x00, 0x00, 0x40 * scaling });\n        gui_set_position(gui, 0, 0);\n        gui_set_fixed(gui, gui->win_w, gui->win_h);\n    gui_element_end(gui);\n\n    gui_element_begin(gui);\n        gui_scale_element(gui, scaling);\n        gui_set_floating(gui);\n        gui_set_position(gui, gui->win_w / 2, gui->win_h / 2);\n        gui_set_anchor(gui, ALIGN_CENTER, ALIGN_CENTER);\n        gui_set_fixed(gui, w, h);\n        if (w == 0) gui_set_fit(gui, DIRECTION_HORIZONTAL);\n        if (h == 0) gui_set_fit(gui, DIRECTION_VERTICAL);\n        gui_set_rect(gui, (GuiColor) { 0x20, 0x20, 0x20, 0xff });\n        gui_set_direction(gui, DIRECTION_VERTICAL);\n        gui_on_hover(gui, window_on_hover);\n\n        gui_element_begin(gui);\n            gui_set_grow(gui, DIRECTION_HORIZONTAL);\n            gui_set_min_size(gui, 0, config.ui_size * 1.2);\n            gui_set_rect(gui, (GuiColor) { 0x30, 0x30, 0x30, 0xff });\n            gui_set_direction(gui, DIRECTION_HORIZONTAL);\n            gui_set_align(gui, ALIGN_LEFT, ALIGN_CENTER);\n            gui_set_gap(gui, ELEMENT_GAP/2);\n\n            gui_grow(gui, DIRECTION_HORIZONTAL);\n            if (icon) gui_image(gui, icon, config.ui_size, GUI_WHITE);\n            gui_text(gui, &assets.fonts.font_eb, title, config.ui_size * 0.8, GUI_WHITE);\n            gui_grow(gui, DIRECTION_HORIZONTAL);\n        gui_element_end(gui);\n\n        gui_element_begin(gui);\n            gui_set_direction(gui, DIRECTION_VERTICAL);\n            gui_set_padding(gui, config.ui_size * 0.5, config.ui_size * 0.5);\n            gui_set_grow(gui, DIRECTION_HORIZONTAL);\n            gui_set_grow(gui, DIRECTION_VERTICAL);\n            gui_set_gap(gui, ELEMENT_GAP);\n}\n\nstatic void end_window(void) {\n        gui_element_end(gui);\n\n        GuiElement* el = gui_get_element(gui);\n\n        gui_element_begin(gui);\n            gui_set_floating(gui);\n            if (IsShaderValid(assets.line_shader)) {\n                gui_set_border(gui, (GuiColor) { 0x60, 0x60, 0x60, 0xff }, 2);\n                gui_set_shader(gui, &assets.line_shader);\n            }\n            gui_set_position(gui, 0, 0);\n            gui_set_fixed(gui, el->w, el->h);\n        gui_element_end(gui);\n\n        gui_element_begin(gui);\n            gui_set_floating(gui);\n            gui_set_position(gui, el->w - config.ui_size * 1.2, 0);\n            gui_set_fixed(gui, config.ui_size * 1.2, config.ui_size * 1.2);\n            gui_set_align(gui, ALIGN_CENTER, ALIGN_CENTER);\n            gui_on_hover(gui, close_button_on_hover);\n\n            gui_text(gui, &assets.fonts.font_cond, \"X\", config.ui_size * 0.8, GUI_WHITE);\n        gui_element_end(gui);\n    gui_element_end(gui);\n}\n\nstatic void warning_on_hover(GuiElement* el) {\n    if (ui.hover.button.handler) return;\n    (void) el;\n    settings_tooltip = true;\n}\n\nstatic void begin_setting(const char* name, bool warning) {\n    gui_element_begin(gui);\n        gui_set_grow(gui, DIRECTION_HORIZONTAL);\n        gui_set_direction(gui, DIRECTION_HORIZONTAL);\n        gui_set_gap(gui, ELEMENT_GAP);\n        gui_set_min_size(gui, 0, config.ui_size);\n\n        gui_element_begin(gui);\n            gui_set_grow(gui, DIRECTION_HORIZONTAL);\n            gui_set_grow(gui, DIRECTION_VERTICAL);\n            gui_set_direction(gui, DIRECTION_HORIZONTAL);\n            gui_set_align(gui, ALIGN_RIGHT, ALIGN_CENTER);\n\n            gui_text(gui, &assets.fonts.font_cond, name, config.ui_size * 0.6, GUI_WHITE);\n        gui_element_end(gui);\n\n        if (warning) {\n            gui_element_begin(gui);\n                gui_set_image(gui, &assets.textures.icon_warning, config.ui_size, GUI_WHITE);\n                gui_on_hover(gui, warning_on_hover);\n            gui_element_end(gui);\n        } else {\n            gui_spacer(gui, config.ui_size, config.ui_size);\n        }\n\n        gui_element_begin(gui);\n            gui_set_grow(gui, DIRECTION_HORIZONTAL);\n            gui_set_direction(gui, DIRECTION_HORIZONTAL);\n            gui_set_gap(gui, ELEMENT_GAP);\n            gui_set_min_size(gui, 0, config.ui_size);\n}\n\nstatic void slider_on_hover(GuiElement* el) {\n    if (ui.hover.button.handler) return;\n    ui.hover.hover_slider = *(SliderHoverInfo*)gui_get_state(el);\n    if (ui.hover.hover_slider.value == ui.hover.dragged_slider.value) {\n        el->color = (GuiColor) { 0x2b, 0x2b, 0x2b, 0xff };\n        settings_applied = false;\n    } else {\n        el->color = (GuiColor) { 0x40, 0x40, 0x40, 0xff };\n    }\n}\n\nstatic void slider_button_on_hover(GuiElement* el) {\n    if (ui.hover.button.handler) return;\n    el->draw_type = DRAWTYPE_RECT;\n    el->color = (GuiColor) { 0x60, 0x60, 0x60, 0xff };\n    el->draw_subtype = GUI_SUBTYPE_DEFAULT;\n    ui.hover.button.handler = el->custom_data;\n}\n\nstatic void draw_slider(int min, int max, int* value) {\n    gui_element_begin(gui);\n        gui_set_grow(gui, DIRECTION_HORIZONTAL);\n        gui_set_grow(gui, DIRECTION_VERTICAL);\n        gui_set_direction(gui, DIRECTION_HORIZONTAL);\n        gui_set_rect(gui, (GuiColor) { 0x30, 0x30, 0x30, 0xff });\n        gui_on_hover(gui, slider_on_hover);\n\n        SliderHoverInfo info = (SliderHoverInfo) {\n            .min = min,\n            .max = max,\n            .value = value,\n        };\n        SliderHoverInfo* state = gui_set_state(gui, &info, sizeof(info));\n\n        gui_element_begin(gui);\n            gui_set_grow(gui, DIRECTION_HORIZONTAL);\n            gui_set_grow(gui, DIRECTION_VERTICAL);\n            gui_set_direction(gui, DIRECTION_HORIZONTAL);\n            gui_set_align(gui, ALIGN_LEFT, ALIGN_CENTER);\n            if (IsShaderValid(assets.line_shader)) {\n                gui_set_border(gui, (GuiColor) { 0x60, 0x60, 0x60, 0xff }, 2);\n                gui_set_shader(gui, &assets.line_shader);\n            }\n\n            snprintf(state->value_str, 16, \"%d\", *state->value);\n\n            gui_element_begin(gui);\n                gui_on_hover(gui, slider_button_on_hover);\n                gui_set_custom_data(gui, settings_on_left_slider_button_click);\n                gui_set_grow(gui, DIRECTION_VERTICAL);\n                gui_set_direction(gui, DIRECTION_HORIZONTAL);\n                gui_set_align(gui, ALIGN_LEFT, ALIGN_CENTER);\n\n                gui_image(gui, &assets.textures.button_arrow_left, BLOCK_IMAGE_SIZE, GUI_WHITE);\n            gui_element_end(gui);\n\n            gui_grow(gui, DIRECTION_HORIZONTAL);\n\n            gui_text(gui, &assets.fonts.font_cond, state->value_str, config.ui_size * 0.6, GUI_WHITE);\n\n            gui_grow(gui, DIRECTION_HORIZONTAL);\n\n            gui_element_begin(gui);\n                gui_on_hover(gui, slider_button_on_hover);\n                gui_set_custom_data(gui, settings_on_right_slider_button_click);\n                gui_set_grow(gui, DIRECTION_VERTICAL);\n                gui_set_direction(gui, DIRECTION_HORIZONTAL);\n                gui_set_align(gui, ALIGN_LEFT, ALIGN_CENTER);\n\n                gui_image(gui, &assets.textures.button_arrow_right, BLOCK_IMAGE_SIZE, GUI_WHITE);\n            gui_element_end(gui);\n        gui_element_end(gui);\n    gui_element_end(gui);\n}\n\nstatic void end_setting(void) {\n    gui_element_end(gui);\n    gui_element_end(gui);\n}\n\nstatic void text_input_on_hover(GuiElement* el) {\n    if (ui.hover.button.handler) return;\n    el->color = (GuiColor) { 0x40, 0x40, 0x40, 0xff };\n}\n\nstatic void dropdown_input_on_hover(GuiElement* el) {\n    if (ui.hover.button.handler) return;\n    ui.hover.settings_dropdown_data = *(DropdownData*)gui_get_state(el);\n    ui.hover.button.handler = settings_on_dropdown_click;\n    if (el->color.r == 0x30) el->color = (GuiColor) { 0x40, 0x40, 0x40, 0xff };\n}\n\nstatic void draw_dropdown_input(int* value, char** list, int list_len) {\n    gui_element_begin(gui);\n        gui_set_grow(gui, DIRECTION_HORIZONTAL);\n        gui_set_grow(gui, DIRECTION_VERTICAL);\n        gui_set_direction(gui, DIRECTION_HORIZONTAL);\n        gui_set_rect(gui, (GuiColor) { 0x30, 0x30, 0x30, 0xff });\n        gui_on_hover(gui, dropdown_input_on_hover);\n\n        DropdownData data = (DropdownData) {\n            .value = value,\n            .list = list,\n            .list_len = list_len,\n        };\n        gui_set_state(gui, &data, sizeof(data));\n\n        if ((int*)ui.dropdown.ref_object == value) {\n            ui.dropdown.element = gui_get_element(gui);\n            gui_set_rect(gui, (GuiColor) { 0x2b, 0x2b, 0x2b, 0xff });\n        }\n\n        gui_element_begin(gui);\n            gui_set_grow(gui, DIRECTION_HORIZONTAL);\n            gui_set_grow(gui, DIRECTION_VERTICAL);\n            gui_set_direction(gui, DIRECTION_HORIZONTAL);\n            gui_set_align(gui, ALIGN_LEFT, ALIGN_CENTER);\n            if (IsShaderValid(assets.line_shader)) {\n                gui_set_border(gui, (GuiColor) { 0x60, 0x60, 0x60, 0xff }, 2);\n                gui_set_shader(gui, &assets.line_shader);\n            }\n            gui_set_padding(gui, ELEMENT_GAP, 0);\n            gui_set_scissor(gui);\n\n            gui_grow(gui, DIRECTION_HORIZONTAL);\n            gui_text(gui, &assets.fonts.font_cond, sgettext(list[*value]), config.ui_size * 0.6, GUI_WHITE);\n            gui_grow(gui, DIRECTION_HORIZONTAL);\n            gui_image(gui, &assets.textures.dropdown, BLOCK_IMAGE_SIZE, GUI_WHITE);\n        gui_element_end(gui);\n    gui_element_end(gui);\n}\n\nstatic void draw_text_input(char** input, const char* hint, int* scroll, bool editable, bool path_input) {\n    gui_element_begin(gui);\n        gui_set_grow(gui, DIRECTION_HORIZONTAL);\n        gui_set_grow(gui, DIRECTION_VERTICAL);\n        gui_set_direction(gui, DIRECTION_HORIZONTAL);\n        gui_set_rect(gui, (GuiColor) { 0x30, 0x30, 0x30, 0xff });\n        gui_on_hover(gui, text_input_on_hover);\n        gui_set_custom_data(gui, input);\n\n        if (input == ui.hover.select_input) gui_set_rect(gui, (GuiColor) { 0x2b, 0x2b, 0x2b, 0xff });\n\n        gui_element_begin(gui);\n            gui_set_grow(gui, DIRECTION_HORIZONTAL);\n            gui_set_grow(gui, DIRECTION_VERTICAL);\n            gui_set_direction(gui, DIRECTION_HORIZONTAL);\n            gui_set_align(gui, ALIGN_LEFT, ALIGN_CENTER);\n            if (IsShaderValid(assets.line_shader)) {\n                gui_set_border(gui, (GuiColor) { 0x60, 0x60, 0x60, 0xff }, 2);\n                gui_set_shader(gui, &assets.line_shader);\n            }\n            gui_set_padding(gui, ELEMENT_GAP, 0);\n            gui_set_scroll(gui, scroll);\n            gui_set_scissor(gui);\n\n            if (editable) {\n                InputHoverInfo info = (InputHoverInfo) {\n                    .input = input,\n                    .rel_pos = (Vector2) { ELEMENT_GAP + *scroll, 0 },\n                    .font = &assets.fonts.font_cond,\n                    .font_size = config.ui_size * 0.6,\n                };\n                gui_set_state(gui, &info, sizeof(info));\n                gui_on_hover(gui, input_on_hover);\n            }\n\n            draw_input_text(&assets.fonts.font_cond, input, hint, config.ui_size * 0.6, GUI_WHITE);\n        gui_element_end(gui);\n    gui_element_end(gui);\n\n    if (path_input) draw_button(NULL, &assets.textures.icon_folder, settings_on_browse_button_click, input);\n}\n\nstatic void draw_button(const char* label, Texture2D* icon, ButtonClickHandler handler, void* data) {\n    gui_element_begin(gui);\n        gui_set_min_size(gui, 0, config.ui_size);\n        gui_set_rect(gui, (GuiColor) { 0x30, 0x30, 0x30, 0xff });\n        gui_on_hover(gui, settings_button_on_hover);\n        ButtonHoverInfo info = (ButtonHoverInfo) {\n            .handler = handler,\n            .data = data,\n        };\n        gui_set_state(gui, &info, sizeof(info));\n\n        gui_element_begin(gui);\n            gui_set_grow(gui, DIRECTION_HORIZONTAL);\n            gui_set_grow(gui, DIRECTION_VERTICAL);\n            gui_set_padding(gui, ELEMENT_GAP, 0);\n            gui_set_gap(gui, ELEMENT_GAP/2);\n            gui_set_direction(gui, DIRECTION_HORIZONTAL);\n            gui_set_align(gui, ALIGN_LEFT, ALIGN_CENTER);\n            if (IsShaderValid(assets.line_shader)) {\n                gui_set_border(gui, (GuiColor) { 0x60, 0x60, 0x60, 0xff }, 2);\n                gui_set_shader(gui, &assets.line_shader);\n            }\n\n            if (icon) gui_image(gui, icon, BLOCK_IMAGE_SIZE, GUI_WHITE);\n            if (label) gui_text(gui, &assets.fonts.font_cond, label, config.ui_size * 0.6, GUI_WHITE);\n        gui_element_end(gui);\n    gui_element_end(gui);\n}\n\nstatic void toggle_on_hover(GuiElement* el) {\n    el->color = (GuiColor) { 0x40, 0x40, 0x40, 0xff };\n    ui.hover.button.handler = settings_on_toggle_button_click;\n    ui.hover.button.data = el->custom_data;\n}\n\nstatic void draw_toggle(bool* value) {\n    gui_element_begin(gui);\n        gui_set_border(gui, (GuiColor) { 0x30, 0x30, 0x30, 0xff }, 2);\n        gui_element_begin(gui);\n            gui_set_fixed(gui, config.ui_size * 2, config.ui_size);\n            gui_set_rect(gui, (GuiColor) { 0x20, 0x20, 0x20, 0xff });\n            gui_set_direction(gui, DIRECTION_HORIZONTAL);\n            gui_on_hover(gui, toggle_on_hover);\n            gui_set_custom_data(gui, value);\n\n            if (*value) gui_spacer(gui, config.ui_size, 0);\n\n            gui_element_begin(gui);\n                gui_set_fixed(gui, config.ui_size, config.ui_size);\n                gui_set_rect(gui, *value ? (GuiColor) { 0x30, 0xff, 0x30, 0xff } : (GuiColor) { 0xff, 0x30, 0x30, 0xff });\n            gui_element_end(gui);\n        gui_element_end(gui);\n    gui_element_end(gui);\n}\n\nvoid handle_window(void) {\n    if (window.is_hiding) {\n        window.shown = false;\n        window.is_hiding = false;\n    }\n    if (window.is_fading) {\n        window.animation_time -= GetFrameTime() * 2.0;\n        if (window.animation_time < 0.0) {\n            window.animation_time = 0.0;\n            if (window.shown) ui.render_surface_needs_redraw = true;\n            window.shown = false;\n            if (about_text_split) {\n                for (size_t i = 0; i < vector_size(about_text_split); i++) {\n                    vector_free(about_text_split[i]);\n                }\n                vector_free(about_text_split);\n                about_text_split = NULL;\n            }\n        } else {\n            ui.render_surface_needs_redraw = true;\n        }\n    } else {\n        window.shown = true;\n        window.animation_time += GetFrameTime() * 2.0;\n        if (window.animation_time > 1.0) {\n            window.animation_time = 1.0;\n        } else {\n            ui.render_surface_needs_redraw = true;\n        }\n    }\n}\n\nvoid draw_settings_window(void) {\n    static int font_path_scroll = 0;\n    static int font_bold_path_scroll = 0;\n    static int font_mono_path_scroll = 0;\n\n    begin_window(gettext(\"Settings\"), &assets.textures.icon_settings, MIN(700 * config.ui_size / 32.0, gui->win_w - config.ui_size), 0, window.animation_ease);\n        begin_setting(gettext(\"Language\"), true);\n            draw_dropdown_input((int*)&window_config.language, language_list, ARRLEN(language_list));\n        end_setting();\n\n        begin_setting(gettext(\"UI size\"), false);\n            draw_slider(8, 64, &window_config.ui_size);\n        end_setting();\n\n        begin_setting(gettext(\"FPS limit\"), false);\n            draw_slider(0, 240, &window_config.fps_limit);\n        end_setting();\n\n        begin_setting(gettext(\"Font path\"), false);\n            draw_text_input(&window_config.font_path, gettext(\"path\"), &font_path_scroll, true, true);\n        end_setting();\n\n        begin_setting(gettext(\"Bold font path\"), false);\n            draw_text_input(&window_config.font_bold_path, gettext(\"path\"), &font_bold_path_scroll, true, true);\n        end_setting();\n\n        begin_setting(gettext(\"Monospaced font path\"), false);\n            draw_text_input(&window_config.font_mono_path, gettext(\"path\"), &font_mono_path_scroll, true, true);\n        end_setting();\n\n        begin_setting(gettext(\"Panel layout\"), false);\n            gui_element_begin(gui);\n                gui_set_grow(gui, DIRECTION_HORIZONTAL);\n                gui_set_grow(gui, DIRECTION_VERTICAL);\n                gui_set_direction(gui, DIRECTION_HORIZONTAL);\n\n                draw_button(gettext(\"Edit\"), &assets.textures.button_edit, settings_on_panel_editor_button_click, NULL);\n            gui_element_end(gui);\n        end_setting();\n\n        begin_setting(gettext(\"Show block previews\"), false);\n            gui_element_begin(gui);\n                gui_set_grow(gui, DIRECTION_HORIZONTAL);\n                gui_set_grow(gui, DIRECTION_VERTICAL);\n                gui_set_direction(gui, DIRECTION_HORIZONTAL);\n\n                draw_toggle(&window_config.show_blockchain_previews);\n            gui_element_end(gui);\n        end_setting();\n\n#ifdef DEBUG\n        begin_setting(gettext(\"Show debug info\"), false);\n            gui_element_begin(gui);\n                gui_set_grow(gui, DIRECTION_HORIZONTAL);\n                gui_set_grow(gui, DIRECTION_VERTICAL);\n                gui_set_direction(gui, DIRECTION_HORIZONTAL);\n\n                draw_toggle(&editor.show_debug);\n            gui_element_end(gui);\n        end_setting();\n#endif\n\n        gui_grow(gui, DIRECTION_VERTICAL);\n\n        gui_element_begin(gui);\n            gui_set_grow(gui, DIRECTION_HORIZONTAL);\n            gui_set_direction(gui, DIRECTION_HORIZONTAL);\n            gui_set_min_size(gui, 0, config.ui_size * 0.6);\n\n            gui_grow(gui, DIRECTION_HORIZONTAL);\n            if (settings_applied) gui_text(gui, &assets.fonts.font_cond, gettext(\"Settings applied\"), config.ui_size * 0.6, GUI_WHITE);\n        gui_element_end(gui);\n\n        gui_element_begin(gui);\n            gui_set_grow(gui, DIRECTION_HORIZONTAL);\n            gui_set_direction(gui, DIRECTION_HORIZONTAL);\n            gui_set_gap(gui, ELEMENT_GAP);\n\n            gui_grow(gui, DIRECTION_HORIZONTAL);\n            draw_button(gettext(\"Reset panels\"), NULL, settings_on_reset_panels_button_click, NULL);\n            draw_button(gettext(\"Reset\"), NULL, settings_on_reset_button_click, NULL);\n            draw_button(gettext(\"Apply\"), NULL, settings_on_apply_button_click, NULL);\n        gui_element_end(gui);\n    end_window();\n\n    if (settings_tooltip) {\n        gui_element_begin(gui);\n            gui_set_floating(gui);\n            gui_set_rect(gui, (GuiColor) { 0x00, 0x00, 0x00, 0x80 });\n            gui_set_position(gui, gui->mouse_x + 10, gui->mouse_y + 10);\n            gui_set_padding(gui, ELEMENT_GAP * 0.5, ELEMENT_GAP * 0.5);\n\n            gui_text(gui, &assets.fonts.font_cond, gettext(\"Needs restart for changes to take effect\"), config.ui_size * 0.6, GUI_WHITE);\n        gui_element_end(gui);\n    }\n\n    settings_tooltip = false;\n}\n\nvoid draw_project_settings_window(void) {\n    static int executable_name_scroll = 0;\n    static int linker_name_scroll = 0;\n\n    begin_window(gettext(\"Build settings\"), &assets.textures.button_build, MIN(600 * config.ui_size / 32.0, gui->win_w - config.ui_size), 0, window.animation_ease);\n        begin_setting(gettext(\"Executable name\"), false);\n            draw_text_input(&project_config.executable_name, gettext(\"name\"), &executable_name_scroll, true, false);\n        end_setting();\n\n        begin_setting(gettext(\"Linker name (Linux only)\"), false);\n            draw_text_input(&project_config.linker_name, gettext(\"name\"), &linker_name_scroll, true, false);\n        end_setting();\n\n        gui_grow(gui, DIRECTION_VERTICAL);\n\n        gui_element_begin(gui);\n            gui_set_grow(gui, DIRECTION_HORIZONTAL);\n            gui_set_direction(gui, DIRECTION_HORIZONTAL);\n            gui_set_gap(gui, ELEMENT_GAP);\n\n            gui_grow(gui, DIRECTION_HORIZONTAL);\n\n            draw_button(gettext(\"Build!\"), &assets.textures.button_build, project_settings_on_build_button_click, NULL);\n        gui_element_end(gui);\n    end_window();\n}\n\nvoid draw_about_window(void) {\n    if (!about_text_split) {\n        scrap_log(LOG_INFO, \"Split about text\");\n        about_text_split = vector_create();\n        const char* about_text = gettext(\"Scrap is a project that allows anyone to build\\n\"\n                                         \"software using simple, block based interface.\");\n        size_t about_text_len = strlen(about_text);\n\n        char* current_text = vector_create();\n        for (size_t i = 0; i < about_text_len; i++) {\n            if (about_text[i] == '\\n') {\n                vector_add(&about_text_split, current_text);\n                current_text = vector_create();\n                continue;\n            }\n            vector_add(&current_text, about_text[i]);\n        }\n        vector_add(&about_text_split, current_text);\n    }\n\n    begin_window(gettext(\"About\"), &assets.textures.icon_about, 500 * config.ui_size / 32.0, 0, window.animation_ease);\n        gui_element_begin(gui);\n            gui_set_direction(gui, DIRECTION_HORIZONTAL);\n            gui_set_align(gui, ALIGN_LEFT, ALIGN_CENTER);\n            gui_set_gap(gui, ELEMENT_GAP);\n\n            gui_image(gui, &assets.textures.icon_logo, config.ui_size, GUI_WHITE);\n            gui_text(gui, &assets.fonts.font_eb, \"Scrap \" SCRAP_VERSION, config.ui_size * 0.8, GUI_WHITE);\n        gui_element_end(gui);\n\n        gui_element_begin(gui);\n            if (about_text_split) {\n                for (size_t i = 0; i < vector_size(about_text_split); i++) {\n                    gui_text_slice(gui, &assets.fonts.font_cond, about_text_split[i], vector_size(about_text_split[i]), config.ui_size * 0.6, GUI_WHITE);\n                }\n            } else {\n                gui_text(gui, &assets.fonts.font_cond, \"ERROR\", config.ui_size * 0.6, (GuiColor) { 0xff, 0x20, 0x20, 0xff });\n            }\n        gui_element_end(gui);\n\n        gui_grow(gui, DIRECTION_VERTICAL);\n\n        gui_element_begin(gui);\n            gui_set_grow(gui, DIRECTION_HORIZONTAL);\n            gui_set_direction(gui, DIRECTION_HORIZONTAL);\n            gui_set_gap(gui, ELEMENT_GAP);\n\n            gui_grow(gui, DIRECTION_HORIZONTAL);\n            draw_button(gettext(\"License\"), &assets.textures.icon_file, about_on_license_button_click, NULL);\n        gui_element_end(gui);\n    end_window();\n}\n\nvoid draw_save_confirmation_window(void) {\n    begin_window(gettext(\"Confirm save\"), &assets.textures.icon_about, 500 * config.ui_size / 32.0, 0, window.animation_ease);\n        gui_text(gui, &assets.fonts.font_cond, gettext(\"Project is modified. Save the changes before quitting?\"), config.ui_size * 0.6, GUI_WHITE);\n\n        gui_grow(gui, DIRECTION_VERTICAL);\n\n        gui_element_begin(gui);\n            gui_set_grow(gui, DIRECTION_HORIZONTAL);\n            gui_set_direction(gui, DIRECTION_HORIZONTAL);\n            gui_set_gap(gui, ELEMENT_GAP);\n\n            gui_grow(gui, DIRECTION_HORIZONTAL);\n\n            draw_button(gettext(\"Yes\"),    NULL, save_confirmation_on_yes_button_click, NULL);\n            draw_button(gettext(\"No\"),     NULL, save_confirmation_on_no_button_click, NULL);\n            draw_button(gettext(\"Cancel\"), NULL, save_confirmation_on_cancel_button_click, NULL);\n        gui_element_end(gui);\n    end_window();\n}\n\nvoid draw_window(void) {\n    if (!window.shown) return;\n\n    window.animation_ease = ease_out_expo(window.animation_time);\n    window.render();\n}\n"
  },
  {
    "path": "translations/kk/LC_MESSAGES/scrap.po",
    "content": "msgid \"\"\nmsgstr \"\"\n\"Language: kz\\n\"\n\"Content-Type: text/plain; charset=UTF-8\\n\"\n\n#: blocks.c\nmsgid \"Hello, scrap!\"\nmsgstr \"Сәлем, scrap!\"\n\n#: blocks.c\nmsgid \"Control\"\nmsgstr \"Бақылау\"\n\n#: blocks.c\nmsgid \"Terminal\"\nmsgstr \"Терминал\"\n\n#: blocks.c\nmsgid \"Math\"\nmsgstr \"Математика\"\n\n#: blocks.c\nmsgid \"Logic\"\nmsgstr \"Логика\"\n\n#: blocks.c\nmsgid \"Strings\"\nmsgstr \"Жолдар\"\n\n#: blocks.c\nmsgid \"Misc.\"\nmsgstr \"Әртүрлі.\"\n\n#: blocks.c\nmsgid \"Data\"\nmsgstr \"Данные\"\n\n#: blocks.c\nmsgid \"When\"\nmsgstr \"Қашан\"\n\n#: blocks.c\nmsgid \"clicked\"\nmsgstr \"басылды\"\n\n#: blocks.c\nmsgid \"Loop\"\nmsgstr \"Цикл\"\n\n#: blocks.c\nmsgid \"Repeat\"\nmsgstr \"Қайталау\"\n\n#: blocks.c\nmsgid \"times\"\nmsgstr \"рет\"\n\n#: blocks.c\nmsgid \"While\"\nmsgstr \"Әзірге\"\n\n#: blocks.c\nmsgid \"If\"\nmsgstr \"Егер\"\n\n#: blocks.c\nmsgid \", then\"\nmsgstr \", содан кейін\"\n\n#: blocks.c\nmsgid \"Else if\"\nmsgstr \"Әйтпесе, егер\"\n\n#: blocks.c\nmsgid \"Else\"\nmsgstr \"Басқа\"\n\n#: blocks.c\nmsgid \"Do nothing\"\nmsgstr \"Ештеңе жасама\"\n\n#: blocks.c\nmsgid \"Sleep\"\nmsgstr \"Ұйқы\"\n\n#: blocks.c\nmsgid \"μs\"\nmsgstr \"мкс\"\n\n#: blocks.c\nmsgid \"Define\"\nmsgstr \"Анықтаңыз\"\n\n#: blocks.c\nmsgid \"Return\"\nmsgstr \"Қайту\"\n\n#: blocks.c\nmsgid \"Get input\"\nmsgstr \"Кіріс алыңыз\"\n\n#: blocks.c\nmsgid \"Get char\"\nmsgstr \"Шарды алыңыз\"\n\n#: blocks.c\nmsgid \"Print\"\nmsgstr \"Басып шығару\"\n\n#: blocks.c\nmsgid \"Print line\"\nmsgstr \"Басып шығару сызығы\"\n\n#: blocks.c\nmsgid \"Cursor X\"\nmsgstr \"Меңзер X\"\n\n#: blocks.c\nmsgid \"Cursor Y\"\nmsgstr \"Меңзер Y\"\n\n#: blocks.c\nmsgid \"Terminal width\"\nmsgstr \"Терминал ені\"\n\n#: blocks.c\nmsgid \"Terminal height\"\nmsgstr \"Терминал биіктігі\"\n\n#: blocks.c\nmsgid \"Set cursor X:\"\nmsgstr \"курсорын орнату X:\"\n\n#: blocks.c\nmsgid \"Y:\"\nmsgstr \"Y:\"\n\n#: blocks.c\nmsgid \"Set text color\"\nmsgstr \"Мәтін түсін орнату\"\n\n#: blocks.c\nmsgid \"Set background color\"\nmsgstr \"Фон түсін орнату\"\n\n#: blocks.c\nmsgid \"Reset color\"\nmsgstr \"Түсті қалпына келтіру\"\n\n#: blocks.c\nmsgid \"Clear terminal\"\nmsgstr \"Терминалды тазалау\"\n\n#: blocks.c\nmsgid \"Set clear color\"\nmsgstr \"Ашық түсті орнатыңыз\"\n\n#: blocks.c\nmsgid \"Pow\"\nmsgstr \"Дәреже\"\n\n#: blocks.c\nmsgid \"Pi\"\nmsgstr \"Пи\"\n\n#: blocks.c\nmsgid \"Not\"\nmsgstr \"Жоқ\"\n\n#: blocks.c\nmsgid \"and\"\nmsgstr \"және\"\n\n#: blocks.c\nmsgid \"or\"\nmsgstr \"немесе\"\n\n#: blocks.c\nmsgid \"True\"\nmsgstr \"Рас\"\n\n#: blocks.c\nmsgid \"False\"\nmsgstr \"Жалған\"\n\n#: blocks.c\nmsgid \"Random\"\nmsgstr \"Кездейсоқ\"\n\n#: blocks.c\nmsgid \"to\"\nmsgstr \"дейін\"\n\n#: blocks.c\nmsgid \"Join\"\nmsgstr \"Қосылыңыз\"\n\n#: blocks.c\nmsgid \"left and \"\nmsgstr \"солға және \"\n\n#: blocks.c\nmsgid \"right\"\nmsgstr \"дұрыс\"\n\n#: blocks.c\nmsgid \"Ord\"\nmsgstr \"Оrd\"\n\n#: blocks.c\nmsgid \"Chr\"\nmsgstr \"Chr\"\n\n#: blocks.c\nmsgid \"Letter\"\nmsgstr \"Хат\"\n\n#: blocks.c\nmsgid \"in\"\nmsgstr \"жылы\"\n\n#: blocks.c\nmsgid \"Substring\"\nmsgstr \"Ішкі жол\"\n\n#: blocks.c\nmsgid \"Length\"\nmsgstr \"Ұзындығы\"\n\n#: blocks.c\nmsgid \"string\"\nmsgstr \"жол\"\n\n#: blocks.c\nmsgid \"Time since 1970\"\nmsgstr \"1970 жылдан бері уақыт\"\n\n#: blocks.c\nmsgid \"Int\"\nmsgstr \"Бүтін сан\"\n\n#: blocks.c\nmsgid \"Float\"\nmsgstr \"Қалқымалы\"\n\n#: blocks.c\nmsgid \"Str\"\nmsgstr \"Кіші әріп\"\n\n#: blocks.c\nmsgid \"Bool\"\nmsgstr \"Булевое\"\n\n#: blocks.c\nmsgid \"Nothing\"\nmsgstr \"Ештеңе\"\n\n#: blocks.c\nmsgid \"Declare\"\nmsgstr \"Мәлімдеу\"\n\n#: blocks.c\nmsgid \"my variable\"\nmsgstr \"менің айнымалым\"\n\n#: blocks.c\nmsgid \"Get\"\nmsgstr \"Алу\"\n\n#: blocks.c\nmsgid \"Set\"\nmsgstr \"Орнату\"\n\n#: blocks.c\nmsgid \"Empty list\"\nmsgstr \"Бос тізім\"\n\n#: blocks.c\nmsgid \"Add\"\nmsgstr \"Қосу\"\n\n#: blocks.c\nmsgid \"value\"\nmsgstr \"мән\"\n\n#: blocks.c\nmsgid \"get at\"\nmsgstr \"жету\"\n\n#: blocks.c\nmsgid \"set at\"\nmsgstr \"орнатыңыз\"\n\n#: blocks.c\nmsgid \"My block\"\nmsgstr \"Менің блогым\"\n\n#: render.c\nmsgid \"Scrap\"\nmsgstr \"Scrap\"\n\n#: input.c\nmsgid \"Code\"\nmsgstr \"Код\"\n\n#: input.c\nmsgid \"Output\"\nmsgstr \"Шығару\"\n\n#: input.c\nmsgid \"Block categories\"\nmsgstr \"Код сәтті орындалды\"\n\n#: input.c\nmsgid \"Block palette\"\nmsgstr \"Блок палитрасы\"\n\n#: render.c\nmsgid \"File\"\nmsgstr \"Файл\"\n\n#: render.c\nmsgid \"Settings\"\nmsgstr \"Параметрлер\"\n\n#: render.c\nmsgid \"About\"\nmsgstr \"Туралы\"\n\n#: scrap.c\nmsgid \"Vm executed successfully\"\nmsgstr \"Код сәтті орындалды\"\n\n#: scrap.c\nmsgid \"Vm stopped >:(\"\nmsgstr \"Код тоқтатылды >:(\"\n\n#: scrap.c\nmsgid \"Vm shitted and died :(\"\nmsgstr \"Код бұзылды және өлді :(\"\n\n#: input.c\nmsgid \"File load failed :(\"\nmsgstr \"Файл жүктелмеді :(\"\n\n#: input.c\nmsgid \"File load succeeded!\"\nmsgstr \"Файл жүктелді!\"\n\n#: input.c\nmsgid \"Jump to chain (%d/%d)\"\nmsgstr \"Переход на цепочку (%d/%d)\"\n\n#: render.c\nmsgid \"Start failed!\"\nmsgstr \"Кодты бастау сәтсіз аяқталды!\"\n\n#: render.c\nmsgid \"Started successfully!\"\nmsgstr \"Код сәтті басталды!\"\n\n#: render.c\nmsgid \"New project\"\nmsgstr \"Жаңа жоба\"\n\n#: render.c\nmsgid \"Save project\"\nmsgstr \"Жобаны сақтау\"\n\n#: render.c\nmsgid \"Load project\"\nmsgstr \"Жобаны жүктеңіз\"\n\n#: window.c\nmsgid \"UI size\"\nmsgstr \"Пайдаланушы интерфейсінің өлшемі\"\n\n#: window.c\nmsgid \"FPS limit\"\nmsgstr \"FPS шегі\"\n\n#: window.c\nmsgid \"Font path\"\nmsgstr \"Қаріп жолы\"\n\n#: window.c\nmsgid \"Bold font path\"\nmsgstr \"Қалың шрифт жолы\"\n\n#: window.c\nmsgid \"Monospaced font path\"\nmsgstr \"Бір кеңістікті қаріп жолы\"\n\n#: window.c\nmsgid \"Panel editor\"\nmsgstr \"Панель редакторы\"\n\n#: window.c\nmsgid \"Open\"\nmsgstr \"Ашық\"\n\n#: window.c\nmsgid \"Reset panels\"\nmsgstr \"Панельдерді қалпына келтіру\"\n\n#: window.c\nmsgid \"Reset\"\nmsgstr \"Қалпына келтіру\"\n\n#: window.c\nmsgid \"Apply\"\nmsgstr \"Қолдану\"\n\n#: window.c\nmsgid \"Needs restart for changes to take effect\"\nmsgstr \"Өзгерістер күшіне енуі үшін қайта іске қосу қажет\"\n\n#: window.c\nmsgid \"Scrap is a project that allows anyone to build\\n\"\n      \"software using simple, block based interface.\"\nmsgstr \"Scrap - бұл кез келген адамға салуға мүмкіндік беретін\\n\"\n       \"жоба қарапайым, блок негізіндегі интерфейсті\\n\"\n       \"пайдаланатын бағдарламалық қамтамасыз ету.\"\n\n#: window.c\nmsgid \"License\"\nmsgstr \"Лицензия\"\n\n#: render.c\nmsgid \"Panel edit mode\"\nmsgstr \"Панельді өңдеу режимі\"\n\n#: render.c\nmsgid \"Click on panels to reposition them\"\nmsgstr \"Олардың орнын өзгерту үшін панельдерді басыңыз\"\n\n#: render.c\nmsgid \"Drag panel edges to resize them\"\nmsgstr \"Өлшемдерін өзгерту үшін панель жиектерін сүйреңіз\"\n\n#: render.c\nmsgid \"Done\"\nmsgstr \"Дайын\"\n\n#: render.c\nmsgid \"Save\"\nmsgstr \"Сақтау\"\n"
  },
  {
    "path": "translations/ru/LC_MESSAGES/scrap.po",
    "content": "msgid \"\"\nmsgstr \"\"\n\"Language: ru\\n\"\n\"Content-Type: text/plain; charset=UTF-8\\n\"\n\n#: blocks.c\nmsgid \"Hello, scrap!\"\nmsgstr \"Привет, мусороид!\"\n\n#: blocks.c\nmsgid \"Control\"\nmsgstr \"Управление\"\n\n#: blocks.c\nmsgid \"Terminal\"\nmsgstr \"Терминал\"\n\n#: blocks.c\nmsgid \"Math\"\nmsgstr \"Математика\"\n\n#: blocks.c\nmsgid \"Logic\"\nmsgstr \"Логика\"\n\n#: blocks.c\nmsgid \"Strings\"\nmsgstr \"Строки\"\n\n#: blocks.c\nmsgid \"Misc.\"\nmsgstr \"Другое\"\n\n#: blocks.c\nmsgid \"Data\"\nmsgstr \"Данные\"\n\n#: blocks.c\nmsgid \"When\"\nmsgstr \"Когда\"\n\n#: blocks.c\nmsgid \"clicked\"\nmsgstr \"нажат\"\n\n#: blocks.c\nmsgid \"Loop\"\nmsgstr \"Цикл\"\n\n#: blocks.c\nmsgid \"Repeat\"\nmsgstr \"Повторить\"\n\n#: blocks.c\nmsgid \"times\"\nmsgstr \"раз\"\n\n#: blocks.c\nmsgid \"While\"\nmsgstr \"Пока\"\n\n#: blocks.c\nmsgid \"If\"\nmsgstr \"Если\"\n\n#: blocks.c\nmsgid \", then\"\nmsgstr \", то\"\n\n#: blocks.c\nmsgid \"Else if\"\nmsgstr \"Иначе если\"\n\n#: blocks.c\nmsgid \"Else\"\nmsgstr \"Иначе\"\n\n#: blocks.c\nmsgid \"Do nothing\"\nmsgstr \"Делать ничего\"\n\n#: blocks.c\nmsgid \"Sleep\"\nmsgstr \"Ждать\"\n\n#: blocks.c\nmsgid \"μs\"\nmsgstr \"мкс\"\n\n#: blocks.c\nmsgid \"Define\"\nmsgstr \"Определить\"\n\n#: blocks.c\nmsgid \"Return\"\nmsgstr \"Вернуть\"\n\n#: blocks.c\nmsgid \"Get input\"\nmsgstr \"Получить ввод\"\n\n#: blocks.c\nmsgid \"Get char\"\nmsgstr \"Получить символ\"\n\n#: blocks.c\nmsgid \"Print\"\nmsgstr \"Напечатать\"\n\n#: blocks.c\nmsgid \"Print line\"\nmsgstr \"Напечатать линию\"\n\n#: blocks.c\nmsgid \"Cursor X\"\nmsgstr \"X курсора\"\n\n#: blocks.c\nmsgid \"Cursor Y\"\nmsgstr \"Y курсора\"\n\n#: blocks.c\nmsgid \"Terminal width\"\nmsgstr \"Ширина терминала\"\n\n#: blocks.c\nmsgid \"Terminal height\"\nmsgstr \"Высота терминала\"\n\n#: blocks.c\nmsgid \"Set cursor X:\"\nmsgstr \"Задать позицию курсора X:\"\n\n#: blocks.c\nmsgid \"Y:\"\nmsgstr \"Y:\"\n\n#: blocks.c\nmsgid \"Set text color\"\nmsgstr \"Задать цвет текста\"\n\n#: blocks.c\nmsgid \"Set background color\"\nmsgstr \"Задать цвет фона\"\n\n#: blocks.c\nmsgid \"Reset color\"\nmsgstr \"Сбросить цвет\"\n\n#: blocks.c\nmsgid \"Clear terminal\"\nmsgstr \"Очистить терминал\"\n\n#: blocks.c\nmsgid \"Set clear color\"\nmsgstr \"Задать цвет очистки\"\n\n#: blocks.c\nmsgid \"Pow\"\nmsgstr \"Степень\"\n\n#: blocks.c\nmsgid \"Not\"\nmsgstr \"Не\"\n\n#: blocks.c\nmsgid \"and\"\nmsgstr \"и\"\n\n#: blocks.c\nmsgid \"or\"\nmsgstr \"или\"\n\n#: blocks.c\nmsgid \"True\"\nmsgstr \"Истина\"\n\n#: blocks.c\nmsgid \"False\"\nmsgstr \"Ложь\"\n\n#: blocks.c\nmsgid \"Random from\"\nmsgstr \"Случайное целое от\"\n\n#: blocks.c\nmsgid \"to\"\nmsgstr \"до\"\n\n#: blocks.c\nmsgid \"Join\"\nmsgstr \"Склеить\"\n\n#: blocks.c\nmsgid \"left and \"\nmsgstr \"левое и \"\n\n#: blocks.c\nmsgid \"right\"\nmsgstr \"правое\"\n\n#: blocks.c\nmsgid \"Ord\"\nmsgstr \"Ord\"\n\n#: blocks.c\nmsgid \"Chr\"\nmsgstr \"Chr\"\n\n#: blocks.c\nmsgid \"Letter\"\nmsgstr \"Буква\"\n\n#: blocks.c\nmsgid \"in\"\nmsgstr \"в\"\n\n#: blocks.c\nmsgid \"Substring\"\nmsgstr \"Подстрока\"\n\n#: blocks.c\nmsgid \"Length\"\nmsgstr \"Длина\"\n\n#: blocks.c\nmsgid \"string\"\nmsgstr \"строка\"\n\n#: blocks.c\nmsgid \"Time since 1970\"\nmsgstr \"Время с 1970\"\n\n#: blocks.c\nmsgid \"Int\"\nmsgstr \"Целое\"\n\n#: blocks.c\nmsgid \"Float\"\nmsgstr \"Вещ-ое\"\n\n#: blocks.c\nmsgid \"Str\"\nmsgstr \"Строчное\"\n\n#: blocks.c\nmsgid \"Bool\"\nmsgstr \"Булевое\"\n\n#: blocks.c\nmsgid \"Color\"\nmsgstr \"Цвет\"\n\n#: blocks.c\nmsgid \"Nothing\"\nmsgstr \"Ничего\"\n\n#: blocks.c\nmsgid \"Declare\"\nmsgstr \"Объявить\"\n\n#: blocks.c\nmsgid \"my variable\"\nmsgstr \"моя переменная\"\n\n#: blocks.c\nmsgid \"list\"\nmsgstr \"список\"\n\n#: blocks.c\nmsgid \"Collect garbage\"\nmsgstr \"Собрать мусор\"\n\n#: blocks.c\nmsgid \"Get\"\nmsgstr \"Получить\"\n\n#: blocks.c\nmsgid \"Set\"\nmsgstr \"Задать\"\n\n#: blocks.c\nmsgid \"Empty list\"\nmsgstr \"Пустой список\"\n\n#: blocks.c\nmsgid \"Add\"\nmsgstr \"Добавить\"\n\n#: blocks.c\nmsgid \"value\"\nmsgstr \"значение\"\n\n#: blocks.c\nmsgid \"get at\"\nmsgstr \"получить по индексу\"\n\n#: blocks.c\nmsgid \"set at\"\nmsgstr \"задать по индексу\"\n\n#: blocks.c\nmsgid \"My block\"\nmsgstr \"Мой блок\"\n\n#: blocks.c\nmsgid \"Type of\"\nmsgstr \"Тип от\"\n\n#: blocks.c\nmsgid \"Invalid data type %s, expected %s\"\nmsgstr \"Неверный тип данных %s, ожидался %s\"\n\n#: blocks.c\nmsgid \"Variable with name \\\"%s\\\" does not exist in the current scope\"\nmsgstr \"Переменная с именем \\\"%s\\\" не существует в данном окружении\"\n\n#: blocks.c\nmsgid \"Assign to variable \\\"%s\\\" of type %s with incompatible type %s\"\nmsgstr \"Присвоение в переменную \\\"%s\\\" с типом %s значением несовместимого типа %s\"\n\n#: blocks.c\nmsgid \"Variable declarations are not allowed inside an argument\"\nmsgstr \"Объявления переменных запрещены внутри аргументов\"\n\n#: blocks.c\nmsgid \"Cannot declare variable with empty name\"\nmsgstr \"Нельзя объявлять переменную с пустым именем\"\n\n#: blocks.c\nmsgid \"Tried to access index %d for list of length %d\"\nmsgstr \"Попытка получить элемент по индексу %d для списка размером %d\"\n\n#: blocks.c\nmsgid \"Division by zero\"\nmsgstr \"Деление на ноль\"\n\n#: blocks.c\nmsgid \"Invalid argument %s\"\nmsgstr \"Неверный аргумент %s\"\n\n#: blocks.c\nmsgid \"Unknown function id \\\"%s\\\"\"\nmsgstr \"Неизвестный id функции\\\"%s\\\"\"\n\n#: blocks.c\nmsgid \"Unknown argument id \\\"%s\\\"\"\nmsgstr \"Неизвестный id аргумента\\\"%s\\\"\"\n\n#: blocks.c\nmsgid \"Cannot represent %s as LLVM value\"\nmsgstr \"Невозможно представить %s как значение LLVM\"\n\n#: blocks.c\nmsgid \"Cannot cast type %s into %s\"\nmsgstr \"Невозможно преобразовать тип %s в %s\"\n\n#: blocks.c\nmsgid \"Cannot cast type %s into any string\"\nmsgstr \"Невозможно преобразовать тип %s в any string\"\n\n#: blocks.c\nmsgid \"Could not find function definition for argument\"\nmsgstr \"Не удалось найти определение функции для аргумента\"\n\n#: blocks.c\nmsgid \"Function argument block used outside of function\"\nmsgstr \"Использование блока аргумента функции вне функции\"\n\n#: blocks.c\nmsgid \"Too many parameters passed into function. Got %d/32\"\nmsgstr \"Слишком много аргументов передано в функцию. Передано %d/32\"\n\n#: blocks.c\nmsgid \"Invalid type %s in print function\"\nmsgstr \"Неверный тип %s в функции печати\"\n\n#: blocks.c\nmsgid \"Unhandled type %s in print function\"\nmsgstr \"Необработанный тип %s в функции печати\"\n\n#: blocks.c\nmsgid \"Cannot declare a variable with zero sized type (i.e. Nothing)\"\nmsgstr \"Невозможно объявить переменную с типом нулевого размера (т.е. Ничего)\"\n\n#: blocks.c\nmsgid \"at\"\nmsgstr \"в\"\n\n#: save.c\nmsgid \"System\"\nmsgstr \"Система\"\n\n#: input.c\nmsgid \"Code\"\nmsgstr \"Код\"\n\n#: input.c\nmsgid \"Output\"\nmsgstr \"Вывод\"\n\n#: input.c\nmsgid \"Block categories\"\nmsgstr \"Категории блоков\"\n\n#: input.c\nmsgid \"Block palette\"\nmsgstr \"Палитра блоков\"\n\n#: render.c\nmsgid \"Scrap\"\nmsgstr \"Scrap\"\n\n#: render.c\nmsgid \"File\"\nmsgstr \"Файл\"\n\n#: render.c\nmsgid \"Settings\"\nmsgstr \"Настройки\"\n\n#: render.c\nmsgid \"About\"\nmsgstr \"О программе\"\n\n#: render.c\nmsgid \"Jump to block\"\nmsgstr \"Перейти к блоку\"\n\n#: render.c\nmsgid \"Close\"\nmsgstr \"Закрыть\"\n\n#: scrap.c\nmsgid \"Vm executed successfully\"\nmsgstr \"Код выполнен успешно\"\n\n#: scrap.c\nmsgid \"Vm stopped >:(\"\nmsgstr \"Код остановлен >:(\"\n\n#: scrap.c\nmsgid \"Vm shitted and died :(\"\nmsgstr \"Код высрал и сдох :(\"\n\n#: input.c\nmsgid \"File load failed :(\"\nmsgstr \"Загрузка файла не удалась :(\"\n\n#: input.c\nmsgid \"File load succeeded!\"\nmsgstr \"Загрузка файла удалась!\"\n\n#: input.c\nmsgid \"Jump to chain (%d/%d)\"\nmsgstr \"Переход на цепочку (%d/%d)\"\n\n#: render.c\nmsgid \"Start failed!\"\nmsgstr \"Запуск кода провалился!\"\n\n#: render.c\nmsgid \"Started successfully!\"\nmsgstr \"Код успешно запущен!\"\n\n#: render.c\nmsgid \"New project\"\nmsgstr \"Новый проект\"\n\n#: render.c\nmsgid \"Save project\"\nmsgstr \"Сохранить проект\"\n\n#: render.c\nmsgid \"Load project\"\nmsgstr \"Загрузить проект\"\n\n#: window.c\nmsgid \"UI size\"\nmsgstr \"Размер интерфейса\"\n\n#: window.c\nmsgid \"FPS limit\"\nmsgstr \"Ограничение FPS\"\n\n#: window.c\nmsgid \"Font path\"\nmsgstr \"Путь шрифта\"\n\n#: window.c\nmsgid \"Bold font path\"\nmsgstr \"Путь жирного шрифта\"\n\n#: window.c\nmsgid \"Monospaced font path\"\nmsgstr \"Путь моноширинного шрифта\"\n\n#: window.c\nmsgid \"Panel layout\"\nmsgstr \"Расположение панелей\"\n\n#: window.c\nmsgid \"Open\"\nmsgstr \"Открыть\"\n\n#: window.c\nmsgid \"Reset panels\"\nmsgstr \"Сбросить панели\"\n\n#: window.c\nmsgid \"Reset\"\nmsgstr \"Сбросить\"\n\n#: window.c\nmsgid \"Apply\"\nmsgstr \"Применить\"\n\n#: window.c\nmsgid \"Needs restart for changes to take effect\"\nmsgstr \"Требуется перезагрузка, чтобы изменения вступили в силу\"\n\n#: window.c\nmsgid \"Scrap is a project that allows anyone to build\\n\"\n      \"software using simple, block based interface.\"\nmsgstr \"Scrap - это проект, который позволяет любому\\n\"\n       \"создавать софт, используя простой, блочный интерфейс\"\n\n#: window.c\nmsgid \"License\"\nmsgstr \"Лицензия\"\n\n#: window.c\nmsgid \"Build settings\"\nmsgstr \"Настройки сборки\"\n\n#: window.c\nmsgid \"Executable name\"\nmsgstr \"Имя приложения\"\n\n#: window.c\nmsgid \"Linker name (Linux only)\"\nmsgstr \"Имя линковщика (только для Linux)\"\n\n#: window.c\nmsgid \"Build!\"\nmsgstr \"Собрать!\"\n\n#: window.c\nmsgid \"Edit\"\nmsgstr \"Изменить\"\n\n#: render.c\nmsgid \"Panel edit mode\"\nmsgstr \"Режим редактирования панелей\"\n\n#: render.c\nmsgid \"Click on panels to reposition them\"\nmsgstr \"Нажимайте на панели чтобы их передвигать\"\n\n#: render.c\nmsgid \"Drag panel edges to resize them\"\nmsgstr \"Перетягивайте края панелей, чтобы изменить размер\"\n\n#: render.c\nmsgid \"Done\"\nmsgstr \"Готово\"\n\n#: render.c\nmsgid \"Save\"\nmsgstr \"Сохранить\"\n\n#: render.c\nmsgid \"Got compiler error!\"\nmsgstr \"Ошибка компиляции!\"\n\n#: blocks.c\nmsgid \"any\"\nmsgstr \"любое\"\n\n#: blocks.c\nmsgid \"cond.\"\nmsgstr \"усл.\"\n\n#: blocks.c\nmsgid \"Abc\"\nmsgstr \"Абв\"\n\n#: blocks.c\nmsgid \"Conditionals\"\nmsgstr \"Условия\"\n\n#: blocks.c\nmsgid \"Loops\"\nmsgstr \"Циклы\"\n\n#: blocks.c\nmsgid \"Functions\"\nmsgstr \"Функции\"\n\n#: blocks.c\nmsgid \"Input/Output\"\nmsgstr \"Ввод/Вывод\"\n\n#: blocks.c\nmsgid \"Cursor\"\nmsgstr \"Курсор\"\n\n#: blocks.c\nmsgid \"Colors\"\nmsgstr \"Цвета\"\n\n#: blocks.c\nmsgid \"Comparisons\"\nmsgstr \"Сравнения\"\n\n#: blocks.c\nmsgid \"Boolean math\"\nmsgstr \"Булевая математика\"\n\n#: blocks.c\nmsgid \"Bitwise math\"\nmsgstr \"Побитовые операторы\"\n\n#: blocks.c\nmsgid \"Variables\"\nmsgstr \"Переменные\"\n\n#: blocks.c\nmsgid \"Lists\"\nmsgstr \"Списки\"\n\n#: blocks.c\nmsgid \"Type casting\"\nmsgstr \"Преобразование типов\"\n\n#: blocks.c\nmsgid \"Doing nothing\"\nmsgstr \"Уголок бездельничества\"\n\n#: blocks.c\nmsgid \"Debug blocks\"\nmsgstr \"Дебаг-блоки\"\n\n#: window.c\nmsgid \"path\"\nmsgstr \"путь\"\n\n#: window.c\nmsgid \"Language\"\nmsgstr \"Язык\"\n\n#: window.c\nmsgid \"Show block previews\"\nmsgstr \"Показывать предпросмотр блоков\"\n\n#: window.c\nmsgid \"Show debug info\"\nmsgstr \"Показывать отладочную информацию\"\n\n#: window.c\nmsgid \"Settings applied\"\nmsgstr \"Настройки применены\"\n\n#: window.c\nmsgid \"Confirm save\"\nmsgstr \"Подтвердите сохранение\"\n\n#: window.c\nmsgid \"Project is modified. Save the changes before quitting?\"\nmsgstr \"Проект изменён. Сохранить изменения перед выходом?\"\n\n#: window.c\nmsgid \"Yes\"\nmsgstr \"Да\"\n\n#: window.c\nmsgid \"No\"\nmsgstr \"Нет\"\n\n#: window.c\nmsgid \"Cancel\"\nmsgstr \"Отмена\"\n\n#: save.c\nmsgid \"LanguageList|System\"\nmsgstr \"Системный\"\n\n#: save.c\nmsgid \"English [en]\"\nmsgstr \"Английский [en]\"\n\n#: save.c\nmsgid \"Russian [ru]\"\nmsgstr \"Русский [ru]\"\n\n#: save.c\nmsgid \"Kazakh [kk]\"\nmsgstr \"Казахский [kk]\"\n\n#: save.c\nmsgid \"Ukrainian [uk]\"\nmsgstr \"Украинский [uk]\"\n\n#: save.c\nmsgid \"UNKNOWN %s\"\nmsgstr \"НЕИЗВЕСТНЫЙ %s\"\n\n#: compiler.c\nmsgid \"Control stack overflow\"\nmsgstr \"Переполнение стека контроля\"\n\n#: compiler.c\nmsgid \"Variable stack overflow\"\nmsgstr \"Переполнение стека переменных\"\n\n#: compiler.c\nmsgid \"Tried to compile block without definition\"\nmsgstr \"Попытка скомпилировать блок без определения\"\n\n#: compiler.c\nmsgid \"Tried to compile block \\\"%s\\\" without implementation\"\nmsgstr \"Попытка скомпилировать блок \\\"%s\\\" без реализации\"\n\n#: compiler.c\nmsgid \"Function with name \\\"%s\\\" does not exist\"\nmsgstr \"Функции с именем \\\"%s\\\" не существует\"\n\n#: compiler.c\nmsgid \"Failed to build module: %s\"\nmsgstr \"Не удалось собрать модуль: %s\"\n\n#: interpreter.c\nmsgid \"Tried to execute block without definition\"\nmsgstr \"Попытка выполнить блок без определения\"\n\n#: interpreter.c\nmsgid \"Tried to execute block \\\"%s\\\" without implementation\"\nmsgstr \"Попытка выполнить блок \\\"%s\\\" без реализации\"\n\n#: platform.c\nmsgid \"Failed to create a pipe. Error code: %ld\"\nmsgstr \"Не удалось создать канал. Код ошибки: %ld\"\n\n#: platform.c\nmsgid \"Failed to create a process. Error code: %ld\"\nmsgstr \"Не удалось создать процесс. Код ошибки: %ld\"\n\n#: platform.c\nmsgid \"Failed to read from pipe. Error code: %ld\"\nmsgstr \"Не удалось прочитать данные из канала. Код ошибки: %ld\"\n\n#: platform.c\nmsgid \"Failed to get exit code. Error code: %ld\"\nmsgstr \"Не удалось получить код выхода. Код ошибки: %ld\"\n\n#: platform.c\nmsgid \"Linker exited with exit code: %ld\"\nmsgstr \"Линковщик завершился с кодом ошибки: %ld\"\n\n#: platform.c\nmsgid \"Failed to fork a process: %s\"\nmsgstr \"Не удалось форкнуть процесс: %s\"\n\n#: platform.c\nmsgid \"Linker signaled with signal number: %d\"\nmsgstr \"Линковщик завершился с кодом сигнала: %d\"\n\n#: platform.c\nmsgid \"Received unknown child status :/\"\nmsgstr \"Получен неизвестный статус от дочернего процесса :/\"\n\n#: platform.c\nmsgid \" is not installed on your system or not installed correctly. Please install it and add it to your PATH environment variable to be able to build executables\"\nmsgstr \" не установлен в системе или установлен неправильно. Пожалуйста, установите его и убедитесь что путь до него добавлен в переменную среды PATH, чтобы можно было собирать проекты\"\n"
  },
  {
    "path": "translations/uk/LC_MESSAGES/scrap.po",
    "content": "msgid \"\"\nmsgstr \"\"\n\"Language: uk\\n\"\n\"Content-Type: text/plain; charset=UTF-8\\n\"\n\n#: blocks.c\nmsgid \"Hello, scrap!\"\nmsgstr \"Привіт, сміттеїде!\"\n\n#: blocks.c\nmsgid \"Control\"\nmsgstr \"Керування\"\n\n#: blocks.c\nmsgid \"Terminal\"\nmsgstr \"Термінал\"\n\n#: blocks.c\nmsgid \"Math\"\nmsgstr \"Математика\"\n\n#: blocks.c\nmsgid \"Logic\"\nmsgstr \"Логіка\"\n\n#: blocks.c\nmsgid \"Strings\"\nmsgstr \"Рядки\"\n\n#: blocks.c\nmsgid \"Misc.\"\nmsgstr \"Інше\"\n\n#: blocks.c\nmsgid \"Data\"\nmsgstr \"Дані\"\n\n#: blocks.c\nmsgid \"When\"\nmsgstr \"Коли\"\n\n#: blocks.c\nmsgid \"clicked\"\nmsgstr \"натиснутий\"\n\n#: blocks.c\nmsgid \"Loop\"\nmsgstr \"Цикл\"\n\n#: blocks.c\nmsgid \"Repeat\"\nmsgstr \"Повторити\"\n\n#: blocks.c\nmsgid \"times\"\nmsgstr \"разів\"\n\n#: blocks.c\nmsgid \"While\"\nmsgstr \"Поки\"\n\n#: blocks.c\nmsgid \"If\"\nmsgstr \"Якщо\"\n\n#: blocks.c\nmsgid \", then\"\nmsgstr \", тоді\"\n\n#: blocks.c\nmsgid \"Else if\"\nmsgstr \"Інакше якщо\"\n\n#: blocks.c\nmsgid \"Else\"\nmsgstr \"Інакше\"\n\n#: blocks.c\nmsgid \"Do nothing\"\nmsgstr \"Нічого не робити\"\n\n#: blocks.c\nmsgid \"Sleep\"\nmsgstr \"Чекати\"\n\n#: blocks.c\nmsgid \"μs\"\nmsgstr \"мкс\"\n\n#: blocks.c\nmsgid \"Define\"\nmsgstr \"Визначити\"\n\n#: blocks.c\nmsgid \"Return\"\nmsgstr \"Повернути\"\n\n#: blocks.c\nmsgid \"Get input\"\nmsgstr \"Отримати ввід\"\n\n#: blocks.c\nmsgid \"Get char\"\nmsgstr \"Отримати символ\"\n\n#: blocks.c\nmsgid \"Print\"\nmsgstr \"Вивести\"\n\n#: blocks.c\nmsgid \"Print line\"\nmsgstr \"Вивести лінію\"\n\n#: blocks.c\nmsgid \"Cursor X\"\nmsgstr \"X курсора\"\n\n#: blocks.c\nmsgid \"Cursor Y\"\nmsgstr \"Y курсора\"\n\n#: blocks.c\nmsgid \"Terminal width\"\nmsgstr \"Ширина терміналу\"\n\n#: blocks.c\nmsgid \"Terminal height\"\nmsgstr \"Висота терміналу\"\n\n#: blocks.c\nmsgid \"Set cursor X:\"\nmsgstr \"Задати позицію курсора X:\"\n\n#: blocks.c\nmsgid \"Y:\"\nmsgstr \"Y:\"\n\n#: blocks.c\nmsgid \"Set text color\"\nmsgstr \"Задати колір тексту\"\n\n#: blocks.c\nmsgid \"Set background color\"\nmsgstr \"Задати колір фону\"\n\n#: blocks.c\nmsgid \"Reset color\"\nmsgstr \"Скинути колір\"\n\n#: blocks.c\nmsgid \"Clear terminal\"\nmsgstr \"Очистити термінал\"\n\n#: blocks.c\nmsgid \"Set clear color\"\nmsgstr \"Задати колір очистки\"\n\n#: blocks.c\nmsgid \"Pow\"\nmsgstr \"Степінь\"\n\n#: blocks.c\nmsgid \"Not\"\nmsgstr \"Не\"\n\n#: blocks.c\nmsgid \"and\"\nmsgstr \"і\"\n\n#: blocks.c\nmsgid \"or\"\nmsgstr \"або\"\n\n#: blocks.c\nmsgid \"True\"\nmsgstr \"Істина\"\n\n#: blocks.c\nmsgid \"False\"\nmsgstr \"Хибний\"\n\n#: blocks.c\nmsgid \"Random\"\nmsgstr \"Випадкове ціле від\"\n\n#: blocks.c\nmsgid \"to\"\nmsgstr \"до\"\n\n#: blocks.c\nmsgid \"Join\"\nmsgstr \"З'єднати\"\n\n#: blocks.c\nmsgid \"left and \"\nmsgstr \"ліве і \"\n\n#: blocks.c\nmsgid \"right\"\nmsgstr \"праве\"\n\n#: blocks.c\nmsgid \"Ord\"\nmsgstr \"Ord\"\n\n#: blocks.c\nmsgid \"Chr\"\nmsgstr \"Chr\"\n\n#: blocks.c\nmsgid \"Letter\"\nmsgstr \"Буква\"\n\n#: blocks.c\nmsgid \"in\"\nmsgstr \"у\"\n\n#: blocks.c\nmsgid \"Substring\"\nmsgstr \"Підрядок\"\n\n#: blocks.c\nmsgid \"Length\"\nmsgstr \"Довжина\"\n\n#: blocks.c\nmsgid \"string\"\nmsgstr \"рядок\"\n\n#: blocks.c\nmsgid \"Time since 1970\"\nmsgstr \"Час після 1970\"\n\n#: blocks.c\nmsgid \"Int\"\nmsgstr \"Ціле\"\n\n#: blocks.c\nmsgid \"Float\"\nmsgstr \"Речове\"\n\n#: blocks.c\nmsgid \"Str\"\nmsgstr \"Рядкове\"\n\n#: blocks.c\nmsgid \"Bool\"\nmsgstr \"Булеве\"\n\n#: blocks.c\nmsgid \"Nothing\"\nmsgstr \"Нічого\"\n\n#: blocks.c\nmsgid \"Declare\"\nmsgstr \"Оголосити\"\n\n#: blocks.c\nmsgid \"my variable\"\nmsgstr \"моя змінна\"\n\n#: blocks.c\nmsgid \"Get\"\nmsgstr \"Отримати\"\n\n#: blocks.c\nmsgid \"Set\"\nmsgstr \"Надати\"\n\n#: blocks.c\nmsgid \"Empty list\"\nmsgstr \"Порожній список\"\n\n#: blocks.c\nmsgid \"Add\"\nmsgstr \"Додати\"\n\n#: blocks.c\nmsgid \"value\"\nmsgstr \"значення\"\n\n#: blocks.c\nmsgid \"get at\"\nmsgstr \"отримати по індексу\"\n\n#: blocks.c\nmsgid \"set at\"\nmsgstr \"задати по індексу\"\n\n#: blocks.c\nmsgid \"My block\"\nmsgstr \"Мій блок\"\n\n#: render.c\nmsgid \"Scrap\"\nmsgstr \"Scrap\"\n\n#: input.c\nmsgid \"Code\"\nmsgstr \"Код\"\n\n#: input.c\nmsgid \"Output\"\nmsgstr \"Вивід\"\n\n#: input.c\nmsgid \"Block categories\"\nmsgstr \"Категорії блоків\"\n\n#: input.c\nmsgid \"Block palette\"\nmsgstr \"Палітра блоків\"\n\n#: render.c\nmsgid \"File\"\nmsgstr \"Файл\"\n\n#: render.c\nmsgid \"Settings\"\nmsgstr \"Налаштування\"\n\n#: render.c\nmsgid \"About\"\nmsgstr \"Про програму\"\n\n#: scrap.c\nmsgid \"Vm executed successfully\"\nmsgstr \"Код виконаний успішно\"\n\n#: scrap.c\nmsgid \"Vm stopped >:(\"\nmsgstr \"Код зупинений >:(\"\n\n#: scrap.c\nmsgid \"Vm shitted and died :(\"\nmsgstr \"Код не витримав навантаження та помер :(\"\n\n#: input.c\nmsgid \"File load failed :(\"\nmsgstr \"Завантаження файлу не вдалось :(\"\n\n#: input.c\nmsgid \"File load succeeded!\"\nmsgstr \"Завантаження файлу вдалось!\"\n\n#: input.c\nmsgid \"Jump to chain (%d/%d)\"\nmsgstr \"Перехід на ланцюжок (%d/%d)\"\n\n#: render.c\nmsgid \"Start failed!\"\nmsgstr \"Запуск коду провалений!\"\n\n#: render.c\nmsgid \"Started successfully!\"\nmsgstr \"Код успішно запущений!\"\n\n#: render.c\nmsgid \"New project\"\nmsgstr \"Новий проект\"\n\n#: render.c\nmsgid \"Save project\"\nmsgstr \"Зберегти проект\"\n\n#: render.c\nmsgid \"Load project\"\nmsgstr \"Завантажити проект\"\n\n#: window.c\nmsgid \"UI size\"\nmsgstr \"Розмір інтерфейсу\"\n\n#: window.c\nmsgid \"FPS limit\"\nmsgstr \"Обмеження FPS\"\n\n#: window.c\nmsgid \"Font path\"\nmsgstr \"Шлях шрифта\"\n\n#: window.c\nmsgid \"Bold font path\"\nmsgstr \"Шлях жирного шрифта\"\n\n#: window.c\nmsgid \"Monospaced font path\"\nmsgstr \"Шлях моноширинного шрифта\"\n\n#: window.c\nmsgid \"Panel editor\"\nmsgstr \"Редактор панелей\"\n\n#: window.c\nmsgid \"Open\"\nmsgstr \"Відкрити\"\n\n#: window.c\nmsgid \"Reset panels\"\nmsgstr \"Скидання панелей\"\n\n#: window.c\nmsgid \"Reset\"\nmsgstr \"Скинути\"\n\n#: window.c\nmsgid \"Apply\"\nmsgstr \"Застосувати\"\n\n#: window.c\nmsgid \"Needs restart for changes to take effect\"\nmsgstr \"Потрібне перезавантаження, щоб зміни вступили у силу\"\n\n#: window.c\nmsgid \"Scrap is a project that allows anyone to build\\n\"\n      \"software using simple, block based interface.\"\nmsgstr \"Scrap - це проект, який надає можливість кожному\\n\"\n       \"створювати програми, використовуючи легкий,\\n\"\n       \"блоковий інтерфейс\"\n\n#: window.c\nmsgid \"License\"\nmsgstr \"Ліцензія\"\n\n#: render.c\nmsgid \"Panel edit mode\"\nmsgstr \"Режим редагування панелей\"\n\n#: render.c\nmsgid \"Click on panels to reposition them\"\nmsgstr \"Натискайте на панелі щоб переміщати їх\"\n\n#: render.c\nmsgid \"Drag panel edges to resize them\"\nmsgstr \"Переміщайте края панелей, щоб змінити розмір\"\n\n#: render.c\nmsgid \"Done\"\nmsgstr \"Готово\"\n\n#: render.c\nmsgid \"Save\"\nmsgstr \"Зберегти\"\n\n#: blocks.c\nmsgid \"any\"\nmsgstr \"будь-яке\"\n\n#: blocks.c\nmsgid \"cond.\"\nmsgstr \"умова\"\n\n#: blocks.c\nmsgid \"Abc\"\nmsgstr \"Абв\"\n\n#: window.c\nmsgid \"path\"\nmsgstr \"шлях\"\n\n#: window.c\nmsgid \"Language\"\nmsgstr \"Мова\"\n\n#: save.c\nmsgid \"System\"\nmsgstr \"Системна\"\n\n#: save.c\nmsgid \"English [en]\"\nmsgstr \"Англійська [en]\"\n\n#: save.c\nmsgid \"Russian [ru]\"\nmsgstr \"Російська [ru]\"\n\n#: save.c\nmsgid \"Kazakh [kk]\"\nmsgstr \"Казахська [kk]\"\n\n#: save.c\nmsgid \"Ukrainian [uk]\"\nmsgstr \"Українська [uk]\"\n"
  }
]