Repository: Grisshink/scrap Branch: main Commit: e63a9395f400 Files: 67 Total size: 1.0 MB Directory structure: gitextract_3r4214vd/ ├── .github/ │ ├── generate_notes.py │ └── workflows/ │ ├── build.yml │ └── release.yml ├── .gitignore ├── .gitmodules ├── CHANGELOG.md ├── LICENSE ├── Makefile ├── README.md ├── data/ │ ├── nk57-cond.otf │ ├── nk57-eb.otf │ └── nk57.otf ├── default.nix ├── examples/ │ ├── 3d.scrp │ ├── actual_3d.scrp │ ├── ball.scrp │ ├── circle.scrp │ ├── clock.scrp │ ├── colors.scrp │ ├── fibonacci.scrp │ ├── guess.scrp │ ├── hello_world.scrp │ ├── neon.scrp │ └── trig.scrp ├── external/ │ ├── cfgpath.c │ ├── cfgpath.h │ ├── nanosvg.h │ ├── nanosvgrast.h │ ├── rprand.h │ ├── tinyfiledialogs.c │ └── tinyfiledialogs.h ├── scrap.desktop ├── scrap.rc ├── src/ │ ├── ast.c │ ├── ast.h │ ├── blocks.c │ ├── compiler.c │ ├── compiler.h │ ├── config.h │ ├── gc.c │ ├── gc.h │ ├── interpreter.c │ ├── interpreter.h │ ├── platform.c │ ├── render.c │ ├── save.c │ ├── scrap-runtime.c │ ├── scrap.c │ ├── scrap.h │ ├── scrap_gui.c │ ├── scrap_gui.h │ ├── std.c │ ├── std.h │ ├── term.c │ ├── term.h │ ├── thread.c │ ├── thread.h │ ├── ui.c │ ├── util.c │ ├── util.h │ ├── vec.c │ ├── vec.h │ ├── vm.c │ └── window.c └── translations/ ├── kk/ │ └── LC_MESSAGES/ │ └── scrap.po ├── ru/ │ └── LC_MESSAGES/ │ └── scrap.po └── uk/ └── LC_MESSAGES/ └── scrap.po ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/generate_notes.py ================================================ import re with open('CHANGELOG.md') as f: changelog = f.read() start = re.search('## ', changelog).start() end = re.search('^# ', changelog[start:], flags=re.MULTILINE).start() + start print(""" > [!WARNING] > Scrap now pushes new experimental LLVM builds tagged with `-llvm`. Use these with caution! """) print(changelog[start:end].strip()) ================================================ FILE: .github/workflows/build.yml ================================================ name: Build on: push: branches: - "main" - "dev_**" paths-ignore: - "README.md" - "LICENSE" - "CHANGELOG.md" pull_request: branches: - "main" - "dev_**" paths-ignore: - "README.md" - "LICENSE" - "CHANGELOG.md" jobs: build-windows: name: Windows build runs-on: windows-latest defaults: run: shell: msys2 {0} steps: - name: Checkout code uses: actions/checkout@v6 with: submodules: recursive - name: Install dependencies uses: msys2/setup-msys2@v2 with: msystem: UCRT64 update: true install: mingw-w64-ucrt-x86_64-gcc mingw-w64-ucrt-x86_64-llvm make gettext - name: Setup windres run: ln -sf "${MSYSTEM_PREFIX}/bin/windres.exe" "${MSYSTEM_PREFIX}/bin/x86_64-w64-mingw32-windres" - name: Build project (With compiler) run: | make clean make USE_COMPILER=TRUE TARGET=WINDOWS - name: Build project (With interpreter) run: | make clean make TARGET=WINDOWS build-linux: name: Linux build runs-on: ubuntu-22.04 steps: - name: Checkout code uses: actions/checkout@v6 with: submodules: recursive - name: Install dependencies run: | sudo apt-get update -y sudo apt-get install -y --no-install-recommends libxcursor-dev libxrandr-dev libxinerama-dev libxi-dev gettext - name: Install LLVM run: | wget -qO- https://apt.llvm.org/llvm-snapshot.gpg.key | gpg --dearmor -o llvm-snapshot.gpg sudo mv llvm-snapshot.gpg /etc/apt/trusted.gpg.d/ sudo add-apt-repository "deb http://apt.llvm.org/focal/ llvm-toolchain-focal-19 main" sudo apt-get update -y sudo apt-get install -y --no-install-recommends llvm-19-dev - name: Build project (With compiler) run: | make clean make USE_COMPILER=TRUE LLVM_CONFIG=llvm-config-19 - name: Build project (With interpreter) run: | make clean make ================================================ FILE: .github/workflows/release.yml ================================================ name: Release on: push: tags: - 'v*.*-*' permissions: contents: write env: SCRAP_VERSION: ${{ github.ref_name }} jobs: build-linux: name: Build Linux release runs-on: ubuntu-22.04 steps: - name: Checkout code uses: actions/checkout@v6 with: submodules: recursive - name: Install dependencies run: | sudo apt-get update -y sudo apt-get upgrade -y sudo apt-get install -y libxcursor-dev libxrandr-dev libxinerama-dev libxi-dev gettext zlib1g-dev libzstd-dev libxml2-dev - name: Install LLVM run: | wget -qO- https://apt.llvm.org/llvm-snapshot.gpg.key | gpg --dearmor -o llvm-snapshot.gpg sudo mv llvm-snapshot.gpg /etc/apt/trusted.gpg.d/ sudo add-apt-repository "deb http://apt.llvm.org/focal/ llvm-toolchain-focal-19 main" sudo apt-get update -y sudo apt-get install -y --no-install-recommends llvm-19-dev - name: Build Scrap compiler run: | make clean make USE_COMPILER=TRUE LLVM_CONFIG=llvm-config-19 LLVM_LINK_STATIC=TRUE mkdir -p build/scrap-${SCRAP_VERSION}-llvm-linux cp -r data examples extras locale build/scrap-${SCRAP_VERSION}-llvm-linux/ cp LICENSE README.md CHANGELOG.md scrap libscrapstd.a build/scrap-${SCRAP_VERSION}-llvm-linux/ tar --directory=build -czf build/scrap-${SCRAP_VERSION}-llvm-linux.tar.gz scrap-${SCRAP_VERSION}-llvm-linux mkdir -p dist mv build/*.gz dist/ - name: Build Scrap interpreter run: | make clean make mkdir -p build/scrap-${SCRAP_VERSION}-linux cp -r data examples extras locale build/scrap-${SCRAP_VERSION}-linux/ cp LICENSE README.md CHANGELOG.md scrap libscrapstd.a build/scrap-${SCRAP_VERSION}-linux/ tar --directory=build -czf build/scrap-${SCRAP_VERSION}-linux.tar.gz scrap-${SCRAP_VERSION}-linux mv build/*.gz dist/ - name: Upload Linux artifacts uses: actions/upload-artifact@v6 with: name: scrap-${{ env.SCRAP_VERSION }}-linux path: dist/* retention-days: 10 build-appimage: name: Build Appimage release runs-on: ubuntu-22.04 steps: - name: Checkout code uses: actions/checkout@v6 with: submodules: recursive - name: Install dependencies run: | sudo apt-get update -y sudo apt-get upgrade -y sudo apt-get install -y libxcursor-dev libxrandr-dev libxinerama-dev libxi-dev gettext zlib1g-dev libzstd-dev libxml2-dev - name: Install LLVM run: | wget -qO- https://apt.llvm.org/llvm-snapshot.gpg.key | gpg --dearmor -o llvm-snapshot.gpg sudo mv llvm-snapshot.gpg /etc/apt/trusted.gpg.d/ sudo add-apt-repository "deb http://apt.llvm.org/focal/ llvm-toolchain-focal-19 main" sudo apt-get update -y sudo apt-get install -y --no-install-recommends llvm-19-dev - name: Install AppImageTool run: | wget -O appimagetool https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage chmod +x appimagetool sudo mv appimagetool /usr/bin/appimagetool - name: Build Scrap compiler run: | make clean make USE_COMPILER=TRUE LLVM_CONFIG=llvm-config-19 LLVM_LINK_STATIC=TRUE mkdir -p build/scrap.AppDir cp scrap build/scrap.AppDir/AppRun cp -r data locale build/scrap.AppDir/ cp scrap.desktop libscrapstd.a extras/scrap.png build/scrap.AppDir/ mkdir -p dist appimagetool --appimage-extract-and-run build/scrap.AppDir build/Scrap-${SCRAP_VERSION}-llvm.AppImage mv build/Scrap-${SCRAP_VERSION}-llvm.AppImage dist/ - name: Build Scrap interpreter run: | make clean make mkdir -p build/scrap.AppDir cp scrap build/scrap.AppDir/AppRun cp -r data locale build/scrap.AppDir/ cp scrap.desktop libscrapstd.a extras/scrap.png build/scrap.AppDir/ appimagetool --appimage-extract-and-run build/scrap.AppDir build/Scrap-${SCRAP_VERSION}.AppImage mv build/Scrap-${SCRAP_VERSION}.AppImage dist/ - name: Upload AppImage artifacts uses: actions/upload-artifact@v6 with: name: scrap-${{ env.SCRAP_VERSION }}-appimage path: dist/* retention-days: 10 build-windows: name: Build Windows release runs-on: windows-latest defaults: run: shell: msys2 {0} steps: - name: Checkout code uses: actions/checkout@v6 with: submodules: recursive - name: Install dependencies uses: msys2/setup-msys2@v2 with: msystem: UCRT64 update: true install: mingw-w64-ucrt-x86_64-gcc mingw-w64-ucrt-x86_64-llvm make gettext - name: Setup windres run: ln -sf "${MSYSTEM_PREFIX}/bin/windres.exe" "${MSYSTEM_PREFIX}/bin/x86_64-w64-mingw32-windres" - name: Build Scrap compiler run: | make clean make USE_COMPILER=TRUE TARGET=WINDOWS mkdir -p build/scrap-${SCRAP_VERSION}-llvm-windows64 cp -r locale data examples extras build/scrap-${SCRAP_VERSION}-llvm-windows64/ cp CHANGELOG.md LICENSE README.md libscrapstd-win.a scrap.exe build/scrap-${SCRAP_VERSION}-llvm-windows64/ powershell.exe -Command "Compress-Archive -Path build/scrap-${SCRAP_VERSION}-llvm-windows64 -DestinationPath build/scrap-${SCRAP_VERSION}-llvm-windows64.zip -Force" mkdir -p dist mv build/*.zip dist/ - name: Build Scrap interpreter run: | make clean make TARGET=WINDOWS mkdir -p build/scrap-${SCRAP_VERSION}-windows64 cp -r locale data examples extras build/scrap-${SCRAP_VERSION}-windows64/ cp CHANGELOG.md LICENSE README.md libscrapstd-win.a scrap.exe build/scrap-${SCRAP_VERSION}-windows64/ powershell.exe -Command "Compress-Archive -Path build/scrap-${SCRAP_VERSION}-windows64 -DestinationPath build/scrap-${SCRAP_VERSION}-windows64.zip -Force" mv build/*.zip dist/ - name: Upload Windows artifacts uses: actions/upload-artifact@v6 with: name: scrap-${{ env.SCRAP_VERSION }}-windows64 path: dist/* retention-days: 10 release: needs: [build-linux, build-appimage, build-windows] runs-on: ubuntu-22.04 steps: - name: Checkout uses: actions/checkout@v6 - name: Download build artifacts uses: actions/download-artifact@v7 with: path: dist pattern: 'scrap-*' merge-multiple: true - name: Generate release notes run: python "$GITHUB_WORKSPACE/.github/generate_notes.py" > release_notes.md - name: Create a draft release env: GITHUB_TOKEN: ${{ github.token }} run: | gh release create "$SCRAP_VERSION" --draft --title "$SCRAP_VERSION" --notes-file release_notes.md gh release upload "$SCRAP_VERSION" "$GITHUB_WORKSPACE/dist/*.AppImage" gh release upload "$SCRAP_VERSION" "$GITHUB_WORKSPACE/dist/*.tar.gz" gh release upload "$SCRAP_VERSION" "$GITHUB_WORKSPACE/dist/*.zip" ================================================ FILE: .gitignore ================================================ tags *.o scrap scrap.exe profile.json coredump config.txt scrap.res private/ locale/ scrap.AppDir/ build/ a.out libscrapstd.a libscrapstd-win.a ================================================ FILE: .gitmodules ================================================ [submodule "raylib"] path = raylib url = https://github.com/raysan5/raylib.git ================================================ FILE: CHANGELOG.md ================================================ # v0.6.1-beta *(27-02-2026)* ## What's new? - 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 ## Fixes - Fixed buffer overflow when rendering glyphs that are not present in font (This also fixes text in Kazakh language being absolutely broken) - Fixed crash when saving/loading blocks with color arguments - Fixed memory leak when converting legacy color arguments # v0.6-beta *(31-01-2026)* ## What's new? - Scrap now shows confirmation window when trying to close scrap with unsaved changes - Blocks inside block pallete are now split into subcategories - Added new color type and integrated it with color related blocks - Added new example (neon.scrp) featuring new color system - Added ability to change colors of custom blocks - Added browse button for path text inputs - Added various icons to buttons and windows - Added multiple block pickup system. You can now pickup and drop multiple block chains using `shift` button - Added a settings option to toggle block previews - UI size and Font settings can now be applied without reloading the app ## Fixes - Fixed block definitions being able to be edited in block palette - Fixed pow block not working with float values in compiler - Dropdowns now close when dragging code area with middle mouse button. This prevents a bunch of crashes related to dropdowns # v0.5.1-beta *(27-12-2025)* ## What's new? - Updated Raylib version to latest commit (fc843dc557 as of time writing) - Added visual feedback when settings are applied - Text inputs in blocks are now rendered as slightly tinted to block color ## Fixes - Fixed search menu spawning incomplete control blocks - Fixed text inputs being editable in block palette - Projects built with LLVM now switch to UTF-8 code page on windows. This fixes garbled output when printing characters outside ASCII range # v0.5-beta *(20-12-2025)* ## What's new? - Added new experimental LLVM compiler backend. It is available as a separate download option in the releases (tagged with `-llvm`) - (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) - 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 - Added a lot of runtime/compile time errors, such as out of scope variable access, mismatched types and etc. - Shortened list and variable blocks to make code a bit more concise - Implemented text selection to all text inputs, as well as more useful shortcuts such as Shift selection (Shift-), copy (Ctrl-C), paste (Ctrl-V), cut (Ctrl-X), delete all (Ctrl-U) and select all (Ctrl-A) - Terminal contents are now cropped when the terminal is resized - Added block previews when trying to attach blockchains together - Save files are no longer limited to 32 KB ## Fixes - Fixed numpad Enter key not working in certain scenarios - Fixed race condition with thread running state after calling `pthread_create`. This should theoretically fix code not stopping its execution on Windows - Fixed code area being able to scroll while window is open - Fixed memory leak when copying blockdef with instantiated blocks - Fixed block search menu being able to open when code is running - Control blocks (like while, if) now render correctly in the block palette - Fixed code area being able to be dragged outside the code panel when code is running # v0.4.2-beta *(12-02-2025)* ## Fixes - Fixed loaded custom blocks having undefined hints - Fixed textboxed being interactable while vm is running - Fixed code area floating away when editing panel while the block is selected - Minor translation fixes # v0.4.1-beta *(07-02-2025)* ## What's new? - Added Ukrainian translation *(by @jackmophin)* ## Fixes - Fixed localizations sometimes not working on Windows - Fixed codebase scrolling with search menu at the same time # v0.4-beta *(05-02-2025)* ## What's new? - Added translations for 2 languages: Russian *(by @Grisshink)* and Kazakh *(by @unknownkeyNB)* - The sidebar (Now named as block palette to remove ambiguity) is now split up into various categories to make finding blocks easier - The terminal's background color has been changed to match with the color of other panels - All of the text boxes were upgraded to allow inserting or deleting at any position - Now if any block input is empty, it will show a small hint of what it needs - Added codebase movement through keyboard keys, see `README.md` for more details - Added block search menu. You can open it by pressing `S` in code area ## Fixes - Fixed "crash" when vm overflows/underflows one its stacks - Fixed crash when trying to divide by zero in code - Fixed scrollbars sometimes appearing behind scroll container - Fixed text in the settings going out of bounds when the text is too large - Fixed codespace occasionally jumping far away when it is being dragged outside of the window - Fixed code renderer not checking with proper culling bounds. This should slightly improve performance of the renderer # v0.3.1-beta *(28-01-2025)* ## Fixes - 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 - Fixed scroll containers not updating their lower bounds when resized # v0.3-beta *(26-01-2025)* ## What's new? - 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 - Added `New project` button to quickly clear the workspace - The code area has been split up into flexible, customizable panels. This gives a lot more choice for UI customization - Now scrap only redraws the screen if its internal state is changed or an input is recieved - Updated `actual_3d.scrp` example - Now scrap config is saved in OS specific folder, instead of saving relative to the working directory - Added terminal colors support - Added `colors.scrp` example to demonstrate the use of colors # v0.2-beta *(06-01-2025)* ## What's new? - Added various string manipulation blocks - Added shortcuts to run/stop project (they are bound to `F5` and `F6` buttons respectively) - Added codebase movement through scroll wheel/touchpad - Added another 3D example ## Fixes - Fixed save corruption when saving unused custom blocks - Fixed custom blocks breaking when deleting arguments - Fixed some compilation issues with gcc-9 - Fixed AppImage paths issue again - Fixed block highlighting with custom blocks # v0.1.1-beta *(30-12-2024)* ## What's new? - Added icon to the executable - Added experimental MacOS release provided by @arducat ## Fixes - Fixed AppImage paths issue - Fixed some resizing issues on linux # v0.1-beta *(29-12-2024)* - First beta release! ================================================ FILE: LICENSE ================================================ zlib License Copyright (C) 2024-2026 Grisshink This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. ================================================ FILE: Makefile ================================================ SCRAP_VERSION ?= dev MAKE ?= make TARGET ?= LINUX BUILD_MODE ?= RELEASE USE_COMPILER ?= FALSE BUILD_FOLDER := build/ PREFIX ?= /usr/local ifeq ($(USE_COMPILER), TRUE) SCRAP_VERSION := $(SCRAP_VERSION)-llvm endif CFLAGS := -Wall -Wextra -std=c11 -D_GNU_SOURCE -DSCRAP_VERSION=\"$(SCRAP_VERSION)\" -I./raylib/src ifeq ($(TARGET), LINUX) CC := gcc LDFLAGS := -lm -lpthread -lX11 -ldl else ifeq ($(TARGET), OSX) # Thanks to @arducat for MacOS support CC := clang LDFLAGS := -framework CoreVideo -framework IOKit -framework Cocoa -framework GLUT -framework OpenGL -lm -lpthread -lintl else CC := x86_64-w64-mingw32-gcc LDFLAGS := -static -lole32 -lcomdlg32 -lwinmm -lgdi32 -lintl -liconv -lshlwapi -Wl,--subsystem,windows endif ifeq ($(ARABIC_MODE), TRUE) CFLAGS += -DARABIC_MODE endif ifeq ($(RAM_OVERLOAD), TRUE) CFLAGS += -DRAM_OVERLOAD endif ifeq ($(CC), clang) CFLAGS += -ferror-limit=5 else CFLAGS += -fmax-errors=5 endif ifeq ($(BUILD_MODE), RELEASE) CFLAGS += -s -O3 else CFLAGS += -g -O1 -DDEBUG LDFLAGS += -fsanitize=address -fsanitize=undefined -fno-omit-frame-pointer endif STD_CFLAGS := $(CFLAGS) -fPIC ifeq ($(BUILD_MODE), DEBUG) CFLAGS += -fsanitize=address -fsanitize=undefined -fno-omit-frame-pointer endif STD_OBJFILES := $(addprefix $(BUILD_FOLDER),vec-stand.o gc-stand.o std-stand.o scrap-runtime.o) OBJFILES := $(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) BUNDLE_FILES := data examples extras locale LICENSE README.md CHANGELOG.md SCRAP_HEADERS := src/scrap.h src/ast.h src/config.h src/scrap_gui.h EXE_NAME := scrap ifeq ($(TARGET), WINDOWS) STD_NAME := libscrapstd-win.a else STD_NAME := libscrapstd.a endif ifeq ($(USE_COMPILER), FALSE) OBJFILES += $(addprefix $(BUILD_FOLDER),interpreter.o) SCRAP_HEADERS += src/interpreter.h CFLAGS += -DUSE_INTERPRETER else LLVM_CONFIG ?= llvm-config OBJFILES += $(addprefix $(BUILD_FOLDER),compiler.o) SCRAP_HEADERS += src/compiler.h LLVM_LDFLAGS := --ldflags --system-libs --libs core executionengine mcjit analysis native ifeq ($(TARGET), WINDOWS) LDFLAGS += `$(LLVM_CONFIG) $(LLVM_FLAGS) --link-static $(LLVM_LDFLAGS) | sed 's/\.dll//'` else ifeq ($(LLVM_LINK_STATIC), TRUE) LDFLAGS += -Wl,-Bstatic `$(LLVM_CONFIG) $(LLVM_FLAGS) --link-static $(LLVM_LDFLAGS)` -Wl,-Bdynamic else LDFLAGS += `$(LLVM_CONFIG) $(LLVM_FLAGS) $(LLVM_LDFLAGS)` endif endif CFLAGS += `$(LLVM_CONFIG) --cflags` LDFLAGS += -lstdc++ endif .PHONY: all clean target translations all: target std translations mkbuild: mkdir -p $(BUILD_FOLDER) clean: $(MAKE) -C raylib/src clean rm -f scrap.res $(EXE_NAME) $(EXE_NAME).exe libscrapstd.a libscrapstd-win.a rm -rf locale $(BUILD_FOLDER) translations: @echo === Generating locales... === rm -rf locale cp -r translations locale msgfmt -o locale/ru/LC_MESSAGES/scrap.mo locale/ru/LC_MESSAGES/scrap.po rm locale/ru/LC_MESSAGES/scrap.po msgfmt -o locale/kk/LC_MESSAGES/scrap.mo locale/kk/LC_MESSAGES/scrap.po rm locale/kk/LC_MESSAGES/scrap.po msgfmt -o locale/uk/LC_MESSAGES/scrap.mo locale/uk/LC_MESSAGES/scrap.po rm locale/uk/LC_MESSAGES/scrap.po install: translations target std mkdir -p $(PREFIX)/share/scrap mkdir -p $(PREFIX)/share/doc/scrap mkdir -p $(PREFIX)/bin mkdir -p $(PREFIX)/lib cp -r data $(PREFIX)/share/scrap cp -r locale $(PREFIX)/share cp -r examples $(PREFIX)/share/doc/scrap cp $(EXE_NAME) $(PREFIX)/bin cp $(STD_NAME) $(PREFIX)/lib uninstall: rm -rf $(PREFIX)/share/scrap rm -rf $(PREFIX)/share/doc/scrap rm -f $(PREFIX)/bin/$(EXE_NAME) rm -f $(PREFIX)/lib/$(STD_NAME) ifeq ($(TARGET), WINDOWS) target: mkbuild $(EXE_NAME).exe else target: mkbuild $(EXE_NAME) endif $(EXE_NAME).exe: $(OBJFILES) $(MAKE) -C raylib/src CC=$(CC) PLATFORM_OS=$(TARGET) x86_64-w64-mingw32-windres scrap.rc -O coff -o scrap.res $(CC) -o $@ $^ raylib/src/libraylib.a scrap.res $(LDFLAGS) $(EXE_NAME): $(OBJFILES) $(MAKE) -C raylib/src CC=$(CC) PLATFORM_OS=$(TARGET) $(CC) -o $@ $^ raylib/src/libraylib.a $(LDFLAGS) std: mkbuild $(STD_OBJFILES) ar rcs $(STD_NAME) $(STD_OBJFILES) $(BUILD_FOLDER)scrap.o: src/scrap.c $(SCRAP_HEADERS) $(CC) $(CFLAGS) -c -o $@ $< $(BUILD_FOLDER)window.o: src/window.c $(SCRAP_HEADERS) external/tinyfiledialogs.h $(CC) $(CFLAGS) -c -o $@ $< $(BUILD_FOLDER)scrap_gui.o: src/scrap_gui.c src/scrap_gui.h $(CC) $(CFLAGS) -c -o $@ $< $(BUILD_FOLDER)render.o: src/render.c $(SCRAP_HEADERS) $(CC) $(CFLAGS) -c -o $@ $< $(BUILD_FOLDER)save.o: src/save.c $(SCRAP_HEADERS) external/cfgpath.h $(CC) $(CFLAGS) -c -o $@ $< $(BUILD_FOLDER)term.o: src/term.c src/term.h $(SCRAP_HEADERS) $(CC) $(CFLAGS) -c -o $@ $< $(BUILD_FOLDER)blocks.o: src/blocks.c $(SCRAP_HEADERS) $(CC) $(CFLAGS) -c -o $@ $< $(BUILD_FOLDER)vec.o: src/vec.c $(CC) $(CFLAGS) -c -o $@ $< $(BUILD_FOLDER)util.o: src/util.c $(SCRAP_HEADERS) $(CC) $(CFLAGS) -c -o $@ $< $(BUILD_FOLDER)ui.o: src/ui.c $(SCRAP_HEADERS) $(CC) $(CFLAGS) -c -o $@ $< $(BUILD_FOLDER)platform.o: src/platform.c $(CC) $(CFLAGS) -c -o $@ $< $(BUILD_FOLDER)ast.o: src/ast.c src/ast.h $(CC) $(CFLAGS) -c -o $@ $< $(BUILD_FOLDER)interpreter.o: src/interpreter.c $(SCRAP_HEADERS) $(CC) $(CFLAGS) -c -o $@ $< $(BUILD_FOLDER)compiler.o: src/compiler.c src/compiler.h src/gc.h src/ast.h $(SCRAP_HEADERS) $(CC) $(CFLAGS) -c -o $@ $< $(BUILD_FOLDER)gc.o: src/gc.c src/gc.h src/vec.h src/std.h $(CC) $(CFLAGS) -c -o $@ $< $(BUILD_FOLDER)std.o: src/std.c src/std.h src/gc.h src/term.h $(CC) $(CFLAGS) -c -o $@ $< $(BUILD_FOLDER)thread.o: src/thread.c src/thread.h $(CC) $(CFLAGS) -c -o $@ $< $(BUILD_FOLDER)vm.o: src/vm.c $(SCRAP_HEADERS) $(CC) $(CFLAGS) -c -o $@ $< $(BUILD_FOLDER)gc-stand.o: src/gc.c src/gc.h src/vec.h src/std.h $(CC) $(STD_CFLAGS) -DSTANDALONE_STD -c -o $@ $< $(BUILD_FOLDER)std-stand.o: src/std.c src/std.h src/gc.h $(CC) $(STD_CFLAGS) -DSTANDALONE_STD -c -o $@ $< $(BUILD_FOLDER)scrap-runtime.o: src/scrap-runtime.c src/gc.h $(CC) $(STD_CFLAGS) -DSTANDALONE_STD -c -o $@ $< $(BUILD_FOLDER)vec-stand.o: src/vec.c $(CC) $(STD_CFLAGS) -DSTANDALONE_STD -c -o $@ $< $(BUILD_FOLDER)filedialogs.o: external/tinyfiledialogs.c $(CC) $(CFLAGS) -c -o $@ $< $(BUILD_FOLDER)cfgpath.o: external/cfgpath.c $(CC) $(CFLAGS) -c -o $@ $< ================================================ FILE: README.md ================================================ ![Scrap splash](/extras/scrap_splash_v2.png) # Scrap ![CI build](https://img.shields.io/github/actions/workflow/status/Grisshink/scrap/build.yml) ![Version](https://img.shields.io/github/v/release/Grisshink/scrap) ![Downloads](https://img.shields.io/github/downloads/Grisshink/scrap/total) ![License](https://img.shields.io/github/license/Grisshink/scrap) Scrap is a new block based programming language with the aim towards advanced users. It is written in pure C and mostly inspired by other block based languages such as [Scratch](https://scratch.mit.edu/) and its forks such as [Turbowarp](https://turbowarp.org). > [!WARNING] > Scrap is currently in **Beta** stage. Some features may be missing or break, so use with caution! ## Notable advantages from scratch - *(LLVM only)* Faster runtime. The speed is achieved using compilation through LLVM - 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!) - Variables can have a lifetime, which avoids variable name conflicts and allows to make temporary variables - Custom blocks can return values and can be used as an argument for other block - Various string manipulation blocks and bitwise operator blocks - Data type conversion functions - More strict checks for [[] = []] and [[] != []] blocks. Now they are case sensitive and will check data type for equality - Lists are now a data type instead of a different type of variable, this allows nesting lists inside a list - The code runs in a separate thread. This solves some performance issues compared to Scratch - Modularized interface. Most of the interface can be rearranged or moved to another tab - *(LLVM only)* Standalone builds. Scrap projects can be built and run as native executables without significant runtime overhead ## Controls - Click on blocks to pick up them, click again to drop them - Hold `Ctrl` to take single block, `Alt` to duplicate blocks and `Shift` to pickup/drop multiple block chains - Hold left mouse button to move around code space - Holding middle mouse button will do the same, except it works everywhere - Press `Tab` to jump to chain in code base (Useful if you got lost in code base) - Press `F5` to run the project. Press `F6` to stop it. - Press arrow keys while the block is highlighted to move the block cursor around - Press `Enter` to enter the highlighted text box and `Esc` to leave that text box - Press `S` to open block search menu ## Binary Installation ### Github releases See [Releases](https://github.com/Grisshink/scrap/releases) page for all available download options for Windows, Linux as well as their respective LLVM builds ### AUR Scrap is now available for download from Arch User Repository (AUR) as [scrap-git](https://aur.archlinux.org/packages/scrap-git) package. This package will download and build latest Scrap commit from git (LLVM PKGBUILDs are coming soon) To install Scrap from AUR you can use your preferred AUR helper, for example with `yay`: ```bash yay -S scrap-git ``` ## Building ### Dependencies Scrap requires these dependencies to run: - [gettext](https://www.gnu.org/software/gettext/) - [LLVM](https://llvm.org/) *(Only required if building with `USE_COMPILER=TRUE` flag)* Currently Scrap can be built for *Windows*, *Linux*, *MacOS* and *FreeBSD*. #### Download commands for Windows (MSYS2 UCRT64) ```bash pacman -S mingw-w64-ucrt-x86_64-gcc make gettext ln -sf "${MSYSTEM_PREFIX}/bin/windres.exe" "${MSYSTEM_PREFIX}/bin/x86_64-w64-mingw32-windres" ``` #### Download commands for Windows (LLVM) *(Experimental)* If you are going to compile with `USE_COMPILER=TRUE` flag, then you need to install additional dependencies: ```bash pacman -S mingw-w64-ucrt-x86_64-llvm ``` #### Download commands for Debian ```bash sudo apt install libxcursor-dev libxrandr-dev libxinerama-dev libxi-dev gettext ``` #### Download commands for Arch linux Download command for arch-based distributions: ```bash sudo pacman -S libx11 libxrandr libxi libxcursor libxinerama gettext ``` #### Download commands for OpenSUSE Download command for openSUSE: ```bash sudo zypper install libX11-devel libXrandr-devel libXi-devel libXcursor-devel libXinerama-devel gettext ``` ### Build Before building the repo needs to be cloned along with its submodules. To do this, run: ```bash git clone --recursive https://github.com/Grisshink/scrap.git cd scrap ``` #### Windows build NOTE: This guide will assume that you have [MSYS2](https://www.msys2.org/) installed and running on your system. After that, run the following commands: ```bash make -B TARGET=WINDOWS ./scrap.exe ``` NOTE: When running `make clean` MSYS2 will occasionally drop you into command prompt. To fix this, just type `exit` in the cmd and the cleanup process will proceed #### Linux build To build and run Scrap on linux you need to install `gcc` (10+) and `make`. After install, just run following commands: ```bash make -j$(nproc) ./scrap ``` #### FreeBSD build To build and run Scrap on FreeBSD you need to install `gcc` (10+) and `gmake`. After install, just run following commands: ```bash gmake MAKE=gmake -j$(nproc) ./scrap ``` #### NixOS build To build and run Scrap on NixOS, just run the following commands: ```bash nix-shell make -j$(nproc) ./scrap ``` #### MacOS build > [!WARNING] > MacOS build is not being tested right now, so it may not work properly or not at all, you have been warned! To build and run Scrap on macOS, you need to install `gcc` (10+) and `make`. First, install Homebrew: ``` /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" ``` After that, you need to run the following commands: ```bash brew install gettext make -j$(nproc) TARGET=OSX ./scrap ``` Thanks to [@arducat](https://github.com/arducat) for MacOS support. ## Screenshots ![Screenshot1](/extras/scrap_screenshot1.png) ![Screenshot2](/extras/scrap_screenshot2.png) ![Screenshot3](/extras/scrap_screenshot3.png) ## Wait, there is more? In `examples/` folder you can find some example code writen in Scrap that uses most features from Scrap In `extras/` folder you can find some various artwork made for Scrap. The splash art was made by [@FlaffyTheBest](https://scratch.mit.edu/users/FlaffyTheBest/), the logo was made by [@Grisshink](https://github.com/Grisshink) with some inspiration for logo from [@unixource](https://github.com/unixource), the wallpaper was made by [@Grisshink](https://github.com/Grisshink) ## License All scrap code is licensed under the terms of [zlib license](/LICENSE). ================================================ FILE: default.nix ================================================ with import {}; pkgs.mkShell { stdenv = pkgs.clangStdenv; packages = with pkgs; [ glfw cmake clang wayland libx11 ]; nativeBuildInputs = [ pkgs.libGL ]; LD_LIBRARY_PATH = with pkgs; lib.makeLibraryPath [ libGL libxrandr libxinerama libxcursor libxi ]; LIBCLANG_PATH = "${pkgs.libclang.lib}/lib"; } # thanks to https://github.com/deltaphc/raylib-rs/issues/187 ! # TODO: implement buildFHSEnv ================================================ FILE: external/cfgpath.c ================================================ #include "cfgpath.h" #if defined(_MSC_VER) || defined(__MINGW32__) #include #define mkdir _mkdir #endif #ifdef __unix__ #include #endif #if defined(CFGPATH_LINUX) #include #include #include #elif defined(CFGPATH_WINDOWS) #include #elif defined(CFGPATH_MAC) #include #include #endif void get_user_config_file(char *out, unsigned int maxlen, const char *appname) { #ifdef CFGPATH_LINUX const char *out_orig = out; char *home = getenv("XDG_CONFIG_HOME"); unsigned int config_len = 0; if (!home) { home = getenv("HOME"); if (!home) { // Can't find home directory out[0] = 0; return; } config_len = strlen(".config/"); } unsigned int home_len = strlen(home); unsigned int appname_len = strlen(appname); const int ext_len = strlen(".conf"); /* first +1 is "/", second is terminating null */ if (home_len + 1 + config_len + appname_len + ext_len + 1 > maxlen) { out[0] = 0; return; } memcpy(out, home, home_len); out += home_len; *out = '/'; out++; if (config_len) { memcpy(out, ".config/", config_len); out += config_len; /* Make the .config folder if it doesn't already exist */ *out = '\0'; mkdir(out_orig, 0755); } memcpy(out, appname, appname_len); out += appname_len; memcpy(out, ".conf", ext_len); out += ext_len; *out = '\0'; #elif defined(CFGPATH_WINDOWS) if (maxlen < MAX_PATH) { out[0] = 0; return; } if (!SUCCEEDED(SHGetFolderPath(NULL, CSIDL_APPDATA, NULL, 0, out))) { out[0] = 0; return; } /* We don't try to create the AppData folder as it always exists already */ unsigned int appname_len = strlen(appname); if (strlen(out) + 1 + appname_len + strlen(".ini") + 1 > maxlen) { out[0] = 0; return; } strcat(out, "\\"); strcat(out, appname); strcat(out, ".ini"); #elif defined(CFGPATH_MAC) FSRef ref; FSFindFolder(kUserDomain, kApplicationSupportFolderType, kCreateFolder, &ref); char home[MAX_PATH]; FSRefMakePath(&ref, (UInt8 *)&home, MAX_PATH); /* first +1 is "/", second is terminating null */ const char *ext = ".conf"; if (strlen(home) + 1 + strlen(appname) + strlen(ext) + 1 > maxlen) { out[0] = 0; return; } strcpy(out, home); strcat(out, PATH_SEPARATOR_STRING); strcat(out, appname); strcat(out, ext); #endif } void get_user_config_folder(char *out, unsigned int maxlen, const char *appname) { #ifdef CFGPATH_LINUX const char *out_orig = out; char *home = getenv("XDG_CONFIG_HOME"); unsigned int config_len = 0; if (!home) { home = getenv("HOME"); if (!home) { // Can't find home directory out[0] = 0; return; } config_len = strlen(".config/"); } unsigned int home_len = strlen(home); unsigned int appname_len = strlen(appname); /* first +1 is "/", second is trailing "/", third is terminating null */ if (home_len + 1 + config_len + appname_len + 1 + 1 > maxlen) { out[0] = 0; return; } memcpy(out, home, home_len); out += home_len; *out = '/'; out++; if (config_len) { memcpy(out, ".config/", config_len); out += config_len; /* Make the .config folder if it doesn't already exist */ *out = '\0'; mkdir(out_orig, 0755); } memcpy(out, appname, appname_len); out += appname_len; /* Make the .config/appname folder if it doesn't already exist */ *out = '\0'; mkdir(out_orig, 0755); *out = '/'; out++; *out = '\0'; #elif defined(CFGPATH_WINDOWS) if (maxlen < MAX_PATH) { out[0] = 0; return; } if (!SUCCEEDED(SHGetFolderPath(NULL, CSIDL_APPDATA, NULL, 0, out))) { out[0] = 0; return; } /* We don't try to create the AppData folder as it always exists already */ unsigned int appname_len = strlen(appname); if (strlen(out) + 1 + appname_len + 1 + 1 > maxlen) { out[0] = 0; return; } strcat(out, "\\"); strcat(out, appname); /* Make the AppData\appname folder if it doesn't already exist */ mkdir(out); strcat(out, "\\"); #elif defined(CFGPATH_MAC) FSRef ref; FSFindFolder(kUserDomain, kApplicationSupportFolderType, kCreateFolder, &ref); char home[MAX_PATH]; FSRefMakePath(&ref, (UInt8 *)&home, MAX_PATH); /* first +1 is "/", second is trailing "/", third is terminating null */ if (strlen(home) + 1 + strlen(appname) + 1 + 1 > maxlen) { out[0] = 0; return; } strcpy(out, home); strcat(out, PATH_SEPARATOR_STRING); strcat(out, appname); /* Make the .config/appname folder if it doesn't already exist */ mkdir(out, 0755); strcat(out, PATH_SEPARATOR_STRING); #endif } void get_user_data_folder(char *out, unsigned int maxlen, const char *appname) { #ifdef CFGPATH_LINUX const char *out_orig = out; char *home = getenv("XDG_DATA_HOME"); unsigned int config_len = 0; if (!home) { home = getenv("HOME"); if (!home) { // Can't find home directory out[0] = 0; return; } config_len = strlen(".local/share/"); } unsigned int home_len = strlen(home); unsigned int appname_len = strlen(appname); /* first +1 is "/", second is trailing "/", third is terminating null */ if (home_len + 1 + config_len + appname_len + 1 + 1 > maxlen) { out[0] = 0; return; } memcpy(out, home, home_len); out += home_len; *out = '/'; out++; if (config_len) { memcpy(out, ".local/share/", config_len); out += config_len; /* Make the .local/share folder if it doesn't already exist */ *out = '\0'; mkdir(out_orig, 0755); } memcpy(out, appname, appname_len); out += appname_len; /* Make the .local/share/appname folder if it doesn't already exist */ *out = '\0'; mkdir(out_orig, 0755); *out = '/'; out++; *out = '\0'; #elif defined(CFGPATH_WINDOWS) || defined(CFGPATH_MAC) /* No distinction under Windows or OS X */ get_user_config_folder(out, maxlen, appname); #endif } void get_user_cache_folder(char *out, unsigned int maxlen, const char *appname) { #ifdef CFGPATH_LINUX const char *out_orig = out; char *home = getenv("XDG_CACHE_HOME"); unsigned int config_len = 0; if (!home) { home = getenv("HOME"); if (!home) { // Can't find home directory out[0] = 0; return; } config_len = strlen(".cache/"); } unsigned int home_len = strlen(home); unsigned int appname_len = strlen(appname); /* first +1 is "/", second is trailing "/", third is terminating null */ if (home_len + 1 + config_len + appname_len + 1 + 1 > maxlen) { out[0] = 0; return; } memcpy(out, home, home_len); out += home_len; *out = '/'; out++; if (config_len) { memcpy(out, ".cache/", config_len); out += config_len; /* Make the .cache folder if it doesn't already exist */ *out = '\0'; mkdir(out_orig, 0755); } memcpy(out, appname, appname_len); out += appname_len; /* Make the .cache/appname folder if it doesn't already exist */ *out = '\0'; mkdir(out_orig, 0755); *out = '/'; out++; *out = '\0'; #elif defined(CFGPATH_WINDOWS) if (maxlen < MAX_PATH) { out[0] = 0; return; } if (!SUCCEEDED(SHGetFolderPath(NULL, CSIDL_LOCAL_APPDATA, NULL, 0, out))) { out[0] = 0; return; } /* We don't try to create the AppData folder as it always exists already */ unsigned int appname_len = strlen(appname); if (strlen(out) + 1 + appname_len + 1 + 1 > maxlen) { out[0] = 0; return; } strcat(out, "\\"); strcat(out, appname); /* Make the AppData\appname folder if it doesn't already exist */ mkdir(out); strcat(out, "\\"); #elif defined(CFGPATH_MAC) /* No distinction under OS X */ get_user_config_folder(out, maxlen, appname); #endif } ================================================ FILE: external/cfgpath.h ================================================ /** * @file cfgpath.h * @brief Cross platform methods for obtaining paths to configuration files. * * Copyright (C) 2013 Adam Nielsen * * This code is placed in the public domain. You are free to use it for any * purpose. If you add new platform support, please contribute a patch! * * Example use: * * char cfgdir[256]; * get_user_config_file(cfgdir, sizeof(cfgdir), "myapp"); * if (cfgdir[0] == 0) { * printf("Unable to find home directory.\n"); * return 1; * } * printf("Saving configuration file to %s\n", cfgdir); * * A number of constants are also defined: * * - MAX_PATH: Maximum length of a path, in characters. Used to allocate a * char array large enough to hold the returned path. * * - PATH_SEPARATOR_CHAR: The separator between folders. This will be either a * forward slash or a backslash depending on the platform. This is a * character constant. * * - PATH_SEPARATOR_STRING: The same as PATH_SEPARATOR_CHAR but as a C string, * to make it easier to append to other string constants. */ #ifndef CFGPATH_H_ #define CFGPATH_H_ #define MAX_PATH 512 /* arbitrary value */ #if defined(__linux__) || defined(BSD) || defined(__FreeBSD__) #define CFGPATH_LINUX #define PATH_SEPARATOR_CHAR '/' #define PATH_SEPARATOR_STRING "/" #elif defined(_WIN32) #define CFGPATH_WINDOWS #define PATH_SEPARATOR_CHAR '\\' #define PATH_SEPARATOR_STRING "\\" #elif defined(__APPLE__) #define CFGPATH_MAC #define PATH_SEPARATOR_CHAR '/' #define PATH_SEPARATOR_STRING "/" #else #error cfgpath.h functions have not been implemented for your platform! Please send patches. #endif /** Get an absolute path to a single configuration file, specific to this user. * * This function is useful for programs that need only a single configuration * file. The file is unique to the user account currently logged in. * * Output is typically: * * Windows: C:\Users\jcitizen\AppData\Roaming\appname.ini * Linux: /home/jcitizen/.config/appname.conf * Mac: /Users/jcitizen/Library/Application Support/appname.conf * * @param out * Buffer to write the path. On return will contain the path, or an empty * string on error. * * @param maxlen * Length of out. Must be >= MAX_PATH. * * @param appname * Short name of the application. Avoid using spaces or version numbers, and * use lowercase if possible. * * @post The file may or may not exist. * @post The folder holding the file is created if needed. */ void get_user_config_file(char *out, unsigned int maxlen, const char *appname); /** Get an absolute path to a configuration folder, specific to this user. * * This function is useful for programs that need to store multiple * configuration files. The output is a folder (which may not exist and will * need to be created) suitable for storing a number of files. * * The returned path will always end in a platform-specific trailing slash, so * that a filename can simply be appended to the path. * * Output is typically: * * Windows: C:\Users\jcitizen\AppData\Roaming\appname\ * Linux: /home/jcitizen/.config/appname/ * Mac: /Users/jcitizen/Library/Application Support/appname/ * * @param out * Buffer to write the path. On return will contain the path, or an empty * string on error. * * @param maxlen * Length of out. Must be >= MAX_PATH. * * @param appname * Short name of the application. Avoid using spaces or version numbers, and * use lowercase if possible. * * @post The folder is created if needed. */ void get_user_config_folder(char *out, unsigned int maxlen, const char *appname); /** Get an absolute path to a data storage folder, specific to this user. * * This function is useful for programs that need to store larger amounts of * data specific to each user. The output is a folder (which may not exist and * will need to be created) suitable for storing a number of data files. * * This path should be used for persistent, important data files the user would * want to keep. Do not use this path for temporary files, cache files, or * other files that can be recreated if they are deleted. Use * get_user_cache_folder() for those instead. * * The returned path will always end in a platform-specific trailing slash, so * that a filename can simply be appended to the path. * * Output is typically: * * Windows: C:\Users\jcitizen\AppData\Roaming\appname-data\ * Linux: /home/jcitizen/.local/share/appname/ * Mac: /Users/jcitizen/Library/Application Support/appname-data/ * * @param out * Buffer to write the path. On return will contain the path, or an empty * string on error. * * @param maxlen * Length of out. Must be >= MAX_PATH. * * @param appname * Short name of the application. Avoid using spaces or version numbers, and * use lowercase if possible. * * @post The folder is created if needed. */ void get_user_data_folder(char *out, unsigned int maxlen, const char *appname); /** Get an absolute path to a temporary storage folder, specific to this user. * * This function is useful for programs that temporarily need to store larger * amounts of data specific to each user. The output is a folder (which may * not exist and will need to be created) suitable for storing a number of * temporary files. The files may be lost or deleted when the program * terminates. * * This path should be used for temporary, unimportant data files that can * safely be deleted after the program terminates. Do not use this path for * any important files the user would want to keep. Use get_user_data_folder() * for those instead. * * The returned path will always end in a platform-specific trailing slash, so * that a filename can simply be appended to the path. * * Output is typically: * * Windows: C:\Users\jcitizen\AppData\Local\appname\ * Linux: /home/jcitizen/.cache/appname/ * Mac: /Users/jcitizen/Library/Application Support/appname/ * * @param out * Buffer to write the path. On return will contain the path, or an empty * string on error. * * @param maxlen * Length of out. Must be >= MAX_PATH. * * @param appname * Short name of the application. Avoid using spaces or version numbers, and * use lowercase if possible. * * @post The folder is created if needed. */ void get_user_cache_folder(char *out, unsigned int maxlen, const char *appname); #endif /* CFGPATH_H_ */ ================================================ FILE: external/nanosvg.h ================================================ /* * Copyright (c) 2013-14 Mikko Mononen memon@inside.org * * This software is provided 'as-is', without any express or implied * warranty. In no event will the authors be held liable for any damages * arising from the use of this software. * * Permission is granted to anyone to use this software for any purpose, * including commercial applications, and to alter it and redistribute it * freely, subject to the following restrictions: * * 1. The origin of this software must not be misrepresented; you must not * claim that you wrote the original software. If you use this software * in a product, an acknowledgment in the product documentation would be * appreciated but is not required. * 2. Altered source versions must be plainly marked as such, and must not be * misrepresented as being the original software. * 3. This notice may not be removed or altered from any source distribution. * * The SVG parser is based on Anti-Grain Geometry 2.4 SVG example * Copyright (C) 2002-2004 Maxim Shemanarev (McSeem) (http://www.antigrain.com/) * * Arc calculation code based on canvg (https://code.google.com/p/canvg/) * * Bounding box calculation based on http://blog.hackers-cafe.net/2009/06/how-to-calculate-bezier-curves-bounding.html * */ #ifndef NANOSVG_H #define NANOSVG_H #ifndef NANOSVG_CPLUSPLUS #ifdef __cplusplus extern "C" { #endif #endif // NanoSVG is a simple stupid single-header-file SVG parse. The output of the parser is a list of cubic bezier shapes. // // The library suits well for anything from rendering scalable icons in your editor application to prototyping a game. // // NanoSVG supports a wide range of SVG features, but something may be missing, feel free to create a pull request! // // The shapes in the SVG images are transformed by the viewBox and converted to specified units. // That is, you should get the same looking data as your designed in your favorite app. // // NanoSVG can return the paths in few different units. For example if you want to render an image, you may choose // to get the paths in pixels, or if you are feeding the data into a CNC-cutter, you may want to use millimeters. // // The units passed to NanoSVG should be one of: 'px', 'pt', 'pc' 'mm', 'cm', or 'in'. // DPI (dots-per-inch) controls how the unit conversion is done. // // If you don't know or care about the units stuff, "px" and 96 should get you going. /* Example Usage: // Load SVG NSVGimage* image; image = nsvgParseFromFile("test.svg", "px", 96); printf("size: %f x %f\n", image->width, image->height); // Use... for (NSVGshape *shape = image->shapes; shape != NULL; shape = shape->next) { for (NSVGpath *path = shape->paths; path != NULL; path = path->next) { for (int i = 0; i < path->npts-1; i += 3) { float* p = &path->pts[i*2]; drawCubicBez(p[0],p[1], p[2],p[3], p[4],p[5], p[6],p[7]); } } } // Delete nsvgDelete(image); */ enum NSVGpaintType { NSVG_PAINT_UNDEF = -1, NSVG_PAINT_NONE = 0, NSVG_PAINT_COLOR = 1, NSVG_PAINT_LINEAR_GRADIENT = 2, NSVG_PAINT_RADIAL_GRADIENT = 3 }; enum NSVGspreadType { NSVG_SPREAD_PAD = 0, NSVG_SPREAD_REFLECT = 1, NSVG_SPREAD_REPEAT = 2 }; enum NSVGlineJoin { NSVG_JOIN_MITER = 0, NSVG_JOIN_ROUND = 1, NSVG_JOIN_BEVEL = 2 }; enum NSVGlineCap { NSVG_CAP_BUTT = 0, NSVG_CAP_ROUND = 1, NSVG_CAP_SQUARE = 2 }; enum NSVGfillRule { NSVG_FILLRULE_NONZERO = 0, NSVG_FILLRULE_EVENODD = 1 }; enum NSVGflags { NSVG_FLAGS_VISIBLE = 0x01 }; enum NSVGpaintOrder { NSVG_PAINT_FILL = 0x00, NSVG_PAINT_MARKERS = 0x01, NSVG_PAINT_STROKE = 0x02, }; typedef struct NSVGgradientStop { unsigned int color; float offset; } NSVGgradientStop; typedef struct NSVGgradient { float xform[6]; char spread; float fx, fy; int nstops; NSVGgradientStop stops[1]; } NSVGgradient; typedef struct NSVGpaint { signed char type; union { unsigned int color; NSVGgradient* gradient; }; } NSVGpaint; typedef struct NSVGpath { float* pts; // Cubic bezier points: x0,y0, [cpx1,cpx1,cpx2,cpy2,x1,y1], ... int npts; // Total number of bezier points. char closed; // Flag indicating if shapes should be treated as closed. float bounds[4]; // Tight bounding box of the shape [minx,miny,maxx,maxy]. struct NSVGpath* next; // Pointer to next path, or NULL if last element. } NSVGpath; typedef struct NSVGshape { char id[64]; // Optional 'id' attr of the shape or its group NSVGpaint fill; // Fill paint NSVGpaint stroke; // Stroke paint float opacity; // Opacity of the shape. float strokeWidth; // Stroke width (scaled). float strokeDashOffset; // Stroke dash offset (scaled). float strokeDashArray[8]; // Stroke dash array (scaled). char strokeDashCount; // Number of dash values in dash array. char strokeLineJoin; // Stroke join type. char strokeLineCap; // Stroke cap type. float miterLimit; // Miter limit char fillRule; // Fill rule, see NSVGfillRule. unsigned char paintOrder; // Encoded paint order (3×2-bit fields) see NSVGpaintOrder unsigned char flags; // Logical or of NSVG_FLAGS_* flags float bounds[4]; // Tight bounding box of the shape [minx,miny,maxx,maxy]. char fillGradient[64]; // Optional 'id' of fill gradient char strokeGradient[64]; // Optional 'id' of stroke gradient float xform[6]; // Root transformation for fill/stroke gradient NSVGpath* paths; // Linked list of paths in the image. struct NSVGshape* next; // Pointer to next shape, or NULL if last element. } NSVGshape; typedef struct NSVGimage { float width; // Width of the image. float height; // Height of the image. NSVGshape* shapes; // Linked list of shapes in the image. } NSVGimage; // Parses SVG file from a file, returns SVG image as paths. NSVGimage* nsvgParseFromFile(const char* filename, const char* units, float dpi); // Parses SVG file from a null terminated string, returns SVG image as paths. // Important note: changes the string. NSVGimage* nsvgParse(char* input, const char* units, float dpi); // Duplicates a path. NSVGpath* nsvgDuplicatePath(NSVGpath* p); // Deletes an image. void nsvgDelete(NSVGimage* image); #ifndef NANOSVG_CPLUSPLUS #ifdef __cplusplus } #endif #endif #ifdef NANOSVG_IMPLEMENTATION #include #include #include #include #define NSVG_PI (3.14159265358979323846264338327f) #define NSVG_KAPPA90 (0.5522847493f) // Length proportional to radius of a cubic bezier handle for 90deg arcs. #define NSVG_ALIGN_MIN 0 #define NSVG_ALIGN_MID 1 #define NSVG_ALIGN_MAX 2 #define NSVG_ALIGN_NONE 0 #define NSVG_ALIGN_MEET 1 #define NSVG_ALIGN_SLICE 2 #define NSVG_NOTUSED(v) do { (void)(1 ? (void)0 : ( (void)(v) ) ); } while(0) #define NSVG_RGB(r, g, b) (((unsigned int)r) | ((unsigned int)g << 8) | ((unsigned int)b << 16)) #ifdef _MSC_VER #pragma warning (disable: 4996) // Switch off security warnings #pragma warning (disable: 4100) // Switch off unreferenced formal parameter warnings #ifdef __cplusplus #define NSVG_INLINE inline #else #define NSVG_INLINE #endif #else #define NSVG_INLINE inline #endif static int nsvg__isspace(char c) { return strchr(" \t\n\v\f\r", c) != 0; } static int nsvg__isdigit(char c) { return c >= '0' && c <= '9'; } static NSVG_INLINE float nsvg__minf(float a, float b) { return a < b ? a : b; } static NSVG_INLINE float nsvg__maxf(float a, float b) { return a > b ? a : b; } // Simple XML parser #define NSVG_XML_TAG 1 #define NSVG_XML_CONTENT 2 #define NSVG_XML_MAX_ATTRIBS 256 static void nsvg__parseContent(char* s, void (*contentCb)(void* ud, const char* s), void* ud) { // Trim start white spaces while (*s && nsvg__isspace(*s)) s++; if (!*s) return; if (contentCb) (*contentCb)(ud, s); } static void nsvg__parseElement(char* s, void (*startelCb)(void* ud, const char* el, const char** attr), void (*endelCb)(void* ud, const char* el), void* ud) { const char* attr[NSVG_XML_MAX_ATTRIBS]; int nattr = 0; char* name; int start = 0; int end = 0; char quote; // Skip white space after the '<' while (*s && nsvg__isspace(*s)) s++; // Check if the tag is end tag if (*s == '/') { s++; end = 1; } else { start = 1; } // Skip comments, data and preprocessor stuff. if (!*s || *s == '?' || *s == '!') return; // Get tag name name = s; while (*s && !nsvg__isspace(*s)) s++; if (*s) { *s++ = '\0'; } // Get attribs while (!end && *s && nattr < NSVG_XML_MAX_ATTRIBS-3) { char* name = NULL; char* value = NULL; // Skip white space before the attrib name while (*s && nsvg__isspace(*s)) s++; if (!*s) break; if (*s == '/') { end = 1; break; } name = s; // Find end of the attrib name. while (*s && !nsvg__isspace(*s) && *s != '=') s++; if (*s) { *s++ = '\0'; } // Skip until the beginning of the value. while (*s && *s != '\"' && *s != '\'') s++; if (!*s) break; quote = *s; s++; // Store value and find the end of it. value = s; while (*s && *s != quote) s++; if (*s) { *s++ = '\0'; } // Store only well formed attributes if (name && value) { attr[nattr++] = name; attr[nattr++] = value; } } // List terminator attr[nattr++] = 0; attr[nattr++] = 0; // Call callbacks. if (start && startelCb) (*startelCb)(ud, name, attr); if (end && endelCb) (*endelCb)(ud, name); } int nsvg__parseXML(char* input, void (*startelCb)(void* ud, const char* el, const char** attr), void (*endelCb)(void* ud, const char* el), void (*contentCb)(void* ud, const char* s), void* ud) { char* s = input; char* mark = s; int state = NSVG_XML_CONTENT; while (*s) { if (*s == '<' && state == NSVG_XML_CONTENT) { // Start of a tag *s++ = '\0'; nsvg__parseContent(mark, contentCb, ud); mark = s; state = NSVG_XML_TAG; } else if (*s == '>' && state == NSVG_XML_TAG) { // Start of a content or new tag. *s++ = '\0'; nsvg__parseElement(mark, startelCb, endelCb, ud); mark = s; state = NSVG_XML_CONTENT; } else { s++; } } return 1; } /* Simple SVG parser. */ #define NSVG_MAX_ATTR 128 enum NSVGgradientUnits { NSVG_USER_SPACE = 0, NSVG_OBJECT_SPACE = 1 }; #define NSVG_MAX_DASHES 8 enum NSVGunits { NSVG_UNITS_USER, NSVG_UNITS_PX, NSVG_UNITS_PT, NSVG_UNITS_PC, NSVG_UNITS_MM, NSVG_UNITS_CM, NSVG_UNITS_IN, NSVG_UNITS_PERCENT, NSVG_UNITS_EM, NSVG_UNITS_EX }; typedef struct NSVGcoordinate { float value; int units; } NSVGcoordinate; typedef struct NSVGlinearData { NSVGcoordinate x1, y1, x2, y2; } NSVGlinearData; typedef struct NSVGradialData { NSVGcoordinate cx, cy, r, fx, fy; } NSVGradialData; typedef struct NSVGgradientData { char id[64]; char ref[64]; signed char type; union { NSVGlinearData linear; NSVGradialData radial; }; char spread; char units; float xform[6]; int nstops; NSVGgradientStop* stops; struct NSVGgradientData* next; } NSVGgradientData; typedef struct NSVGattrib { char id[64]; float xform[6]; unsigned int fillColor; unsigned int strokeColor; float opacity; float fillOpacity; float strokeOpacity; char fillGradient[64]; char strokeGradient[64]; float strokeWidth; float strokeDashOffset; float strokeDashArray[NSVG_MAX_DASHES]; int strokeDashCount; char strokeLineJoin; char strokeLineCap; float miterLimit; char fillRule; float fontSize; unsigned int stopColor; float stopOpacity; float stopOffset; char hasFill; char hasStroke; char visible; unsigned char paintOrder; } NSVGattrib; typedef struct NSVGparser { NSVGattrib attr[NSVG_MAX_ATTR]; int attrHead; float* pts; int npts; int cpts; NSVGpath* plist; NSVGimage* image; NSVGgradientData* gradients; NSVGshape* shapesTail; float viewMinx, viewMiny, viewWidth, viewHeight; int alignX, alignY, alignType; float dpi; char pathFlag; char defsFlag; } NSVGparser; static void nsvg__xformIdentity(float* t) { t[0] = 1.0f; t[1] = 0.0f; t[2] = 0.0f; t[3] = 1.0f; t[4] = 0.0f; t[5] = 0.0f; } static void nsvg__xformSetTranslation(float* t, float tx, float ty) { t[0] = 1.0f; t[1] = 0.0f; t[2] = 0.0f; t[3] = 1.0f; t[4] = tx; t[5] = ty; } static void nsvg__xformSetScale(float* t, float sx, float sy) { t[0] = sx; t[1] = 0.0f; t[2] = 0.0f; t[3] = sy; t[4] = 0.0f; t[5] = 0.0f; } static void nsvg__xformSetSkewX(float* t, float a) { t[0] = 1.0f; t[1] = 0.0f; t[2] = tanf(a); t[3] = 1.0f; t[4] = 0.0f; t[5] = 0.0f; } static void nsvg__xformSetSkewY(float* t, float a) { t[0] = 1.0f; t[1] = tanf(a); t[2] = 0.0f; t[3] = 1.0f; t[4] = 0.0f; t[5] = 0.0f; } static void nsvg__xformSetRotation(float* t, float a) { float cs = cosf(a), sn = sinf(a); t[0] = cs; t[1] = sn; t[2] = -sn; t[3] = cs; t[4] = 0.0f; t[5] = 0.0f; } static void nsvg__xformMultiply(float* t, float* s) { float t0 = t[0] * s[0] + t[1] * s[2]; float t2 = t[2] * s[0] + t[3] * s[2]; float t4 = t[4] * s[0] + t[5] * s[2] + s[4]; t[1] = t[0] * s[1] + t[1] * s[3]; t[3] = t[2] * s[1] + t[3] * s[3]; t[5] = t[4] * s[1] + t[5] * s[3] + s[5]; t[0] = t0; t[2] = t2; t[4] = t4; } static void nsvg__xformInverse(float* inv, float* t) { double invdet, det = (double)t[0] * t[3] - (double)t[2] * t[1]; if (det > -1e-6 && det < 1e-6) { nsvg__xformIdentity(t); return; } invdet = 1.0 / det; inv[0] = (float)(t[3] * invdet); inv[2] = (float)(-t[2] * invdet); inv[4] = (float)(((double)t[2] * t[5] - (double)t[3] * t[4]) * invdet); inv[1] = (float)(-t[1] * invdet); inv[3] = (float)(t[0] * invdet); inv[5] = (float)(((double)t[1] * t[4] - (double)t[0] * t[5]) * invdet); } static void nsvg__xformPremultiply(float* t, float* s) { float s2[6]; memcpy(s2, s, sizeof(float)*6); nsvg__xformMultiply(s2, t); memcpy(t, s2, sizeof(float)*6); } static void nsvg__xformPoint(float* dx, float* dy, float x, float y, float* t) { *dx = x*t[0] + y*t[2] + t[4]; *dy = x*t[1] + y*t[3] + t[5]; } static void nsvg__xformVec(float* dx, float* dy, float x, float y, float* t) { *dx = x*t[0] + y*t[2]; *dy = x*t[1] + y*t[3]; } #define NSVG_EPSILON (1e-12) static int nsvg__ptInBounds(float* pt, float* bounds) { return pt[0] >= bounds[0] && pt[0] <= bounds[2] && pt[1] >= bounds[1] && pt[1] <= bounds[3]; } static double nsvg__evalBezier(double t, double p0, double p1, double p2, double p3) { double it = 1.0-t; return it*it*it*p0 + 3.0*it*it*t*p1 + 3.0*it*t*t*p2 + t*t*t*p3; } static void nsvg__curveBounds(float* bounds, float* curve) { int i, j, count; double roots[2], a, b, c, b2ac, t, v; float* v0 = &curve[0]; float* v1 = &curve[2]; float* v2 = &curve[4]; float* v3 = &curve[6]; // Start the bounding box by end points bounds[0] = nsvg__minf(v0[0], v3[0]); bounds[1] = nsvg__minf(v0[1], v3[1]); bounds[2] = nsvg__maxf(v0[0], v3[0]); bounds[3] = nsvg__maxf(v0[1], v3[1]); // Bezier curve fits inside the convex hull of it's control points. // If control points are inside the bounds, we're done. if (nsvg__ptInBounds(v1, bounds) && nsvg__ptInBounds(v2, bounds)) return; // Add bezier curve inflection points in X and Y. for (i = 0; i < 2; i++) { a = -3.0 * v0[i] + 9.0 * v1[i] - 9.0 * v2[i] + 3.0 * v3[i]; b = 6.0 * v0[i] - 12.0 * v1[i] + 6.0 * v2[i]; c = 3.0 * v1[i] - 3.0 * v0[i]; count = 0; if (fabs(a) < NSVG_EPSILON) { if (fabs(b) > NSVG_EPSILON) { t = -c / b; if (t > NSVG_EPSILON && t < 1.0-NSVG_EPSILON) roots[count++] = t; } } else { b2ac = b*b - 4.0*c*a; if (b2ac > NSVG_EPSILON) { t = (-b + sqrt(b2ac)) / (2.0 * a); if (t > NSVG_EPSILON && t < 1.0-NSVG_EPSILON) roots[count++] = t; t = (-b - sqrt(b2ac)) / (2.0 * a); if (t > NSVG_EPSILON && t < 1.0-NSVG_EPSILON) roots[count++] = t; } } for (j = 0; j < count; j++) { v = nsvg__evalBezier(roots[j], v0[i], v1[i], v2[i], v3[i]); bounds[0+i] = nsvg__minf(bounds[0+i], (float)v); bounds[2+i] = nsvg__maxf(bounds[2+i], (float)v); } } } static unsigned char nsvg__encodePaintOrder(enum NSVGpaintOrder a, enum NSVGpaintOrder b, enum NSVGpaintOrder c) { return (a & 0x03) | ((b & 0x03) << 2) | ((c & 0x03) << 4); } static NSVGparser* nsvg__createParser(void) { NSVGparser* p; p = (NSVGparser*)malloc(sizeof(NSVGparser)); if (p == NULL) goto error; memset(p, 0, sizeof(NSVGparser)); p->image = (NSVGimage*)malloc(sizeof(NSVGimage)); if (p->image == NULL) goto error; memset(p->image, 0, sizeof(NSVGimage)); // Init style nsvg__xformIdentity(p->attr[0].xform); memset(p->attr[0].id, 0, sizeof p->attr[0].id); p->attr[0].fillColor = NSVG_RGB(0,0,0); p->attr[0].strokeColor = NSVG_RGB(0,0,0); p->attr[0].opacity = 1; p->attr[0].fillOpacity = 1; p->attr[0].strokeOpacity = 1; p->attr[0].stopOpacity = 1; p->attr[0].strokeWidth = 1; p->attr[0].strokeLineJoin = NSVG_JOIN_MITER; p->attr[0].strokeLineCap = NSVG_CAP_BUTT; p->attr[0].miterLimit = 4; p->attr[0].fillRule = NSVG_FILLRULE_NONZERO; p->attr[0].hasFill = 1; p->attr[0].visible = 1; p->attr[0].paintOrder = nsvg__encodePaintOrder(NSVG_PAINT_FILL, NSVG_PAINT_STROKE, NSVG_PAINT_MARKERS); return p; error: if (p) { if (p->image) free(p->image); free(p); } return NULL; } static void nsvg__deletePaths(NSVGpath* path) { while (path) { NSVGpath *next = path->next; if (path->pts != NULL) free(path->pts); free(path); path = next; } } static void nsvg__deletePaint(NSVGpaint* paint) { if (paint->type == NSVG_PAINT_LINEAR_GRADIENT || paint->type == NSVG_PAINT_RADIAL_GRADIENT) free(paint->gradient); } static void nsvg__deleteGradientData(NSVGgradientData* grad) { NSVGgradientData* next; while (grad != NULL) { next = grad->next; free(grad->stops); free(grad); grad = next; } } static void nsvg__deleteParser(NSVGparser* p) { if (p != NULL) { nsvg__deletePaths(p->plist); nsvg__deleteGradientData(p->gradients); nsvgDelete(p->image); free(p->pts); free(p); } } static void nsvg__resetPath(NSVGparser* p) { p->npts = 0; } static void nsvg__addPoint(NSVGparser* p, float x, float y) { if (p->npts+1 > p->cpts) { p->cpts = p->cpts ? p->cpts*2 : 8; p->pts = (float*)realloc(p->pts, p->cpts*2*sizeof(float)); if (!p->pts) return; } p->pts[p->npts*2+0] = x; p->pts[p->npts*2+1] = y; p->npts++; } static void nsvg__moveTo(NSVGparser* p, float x, float y) { if (p->npts > 0) { p->pts[(p->npts-1)*2+0] = x; p->pts[(p->npts-1)*2+1] = y; } else { nsvg__addPoint(p, x, y); } } static void nsvg__lineTo(NSVGparser* p, float x, float y) { float px,py, dx,dy; if (p->npts > 0) { px = p->pts[(p->npts-1)*2+0]; py = p->pts[(p->npts-1)*2+1]; dx = x - px; dy = y - py; nsvg__addPoint(p, px + dx/3.0f, py + dy/3.0f); nsvg__addPoint(p, x - dx/3.0f, y - dy/3.0f); nsvg__addPoint(p, x, y); } } static void nsvg__cubicBezTo(NSVGparser* p, float cpx1, float cpy1, float cpx2, float cpy2, float x, float y) { if (p->npts > 0) { nsvg__addPoint(p, cpx1, cpy1); nsvg__addPoint(p, cpx2, cpy2); nsvg__addPoint(p, x, y); } } static NSVGattrib* nsvg__getAttr(NSVGparser* p) { return &p->attr[p->attrHead]; } static void nsvg__pushAttr(NSVGparser* p) { if (p->attrHead < NSVG_MAX_ATTR-1) { p->attrHead++; memcpy(&p->attr[p->attrHead], &p->attr[p->attrHead-1], sizeof(NSVGattrib)); } } static void nsvg__popAttr(NSVGparser* p) { if (p->attrHead > 0) p->attrHead--; } static float nsvg__actualOrigX(NSVGparser* p) { return p->viewMinx; } static float nsvg__actualOrigY(NSVGparser* p) { return p->viewMiny; } static float nsvg__actualWidth(NSVGparser* p) { return p->viewWidth; } static float nsvg__actualHeight(NSVGparser* p) { return p->viewHeight; } static float nsvg__actualLength(NSVGparser* p) { float w = nsvg__actualWidth(p), h = nsvg__actualHeight(p); return sqrtf(w*w + h*h) / sqrtf(2.0f); } static float nsvg__convertToPixels(NSVGparser* p, NSVGcoordinate c, float orig, float length) { NSVGattrib* attr = nsvg__getAttr(p); switch (c.units) { case NSVG_UNITS_USER: return c.value; case NSVG_UNITS_PX: return c.value; case NSVG_UNITS_PT: return c.value / 72.0f * p->dpi; case NSVG_UNITS_PC: return c.value / 6.0f * p->dpi; case NSVG_UNITS_MM: return c.value / 25.4f * p->dpi; case NSVG_UNITS_CM: return c.value / 2.54f * p->dpi; case NSVG_UNITS_IN: return c.value * p->dpi; case NSVG_UNITS_EM: return c.value * attr->fontSize; case NSVG_UNITS_EX: return c.value * attr->fontSize * 0.52f; // x-height of Helvetica. case NSVG_UNITS_PERCENT: return orig + c.value / 100.0f * length; default: return c.value; } return c.value; } static NSVGgradientData* nsvg__findGradientData(NSVGparser* p, const char* id) { NSVGgradientData* grad = p->gradients; if (id == NULL || *id == '\0') return NULL; while (grad != NULL) { if (strcmp(grad->id, id) == 0) return grad; grad = grad->next; } return NULL; } static NSVGgradient* nsvg__createGradient(NSVGparser* p, const char* id, const float* localBounds, float *xform, signed char* paintType) { NSVGgradientData* data = NULL; NSVGgradientData* ref = NULL; NSVGgradientStop* stops = NULL; NSVGgradient* grad; float ox, oy, sw, sh, sl; int nstops = 0; int refIter; data = nsvg__findGradientData(p, id); if (data == NULL) return NULL; // TODO: use ref to fill in all unset values too. ref = data; refIter = 0; while (ref != NULL) { NSVGgradientData* nextRef = NULL; if (stops == NULL && ref->stops != NULL) { stops = ref->stops; nstops = ref->nstops; break; } nextRef = nsvg__findGradientData(p, ref->ref); if (nextRef == ref) break; // prevent infite loops on malformed data ref = nextRef; refIter++; if (refIter > 32) break; // prevent infite loops on malformed data } if (stops == NULL) return NULL; grad = (NSVGgradient*)malloc(sizeof(NSVGgradient) + sizeof(NSVGgradientStop)*(nstops-1)); if (grad == NULL) return NULL; // The shape width and height. if (data->units == NSVG_OBJECT_SPACE) { ox = localBounds[0]; oy = localBounds[1]; sw = localBounds[2] - localBounds[0]; sh = localBounds[3] - localBounds[1]; } else { ox = nsvg__actualOrigX(p); oy = nsvg__actualOrigY(p); sw = nsvg__actualWidth(p); sh = nsvg__actualHeight(p); } sl = sqrtf(sw*sw + sh*sh) / sqrtf(2.0f); if (data->type == NSVG_PAINT_LINEAR_GRADIENT) { float x1, y1, x2, y2, dx, dy; x1 = nsvg__convertToPixels(p, data->linear.x1, ox, sw); y1 = nsvg__convertToPixels(p, data->linear.y1, oy, sh); x2 = nsvg__convertToPixels(p, data->linear.x2, ox, sw); y2 = nsvg__convertToPixels(p, data->linear.y2, oy, sh); // Calculate transform aligned to the line dx = x2 - x1; dy = y2 - y1; grad->xform[0] = dy; grad->xform[1] = -dx; grad->xform[2] = dx; grad->xform[3] = dy; grad->xform[4] = x1; grad->xform[5] = y1; } else { float cx, cy, fx, fy, r; cx = nsvg__convertToPixels(p, data->radial.cx, ox, sw); cy = nsvg__convertToPixels(p, data->radial.cy, oy, sh); fx = nsvg__convertToPixels(p, data->radial.fx, ox, sw); fy = nsvg__convertToPixels(p, data->radial.fy, oy, sh); r = nsvg__convertToPixels(p, data->radial.r, 0, sl); // Calculate transform aligned to the circle grad->xform[0] = r; grad->xform[1] = 0; grad->xform[2] = 0; grad->xform[3] = r; grad->xform[4] = cx; grad->xform[5] = cy; grad->fx = fx / r; grad->fy = fy / r; } nsvg__xformMultiply(grad->xform, data->xform); nsvg__xformMultiply(grad->xform, xform); grad->spread = data->spread; memcpy(grad->stops, stops, nstops*sizeof(NSVGgradientStop)); grad->nstops = nstops; *paintType = data->type; return grad; } static float nsvg__getAverageScale(float* t) { float sx = sqrtf(t[0]*t[0] + t[2]*t[2]); float sy = sqrtf(t[1]*t[1] + t[3]*t[3]); return (sx + sy) * 0.5f; } static void nsvg__getLocalBounds(float* bounds, NSVGshape *shape, float* xform) { NSVGpath* path; float curve[4*2], curveBounds[4]; int i, first = 1; for (path = shape->paths; path != NULL; path = path->next) { nsvg__xformPoint(&curve[0], &curve[1], path->pts[0], path->pts[1], xform); for (i = 0; i < path->npts-1; i += 3) { nsvg__xformPoint(&curve[2], &curve[3], path->pts[(i+1)*2], path->pts[(i+1)*2+1], xform); nsvg__xformPoint(&curve[4], &curve[5], path->pts[(i+2)*2], path->pts[(i+2)*2+1], xform); nsvg__xformPoint(&curve[6], &curve[7], path->pts[(i+3)*2], path->pts[(i+3)*2+1], xform); nsvg__curveBounds(curveBounds, curve); if (first) { bounds[0] = curveBounds[0]; bounds[1] = curveBounds[1]; bounds[2] = curveBounds[2]; bounds[3] = curveBounds[3]; first = 0; } else { bounds[0] = nsvg__minf(bounds[0], curveBounds[0]); bounds[1] = nsvg__minf(bounds[1], curveBounds[1]); bounds[2] = nsvg__maxf(bounds[2], curveBounds[2]); bounds[3] = nsvg__maxf(bounds[3], curveBounds[3]); } curve[0] = curve[6]; curve[1] = curve[7]; } } } static void nsvg__addShape(NSVGparser* p) { NSVGattrib* attr = nsvg__getAttr(p); float scale = 1.0f; NSVGshape* shape; NSVGpath* path; int i; if (p->plist == NULL) return; shape = (NSVGshape*)malloc(sizeof(NSVGshape)); if (shape == NULL) goto error; memset(shape, 0, sizeof(NSVGshape)); memcpy(shape->id, attr->id, sizeof shape->id); memcpy(shape->fillGradient, attr->fillGradient, sizeof shape->fillGradient); memcpy(shape->strokeGradient, attr->strokeGradient, sizeof shape->strokeGradient); memcpy(shape->xform, attr->xform, sizeof shape->xform); scale = nsvg__getAverageScale(attr->xform); shape->strokeWidth = attr->strokeWidth * scale; shape->strokeDashOffset = attr->strokeDashOffset * scale; shape->strokeDashCount = (char)attr->strokeDashCount; for (i = 0; i < attr->strokeDashCount; i++) shape->strokeDashArray[i] = attr->strokeDashArray[i] * scale; shape->strokeLineJoin = attr->strokeLineJoin; shape->strokeLineCap = attr->strokeLineCap; shape->miterLimit = attr->miterLimit; shape->fillRule = attr->fillRule; shape->opacity = attr->opacity; shape->paintOrder = attr->paintOrder; shape->paths = p->plist; p->plist = NULL; // Calculate shape bounds shape->bounds[0] = shape->paths->bounds[0]; shape->bounds[1] = shape->paths->bounds[1]; shape->bounds[2] = shape->paths->bounds[2]; shape->bounds[3] = shape->paths->bounds[3]; for (path = shape->paths->next; path != NULL; path = path->next) { shape->bounds[0] = nsvg__minf(shape->bounds[0], path->bounds[0]); shape->bounds[1] = nsvg__minf(shape->bounds[1], path->bounds[1]); shape->bounds[2] = nsvg__maxf(shape->bounds[2], path->bounds[2]); shape->bounds[3] = nsvg__maxf(shape->bounds[3], path->bounds[3]); } // Set fill if (attr->hasFill == 0) { shape->fill.type = NSVG_PAINT_NONE; } else if (attr->hasFill == 1) { shape->fill.type = NSVG_PAINT_COLOR; shape->fill.color = attr->fillColor; shape->fill.color |= (unsigned int)(attr->fillOpacity*255) << 24; } else if (attr->hasFill == 2) { shape->fill.type = NSVG_PAINT_UNDEF; } // Set stroke if (attr->hasStroke == 0) { shape->stroke.type = NSVG_PAINT_NONE; } else if (attr->hasStroke == 1) { shape->stroke.type = NSVG_PAINT_COLOR; shape->stroke.color = attr->strokeColor; shape->stroke.color |= (unsigned int)(attr->strokeOpacity*255) << 24; } else if (attr->hasStroke == 2) { shape->stroke.type = NSVG_PAINT_UNDEF; } // Set flags shape->flags = (attr->visible ? NSVG_FLAGS_VISIBLE : 0x00); // Add to tail if (p->image->shapes == NULL) p->image->shapes = shape; else p->shapesTail->next = shape; p->shapesTail = shape; return; error: if (shape) free(shape); } static void nsvg__addPath(NSVGparser* p, char closed) { NSVGattrib* attr = nsvg__getAttr(p); NSVGpath* path = NULL; float bounds[4]; float* curve; int i; if (p->npts < 4) return; if (closed) nsvg__lineTo(p, p->pts[0], p->pts[1]); // Expect 1 + N*3 points (N = number of cubic bezier segments). if ((p->npts % 3) != 1) return; path = (NSVGpath*)malloc(sizeof(NSVGpath)); if (path == NULL) goto error; memset(path, 0, sizeof(NSVGpath)); path->pts = (float*)malloc(p->npts*2*sizeof(float)); if (path->pts == NULL) goto error; path->closed = closed; path->npts = p->npts; // Transform path. for (i = 0; i < p->npts; ++i) nsvg__xformPoint(&path->pts[i*2], &path->pts[i*2+1], p->pts[i*2], p->pts[i*2+1], attr->xform); // Find bounds for (i = 0; i < path->npts-1; i += 3) { curve = &path->pts[i*2]; nsvg__curveBounds(bounds, curve); if (i == 0) { path->bounds[0] = bounds[0]; path->bounds[1] = bounds[1]; path->bounds[2] = bounds[2]; path->bounds[3] = bounds[3]; } else { path->bounds[0] = nsvg__minf(path->bounds[0], bounds[0]); path->bounds[1] = nsvg__minf(path->bounds[1], bounds[1]); path->bounds[2] = nsvg__maxf(path->bounds[2], bounds[2]); path->bounds[3] = nsvg__maxf(path->bounds[3], bounds[3]); } } path->next = p->plist; p->plist = path; return; error: if (path != NULL) { if (path->pts != NULL) free(path->pts); free(path); } } // We roll our own string to float because the std library one uses locale and messes things up. static double nsvg__atof(const char* s) { char* cur = (char*)s; char* end = NULL; double res = 0.0, sign = 1.0; long long intPart = 0, fracPart = 0; char hasIntPart = 0, hasFracPart = 0; // Parse optional sign if (*cur == '+') { cur++; } else if (*cur == '-') { sign = -1; cur++; } // Parse integer part if (nsvg__isdigit(*cur)) { // Parse digit sequence intPart = strtoll(cur, &end, 10); if (cur != end) { res = (double)intPart; hasIntPart = 1; cur = end; } } // Parse fractional part. if (*cur == '.') { cur++; // Skip '.' if (nsvg__isdigit(*cur)) { // Parse digit sequence fracPart = strtoll(cur, &end, 10); if (cur != end) { res += (double)fracPart / pow(10.0, (double)(end - cur)); hasFracPart = 1; cur = end; } } } // A valid number should have integer or fractional part. if (!hasIntPart && !hasFracPart) return 0.0; // Parse optional exponent if (*cur == 'e' || *cur == 'E') { long expPart = 0; cur++; // skip 'E' expPart = strtol(cur, &end, 10); // Parse digit sequence with sign if (cur != end) { res *= pow(10.0, (double)expPart); } } return res * sign; } static const char* nsvg__parseNumber(const char* s, char* it, const int size) { const int last = size-1; int i = 0; // sign if (*s == '-' || *s == '+') { if (i < last) it[i++] = *s; s++; } // integer part while (*s && nsvg__isdigit(*s)) { if (i < last) it[i++] = *s; s++; } if (*s == '.') { // decimal point if (i < last) it[i++] = *s; s++; // fraction part while (*s && nsvg__isdigit(*s)) { if (i < last) it[i++] = *s; s++; } } // exponent if ((*s == 'e' || *s == 'E') && (s[1] != 'm' && s[1] != 'x')) { if (i < last) it[i++] = *s; s++; if (*s == '-' || *s == '+') { if (i < last) it[i++] = *s; s++; } while (*s && nsvg__isdigit(*s)) { if (i < last) it[i++] = *s; s++; } } it[i] = '\0'; return s; } static const char* nsvg__getNextPathItemWhenArcFlag(const char* s, char* it) { it[0] = '\0'; while (*s && (nsvg__isspace(*s) || *s == ',')) s++; if (!*s) return s; if (*s == '0' || *s == '1') { it[0] = *s++; it[1] = '\0'; return s; } return s; } static const char* nsvg__getNextPathItem(const char* s, char* it) { it[0] = '\0'; // Skip white spaces and commas while (*s && (nsvg__isspace(*s) || *s == ',')) s++; if (!*s) return s; if (*s == '-' || *s == '+' || *s == '.' || nsvg__isdigit(*s)) { s = nsvg__parseNumber(s, it, 64); } else { // Parse command it[0] = *s++; it[1] = '\0'; return s; } return s; } static unsigned int nsvg__parseColorHex(const char* str) { unsigned int r=0, g=0, b=0; if (sscanf(str, "#%2x%2x%2x", &r, &g, &b) == 3 ) // 2 digit hex return NSVG_RGB(r, g, b); if (sscanf(str, "#%1x%1x%1x", &r, &g, &b) == 3 ) // 1 digit hex, e.g. #abc -> 0xccbbaa return NSVG_RGB(r*17, g*17, b*17); // same effect as (r<<4|r), (g<<4|g), .. return NSVG_RGB(128, 128, 128); } // Parse rgb color. The pointer 'str' must point at "rgb(" (4+ characters). // This function returns gray (rgb(128, 128, 128) == '#808080') on parse errors // for backwards compatibility. Note: other image viewers return black instead. static unsigned int nsvg__parseColorRGB(const char* str) { int i; unsigned int rgbi[3]; float rgbf[3]; // try decimal integers first if (sscanf(str, "rgb(%u, %u, %u)", &rgbi[0], &rgbi[1], &rgbi[2]) != 3) { // integers failed, try percent values (float, locale independent) const char delimiter[3] = {',', ',', ')'}; str += 4; // skip "rgb(" for (i = 0; i < 3; i++) { while (*str && (nsvg__isspace(*str))) str++; // skip leading spaces if (*str == '+') str++; // skip '+' (don't allow '-') if (!*str) break; rgbf[i] = nsvg__atof(str); // Note 1: it would be great if nsvg__atof() returned how many // bytes it consumed but it doesn't. We need to skip the number, // the '%' character, spaces, and the delimiter ',' or ')'. // Note 2: The following code does not allow values like "33.%", // i.e. a decimal point w/o fractional part, but this is consistent // with other image viewers, e.g. firefox, chrome, eog, gimp. while (*str && nsvg__isdigit(*str)) str++; // skip integer part if (*str == '.') { str++; if (!nsvg__isdigit(*str)) break; // error: no digit after '.' while (*str && nsvg__isdigit(*str)) str++; // skip fractional part } if (*str == '%') str++; else break; while (*str && nsvg__isspace(*str)) str++; if (*str == delimiter[i]) str++; else break; } if (i == 3) { rgbi[0] = roundf(rgbf[0] * 2.55f); rgbi[1] = roundf(rgbf[1] * 2.55f); rgbi[2] = roundf(rgbf[2] * 2.55f); } else { rgbi[0] = rgbi[1] = rgbi[2] = 128; } } // clip values as the CSS spec requires for (i = 0; i < 3; i++) { if (rgbi[i] > 255) rgbi[i] = 255; } return NSVG_RGB(rgbi[0], rgbi[1], rgbi[2]); } typedef struct NSVGNamedColor { const char* name; unsigned int color; } NSVGNamedColor; NSVGNamedColor nsvg__colors[] = { { "red", NSVG_RGB(255, 0, 0) }, { "green", NSVG_RGB( 0, 128, 0) }, { "blue", NSVG_RGB( 0, 0, 255) }, { "yellow", NSVG_RGB(255, 255, 0) }, { "cyan", NSVG_RGB( 0, 255, 255) }, { "magenta", NSVG_RGB(255, 0, 255) }, { "black", NSVG_RGB( 0, 0, 0) }, { "grey", NSVG_RGB(128, 128, 128) }, { "gray", NSVG_RGB(128, 128, 128) }, { "white", NSVG_RGB(255, 255, 255) }, #ifdef NANOSVG_ALL_COLOR_KEYWORDS { "aliceblue", NSVG_RGB(240, 248, 255) }, { "antiquewhite", NSVG_RGB(250, 235, 215) }, { "aqua", NSVG_RGB( 0, 255, 255) }, { "aquamarine", NSVG_RGB(127, 255, 212) }, { "azure", NSVG_RGB(240, 255, 255) }, { "beige", NSVG_RGB(245, 245, 220) }, { "bisque", NSVG_RGB(255, 228, 196) }, { "blanchedalmond", NSVG_RGB(255, 235, 205) }, { "blueviolet", NSVG_RGB(138, 43, 226) }, { "brown", NSVG_RGB(165, 42, 42) }, { "burlywood", NSVG_RGB(222, 184, 135) }, { "cadetblue", NSVG_RGB( 95, 158, 160) }, { "chartreuse", NSVG_RGB(127, 255, 0) }, { "chocolate", NSVG_RGB(210, 105, 30) }, { "coral", NSVG_RGB(255, 127, 80) }, { "cornflowerblue", NSVG_RGB(100, 149, 237) }, { "cornsilk", NSVG_RGB(255, 248, 220) }, { "crimson", NSVG_RGB(220, 20, 60) }, { "darkblue", NSVG_RGB( 0, 0, 139) }, { "darkcyan", NSVG_RGB( 0, 139, 139) }, { "darkgoldenrod", NSVG_RGB(184, 134, 11) }, { "darkgray", NSVG_RGB(169, 169, 169) }, { "darkgreen", NSVG_RGB( 0, 100, 0) }, { "darkgrey", NSVG_RGB(169, 169, 169) }, { "darkkhaki", NSVG_RGB(189, 183, 107) }, { "darkmagenta", NSVG_RGB(139, 0, 139) }, { "darkolivegreen", NSVG_RGB( 85, 107, 47) }, { "darkorange", NSVG_RGB(255, 140, 0) }, { "darkorchid", NSVG_RGB(153, 50, 204) }, { "darkred", NSVG_RGB(139, 0, 0) }, { "darksalmon", NSVG_RGB(233, 150, 122) }, { "darkseagreen", NSVG_RGB(143, 188, 143) }, { "darkslateblue", NSVG_RGB( 72, 61, 139) }, { "darkslategray", NSVG_RGB( 47, 79, 79) }, { "darkslategrey", NSVG_RGB( 47, 79, 79) }, { "darkturquoise", NSVG_RGB( 0, 206, 209) }, { "darkviolet", NSVG_RGB(148, 0, 211) }, { "deeppink", NSVG_RGB(255, 20, 147) }, { "deepskyblue", NSVG_RGB( 0, 191, 255) }, { "dimgray", NSVG_RGB(105, 105, 105) }, { "dimgrey", NSVG_RGB(105, 105, 105) }, { "dodgerblue", NSVG_RGB( 30, 144, 255) }, { "firebrick", NSVG_RGB(178, 34, 34) }, { "floralwhite", NSVG_RGB(255, 250, 240) }, { "forestgreen", NSVG_RGB( 34, 139, 34) }, { "fuchsia", NSVG_RGB(255, 0, 255) }, { "gainsboro", NSVG_RGB(220, 220, 220) }, { "ghostwhite", NSVG_RGB(248, 248, 255) }, { "gold", NSVG_RGB(255, 215, 0) }, { "goldenrod", NSVG_RGB(218, 165, 32) }, { "greenyellow", NSVG_RGB(173, 255, 47) }, { "honeydew", NSVG_RGB(240, 255, 240) }, { "hotpink", NSVG_RGB(255, 105, 180) }, { "indianred", NSVG_RGB(205, 92, 92) }, { "indigo", NSVG_RGB( 75, 0, 130) }, { "ivory", NSVG_RGB(255, 255, 240) }, { "khaki", NSVG_RGB(240, 230, 140) }, { "lavender", NSVG_RGB(230, 230, 250) }, { "lavenderblush", NSVG_RGB(255, 240, 245) }, { "lawngreen", NSVG_RGB(124, 252, 0) }, { "lemonchiffon", NSVG_RGB(255, 250, 205) }, { "lightblue", NSVG_RGB(173, 216, 230) }, { "lightcoral", NSVG_RGB(240, 128, 128) }, { "lightcyan", NSVG_RGB(224, 255, 255) }, { "lightgoldenrodyellow", NSVG_RGB(250, 250, 210) }, { "lightgray", NSVG_RGB(211, 211, 211) }, { "lightgreen", NSVG_RGB(144, 238, 144) }, { "lightgrey", NSVG_RGB(211, 211, 211) }, { "lightpink", NSVG_RGB(255, 182, 193) }, { "lightsalmon", NSVG_RGB(255, 160, 122) }, { "lightseagreen", NSVG_RGB( 32, 178, 170) }, { "lightskyblue", NSVG_RGB(135, 206, 250) }, { "lightslategray", NSVG_RGB(119, 136, 153) }, { "lightslategrey", NSVG_RGB(119, 136, 153) }, { "lightsteelblue", NSVG_RGB(176, 196, 222) }, { "lightyellow", NSVG_RGB(255, 255, 224) }, { "lime", NSVG_RGB( 0, 255, 0) }, { "limegreen", NSVG_RGB( 50, 205, 50) }, { "linen", NSVG_RGB(250, 240, 230) }, { "maroon", NSVG_RGB(128, 0, 0) }, { "mediumaquamarine", NSVG_RGB(102, 205, 170) }, { "mediumblue", NSVG_RGB( 0, 0, 205) }, { "mediumorchid", NSVG_RGB(186, 85, 211) }, { "mediumpurple", NSVG_RGB(147, 112, 219) }, { "mediumseagreen", NSVG_RGB( 60, 179, 113) }, { "mediumslateblue", NSVG_RGB(123, 104, 238) }, { "mediumspringgreen", NSVG_RGB( 0, 250, 154) }, { "mediumturquoise", NSVG_RGB( 72, 209, 204) }, { "mediumvioletred", NSVG_RGB(199, 21, 133) }, { "midnightblue", NSVG_RGB( 25, 25, 112) }, { "mintcream", NSVG_RGB(245, 255, 250) }, { "mistyrose", NSVG_RGB(255, 228, 225) }, { "moccasin", NSVG_RGB(255, 228, 181) }, { "navajowhite", NSVG_RGB(255, 222, 173) }, { "navy", NSVG_RGB( 0, 0, 128) }, { "oldlace", NSVG_RGB(253, 245, 230) }, { "olive", NSVG_RGB(128, 128, 0) }, { "olivedrab", NSVG_RGB(107, 142, 35) }, { "orange", NSVG_RGB(255, 165, 0) }, { "orangered", NSVG_RGB(255, 69, 0) }, { "orchid", NSVG_RGB(218, 112, 214) }, { "palegoldenrod", NSVG_RGB(238, 232, 170) }, { "palegreen", NSVG_RGB(152, 251, 152) }, { "paleturquoise", NSVG_RGB(175, 238, 238) }, { "palevioletred", NSVG_RGB(219, 112, 147) }, { "papayawhip", NSVG_RGB(255, 239, 213) }, { "peachpuff", NSVG_RGB(255, 218, 185) }, { "peru", NSVG_RGB(205, 133, 63) }, { "pink", NSVG_RGB(255, 192, 203) }, { "plum", NSVG_RGB(221, 160, 221) }, { "powderblue", NSVG_RGB(176, 224, 230) }, { "purple", NSVG_RGB(128, 0, 128) }, { "rosybrown", NSVG_RGB(188, 143, 143) }, { "royalblue", NSVG_RGB( 65, 105, 225) }, { "saddlebrown", NSVG_RGB(139, 69, 19) }, { "salmon", NSVG_RGB(250, 128, 114) }, { "sandybrown", NSVG_RGB(244, 164, 96) }, { "seagreen", NSVG_RGB( 46, 139, 87) }, { "seashell", NSVG_RGB(255, 245, 238) }, { "sienna", NSVG_RGB(160, 82, 45) }, { "silver", NSVG_RGB(192, 192, 192) }, { "skyblue", NSVG_RGB(135, 206, 235) }, { "slateblue", NSVG_RGB(106, 90, 205) }, { "slategray", NSVG_RGB(112, 128, 144) }, { "slategrey", NSVG_RGB(112, 128, 144) }, { "snow", NSVG_RGB(255, 250, 250) }, { "springgreen", NSVG_RGB( 0, 255, 127) }, { "steelblue", NSVG_RGB( 70, 130, 180) }, { "tan", NSVG_RGB(210, 180, 140) }, { "teal", NSVG_RGB( 0, 128, 128) }, { "thistle", NSVG_RGB(216, 191, 216) }, { "tomato", NSVG_RGB(255, 99, 71) }, { "turquoise", NSVG_RGB( 64, 224, 208) }, { "violet", NSVG_RGB(238, 130, 238) }, { "wheat", NSVG_RGB(245, 222, 179) }, { "whitesmoke", NSVG_RGB(245, 245, 245) }, { "yellowgreen", NSVG_RGB(154, 205, 50) }, #endif }; static unsigned int nsvg__parseColorName(const char* str) { int i, ncolors = sizeof(nsvg__colors) / sizeof(NSVGNamedColor); for (i = 0; i < ncolors; i++) { if (strcmp(nsvg__colors[i].name, str) == 0) { return nsvg__colors[i].color; } } return NSVG_RGB(128, 128, 128); } static unsigned int nsvg__parseColor(const char* str) { size_t len = 0; while(*str == ' ') ++str; len = strlen(str); if (len >= 1 && *str == '#') return nsvg__parseColorHex(str); else if (len >= 4 && str[0] == 'r' && str[1] == 'g' && str[2] == 'b' && str[3] == '(') return nsvg__parseColorRGB(str); return nsvg__parseColorName(str); } static float nsvg__parseOpacity(const char* str) { float val = nsvg__atof(str); if (val < 0.0f) val = 0.0f; if (val > 1.0f) val = 1.0f; return val; } static float nsvg__parseMiterLimit(const char* str) { float val = nsvg__atof(str); if (val < 0.0f) val = 0.0f; return val; } static int nsvg__parseUnits(const char* units) { if (units[0] == 'p' && units[1] == 'x') return NSVG_UNITS_PX; else if (units[0] == 'p' && units[1] == 't') return NSVG_UNITS_PT; else if (units[0] == 'p' && units[1] == 'c') return NSVG_UNITS_PC; else if (units[0] == 'm' && units[1] == 'm') return NSVG_UNITS_MM; else if (units[0] == 'c' && units[1] == 'm') return NSVG_UNITS_CM; else if (units[0] == 'i' && units[1] == 'n') return NSVG_UNITS_IN; else if (units[0] == '%') return NSVG_UNITS_PERCENT; else if (units[0] == 'e' && units[1] == 'm') return NSVG_UNITS_EM; else if (units[0] == 'e' && units[1] == 'x') return NSVG_UNITS_EX; return NSVG_UNITS_USER; } static int nsvg__isCoordinate(const char* s) { // optional sign if (*s == '-' || *s == '+') s++; // must have at least one digit, or start by a dot return (nsvg__isdigit(*s) || *s == '.'); } static NSVGcoordinate nsvg__parseCoordinateRaw(const char* str) { NSVGcoordinate coord = {0, NSVG_UNITS_USER}; char buf[64]; coord.units = nsvg__parseUnits(nsvg__parseNumber(str, buf, 64)); coord.value = nsvg__atof(buf); return coord; } static NSVGcoordinate nsvg__coord(float v, int units) { NSVGcoordinate coord = {v, units}; return coord; } static float nsvg__parseCoordinate(NSVGparser* p, const char* str, float orig, float length) { NSVGcoordinate coord = nsvg__parseCoordinateRaw(str); return nsvg__convertToPixels(p, coord, orig, length); } static int nsvg__parseTransformArgs(const char* str, float* args, int maxNa, int* na) { const char* end; const char* ptr; char it[64]; *na = 0; ptr = str; while (*ptr && *ptr != '(') ++ptr; if (*ptr == 0) return 1; end = ptr; while (*end && *end != ')') ++end; if (*end == 0) return 1; while (ptr < end) { if (*ptr == '-' || *ptr == '+' || *ptr == '.' || nsvg__isdigit(*ptr)) { if (*na >= maxNa) return 0; ptr = nsvg__parseNumber(ptr, it, 64); args[(*na)++] = (float)nsvg__atof(it); } else { ++ptr; } } return (int)(end - str); } static int nsvg__parseMatrix(float* xform, const char* str) { float t[6]; int na = 0; int len = nsvg__parseTransformArgs(str, t, 6, &na); if (na != 6) return len; memcpy(xform, t, sizeof(float)*6); return len; } static int nsvg__parseTranslate(float* xform, const char* str) { float args[2]; float t[6]; int na = 0; int len = nsvg__parseTransformArgs(str, args, 2, &na); if (na == 1) args[1] = 0.0; nsvg__xformSetTranslation(t, args[0], args[1]); memcpy(xform, t, sizeof(float)*6); return len; } static int nsvg__parseScale(float* xform, const char* str) { float args[2]; int na = 0; float t[6]; int len = nsvg__parseTransformArgs(str, args, 2, &na); if (na == 1) args[1] = args[0]; nsvg__xformSetScale(t, args[0], args[1]); memcpy(xform, t, sizeof(float)*6); return len; } static int nsvg__parseSkewX(float* xform, const char* str) { float args[1]; int na = 0; float t[6]; int len = nsvg__parseTransformArgs(str, args, 1, &na); nsvg__xformSetSkewX(t, args[0]/180.0f*NSVG_PI); memcpy(xform, t, sizeof(float)*6); return len; } static int nsvg__parseSkewY(float* xform, const char* str) { float args[1]; int na = 0; float t[6]; int len = nsvg__parseTransformArgs(str, args, 1, &na); nsvg__xformSetSkewY(t, args[0]/180.0f*NSVG_PI); memcpy(xform, t, sizeof(float)*6); return len; } static int nsvg__parseRotate(float* xform, const char* str) { float args[3]; int na = 0; float m[6]; float t[6]; int len = nsvg__parseTransformArgs(str, args, 3, &na); if (na == 1) args[1] = args[2] = 0.0f; nsvg__xformIdentity(m); if (na > 1) { nsvg__xformSetTranslation(t, -args[1], -args[2]); nsvg__xformMultiply(m, t); } nsvg__xformSetRotation(t, args[0]/180.0f*NSVG_PI); nsvg__xformMultiply(m, t); if (na > 1) { nsvg__xformSetTranslation(t, args[1], args[2]); nsvg__xformMultiply(m, t); } memcpy(xform, m, sizeof(float)*6); return len; } static void nsvg__parseTransform(float* xform, const char* str) { float t[6]; int len; nsvg__xformIdentity(xform); while (*str) { if (strncmp(str, "matrix", 6) == 0) len = nsvg__parseMatrix(t, str); else if (strncmp(str, "translate", 9) == 0) len = nsvg__parseTranslate(t, str); else if (strncmp(str, "scale", 5) == 0) len = nsvg__parseScale(t, str); else if (strncmp(str, "rotate", 6) == 0) len = nsvg__parseRotate(t, str); else if (strncmp(str, "skewX", 5) == 0) len = nsvg__parseSkewX(t, str); else if (strncmp(str, "skewY", 5) == 0) len = nsvg__parseSkewY(t, str); else{ ++str; continue; } if (len != 0) { str += len; } else { ++str; continue; } nsvg__xformPremultiply(xform, t); } } static void nsvg__parseUrl(char* id, const char* str) { int i = 0; str += 4; // "url("; if (*str && *str == '#') str++; while (i < 63 && *str && *str != ')') { id[i] = *str++; i++; } id[i] = '\0'; } static char nsvg__parseLineCap(const char* str) { if (strcmp(str, "butt") == 0) return NSVG_CAP_BUTT; else if (strcmp(str, "round") == 0) return NSVG_CAP_ROUND; else if (strcmp(str, "square") == 0) return NSVG_CAP_SQUARE; // TODO: handle inherit. return NSVG_CAP_BUTT; } static char nsvg__parseLineJoin(const char* str) { if (strcmp(str, "miter") == 0) return NSVG_JOIN_MITER; else if (strcmp(str, "round") == 0) return NSVG_JOIN_ROUND; else if (strcmp(str, "bevel") == 0) return NSVG_JOIN_BEVEL; // TODO: handle inherit. return NSVG_JOIN_MITER; } static char nsvg__parseFillRule(const char* str) { if (strcmp(str, "nonzero") == 0) return NSVG_FILLRULE_NONZERO; else if (strcmp(str, "evenodd") == 0) return NSVG_FILLRULE_EVENODD; // TODO: handle inherit. return NSVG_FILLRULE_NONZERO; } static unsigned char nsvg__parsePaintOrder(const char* str) { if (strcmp(str, "normal") == 0 || strcmp(str, "fill stroke markers") == 0) return nsvg__encodePaintOrder(NSVG_PAINT_FILL, NSVG_PAINT_STROKE, NSVG_PAINT_MARKERS); else if (strcmp(str, "fill markers stroke") == 0) return nsvg__encodePaintOrder(NSVG_PAINT_FILL, NSVG_PAINT_MARKERS, NSVG_PAINT_STROKE); else if (strcmp(str, "markers fill stroke") == 0) return nsvg__encodePaintOrder(NSVG_PAINT_MARKERS, NSVG_PAINT_FILL, NSVG_PAINT_STROKE); else if (strcmp(str, "markers stroke fill") == 0) return nsvg__encodePaintOrder(NSVG_PAINT_MARKERS, NSVG_PAINT_STROKE, NSVG_PAINT_FILL); else if (strcmp(str, "stroke fill markers") == 0) return nsvg__encodePaintOrder(NSVG_PAINT_STROKE, NSVG_PAINT_FILL, NSVG_PAINT_MARKERS); else if (strcmp(str, "stroke markers fill") == 0) return nsvg__encodePaintOrder(NSVG_PAINT_STROKE, NSVG_PAINT_MARKERS, NSVG_PAINT_FILL); // TODO: handle inherit. return nsvg__encodePaintOrder(NSVG_PAINT_FILL, NSVG_PAINT_STROKE, NSVG_PAINT_MARKERS); } static const char* nsvg__getNextDashItem(const char* s, char* it) { int n = 0; it[0] = '\0'; // Skip white spaces and commas while (*s && (nsvg__isspace(*s) || *s == ',')) s++; // Advance until whitespace, comma or end. while (*s && (!nsvg__isspace(*s) && *s != ',')) { if (n < 63) it[n++] = *s; s++; } it[n++] = '\0'; return s; } static int nsvg__parseStrokeDashArray(NSVGparser* p, const char* str, float* strokeDashArray) { char item[64]; int count = 0, i; float sum = 0.0f; // Handle "none" if (str[0] == 'n') return 0; // Parse dashes while (*str) { str = nsvg__getNextDashItem(str, item); if (!*item) break; if (count < NSVG_MAX_DASHES) strokeDashArray[count++] = fabsf(nsvg__parseCoordinate(p, item, 0.0f, nsvg__actualLength(p))); } for (i = 0; i < count; i++) sum += strokeDashArray[i]; if (sum <= 1e-6f) count = 0; return count; } static void nsvg__parseStyle(NSVGparser* p, const char* str); static int nsvg__parseAttr(NSVGparser* p, const char* name, const char* value) { float xform[6]; NSVGattrib* attr = nsvg__getAttr(p); if (!attr) return 0; if (strcmp(name, "style") == 0) { nsvg__parseStyle(p, value); } else if (strcmp(name, "display") == 0) { if (strcmp(value, "none") == 0) attr->visible = 0; // Don't reset ->visible on display:inline, one display:none hides the whole subtree } else if (strcmp(name, "fill") == 0) { if (strcmp(value, "none") == 0) { attr->hasFill = 0; } else if (strncmp(value, "url(", 4) == 0) { attr->hasFill = 2; nsvg__parseUrl(attr->fillGradient, value); } else { attr->hasFill = 1; attr->fillColor = nsvg__parseColor(value); } } else if (strcmp(name, "opacity") == 0) { attr->opacity = nsvg__parseOpacity(value); } else if (strcmp(name, "fill-opacity") == 0) { attr->fillOpacity = nsvg__parseOpacity(value); } else if (strcmp(name, "stroke") == 0) { if (strcmp(value, "none") == 0) { attr->hasStroke = 0; } else if (strncmp(value, "url(", 4) == 0) { attr->hasStroke = 2; nsvg__parseUrl(attr->strokeGradient, value); } else { attr->hasStroke = 1; attr->strokeColor = nsvg__parseColor(value); } } else if (strcmp(name, "stroke-width") == 0) { attr->strokeWidth = nsvg__parseCoordinate(p, value, 0.0f, nsvg__actualLength(p)); } else if (strcmp(name, "stroke-dasharray") == 0) { attr->strokeDashCount = nsvg__parseStrokeDashArray(p, value, attr->strokeDashArray); } else if (strcmp(name, "stroke-dashoffset") == 0) { attr->strokeDashOffset = nsvg__parseCoordinate(p, value, 0.0f, nsvg__actualLength(p)); } else if (strcmp(name, "stroke-opacity") == 0) { attr->strokeOpacity = nsvg__parseOpacity(value); } else if (strcmp(name, "stroke-linecap") == 0) { attr->strokeLineCap = nsvg__parseLineCap(value); } else if (strcmp(name, "stroke-linejoin") == 0) { attr->strokeLineJoin = nsvg__parseLineJoin(value); } else if (strcmp(name, "stroke-miterlimit") == 0) { attr->miterLimit = nsvg__parseMiterLimit(value); } else if (strcmp(name, "fill-rule") == 0) { attr->fillRule = nsvg__parseFillRule(value); } else if (strcmp(name, "font-size") == 0) { attr->fontSize = nsvg__parseCoordinate(p, value, 0.0f, nsvg__actualLength(p)); } else if (strcmp(name, "transform") == 0) { nsvg__parseTransform(xform, value); nsvg__xformPremultiply(attr->xform, xform); } else if (strcmp(name, "stop-color") == 0) { attr->stopColor = nsvg__parseColor(value); } else if (strcmp(name, "stop-opacity") == 0) { attr->stopOpacity = nsvg__parseOpacity(value); } else if (strcmp(name, "offset") == 0) { attr->stopOffset = nsvg__parseCoordinate(p, value, 0.0f, 1.0f); } else if (strcmp(name, "paint-order") == 0) { attr->paintOrder = nsvg__parsePaintOrder(value); } else if (strcmp(name, "id") == 0) { strncpy(attr->id, value, 63); attr->id[63] = '\0'; } else { return 0; } return 1; } static int nsvg__parseNameValue(NSVGparser* p, const char* start, const char* end) { const char* str; const char* val; char name[512]; char value[512]; int n; str = start; while (str < end && *str != ':') ++str; val = str; // Right Trim while (str > start && (*str == ':' || nsvg__isspace(*str))) --str; ++str; n = (int)(str - start); if (n > 511) n = 511; if (n) memcpy(name, start, n); name[n] = 0; while (val < end && (*val == ':' || nsvg__isspace(*val))) ++val; n = (int)(end - val); if (n > 511) n = 511; if (n) memcpy(value, val, n); value[n] = 0; return nsvg__parseAttr(p, name, value); } static void nsvg__parseStyle(NSVGparser* p, const char* str) { const char* start; const char* end; while (*str) { // Left Trim while(*str && nsvg__isspace(*str)) ++str; start = str; while(*str && *str != ';') ++str; end = str; // Right Trim while (end > start && (*end == ';' || nsvg__isspace(*end))) --end; ++end; nsvg__parseNameValue(p, start, end); if (*str) ++str; } } static void nsvg__parseAttribs(NSVGparser* p, const char** attr) { int i; for (i = 0; attr[i]; i += 2) { if (strcmp(attr[i], "style") == 0) nsvg__parseStyle(p, attr[i + 1]); else nsvg__parseAttr(p, attr[i], attr[i + 1]); } } static int nsvg__getArgsPerElement(char cmd) { switch (cmd) { case 'v': case 'V': case 'h': case 'H': return 1; case 'm': case 'M': case 'l': case 'L': case 't': case 'T': return 2; case 'q': case 'Q': case 's': case 'S': return 4; case 'c': case 'C': return 6; case 'a': case 'A': return 7; case 'z': case 'Z': return 0; } return -1; } static void nsvg__pathMoveTo(NSVGparser* p, float* cpx, float* cpy, float* args, int rel) { if (rel) { *cpx += args[0]; *cpy += args[1]; } else { *cpx = args[0]; *cpy = args[1]; } nsvg__moveTo(p, *cpx, *cpy); } static void nsvg__pathLineTo(NSVGparser* p, float* cpx, float* cpy, float* args, int rel) { if (rel) { *cpx += args[0]; *cpy += args[1]; } else { *cpx = args[0]; *cpy = args[1]; } nsvg__lineTo(p, *cpx, *cpy); } static void nsvg__pathHLineTo(NSVGparser* p, float* cpx, float* cpy, float* args, int rel) { if (rel) *cpx += args[0]; else *cpx = args[0]; nsvg__lineTo(p, *cpx, *cpy); } static void nsvg__pathVLineTo(NSVGparser* p, float* cpx, float* cpy, float* args, int rel) { if (rel) *cpy += args[0]; else *cpy = args[0]; nsvg__lineTo(p, *cpx, *cpy); } static void nsvg__pathCubicBezTo(NSVGparser* p, float* cpx, float* cpy, float* cpx2, float* cpy2, float* args, int rel) { float x2, y2, cx1, cy1, cx2, cy2; if (rel) { cx1 = *cpx + args[0]; cy1 = *cpy + args[1]; cx2 = *cpx + args[2]; cy2 = *cpy + args[3]; x2 = *cpx + args[4]; y2 = *cpy + args[5]; } else { cx1 = args[0]; cy1 = args[1]; cx2 = args[2]; cy2 = args[3]; x2 = args[4]; y2 = args[5]; } nsvg__cubicBezTo(p, cx1,cy1, cx2,cy2, x2,y2); *cpx2 = cx2; *cpy2 = cy2; *cpx = x2; *cpy = y2; } static void nsvg__pathCubicBezShortTo(NSVGparser* p, float* cpx, float* cpy, float* cpx2, float* cpy2, float* args, int rel) { float x1, y1, x2, y2, cx1, cy1, cx2, cy2; x1 = *cpx; y1 = *cpy; if (rel) { cx2 = *cpx + args[0]; cy2 = *cpy + args[1]; x2 = *cpx + args[2]; y2 = *cpy + args[3]; } else { cx2 = args[0]; cy2 = args[1]; x2 = args[2]; y2 = args[3]; } cx1 = 2*x1 - *cpx2; cy1 = 2*y1 - *cpy2; nsvg__cubicBezTo(p, cx1,cy1, cx2,cy2, x2,y2); *cpx2 = cx2; *cpy2 = cy2; *cpx = x2; *cpy = y2; } static void nsvg__pathQuadBezTo(NSVGparser* p, float* cpx, float* cpy, float* cpx2, float* cpy2, float* args, int rel) { float x1, y1, x2, y2, cx, cy; float cx1, cy1, cx2, cy2; x1 = *cpx; y1 = *cpy; if (rel) { cx = *cpx + args[0]; cy = *cpy + args[1]; x2 = *cpx + args[2]; y2 = *cpy + args[3]; } else { cx = args[0]; cy = args[1]; x2 = args[2]; y2 = args[3]; } // Convert to cubic bezier cx1 = x1 + 2.0f/3.0f*(cx - x1); cy1 = y1 + 2.0f/3.0f*(cy - y1); cx2 = x2 + 2.0f/3.0f*(cx - x2); cy2 = y2 + 2.0f/3.0f*(cy - y2); nsvg__cubicBezTo(p, cx1,cy1, cx2,cy2, x2,y2); *cpx2 = cx; *cpy2 = cy; *cpx = x2; *cpy = y2; } static void nsvg__pathQuadBezShortTo(NSVGparser* p, float* cpx, float* cpy, float* cpx2, float* cpy2, float* args, int rel) { float x1, y1, x2, y2, cx, cy; float cx1, cy1, cx2, cy2; x1 = *cpx; y1 = *cpy; if (rel) { x2 = *cpx + args[0]; y2 = *cpy + args[1]; } else { x2 = args[0]; y2 = args[1]; } cx = 2*x1 - *cpx2; cy = 2*y1 - *cpy2; // Convert to cubix bezier cx1 = x1 + 2.0f/3.0f*(cx - x1); cy1 = y1 + 2.0f/3.0f*(cy - y1); cx2 = x2 + 2.0f/3.0f*(cx - x2); cy2 = y2 + 2.0f/3.0f*(cy - y2); nsvg__cubicBezTo(p, cx1,cy1, cx2,cy2, x2,y2); *cpx2 = cx; *cpy2 = cy; *cpx = x2; *cpy = y2; } static float nsvg__sqr(float x) { return x*x; } static float nsvg__vmag(float x, float y) { return sqrtf(x*x + y*y); } static float nsvg__vecrat(float ux, float uy, float vx, float vy) { return (ux*vx + uy*vy) / (nsvg__vmag(ux,uy) * nsvg__vmag(vx,vy)); } static float nsvg__vecang(float ux, float uy, float vx, float vy) { float r = nsvg__vecrat(ux,uy, vx,vy); if (r < -1.0f) r = -1.0f; if (r > 1.0f) r = 1.0f; return ((ux*vy < uy*vx) ? -1.0f : 1.0f) * acosf(r); } static void nsvg__pathArcTo(NSVGparser* p, float* cpx, float* cpy, float* args, int rel) { // Ported from canvg (https://code.google.com/p/canvg/) float rx, ry, rotx; float x1, y1, x2, y2, cx, cy, dx, dy, d; float x1p, y1p, cxp, cyp, s, sa, sb; float ux, uy, vx, vy, a1, da; float x, y, tanx, tany, a, px = 0, py = 0, ptanx = 0, ptany = 0, t[6]; float sinrx, cosrx; int fa, fs; int i, ndivs; float hda, kappa; rx = fabsf(args[0]); // y radius ry = fabsf(args[1]); // x radius rotx = args[2] / 180.0f * NSVG_PI; // x rotation angle fa = fabsf(args[3]) > 1e-6 ? 1 : 0; // Large arc fs = fabsf(args[4]) > 1e-6 ? 1 : 0; // Sweep direction x1 = *cpx; // start point y1 = *cpy; if (rel) { // end point x2 = *cpx + args[5]; y2 = *cpy + args[6]; } else { x2 = args[5]; y2 = args[6]; } dx = x1 - x2; dy = y1 - y2; d = sqrtf(dx*dx + dy*dy); if (d < 1e-6f || rx < 1e-6f || ry < 1e-6f) { // The arc degenerates to a line nsvg__lineTo(p, x2, y2); *cpx = x2; *cpy = y2; return; } sinrx = sinf(rotx); cosrx = cosf(rotx); // Convert to center point parameterization. // http://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes // 1) Compute x1', y1' x1p = cosrx * dx / 2.0f + sinrx * dy / 2.0f; y1p = -sinrx * dx / 2.0f + cosrx * dy / 2.0f; d = nsvg__sqr(x1p)/nsvg__sqr(rx) + nsvg__sqr(y1p)/nsvg__sqr(ry); if (d > 1) { d = sqrtf(d); rx *= d; ry *= d; } // 2) Compute cx', cy' s = 0.0f; sa = nsvg__sqr(rx)*nsvg__sqr(ry) - nsvg__sqr(rx)*nsvg__sqr(y1p) - nsvg__sqr(ry)*nsvg__sqr(x1p); sb = nsvg__sqr(rx)*nsvg__sqr(y1p) + nsvg__sqr(ry)*nsvg__sqr(x1p); if (sa < 0.0f) sa = 0.0f; if (sb > 0.0f) s = sqrtf(sa / sb); if (fa == fs) s = -s; cxp = s * rx * y1p / ry; cyp = s * -ry * x1p / rx; // 3) Compute cx,cy from cx',cy' cx = (x1 + x2)/2.0f + cosrx*cxp - sinrx*cyp; cy = (y1 + y2)/2.0f + sinrx*cxp + cosrx*cyp; // 4) Calculate theta1, and delta theta. ux = (x1p - cxp) / rx; uy = (y1p - cyp) / ry; vx = (-x1p - cxp) / rx; vy = (-y1p - cyp) / ry; a1 = nsvg__vecang(1.0f,0.0f, ux,uy); // Initial angle da = nsvg__vecang(ux,uy, vx,vy); // Delta angle // if (vecrat(ux,uy,vx,vy) <= -1.0f) da = NSVG_PI; // if (vecrat(ux,uy,vx,vy) >= 1.0f) da = 0; if (fs == 0 && da > 0) da -= 2 * NSVG_PI; else if (fs == 1 && da < 0) da += 2 * NSVG_PI; // Approximate the arc using cubic spline segments. t[0] = cosrx; t[1] = sinrx; t[2] = -sinrx; t[3] = cosrx; t[4] = cx; t[5] = cy; // Split arc into max 90 degree segments. // The loop assumes an iteration per end point (including start and end), this +1. ndivs = (int)(fabsf(da) / (NSVG_PI*0.5f) + 1.0f); hda = (da / (float)ndivs) / 2.0f; // Fix for ticket #179: division by 0: avoid cotangens around 0 (infinite) if ((hda < 1e-3f) && (hda > -1e-3f)) hda *= 0.5f; else hda = (1.0f - cosf(hda)) / sinf(hda); kappa = fabsf(4.0f / 3.0f * hda); if (da < 0.0f) kappa = -kappa; for (i = 0; i <= ndivs; i++) { a = a1 + da * ((float)i/(float)ndivs); dx = cosf(a); dy = sinf(a); nsvg__xformPoint(&x, &y, dx*rx, dy*ry, t); // position nsvg__xformVec(&tanx, &tany, -dy*rx * kappa, dx*ry * kappa, t); // tangent if (i > 0) nsvg__cubicBezTo(p, px+ptanx,py+ptany, x-tanx, y-tany, x, y); px = x; py = y; ptanx = tanx; ptany = tany; } *cpx = x2; *cpy = y2; } static void nsvg__parsePath(NSVGparser* p, const char** attr) { const char* s = NULL; char cmd = '\0'; float args[10]; int nargs; int rargs = 0; char initPoint; float cpx, cpy, cpx2, cpy2; const char* tmp[4]; char closedFlag; int i; char item[64]; for (i = 0; attr[i]; i += 2) { if (strcmp(attr[i], "d") == 0) { s = attr[i + 1]; } else { tmp[0] = attr[i]; tmp[1] = attr[i + 1]; tmp[2] = 0; tmp[3] = 0; nsvg__parseAttribs(p, tmp); } } if (s) { nsvg__resetPath(p); cpx = 0; cpy = 0; cpx2 = 0; cpy2 = 0; initPoint = 0; closedFlag = 0; nargs = 0; while (*s) { item[0] = '\0'; if ((cmd == 'A' || cmd == 'a') && (nargs == 3 || nargs == 4)) s = nsvg__getNextPathItemWhenArcFlag(s, item); if (!*item) s = nsvg__getNextPathItem(s, item); if (!*item) break; if (cmd != '\0' && nsvg__isCoordinate(item)) { if (nargs < 10) args[nargs++] = (float)nsvg__atof(item); if (nargs >= rargs) { switch (cmd) { case 'm': case 'M': nsvg__pathMoveTo(p, &cpx, &cpy, args, cmd == 'm' ? 1 : 0); // Moveto can be followed by multiple coordinate pairs, // which should be treated as linetos. cmd = (cmd == 'm') ? 'l' : 'L'; rargs = nsvg__getArgsPerElement(cmd); cpx2 = cpx; cpy2 = cpy; initPoint = 1; break; case 'l': case 'L': nsvg__pathLineTo(p, &cpx, &cpy, args, cmd == 'l' ? 1 : 0); cpx2 = cpx; cpy2 = cpy; break; case 'H': case 'h': nsvg__pathHLineTo(p, &cpx, &cpy, args, cmd == 'h' ? 1 : 0); cpx2 = cpx; cpy2 = cpy; break; case 'V': case 'v': nsvg__pathVLineTo(p, &cpx, &cpy, args, cmd == 'v' ? 1 : 0); cpx2 = cpx; cpy2 = cpy; break; case 'C': case 'c': nsvg__pathCubicBezTo(p, &cpx, &cpy, &cpx2, &cpy2, args, cmd == 'c' ? 1 : 0); break; case 'S': case 's': nsvg__pathCubicBezShortTo(p, &cpx, &cpy, &cpx2, &cpy2, args, cmd == 's' ? 1 : 0); break; case 'Q': case 'q': nsvg__pathQuadBezTo(p, &cpx, &cpy, &cpx2, &cpy2, args, cmd == 'q' ? 1 : 0); break; case 'T': case 't': nsvg__pathQuadBezShortTo(p, &cpx, &cpy, &cpx2, &cpy2, args, cmd == 't' ? 1 : 0); break; case 'A': case 'a': nsvg__pathArcTo(p, &cpx, &cpy, args, cmd == 'a' ? 1 : 0); cpx2 = cpx; cpy2 = cpy; break; default: if (nargs >= 2) { cpx = args[nargs-2]; cpy = args[nargs-1]; cpx2 = cpx; cpy2 = cpy; } break; } nargs = 0; } } else { cmd = item[0]; if (cmd == 'M' || cmd == 'm') { // Commit path. if (p->npts > 0) nsvg__addPath(p, closedFlag); // Start new subpath. nsvg__resetPath(p); closedFlag = 0; nargs = 0; } else if (initPoint == 0) { // Do not allow other commands until initial point has been set (moveTo called once). cmd = '\0'; } if (cmd == 'Z' || cmd == 'z') { closedFlag = 1; // Commit path. if (p->npts > 0) { // Move current point to first point cpx = p->pts[0]; cpy = p->pts[1]; cpx2 = cpx; cpy2 = cpy; nsvg__addPath(p, closedFlag); } // Start new subpath. nsvg__resetPath(p); nsvg__moveTo(p, cpx, cpy); closedFlag = 0; nargs = 0; } rargs = nsvg__getArgsPerElement(cmd); if (rargs == -1) { // Command not recognized cmd = '\0'; rargs = 0; } } } // Commit path. if (p->npts) nsvg__addPath(p, closedFlag); } nsvg__addShape(p); } static void nsvg__parseRect(NSVGparser* p, const char** attr) { float x = 0.0f; float y = 0.0f; float w = 0.0f; float h = 0.0f; float rx = -1.0f; // marks not set float ry = -1.0f; int i; for (i = 0; attr[i]; i += 2) { if (!nsvg__parseAttr(p, attr[i], attr[i + 1])) { if (strcmp(attr[i], "x") == 0) x = nsvg__parseCoordinate(p, attr[i+1], nsvg__actualOrigX(p), nsvg__actualWidth(p)); if (strcmp(attr[i], "y") == 0) y = nsvg__parseCoordinate(p, attr[i+1], nsvg__actualOrigY(p), nsvg__actualHeight(p)); if (strcmp(attr[i], "width") == 0) w = nsvg__parseCoordinate(p, attr[i+1], 0.0f, nsvg__actualWidth(p)); if (strcmp(attr[i], "height") == 0) h = nsvg__parseCoordinate(p, attr[i+1], 0.0f, nsvg__actualHeight(p)); if (strcmp(attr[i], "rx") == 0) rx = fabsf(nsvg__parseCoordinate(p, attr[i+1], 0.0f, nsvg__actualWidth(p))); if (strcmp(attr[i], "ry") == 0) ry = fabsf(nsvg__parseCoordinate(p, attr[i+1], 0.0f, nsvg__actualHeight(p))); } } if (rx < 0.0f && ry > 0.0f) rx = ry; if (ry < 0.0f && rx > 0.0f) ry = rx; if (rx < 0.0f) rx = 0.0f; if (ry < 0.0f) ry = 0.0f; if (rx > w/2.0f) rx = w/2.0f; if (ry > h/2.0f) ry = h/2.0f; if (w != 0.0f && h != 0.0f) { nsvg__resetPath(p); if (rx < 0.00001f || ry < 0.0001f) { nsvg__moveTo(p, x, y); nsvg__lineTo(p, x+w, y); nsvg__lineTo(p, x+w, y+h); nsvg__lineTo(p, x, y+h); } else { // Rounded rectangle nsvg__moveTo(p, x+rx, y); nsvg__lineTo(p, x+w-rx, y); nsvg__cubicBezTo(p, x+w-rx*(1-NSVG_KAPPA90), y, x+w, y+ry*(1-NSVG_KAPPA90), x+w, y+ry); nsvg__lineTo(p, x+w, y+h-ry); nsvg__cubicBezTo(p, x+w, y+h-ry*(1-NSVG_KAPPA90), x+w-rx*(1-NSVG_KAPPA90), y+h, x+w-rx, y+h); nsvg__lineTo(p, x+rx, y+h); nsvg__cubicBezTo(p, x+rx*(1-NSVG_KAPPA90), y+h, x, y+h-ry*(1-NSVG_KAPPA90), x, y+h-ry); nsvg__lineTo(p, x, y+ry); nsvg__cubicBezTo(p, x, y+ry*(1-NSVG_KAPPA90), x+rx*(1-NSVG_KAPPA90), y, x+rx, y); } nsvg__addPath(p, 1); nsvg__addShape(p); } } static void nsvg__parseCircle(NSVGparser* p, const char** attr) { float cx = 0.0f; float cy = 0.0f; float r = 0.0f; int i; for (i = 0; attr[i]; i += 2) { if (!nsvg__parseAttr(p, attr[i], attr[i + 1])) { if (strcmp(attr[i], "cx") == 0) cx = nsvg__parseCoordinate(p, attr[i+1], nsvg__actualOrigX(p), nsvg__actualWidth(p)); if (strcmp(attr[i], "cy") == 0) cy = nsvg__parseCoordinate(p, attr[i+1], nsvg__actualOrigY(p), nsvg__actualHeight(p)); if (strcmp(attr[i], "r") == 0) r = fabsf(nsvg__parseCoordinate(p, attr[i+1], 0.0f, nsvg__actualLength(p))); } } if (r > 0.0f) { nsvg__resetPath(p); nsvg__moveTo(p, cx+r, cy); nsvg__cubicBezTo(p, cx+r, cy+r*NSVG_KAPPA90, cx+r*NSVG_KAPPA90, cy+r, cx, cy+r); nsvg__cubicBezTo(p, cx-r*NSVG_KAPPA90, cy+r, cx-r, cy+r*NSVG_KAPPA90, cx-r, cy); nsvg__cubicBezTo(p, cx-r, cy-r*NSVG_KAPPA90, cx-r*NSVG_KAPPA90, cy-r, cx, cy-r); nsvg__cubicBezTo(p, cx+r*NSVG_KAPPA90, cy-r, cx+r, cy-r*NSVG_KAPPA90, cx+r, cy); nsvg__addPath(p, 1); nsvg__addShape(p); } } static void nsvg__parseEllipse(NSVGparser* p, const char** attr) { float cx = 0.0f; float cy = 0.0f; float rx = 0.0f; float ry = 0.0f; int i; for (i = 0; attr[i]; i += 2) { if (!nsvg__parseAttr(p, attr[i], attr[i + 1])) { if (strcmp(attr[i], "cx") == 0) cx = nsvg__parseCoordinate(p, attr[i+1], nsvg__actualOrigX(p), nsvg__actualWidth(p)); if (strcmp(attr[i], "cy") == 0) cy = nsvg__parseCoordinate(p, attr[i+1], nsvg__actualOrigY(p), nsvg__actualHeight(p)); if (strcmp(attr[i], "rx") == 0) rx = fabsf(nsvg__parseCoordinate(p, attr[i+1], 0.0f, nsvg__actualWidth(p))); if (strcmp(attr[i], "ry") == 0) ry = fabsf(nsvg__parseCoordinate(p, attr[i+1], 0.0f, nsvg__actualHeight(p))); } } if (rx > 0.0f && ry > 0.0f) { nsvg__resetPath(p); nsvg__moveTo(p, cx+rx, cy); nsvg__cubicBezTo(p, cx+rx, cy+ry*NSVG_KAPPA90, cx+rx*NSVG_KAPPA90, cy+ry, cx, cy+ry); nsvg__cubicBezTo(p, cx-rx*NSVG_KAPPA90, cy+ry, cx-rx, cy+ry*NSVG_KAPPA90, cx-rx, cy); nsvg__cubicBezTo(p, cx-rx, cy-ry*NSVG_KAPPA90, cx-rx*NSVG_KAPPA90, cy-ry, cx, cy-ry); nsvg__cubicBezTo(p, cx+rx*NSVG_KAPPA90, cy-ry, cx+rx, cy-ry*NSVG_KAPPA90, cx+rx, cy); nsvg__addPath(p, 1); nsvg__addShape(p); } } static void nsvg__parseLine(NSVGparser* p, const char** attr) { float x1 = 0.0; float y1 = 0.0; float x2 = 0.0; float y2 = 0.0; int i; for (i = 0; attr[i]; i += 2) { if (!nsvg__parseAttr(p, attr[i], attr[i + 1])) { if (strcmp(attr[i], "x1") == 0) x1 = nsvg__parseCoordinate(p, attr[i + 1], nsvg__actualOrigX(p), nsvg__actualWidth(p)); if (strcmp(attr[i], "y1") == 0) y1 = nsvg__parseCoordinate(p, attr[i + 1], nsvg__actualOrigY(p), nsvg__actualHeight(p)); if (strcmp(attr[i], "x2") == 0) x2 = nsvg__parseCoordinate(p, attr[i + 1], nsvg__actualOrigX(p), nsvg__actualWidth(p)); if (strcmp(attr[i], "y2") == 0) y2 = nsvg__parseCoordinate(p, attr[i + 1], nsvg__actualOrigY(p), nsvg__actualHeight(p)); } } nsvg__resetPath(p); nsvg__moveTo(p, x1, y1); nsvg__lineTo(p, x2, y2); nsvg__addPath(p, 0); nsvg__addShape(p); } static void nsvg__parsePoly(NSVGparser* p, const char** attr, int closeFlag) { int i; const char* s; float args[2]; int nargs, npts = 0; char item[64]; nsvg__resetPath(p); for (i = 0; attr[i]; i += 2) { if (!nsvg__parseAttr(p, attr[i], attr[i + 1])) { if (strcmp(attr[i], "points") == 0) { s = attr[i + 1]; nargs = 0; while (*s) { s = nsvg__getNextPathItem(s, item); args[nargs++] = (float)nsvg__atof(item); if (nargs >= 2) { if (npts == 0) nsvg__moveTo(p, args[0], args[1]); else nsvg__lineTo(p, args[0], args[1]); nargs = 0; npts++; } } } } } nsvg__addPath(p, (char)closeFlag); nsvg__addShape(p); } static void nsvg__parseSVG(NSVGparser* p, const char** attr) { int i; for (i = 0; attr[i]; i += 2) { if (!nsvg__parseAttr(p, attr[i], attr[i + 1])) { if (strcmp(attr[i], "width") == 0) { p->image->width = nsvg__parseCoordinate(p, attr[i + 1], 0.0f, 0.0f); } else if (strcmp(attr[i], "height") == 0) { p->image->height = nsvg__parseCoordinate(p, attr[i + 1], 0.0f, 0.0f); } else if (strcmp(attr[i], "viewBox") == 0) { const char *s = attr[i + 1]; char buf[64]; s = nsvg__parseNumber(s, buf, 64); p->viewMinx = nsvg__atof(buf); while (*s && (nsvg__isspace(*s) || *s == '%' || *s == ',')) s++; if (!*s) return; s = nsvg__parseNumber(s, buf, 64); p->viewMiny = nsvg__atof(buf); while (*s && (nsvg__isspace(*s) || *s == '%' || *s == ',')) s++; if (!*s) return; s = nsvg__parseNumber(s, buf, 64); p->viewWidth = nsvg__atof(buf); while (*s && (nsvg__isspace(*s) || *s == '%' || *s == ',')) s++; if (!*s) return; s = nsvg__parseNumber(s, buf, 64); p->viewHeight = nsvg__atof(buf); } else if (strcmp(attr[i], "preserveAspectRatio") == 0) { if (strstr(attr[i + 1], "none") != 0) { // No uniform scaling p->alignType = NSVG_ALIGN_NONE; } else { // Parse X align if (strstr(attr[i + 1], "xMin") != 0) p->alignX = NSVG_ALIGN_MIN; else if (strstr(attr[i + 1], "xMid") != 0) p->alignX = NSVG_ALIGN_MID; else if (strstr(attr[i + 1], "xMax") != 0) p->alignX = NSVG_ALIGN_MAX; // Parse X align if (strstr(attr[i + 1], "yMin") != 0) p->alignY = NSVG_ALIGN_MIN; else if (strstr(attr[i + 1], "yMid") != 0) p->alignY = NSVG_ALIGN_MID; else if (strstr(attr[i + 1], "yMax") != 0) p->alignY = NSVG_ALIGN_MAX; // Parse meet/slice p->alignType = NSVG_ALIGN_MEET; if (strstr(attr[i + 1], "slice") != 0) p->alignType = NSVG_ALIGN_SLICE; } } } } } static void nsvg__parseGradient(NSVGparser* p, const char** attr, signed char type) { int i; NSVGgradientData* grad = (NSVGgradientData*)malloc(sizeof(NSVGgradientData)); if (grad == NULL) return; memset(grad, 0, sizeof(NSVGgradientData)); grad->units = NSVG_OBJECT_SPACE; grad->type = type; if (grad->type == NSVG_PAINT_LINEAR_GRADIENT) { grad->linear.x1 = nsvg__coord(0.0f, NSVG_UNITS_PERCENT); grad->linear.y1 = nsvg__coord(0.0f, NSVG_UNITS_PERCENT); grad->linear.x2 = nsvg__coord(100.0f, NSVG_UNITS_PERCENT); grad->linear.y2 = nsvg__coord(0.0f, NSVG_UNITS_PERCENT); } else if (grad->type == NSVG_PAINT_RADIAL_GRADIENT) { grad->radial.cx = nsvg__coord(50.0f, NSVG_UNITS_PERCENT); grad->radial.cy = nsvg__coord(50.0f, NSVG_UNITS_PERCENT); grad->radial.r = nsvg__coord(50.0f, NSVG_UNITS_PERCENT); } nsvg__xformIdentity(grad->xform); for (i = 0; attr[i]; i += 2) { if (strcmp(attr[i], "id") == 0) { strncpy(grad->id, attr[i+1], 63); grad->id[63] = '\0'; } else if (!nsvg__parseAttr(p, attr[i], attr[i + 1])) { if (strcmp(attr[i], "gradientUnits") == 0) { if (strcmp(attr[i+1], "objectBoundingBox") == 0) grad->units = NSVG_OBJECT_SPACE; else grad->units = NSVG_USER_SPACE; } else if (strcmp(attr[i], "gradientTransform") == 0) { nsvg__parseTransform(grad->xform, attr[i + 1]); } else if (strcmp(attr[i], "cx") == 0) { grad->radial.cx = nsvg__parseCoordinateRaw(attr[i + 1]); } else if (strcmp(attr[i], "cy") == 0) { grad->radial.cy = nsvg__parseCoordinateRaw(attr[i + 1]); } else if (strcmp(attr[i], "r") == 0) { grad->radial.r = nsvg__parseCoordinateRaw(attr[i + 1]); } else if (strcmp(attr[i], "fx") == 0) { grad->radial.fx = nsvg__parseCoordinateRaw(attr[i + 1]); } else if (strcmp(attr[i], "fy") == 0) { grad->radial.fy = nsvg__parseCoordinateRaw(attr[i + 1]); } else if (strcmp(attr[i], "x1") == 0) { grad->linear.x1 = nsvg__parseCoordinateRaw(attr[i + 1]); } else if (strcmp(attr[i], "y1") == 0) { grad->linear.y1 = nsvg__parseCoordinateRaw(attr[i + 1]); } else if (strcmp(attr[i], "x2") == 0) { grad->linear.x2 = nsvg__parseCoordinateRaw(attr[i + 1]); } else if (strcmp(attr[i], "y2") == 0) { grad->linear.y2 = nsvg__parseCoordinateRaw(attr[i + 1]); } else if (strcmp(attr[i], "spreadMethod") == 0) { if (strcmp(attr[i+1], "pad") == 0) grad->spread = NSVG_SPREAD_PAD; else if (strcmp(attr[i+1], "reflect") == 0) grad->spread = NSVG_SPREAD_REFLECT; else if (strcmp(attr[i+1], "repeat") == 0) grad->spread = NSVG_SPREAD_REPEAT; } else if (strcmp(attr[i], "xlink:href") == 0) { const char *href = attr[i+1]; strncpy(grad->ref, href+1, 62); grad->ref[62] = '\0'; } } } grad->next = p->gradients; p->gradients = grad; } static void nsvg__parseGradientStop(NSVGparser* p, const char** attr) { NSVGattrib* curAttr = nsvg__getAttr(p); NSVGgradientData* grad; NSVGgradientStop* stop; int i, idx; curAttr->stopOffset = 0; curAttr->stopColor = 0; curAttr->stopOpacity = 1.0f; for (i = 0; attr[i]; i += 2) { nsvg__parseAttr(p, attr[i], attr[i + 1]); } // Add stop to the last gradient. grad = p->gradients; if (grad == NULL) return; grad->nstops++; grad->stops = (NSVGgradientStop*)realloc(grad->stops, sizeof(NSVGgradientStop)*grad->nstops); if (grad->stops == NULL) return; // Insert idx = grad->nstops-1; for (i = 0; i < grad->nstops-1; i++) { if (curAttr->stopOffset < grad->stops[i].offset) { idx = i; break; } } if (idx != grad->nstops-1) { for (i = grad->nstops-1; i > idx; i--) grad->stops[i] = grad->stops[i-1]; } stop = &grad->stops[idx]; stop->color = curAttr->stopColor; stop->color |= (unsigned int)(curAttr->stopOpacity*255) << 24; stop->offset = curAttr->stopOffset; } static void nsvg__startElement(void* ud, const char* el, const char** attr) { NSVGparser* p = (NSVGparser*)ud; if (p->defsFlag) { // Skip everything but gradients in defs if (strcmp(el, "linearGradient") == 0) { nsvg__parseGradient(p, attr, NSVG_PAINT_LINEAR_GRADIENT); } else if (strcmp(el, "radialGradient") == 0) { nsvg__parseGradient(p, attr, NSVG_PAINT_RADIAL_GRADIENT); } else if (strcmp(el, "stop") == 0) { nsvg__parseGradientStop(p, attr); } return; } if (strcmp(el, "g") == 0) { nsvg__pushAttr(p); nsvg__parseAttribs(p, attr); } else if (strcmp(el, "path") == 0) { if (p->pathFlag) // Do not allow nested paths. return; nsvg__pushAttr(p); nsvg__parsePath(p, attr); nsvg__popAttr(p); } else if (strcmp(el, "rect") == 0) { nsvg__pushAttr(p); nsvg__parseRect(p, attr); nsvg__popAttr(p); } else if (strcmp(el, "circle") == 0) { nsvg__pushAttr(p); nsvg__parseCircle(p, attr); nsvg__popAttr(p); } else if (strcmp(el, "ellipse") == 0) { nsvg__pushAttr(p); nsvg__parseEllipse(p, attr); nsvg__popAttr(p); } else if (strcmp(el, "line") == 0) { nsvg__pushAttr(p); nsvg__parseLine(p, attr); nsvg__popAttr(p); } else if (strcmp(el, "polyline") == 0) { nsvg__pushAttr(p); nsvg__parsePoly(p, attr, 0); nsvg__popAttr(p); } else if (strcmp(el, "polygon") == 0) { nsvg__pushAttr(p); nsvg__parsePoly(p, attr, 1); nsvg__popAttr(p); } else if (strcmp(el, "linearGradient") == 0) { nsvg__parseGradient(p, attr, NSVG_PAINT_LINEAR_GRADIENT); } else if (strcmp(el, "radialGradient") == 0) { nsvg__parseGradient(p, attr, NSVG_PAINT_RADIAL_GRADIENT); } else if (strcmp(el, "stop") == 0) { nsvg__parseGradientStop(p, attr); } else if (strcmp(el, "defs") == 0) { p->defsFlag = 1; } else if (strcmp(el, "svg") == 0) { nsvg__parseSVG(p, attr); } } static void nsvg__endElement(void* ud, const char* el) { NSVGparser* p = (NSVGparser*)ud; if (strcmp(el, "g") == 0) { nsvg__popAttr(p); } else if (strcmp(el, "path") == 0) { p->pathFlag = 0; } else if (strcmp(el, "defs") == 0) { p->defsFlag = 0; } } static void nsvg__content(void* ud, const char* s) { NSVG_NOTUSED(ud); NSVG_NOTUSED(s); // empty } static void nsvg__imageBounds(NSVGparser* p, float* bounds) { NSVGshape* shape; shape = p->image->shapes; if (shape == NULL) { bounds[0] = bounds[1] = bounds[2] = bounds[3] = 0.0; return; } bounds[0] = shape->bounds[0]; bounds[1] = shape->bounds[1]; bounds[2] = shape->bounds[2]; bounds[3] = shape->bounds[3]; for (shape = shape->next; shape != NULL; shape = shape->next) { bounds[0] = nsvg__minf(bounds[0], shape->bounds[0]); bounds[1] = nsvg__minf(bounds[1], shape->bounds[1]); bounds[2] = nsvg__maxf(bounds[2], shape->bounds[2]); bounds[3] = nsvg__maxf(bounds[3], shape->bounds[3]); } } static float nsvg__viewAlign(float content, float container, int type) { if (type == NSVG_ALIGN_MIN) return 0; else if (type == NSVG_ALIGN_MAX) return container - content; // mid return (container - content) * 0.5f; } static void nsvg__scaleGradient(NSVGgradient* grad, float tx, float ty, float sx, float sy) { float t[6]; nsvg__xformSetTranslation(t, tx, ty); nsvg__xformMultiply (grad->xform, t); nsvg__xformSetScale(t, sx, sy); nsvg__xformMultiply (grad->xform, t); } static void nsvg__scaleToViewbox(NSVGparser* p, const char* units) { NSVGshape* shape; NSVGpath* path; float tx, ty, sx, sy, us, bounds[4], t[6], avgs; int i; float* pt; // Guess image size if not set completely. nsvg__imageBounds(p, bounds); if (p->viewWidth == 0) { if (p->image->width > 0) { p->viewWidth = p->image->width; } else { p->viewMinx = bounds[0]; p->viewWidth = bounds[2] - bounds[0]; } } if (p->viewHeight == 0) { if (p->image->height > 0) { p->viewHeight = p->image->height; } else { p->viewMiny = bounds[1]; p->viewHeight = bounds[3] - bounds[1]; } } if (p->image->width == 0) p->image->width = p->viewWidth; if (p->image->height == 0) p->image->height = p->viewHeight; tx = -p->viewMinx; ty = -p->viewMiny; sx = p->viewWidth > 0 ? p->image->width / p->viewWidth : 0; sy = p->viewHeight > 0 ? p->image->height / p->viewHeight : 0; // Unit scaling us = 1.0f / nsvg__convertToPixels(p, nsvg__coord(1.0f, nsvg__parseUnits(units)), 0.0f, 1.0f); // Fix aspect ratio if (p->alignType == NSVG_ALIGN_MEET) { // fit whole image into viewbox sx = sy = nsvg__minf(sx, sy); tx += nsvg__viewAlign(p->viewWidth*sx, p->image->width, p->alignX) / sx; ty += nsvg__viewAlign(p->viewHeight*sy, p->image->height, p->alignY) / sy; } else if (p->alignType == NSVG_ALIGN_SLICE) { // fill whole viewbox with image sx = sy = nsvg__maxf(sx, sy); tx += nsvg__viewAlign(p->viewWidth*sx, p->image->width, p->alignX) / sx; ty += nsvg__viewAlign(p->viewHeight*sy, p->image->height, p->alignY) / sy; } // Transform sx *= us; sy *= us; avgs = (sx+sy) / 2.0f; for (shape = p->image->shapes; shape != NULL; shape = shape->next) { shape->bounds[0] = (shape->bounds[0] + tx) * sx; shape->bounds[1] = (shape->bounds[1] + ty) * sy; shape->bounds[2] = (shape->bounds[2] + tx) * sx; shape->bounds[3] = (shape->bounds[3] + ty) * sy; for (path = shape->paths; path != NULL; path = path->next) { path->bounds[0] = (path->bounds[0] + tx) * sx; path->bounds[1] = (path->bounds[1] + ty) * sy; path->bounds[2] = (path->bounds[2] + tx) * sx; path->bounds[3] = (path->bounds[3] + ty) * sy; for (i =0; i < path->npts; i++) { pt = &path->pts[i*2]; pt[0] = (pt[0] + tx) * sx; pt[1] = (pt[1] + ty) * sy; } } if (shape->fill.type == NSVG_PAINT_LINEAR_GRADIENT || shape->fill.type == NSVG_PAINT_RADIAL_GRADIENT) { nsvg__scaleGradient(shape->fill.gradient, tx,ty, sx,sy); memcpy(t, shape->fill.gradient->xform, sizeof(float)*6); nsvg__xformInverse(shape->fill.gradient->xform, t); } if (shape->stroke.type == NSVG_PAINT_LINEAR_GRADIENT || shape->stroke.type == NSVG_PAINT_RADIAL_GRADIENT) { nsvg__scaleGradient(shape->stroke.gradient, tx,ty, sx,sy); memcpy(t, shape->stroke.gradient->xform, sizeof(float)*6); nsvg__xformInverse(shape->stroke.gradient->xform, t); } shape->strokeWidth *= avgs; shape->strokeDashOffset *= avgs; for (i = 0; i < shape->strokeDashCount; i++) shape->strokeDashArray[i] *= avgs; } } static void nsvg__createGradients(NSVGparser* p) { NSVGshape* shape; for (shape = p->image->shapes; shape != NULL; shape = shape->next) { if (shape->fill.type == NSVG_PAINT_UNDEF) { if (shape->fillGradient[0] != '\0') { float inv[6], localBounds[4]; nsvg__xformInverse(inv, shape->xform); nsvg__getLocalBounds(localBounds, shape, inv); shape->fill.gradient = nsvg__createGradient(p, shape->fillGradient, localBounds, shape->xform, &shape->fill.type); } if (shape->fill.type == NSVG_PAINT_UNDEF) { shape->fill.type = NSVG_PAINT_NONE; } } if (shape->stroke.type == NSVG_PAINT_UNDEF) { if (shape->strokeGradient[0] != '\0') { float inv[6], localBounds[4]; nsvg__xformInverse(inv, shape->xform); nsvg__getLocalBounds(localBounds, shape, inv); shape->stroke.gradient = nsvg__createGradient(p, shape->strokeGradient, localBounds, shape->xform, &shape->stroke.type); } if (shape->stroke.type == NSVG_PAINT_UNDEF) { shape->stroke.type = NSVG_PAINT_NONE; } } } } NSVGimage* nsvgParse(char* input, const char* units, float dpi) { NSVGparser* p; NSVGimage* ret = 0; p = nsvg__createParser(); if (p == NULL) { return NULL; } p->dpi = dpi; nsvg__parseXML(input, nsvg__startElement, nsvg__endElement, nsvg__content, p); // Create gradients after all definitions have been parsed nsvg__createGradients(p); // Scale to viewBox nsvg__scaleToViewbox(p, units); ret = p->image; p->image = NULL; nsvg__deleteParser(p); return ret; } NSVGimage* nsvgParseFromFile(const char* filename, const char* units, float dpi) { FILE* fp = NULL; size_t size; char* data = NULL; NSVGimage* image = NULL; fp = fopen(filename, "rb"); if (!fp) goto error; fseek(fp, 0, SEEK_END); size = ftell(fp); fseek(fp, 0, SEEK_SET); data = (char*)malloc(size+1); if (data == NULL) goto error; if (fread(data, 1, size, fp) != size) goto error; data[size] = '\0'; // Must be null terminated. fclose(fp); image = nsvgParse(data, units, dpi); free(data); return image; error: if (fp) fclose(fp); if (data) free(data); if (image) nsvgDelete(image); return NULL; } NSVGpath* nsvgDuplicatePath(NSVGpath* p) { NSVGpath* res = NULL; if (p == NULL) return NULL; res = (NSVGpath*)malloc(sizeof(NSVGpath)); if (res == NULL) goto error; memset(res, 0, sizeof(NSVGpath)); res->pts = (float*)malloc(p->npts*2*sizeof(float)); if (res->pts == NULL) goto error; memcpy(res->pts, p->pts, p->npts * sizeof(float) * 2); res->npts = p->npts; memcpy(res->bounds, p->bounds, sizeof(p->bounds)); res->closed = p->closed; return res; error: if (res != NULL) { free(res->pts); free(res); } return NULL; } void nsvgDelete(NSVGimage* image) { NSVGshape *snext, *shape; if (image == NULL) return; shape = image->shapes; while (shape != NULL) { snext = shape->next; nsvg__deletePaths(shape->paths); nsvg__deletePaint(&shape->fill); nsvg__deletePaint(&shape->stroke); free(shape); shape = snext; } free(image); } #endif // NANOSVG_IMPLEMENTATION #endif // NANOSVG_H ================================================ FILE: external/nanosvgrast.h ================================================ /* * Copyright (c) 2013-14 Mikko Mononen memon@inside.org * * This software is provided 'as-is', without any express or implied * warranty. In no event will the authors be held liable for any damages * arising from the use of this software. * * Permission is granted to anyone to use this software for any purpose, * including commercial applications, and to alter it and redistribute it * freely, subject to the following restrictions: * * 1. The origin of this software must not be misrepresented; you must not * claim that you wrote the original software. If you use this software * in a product, an acknowledgment in the product documentation would be * appreciated but is not required. * 2. Altered source versions must be plainly marked as such, and must not be * misrepresented as being the original software. * 3. This notice may not be removed or altered from any source distribution. * * The polygon rasterization is heavily based on stb_truetype rasterizer * by Sean Barrett - http://nothings.org/ * */ #ifndef NANOSVGRAST_H #define NANOSVGRAST_H #include "nanosvg.h" #ifndef NANOSVGRAST_CPLUSPLUS #ifdef __cplusplus extern "C" { #endif #endif typedef struct NSVGrasterizer NSVGrasterizer; /* Example Usage: // Load SVG NSVGimage* image; image = nsvgParseFromFile("test.svg", "px", 96); // Create rasterizer (can be used to render multiple images). struct NSVGrasterizer* rast = nsvgCreateRasterizer(); // Allocate memory for image unsigned char* img = malloc(w*h*4); // Rasterize nsvgRasterize(rast, image, 0,0,1, img, w, h, w*4); */ // Allocated rasterizer context. NSVGrasterizer* nsvgCreateRasterizer(void); // Rasterizes SVG image, returns RGBA image (non-premultiplied alpha) // r - pointer to rasterizer context // image - pointer to image to rasterize // tx,ty - image offset (applied after scaling) // scale - image scale // dst - pointer to destination image data, 4 bytes per pixel (RGBA) // w - width of the image to render // h - height of the image to render // stride - number of bytes per scaleline in the destination buffer void nsvgRasterize(NSVGrasterizer* r, NSVGimage* image, float tx, float ty, float scale, unsigned char* dst, int w, int h, int stride); // Deletes rasterizer context. void nsvgDeleteRasterizer(NSVGrasterizer*); #ifndef NANOSVGRAST_CPLUSPLUS #ifdef __cplusplus } #endif #endif #ifdef NANOSVGRAST_IMPLEMENTATION #include #include #include #define NSVG__SUBSAMPLES 5 #define NSVG__FIXSHIFT 10 #define NSVG__FIX (1 << NSVG__FIXSHIFT) #define NSVG__FIXMASK (NSVG__FIX-1) #define NSVG__MEMPAGE_SIZE 1024 typedef struct NSVGedge { float x0,y0, x1,y1; int dir; struct NSVGedge* next; } NSVGedge; typedef struct NSVGpoint { float x, y; float dx, dy; float len; float dmx, dmy; unsigned char flags; } NSVGpoint; typedef struct NSVGactiveEdge { int x,dx; float ey; int dir; struct NSVGactiveEdge *next; } NSVGactiveEdge; typedef struct NSVGmemPage { unsigned char mem[NSVG__MEMPAGE_SIZE]; int size; struct NSVGmemPage* next; } NSVGmemPage; typedef struct NSVGcachedPaint { signed char type; char spread; float xform[6]; unsigned int colors[256]; } NSVGcachedPaint; struct NSVGrasterizer { float px, py; float tessTol; float distTol; NSVGedge* edges; int nedges; int cedges; NSVGpoint* points; int npoints; int cpoints; NSVGpoint* points2; int npoints2; int cpoints2; NSVGactiveEdge* freelist; NSVGmemPage* pages; NSVGmemPage* curpage; unsigned char* scanline; int cscanline; unsigned char* bitmap; int width, height, stride; }; NSVGrasterizer* nsvgCreateRasterizer(void) { NSVGrasterizer* r = (NSVGrasterizer*)malloc(sizeof(NSVGrasterizer)); if (r == NULL) goto error; memset(r, 0, sizeof(NSVGrasterizer)); r->tessTol = 0.25f; r->distTol = 0.01f; return r; error: nsvgDeleteRasterizer(r); return NULL; } void nsvgDeleteRasterizer(NSVGrasterizer* r) { NSVGmemPage* p; if (r == NULL) return; p = r->pages; while (p != NULL) { NSVGmemPage* next = p->next; free(p); p = next; } if (r->edges) free(r->edges); if (r->points) free(r->points); if (r->points2) free(r->points2); if (r->scanline) free(r->scanline); free(r); } static NSVGmemPage* nsvg__nextPage(NSVGrasterizer* r, NSVGmemPage* cur) { NSVGmemPage *newp; // If using existing chain, return the next page in chain if (cur != NULL && cur->next != NULL) { return cur->next; } // Alloc new page newp = (NSVGmemPage*)malloc(sizeof(NSVGmemPage)); if (newp == NULL) return NULL; memset(newp, 0, sizeof(NSVGmemPage)); // Add to linked list if (cur != NULL) cur->next = newp; else r->pages = newp; return newp; } static void nsvg__resetPool(NSVGrasterizer* r) { NSVGmemPage* p = r->pages; while (p != NULL) { p->size = 0; p = p->next; } r->curpage = r->pages; } static unsigned char* nsvg__alloc(NSVGrasterizer* r, int size) { unsigned char* buf; if (size > NSVG__MEMPAGE_SIZE) return NULL; if (r->curpage == NULL || r->curpage->size+size > NSVG__MEMPAGE_SIZE) { r->curpage = nsvg__nextPage(r, r->curpage); } buf = &r->curpage->mem[r->curpage->size]; r->curpage->size += size; return buf; } static int nsvg__ptEquals(float x1, float y1, float x2, float y2, float tol) { float dx = x2 - x1; float dy = y2 - y1; return dx*dx + dy*dy < tol*tol; } static void nsvg__addPathPoint(NSVGrasterizer* r, float x, float y, int flags) { NSVGpoint* pt; if (r->npoints > 0) { pt = &r->points[r->npoints-1]; if (nsvg__ptEquals(pt->x,pt->y, x,y, r->distTol)) { pt->flags = (unsigned char)(pt->flags | flags); return; } } if (r->npoints+1 > r->cpoints) { r->cpoints = r->cpoints > 0 ? r->cpoints * 2 : 64; r->points = (NSVGpoint*)realloc(r->points, sizeof(NSVGpoint) * r->cpoints); if (r->points == NULL) return; } pt = &r->points[r->npoints]; pt->x = x; pt->y = y; pt->flags = (unsigned char)flags; r->npoints++; } static void nsvg__appendPathPoint(NSVGrasterizer* r, NSVGpoint pt) { if (r->npoints+1 > r->cpoints) { r->cpoints = r->cpoints > 0 ? r->cpoints * 2 : 64; r->points = (NSVGpoint*)realloc(r->points, sizeof(NSVGpoint) * r->cpoints); if (r->points == NULL) return; } r->points[r->npoints] = pt; r->npoints++; } static void nsvg__duplicatePoints(NSVGrasterizer* r) { if (r->npoints > r->cpoints2) { r->cpoints2 = r->npoints; r->points2 = (NSVGpoint*)realloc(r->points2, sizeof(NSVGpoint) * r->cpoints2); if (r->points2 == NULL) return; } memcpy(r->points2, r->points, sizeof(NSVGpoint) * r->npoints); r->npoints2 = r->npoints; } static void nsvg__addEdge(NSVGrasterizer* r, float x0, float y0, float x1, float y1) { NSVGedge* e; // Skip horizontal edges if (y0 == y1) return; if (r->nedges+1 > r->cedges) { r->cedges = r->cedges > 0 ? r->cedges * 2 : 64; r->edges = (NSVGedge*)realloc(r->edges, sizeof(NSVGedge) * r->cedges); if (r->edges == NULL) return; } e = &r->edges[r->nedges]; r->nedges++; if (y0 < y1) { e->x0 = x0; e->y0 = y0; e->x1 = x1; e->y1 = y1; e->dir = 1; } else { e->x0 = x1; e->y0 = y1; e->x1 = x0; e->y1 = y0; e->dir = -1; } } static float nsvg__normalize(float *x, float* y) { float d = sqrtf((*x)*(*x) + (*y)*(*y)); if (d > 1e-6f) { float id = 1.0f / d; *x *= id; *y *= id; } return d; } static float nsvg__absf(float x) { return x < 0 ? -x : x; } static float nsvg__roundf(float x) { return (x >= 0) ? floorf(x + 0.5) : ceilf(x - 0.5); } static void nsvg__flattenCubicBez(NSVGrasterizer* r, float x1, float y1, float x2, float y2, float x3, float y3, float x4, float y4, int level, int type) { float x12,y12,x23,y23,x34,y34,x123,y123,x234,y234,x1234,y1234; float dx,dy,d2,d3; if (level > 10) return; x12 = (x1+x2)*0.5f; y12 = (y1+y2)*0.5f; x23 = (x2+x3)*0.5f; y23 = (y2+y3)*0.5f; x34 = (x3+x4)*0.5f; y34 = (y3+y4)*0.5f; x123 = (x12+x23)*0.5f; y123 = (y12+y23)*0.5f; dx = x4 - x1; dy = y4 - y1; d2 = nsvg__absf((x2 - x4) * dy - (y2 - y4) * dx); d3 = nsvg__absf((x3 - x4) * dy - (y3 - y4) * dx); if ((d2 + d3)*(d2 + d3) < r->tessTol * (dx*dx + dy*dy)) { nsvg__addPathPoint(r, x4, y4, type); return; } x234 = (x23+x34)*0.5f; y234 = (y23+y34)*0.5f; x1234 = (x123+x234)*0.5f; y1234 = (y123+y234)*0.5f; nsvg__flattenCubicBez(r, x1,y1, x12,y12, x123,y123, x1234,y1234, level+1, 0); nsvg__flattenCubicBez(r, x1234,y1234, x234,y234, x34,y34, x4,y4, level+1, type); } static void nsvg__flattenShape(NSVGrasterizer* r, NSVGshape* shape, float scale) { int i, j; NSVGpath* path; for (path = shape->paths; path != NULL; path = path->next) { r->npoints = 0; // Flatten path nsvg__addPathPoint(r, path->pts[0]*scale, path->pts[1]*scale, 0); for (i = 0; i < path->npts-1; i += 3) { float* p = &path->pts[i*2]; nsvg__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); } // Close path nsvg__addPathPoint(r, path->pts[0]*scale, path->pts[1]*scale, 0); // Build edges for (i = 0, j = r->npoints-1; i < r->npoints; j = i++) nsvg__addEdge(r, r->points[j].x, r->points[j].y, r->points[i].x, r->points[i].y); } } enum NSVGpointFlags { NSVG_PT_CORNER = 0x01, NSVG_PT_BEVEL = 0x02, NSVG_PT_LEFT = 0x04 }; static void nsvg__initClosed(NSVGpoint* left, NSVGpoint* right, NSVGpoint* p0, NSVGpoint* p1, float lineWidth) { float w = lineWidth * 0.5f; float dx = p1->x - p0->x; float dy = p1->y - p0->y; float len = nsvg__normalize(&dx, &dy); float px = p0->x + dx*len*0.5f, py = p0->y + dy*len*0.5f; float dlx = dy, dly = -dx; float lx = px - dlx*w, ly = py - dly*w; float rx = px + dlx*w, ry = py + dly*w; left->x = lx; left->y = ly; right->x = rx; right->y = ry; } static void nsvg__buttCap(NSVGrasterizer* r, NSVGpoint* left, NSVGpoint* right, NSVGpoint* p, float dx, float dy, float lineWidth, int connect) { float w = lineWidth * 0.5f; float px = p->x, py = p->y; float dlx = dy, dly = -dx; float lx = px - dlx*w, ly = py - dly*w; float rx = px + dlx*w, ry = py + dly*w; nsvg__addEdge(r, lx, ly, rx, ry); if (connect) { nsvg__addEdge(r, left->x, left->y, lx, ly); nsvg__addEdge(r, rx, ry, right->x, right->y); } left->x = lx; left->y = ly; right->x = rx; right->y = ry; } static void nsvg__squareCap(NSVGrasterizer* r, NSVGpoint* left, NSVGpoint* right, NSVGpoint* p, float dx, float dy, float lineWidth, int connect) { float w = lineWidth * 0.5f; float px = p->x - dx*w, py = p->y - dy*w; float dlx = dy, dly = -dx; float lx = px - dlx*w, ly = py - dly*w; float rx = px + dlx*w, ry = py + dly*w; nsvg__addEdge(r, lx, ly, rx, ry); if (connect) { nsvg__addEdge(r, left->x, left->y, lx, ly); nsvg__addEdge(r, rx, ry, right->x, right->y); } left->x = lx; left->y = ly; right->x = rx; right->y = ry; } #ifndef NSVG_PI #define NSVG_PI (3.14159265358979323846264338327f) #endif static void nsvg__roundCap(NSVGrasterizer* r, NSVGpoint* left, NSVGpoint* right, NSVGpoint* p, float dx, float dy, float lineWidth, int ncap, int connect) { int i; float w = lineWidth * 0.5f; float px = p->x, py = p->y; float dlx = dy, dly = -dx; float lx = 0, ly = 0, rx = 0, ry = 0, prevx = 0, prevy = 0; for (i = 0; i < ncap; i++) { float a = (float)i/(float)(ncap-1)*NSVG_PI; float ax = cosf(a) * w, ay = sinf(a) * w; float x = px - dlx*ax - dx*ay; float y = py - dly*ax - dy*ay; if (i > 0) nsvg__addEdge(r, prevx, prevy, x, y); prevx = x; prevy = y; if (i == 0) { lx = x; ly = y; } else if (i == ncap-1) { rx = x; ry = y; } } if (connect) { nsvg__addEdge(r, left->x, left->y, lx, ly); nsvg__addEdge(r, rx, ry, right->x, right->y); } left->x = lx; left->y = ly; right->x = rx; right->y = ry; } static void nsvg__bevelJoin(NSVGrasterizer* r, NSVGpoint* left, NSVGpoint* right, NSVGpoint* p0, NSVGpoint* p1, float lineWidth) { float w = lineWidth * 0.5f; float dlx0 = p0->dy, dly0 = -p0->dx; float dlx1 = p1->dy, dly1 = -p1->dx; float lx0 = p1->x - (dlx0 * w), ly0 = p1->y - (dly0 * w); float rx0 = p1->x + (dlx0 * w), ry0 = p1->y + (dly0 * w); float lx1 = p1->x - (dlx1 * w), ly1 = p1->y - (dly1 * w); float rx1 = p1->x + (dlx1 * w), ry1 = p1->y + (dly1 * w); nsvg__addEdge(r, lx0, ly0, left->x, left->y); nsvg__addEdge(r, lx1, ly1, lx0, ly0); nsvg__addEdge(r, right->x, right->y, rx0, ry0); nsvg__addEdge(r, rx0, ry0, rx1, ry1); left->x = lx1; left->y = ly1; right->x = rx1; right->y = ry1; } static void nsvg__miterJoin(NSVGrasterizer* r, NSVGpoint* left, NSVGpoint* right, NSVGpoint* p0, NSVGpoint* p1, float lineWidth) { float w = lineWidth * 0.5f; float dlx0 = p0->dy, dly0 = -p0->dx; float dlx1 = p1->dy, dly1 = -p1->dx; float lx0, rx0, lx1, rx1; float ly0, ry0, ly1, ry1; if (p1->flags & NSVG_PT_LEFT) { lx0 = lx1 = p1->x - p1->dmx * w; ly0 = ly1 = p1->y - p1->dmy * w; nsvg__addEdge(r, lx1, ly1, left->x, left->y); rx0 = p1->x + (dlx0 * w); ry0 = p1->y + (dly0 * w); rx1 = p1->x + (dlx1 * w); ry1 = p1->y + (dly1 * w); nsvg__addEdge(r, right->x, right->y, rx0, ry0); nsvg__addEdge(r, rx0, ry0, rx1, ry1); } else { lx0 = p1->x - (dlx0 * w); ly0 = p1->y - (dly0 * w); lx1 = p1->x - (dlx1 * w); ly1 = p1->y - (dly1 * w); nsvg__addEdge(r, lx0, ly0, left->x, left->y); nsvg__addEdge(r, lx1, ly1, lx0, ly0); rx0 = rx1 = p1->x + p1->dmx * w; ry0 = ry1 = p1->y + p1->dmy * w; nsvg__addEdge(r, right->x, right->y, rx1, ry1); } left->x = lx1; left->y = ly1; right->x = rx1; right->y = ry1; } static void nsvg__roundJoin(NSVGrasterizer* r, NSVGpoint* left, NSVGpoint* right, NSVGpoint* p0, NSVGpoint* p1, float lineWidth, int ncap) { int i, n; float w = lineWidth * 0.5f; float dlx0 = p0->dy, dly0 = -p0->dx; float dlx1 = p1->dy, dly1 = -p1->dx; float a0 = atan2f(dly0, dlx0); float a1 = atan2f(dly1, dlx1); float da = a1 - a0; float lx, ly, rx, ry; if (da < NSVG_PI) da += NSVG_PI*2; if (da > NSVG_PI) da -= NSVG_PI*2; n = (int)ceilf((nsvg__absf(da) / NSVG_PI) * (float)ncap); if (n < 2) n = 2; if (n > ncap) n = ncap; lx = left->x; ly = left->y; rx = right->x; ry = right->y; for (i = 0; i < n; i++) { float u = (float)i/(float)(n-1); float a = a0 + u*da; float ax = cosf(a) * w, ay = sinf(a) * w; float lx1 = p1->x - ax, ly1 = p1->y - ay; float rx1 = p1->x + ax, ry1 = p1->y + ay; nsvg__addEdge(r, lx1, ly1, lx, ly); nsvg__addEdge(r, rx, ry, rx1, ry1); lx = lx1; ly = ly1; rx = rx1; ry = ry1; } left->x = lx; left->y = ly; right->x = rx; right->y = ry; } static void nsvg__straightJoin(NSVGrasterizer* r, NSVGpoint* left, NSVGpoint* right, NSVGpoint* p1, float lineWidth) { float w = lineWidth * 0.5f; float lx = p1->x - (p1->dmx * w), ly = p1->y - (p1->dmy * w); float rx = p1->x + (p1->dmx * w), ry = p1->y + (p1->dmy * w); nsvg__addEdge(r, lx, ly, left->x, left->y); nsvg__addEdge(r, right->x, right->y, rx, ry); left->x = lx; left->y = ly; right->x = rx; right->y = ry; } static int nsvg__curveDivs(float r, float arc, float tol) { float da = acosf(r / (r + tol)) * 2.0f; int divs = (int)ceilf(arc / da); if (divs < 2) divs = 2; return divs; } static void nsvg__expandStroke(NSVGrasterizer* r, NSVGpoint* points, int npoints, int closed, int lineJoin, int lineCap, float lineWidth) { int ncap = nsvg__curveDivs(lineWidth*0.5f, NSVG_PI, r->tessTol); // Calculate divisions per half circle. NSVGpoint 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}; NSVGpoint* p0, *p1; int j, s, e; // Build stroke edges if (closed) { // Looping p0 = &points[npoints-1]; p1 = &points[0]; s = 0; e = npoints; } else { // Add cap p0 = &points[0]; p1 = &points[1]; s = 1; e = npoints-1; } if (closed) { nsvg__initClosed(&left, &right, p0, p1, lineWidth); firstLeft = left; firstRight = right; } else { // Add cap float dx = p1->x - p0->x; float dy = p1->y - p0->y; nsvg__normalize(&dx, &dy); if (lineCap == NSVG_CAP_BUTT) nsvg__buttCap(r, &left, &right, p0, dx, dy, lineWidth, 0); else if (lineCap == NSVG_CAP_SQUARE) nsvg__squareCap(r, &left, &right, p0, dx, dy, lineWidth, 0); else if (lineCap == NSVG_CAP_ROUND) nsvg__roundCap(r, &left, &right, p0, dx, dy, lineWidth, ncap, 0); } for (j = s; j < e; ++j) { if (p1->flags & NSVG_PT_CORNER) { if (lineJoin == NSVG_JOIN_ROUND) nsvg__roundJoin(r, &left, &right, p0, p1, lineWidth, ncap); else if (lineJoin == NSVG_JOIN_BEVEL || (p1->flags & NSVG_PT_BEVEL)) nsvg__bevelJoin(r, &left, &right, p0, p1, lineWidth); else nsvg__miterJoin(r, &left, &right, p0, p1, lineWidth); } else { nsvg__straightJoin(r, &left, &right, p1, lineWidth); } p0 = p1++; } if (closed) { // Loop it nsvg__addEdge(r, firstLeft.x, firstLeft.y, left.x, left.y); nsvg__addEdge(r, right.x, right.y, firstRight.x, firstRight.y); } else { // Add cap float dx = p1->x - p0->x; float dy = p1->y - p0->y; nsvg__normalize(&dx, &dy); if (lineCap == NSVG_CAP_BUTT) nsvg__buttCap(r, &right, &left, p1, -dx, -dy, lineWidth, 1); else if (lineCap == NSVG_CAP_SQUARE) nsvg__squareCap(r, &right, &left, p1, -dx, -dy, lineWidth, 1); else if (lineCap == NSVG_CAP_ROUND) nsvg__roundCap(r, &right, &left, p1, -dx, -dy, lineWidth, ncap, 1); } } static void nsvg__prepareStroke(NSVGrasterizer* r, float miterLimit, int lineJoin) { int i, j; NSVGpoint* p0, *p1; p0 = &r->points[r->npoints-1]; p1 = &r->points[0]; for (i = 0; i < r->npoints; i++) { // Calculate segment direction and length p0->dx = p1->x - p0->x; p0->dy = p1->y - p0->y; p0->len = nsvg__normalize(&p0->dx, &p0->dy); // Advance p0 = p1++; } // calculate joins p0 = &r->points[r->npoints-1]; p1 = &r->points[0]; for (j = 0; j < r->npoints; j++) { float dlx0, dly0, dlx1, dly1, dmr2, cross; dlx0 = p0->dy; dly0 = -p0->dx; dlx1 = p1->dy; dly1 = -p1->dx; // Calculate extrusions p1->dmx = (dlx0 + dlx1) * 0.5f; p1->dmy = (dly0 + dly1) * 0.5f; dmr2 = p1->dmx*p1->dmx + p1->dmy*p1->dmy; if (dmr2 > 0.000001f) { float s2 = 1.0f / dmr2; if (s2 > 600.0f) { s2 = 600.0f; } p1->dmx *= s2; p1->dmy *= s2; } // Clear flags, but keep the corner. p1->flags = (p1->flags & NSVG_PT_CORNER) ? NSVG_PT_CORNER : 0; // Keep track of left turns. cross = p1->dx * p0->dy - p0->dx * p1->dy; if (cross > 0.0f) p1->flags |= NSVG_PT_LEFT; // Check to see if the corner needs to be beveled. if (p1->flags & NSVG_PT_CORNER) { if ((dmr2 * miterLimit*miterLimit) < 1.0f || lineJoin == NSVG_JOIN_BEVEL || lineJoin == NSVG_JOIN_ROUND) { p1->flags |= NSVG_PT_BEVEL; } } p0 = p1++; } } static void nsvg__flattenShapeStroke(NSVGrasterizer* r, NSVGshape* shape, float scale) { int i, j, closed; NSVGpath* path; NSVGpoint* p0, *p1; float miterLimit = shape->miterLimit; int lineJoin = shape->strokeLineJoin; int lineCap = shape->strokeLineCap; float lineWidth = shape->strokeWidth * scale; for (path = shape->paths; path != NULL; path = path->next) { // Flatten path r->npoints = 0; nsvg__addPathPoint(r, path->pts[0]*scale, path->pts[1]*scale, NSVG_PT_CORNER); for (i = 0; i < path->npts-1; i += 3) { float* p = &path->pts[i*2]; nsvg__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); } if (r->npoints < 2) continue; closed = path->closed; // If the first and last points are the same, remove the last, mark as closed path. p0 = &r->points[r->npoints-1]; p1 = &r->points[0]; if (nsvg__ptEquals(p0->x,p0->y, p1->x,p1->y, r->distTol)) { r->npoints--; p0 = &r->points[r->npoints-1]; closed = 1; } if (shape->strokeDashCount > 0) { int idash = 0, dashState = 1; float totalDist = 0, dashLen, allDashLen, dashOffset; NSVGpoint cur; if (closed) nsvg__appendPathPoint(r, r->points[0]); // Duplicate points -> points2. nsvg__duplicatePoints(r); r->npoints = 0; cur = r->points2[0]; nsvg__appendPathPoint(r, cur); // Figure out dash offset. allDashLen = 0; for (j = 0; j < shape->strokeDashCount; j++) allDashLen += shape->strokeDashArray[j]; if (shape->strokeDashCount & 1) allDashLen *= 2.0f; // Find location inside pattern dashOffset = fmodf(shape->strokeDashOffset, allDashLen); if (dashOffset < 0.0f) dashOffset += allDashLen; while (dashOffset > shape->strokeDashArray[idash]) { dashOffset -= shape->strokeDashArray[idash]; idash = (idash + 1) % shape->strokeDashCount; } dashLen = (shape->strokeDashArray[idash] - dashOffset) * scale; for (j = 1; j < r->npoints2; ) { float dx = r->points2[j].x - cur.x; float dy = r->points2[j].y - cur.y; float dist = sqrtf(dx*dx + dy*dy); if ((totalDist + dist) > dashLen) { // Calculate intermediate point float d = (dashLen - totalDist) / dist; float x = cur.x + dx * d; float y = cur.y + dy * d; nsvg__addPathPoint(r, x, y, NSVG_PT_CORNER); // Stroke if (r->npoints > 1 && dashState) { nsvg__prepareStroke(r, miterLimit, lineJoin); nsvg__expandStroke(r, r->points, r->npoints, 0, lineJoin, lineCap, lineWidth); } // Advance dash pattern dashState = !dashState; idash = (idash+1) % shape->strokeDashCount; dashLen = shape->strokeDashArray[idash] * scale; // Restart cur.x = x; cur.y = y; cur.flags = NSVG_PT_CORNER; totalDist = 0.0f; r->npoints = 0; nsvg__appendPathPoint(r, cur); } else { totalDist += dist; cur = r->points2[j]; nsvg__appendPathPoint(r, cur); j++; } } // Stroke any leftover path if (r->npoints > 1 && dashState) { nsvg__prepareStroke(r, miterLimit, lineJoin); nsvg__expandStroke(r, r->points, r->npoints, 0, lineJoin, lineCap, lineWidth); } } else { nsvg__prepareStroke(r, miterLimit, lineJoin); nsvg__expandStroke(r, r->points, r->npoints, closed, lineJoin, lineCap, lineWidth); } } } static int nsvg__cmpEdge(const void *p, const void *q) { const NSVGedge* a = (const NSVGedge*)p; const NSVGedge* b = (const NSVGedge*)q; if (a->y0 < b->y0) return -1; if (a->y0 > b->y0) return 1; return 0; } static NSVGactiveEdge* nsvg__addActive(NSVGrasterizer* r, NSVGedge* e, float startPoint) { NSVGactiveEdge* z; if (r->freelist != NULL) { // Restore from freelist. z = r->freelist; r->freelist = z->next; } else { // Alloc new edge. z = (NSVGactiveEdge*)nsvg__alloc(r, sizeof(NSVGactiveEdge)); if (z == NULL) return NULL; } float dxdy = (e->x1 - e->x0) / (e->y1 - e->y0); // STBTT_assert(e->y0 <= start_point); // round dx down to avoid going too far if (dxdy < 0) z->dx = (int)(-nsvg__roundf(NSVG__FIX * -dxdy)); else z->dx = (int)nsvg__roundf(NSVG__FIX * dxdy); z->x = (int)nsvg__roundf(NSVG__FIX * (e->x0 + dxdy * (startPoint - e->y0))); // z->x -= off_x * FIX; z->ey = e->y1; z->next = 0; z->dir = e->dir; return z; } static void nsvg__freeActive(NSVGrasterizer* r, NSVGactiveEdge* z) { z->next = r->freelist; r->freelist = z; } static void nsvg__fillScanline(unsigned char* scanline, int len, int x0, int x1, int maxWeight, int* xmin, int* xmax) { int i = x0 >> NSVG__FIXSHIFT; int j = x1 >> NSVG__FIXSHIFT; if (i < *xmin) *xmin = i; if (j > *xmax) *xmax = j; if (i < len && j >= 0) { if (i == j) { // x0,x1 are the same pixel, so compute combined coverage scanline[i] = (unsigned char)(scanline[i] + ((x1 - x0) * maxWeight >> NSVG__FIXSHIFT)); } else { if (i >= 0) // add antialiasing for x0 scanline[i] = (unsigned char)(scanline[i] + (((NSVG__FIX - (x0 & NSVG__FIXMASK)) * maxWeight) >> NSVG__FIXSHIFT)); else i = -1; // clip if (j < len) // add antialiasing for x1 scanline[j] = (unsigned char)(scanline[j] + (((x1 & NSVG__FIXMASK) * maxWeight) >> NSVG__FIXSHIFT)); else j = len; // clip for (++i; i < j; ++i) // fill pixels between x0 and x1 scanline[i] = (unsigned char)(scanline[i] + maxWeight); } } } // note: this routine clips fills that extend off the edges... ideally this // wouldn't happen, but it could happen if the truetype glyph bounding boxes // are wrong, or if the user supplies a too-small bitmap static void nsvg__fillActiveEdges(unsigned char* scanline, int len, NSVGactiveEdge* e, int maxWeight, int* xmin, int* xmax, char fillRule) { // non-zero winding fill int x0 = 0, w = 0; if (fillRule == NSVG_FILLRULE_NONZERO) { // Non-zero while (e != NULL) { if (w == 0) { // if we're currently at zero, we need to record the edge start point x0 = e->x; w += e->dir; } else { int x1 = e->x; w += e->dir; // if we went to zero, we need to draw if (w == 0) nsvg__fillScanline(scanline, len, x0, x1, maxWeight, xmin, xmax); } e = e->next; } } else if (fillRule == NSVG_FILLRULE_EVENODD) { // Even-odd while (e != NULL) { if (w == 0) { // if we're currently at zero, we need to record the edge start point x0 = e->x; w = 1; } else { int x1 = e->x; w = 0; nsvg__fillScanline(scanline, len, x0, x1, maxWeight, xmin, xmax); } e = e->next; } } } static float nsvg__clampf(float a, float mn, float mx) { if (isnan(a)) return mn; return a < mn ? mn : (a > mx ? mx : a); } static unsigned int nsvg__RGBA(unsigned char r, unsigned char g, unsigned char b, unsigned char a) { return ((unsigned int)r) | ((unsigned int)g << 8) | ((unsigned int)b << 16) | ((unsigned int)a << 24); } static unsigned int nsvg__lerpRGBA(unsigned int c0, unsigned int c1, float u) { int iu = (int)(nsvg__clampf(u, 0.0f, 1.0f) * 256.0f); int r = (((c0) & 0xff)*(256-iu) + (((c1) & 0xff)*iu)) >> 8; int g = (((c0>>8) & 0xff)*(256-iu) + (((c1>>8) & 0xff)*iu)) >> 8; int b = (((c0>>16) & 0xff)*(256-iu) + (((c1>>16) & 0xff)*iu)) >> 8; int a = (((c0>>24) & 0xff)*(256-iu) + (((c1>>24) & 0xff)*iu)) >> 8; return nsvg__RGBA((unsigned char)r, (unsigned char)g, (unsigned char)b, (unsigned char)a); } static unsigned int nsvg__applyOpacity(unsigned int c, float u) { int iu = (int)(nsvg__clampf(u, 0.0f, 1.0f) * 256.0f); int r = (c) & 0xff; int g = (c>>8) & 0xff; int b = (c>>16) & 0xff; int a = (((c>>24) & 0xff)*iu) >> 8; return nsvg__RGBA((unsigned char)r, (unsigned char)g, (unsigned char)b, (unsigned char)a); } static inline int nsvg__div255(int x) { return ((x+1) * 257) >> 16; } static void nsvg__scanlineSolid(unsigned char* dst, int count, unsigned char* cover, int x, int y, float tx, float ty, float scale, NSVGcachedPaint* cache) { if (cache->type == NSVG_PAINT_COLOR) { int i, cr, cg, cb, ca; cr = cache->colors[0] & 0xff; cg = (cache->colors[0] >> 8) & 0xff; cb = (cache->colors[0] >> 16) & 0xff; ca = (cache->colors[0] >> 24) & 0xff; for (i = 0; i < count; i++) { int r,g,b; int a = nsvg__div255((int)cover[0] * ca); int ia = 255 - a; // Premultiply r = nsvg__div255(cr * a); g = nsvg__div255(cg * a); b = nsvg__div255(cb * a); // Blend over r += nsvg__div255(ia * (int)dst[0]); g += nsvg__div255(ia * (int)dst[1]); b += nsvg__div255(ia * (int)dst[2]); a += nsvg__div255(ia * (int)dst[3]); dst[0] = (unsigned char)r; dst[1] = (unsigned char)g; dst[2] = (unsigned char)b; dst[3] = (unsigned char)a; cover++; dst += 4; } } else if (cache->type == NSVG_PAINT_LINEAR_GRADIENT) { // TODO: spread modes. // TODO: plenty of opportunities to optimize. float fx, fy, dx, gy; float* t = cache->xform; int i, cr, cg, cb, ca; unsigned int c; fx = ((float)x - tx) / scale; fy = ((float)y - ty) / scale; dx = 1.0f / scale; for (i = 0; i < count; i++) { int r,g,b,a,ia; gy = fx*t[1] + fy*t[3] + t[5]; c = cache->colors[(int)nsvg__clampf(gy*255.0f, 0, 255.0f)]; cr = (c) & 0xff; cg = (c >> 8) & 0xff; cb = (c >> 16) & 0xff; ca = (c >> 24) & 0xff; a = nsvg__div255((int)cover[0] * ca); ia = 255 - a; // Premultiply r = nsvg__div255(cr * a); g = nsvg__div255(cg * a); b = nsvg__div255(cb * a); // Blend over r += nsvg__div255(ia * (int)dst[0]); g += nsvg__div255(ia * (int)dst[1]); b += nsvg__div255(ia * (int)dst[2]); a += nsvg__div255(ia * (int)dst[3]); dst[0] = (unsigned char)r; dst[1] = (unsigned char)g; dst[2] = (unsigned char)b; dst[3] = (unsigned char)a; cover++; dst += 4; fx += dx; } } else if (cache->type == NSVG_PAINT_RADIAL_GRADIENT) { // TODO: spread modes. // TODO: plenty of opportunities to optimize. // TODO: focus (fx,fy) float fx, fy, dx, gx, gy, gd; float* t = cache->xform; int i, cr, cg, cb, ca; unsigned int c; fx = ((float)x - tx) / scale; fy = ((float)y - ty) / scale; dx = 1.0f / scale; for (i = 0; i < count; i++) { int r,g,b,a,ia; gx = fx*t[0] + fy*t[2] + t[4]; gy = fx*t[1] + fy*t[3] + t[5]; gd = sqrtf(gx*gx + gy*gy); c = cache->colors[(int)nsvg__clampf(gd*255.0f, 0, 255.0f)]; cr = (c) & 0xff; cg = (c >> 8) & 0xff; cb = (c >> 16) & 0xff; ca = (c >> 24) & 0xff; a = nsvg__div255((int)cover[0] * ca); ia = 255 - a; // Premultiply r = nsvg__div255(cr * a); g = nsvg__div255(cg * a); b = nsvg__div255(cb * a); // Blend over r += nsvg__div255(ia * (int)dst[0]); g += nsvg__div255(ia * (int)dst[1]); b += nsvg__div255(ia * (int)dst[2]); a += nsvg__div255(ia * (int)dst[3]); dst[0] = (unsigned char)r; dst[1] = (unsigned char)g; dst[2] = (unsigned char)b; dst[3] = (unsigned char)a; cover++; dst += 4; fx += dx; } } } static void nsvg__rasterizeSortedEdges(NSVGrasterizer *r, float tx, float ty, float scale, NSVGcachedPaint* cache, char fillRule) { NSVGactiveEdge *active = NULL; int y, s; int e = 0; int maxWeight = (255 / NSVG__SUBSAMPLES); // weight per vertical scanline int xmin, xmax; for (y = 0; y < r->height; y++) { memset(r->scanline, 0, r->width); xmin = r->width; xmax = 0; for (s = 0; s < NSVG__SUBSAMPLES; ++s) { // find center of pixel for this scanline float scany = (float)(y*NSVG__SUBSAMPLES + s) + 0.5f; NSVGactiveEdge **step = &active; // update all active edges; // remove all active edges that terminate before the center of this scanline while (*step) { NSVGactiveEdge *z = *step; if (z->ey <= scany) { *step = z->next; // delete from list // NSVG__assert(z->valid); nsvg__freeActive(r, z); } else { z->x += z->dx; // advance to position for current scanline step = &((*step)->next); // advance through list } } // resort the list if needed for (;;) { int changed = 0; step = &active; while (*step && (*step)->next) { if ((*step)->x > (*step)->next->x) { NSVGactiveEdge* t = *step; NSVGactiveEdge* q = t->next; t->next = q->next; q->next = t; *step = q; changed = 1; } step = &(*step)->next; } if (!changed) break; } // insert all edges that start before the center of this scanline -- omit ones that also end on this scanline while (e < r->nedges && r->edges[e].y0 <= scany) { if (r->edges[e].y1 > scany) { NSVGactiveEdge* z = nsvg__addActive(r, &r->edges[e], scany); if (z == NULL) break; // find insertion point if (active == NULL) { active = z; } else if (z->x < active->x) { // insert at front z->next = active; active = z; } else { // find thing to insert AFTER NSVGactiveEdge* p = active; while (p->next && p->next->x < z->x) p = p->next; // at this point, p->next->x is NOT < z->x z->next = p->next; p->next = z; } } e++; } // now process all active edges in non-zero fashion if (active != NULL) nsvg__fillActiveEdges(r->scanline, r->width, active, maxWeight, &xmin, &xmax, fillRule); } // Blit if (xmin < 0) xmin = 0; if (xmax > r->width-1) xmax = r->width-1; if (xmin <= xmax) { nsvg__scanlineSolid(&r->bitmap[y * r->stride] + xmin*4, xmax-xmin+1, &r->scanline[xmin], xmin, y, tx,ty, scale, cache); } } } static void nsvg__unpremultiplyAlpha(unsigned char* image, int w, int h, int stride) { int x,y; // Unpremultiply for (y = 0; y < h; y++) { unsigned char *row = &image[y*stride]; for (x = 0; x < w; x++) { int r = row[0], g = row[1], b = row[2], a = row[3]; if (a != 0) { row[0] = (unsigned char)(r*255/a); row[1] = (unsigned char)(g*255/a); row[2] = (unsigned char)(b*255/a); } row += 4; } } // Defringe for (y = 0; y < h; y++) { unsigned char *row = &image[y*stride]; for (x = 0; x < w; x++) { int r = 0, g = 0, b = 0, a = row[3], n = 0; if (a == 0) { if (x-1 > 0 && row[-1] != 0) { r += row[-4]; g += row[-3]; b += row[-2]; n++; } if (x+1 < w && row[7] != 0) { r += row[4]; g += row[5]; b += row[6]; n++; } if (y-1 > 0 && row[-stride+3] != 0) { r += row[-stride]; g += row[-stride+1]; b += row[-stride+2]; n++; } if (y+1 < h && row[stride+3] != 0) { r += row[stride]; g += row[stride+1]; b += row[stride+2]; n++; } if (n > 0) { row[0] = (unsigned char)(r/n); row[1] = (unsigned char)(g/n); row[2] = (unsigned char)(b/n); } } row += 4; } } } static void nsvg__initPaint(NSVGcachedPaint* cache, NSVGpaint* paint, float opacity) { int i, j; NSVGgradient* grad; cache->type = paint->type; if (paint->type == NSVG_PAINT_COLOR) { cache->colors[0] = nsvg__applyOpacity(paint->color, opacity); return; } grad = paint->gradient; cache->spread = grad->spread; memcpy(cache->xform, grad->xform, sizeof(float)*6); if (grad->nstops == 0) { for (i = 0; i < 256; i++) cache->colors[i] = 0; } else if (grad->nstops == 1) { unsigned int color = nsvg__applyOpacity(grad->stops[0].color, opacity); for (i = 0; i < 256; i++) cache->colors[i] = color; } else { unsigned int ca, cb = 0; float ua, ub, du, u; int ia, ib, count; ca = nsvg__applyOpacity(grad->stops[0].color, opacity); ua = nsvg__clampf(grad->stops[0].offset, 0, 1); ub = nsvg__clampf(grad->stops[grad->nstops-1].offset, ua, 1); ia = (int)(ua * 255.0f); ib = (int)(ub * 255.0f); for (i = 0; i < ia; i++) { cache->colors[i] = ca; } for (i = 0; i < grad->nstops-1; i++) { ca = nsvg__applyOpacity(grad->stops[i].color, opacity); cb = nsvg__applyOpacity(grad->stops[i+1].color, opacity); ua = nsvg__clampf(grad->stops[i].offset, 0, 1); ub = nsvg__clampf(grad->stops[i+1].offset, 0, 1); ia = (int)(ua * 255.0f); ib = (int)(ub * 255.0f); count = ib - ia; if (count <= 0) continue; u = 0; du = 1.0f / (float)count; for (j = 0; j < count; j++) { cache->colors[ia+j] = nsvg__lerpRGBA(ca,cb,u); u += du; } } for (i = ib; i < 256; i++) cache->colors[i] = cb; } } /* static void dumpEdges(NSVGrasterizer* r, const char* name) { float xmin = 0, xmax = 0, ymin = 0, ymax = 0; NSVGedge *e = NULL; int i; if (r->nedges == 0) return; FILE* fp = fopen(name, "w"); if (fp == NULL) return; xmin = xmax = r->edges[0].x0; ymin = ymax = r->edges[0].y0; for (i = 0; i < r->nedges; i++) { e = &r->edges[i]; xmin = nsvg__minf(xmin, e->x0); xmin = nsvg__minf(xmin, e->x1); xmax = nsvg__maxf(xmax, e->x0); xmax = nsvg__maxf(xmax, e->x1); ymin = nsvg__minf(ymin, e->y0); ymin = nsvg__minf(ymin, e->y1); ymax = nsvg__maxf(ymax, e->y0); ymax = nsvg__maxf(ymax, e->y1); } fprintf(fp, "", xmin, ymin, (xmax - xmin), (ymax - ymin)); for (i = 0; i < r->nedges; i++) { e = &r->edges[i]; fprintf(fp ,"", e->x0,e->y0, e->x1,e->y1); } for (i = 0; i < r->npoints; i++) { if (i+1 < r->npoints) fprintf(fp ,"", r->points[i].x, r->points[i].y, r->points[i+1].x, r->points[i+1].y); fprintf(fp ,"", r->points[i].x, r->points[i].y, r->points[i].flags == 0 ? "#f00" : "#0f0"); } fprintf(fp, ""); fclose(fp); } */ void nsvgRasterize(NSVGrasterizer* r, NSVGimage* image, float tx, float ty, float scale, unsigned char* dst, int w, int h, int stride) { NSVGshape *shape = NULL; NSVGedge *e = NULL; NSVGcachedPaint cache; int i; int j; unsigned char paintOrder; r->bitmap = dst; r->width = w; r->height = h; r->stride = stride; if (w > r->cscanline) { r->cscanline = w; r->scanline = (unsigned char*)realloc(r->scanline, w); if (r->scanline == NULL) return; } for (i = 0; i < h; i++) memset(&dst[i*stride], 0, w*4); for (shape = image->shapes; shape != NULL; shape = shape->next) { if (!(shape->flags & NSVG_FLAGS_VISIBLE)) continue; for (j = 0; j < 3; j++) { paintOrder = (shape->paintOrder >> (2 * j)) & 0x03; if (paintOrder == NSVG_PAINT_FILL && shape->fill.type != NSVG_PAINT_NONE) { nsvg__resetPool(r); r->freelist = NULL; r->nedges = 0; nsvg__flattenShape(r, shape, scale); // Scale and translate edges for (i = 0; i < r->nedges; i++) { e = &r->edges[i]; e->x0 = tx + e->x0; e->y0 = (ty + e->y0) * NSVG__SUBSAMPLES; e->x1 = tx + e->x1; e->y1 = (ty + e->y1) * NSVG__SUBSAMPLES; } // Rasterize edges if (r->nedges != 0) qsort(r->edges, r->nedges, sizeof(NSVGedge), nsvg__cmpEdge); // now, traverse the scanlines and find the intersections on each scanline, use non-zero rule nsvg__initPaint(&cache, &shape->fill, shape->opacity); nsvg__rasterizeSortedEdges(r, tx,ty,scale, &cache, shape->fillRule); } if (paintOrder == NSVG_PAINT_STROKE && shape->stroke.type != NSVG_PAINT_NONE && (shape->strokeWidth * scale) > 0.01f) { nsvg__resetPool(r); r->freelist = NULL; r->nedges = 0; nsvg__flattenShapeStroke(r, shape, scale); // dumpEdges(r, "edge.svg"); // Scale and translate edges for (i = 0; i < r->nedges; i++) { e = &r->edges[i]; e->x0 = tx + e->x0; e->y0 = (ty + e->y0) * NSVG__SUBSAMPLES; e->x1 = tx + e->x1; e->y1 = (ty + e->y1) * NSVG__SUBSAMPLES; } // Rasterize edges if (r->nedges != 0) qsort(r->edges, r->nedges, sizeof(NSVGedge), nsvg__cmpEdge); // now, traverse the scanlines and find the intersections on each scanline, use non-zero rule nsvg__initPaint(&cache, &shape->stroke, shape->opacity); nsvg__rasterizeSortedEdges(r, tx,ty,scale, &cache, NSVG_FILLRULE_NONZERO); } } } nsvg__unpremultiplyAlpha(dst, w, h, stride); r->bitmap = NULL; r->width = 0; r->height = 0; r->stride = 0; } #endif // NANOSVGRAST_IMPLEMENTATION #endif // NANOSVGRAST_H ================================================ FILE: external/rprand.h ================================================ /********************************************************************************************** * * rprand v1.0 - A simple and easy-to-use pseudo-random numbers generator (PRNG) * * FEATURES: * - Pseudo-random values generation, 32 bits: [0..4294967295] * - Sequence generation avoiding duplicate values * - Using standard and proven prng algorithm (Xoshiro128**) * - State initialized with a separate generator (SplitMix64) * * LIMITATIONS: * - No negative numbers, up to the user to manage them * * POSSIBLE IMPROVEMENTS: * - Support 64 bits generation * * ADDITIONAL NOTES: * This library implements two pseudo-random number generation algorithms: * * - Xoshiro128** : https://prng.di.unimi.it/xoshiro128starstar.c * - SplitMix64 : https://prng.di.unimi.it/splitmix64.c * * SplitMix64 is used to initialize the Xoshiro128** state, from a provided seed * * It's suggested to use SplitMix64 to initialize the state of the generators starting from * a 64-bit seed, as research has shown that initialization must be performed with a generator * radically different in nature from the one initialized to avoid correlation on similar seeds. * * CONFIGURATION: * #define RPRAND_IMPLEMENTATION * Generates the implementation of the library into the included file. * If not defined, the library is in header only mode and can be included in other headers * or source files without problems. But only ONE file should hold the implementation. * * DEPENDENCIES: none * * VERSIONS HISTORY: * 1.0 (01-Jun-2023) First version * * * LICENSE: zlib/libpng * * Copyright (c) 2023 Ramon Santamaria (@raysan5) * * This software is provided "as-is", without any express or implied warranty. In no event * will the authors be held liable for any damages arising from the use of this software. * * Permission is granted to anyone to use this software for any purpose, including commercial * applications, and to alter it and redistribute it freely, subject to the following restrictions: * * 1. The origin of this software must not be misrepresented; you must not claim that you * wrote the original software. If you use this software in a product, an acknowledgment * in the product documentation would be appreciated but is not required. * * 2. Altered source versions must be plainly marked as such, and must not be misrepresented * as being the original software. * * 3. This notice may not be removed or altered from any source distribution. * **********************************************************************************************/ #ifndef RPRAND_H #define RPRAND_H #define RPRAND_VERSION "1.0" // Function specifiers in case library is build/used as a shared library (Windows) // NOTE: Microsoft specifiers to tell compiler that symbols are imported/exported from a .dll #if defined(_WIN32) #if defined(BUILD_LIBTYPE_SHARED) #define RPRAND __declspec(dllexport) // We are building the library as a Win32 shared library (.dll) #elif defined(USE_LIBTYPE_SHARED) #define RPRAND __declspec(dllimport) // We are using the library as a Win32 shared library (.dll) #endif #endif // Function specifiers definition #ifndef RPRANDAPI #define RPRANDAPI // Functions defined as 'extern' by default (implicit specifiers) #endif //---------------------------------------------------------------------------------- // Defines and Macros //---------------------------------------------------------------------------------- // Allow custom memory allocators #ifndef RPRAND_CALLOC #define RPRAND_CALLOC(ptr,sz) calloc(ptr,sz) #endif #ifndef RPRAND_FREE #define RPRAND_FREE(ptr) free(ptr) #endif // Simple log system to avoid RPNG_LOG() calls if required // NOTE: Avoiding those calls, also avoids const strings memory usage #define RPRAND_SHOW_LOG_INFO #if defined(RPNG_SHOW_LOG_INFO) #define RPRAND_LOG(...) printf(__VA_ARGS__) #else #define RPRAND_LOG(...) #endif //---------------------------------------------------------------------------------- // Types and Structures Definition //---------------------------------------------------------------------------------- //... #ifdef __cplusplus extern "C" { // Prevents name mangling of functions #endif //---------------------------------------------------------------------------------- // Global Variables Definition //---------------------------------------------------------------------------------- //... //---------------------------------------------------------------------------------- // Module Functions Declaration //---------------------------------------------------------------------------------- RPRANDAPI void rprand_set_seed(unsigned long long seed); // Set rprand_state for Xoshiro128**, seed is 64bit RPRANDAPI int rprand_get_value(int min, int max); // Get random value within a range, min and max included RPRANDAPI int *rprand_load_sequence(unsigned int count, int min, int max); // Load pseudo-random numbers sequence with no duplicates RPRANDAPI void rprand_unload_sequence(int *sequence); // Unload pseudo-random numbers sequence #ifdef __cplusplus } #endif #endif // RPRAND_H /*********************************************************************************** * * RPRAND IMPLEMENTATION * ************************************************************************************/ #if defined(RPRAND_IMPLEMENTATION) #include // Required for: calloc(), free(), abs() #include // Required for data types: uint32_t, uint64_t //---------------------------------------------------------------------------------- // Types and Structures Definition //---------------------------------------------------------------------------------- // ... //---------------------------------------------------------------------------------- // Global Variables Definition //---------------------------------------------------------------------------------- static uint64_t rprand_seed = 0xAABBCCDD; // SplitMix64 default seed (aligned to rprand_state) static uint32_t rprand_state[4] = { // Xoshiro128** state, initialized by SplitMix64 0x96ea83c1, 0x218b21e5, 0xaa91febd, 0x976414d4 }; //---------------------------------------------------------------------------------- // Module internal functions declaration //---------------------------------------------------------------------------------- static uint32_t rprand_xoshiro(void); // Xoshiro128** generator (uses global rprand_state) static uint64_t rprand_splitmix64(void); // SplitMix64 generator (uses seed to generate rprand_state) //---------------------------------------------------------------------------------- // Module functions definition //---------------------------------------------------------------------------------- // Set rprand_state for Xoshiro128** // NOTE: We use a custom generation algorithm using SplitMix64 void rprand_set_seed(unsigned long long seed) { rprand_seed = (uint64_t)seed; // Set SplitMix64 seed for further use // To generate the Xoshiro128** state, we use SplitMix64 generator first // We generate 4 pseudo-random 64bit numbers that we combine using their LSB|MSB rprand_state[0] = (uint32_t)(rprand_splitmix64() & 0xffffffff); rprand_state[1] = (uint32_t)((rprand_splitmix64() & 0xffffffff00000000) >> 32); rprand_state[2] = (uint32_t)(rprand_splitmix64() & 0xffffffff); rprand_state[3] = (uint32_t)((rprand_splitmix64() & 0xffffffff00000000) >> 32); } // Get random value within a range, min and max included int rprand_get_value(int min, int max) { int value = rprand_xoshiro()%(abs(max - min) + 1) + min; return value; } // Load pseudo-random numbers sequence with no duplicates, min and max included int *rprand_load_sequence(unsigned int count, int min, int max) { int *sequence = NULL; if (count > (unsigned int)(abs(max - min) + 1)) { RPRAND_LOG("WARNING: Sequence count required is greater than range provided\n"); //count = (max - min); return sequence; } sequence = (int *)RPRAND_CALLOC(count, sizeof(int)); int value = 0; bool value_is_dup = false; for (unsigned int i = 0; i < count;) { value = ((unsigned int)rprand_xoshiro()%(abs(max - min) + 1)) + min; for (unsigned int j = 0; j < i; j++) { if (sequence[j] == value) { value_is_dup = true; break; } } if (!value_is_dup) { sequence[i] = value; i++; } value_is_dup = false; } return sequence; } // Unload pseudo-random numbers sequence void rprand_unload_sequence(int *sequence) { RPRAND_FREE(sequence); sequence = NULL; } //---------------------------------------------------------------------------------- // Module internal functions definition //---------------------------------------------------------------------------------- static inline uint32_t rprand_rotate_left(const uint32_t x, int k) { return (x << k) | (x >> (32 - k)); } // Xoshiro128** generator info: // // Written in 2018 by David Blackman and Sebastiano Vigna (vigna@acm.org) // // To the extent possible under law, the author has dedicated all copyright // and related and neighboring rights to this software to the public domain // worldwide. This software is distributed without any warranty. // // See . // // This is xoshiro128** 1.1, one of our 32-bit all-purpose, rock-solid // generators. It has excellent speed, a state size (128 bits) that is // large enough for mild parallelism, and it passes all tests we are aware // of. // // Note that version 1.0 had mistakenly s[0] instead of s[1] as state // word passed to the scrambler. // // For generating just single-precision (i.e., 32-bit) floating-point // numbers, xoshiro128+ is even faster. // // The state must be seeded so that it is not everywhere zero. // uint32_t rprand_xoshiro(void) { const uint32_t result = rprand_rotate_left(rprand_state[1]*5, 7)*9; const uint32_t t = rprand_state[1] << 9; rprand_state[2] ^= rprand_state[0]; rprand_state[3] ^= rprand_state[1]; rprand_state[1] ^= rprand_state[2]; rprand_state[0] ^= rprand_state[3]; rprand_state[2] ^= t; rprand_state[3] = rprand_rotate_left(rprand_state[3], 11); return result; } // SplitMix64 generator info: // // Written in 2015 by Sebastiano Vigna (vigna@acm.org) // // To the extent possible under law, the author has dedicated all copyright // and related and neighboring rights to this software to the public domain // worldwide. This software is distributed without any warranty. // // See . // // // This is a fixed-increment version of Java 8's SplittableRandom generator // See http://dx.doi.org/10.1145/2714064.2660195 and // http://docs.oracle.com/javase/8/docs/api/java/util/SplittableRandom.html // // It is a very fast generator passing BigCrush, and it can be useful if // for some reason you absolutely want 64 bits of state. uint64_t rprand_splitmix64() { uint64_t z = (rprand_seed += 0x9e3779b97f4a7c15); z = (z ^ (z >> 30))*0xbf58476d1ce4e5b9; z = (z ^ (z >> 27))*0x94d049bb133111eb; return z ^ (z >> 31); } #endif // RPRAND_IMPLEMENTATION ================================================ FILE: external/tinyfiledialogs.c ================================================ /* SPDX-License-Identifier: Zlib Copyright (c) 2014 - 2024 Guillaume Vareille http://ysengrin.com ________________________________________________________________ | | | 100% compatible C C++ -> You can rename this .c file as .cpp | |________________________________________________________________| ********* TINY FILE DIALOGS OFFICIAL WEBSITE IS ON SOURCEFORGE ********* _________ / \ tinyfiledialogs.c v3.18.2 [Jun 8, 2024] zlib licence |tiny file| Unique code file created [November 9, 2014] | dialogs | \____ ___/ http://tinyfiledialogs.sourceforge.net \| git clone http://git.code.sf.net/p/tinyfiledialogs/code tinyfd ____________________________________________ | | | email: tinyfiledialogs at ysengrin.com | |____________________________________________| _________________________________________________________________________________ | | | the windows only wchar_t UTF-16 prototypes are at the bottom of the header file | |_________________________________________________________________________________| _________________________________________________________ | | | on windows: - since v3.6 char is UTF-8 by default | | - if you want MBCS set tinyfd_winUtf8 to 0 | | - functions like fopen expect MBCS | |_________________________________________________________| If you like tinyfiledialogs, please upvote my stackoverflow answer https://stackoverflow.com/a/47651444 - License - This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. __________________________________________ | ______________________________________ | | | | | | | DO NOT USE USER INPUT IN THE DIALOGS | | | |______________________________________| | |__________________________________________| */ #if defined(__GNUC__) || defined(__clang__) #ifndef _GNU_SOURCE #define _GNU_SOURCE /* used only to resolve symbolic links. Can be commented out */ #ifndef _POSIX_C_SOURCE #ifdef __FreeBSD__ #define _POSIX_C_SOURCE 199506L /* 199506L is enough for freebsd for realpath() */ #elif defined(__illumos__) || defined(__solaris__) #define _POSIX_C_SOURCE 200112L /* illumos/solaris needs 200112L for realpath() */ #else #define _POSIX_C_SOURCE 2 /* to accept POSIX 2 in old ANSI C standards */ #endif #endif #endif #endif #include #include #include #include #include #ifdef _WIN32 #ifdef __BORLANDC__ #define _getch getch #endif #ifndef _WIN32_WINNT #define _WIN32_WINNT 0x0500 #endif #include #include #include #include #include #define TINYFD_NOCCSUNICODE #define TINYFD_SLASH "\\" #else #include #include #include /* on old systems try instead */ #include #include #include /* on old systems try instead */ #define TINYFD_SLASH "/" #endif /* _WIN32 */ #include "tinyfiledialogs.h" #define MAX_PATH_OR_CMD 1024 /* _MAX_PATH or MAX_PATH */ #ifndef MAX_MULTIPLE_FILES #define MAX_MULTIPLE_FILES 1024 #endif #define LOW_MULTIPLE_FILES 32 char tinyfd_version[8] = "3.18.2"; /******************************************************************************************************/ /**************************************** UTF-8 on Windows ********************************************/ /******************************************************************************************************/ #ifdef _WIN32 /* if you want to use UTF-8 ( instead of the UTF-16/wchar_t functions at the end of tinyfiledialogs.h ) Make sure your code is really prepared for UTF-8 (on windows, functions like fopen() expect MBCS and not UTF-8) */ int tinyfd_winUtf8 = 1; /* on windows char strings can be 1:UTF-8(default) or 0:MBCS */ /* for MBCS change this to 0, here or in your code */ #endif /******************************************************************************************************/ /******************************************************************************************************/ /******************************************************************************************************/ int tinyfd_verbose = 0 ; /* on unix: prints the command line calls */ int tinyfd_silent = 1 ; /* 1 (default) or 0 : on unix, hide errors and warnings from called dialogs */ /* Curses dialogs are difficult to use, on windows they are only ascii and uses the unix backslah */ int tinyfd_allowCursesDialogs = 0 ; /* 0 (default) or 1 */ int tinyfd_forceConsole = 0 ; /* 0 (default) or 1 */ /* for unix & windows: 0 (graphic mode) or 1 (console mode). 0: try to use a graphic solution, if it fails then it uses console mode. 1: forces all dialogs into console mode even when the X server is present. it can use the package dialog or dialog.exe. on windows it only make sense for console applications */ int tinyfd_assumeGraphicDisplay = 0; /* 0 (default) or 1 */ /* some systems don't set the environment variable DISPLAY even when a graphic display is present. set this to 1 to tell tinyfiledialogs to assume the existence of a graphic display */ char tinyfd_response[1024]; /* if you pass "tinyfd_query" as aTitle, the functions will not display the dialogs but return 0 for console mode, 1 for graphic mode. tinyfd_response is then filled with the retain solution. possible values for tinyfd_response are (all lowercase) for graphic mode: windows_wchar windows applescript kdialog zenity zenity3 yad matedialog shellementary qarma python2-tkinter python3-tkinter python-dbus perl-dbus gxmessage gmessage xmessage xdialog gdialog dunst for console mode: dialog whiptail basicinput no_solution */ static int gWarningDisplayed = 0 ; static char gTitle[]="missing software! (we will try basic console input)"; #ifdef _WIN32 char tinyfd_needs[] = "\ ___________\n\ / \\ \n\ | tiny file |\n\ | dialogs |\n\ \\_____ ____/\n\ \\|\ \ntiny file dialogs on Windows needs:\ \n a graphic display\ \nor dialog.exe (curses console mode ** Disabled by default **)\ \nor a console for basic input"; #else char tinyfd_needs[] = "\ ___________\n\ / \\ \n\ | tiny file |\n\ | dialogs |\n\ \\_____ ____/\n\ \\|\ \ntiny file dialogs on UNIX needs:\ \n applescript or kdialog or yad or Xdialog\ \nor zenity (or matedialog or shellementary or qarma)\ \nor python (2 or 3) + tkinter + python-dbus (optional)\ \nor dialog (opens console if needed) ** Disabled by default **\ \nor xterm + bash (opens console for basic input)\ \nor existing console for basic input."; #endif #ifdef _MSC_VER #pragma warning(disable:4996) /* allows usage of strncpy, strcpy, strcat, sprintf, fopen */ #pragma warning(disable:4100) /* allows usage of strncpy, strcpy, strcat, sprintf, fopen */ #pragma warning(disable:4706) /* allows usage of strncpy, strcpy, strcat, sprintf, fopen */ #endif static int getenvDISPLAY(void) { return tinyfd_assumeGraphicDisplay || getenv("DISPLAY"); } static char * getCurDir(void) { static char lCurDir[MAX_PATH_OR_CMD]; return getcwd(lCurDir, sizeof(lCurDir)); } static char * getPathWithoutFinalSlash( char * aoDestination, /* make sure it is allocated, use _MAX_PATH */ char const * aSource) /* aoDestination and aSource can be the same */ { char const * lTmp ; if ( aSource ) { lTmp = strrchr(aSource, '/'); if (!lTmp) { lTmp = strrchr(aSource, '\\'); } if (lTmp) { strncpy(aoDestination, aSource, lTmp - aSource ); aoDestination[lTmp - aSource] = '\0'; } else { * aoDestination = '\0'; } } else { * aoDestination = '\0'; } return aoDestination; } static char * getLastName( char * aoDestination, /* make sure it is allocated */ char const * aSource) { /* copy the last name after '/' or '\' */ char const * lTmp ; if ( aSource ) { lTmp = strrchr(aSource, '/'); if (!lTmp) { lTmp = strrchr(aSource, '\\'); } if (lTmp) { strcpy(aoDestination, lTmp + 1); } else { strcpy(aoDestination, aSource); } } else { * aoDestination = '\0'; } return aoDestination; } static void ensureFinalSlash( char * aioString ) { if ( aioString && strlen( aioString ) ) { char * lastcar = aioString + strlen( aioString ) - 1 ; if ( strncmp( lastcar , TINYFD_SLASH , 1 ) ) { strcat( lastcar , TINYFD_SLASH ) ; } } } static void Hex2RGB( char const aHexRGB[8] , unsigned char aoResultRGB[3] ) { char lColorChannel[8] ; if ( aoResultRGB ) { if ( aHexRGB ) { strcpy(lColorChannel, aHexRGB ) ; aoResultRGB[2] = (unsigned char)strtoul(lColorChannel+5,NULL,16); lColorChannel[5] = '\0'; aoResultRGB[1] = (unsigned char)strtoul(lColorChannel+3,NULL,16); lColorChannel[3] = '\0'; aoResultRGB[0] = (unsigned char)strtoul(lColorChannel+1,NULL,16); /* printf("%d %d %d\n", aoResultRGB[0], aoResultRGB[1], aoResultRGB[2]); */ } else { aoResultRGB[0]=0; aoResultRGB[1]=0; aoResultRGB[2]=0; } } } static void RGB2Hex( unsigned char const aRGB[3], char aoResultHexRGB[8] ) { if ( aoResultHexRGB ) { if ( aRGB ) { #if (defined(__cplusplus ) && __cplusplus >= 201103L) || (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L) || defined(__clang__) sprintf(aoResultHexRGB, "#%02hhx%02hhx%02hhx", aRGB[0], aRGB[1], aRGB[2]); #else sprintf(aoResultHexRGB, "#%02hx%02hx%02hx", aRGB[0], aRGB[1], aRGB[2]); #endif /*printf("aoResultHexRGB %s\n", aoResultHexRGB);*/ } else { aoResultHexRGB[0]=0; aoResultHexRGB[1]=0; aoResultHexRGB[2]=0; } } } void tfd_replaceSubStr( char const * aSource, char const * aOldSubStr, char const * aNewSubStr, char * aoDestination ) { char const * pOccurence ; char const * p ; char const * lNewSubStr = "" ; size_t lOldSubLen = strlen( aOldSubStr ) ; if ( ! aSource ) { * aoDestination = '\0' ; return ; } if ( ! aOldSubStr ) { strcpy( aoDestination , aSource ) ; return ; } if ( aNewSubStr ) { lNewSubStr = aNewSubStr ; } p = aSource ; * aoDestination = '\0' ; while ( ( pOccurence = strstr( p , aOldSubStr ) ) != NULL ) { strncat( aoDestination , p , pOccurence - p ) ; strcat( aoDestination , lNewSubStr ) ; p = pOccurence + lOldSubLen ; } strcat( aoDestination , p ) ; } static int filenameValid( char const * aFileNameWithoutPath ) { if ( ! aFileNameWithoutPath || ! strlen(aFileNameWithoutPath) || strpbrk(aFileNameWithoutPath , "\\/:*?\"<>|") ) { return 0 ; } return 1 ; } #ifndef _WIN32 static int fileExists( char const * aFilePathAndName ) { FILE * lIn ; if ( ! aFilePathAndName || ! strlen(aFilePathAndName) ) { return 0 ; } lIn = fopen( aFilePathAndName , "r" ) ; if ( ! lIn ) { return 0 ; } fclose( lIn ) ; return 1 ; } #endif static void wipefile(char const * aFilename) { int i; struct stat st; FILE * lIn; if (stat(aFilename, &st) == 0) { if ((lIn = fopen(aFilename, "w"))) { for (i = 0; i < st.st_size; i++) { fputc('A', lIn); } fclose(lIn); } } } int tfd_quoteDetected(char const * aString) { char const * p; if (!aString) return 0; p = aString; if ( strchr(p, '\'')) { return 1; } if ( strchr(p, '\"')) { return 1; } if ( strchr(p, '`')) { return 1; } p = aString; while ((p = strchr(p, '$'))) { p ++ ; if ( ( * p == '(' ) || ( * p == '_' ) || isalpha( * p) ) return 1 ; } return 0; } char const * tinyfd_getGlobalChar(char const * aCharVariableName) /* to be called from C# (you don't need this in C or C++) */ { if (!aCharVariableName || !strlen(aCharVariableName)) return NULL; else if (!strcmp(aCharVariableName, "tinyfd_version")) return tinyfd_version; else if (!strcmp(aCharVariableName, "tinyfd_needs")) return tinyfd_needs; else if (!strcmp(aCharVariableName, "tinyfd_response")) return tinyfd_response; else return NULL ; } int tinyfd_getGlobalInt(char const * aIntVariableName) /* to be called from C# (you don't need this in C or C++) */ { if ( !aIntVariableName || !strlen(aIntVariableName) ) return -1 ; else if ( !strcmp(aIntVariableName, "tinyfd_verbose") ) return tinyfd_verbose ; else if ( !strcmp(aIntVariableName, "tinyfd_silent") ) return tinyfd_silent ; else if ( !strcmp(aIntVariableName, "tinyfd_allowCursesDialogs") ) return tinyfd_allowCursesDialogs ; else if ( !strcmp(aIntVariableName, "tinyfd_forceConsole") ) return tinyfd_forceConsole ; else if ( !strcmp(aIntVariableName, "tinyfd_assumeGraphicDisplay") ) return tinyfd_assumeGraphicDisplay ; #ifdef _WIN32 else if ( !strcmp(aIntVariableName, "tinyfd_winUtf8") ) return tinyfd_winUtf8 ; #endif else return -1; } int tinyfd_setGlobalInt(char const * aIntVariableName, int aValue) /* to be called from C# (you don't need this in C or C++) */ { if (!aIntVariableName || !strlen(aIntVariableName)) return -1 ; else if (!strcmp(aIntVariableName, "tinyfd_verbose")) { tinyfd_verbose = aValue; return tinyfd_verbose; } else if (!strcmp(aIntVariableName, "tinyfd_silent")) { tinyfd_silent = aValue; return tinyfd_silent; } else if (!strcmp(aIntVariableName, "tinyfd_allowCursesDialogs")) { tinyfd_allowCursesDialogs = aValue; return tinyfd_allowCursesDialogs; } else if (!strcmp(aIntVariableName, "tinyfd_forceConsole")) { tinyfd_forceConsole = aValue; return tinyfd_forceConsole; } else if (!strcmp(aIntVariableName, "tinyfd_assumeGraphicDisplay")) { tinyfd_assumeGraphicDisplay = aValue; return tinyfd_assumeGraphicDisplay; } #ifdef _WIN32 else if (!strcmp(aIntVariableName, "tinyfd_winUtf8")) { tinyfd_winUtf8 = aValue; return tinyfd_winUtf8; } #endif else return -1; } #ifdef _WIN32 static int powershellPresent(void) { /*only on vista and above (or installed on xp)*/ static int lPowershellPresent = -1; char lBuff[MAX_PATH_OR_CMD]; FILE* lIn; char const* lString = "powershell.exe"; if (lPowershellPresent < 0) { if (!(lIn = _popen("where powershell.exe", "r"))) { lPowershellPresent = 0; return 0; } while (fgets(lBuff, sizeof(lBuff), lIn) != NULL) { } _pclose(lIn); if (lBuff[strlen(lBuff) - 1] == '\n') { lBuff[strlen(lBuff) - 1] = '\0'; } if (strcmp(lBuff + strlen(lBuff) - strlen(lString), lString)) { lPowershellPresent = 0; } else { lPowershellPresent = 1; } } return lPowershellPresent; } static int windowsVersion(void) { #if !defined(__MINGW32__) || defined(__MINGW64_VERSION_MAJOR) typedef LONG NTSTATUS ; typedef NTSTATUS(WINAPI* RtlGetVersionPtr)(PRTL_OSVERSIONINFOW); HMODULE hMod; RtlGetVersionPtr lFxPtr; RTL_OSVERSIONINFOW lRovi = { 0 }; hMod = GetModuleHandleW(L"ntdll.dll"); if (hMod) { lFxPtr = (RtlGetVersionPtr)GetProcAddress(hMod, "RtlGetVersion"); if (lFxPtr) { lRovi.dwOSVersionInfoSize = sizeof(lRovi); if (!lFxPtr(&lRovi)) { return lRovi.dwMajorVersion; } } } #endif if (powershellPresent()) return 6; /*minimum is vista or installed on xp*/ return 0; } static void replaceChr(char * aString, char aOldChr, char aNewChr) { char * p; if (!aString) return; if (aOldChr == aNewChr) return; p = aString; while ((p = strchr(p, aOldChr))) { *p = aNewChr; p++; } return; } #if !defined(WC_ERR_INVALID_CHARS) /* undefined prior to Vista, so not yet in MINGW header file */ #define WC_ERR_INVALID_CHARS 0x00000000 /* 0x00000080 for MINGW maybe ? */ #endif static int sizeUtf16From8(char const * aUtf8string) { return MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, aUtf8string, -1, NULL, 0); } static int sizeUtf16FromMbcs(char const * aMbcsString) { return MultiByteToWideChar(CP_ACP, MB_ERR_INVALID_CHARS, aMbcsString, -1, NULL, 0); } static int sizeUtf8(wchar_t const * aUtf16string) { return WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, aUtf16string, -1, NULL, 0, NULL, NULL); } static int sizeMbcs(wchar_t const * aMbcsString) { int lRes = WideCharToMultiByte(CP_ACP, 0, aMbcsString, -1, NULL, 0, NULL, NULL); /* DWORD licic = GetLastError(); */ return lRes; } wchar_t* tinyfd_mbcsTo16(char const* aMbcsString) { static wchar_t* lMbcsString = NULL; int lSize; free(lMbcsString); if (!aMbcsString) { lMbcsString = NULL; return NULL; } lSize = sizeUtf16FromMbcs(aMbcsString); if (lSize) { lMbcsString = (wchar_t*) malloc(lSize * sizeof(wchar_t)); lSize = MultiByteToWideChar(CP_ACP, 0, aMbcsString, -1, lMbcsString, lSize); } else wcscpy(lMbcsString, L""); return lMbcsString; } wchar_t * tinyfd_utf8to16(char const * aUtf8string) { static wchar_t * lUtf16string = NULL; int lSize; free(lUtf16string); if (!aUtf8string) {lUtf16string = NULL; return NULL;} lSize = sizeUtf16From8(aUtf8string); if (lSize) { lUtf16string = (wchar_t*) malloc(lSize * sizeof(wchar_t)); lSize = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, aUtf8string, -1, lUtf16string, lSize); return lUtf16string; } else { /* let's try mbcs anyway */ lUtf16string = NULL; return tinyfd_mbcsTo16(aUtf8string); } } char * tinyfd_utf16toMbcs(wchar_t const * aUtf16string) { static char * lMbcsString = NULL; int lSize; free(lMbcsString); if (!aUtf16string) { lMbcsString = NULL; return NULL; } lSize = sizeMbcs(aUtf16string); if (lSize) { lMbcsString = (char*) malloc(lSize); lSize = WideCharToMultiByte(CP_ACP, 0, aUtf16string, -1, lMbcsString, lSize, NULL, NULL); } else strcpy(lMbcsString, ""); return lMbcsString; } char * tinyfd_utf8toMbcs(char const * aUtf8string) { wchar_t const * lUtf16string; lUtf16string = tinyfd_utf8to16(aUtf8string); return tinyfd_utf16toMbcs(lUtf16string); } char * tinyfd_utf16to8(wchar_t const * aUtf16string) { static char * lUtf8string = NULL; int lSize; free(lUtf8string); if (!aUtf16string) { lUtf8string = NULL; return NULL; } lSize = sizeUtf8(aUtf16string); if (lSize) { lUtf8string = (char*) malloc(lSize); lSize = WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, aUtf16string, -1, lUtf8string, lSize, NULL, NULL); } else strcpy(lUtf8string, ""); return lUtf8string; } char * tinyfd_mbcsTo8(char const * aMbcsString) { wchar_t const * lUtf16string; lUtf16string = tinyfd_mbcsTo16(aMbcsString); return tinyfd_utf16to8(lUtf16string); } void tinyfd_beep(void) { if (windowsVersion() > 5) Beep(440, 300); else MessageBeep(MB_OK); } static void wipefileW(wchar_t const * aFilename) { int i; FILE * lIn; #if (defined(__MINGW32_MAJOR_VERSION) && !defined(__MINGW64__) && (__MINGW32_MAJOR_VERSION <= 3)) || defined(__BORLANDC__) || defined(__WATCOMC__) struct _stat st; if (_wstat(aFilename, &st) == 0) #else struct __stat64 st; if (_wstat64(aFilename, &st) == 0) #endif { if ((lIn = _wfopen(aFilename, L"w"))) { for (i = 0; i < st.st_size; i++) { fputc('A', lIn); } fclose(lIn); } } } static wchar_t * getPathWithoutFinalSlashW( wchar_t * aoDestination, /* make sure it is allocated, use _MAX_PATH */ wchar_t const * aSource) /* aoDestination and aSource can be the same */ { wchar_t const * lTmp; if (aSource) { lTmp = wcsrchr(aSource, L'/'); if (!lTmp) { lTmp = wcsrchr(aSource, L'\\'); } if (lTmp) { wcsncpy(aoDestination, aSource, lTmp - aSource); aoDestination[lTmp - aSource] = L'\0'; } else { *aoDestination = L'\0'; } } else { *aoDestination = L'\0'; } return aoDestination; } static wchar_t * getLastNameW( wchar_t * aoDestination, /* make sure it is allocated */ wchar_t const * aSource) { /* copy the last name after '/' or '\' */ wchar_t const * lTmp; if (aSource) { lTmp = wcsrchr(aSource, L'/'); if (!lTmp) { lTmp = wcsrchr(aSource, L'\\'); } if (lTmp) { wcscpy(aoDestination, lTmp + 1); } else { wcscpy(aoDestination, aSource); } } else { *aoDestination = L'\0'; } return aoDestination; } static void Hex2RGBW(wchar_t const aHexRGB[8], unsigned char aoResultRGB[3]) { wchar_t lColorChannel[8]; if (aoResultRGB) { if (aHexRGB) { wcscpy(lColorChannel, aHexRGB); aoResultRGB[2] = (unsigned char)wcstoul(lColorChannel + 5, NULL, 16); lColorChannel[5] = '\0'; aoResultRGB[1] = (unsigned char)wcstoul(lColorChannel + 3, NULL, 16); lColorChannel[3] = '\0'; aoResultRGB[0] = (unsigned char)wcstoul(lColorChannel + 1, NULL, 16); /* printf("%d %d %d\n", aoResultRGB[0], aoResultRGB[1], aoResultRGB[2]); */ } else { aoResultRGB[0] = 0; aoResultRGB[1] = 0; aoResultRGB[2] = 0; } } } static void RGB2HexW( unsigned char const aRGB[3], wchar_t aoResultHexRGB[8]) { #if (defined(__cplusplus ) && __cplusplus >= 201103L) || (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L) || defined(__clang__) wchar_t const * const lPrintFormat = L"#%02hhx%02hhx%02hhx"; #else wchar_t const * const lPrintFormat = L"#%02hx%02hx%02hx"; #endif if (aoResultHexRGB) { if (aRGB) { /* wprintf(L"aoResultHexRGB %s\n", aoResultHexRGB); */ #if !defined(__BORLANDC__) && !defined(__TINYC__) && !(defined(__MINGW32__) && !defined(__MINGW64_VERSION_MAJOR)) swprintf(aoResultHexRGB, 8, lPrintFormat, aRGB[0], aRGB[1], aRGB[2]); #else swprintf(aoResultHexRGB, lPrintFormat, aRGB[0], aRGB[1], aRGB[2]); #endif } else { aoResultHexRGB[0] = 0; aoResultHexRGB[1] = 0; aoResultHexRGB[2] = 0; } } } static int dirExists(char const * aDirPath) { #if (defined(__MINGW32_MAJOR_VERSION) && !defined(__MINGW64__) && (__MINGW32_MAJOR_VERSION <= 3)) || defined(__BORLANDC__) || defined(__WATCOMC__) struct _stat lInfo; #else struct __stat64 lInfo; #endif wchar_t * lTmpWChar; int lStatRet; size_t lDirLen; if (!aDirPath) return 0; lDirLen = strlen(aDirPath); if (!lDirLen) return 1; if ( (lDirLen == 2) && (aDirPath[1] == ':') ) return 1; if (tinyfd_winUtf8) { lTmpWChar = tinyfd_utf8to16(aDirPath); #if (defined(__MINGW32_MAJOR_VERSION) && !defined(__MINGW64__) && (__MINGW32_MAJOR_VERSION <= 3)) || defined(__BORLANDC__) || defined(__WATCOMC__) lStatRet = _wstat(lTmpWChar, &lInfo); #else lStatRet = _wstat64(lTmpWChar, &lInfo); #endif if (lStatRet != 0) return 0; else if (lInfo.st_mode & S_IFDIR) return 1; else return 0; } #if (defined(__MINGW32_MAJOR_VERSION) && !defined(__MINGW64__) && (__MINGW32_MAJOR_VERSION <= 3)) || defined(__BORLANDC__) || defined(__WATCOMC__) else if (_stat(aDirPath, &lInfo) != 0) #else else if (_stat64(aDirPath, &lInfo) != 0) #endif return 0; else if (lInfo.st_mode & S_IFDIR) return 1; else return 0; } static int fileExists(char const * aFilePathAndName) { #if (defined(__MINGW32_MAJOR_VERSION) && !defined(__MINGW64__) && (__MINGW32_MAJOR_VERSION <= 3)) || defined(__BORLANDC__) || defined(__WATCOMC__) struct _stat lInfo; #else struct __stat64 lInfo; #endif wchar_t * lTmpWChar; int lStatRet; FILE * lIn; if (!aFilePathAndName || !strlen(aFilePathAndName)) { return 0; } if (tinyfd_winUtf8) { lTmpWChar = tinyfd_utf8to16(aFilePathAndName); #if (defined(__MINGW32_MAJOR_VERSION) && !defined(__MINGW64__) && (__MINGW32_MAJOR_VERSION <= 3)) || defined(__BORLANDC__) || defined(__WATCOMC__) lStatRet = _wstat(lTmpWChar, &lInfo); #else lStatRet = _wstat64(lTmpWChar, &lInfo); #endif if (lStatRet != 0) return 0; else if (lInfo.st_mode & _S_IFREG) return 1; else return 0; } else { lIn = fopen(aFilePathAndName, "r"); if (!lIn) { return 0; } fclose(lIn); return 1; } } static void replaceWchar(wchar_t * aString, wchar_t aOldChr, wchar_t aNewChr) { wchar_t * p; if (!aString) { return ; } if (aOldChr == aNewChr) { return ; } p = aString; while ((p = wcsrchr(p, aOldChr))) { *p = aNewChr; #ifdef TINYFD_NOCCSUNICODE p++; #endif p++; } return ; } static int quoteDetectedW(wchar_t const * aString) { wchar_t const * p; if (!aString) return 0; p = aString; while ((p = wcsrchr(p, L'\''))) { return 1; } p = aString; while ((p = wcsrchr(p, L'\"'))) { return 1; } return 0; } #endif /* _WIN32 */ /* source and destination can be the same or ovelap*/ static char * ensureFilesExist(char * aDestination, char const * aSourcePathsAndNames) { char * lDestination = aDestination; char const * p; char const * p2; size_t lLen; if (!aSourcePathsAndNames) { return NULL; } lLen = strlen(aSourcePathsAndNames); if (!lLen) { return NULL; } p = aSourcePathsAndNames; while ((p2 = strchr(p, '|')) != NULL) { lLen = p2 - p; memmove(lDestination, p, lLen); lDestination[lLen] = '\0'; if (fileExists(lDestination)) { lDestination += lLen; *lDestination = '|'; lDestination++; } p = p2 + 1; } if (fileExists(p)) { lLen = strlen(p); memmove(lDestination, p, lLen); lDestination[lLen] = '\0'; } else { *(lDestination - 1) = '\0'; } return aDestination; } #ifdef _WIN32 static int __stdcall EnumThreadWndProc(HWND hwnd, LPARAM lParam) { wchar_t lTitleName[MAX_PATH]; wchar_t const* lDialogTitle = (wchar_t const *) lParam; GetWindowTextW(hwnd, lTitleName, MAX_PATH); /* wprintf(L"lTitleName %ls \n", lTitleName); */ if (wcscmp(lDialogTitle, lTitleName) == 0) { SetWindowPos(hwnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE); return 0; } return 1; } static void hiddenConsoleW(wchar_t const * aString, wchar_t const * aDialogTitle, int aInFront) { STARTUPINFOW StartupInfo; PROCESS_INFORMATION ProcessInfo; if (!aString || !wcslen(aString) ) return; memset(&StartupInfo, 0, sizeof(StartupInfo)); StartupInfo.cb = sizeof(STARTUPINFOW); StartupInfo.dwFlags = STARTF_USESHOWWINDOW; StartupInfo.wShowWindow = SW_HIDE; if (!CreateProcessW(NULL, (LPWSTR)aString, NULL, NULL, FALSE, CREATE_NEW_CONSOLE, NULL, NULL, &StartupInfo, &ProcessInfo)) { return; /* GetLastError(); */ } WaitForInputIdle(ProcessInfo.hProcess, INFINITE); if (aInFront) { while (EnumWindows(EnumThreadWndProc, (LPARAM)aDialogTitle)) {} } WaitForSingleObject(ProcessInfo.hProcess, INFINITE); CloseHandle(ProcessInfo.hThread); CloseHandle(ProcessInfo.hProcess); } int tinyfd_messageBoxW( wchar_t const * aTitle, /* NULL or "" */ wchar_t const * aMessage, /* NULL or "" may contain \n and \t */ wchar_t const * aDialogType, /* "ok" "okcancel" "yesno" "yesnocancel" */ wchar_t const * aIconType, /* "info" "warning" "error" "question" */ int aDefaultButton) /* 0 for cancel/no , 1 for ok/yes , 2 for no in yesnocancel */ { int lBoxReturnValue; UINT aCode; if (aTitle&&!wcscmp(aTitle, L"tinyfd_query")){ strcpy(tinyfd_response, "windows_wchar"); return 1; } /*if (quoteDetectedW(aTitle)) return tinyfd_messageBoxW(L"INVALID TITLE WITH QUOTES", aMessage, aDialogType, aIconType, aDefaultButton); if (quoteDetectedW(aMessage)) return tinyfd_messageBoxW(aTitle, L"INVALID MESSAGE WITH QUOTES", aDialogType, aIconType, aDefaultButton);*/ if (aIconType && !wcscmp(L"warning", aIconType)) { aCode = MB_ICONWARNING; } else if (aIconType && !wcscmp(L"error", aIconType)) { aCode = MB_ICONERROR; } else if (aIconType && !wcscmp(L"question", aIconType)) { aCode = MB_ICONQUESTION; } else { aCode = MB_ICONINFORMATION; } if (aDialogType && !wcscmp(L"okcancel", aDialogType)) { aCode += MB_OKCANCEL; if (!aDefaultButton) { aCode += MB_DEFBUTTON2; } } else if (aDialogType && !wcscmp(L"yesno", aDialogType)) { aCode += MB_YESNO; if (!aDefaultButton) { aCode += MB_DEFBUTTON2; } } else if (aDialogType && !wcscmp(L"yesnocancel", aDialogType)) { aCode += MB_YESNOCANCEL; if (aDefaultButton == 1) { aCode += MB_DEFBUTTON1; } else if (aDefaultButton == 2) { aCode += MB_DEFBUTTON2; } else { aCode += MB_DEFBUTTON3; } } else { aCode += MB_OK; } aCode += MB_TOPMOST; lBoxReturnValue = MessageBoxW(GetForegroundWindow(), aMessage, aTitle, aCode); if ( (lBoxReturnValue == IDNO) && (aDialogType && !wcscmp(L"yesnocancel", aDialogType)) ) { return 2; } else if ( (lBoxReturnValue == IDOK) || (lBoxReturnValue == IDYES) ) { return 1; } else { return 0; } } /* int tinyfd_notifyPopupW_ORIGINAL( wchar_t const * aTitle, wchar_t const * aMessage, wchar_t const * aIconType) { wchar_t * lDialogString; size_t lTitleLen; size_t lMessageLen; size_t lDialogStringLen; if (aTitle && !wcscmp(aTitle, L"tinyfd_query")) { strcpy(tinyfd_response, "windows_wchar"); return 1; } if (quoteDetectedW(aTitle)) return tinyfd_notifyPopupW(L"INVALID TITLE WITH QUOTES", aMessage, aIconType); if (quoteDetectedW(aMessage)) return tinyfd_notifyPopupW(aTitle, L"INVALID MESSAGE WITH QUOTES", aIconType); lTitleLen = aTitle ? wcslen(aTitle) : 0; lMessageLen = aMessage ? wcslen(aMessage) : 0; lDialogStringLen = 3 * MAX_PATH_OR_CMD + lTitleLen + lMessageLen; lDialogString = (wchar_t *) malloc(2 * lDialogStringLen); if (!lDialogString) return 0; wcscpy(lDialogString, L"powershell.exe -executionpolicy bypass -command \"\ function Show-BalloonTip {\ [cmdletbinding()] \ param( \ [string]$Title = ' ', \ [string]$Message = ' ', \ [ValidateSet('info', 'warning', 'error')] \ [string]$IconType = 'info');\ [system.Reflection.Assembly]::LoadWithPartialName('System.Windows.Forms') | Out-Null ; \ $balloon = New-Object System.Windows.Forms.NotifyIcon ; \ $path = Get-Process -id $pid | Select-Object -ExpandProperty Path ; \ $icon = [System.Drawing.Icon]::ExtractAssociatedIcon($path) ;"); wcscat(lDialogString, L"\ $balloon.Icon = $icon ; \ $balloon.BalloonTipIcon = $IconType ; \ $balloon.BalloonTipText = $Message ; \ $balloon.BalloonTipTitle = $Title ; \ $balloon.Text = 'tinyfiledialogs' ; \ $balloon.Visible = $true ; \ $balloon.ShowBalloonTip(5000)};\ Show-BalloonTip"); if (aTitle && wcslen(aTitle)) { wcscat(lDialogString, L" -Title '"); wcscat(lDialogString, aTitle); wcscat(lDialogString, L"'"); } if (aMessage && wcslen(aMessage)) { wcscat(lDialogString, L" -Message '"); wcscat(lDialogString, aMessage); wcscat(lDialogString, L"'"); } if (aMessage && wcslen(aIconType)) { wcscat(lDialogString, L" -IconType '"); wcscat(lDialogString, aIconType); wcscat(lDialogString, L"'"); } wcscat(lDialogString, L"\""); hiddenConsoleW(lDialogString, aTitle, 0); free(lDialogString); return 1; }*/ /* return has only meaning for tinyfd_query */ int tinyfd_notifyPopupW( wchar_t const* aTitle, /* NULL or L"" */ wchar_t const* aMessage, /* NULL or L"" may contain \n \t */ wchar_t const* aIconType) /* L"info" L"warning" L"error" */ { wchar_t* lDialogString; size_t lTitleLen; size_t lMessageLen; size_t lDialogStringLen; FILE* lIn; if (aTitle && !wcscmp(aTitle, L"tinyfd_query")) { strcpy(tinyfd_response, "windows_wchar"); return 1; } if (quoteDetectedW(aTitle)) return tinyfd_notifyPopupW(L"INVALID TITLE WITH QUOTES", aMessage, aIconType); if (quoteDetectedW(aMessage)) return tinyfd_notifyPopupW(aTitle, L"INVALID MESSAGE WITH QUOTES", aIconType); lTitleLen = aTitle ? wcslen(aTitle) : 0; lMessageLen = aMessage ? wcslen(aMessage) : 0; lDialogStringLen = 3 * MAX_PATH_OR_CMD + lTitleLen + lMessageLen; lDialogString = (wchar_t*)malloc(2 * lDialogStringLen); if (!lDialogString) return 0; swprintf(lDialogString, #if !defined(__BORLANDC__) && !defined(__TINYC__) && !(defined(__MINGW32__) && !defined(__MINGW64_VERSION_MAJOR)) lDialogStringLen, #endif L"%ls\\tinyfd.hta", _wgetenv(L"TEMP")); lIn = _wfopen(lDialogString, L"w"); if (!lIn) { free(lDialogString); return 0; } wcscpy(lDialogString, L"\n\ \n\ \n\ "); if ( aTitle && wcslen(aTitle) ) wcscat(lDialogString, aTitle); wcscat(lDialogString, L"\n\ \n\ \n\ \n\ \n\ \n\ \n\
\n"); wcscat(lDialogString, aMessage ? aMessage : L""); wcscat(lDialogString, L"\n\ \n\ \n\ "); fputws(lDialogString, lIn); fclose(lIn); if (aTitle && wcslen(aTitle)) { wcscat(lDialogString, L" -Title '"); wcscat(lDialogString, aTitle); wcscat(lDialogString, L"'"); } if (aMessage && wcslen(aMessage)) { wcscat(lDialogString, L" -Message '"); wcscat(lDialogString, aMessage); wcscat(lDialogString, L"'"); } if (aMessage && wcslen(aIconType)) { wcscat(lDialogString, L" -IconType '"); wcscat(lDialogString, aIconType); wcscat(lDialogString, L"'"); } wcscat(lDialogString, L"\""); /* wprintf ( L"lDialogString: %ls\n" , lDialogString ) ; */ wcscpy(lDialogString, L"cmd.exe /c mshta.exe \"%TEMP%\\tinyfd.hta\""); hiddenConsoleW(lDialogString, aTitle, 0); free(lDialogString); return 1; } wchar_t * tinyfd_inputBoxW( wchar_t const * aTitle, /* NULL or L"" */ wchar_t const * aMessage, /* NULL or L"" (\n and \t have no effect) */ wchar_t const * aDefaultInput) /* L"" , if NULL it's a passwordBox */ { static wchar_t lBuff[MAX_PATH_OR_CMD]; wchar_t * lDialogString; FILE * lIn; FILE * lFile; int lResult; size_t lTitleLen; size_t lMessageLen; size_t lDialogStringLen; if (aTitle&&!wcscmp(aTitle, L"tinyfd_query")){ strcpy(tinyfd_response, "windows_wchar"); return (wchar_t *)1; } if (quoteDetectedW(aTitle)) return tinyfd_inputBoxW(L"INVALID TITLE WITH QUOTES", aMessage, aDefaultInput); if (quoteDetectedW(aMessage)) return tinyfd_inputBoxW(aTitle, L"INVALID MESSAGE WITH QUOTES", aDefaultInput); if (quoteDetectedW(aDefaultInput)) return tinyfd_inputBoxW(aTitle, aMessage, L"INVALID DEFAULT_INPUT WITH QUOTES: use the GRAVE ACCENT \\x60 instead."); lTitleLen = aTitle ? wcslen(aTitle) : 0 ; lMessageLen = aMessage ? wcslen(aMessage) : 0 ; lDialogStringLen = 3 * MAX_PATH_OR_CMD + lTitleLen + lMessageLen; lDialogString = (wchar_t *) malloc(2 * lDialogStringLen); if (aDefaultInput) { swprintf(lDialogString, #if !defined(__BORLANDC__) && !defined(__TINYC__) && !(defined(__MINGW32__) && !defined(__MINGW64_VERSION_MAJOR)) lDialogStringLen, #endif L"%ls\\tinyfd.vbs", _wgetenv(L"TEMP")); } else { swprintf(lDialogString, #if !defined(__BORLANDC__) && !defined(__TINYC__) && !(defined(__MINGW32__) && !defined(__MINGW64_VERSION_MAJOR)) lDialogStringLen, #endif L"%ls\\tinyfd.hta", _wgetenv(L"TEMP")); } lIn = _wfopen(lDialogString, L"w"); if (!lIn) { free(lDialogString); return NULL; } if ( aDefaultInput ) { wcscpy(lDialogString, L"Dim result:result=InputBox(\""); if (aMessage && wcslen(aMessage)) { wcscpy(lBuff, aMessage); replaceWchar(lBuff, L'\n', L' '); wcscat(lDialogString, lBuff); } wcscat(lDialogString, L"\",\""); if (aTitle) wcscat(lDialogString, aTitle); wcscat(lDialogString, L"\",\""); if (aDefaultInput && wcslen(aDefaultInput)) { wcscpy(lBuff, aDefaultInput); replaceWchar(lBuff, L'\n', L' '); wcscat(lDialogString, lBuff); } wcscat(lDialogString, L"\"):If IsEmpty(result) then:WScript.Echo 0"); wcscat(lDialogString, L":Else: WScript.Echo \"1\" & result : End If"); } else { wcscpy(lDialogString, L"\n\ \n\ \n\ "); if (aTitle) wcscat(lDialogString, aTitle); wcscat(lDialogString, L"\n\ \n\ \n\ \n\ \n\ \n\ \n\ \n\ \n\ \n\ \n\
\n"); wcscat(lDialogString, aMessage ? aMessage : L""); wcscat(lDialogString, L"\n\ \n\ \n\ \n\
\n\

\n\ \n\
\n\
\n"); wcscat(lDialogString, L"\n\ \n\ \n\ \n\
\n\
\n\
\n\ \n\ \n\ " ) ; } fputws(lDialogString, lIn); fclose(lIn); if (aDefaultInput) { swprintf(lDialogString, #if !defined(__BORLANDC__) && !defined(__TINYC__) && !(defined(__MINGW32__) && !defined(__MINGW64_VERSION_MAJOR)) lDialogStringLen, #endif L"%ls\\tinyfd.txt",_wgetenv(L"TEMP")); #ifdef TINYFD_NOCCSUNICODE lFile = _wfopen(lDialogString, L"w"); fputc(0xFF, lFile); fputc(0xFE, lFile); #else lFile = _wfopen(lDialogString, L"wt, ccs=UNICODE"); /*or ccs=UTF-16LE*/ #endif fclose(lFile); wcscpy(lDialogString, L"cmd.exe /c cscript.exe //U //Nologo "); wcscat(lDialogString, L"\"%TEMP%\\tinyfd.vbs\" "); wcscat(lDialogString, L">> \"%TEMP%\\tinyfd.txt\""); } else { wcscpy(lDialogString, L"cmd.exe /c mshta.exe \"%TEMP%\\tinyfd.hta\""); } /* wprintf ( "lDialogString: %ls\n" , lDialogString ) ; */ hiddenConsoleW(lDialogString, aTitle, 1); swprintf(lDialogString, #if !defined(__BORLANDC__) && !defined(__TINYC__) && !(defined(__MINGW32__) && !defined(__MINGW64_VERSION_MAJOR)) lDialogStringLen, #endif L"%ls\\tinyfd.txt", _wgetenv(L"TEMP")); /* wprintf(L"lDialogString: %ls\n", lDialogString); */ #ifdef TINYFD_NOCCSUNICODE if (!(lIn = _wfopen(lDialogString, L"r"))) #else if (!(lIn = _wfopen(lDialogString, L"rt, ccs=UNICODE"))) /*or ccs=UTF-16LE*/ #endif { _wremove(lDialogString); free(lDialogString); return NULL; } memset(lBuff, 0, MAX_PATH_OR_CMD * sizeof(wchar_t) ); #ifdef TINYFD_NOCCSUNICODE fgets((char *)lBuff, 2*MAX_PATH_OR_CMD, lIn); #else fgetws(lBuff, MAX_PATH_OR_CMD, lIn); #endif fclose(lIn); wipefileW(lDialogString); _wremove(lDialogString); if (aDefaultInput) { swprintf(lDialogString, #if !defined(__BORLANDC__) && !defined(__TINYC__) && !(defined(__MINGW32__) && !defined(__MINGW64_VERSION_MAJOR)) lDialogStringLen, #endif L"%ls\\tinyfd.vbs", _wgetenv(L"TEMP")); } else { swprintf(lDialogString, #if !defined(__BORLANDC__) && !defined(__TINYC__) && !(defined(__MINGW32__) && !defined(__MINGW64_VERSION_MAJOR)) lDialogStringLen, #endif L"%ls\\tinyfd.hta", _wgetenv(L"TEMP")); } _wremove(lDialogString); free(lDialogString); /* wprintf( L"lBuff: %ls\n" , lBuff ) ; */ #ifdef TINYFD_NOCCSUNICODE lResult = !wcsncmp(lBuff+1, L"1", 1); #else lResult = !wcsncmp(lBuff, L"1", 1); #endif /* printf( "lResult: %d \n" , lResult ) ; */ if (!lResult) { return NULL ; } /* wprintf( "lBuff+1: %ls\n" , lBuff+1 ) ; */ #ifdef TINYFD_NOCCSUNICODE if (aDefaultInput) { lDialogStringLen = wcslen(lBuff) ; lBuff[lDialogStringLen - 1] = L'\0'; lBuff[lDialogStringLen - 2] = L'\0'; } return lBuff + 2; #else if (aDefaultInput) lBuff[wcslen(lBuff) - 1] = L'\0'; return lBuff + 1; #endif } wchar_t * tinyfd_saveFileDialogW( wchar_t const * aTitle, /* NULL or "" */ wchar_t const * aDefaultPathAndOrFile, /* NULL or "" */ int aNumOfFilterPatterns, /* 0 */ wchar_t const * const * aFilterPatterns, /* NULL or {"*.jpg","*.png"} */ wchar_t const * aSingleFilterDescription) /* NULL or "image files" */ { static wchar_t lBuff[MAX_PATH_OR_CMD]; wchar_t lDirname[MAX_PATH_OR_CMD]; wchar_t lDialogString[MAX_PATH_OR_CMD]; wchar_t lFilterPatterns[MAX_PATH_OR_CMD] = L""; wchar_t * p; wchar_t * lRetval; wchar_t const * ldefExt = NULL; int i; HRESULT lHResult; OPENFILENAMEW ofn = {0}; if (aTitle&&!wcscmp(aTitle, L"tinyfd_query")){ strcpy(tinyfd_response, "windows_wchar"); return (wchar_t *)1; } /*if (quoteDetectedW(aTitle)) return tinyfd_saveFileDialogW(L"INVALID TITLE WITH QUOTES", aDefaultPathAndOrFile, aNumOfFilterPatterns, aFilterPatterns, aSingleFilterDescription); if (quoteDetectedW(aDefaultPathAndOrFile)) return tinyfd_saveFileDialogW(aTitle, L"INVALID DEFAULT_PATH WITH QUOTES", aNumOfFilterPatterns, aFilterPatterns, aSingleFilterDescription); if (quoteDetectedW(aSingleFilterDescription)) return tinyfd_saveFileDialogW(aTitle, aDefaultPathAndOrFile, aNumOfFilterPatterns, aFilterPatterns, L"INVALID FILTER_DESCRIPTION WITH QUOTES"); for (i = 0; i < aNumOfFilterPatterns; i++) { if (quoteDetectedW(aFilterPatterns[i])) return tinyfd_saveFileDialogW(L"INVALID FILTER_PATTERN WITH QUOTES: use the GRAVE ACCENT \\x60 instead.", aDefaultPathAndOrFile, 0, NULL, NULL); }*/ lHResult = CoInitializeEx(NULL, 0); getPathWithoutFinalSlashW(lDirname, aDefaultPathAndOrFile); getLastNameW(lBuff, aDefaultPathAndOrFile); if (aNumOfFilterPatterns > 0) { ldefExt = aFilterPatterns[0]; if (aSingleFilterDescription && wcslen(aSingleFilterDescription)) { wcscpy(lFilterPatterns, aSingleFilterDescription); wcscat(lFilterPatterns, L"\n"); } wcscat(lFilterPatterns, aFilterPatterns[0]); for (i = 1; i < aNumOfFilterPatterns; i++) { wcscat(lFilterPatterns, L";"); wcscat(lFilterPatterns, aFilterPatterns[i]); } wcscat(lFilterPatterns, L"\n"); if (!(aSingleFilterDescription && wcslen(aSingleFilterDescription))) { wcscpy(lDialogString, lFilterPatterns); wcscat(lFilterPatterns, lDialogString); } wcscat(lFilterPatterns, L"All Files\n*.*\n"); p = lFilterPatterns; while ((p = wcschr(p, L'\n')) != NULL) { *p = L'\0'; p++; } } ofn.lStructSize = sizeof(OPENFILENAMEW); ofn.hwndOwner = GetForegroundWindow(); ofn.hInstance = 0; ofn.lpstrFilter = wcslen(lFilterPatterns) ? lFilterPatterns : NULL; ofn.lpstrCustomFilter = NULL; ofn.nMaxCustFilter = 0; ofn.nFilterIndex = 1; ofn.lpstrFile = lBuff; ofn.nMaxFile = MAX_PATH_OR_CMD; ofn.lpstrFileTitle = NULL; ofn.nMaxFileTitle = MAX_PATH_OR_CMD/2; ofn.lpstrInitialDir = wcslen(lDirname) ? lDirname : NULL; ofn.lpstrTitle = aTitle && wcslen(aTitle) ? aTitle : NULL; ofn.Flags = OFN_OVERWRITEPROMPT | OFN_NOCHANGEDIR | OFN_PATHMUSTEXIST ; ofn.nFileOffset = 0; ofn.nFileExtension = 0; ofn.lpstrDefExt = ldefExt; ofn.lCustData = 0L; ofn.lpfnHook = NULL; ofn.lpTemplateName = NULL; if (GetSaveFileNameW(&ofn) == 0) { lRetval = NULL; } else { lRetval = lBuff; } if (lHResult == S_OK || lHResult == S_FALSE) { CoUninitialize(); } return lRetval; } wchar_t * tinyfd_openFileDialogW( wchar_t const * aTitle, /* NULL or "" */ wchar_t const * aDefaultPathAndOrFile, /* NULL or "" */ int aNumOfFilterPatterns, /* 0 */ wchar_t const * const * aFilterPatterns, /* NULL or {"*.jpg","*.png"} */ wchar_t const * aSingleFilterDescription, /* NULL or "image files" */ int aAllowMultipleSelects) /* 0 or 1 ; -1 to free allocated memory and return */ { size_t lLengths[MAX_MULTIPLE_FILES]; wchar_t lDirname[MAX_PATH_OR_CMD]; wchar_t lFilterPatterns[MAX_PATH_OR_CMD] = L""; wchar_t lDialogString[MAX_PATH_OR_CMD]; wchar_t * lPointers[MAX_MULTIPLE_FILES+1]; wchar_t * p; int i, j; size_t lBuffLen; DWORD lFullBuffLen; HRESULT lHResult; OPENFILENAMEW ofn = { 0 }; static wchar_t * lBuff = NULL; free(lBuff); lBuff = NULL; if (aAllowMultipleSelects < 0) return (wchar_t *)0; if (aTitle&&!wcscmp(aTitle, L"tinyfd_query")){ strcpy(tinyfd_response, "windows_wchar"); return (wchar_t *)1; } /*if (quoteDetectedW(aTitle)) return tinyfd_openFileDialogW(L"INVALID TITLE WITH QUOTES", aDefaultPathAndOrFile, aNumOfFilterPatterns, aFilterPatterns, aSingleFilterDescription, aAllowMultipleSelects); if (quoteDetectedW(aDefaultPathAndOrFile)) return tinyfd_openFileDialogW(aTitle, L"INVALID DEFAULT_PATH WITH QUOTES", aNumOfFilterPatterns, aFilterPatterns, aSingleFilterDescription, aAllowMultipleSelects); if (quoteDetectedW(aSingleFilterDescription)) return tinyfd_openFileDialogW(aTitle, aDefaultPathAndOrFile, aNumOfFilterPatterns, aFilterPatterns, L"INVALID FILTER_DESCRIPTION WITH QUOTES", aAllowMultipleSelects); for (i = 0; i < aNumOfFilterPatterns; i++) { if (quoteDetectedW(aFilterPatterns[i])) return tinyfd_openFileDialogW(L"INVALID FILTER_PATTERN WITH QUOTES: use the GRAVE ACCENT \\x60 instead.", aDefaultPathAndOrFile, 0, NULL, NULL, aAllowMultipleSelects); }*/ if (aAllowMultipleSelects) { lFullBuffLen = MAX_MULTIPLE_FILES * MAX_PATH_OR_CMD + 1; lBuff = (wchar_t*) malloc(lFullBuffLen * sizeof(wchar_t)); if (!lBuff) { lFullBuffLen = LOW_MULTIPLE_FILES * MAX_PATH_OR_CMD + 1; lBuff = (wchar_t*) malloc( lFullBuffLen * sizeof(wchar_t)); } } else { lFullBuffLen = MAX_PATH_OR_CMD + 1; lBuff = (wchar_t*) malloc(lFullBuffLen * sizeof(wchar_t)); } if (!lBuff) return NULL; lHResult = CoInitializeEx(NULL, 0); getPathWithoutFinalSlashW(lDirname, aDefaultPathAndOrFile); getLastNameW(lBuff, aDefaultPathAndOrFile); if (aNumOfFilterPatterns > 0) { if (aSingleFilterDescription && wcslen(aSingleFilterDescription)) { wcscpy(lFilterPatterns, aSingleFilterDescription); wcscat(lFilterPatterns, L"\n"); } wcscat(lFilterPatterns, aFilterPatterns[0]); for (i = 1; i < aNumOfFilterPatterns; i++) { wcscat(lFilterPatterns, L";"); wcscat(lFilterPatterns, aFilterPatterns[i]); } wcscat(lFilterPatterns, L"\n"); if (!(aSingleFilterDescription && wcslen(aSingleFilterDescription))) { wcscpy(lDialogString, lFilterPatterns); wcscat(lFilterPatterns, lDialogString); } wcscat(lFilterPatterns, L"All Files\n*.*\n"); p = lFilterPatterns; while ((p = wcschr(p, L'\n')) != NULL) { *p = L'\0'; p++; } } ofn.lStructSize = sizeof(OPENFILENAME); ofn.hwndOwner = GetForegroundWindow(); ofn.hInstance = 0; ofn.lpstrFilter = wcslen(lFilterPatterns) ? lFilterPatterns : NULL; ofn.lpstrCustomFilter = NULL; ofn.nMaxCustFilter = 0; ofn.nFilterIndex = 1; ofn.lpstrFile = lBuff; ofn.nMaxFile = lFullBuffLen; ofn.lpstrFileTitle = NULL; ofn.nMaxFileTitle = MAX_PATH_OR_CMD / 2; ofn.lpstrInitialDir = wcslen(lDirname) ? lDirname : NULL; ofn.lpstrTitle = aTitle && wcslen(aTitle) ? aTitle : NULL; ofn.Flags = OFN_EXPLORER | OFN_NOCHANGEDIR | OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST; ofn.nFileOffset = 0; ofn.nFileExtension = 0; ofn.lpstrDefExt = NULL; ofn.lCustData = 0L; ofn.lpfnHook = NULL; ofn.lpTemplateName = NULL; if (aAllowMultipleSelects) { ofn.Flags |= OFN_ALLOWMULTISELECT; } if (GetOpenFileNameW(&ofn) == 0) { free(lBuff); lBuff = NULL; } else { lBuffLen = wcslen(lBuff); lPointers[0] = lBuff + lBuffLen + 1; if (aAllowMultipleSelects && (lPointers[0][0] != L'\0')) { i = 0; do { lLengths[i] = wcslen(lPointers[i]); lPointers[i + 1] = lPointers[i] + lLengths[i] + 1; i++; } while (lPointers[i][0] != L'\0' && i < MAX_MULTIPLE_FILES ); if (i > MAX_MULTIPLE_FILES) { free(lBuff); lBuff = NULL; } else { i--; p = lBuff + lFullBuffLen - 1; *p = L'\0'; for (j = i; j >= 0; j--) { p -= lLengths[j]; memmove(p, lPointers[j], lLengths[j] * sizeof(wchar_t)); p--; *p = L'\\'; p -= lBuffLen; memmove(p, lBuff, lBuffLen*sizeof(wchar_t)); p--; *p = L'|'; } p++; wcscpy(lBuff, p); lBuffLen = wcslen(lBuff); } } if (lBuff) lBuff = (wchar_t*)(realloc(lBuff, (lBuffLen + 1) * sizeof(wchar_t))); } if (lHResult == S_OK || lHResult == S_FALSE) { CoUninitialize(); } return lBuff; } BOOL CALLBACK BrowseCallbackProcW_enum(HWND hWndChild, LPARAM lParam) { wchar_t buf[255]; (void)lParam; GetClassNameW(hWndChild, buf, sizeof(buf)); if (wcscmp(buf, L"SysTreeView32") == 0) { HTREEITEM hNode = TreeView_GetSelection(hWndChild); TreeView_EnsureVisible(hWndChild, hNode); return FALSE; } return TRUE; } static int __stdcall BrowseCallbackProcW(HWND hwnd, UINT uMsg, LPARAM lp, LPARAM pData) { (void)lp; switch (uMsg) { case BFFM_INITIALIZED: SendMessage(hwnd, BFFM_SETSELECTIONW, TRUE, (LPARAM)pData); break; case BFFM_SELCHANGED: EnumChildWindows(hwnd, BrowseCallbackProcW_enum, 0); } return 0; } wchar_t * tinyfd_selectFolderDialogW( wchar_t const * aTitle, /* NULL or "" */ wchar_t const * aDefaultPath) /* NULL or "" */ { static wchar_t lBuff[MAX_PATH_OR_CMD]; wchar_t * lRetval; BROWSEINFOW bInfo; LPITEMIDLIST lpItem; HRESULT lHResult; if (aTitle&&!wcscmp(aTitle, L"tinyfd_query")){ strcpy(tinyfd_response, "windows_wchar"); return (wchar_t *)1; } /*if (quoteDetectedW(aTitle)) return tinyfd_selectFolderDialogW(L"INVALID TITLE WITH QUOTES", aDefaultPath); if (quoteDetectedW(aDefaultPath)) return tinyfd_selectFolderDialogW(aTitle, L"INVALID DEFAULT_PATH WITH QUOTES");*/ lHResult = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED); bInfo.hwndOwner = GetForegroundWindow(); bInfo.pidlRoot = NULL; bInfo.pszDisplayName = lBuff; bInfo.lpszTitle = aTitle && wcslen(aTitle) ? aTitle : NULL; if (lHResult == S_OK || lHResult == S_FALSE) { bInfo.ulFlags = BIF_USENEWUI; } bInfo.lpfn = BrowseCallbackProcW; bInfo.lParam = (LPARAM)aDefaultPath; bInfo.iImage = -1; lpItem = SHBrowseForFolderW(&bInfo); if (!lpItem) { lRetval = NULL; } else { SHGetPathFromIDListW(lpItem, lBuff); lRetval = lBuff ; } if (lHResult == S_OK || lHResult == S_FALSE) { CoUninitialize(); } return lRetval; } wchar_t * tinyfd_colorChooserW( wchar_t const * aTitle, /* NULL or "" */ wchar_t const * aDefaultHexRGB, /* NULL or "#FF0000"*/ unsigned char const aDefaultRGB[3], /* { 0 , 255 , 255 } */ unsigned char aoResultRGB[3]) /* { 0 , 0 , 0 } */ { static wchar_t lResultHexRGB[8]; CHOOSECOLORW cc; COLORREF crCustColors[16]; unsigned char lDefaultRGB[3]; int lRet; HRESULT lHResult; if (aTitle&&!wcscmp(aTitle, L"tinyfd_query")){ strcpy(tinyfd_response, "windows_wchar"); return (wchar_t *)1; } /*if (quoteDetectedW(aTitle)) return tinyfd_colorChooserW(L"INVALID TITLE WITH QUOTES", aDefaultHexRGB, aDefaultRGB, aoResultRGB); if (quoteDetectedW(aDefaultHexRGB)) return tinyfd_colorChooserW(aTitle, L"INVALID DEFAULT_HEX_RGB WITH QUOTES: use the GRAVE ACCENT \\x60 instead.", aDefaultRGB, aoResultRGB);*/ lHResult = CoInitializeEx(NULL, 0); if ( aDefaultHexRGB && wcslen(aDefaultHexRGB) ) { Hex2RGBW(aDefaultHexRGB, lDefaultRGB); } else { lDefaultRGB[0] = aDefaultRGB[0]; lDefaultRGB[1] = aDefaultRGB[1]; lDefaultRGB[2] = aDefaultRGB[2]; } /* we can't use aTitle */ cc.lStructSize = sizeof(CHOOSECOLOR); cc.hwndOwner = GetForegroundWindow(); cc.hInstance = NULL; cc.rgbResult = RGB(lDefaultRGB[0], lDefaultRGB[1], lDefaultRGB[2]); cc.lpCustColors = crCustColors; cc.Flags = CC_RGBINIT | CC_FULLOPEN | CC_ANYCOLOR ; cc.lCustData = 0; cc.lpfnHook = NULL; cc.lpTemplateName = NULL; lRet = ChooseColorW(&cc); if (!lRet) { return NULL; } aoResultRGB[0] = GetRValue(cc.rgbResult); aoResultRGB[1] = GetGValue(cc.rgbResult); aoResultRGB[2] = GetBValue(cc.rgbResult); RGB2HexW(aoResultRGB, lResultHexRGB); if (lHResult == S_OK || lHResult == S_FALSE) { CoUninitialize(); } return lResultHexRGB; } static int messageBoxWinGui( char const * aTitle, /* NULL or "" */ char const * aMessage, /* NULL or "" may contain \n and \t */ char const * aDialogType, /* "ok" "okcancel" "yesno" "yesnocancel" */ char const * aIconType, /* "info" "warning" "error" "question" */ int aDefaultButton) /* 0 for cancel/no , 1 for ok/yes , 2 for no in yesnocancel */ { int lIntRetVal; wchar_t lTitle[128] = L""; wchar_t * lMessage = NULL; wchar_t lDialogType[16] = L""; wchar_t lIconType[16] = L""; wchar_t * lTmpWChar; if (aTitle) { if (tinyfd_winUtf8) lTmpWChar = tinyfd_utf8to16(aTitle); else lTmpWChar = tinyfd_mbcsTo16(aTitle); wcscpy(lTitle, lTmpWChar); } if (aMessage) { if (tinyfd_winUtf8) lTmpWChar = tinyfd_utf8to16(aMessage); else lTmpWChar = tinyfd_mbcsTo16(aMessage); lMessage = (wchar_t *) malloc((wcslen(lTmpWChar) + 1)* sizeof(wchar_t)); if (lMessage) wcscpy(lMessage, lTmpWChar); } if (aDialogType) { if (tinyfd_winUtf8) lTmpWChar = tinyfd_utf8to16(aDialogType); else lTmpWChar = tinyfd_mbcsTo16(aDialogType); wcscpy(lDialogType, lTmpWChar); } if (aIconType) { if (tinyfd_winUtf8) lTmpWChar = tinyfd_utf8to16(aIconType); else lTmpWChar = tinyfd_mbcsTo16(aIconType); wcscpy(lIconType, lTmpWChar); } lIntRetVal = tinyfd_messageBoxW(lTitle, lMessage, lDialogType, lIconType, aDefaultButton); free(lMessage); return lIntRetVal; } static int notifyWinGui( char const * aTitle, /* NULL or "" */ char const * aMessage, /* NULL or "" may NOT contain \n nor \t */ char const * aIconType) { wchar_t lTitle[128] = L""; wchar_t * lMessage = NULL; wchar_t lIconType[16] = L""; wchar_t * lTmpWChar; if (aTitle) { if (tinyfd_winUtf8) lTmpWChar = tinyfd_utf8to16(aTitle); else lTmpWChar = tinyfd_mbcsTo16(aTitle); wcscpy(lTitle, lTmpWChar); } if (aMessage) { if (tinyfd_winUtf8) lTmpWChar = tinyfd_utf8to16(aMessage); else lTmpWChar = tinyfd_mbcsTo16(aMessage); lMessage = (wchar_t *) malloc((wcslen(lTmpWChar) + 1)* sizeof(wchar_t)); if (lMessage) wcscpy(lMessage, lTmpWChar); } if (aIconType) { if (tinyfd_winUtf8) lTmpWChar = tinyfd_utf8to16(aIconType); else lTmpWChar = tinyfd_mbcsTo16(aIconType); wcscpy(lIconType, lTmpWChar); } tinyfd_notifyPopupW(lTitle, lMessage, lIconType); free(lMessage); return 1; } static int inputBoxWinGui( char * aoBuff, char const * aTitle, /* NULL or "" */ char const * aMessage, /* NULL or "" may NOT contain \n nor \t */ char const * aDefaultInput) /* "" , if NULL it's a passwordBox */ { wchar_t lTitle[128] = L""; wchar_t * lMessage = NULL; wchar_t lDefaultInput[MAX_PATH_OR_CMD] = L""; wchar_t * lTmpWChar; char * lTmpChar; if (aTitle) { if (tinyfd_winUtf8) lTmpWChar = tinyfd_utf8to16(aTitle); else lTmpWChar = tinyfd_mbcsTo16(aTitle); wcscpy(lTitle, lTmpWChar); } if (aMessage) { if (tinyfd_winUtf8) lTmpWChar = tinyfd_utf8to16(aMessage); else lTmpWChar = tinyfd_mbcsTo16(aMessage); lMessage = (wchar_t *) malloc((wcslen(lTmpWChar) + 1)* sizeof(wchar_t)); if (lMessage) wcscpy(lMessage, lTmpWChar); } if (aDefaultInput) { if (tinyfd_winUtf8) lTmpWChar = tinyfd_utf8to16(aDefaultInput); else lTmpWChar = tinyfd_mbcsTo16(aDefaultInput); wcscpy(lDefaultInput, lTmpWChar); lTmpWChar = tinyfd_inputBoxW(lTitle, lMessage, lDefaultInput); } else lTmpWChar = tinyfd_inputBoxW(lTitle, lMessage, NULL); free(lMessage); if (!lTmpWChar) { aoBuff[0] = '\0'; return 0; } if (tinyfd_winUtf8) lTmpChar = tinyfd_utf16to8(lTmpWChar); else lTmpChar = tinyfd_utf16toMbcs(lTmpWChar); strcpy(aoBuff, lTmpChar); return 1; } static char * saveFileDialogWinGui( char * aoBuff, char const * aTitle, /* NULL or "" */ char const * aDefaultPathAndOrFile, /* NULL or "" */ int aNumOfFilterPatterns, /* 0 */ char const * const * aFilterPatterns, /* NULL or {"*.jpg","*.png"} */ char const * aSingleFilterDescription) /* NULL or "image files" */ { wchar_t lTitle[128] = L""; wchar_t lDefaultPathAndFile[MAX_PATH_OR_CMD] = L""; wchar_t lSingleFilterDescription[128] = L""; wchar_t * * lFilterPatterns; wchar_t * lTmpWChar; char * lTmpChar; int i; lFilterPatterns = (wchar_t **) malloc(aNumOfFilterPatterns*sizeof(wchar_t *)); for (i = 0; i < aNumOfFilterPatterns; i++) { if (tinyfd_winUtf8) lTmpWChar = tinyfd_utf8to16(aFilterPatterns[i]); else lTmpWChar = tinyfd_mbcsTo16(aFilterPatterns[i]); lFilterPatterns[i] = (wchar_t *) malloc((wcslen(lTmpWChar) + 1) * sizeof(wchar_t *)); if (lFilterPatterns[i]) wcscpy(lFilterPatterns[i], lTmpWChar); } if (aTitle) { if (tinyfd_winUtf8) lTmpWChar = tinyfd_utf8to16(aTitle); else lTmpWChar = tinyfd_mbcsTo16(aTitle); wcscpy(lTitle, lTmpWChar); } if (aDefaultPathAndOrFile) { if (tinyfd_winUtf8) lTmpWChar = tinyfd_utf8to16(aDefaultPathAndOrFile); else lTmpWChar = tinyfd_mbcsTo16(aDefaultPathAndOrFile); wcscpy(lDefaultPathAndFile, lTmpWChar); } if (aSingleFilterDescription) { if (tinyfd_winUtf8) lTmpWChar = tinyfd_utf8to16(aSingleFilterDescription); else lTmpWChar = tinyfd_mbcsTo16(aSingleFilterDescription); wcscpy(lSingleFilterDescription, lTmpWChar); } lTmpWChar = tinyfd_saveFileDialogW( lTitle, lDefaultPathAndFile, aNumOfFilterPatterns, (wchar_t const**) lFilterPatterns, /*stupid cast for gcc*/ lSingleFilterDescription); for (i = 0; i < aNumOfFilterPatterns; i++) { free(lFilterPatterns[i]); } free(lFilterPatterns); if (!lTmpWChar) { return NULL; } if (tinyfd_winUtf8) lTmpChar = tinyfd_utf16to8(lTmpWChar); else lTmpChar = tinyfd_utf16toMbcs(lTmpWChar); strcpy(aoBuff, lTmpChar); if (tinyfd_winUtf8) (void)tinyfd_utf16to8(NULL); else (void)tinyfd_utf16toMbcs(NULL); return aoBuff; } static char * openFileDialogWinGui( char const * aTitle, /* NULL or "" */ char const * aDefaultPathAndOrFile, /* NULL or "" */ int aNumOfFilterPatterns, /* 0 */ char const * const * aFilterPatterns, /* NULL or {"*.jpg","*.png"} */ char const * aSingleFilterDescription, /* NULL or "image files" */ int aAllowMultipleSelects) /* 0 or 1 */ { wchar_t lTitle[128] = L""; wchar_t lDefaultPathAndFile[MAX_PATH_OR_CMD] = L""; wchar_t lSingleFilterDescription[128] = L""; wchar_t * * lFilterPatterns; wchar_t * lTmpWChar; char * lTmpChar; int i; lFilterPatterns = (wchar_t * *) malloc(aNumOfFilterPatterns*sizeof(wchar_t *)); for (i = 0; i < aNumOfFilterPatterns; i++) { if (tinyfd_winUtf8) lTmpWChar = tinyfd_utf8to16(aFilterPatterns[i]); else lTmpWChar = tinyfd_mbcsTo16(aFilterPatterns[i]); lFilterPatterns[i] = (wchar_t *) malloc((wcslen(lTmpWChar) + 1)*sizeof(wchar_t *)); if (lFilterPatterns[i]) wcscpy(lFilterPatterns[i], lTmpWChar); } if (aTitle) { if (tinyfd_winUtf8) lTmpWChar = tinyfd_utf8to16(aTitle); else lTmpWChar = tinyfd_mbcsTo16(aTitle); wcscpy(lTitle, lTmpWChar); } if (aDefaultPathAndOrFile) { if (tinyfd_winUtf8) lTmpWChar = tinyfd_utf8to16(aDefaultPathAndOrFile); else lTmpWChar = tinyfd_mbcsTo16(aDefaultPathAndOrFile); wcscpy(lDefaultPathAndFile, lTmpWChar); } if (aSingleFilterDescription) { if (tinyfd_winUtf8) lTmpWChar = tinyfd_utf8to16(aSingleFilterDescription); else lTmpWChar = tinyfd_mbcsTo16(aSingleFilterDescription); wcscpy(lSingleFilterDescription, lTmpWChar); } lTmpWChar = tinyfd_openFileDialogW( lTitle, lDefaultPathAndFile, aNumOfFilterPatterns, (wchar_t const**) lFilterPatterns, /*stupid cast for gcc*/ lSingleFilterDescription, aAllowMultipleSelects); for (i = 0; i < aNumOfFilterPatterns; i++) { free(lFilterPatterns[i]); } free(lFilterPatterns); if (!lTmpWChar) return NULL; if (tinyfd_winUtf8) lTmpChar = tinyfd_utf16to8(lTmpWChar); else lTmpChar = tinyfd_utf16toMbcs(lTmpWChar); (void)tinyfd_openFileDialogW(NULL, NULL, 0, NULL, NULL, -1); return lTmpChar; } static char * selectFolderDialogWinGui( char * aoBuff, char const * aTitle, /* NULL or "" */ char const * aDefaultPath) /* NULL or "" */ { wchar_t lTitle[128] = L""; wchar_t lDefaultPath[MAX_PATH_OR_CMD] = L""; wchar_t * lTmpWChar; char * lTmpChar; if (aTitle) { if (tinyfd_winUtf8) lTmpWChar = tinyfd_utf8to16(aTitle); else lTmpWChar = tinyfd_mbcsTo16(aTitle); wcscpy(lTitle, lTmpWChar); } if (aDefaultPath) { if (tinyfd_winUtf8) lTmpWChar = tinyfd_utf8to16(aDefaultPath); else lTmpWChar = tinyfd_mbcsTo16(aDefaultPath); wcscpy(lDefaultPath, lTmpWChar); } lTmpWChar = tinyfd_selectFolderDialogW( lTitle, lDefaultPath); if (!lTmpWChar) { return NULL; } if (tinyfd_winUtf8) lTmpChar = tinyfd_utf16to8(lTmpWChar); else lTmpChar = tinyfd_utf16toMbcs(lTmpWChar); strcpy(aoBuff, lTmpChar); return aoBuff; } static char * colorChooserWinGui( char const * aTitle, /* NULL or "" */ char const * aDefaultHexRGB, /* NULL or "#FF0000"*/ unsigned char const aDefaultRGB[3], /* { 0 , 255 , 255 } */ unsigned char aoResultRGB[3]) /* { 0 , 0 , 0 } */ { static char lResultHexRGB[8]; wchar_t lTitle[128]; wchar_t * lTmpWChar; char * lTmpChar; wchar_t lDefaultHexRGB[16] = L""; if (aTitle) { if (tinyfd_winUtf8) lTmpWChar = tinyfd_utf8to16(aTitle); else lTmpWChar = tinyfd_mbcsTo16(aTitle); wcscpy(lTitle, lTmpWChar); } if (aDefaultHexRGB) { if (tinyfd_winUtf8) lTmpWChar = tinyfd_utf8to16(aDefaultHexRGB); else lTmpWChar = tinyfd_mbcsTo16(aDefaultHexRGB); wcscpy(lDefaultHexRGB, lTmpWChar); } lTmpWChar = tinyfd_colorChooserW( lTitle, lDefaultHexRGB, aDefaultRGB, aoResultRGB ); if (!lTmpWChar) { return NULL; } if (tinyfd_winUtf8) lTmpChar = tinyfd_utf16to8(lTmpWChar); else lTmpChar = tinyfd_utf16toMbcs(lTmpWChar); strcpy(lResultHexRGB, lTmpChar); return lResultHexRGB; } static int dialogPresent(void) { static int lDialogPresent = -1 ; char lBuff[MAX_PATH_OR_CMD] ; FILE * lIn ; char const * lString = "dialog.exe"; if (!tinyfd_allowCursesDialogs) return 0; if (lDialogPresent < 0) { lIn = _popen("where dialog.exe", "r"); if ( ! lIn ) { lDialogPresent = 0 ; return 0 ; } while ( fgets( lBuff , sizeof( lBuff ) , lIn ) != NULL ) {} _pclose( lIn ) ; if ( lBuff[strlen( lBuff ) -1] == '\n' ) { lBuff[strlen( lBuff ) -1] = '\0' ; } if ( strcmp(lBuff+strlen(lBuff)-strlen(lString),lString) ) { lDialogPresent = 0 ; } else { lDialogPresent = 1 ; } } return lDialogPresent; } static int messageBoxWinConsole( char const * aTitle , /* NULL or "" */ char const * aMessage , /* NULL or "" may contain \n and \t */ char const * aDialogType , /* "ok" "okcancel" "yesno" "yesnocancel" */ char const * aIconType , /* "info" "warning" "error" "question" */ int aDefaultButton ) /* 0 for cancel/no , 1 for ok/yes , 2 for no in yesnocancel */ { char lDialogString[MAX_PATH_OR_CMD]; char lDialogFile[MAX_PATH_OR_CMD]; FILE * lIn; char lBuff[MAX_PATH_OR_CMD] = ""; (void)aIconType; strcpy(lDialogString, "dialog "); if (aTitle && strlen(aTitle)) { strcat(lDialogString, "--title \"") ; strcat(lDialogString, aTitle) ; strcat(lDialogString, "\" ") ; } if ( aDialogType && ( !strcmp( "okcancel" , aDialogType ) || !strcmp("yesno", aDialogType) || !strcmp("yesnocancel", aDialogType) ) ) { strcat(lDialogString, "--backtitle \"") ; strcat(lDialogString, "tab: move focus") ; strcat(lDialogString, "\" ") ; } if ( aDialogType && ! strcmp( "okcancel" , aDialogType ) ) { if ( ! aDefaultButton ) { strcat( lDialogString , "--defaultno " ) ; } strcat( lDialogString , "--yes-label \"Ok\" --no-label \"Cancel\" --yesno " ) ; } else if ( aDialogType && ! strcmp( "yesno" , aDialogType ) ) { if ( ! aDefaultButton ) { strcat( lDialogString , "--defaultno " ) ; } strcat( lDialogString , "--yesno " ) ; } else if (aDialogType && !strcmp("yesnocancel", aDialogType)) { if (!aDefaultButton) { strcat(lDialogString, "--defaultno "); } strcat(lDialogString, "--menu "); } else { strcat( lDialogString , "--msgbox " ) ; } strcat( lDialogString , "\"" ) ; if ( aMessage && strlen(aMessage) ) { tfd_replaceSubStr( aMessage , "\n" , "\\n" , lBuff ) ; strcat(lDialogString, lBuff) ; lBuff[0]='\0'; } strcat(lDialogString, "\" "); if (aDialogType && !strcmp("yesnocancel", aDialogType)) { strcat(lDialogString, "0 60 0 Yes \"\" No \"\""); strcat(lDialogString, "2>>"); } else { strcat(lDialogString, "10 60"); strcat(lDialogString, " && echo 1 > "); } strcpy(lDialogFile, getenv("TEMP")); strcat(lDialogFile, "\\tinyfd.txt"); strcat(lDialogString, lDialogFile); /*if (tinyfd_verbose) printf( "lDialogString: %s\n" , lDialogString ) ;*/ system( lDialogString ) ; if (!(lIn = fopen(lDialogFile, "r"))) { remove(lDialogFile); return 0 ; } while (fgets(lBuff, sizeof(lBuff), lIn) != NULL) {} fclose(lIn); remove(lDialogFile); if ( lBuff[strlen( lBuff ) -1] == '\n' ) { lBuff[strlen( lBuff ) -1] = '\0' ; } /* if (tinyfd_verbose) printf("lBuff: %s\n", lBuff); */ if ( ! strlen(lBuff) ) { return 0; } if (aDialogType && !strcmp("yesnocancel", aDialogType)) { if (lBuff[0] == 'Y') return 1; else return 2; } return 1; } static int inputBoxWinConsole( char * aoBuff , char const * aTitle , /* NULL or "" */ char const * aMessage , /* NULL or "" may NOT contain \n nor \t */ char const * aDefaultInput ) /* "" , if NULL it's a passwordBox */ { char lDialogString[MAX_PATH_OR_CMD]; char lDialogFile[MAX_PATH_OR_CMD]; FILE * lIn; int lResult; strcpy(lDialogFile, getenv("TEMP")); strcat(lDialogFile, "\\tinyfd.txt"); strcpy(lDialogString , "echo|set /p=1 >" ) ; strcat(lDialogString, lDialogFile); strcat( lDialogString , " & " ) ; strcat( lDialogString , "dialog " ) ; if ( aTitle && strlen(aTitle) ) { strcat(lDialogString, "--title \"") ; strcat(lDialogString, aTitle) ; strcat(lDialogString, "\" ") ; } strcat(lDialogString, "--backtitle \"") ; strcat(lDialogString, "tab: move focus") ; if ( ! aDefaultInput ) { strcat(lDialogString, " (sometimes nothing, no blink nor star, is shown in text field)") ; } strcat(lDialogString, "\" ") ; if ( ! aDefaultInput ) { strcat( lDialogString , "--insecure --passwordbox" ) ; } else { strcat( lDialogString , "--inputbox" ) ; } strcat( lDialogString , " \"" ) ; if ( aMessage && strlen(aMessage) ) { strcat(lDialogString, aMessage) ; } strcat(lDialogString,"\" 10 60 ") ; if ( aDefaultInput && strlen(aDefaultInput) ) { strcat(lDialogString, "\"") ; strcat(lDialogString, aDefaultInput) ; strcat(lDialogString, "\" ") ; } strcat(lDialogString, "2>>"); strcpy(lDialogFile, getenv("TEMP")); strcat(lDialogFile, "\\tinyfd.txt"); strcat(lDialogString, lDialogFile); strcat(lDialogString, " || echo 0 > "); strcat(lDialogString, lDialogFile); /* printf( "lDialogString: %s\n" , lDialogString ) ; */ system( lDialogString ) ; if (!(lIn = fopen(lDialogFile, "r"))) { remove(lDialogFile); aoBuff[0] = '\0'; return 0; } while (fgets(aoBuff, MAX_PATH_OR_CMD, lIn) != NULL) {} fclose(lIn); wipefile(lDialogFile); remove(lDialogFile); if ( aoBuff[strlen( aoBuff ) -1] == '\n' ) { aoBuff[strlen( aoBuff ) -1] = '\0' ; } /* printf( "aoBuff: %s\n" , aoBuff ) ; */ /* printf( "aoBuff: %s len: %lu \n" , aoBuff , strlen(aoBuff) ) ; */ lResult = strncmp( aoBuff , "1" , 1) ? 0 : 1 ; /* printf( "lResult: %d \n" , lResult ) ; */ if ( ! lResult ) { aoBuff[0] = '\0'; return 0 ; } /* printf( "aoBuff+1: %s\n" , aoBuff+1 ) ; */ strcpy(aoBuff, aoBuff+3); return 1; } static char * saveFileDialogWinConsole( char * aoBuff , char const * aTitle , /* NULL or "" */ char const * aDefaultPathAndOrFile ) /* NULL or "" */ { char lDialogString[MAX_PATH_OR_CMD]; char lPathAndFile[MAX_PATH_OR_CMD] = ""; FILE * lIn; strcpy( lDialogString , "dialog " ) ; if ( aTitle && strlen(aTitle) ) { strcat(lDialogString, "--title \"") ; strcat(lDialogString, aTitle) ; strcat(lDialogString, "\" ") ; } strcat(lDialogString, "--backtitle \"") ; strcat(lDialogString, "tab: focus | /: populate | spacebar: fill text field | ok: TEXT FIELD ONLY") ; strcat(lDialogString, "\" ") ; strcat( lDialogString , "--fselect \"" ) ; if ( aDefaultPathAndOrFile && strlen(aDefaultPathAndOrFile) ) { /* dialog.exe uses unix separators even on windows */ strcpy(lPathAndFile, aDefaultPathAndOrFile); replaceChr( lPathAndFile , '\\' , '/' ) ; } /* dialog.exe needs at least one separator */ if ( ! strchr(lPathAndFile, '/') ) { strcat(lDialogString, "./") ; } strcat(lDialogString, lPathAndFile) ; strcat(lDialogString, "\" 0 60 2>"); strcpy(lPathAndFile, getenv("TEMP")); strcat(lPathAndFile, "\\tinyfd.txt"); strcat(lDialogString, lPathAndFile); /* printf( "lDialogString: %s\n" , lDialogString ) ; */ system( lDialogString ) ; if (!(lIn = fopen(lPathAndFile, "r"))) { remove(lPathAndFile); return NULL; } while (fgets(aoBuff, MAX_PATH_OR_CMD, lIn) != NULL) {} fclose(lIn); remove(lPathAndFile); replaceChr( aoBuff , '/' , '\\' ) ; /* printf( "aoBuff: %s\n" , aoBuff ) ; */ getLastName(lDialogString,aoBuff); if ( ! strlen(lDialogString) ) { return NULL; } return aoBuff; } static char * openFileDialogWinConsole( char const * aTitle , /* NULL or "" */ char const * aDefaultPathAndOrFile ) /* NULL or "" */ { char lFilterPatterns[MAX_PATH_OR_CMD] = ""; char lDialogString[MAX_PATH_OR_CMD] ; FILE * lIn; static char aoBuff[MAX_PATH_OR_CMD]; strcpy( lDialogString , "dialog " ) ; if ( aTitle && strlen(aTitle) ) { strcat(lDialogString, "--title \"") ; strcat(lDialogString, aTitle) ; strcat(lDialogString, "\" ") ; } strcat(lDialogString, "--backtitle \"") ; strcat(lDialogString, "tab: focus | /: populate | spacebar: fill text field | ok: TEXT FIELD ONLY") ; strcat(lDialogString, "\" ") ; strcat( lDialogString , "--fselect \"" ) ; if ( aDefaultPathAndOrFile && strlen(aDefaultPathAndOrFile) ) { /* dialog.exe uses unix separators even on windows */ strcpy(lFilterPatterns, aDefaultPathAndOrFile); replaceChr( lFilterPatterns , '\\' , '/' ) ; } /* dialog.exe needs at least one separator */ if ( ! strchr(lFilterPatterns, '/') ) { strcat(lDialogString, "./") ; } strcat(lDialogString, lFilterPatterns) ; strcat(lDialogString, "\" 0 60 2>"); strcpy(lFilterPatterns, getenv("TEMP")); strcat(lFilterPatterns, "\\tinyfd.txt"); strcat(lDialogString, lFilterPatterns); /* printf( "lDialogString: %s\n" , lDialogString ) ; */ system( lDialogString ) ; if (!(lIn = fopen(lFilterPatterns, "r"))) { remove(lFilterPatterns); return NULL; } while (fgets(aoBuff, MAX_PATH_OR_CMD, lIn) != NULL) {} fclose(lIn); remove(lFilterPatterns); replaceChr( aoBuff , '/' , '\\' ) ; /* printf( "aoBuff: %s\n" , aoBuff ) ; */ return aoBuff; } static char * selectFolderDialogWinConsole( char * aoBuff , char const * aTitle , /* NULL or "" */ char const * aDefaultPath ) /* NULL or "" */ { char lDialogString[MAX_PATH_OR_CMD] ; char lString[MAX_PATH_OR_CMD] ; FILE * lIn ; strcpy( lDialogString , "dialog " ) ; if ( aTitle && strlen(aTitle) ) { strcat(lDialogString, "--title \"") ; strcat(lDialogString, aTitle) ; strcat(lDialogString, "\" ") ; } strcat(lDialogString, "--backtitle \"") ; strcat(lDialogString, "tab: focus | /: populate | spacebar: fill text field | ok: TEXT FIELD ONLY") ; strcat(lDialogString, "\" ") ; strcat( lDialogString , "--dselect \"" ) ; if ( aDefaultPath && strlen(aDefaultPath) ) { /* dialog.exe uses unix separators even on windows */ strcpy(lString, aDefaultPath) ; ensureFinalSlash(lString); replaceChr( lString , '\\' , '/' ) ; strcat(lDialogString, lString) ; } else { /* dialog.exe needs at least one separator */ strcat(lDialogString, "./") ; } strcat(lDialogString, "\" 0 60 2>"); strcpy(lString, getenv("TEMP")); strcat(lString, "\\tinyfd.txt"); strcat(lDialogString, lString); /* printf( "lDialogString: %s\n" , lDialogString ) ; */ system( lDialogString ) ; if (!(lIn = fopen(lString, "r"))) { remove(lString); return NULL; } while (fgets(aoBuff, MAX_PATH_OR_CMD, lIn) != NULL) {} fclose(lIn); remove(lString); replaceChr( aoBuff , '/' , '\\' ) ; /* printf( "aoBuff: %s\n" , aoBuff ) ; */ return aoBuff; } static void writeUtf8( char const * aUtf8String ) { unsigned long lNum; void * lConsoleHandle; wchar_t * lTmpWChar; lConsoleHandle = GetStdHandle(STD_OUTPUT_HANDLE); lTmpWChar = tinyfd_utf8to16(aUtf8String); (void)WriteConsoleW(lConsoleHandle, lTmpWChar, (DWORD) wcslen(lTmpWChar), &lNum, NULL); } int tinyfd_messageBox( char const * aTitle, /* NULL or "" */ char const * aMessage, /* NULL or "" may contain \n and \t */ char const * aDialogType, /* "ok" "okcancel" "yesno" "yesnocancel" */ char const * aIconType, /* "info" "warning" "error" "question" */ int aDefaultButton) /* 0 for cancel/no , 1 for ok/yes , 2 for no in yesnocancel */ { char lChar; UINT lOriginalCP = 0; UINT lOriginalOutputCP = 0; if (tfd_quoteDetected(aTitle)) return tinyfd_messageBox("INVALID TITLE WITH QUOTES", aMessage, aDialogType, aIconType, aDefaultButton); if (tfd_quoteDetected(aMessage)) return tinyfd_messageBox(aTitle, "INVALID MESSAGE WITH QUOTES", aDialogType, aIconType, aDefaultButton); if ((!tinyfd_forceConsole || !(GetConsoleWindow() || dialogPresent())) && (!getenv("SSH_CLIENT") || getenvDISPLAY())) { if (aTitle&&!strcmp(aTitle, "tinyfd_query")){ strcpy(tinyfd_response, "windows"); return 1; } return messageBoxWinGui(aTitle, aMessage, aDialogType, aIconType, aDefaultButton); } else if (dialogPresent()) { if (aTitle&&!strcmp(aTitle, "tinyfd_query")){ strcpy(tinyfd_response, "dialog"); return 0; } return messageBoxWinConsole( aTitle, aMessage, aDialogType, aIconType, aDefaultButton); } else { if (!tinyfd_winUtf8) { lOriginalCP = GetConsoleCP(); lOriginalOutputCP = GetConsoleOutputCP(); (void)SetConsoleCP(GetACP()); (void)SetConsoleOutputCP(GetACP()); } if (aTitle&&!strcmp(aTitle, "tinyfd_query")){ strcpy(tinyfd_response, "basicinput"); return 0; } if (!gWarningDisplayed && !tinyfd_forceConsole) { gWarningDisplayed = 1; printf("\n\n%s\n", gTitle); printf("%s\n\n", tinyfd_needs); } if (aTitle && strlen(aTitle)) { printf("\n"); if (tinyfd_winUtf8) writeUtf8(aTitle); else printf("%s", aTitle); printf("\n\n"); } if (aDialogType && !strcmp("yesno", aDialogType)) { do { if (aMessage && strlen(aMessage)) { if (tinyfd_winUtf8) writeUtf8(aMessage); else printf("%s", aMessage); printf("\n"); } printf("y/n: "); lChar = (char)tolower(_getch()); printf("\n\n"); } while (lChar != 'y' && lChar != 'n'); if (!tinyfd_winUtf8) { (void)SetConsoleCP(lOriginalCP); (void)SetConsoleOutputCP(lOriginalOutputCP); } return lChar == 'y' ? 1 : 0; } else if (aDialogType && !strcmp("okcancel", aDialogType)) { do { if (aMessage && strlen(aMessage)) { if (tinyfd_winUtf8) writeUtf8(aMessage); else printf("%s", aMessage); printf("\n"); } printf("[O]kay/[C]ancel: "); lChar = (char)tolower(_getch()); printf("\n\n"); } while (lChar != 'o' && lChar != 'c'); if (!tinyfd_winUtf8) { (void)SetConsoleCP(lOriginalCP); (void)SetConsoleOutputCP(lOriginalOutputCP); } return lChar == 'o' ? 1 : 0; } else if (aDialogType && !strcmp("yesnocancel", aDialogType)) { do { if (aMessage && strlen(aMessage)) { if (tinyfd_winUtf8) writeUtf8(aMessage); else printf("%s", aMessage); printf("\n"); } printf("[Y]es/[N]o/[C]ancel: "); lChar = (char)tolower(_getch()); printf("\n\n"); } while (lChar != 'y' && lChar != 'n' && lChar != 'c'); if (!tinyfd_winUtf8) { (void)SetConsoleCP(lOriginalCP); (void)SetConsoleOutputCP(lOriginalOutputCP); } return (lChar == 'y') ? 1 : (lChar == 'n') ? 2 : 0; } else { if (aMessage && strlen(aMessage)) { if (tinyfd_winUtf8) writeUtf8(aMessage); else printf("%s", aMessage); printf("\n\n"); } printf("press enter to continue "); fflush(stdout); lChar = (char)_getch(); printf("\n\n"); if (!tinyfd_winUtf8) { (void)SetConsoleCP(lOriginalCP); (void)SetConsoleOutputCP(lOriginalOutputCP); } return 1; } } } /* return has only meaning for tinyfd_query */ int tinyfd_notifyPopup( char const * aTitle, /* NULL or "" */ char const * aMessage , /* NULL or "" may contain \n \t */ char const * aIconType ) /* "info" "warning" "error" */ { if (tfd_quoteDetected(aTitle)) return tinyfd_notifyPopup("INVALID TITLE WITH QUOTES", aMessage, aIconType); if (tfd_quoteDetected(aMessage)) return tinyfd_notifyPopup(aTitle, "INVALID MESSAGE WITH QUOTES", aIconType); if ( powershellPresent() && (!tinyfd_forceConsole || !( GetConsoleWindow() || dialogPresent())) && (!getenv("SSH_CLIENT") || getenvDISPLAY())) { if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"windows");return 1;} return notifyWinGui(aTitle, aMessage, aIconType); } else return tinyfd_messageBox(aTitle, aMessage, "ok" , aIconType, 0); } /* returns NULL on cancel */ char * tinyfd_inputBox( char const * aTitle , /* NULL or "" */ char const * aMessage , /* NULL or "" (\n and \t have no effect) */ char const * aDefaultInput ) /* "" , if NULL it's a passwordBox */ { static char lBuff[MAX_PATH_OR_CMD] = ""; char * lEOF; DWORD mode = 0; HANDLE hStdin = GetStdHandle(STD_INPUT_HANDLE); unsigned long lNum; void * lConsoleHandle; char * lTmpChar; wchar_t lBuffW[1024]; UINT lOriginalCP = 0; UINT lOriginalOutputCP = 0; if (!aTitle && !aMessage && !aDefaultInput) return lBuff; /* now I can fill lBuff from outside */ if (tfd_quoteDetected(aTitle)) return tinyfd_inputBox("INVALID TITLE WITH QUOTES", aMessage, aDefaultInput); if (tfd_quoteDetected(aMessage)) return tinyfd_inputBox(aTitle, "INVALID MESSAGE WITH QUOTES", aDefaultInput); if (tfd_quoteDetected(aDefaultInput)) return tinyfd_inputBox(aTitle, aMessage, "INVALID DEFAULT_INPUT WITH QUOTES: use the GRAVE ACCENT \\x60 instead."); mode = 0; hStdin = GetStdHandle(STD_INPUT_HANDLE); if ((!tinyfd_forceConsole || !( GetConsoleWindow() || dialogPresent())) && (!getenv("SSH_CLIENT") || getenvDISPLAY())) { if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"windows");return (char *)1;} lBuff[0]='\0'; if (inputBoxWinGui(lBuff, aTitle, aMessage, aDefaultInput)) return lBuff; else return NULL; } else if ( dialogPresent() ) { if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"dialog");return (char *)0;} lBuff[0]='\0'; if (inputBoxWinConsole(lBuff, aTitle, aMessage, aDefaultInput) ) return lBuff; else return NULL; } else { if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"basicinput");return (char *)0;} lBuff[0]='\0'; if (!gWarningDisplayed && !tinyfd_forceConsole) { gWarningDisplayed = 1 ; printf("\n\n%s\n", gTitle); printf("%s\n\n", tinyfd_needs); } if (!tinyfd_winUtf8) { lOriginalCP = GetConsoleCP(); lOriginalOutputCP = GetConsoleOutputCP(); (void)SetConsoleCP(GetACP()); (void)SetConsoleOutputCP(GetACP()); } if (aTitle && strlen(aTitle)) { printf("\n"); if (tinyfd_winUtf8) writeUtf8(aTitle); else printf("%s", aTitle); printf("\n\n"); } if ( aMessage && strlen(aMessage) ) { if (tinyfd_winUtf8) writeUtf8(aMessage); else printf("%s", aMessage); printf("\n"); } printf("(ctrl-Z + enter to cancel): "); fflush(stdout); if ( ! aDefaultInput ) { (void) GetConsoleMode(hStdin, &mode); (void) SetConsoleMode(hStdin, mode & (~ENABLE_ECHO_INPUT)); } if (tinyfd_winUtf8) { lConsoleHandle = GetStdHandle(STD_INPUT_HANDLE); (void) ReadConsoleW(lConsoleHandle, lBuffW, MAX_PATH_OR_CMD, &lNum, NULL); if (!aDefaultInput) { (void)SetConsoleMode(hStdin, mode); printf("\n"); } lBuffW[lNum] = '\0'; if (lBuffW[wcslen(lBuffW) - 1] == '\n') lBuffW[wcslen(lBuffW) - 1] = '\0'; if (lBuffW[wcslen(lBuffW) - 1] == '\r') lBuffW[wcslen(lBuffW) - 1] = '\0'; lTmpChar = tinyfd_utf16to8(lBuffW); if (lTmpChar) { strcpy(lBuff, lTmpChar); return lBuff; } else return NULL; } else { lEOF = fgets(lBuff, MAX_PATH_OR_CMD, stdin); if (!aDefaultInput) { (void)SetConsoleMode(hStdin, mode); printf("\n"); } if (!tinyfd_winUtf8) { (void)SetConsoleCP(lOriginalCP); (void)SetConsoleOutputCP(lOriginalOutputCP); } if (!lEOF) { return NULL; } printf("\n"); if (strchr(lBuff, 27)) { return NULL; } if (lBuff[strlen(lBuff) - 1] == '\n') { lBuff[strlen(lBuff) - 1] = '\0'; } return lBuff; } } } char * tinyfd_saveFileDialog( char const * aTitle , /* NULL or "" */ char const * aDefaultPathAndOrFile , /* NULL or "" */ int aNumOfFilterPatterns , /* 0 */ char const * const * aFilterPatterns , /* NULL or {"*.jpg","*.png"} */ char const * aSingleFilterDescription ) /* NULL or "image files" */ { static char lBuff[MAX_PATH_OR_CMD] ; char lString[MAX_PATH_OR_CMD] ; char * p ; char * lPointerInputBox; int i; lBuff[0]='\0'; if ( ! aFilterPatterns ) aNumOfFilterPatterns = 0 ; if (tfd_quoteDetected(aTitle)) return tinyfd_saveFileDialog("INVALID TITLE WITH QUOTES", aDefaultPathAndOrFile, aNumOfFilterPatterns, aFilterPatterns, aSingleFilterDescription); if (tfd_quoteDetected(aDefaultPathAndOrFile)) return tinyfd_saveFileDialog(aTitle, "INVALID DEFAULT_PATH WITH QUOTES", aNumOfFilterPatterns, aFilterPatterns, aSingleFilterDescription); if (tfd_quoteDetected(aSingleFilterDescription)) return tinyfd_saveFileDialog(aTitle, aDefaultPathAndOrFile, aNumOfFilterPatterns, aFilterPatterns, "INVALID FILTER_DESCRIPTION WITH QUOTES"); for (i = 0; i < aNumOfFilterPatterns; i++) { if (tfd_quoteDetected(aFilterPatterns[i])) return tinyfd_saveFileDialog("INVALID FILTER_PATTERN WITH QUOTES: use the GRAVE ACCENT \\x60 instead.", aDefaultPathAndOrFile, 0, NULL, NULL); } if ( ( !tinyfd_forceConsole || !( GetConsoleWindow() || dialogPresent() ) ) && (!getenv("SSH_CLIENT") || getenvDISPLAY())) { if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"windows");return (char *)1;} p = saveFileDialogWinGui(lBuff, aTitle, aDefaultPathAndOrFile, aNumOfFilterPatterns, (char const * const *)aFilterPatterns, aSingleFilterDescription); } else if (dialogPresent()) { if (aTitle&&!strcmp(aTitle, "tinyfd_query")){ strcpy(tinyfd_response, "dialog"); return (char *)0; } p = saveFileDialogWinConsole(lBuff, aTitle, aDefaultPathAndOrFile); } else { if (aTitle&&!strcmp(aTitle, "tinyfd_query")){ strcpy(tinyfd_response, "basicinput"); return (char *)0; } strcpy(lBuff, "Save file in "); strcat(lBuff, getCurDir()); lPointerInputBox = tinyfd_inputBox(NULL,NULL,NULL); /* obtain a pointer on the current content of tinyfd_inputBox */ if (lPointerInputBox) strcpy(lString, lPointerInputBox); /* preserve the current content of tinyfd_inputBox */ p = tinyfd_inputBox(aTitle, lBuff, ""); if (p) strcpy(lBuff, p); else lBuff[0] = '\0'; if (lPointerInputBox) strcpy(lPointerInputBox, lString); /* restore its previous content to tinyfd_inputBox */ p = lBuff; } if ( ! p || ! strlen( p ) ) { return NULL; } getPathWithoutFinalSlash( lString , p ) ; if ( strlen( lString ) && ! dirExists( lString ) ) { return NULL ; } getLastName(lString,p); if ( ! filenameValid(lString) ) { return NULL; } return p ; } /* in case of multiple files, the separator is | */ char * tinyfd_openFileDialog( char const * aTitle , /* NULL or "" */ char const * aDefaultPathAndOrFile, /* NULL or "" */ int aNumOfFilterPatterns , /* 0 */ char const * const * aFilterPatterns, /* NULL or {"*.jpg","*.png"} */ char const * aSingleFilterDescription, /* NULL or "image files" */ int aAllowMultipleSelects ) /* 0 or 1 */ { static char lBuff[MAX_PATH_OR_CMD]; char lString[MAX_PATH_OR_CMD]; char * p; char * lPointerInputBox; int i; if ( ! aFilterPatterns ) aNumOfFilterPatterns = 0 ; if (tfd_quoteDetected(aTitle)) return tinyfd_openFileDialog("INVALID TITLE WITH QUOTES", aDefaultPathAndOrFile, aNumOfFilterPatterns, aFilterPatterns, aSingleFilterDescription, aAllowMultipleSelects); if (tfd_quoteDetected(aDefaultPathAndOrFile)) return tinyfd_openFileDialog(aTitle, "INVALID DEFAULT_PATH WITH QUOTES", aNumOfFilterPatterns, aFilterPatterns, aSingleFilterDescription, aAllowMultipleSelects); if (tfd_quoteDetected(aSingleFilterDescription)) return tinyfd_openFileDialog(aTitle, aDefaultPathAndOrFile, aNumOfFilterPatterns, aFilterPatterns, "INVALID FILTER_DESCRIPTION WITH QUOTES", aAllowMultipleSelects); for (i = 0; i < aNumOfFilterPatterns; i++) { if (tfd_quoteDetected(aFilterPatterns[i])) return tinyfd_openFileDialog("INVALID FILTER_PATTERN WITH QUOTES: use the GRAVE ACCENT \\x60 instead.", aDefaultPathAndOrFile, 0, NULL, NULL, aAllowMultipleSelects); } if ( ( !tinyfd_forceConsole || !( GetConsoleWindow() || dialogPresent() ) ) && (!getenv("SSH_CLIENT") || getenvDISPLAY())) { if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"windows");return (char *)1;} p = openFileDialogWinGui( aTitle, aDefaultPathAndOrFile, aNumOfFilterPatterns, (char const * const *)aFilterPatterns, aSingleFilterDescription, aAllowMultipleSelects); } else if (dialogPresent()) { if (aTitle&&!strcmp(aTitle, "tinyfd_query")){ strcpy(tinyfd_response, "dialog"); return (char *)0; } p = openFileDialogWinConsole(aTitle, aDefaultPathAndOrFile); } else { if (aTitle&&!strcmp(aTitle, "tinyfd_query")){ strcpy(tinyfd_response, "basicinput"); return (char *)0; } strcpy(lBuff, "Open file from "); strcat(lBuff, getCurDir()); lPointerInputBox = tinyfd_inputBox(NULL, NULL, NULL); /* obtain a pointer on the current content of tinyfd_inputBox */ if (lPointerInputBox) strcpy(lString, lPointerInputBox); /* preserve the current content of tinyfd_inputBox */ p = tinyfd_inputBox(aTitle, lBuff, ""); if (p) strcpy(lBuff, p); else lBuff[0] = '\0'; if (lPointerInputBox) strcpy(lPointerInputBox, lString); /* restore its previous content to tinyfd_inputBox */ p = lBuff; } if ( ! p || ! strlen( p ) ) { return NULL; } if ( aAllowMultipleSelects && strchr(p, '|') ) { p = ensureFilesExist( (char *) p , p ) ; } else if ( ! fileExists(p) ) { return NULL ; } /* printf( "lBuff3: %s\n" , p ) ; */ return p ; } char * tinyfd_selectFolderDialog( char const * aTitle , /* NULL or "" */ char const * aDefaultPath ) /* NULL or "" */ { static char lBuff[MAX_PATH_OR_CMD]; char * p; char * lPointerInputBox; char lString[MAX_PATH_OR_CMD]; if (tfd_quoteDetected(aTitle)) return tinyfd_selectFolderDialog("INVALID TITLE WITH QUOTES", aDefaultPath); if (tfd_quoteDetected(aDefaultPath)) return tinyfd_selectFolderDialog(aTitle, "INVALID DEFAULT_PATH WITH QUOTES"); if ( ( !tinyfd_forceConsole || !( GetConsoleWindow() || dialogPresent() ) ) && (!getenv("SSH_CLIENT") || getenvDISPLAY())) { if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"windows");return (char *)1;} p = selectFolderDialogWinGui(lBuff, aTitle, aDefaultPath); } else if (dialogPresent()) { if (aTitle&&!strcmp(aTitle, "tinyfd_query")){ strcpy(tinyfd_response, "dialog"); return (char *)0; } p = selectFolderDialogWinConsole(lBuff, aTitle, aDefaultPath); } else { if (aTitle&&!strcmp(aTitle, "tinyfd_query")){ strcpy(tinyfd_response, "basicinput"); return (char *)0; } strcpy(lBuff, "Select folder from "); strcat(lBuff, getCurDir()); lPointerInputBox = tinyfd_inputBox(NULL, NULL, NULL); /* obtain a pointer on the current content of tinyfd_inputBox */ if (lPointerInputBox) strcpy(lString, lPointerInputBox); /* preserve the current content of tinyfd_inputBox */ p = tinyfd_inputBox(aTitle, lBuff, ""); if (p) strcpy(lBuff, p); else lBuff[0] = '\0'; if (lPointerInputBox) strcpy(lPointerInputBox, lString); /* restore its previous content to tinyfd_inputBox */ p = lBuff; } if ( ! p || ! strlen( p ) || ! dirExists( p ) ) { return NULL ; } return p ; } /* aDefaultRGB is used only if aDefaultHexRGB is absent */ /* aDefaultRGB and aoResultRGB can be the same array */ /* returns NULL on cancel */ /* returns the hexcolor as a string "#FF0000" */ /* aoResultRGB also contains the result */ char * tinyfd_colorChooser( char const * aTitle, /* NULL or "" */ char const * aDefaultHexRGB, /* NULL or "" or "#FF0000"*/ unsigned char const aDefaultRGB[3], /* { 0 , 255 , 255 } */ unsigned char aoResultRGB[3]) /* { 0 , 0 , 0 } */ { static char lDefaultHexRGB[16]; int i; char * p ; char * lPointerInputBox; char lString[MAX_PATH_OR_CMD]; lDefaultHexRGB[0] = '\0'; if (tfd_quoteDetected(aTitle)) return tinyfd_colorChooser("INVALID TITLE WITH QUOTES", aDefaultHexRGB, aDefaultRGB, aoResultRGB); if (tfd_quoteDetected(aDefaultHexRGB)) return tinyfd_colorChooser(aTitle, "INVALID DEFAULT_HEX_RGB WITH QUOTES: use the GRAVE ACCENT \\x60 instead.", aDefaultRGB, aoResultRGB); if ( (!tinyfd_forceConsole || !( GetConsoleWindow() || dialogPresent()) ) && (!getenv("SSH_CLIENT") || getenvDISPLAY())) { if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"windows");return (char *)1;} p = colorChooserWinGui(aTitle, aDefaultHexRGB, aDefaultRGB, aoResultRGB); if (p) { strcpy(lDefaultHexRGB, p); return lDefaultHexRGB; } return NULL; } else if (dialogPresent()) { if (aTitle&&!strcmp(aTitle, "tinyfd_query")){ strcpy(tinyfd_response, "dialog"); return (char *)0; } } else { if (aTitle&&!strcmp(aTitle, "tinyfd_query")){ strcpy(tinyfd_response, "basicinput"); return (char *)0; } } if (aDefaultHexRGB && (strlen(aDefaultHexRGB)==7) ) { strncpy(lDefaultHexRGB, aDefaultHexRGB,7); lDefaultHexRGB[7]='\0'; } else { RGB2Hex(aDefaultRGB, lDefaultHexRGB); } lPointerInputBox = tinyfd_inputBox(NULL, NULL, NULL); /* obtain a pointer on the current content of tinyfd_inputBox */ if (lPointerInputBox) strcpy(lString, lPointerInputBox); /* preserve the current content of tinyfd_inputBox */ p = tinyfd_inputBox(aTitle, "Enter hex rgb color (i.e. #f5ca20)", lDefaultHexRGB); if ( !p || (strlen(p) != 7) || (p[0] != '#') ) { return NULL ; } for ( i = 1 ; i < 7 ; i ++ ) { if ( ! isxdigit( (int) p[i] ) ) { return NULL ; } } Hex2RGB(p,aoResultRGB); strcpy(lDefaultHexRGB, p); if (lPointerInputBox) strcpy(lPointerInputBox, lString); /* restore its previous content to tinyfd_inputBox */ return lDefaultHexRGB; } #else /* unix */ static char gPython2Name[16]; static char gPython3Name[16]; static char gPythonName[16]; int tfd_isDarwin(void) { static int lsIsDarwin = -1 ; struct utsname lUtsname ; if ( lsIsDarwin < 0 ) { lsIsDarwin = !uname(&lUtsname) && !strcmp(lUtsname.sysname,"Darwin") ; } return lsIsDarwin ; } static int dirExists( char const * aDirPath ) { DIR * lDir ; if ( ! aDirPath || ! strlen( aDirPath ) ) return 0 ; lDir = opendir( aDirPath ) ; if ( ! lDir ) { return 0 ; } closedir( lDir ) ; return 1 ; } static int detectPresence( char const * aExecutable ) { char lBuff[MAX_PATH_OR_CMD] ; char lTestedString[MAX_PATH_OR_CMD] = "command -v " ; FILE * lIn ; #ifdef _GNU_SOURCE char* lAllocatedCharString; int lSubstringUndetected; #endif strcat( lTestedString , aExecutable ) ; strcat( lTestedString, " 2>/dev/null "); lIn = popen( lTestedString , "r" ) ; if ( ( fgets( lBuff , sizeof( lBuff ) , lIn ) != NULL ) && ( ! strchr( lBuff , ':' ) ) && ( strncmp(lBuff, "no ", 3) ) ) { /* present */ pclose( lIn ) ; #ifdef _GNU_SOURCE /*to bypass this, just comment out "#define _GNU_SOURCE" at the top of the file*/ if ( lBuff[strlen( lBuff ) -1] == '\n' ) lBuff[strlen( lBuff ) -1] = '\0' ; lAllocatedCharString = realpath(lBuff,NULL); /*same as canonicalize_file_name*/ lSubstringUndetected = ! strstr(lAllocatedCharString, aExecutable); free(lAllocatedCharString); if (lSubstringUndetected) { if (tinyfd_verbose) printf("detectPresence %s %d\n", aExecutable, 0); return 0; } #endif /*_GNU_SOURCE*/ if (tinyfd_verbose) printf("detectPresence %s %d\n", aExecutable, 1); return 1 ; } else { pclose( lIn ) ; if (tinyfd_verbose) printf("detectPresence %s %d\n", aExecutable, 0); return 0 ; } } static char * getVersion( char const * aExecutable ) /*version must be first numeral*/ { static char lBuff[MAX_PATH_OR_CMD] ; char lTestedString[MAX_PATH_OR_CMD] ; FILE * lIn ; char * lTmp ; strcpy( lTestedString , aExecutable ) ; strcat( lTestedString , " --version" ) ; lIn = popen( lTestedString , "r" ) ; lTmp = fgets( lBuff , sizeof( lBuff ) , lIn ) ; pclose( lIn ) ; lTmp += strcspn(lTmp,"0123456789"); /* printf("lTmp:%s\n", lTmp); */ return lTmp ; } static int * getMajorMinorPatch( char const * aExecutable ) { static int lArray[3] ; char * lTmp ; lTmp = (char *) getVersion(aExecutable); lArray[0] = atoi( strtok(lTmp," ,.-") ) ; /* printf("lArray0 %d\n", lArray[0]); */ lArray[1] = atoi( strtok(0," ,.-") ) ; /* printf("lArray1 %d\n", lArray[1]); */ lArray[2] = atoi( strtok(0," ,.-") ) ; /* printf("lArray2 %d\n", lArray[2]); */ if ( !lArray[0] && !lArray[1] && !lArray[2] ) return NULL; return lArray ; } static int tryCommand( char const * aCommand ) { char lBuff[MAX_PATH_OR_CMD] ; FILE * lIn ; lIn = popen( aCommand , "r" ) ; if ( fgets( lBuff , sizeof( lBuff ) , lIn ) == NULL ) { /* present */ pclose( lIn ) ; return 1 ; } else { pclose( lIn ) ; return 0 ; } } static int isTerminalRunning(void) { static int lIsTerminalRunning = -1 ; if ( lIsTerminalRunning < 0 ) { lIsTerminalRunning = isatty(1); if (tinyfd_verbose) printf("isTerminalRunning %d\n", lIsTerminalRunning ); } return lIsTerminalRunning; } static char * dialogNameOnly(void) { static char lDialogName[128] = "*" ; if ( lDialogName[0] == '*' ) { if (!tinyfd_allowCursesDialogs) { strcpy(lDialogName , "" ); } else if ( tfd_isDarwin() && * strcpy(lDialogName , "/opt/local/bin/dialog" ) && detectPresence( lDialogName ) ) {} else if ( * strcpy(lDialogName , "dialog" ) && detectPresence( lDialogName ) ) {} else { strcpy(lDialogName , "" ); } } return lDialogName ; } int isDialogVersionBetter09b(void) { char const * lDialogName ; char * lVersion ; int lMajor ; int lMinor ; int lDate ; int lResult ; char * lMinorP ; char * lLetter ; char lBuff[128] ; /*char lTest[128] = " 0.9b-20031126" ;*/ lDialogName = dialogNameOnly() ; if ( ! strlen(lDialogName) || !(lVersion = (char *) getVersion(lDialogName)) ) return 0 ; /*lVersion = lTest ;*/ /*printf("lVersion %s\n", lVersion);*/ strcpy(lBuff,lVersion); lMajor = atoi( strtok(lVersion," ,.-") ) ; /*printf("lMajor %d\n", lMajor);*/ lMinorP = strtok(0," ,.-abcdefghijklmnopqrstuvxyz"); lMinor = atoi( lMinorP ) ; /*printf("lMinor %d\n", lMinor );*/ lDate = atoi( strtok(0," ,.-") ) ; if (lDate<0) lDate = - lDate; /*printf("lDate %d\n", lDate);*/ lLetter = lMinorP + strlen(lMinorP) ; strcpy(lVersion,lBuff); strtok(lLetter," ,.-"); /*printf("lLetter %s\n", lLetter);*/ lResult = (lMajor > 0) || ( ( lMinor == 9 ) && (*lLetter == 'b') && (lDate >= 20031126) ); /*printf("lResult %d\n", lResult);*/ return lResult; } static int whiptailPresentOnly(void) { static int lWhiptailPresent = -1 ; if (!tinyfd_allowCursesDialogs) return 0; if ( lWhiptailPresent < 0 ) { lWhiptailPresent = detectPresence( "whiptail" ) ; } return lWhiptailPresent ; } static char * terminalName(void) { static char lTerminalName[128] = "*" ; char lShellName[64] = "*" ; int * lArray; if ( lTerminalName[0] == '*' ) { if ( detectPresence( "bash" ) ) { strcpy(lShellName , "bash -c " ) ; /*good for basic input*/ } else if ( strlen(dialogNameOnly()) || whiptailPresentOnly() ) { strcpy(lShellName , "sh -c " ) ; /*good enough for dialog & whiptail*/ } else { strcpy(lTerminalName , "" ) ; return NULL ; } if ( tfd_isDarwin() ) { if ( * strcpy(lTerminalName , "/opt/X11/bin/xterm" ) && detectPresence( lTerminalName ) ) { strcat(lTerminalName , " -fa 'DejaVu Sans Mono' -fs 10 -title tinyfiledialogs -e " ) ; strcat(lTerminalName , lShellName ) ; } else { strcpy(lTerminalName , "" ) ; } } else if ( * strcpy(lTerminalName,"xterm") /*good (small without parameters)*/ && detectPresence(lTerminalName) ) { strcat(lTerminalName , " -fa 'DejaVu Sans Mono' -fs 10 -title tinyfiledialogs -e " ) ; strcat(lTerminalName , lShellName ) ; } else if ( * strcpy(lTerminalName,"terminator") /*good*/ && detectPresence(lTerminalName) ) { strcat(lTerminalName , " -x " ) ; strcat(lTerminalName , lShellName ) ; } else if ( * strcpy(lTerminalName,"lxterminal") /*good*/ && detectPresence(lTerminalName) ) { strcat(lTerminalName , " -e " ) ; strcat(lTerminalName , lShellName ) ; } else if ( * strcpy(lTerminalName,"konsole") /*good*/ && detectPresence(lTerminalName) ) { strcat(lTerminalName , " -e " ) ; strcat(lTerminalName , lShellName ) ; } else if ( * strcpy(lTerminalName,"kterm") /*good*/ && detectPresence(lTerminalName) ) { strcat(lTerminalName , " -e " ) ; strcat(lTerminalName , lShellName ) ; } else if ( * strcpy(lTerminalName,"tilix") /*good*/ && detectPresence(lTerminalName) ) { strcat(lTerminalName , " -e " ) ; strcat(lTerminalName , lShellName ) ; } else if ( * strcpy(lTerminalName,"xfce4-terminal") /*good*/ && detectPresence(lTerminalName) ) { strcat(lTerminalName , " -x " ) ; strcat(lTerminalName , lShellName ) ; } else if ( * strcpy(lTerminalName,"mate-terminal") /*good*/ && detectPresence(lTerminalName) ) { strcat(lTerminalName , " -x " ) ; strcat(lTerminalName , lShellName ) ; } else if ( * strcpy(lTerminalName,"Eterm") /*good*/ && detectPresence(lTerminalName) ) { strcat(lTerminalName , " -e " ) ; strcat(lTerminalName , lShellName ) ; } else if ( * strcpy(lTerminalName,"evilvte") /*good*/ && detectPresence(lTerminalName) ) { strcat(lTerminalName , " -e " ) ; strcat(lTerminalName , lShellName ) ; } else if ( * strcpy(lTerminalName,"pterm") /*good (only letters)*/ && detectPresence(lTerminalName) ) { strcat(lTerminalName , " -e " ) ; strcat(lTerminalName , lShellName ) ; } else if ( * strcpy(lTerminalName,"gnome-terminal") && detectPresence(lTerminalName) && (lArray = getMajorMinorPatch(lTerminalName)) && ((lArray[0]<3) || (lArray[0]==3 && lArray[1]<=6)) ) { strcat(lTerminalName , " --disable-factory -x " ) ; strcat(lTerminalName , lShellName ) ; } else { strcpy(lTerminalName , "" ) ; } /* bad: koi rxterm guake tilda vala-terminal qterminal kgx aterm Terminal terminology sakura lilyterm weston-terminal roxterm termit xvt rxvt mrxvt urxvt */ } if ( strlen(lTerminalName) ) { return lTerminalName ; } else { return NULL ; } } static char * dialogName(void) { char * lDialogName ; lDialogName = dialogNameOnly( ) ; if ( strlen(lDialogName) && ( isTerminalRunning() || terminalName() ) ) { return lDialogName ; } else { return NULL ; } } static int whiptailPresent(void) { int lWhiptailPresent ; lWhiptailPresent = whiptailPresentOnly( ) ; if ( lWhiptailPresent && ( isTerminalRunning() || terminalName() ) ) { return lWhiptailPresent ; } else { return 0 ; } } static int graphicMode(void) { return !( tinyfd_forceConsole && (isTerminalRunning() || terminalName()) ) && ( getenvDISPLAY() || (tfd_isDarwin() && (!getenv("SSH_TTY") || getenvDISPLAY() ) ) ) ; } static int ffplayPresent(void) { static int lFFplayPresent = -1; if (lFFplayPresent < 0) { lFFplayPresent = detectPresence("ffplay"); } return lFFplayPresent; } static int pactlPresent( void ) { static int lPactlPresent = -1 ; char lBuff [256] ; FILE * lIn ; if ( lPactlPresent < 0 ) { lPactlPresent = detectPresence("pactl") ; if ( lPactlPresent ) { lIn = popen( "pactl info | grep -iF pulseaudio" , "r" ) ; if ( ! (fgets( lBuff , sizeof( lBuff ) , lIn ) && ! strstr(lBuff, "PipeWire") ) ) { lPactlPresent = 0 ; } pclose( lIn ) ; if (tinyfd_verbose) printf("is pactl valid ? %d\n", lPactlPresent); } } return lPactlPresent ; } static int speakertestPresent(void) { static int lSpeakertestPresent = -1 ; if ( lSpeakertestPresent < 0 ) { lSpeakertestPresent = detectPresence("speaker-test") ; } return lSpeakertestPresent ; } static int playPresent(void) /* play is part of sox */ { static int lPlayPresent = -1; if (lPlayPresent < 0) { lPlayPresent = detectPresence("sox"); /*if sox is present, play is ready*/ } return lPlayPresent; } static int beepexePresent(void) { static int lBeepexePresent = -1; if (lBeepexePresent < 0) { lBeepexePresent = detectPresence("beep.exe"); } return lBeepexePresent; } /*static int beepPresent(void) { static int lBeepPresent = -1 ; if ( lBeepPresent < 0 ) { lBeepPresent = detectPresence("beep") ; } return lBeepPresent ; }*/ static int playsoundPresent(void) /* playsound is part of pipewire */ { static int lPlaysoundPresent = -1 ; if (lPlaysoundPresent < 0) { lPlaysoundPresent = detectPresence("playsound_simple"); if ( lPlaysoundPresent && ! fileExists("/usr/share/sounds/freedesktop/stereo/bell.oga") ) { lPlaysoundPresent = 0 ; } } return lPlaysoundPresent; } static int paplayPresent(void) /* playsound is part of pipewire */ { static int lPaplayPresent = -1 ; if (lPaplayPresent < 0) { lPaplayPresent = detectPresence("paplay"); if ( lPaplayPresent && ! fileExists("/usr/share/sounds/freedesktop/stereo/bell.oga") ) { lPaplayPresent = 0 ; } } return lPaplayPresent; } static int xmessagePresent(void) { static int lXmessagePresent = -1 ; if ( lXmessagePresent < 0 ) { lXmessagePresent = detectPresence("xmessage");/*if not tty,not on osxpath*/ } return lXmessagePresent && graphicMode( ) ; } static int gxmessagePresent(void) { static int lGxmessagePresent = -1 ; if ( lGxmessagePresent < 0 ) { lGxmessagePresent = detectPresence("gxmessage") ; } return lGxmessagePresent && graphicMode( ) ; } static int gmessagePresent(void) { static int lGmessagePresent = -1 ; if ( lGmessagePresent < 0 ) { lGmessagePresent = detectPresence("gmessage") ; } return lGmessagePresent && graphicMode( ) ; } static int notifysendPresent(void) { static int lNotifysendPresent = -1 ; if ( lNotifysendPresent < 0 ) { lNotifysendPresent = detectPresence("notify-send") ; } return lNotifysendPresent && graphicMode( ) ; } static int perlPresent(void) { static int lPerlPresent = -1 ; char lBuff[MAX_PATH_OR_CMD] ; FILE * lIn ; if ( lPerlPresent < 0 ) { lPerlPresent = detectPresence("perl") ; if (lPerlPresent) { lIn = popen("perl -MNet::DBus -e \"Net::DBus->session->get_service('org.freedesktop.Notifications')\" 2>&1", "r"); if (fgets(lBuff, sizeof(lBuff), lIn) == NULL) { lPerlPresent = 2; } pclose(lIn); if (tinyfd_verbose) printf("perl-dbus %d\n", lPerlPresent); } } return graphicMode() ? lPerlPresent : 0 ; } static int afplayPresent(void) { static int lAfplayPresent = -1 ; char lBuff[MAX_PATH_OR_CMD] ; FILE * lIn ; if ( lAfplayPresent < 0 ) { lAfplayPresent = detectPresence("afplay") ; if ( lAfplayPresent ) { lIn = popen( "test -e /System/Library/Sounds/Ping.aiff || echo Ping" , "r" ) ; if ( fgets( lBuff , sizeof( lBuff ) , lIn ) == NULL ) { lAfplayPresent = 2 ; } pclose( lIn ) ; if (tinyfd_verbose) printf("afplay %d\n", lAfplayPresent); } } return graphicMode() ? lAfplayPresent : 0 ; } static int xdialogPresent(void) { static int lXdialogPresent = -1 ; if ( lXdialogPresent < 0 ) { lXdialogPresent = detectPresence("Xdialog") ; } return lXdialogPresent && graphicMode( ) ; } static int gdialogPresent(void) { static int lGdialoglPresent = -1 ; if ( lGdialoglPresent < 0 ) { lGdialoglPresent = detectPresence( "gdialog" ) ; } return lGdialoglPresent && graphicMode( ) ; } static int osascriptPresent(void) { static int lOsascriptPresent = -1 ; if ( lOsascriptPresent < 0 ) { gWarningDisplayed |= !!getenv("SSH_TTY"); lOsascriptPresent = detectPresence( "osascript" ) ; } return lOsascriptPresent && graphicMode() && !getenv("SSH_TTY") ; } static int dunstifyPresent(void) { static int lDunstifyPresent = -1 ; static char lBuff[MAX_PATH_OR_CMD] ; FILE * lIn ; char * lTmp ; if ( lDunstifyPresent < 0 ) { lDunstifyPresent = detectPresence( "dunstify" ) ; if ( lDunstifyPresent ) { lIn = popen( "dunstify -s" , "r" ) ; lTmp = fgets( lBuff , sizeof( lBuff ) , lIn ) ; pclose( lIn ) ; /* printf("lTmp:%s\n", lTmp); */ lDunstifyPresent = strstr(lTmp,"name:dunst\n") ? 1 : 0 ; if (tinyfd_verbose) printf("lDunstifyPresent %d\n", lDunstifyPresent); } } return lDunstifyPresent && graphicMode( ) ; } static int dunstPresent(void) { static int lDunstPresent = -1 ; static char lBuff[MAX_PATH_OR_CMD] ; FILE * lIn ; char * lTmp ; if ( lDunstPresent < 0 ) { lDunstPresent = detectPresence( "dunst" ) ; if ( lDunstPresent ) { lIn = popen( "ps -e | grep dunst | grep -v grep" , "r" ) ; /* add "| wc -l" to receive the number of lines */ lTmp = fgets( lBuff , sizeof( lBuff ) , lIn ) ; pclose( lIn ) ; /* if ( lTmp ) printf("lTmp:%s\n", lTmp); */ if ( lTmp ) lDunstPresent = 1 ; else lDunstPresent = 0 ; if (tinyfd_verbose) printf("lDunstPresent %d\n", lDunstPresent); } } return lDunstPresent && graphicMode( ) ; } int tfd_qarmaPresent(void) { static int lQarmaPresent = -1 ; if ( lQarmaPresent < 0 ) { lQarmaPresent = detectPresence("qarma") ; } return lQarmaPresent && graphicMode( ) ; } int tfd_matedialogPresent(void) { static int lMatedialogPresent = -1 ; if ( lMatedialogPresent < 0 ) { lMatedialogPresent = detectPresence("matedialog") ; } return lMatedialogPresent && graphicMode( ) ; } int tfd_shellementaryPresent(void) { static int lShellementaryPresent = -1 ; if ( lShellementaryPresent < 0 ) { lShellementaryPresent = 0 ; /*detectPresence("shellementary"); shellementary is not ready yet */ } return lShellementaryPresent && graphicMode( ) ; } int tfd_xpropPresent(void) { static int lXpropReady = 0 ; static int lXpropDetected = -1 ; char lBuff[MAX_PATH_OR_CMD] ; FILE * lIn ; if ( lXpropDetected < 0 ) { lXpropDetected = detectPresence("xprop") ; } if ( !lXpropReady && lXpropDetected ) { /* xwayland Debian issue reported by Kay F. Jahnke and solved with his help */ lIn = popen( "xprop -root 32x ' $0' _NET_ACTIVE_WINDOW" , "r" ) ; if ( fgets( lBuff , sizeof( lBuff ) , lIn ) != NULL ) { if ( ! strstr( lBuff , "not found" ) ) { if (tinyfd_verbose) printf("xprop is ready\n"); lXpropReady = 1 ; } } pclose( lIn ) ; } return graphicMode() ? lXpropReady : 0 ; } int tfd_zenityPresent(void) { static int lZenityPresent = -1 ; if ( lZenityPresent < 0 ) { lZenityPresent = detectPresence("zenity") ; } return lZenityPresent && graphicMode( ) ; } int tfd_yadPresent(void) { static int lYadPresent = -1; if (lYadPresent < 0) { lYadPresent = detectPresence("yad"); } return lYadPresent && graphicMode(); } int tfd_zenity3Present(void) { static int lZenity3Present = -1 ; char lBuff[MAX_PATH_OR_CMD] ; FILE * lIn ; int lIntTmp ; if ( lZenity3Present < 0 ) { lZenity3Present = 0 ; if ( tfd_zenityPresent() ) { lIn = popen( "zenity --version" , "r" ) ; if ( fgets( lBuff , sizeof( lBuff ) , lIn ) != NULL ) { if ( atoi(lBuff) >= 3 ) { lZenity3Present = 3 ; lIntTmp = atoi(strtok(lBuff,".")+2 ) ; if ( lIntTmp >= 18 ) { lZenity3Present = 5 ; } else if ( lIntTmp >= 10 ) { lZenity3Present = 4 ; } } else if ( ( atoi(lBuff) == 2 ) && ( atoi(strtok(lBuff,".")+2 ) >= 32 ) ) { lZenity3Present = 2 ; } if (tinyfd_verbose) printf("zenity type %d\n", lZenity3Present); } pclose( lIn ) ; } } return graphicMode() ? lZenity3Present : 0 ; } int tfd_kdialogPresent(void) { static int lKdialogPresent = -1 ; char lBuff[MAX_PATH_OR_CMD] ; FILE * lIn ; char * lDesktop; if ( lKdialogPresent < 0 ) { lDesktop = getenv("XDG_SESSION_DESKTOP"); if ( !lDesktop || ( strcmp(lDesktop, "KDE") && strcmp(lDesktop, "lxqt") ) ) { if ( tfd_zenityPresent() ) { lKdialogPresent = 0 ; return lKdialogPresent ; } } lKdialogPresent = detectPresence("kdialog") ; if ( lKdialogPresent && !getenv("SSH_TTY") ) { lIn = popen( "kdialog --attach 2>&1" , "r" ) ; if ( fgets( lBuff , sizeof( lBuff ) , lIn ) != NULL ) { if ( ! strstr( "Unknown" , lBuff ) ) { lKdialogPresent = 2 ; if (tinyfd_verbose) printf("kdialog-attach %d\n", lKdialogPresent); } } pclose( lIn ) ; if (lKdialogPresent == 2) { lKdialogPresent = 1 ; lIn = popen( "kdialog --passivepopup 2>&1" , "r" ) ; if ( fgets( lBuff , sizeof( lBuff ) , lIn ) != NULL ) { if ( ! strstr( "Unknown" , lBuff ) ) { lKdialogPresent = 2 ; if (tinyfd_verbose) printf("kdialog-popup %d\n", lKdialogPresent); } } pclose( lIn ) ; } } } return graphicMode() ? lKdialogPresent : 0 ; } static int osx9orBetter(void) { static int lOsx9orBetter = -1 ; char lBuff[MAX_PATH_OR_CMD] ; FILE * lIn ; int V,v; if ( lOsx9orBetter < 0 ) { lOsx9orBetter = 0 ; lIn = popen( "osascript -e 'set osver to system version of (system info)'" , "r" ) ; V = 0 ; if ( ( fgets( lBuff , sizeof( lBuff ) , lIn ) != NULL ) && ( 2 == sscanf(lBuff, "%d.%d", &V, &v) ) ) { V = V * 100 + v; if ( V >= 1009 ) { lOsx9orBetter = 1 ; } } pclose( lIn ) ; if (tinyfd_verbose) printf("Osx10 = %d, %d = %s\n", lOsx9orBetter, V, lBuff) ; } return lOsx9orBetter ; } static int python3Present(void) { static int lPython3Present = -1 ; if ( lPython3Present < 0 ) { lPython3Present = 0 ; strcpy(gPython3Name , "python3" ) ; if ( detectPresence(gPython3Name) ) lPython3Present = 1; if (tinyfd_verbose) printf("lPython3Present %d\n", lPython3Present) ; if (tinyfd_verbose) printf("gPython3Name %s\n", gPython3Name) ; } return lPython3Present ; } static int python2Present(void) { static int lPython2Present = -1 ; if ( lPython2Present < 0 ) { lPython2Present = 0 ; strcpy(gPython2Name , "python2" ) ; if ( detectPresence(gPython2Name) ) lPython2Present = 1; if (tinyfd_verbose) printf("lPython2Present %d\n", lPython2Present) ; if (tinyfd_verbose) printf("gPython2Name %s\n", gPython2Name) ; } return lPython2Present ; } static int tkinter3Present(void) { static int lTkinter3Present = -1 ; char lPythonCommand[256]; char lPythonParams[128] = "-S -c \"try:\n\timport tkinter;\nexcept:\n\tprint(0);\""; if ( lTkinter3Present < 0 ) { lTkinter3Present = 0 ; if ( python3Present() ) { sprintf( lPythonCommand , "%s %s" , gPython3Name , lPythonParams ) ; lTkinter3Present = tryCommand(lPythonCommand) ; } if (tinyfd_verbose) printf("lTkinter3Present %d\n", lTkinter3Present) ; } return lTkinter3Present && graphicMode() && !(tfd_isDarwin() && getenv("SSH_TTY") ); } static int tkinter2Present(void) { static int lTkinter2Present = -1 ; char lPythonCommand[256]; char lPythonParams[128] = "-S -c \"try:\n\timport Tkinter;\nexcept:\n\tprint 0;\""; if ( lTkinter2Present < 0 ) { lTkinter2Present = 0 ; if ( python2Present() ) { sprintf( lPythonCommand , "%s %s" , gPython2Name , lPythonParams ) ; lTkinter2Present = tryCommand(lPythonCommand) ; } if (tinyfd_verbose) printf("lTkinter2Present %d graphicMode %d \n", lTkinter2Present, graphicMode() ) ; } return lTkinter2Present && graphicMode() && !(tfd_isDarwin() && getenv("SSH_TTY") ); } static int pythonDbusPresent(void) { static int lPythonDbusPresent = -1 ; char lPythonCommand[384]; char lPythonParams[256] = "-c \"try:\n\timport dbus;bus=dbus.SessionBus();\ notif=bus.get_object('org.freedesktop.Notifications','/org/freedesktop/Notifications');\ notify=dbus.Interface(notif,'org.freedesktop.Notifications');\nexcept:\n\tprint(0);\""; if (lPythonDbusPresent < 0 ) { lPythonDbusPresent = 0 ; if ( python2Present() ) { strcpy(gPythonName , gPython2Name ) ; sprintf( lPythonCommand , "%s %s" , gPythonName , lPythonParams ) ; lPythonDbusPresent = tryCommand(lPythonCommand) ; } if ( !lPythonDbusPresent && python3Present() ) { strcpy(gPythonName , gPython3Name ) ; sprintf( lPythonCommand , "%s %s" , gPythonName , lPythonParams ) ; lPythonDbusPresent = tryCommand(lPythonCommand) ; } if (tinyfd_verbose) printf("lPythonDbusPresent %d\n", lPythonDbusPresent) ; if (tinyfd_verbose) printf("gPythonName %s\n", gPythonName) ; } return lPythonDbusPresent && graphicMode() && !(tfd_isDarwin() && getenv("SSH_TTY") ); } static void sigHandler(int signum) { FILE * lIn ; if ( ( lIn = popen( "pactl unload-module module-sine" , "r" ) ) ) { pclose( lIn ) ; } if (tinyfd_verbose) printf("tinyfiledialogs caught signal %d\n", signum); } void tinyfd_beep(void) { char lDialogString[256] ; FILE * lIn ; if ( pactlPresent() ) { signal(SIGINT, sigHandler); strcpy( lDialogString , "thnum=$(pactl load-module module-sine frequency=440);sleep .3;pactl unload-module $thnum" ) ; } else if ( osascriptPresent() ) { if ( afplayPresent() >= 2 ) { strcpy( lDialogString , "afplay /System/Library/Sounds/Ping.aiff") ; } else { strcpy( lDialogString , "osascript -e 'tell application \"System Events\" to beep'") ; } } else if ( speakertestPresent() ) { /*strcpy( lDialogString , "timeout -k .3 .3 speaker-test --frequency 440 --test sine > /dev/tty" ) ;*/ strcpy( lDialogString , "( speaker-test -t sine -f 440 > /dev/tty )& pid=$!;sleep .5; kill -9 $pid" ) ; /*.3 was too short for mac g3*/ } else if ( ffplayPresent() ) { strcpy(lDialogString, "ffplay -f lavfi -i sine=f=440:d=0.15 -autoexit -nodisp" ); } else if (playPresent()) /* play is part of sox */ { strcpy(lDialogString, "play -q -n synth .3 sine 440"); } else if ( playsoundPresent() ) { strcpy( lDialogString , "playsound_simple /usr/share/sounds/freedesktop/stereo/bell.oga") ; } else if ( paplayPresent() ) { strcpy( lDialogString , "paplay /usr/share/sounds/freedesktop/stereo/bell.oga") ; } else if (beepexePresent()) { strcpy(lDialogString, "beep.exe 440 300"); } /*else if ( beepPresent() ) { strcpy( lDialogString , "beep -f 440 -l 300" ) ; }*/ else { strcpy( lDialogString , "printf '\\a' > /dev/tty" ) ; } if (tinyfd_verbose) printf( "lDialogString: %s\n" , lDialogString ) ; if ( ( lIn = popen( lDialogString , "r" ) ) ) { pclose( lIn ) ; } if ( pactlPresent() ) { signal(SIGINT, SIG_DFL); } } int tinyfd_messageBox( char const * aTitle , /* NULL or "" */ char const * aMessage , /* NULL or "" may contain \n and \t */ char const * aDialogType , /* "ok" "okcancel" "yesno" "yesnocancel" */ char const * aIconType , /* "info" "warning" "error" "question" */ int aDefaultButton ) /* 0 for cancel/no , 1 for ok/yes , 2 for no in yesnocancel */ { char lBuff[MAX_PATH_OR_CMD] ; char * lDialogString = NULL ; char * lpDialogString; FILE * lIn ; int lWasGraphicDialog = 0 ; int lWasXterm = 0 ; int lResult ; char lChar ; struct termios infoOri; struct termios info; size_t lTitleLen ; size_t lMessageLen ; lBuff[0]='\0'; if (tfd_quoteDetected(aTitle)) return tinyfd_messageBox("INVALID TITLE WITH QUOTES", aMessage, aDialogType, aIconType, aDefaultButton); if (tfd_quoteDetected(aMessage)) return tinyfd_messageBox(aTitle, "INVALID MESSAGE WITH QUOTES", aDialogType, aIconType, aDefaultButton); lTitleLen = aTitle ? strlen(aTitle) : 0 ; lMessageLen = aMessage ? strlen(aMessage) : 0 ; if ( !aTitle || strcmp(aTitle,"tinyfd_query") ) { lDialogString = (char *) malloc( MAX_PATH_OR_CMD + lTitleLen + lMessageLen ); } if ( osascriptPresent( ) ) { if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"applescript");return 1;} strcpy( lDialogString , "osascript "); if ( ! osx9orBetter() ) strcat( lDialogString , " -e 'tell application \"System Events\"' -e 'Activate'"); strcat( lDialogString , " -e 'try' -e 'set {vButton} to {button returned} of ( display dialog \"") ; if ( aMessage && strlen(aMessage) ) { strcat(lDialogString, aMessage) ; } strcat(lDialogString, "\" ") ; if ( aTitle && strlen(aTitle) ) { strcat(lDialogString, "with title \"") ; strcat(lDialogString, aTitle) ; strcat(lDialogString, "\" ") ; } strcat(lDialogString, "with icon ") ; if ( aIconType && ! strcmp( "error" , aIconType ) ) { strcat(lDialogString, "stop " ) ; } else if ( aIconType && ! strcmp( "warning" , aIconType ) ) { strcat(lDialogString, "caution " ) ; } else /* question or info */ { strcat(lDialogString, "note " ) ; } if ( aDialogType && ! strcmp( "okcancel" , aDialogType ) ) { if ( ! aDefaultButton ) { strcat( lDialogString ,"default button \"Cancel\" " ) ; } } else if ( aDialogType && ! strcmp( "yesno" , aDialogType ) ) { strcat( lDialogString ,"buttons {\"No\", \"Yes\"} " ) ; if (aDefaultButton) { strcat( lDialogString ,"default button \"Yes\" " ) ; } else { strcat( lDialogString ,"default button \"No\" " ) ; } strcat( lDialogString ,"cancel button \"No\"" ) ; } else if ( aDialogType && ! strcmp( "yesnocancel" , aDialogType ) ) { strcat( lDialogString ,"buttons {\"No\", \"Yes\", \"Cancel\"} " ) ; switch (aDefaultButton) { case 1: strcat( lDialogString ,"default button \"Yes\" " ) ; break; case 2: strcat( lDialogString ,"default button \"No\" " ) ; break; case 0: strcat( lDialogString ,"default button \"Cancel\" " ) ; break; } strcat( lDialogString ,"cancel button \"Cancel\"" ) ; } else { strcat( lDialogString ,"buttons {\"OK\"} " ) ; strcat( lDialogString ,"default button \"OK\" " ) ; } strcat( lDialogString, ")' ") ; strcat( lDialogString, "-e 'if vButton is \"Yes\" then' -e 'return 1'\ -e 'else if vButton is \"OK\" then' -e 'return 1'\ -e 'else if vButton is \"No\" then' -e 'return 2'\ -e 'else' -e 'return 0' -e 'end if' " ); strcat( lDialogString, "-e 'on error number -128' " ) ; strcat( lDialogString, "-e '0' " ); strcat( lDialogString, "-e 'end try'") ; if ( ! osx9orBetter() ) strcat( lDialogString, " -e 'end tell'") ; } else if ( tfd_kdialogPresent() ) { if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"kdialog");return 1;} strcpy( lDialogString , "kdialog" ) ; if ( (tfd_kdialogPresent() == 2) && tfd_xpropPresent() ) { strcat(lDialogString, " --attach=$(xprop -root 32x '\t$0' _NET_ACTIVE_WINDOW | cut -f 2)"); /* contribution: Paul Rouget */ } strcat( lDialogString , " --" ) ; if ( aDialogType && ( ! strcmp( "okcancel" , aDialogType ) || ! strcmp( "yesno" , aDialogType ) || ! strcmp( "yesnocancel" , aDialogType ) ) ) { if ( aIconType && ( ! strcmp( "warning" , aIconType ) || ! strcmp( "error" , aIconType ) ) ) { strcat( lDialogString , "warning" ) ; } if ( ! strcmp( "yesnocancel" , aDialogType ) ) { strcat( lDialogString , "yesnocancel" ) ; } else { strcat( lDialogString , "yesno" ) ; } } else if ( aIconType && ! strcmp( "error" , aIconType ) ) { strcat( lDialogString , "error" ) ; } else if ( aIconType && ! strcmp( "warning" , aIconType ) ) { strcat( lDialogString , "sorry" ) ; } else { strcat( lDialogString , "msgbox" ) ; } strcat( lDialogString , " \"" ) ; if ( aMessage ) { strcat( lDialogString , aMessage ) ; } strcat( lDialogString , "\"" ) ; if ( aDialogType && ! strcmp( "okcancel" , aDialogType ) ) { strcat( lDialogString , " --yes-label Ok --no-label Cancel" ) ; } if ( aTitle && strlen(aTitle) ) { strcat(lDialogString, " --title \"") ; strcat(lDialogString, aTitle) ; strcat(lDialogString, "\"") ; } if ( ! strcmp( "yesnocancel" , aDialogType ) ) { strcat( lDialogString , "; x=$? ;if [ $x = 0 ] ;then echo 1;elif [ $x = 1 ] ;then echo 2;else echo 0;fi"); } else { strcat( lDialogString , ";if [ $? = 0 ];then echo 1;else echo 0;fi"); } } else if ( tfd_zenityPresent() || tfd_matedialogPresent() || tfd_shellementaryPresent() || tfd_qarmaPresent() ) { if ( tfd_zenityPresent() ) { if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"zenity");return 1;} strcpy( lDialogString , "szAnswer=$(zenity" ) ; if ( (tfd_zenity3Present() >= 4) && !getenv("SSH_TTY") && tfd_xpropPresent() ) { strcat(lDialogString, " --attach=$(sleep .01;xprop -root 32x '\t$0' _NET_ACTIVE_WINDOW | cut -f 2)"); /* contribution: Paul Rouget */ } } else if ( tfd_matedialogPresent() ) { if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"matedialog");return 1;} strcpy( lDialogString , "szAnswer=$(matedialog" ) ; } else if ( tfd_shellementaryPresent() ) { if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"shellementary");return 1;} strcpy( lDialogString , "szAnswer=$(shellementary" ) ; } else { if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"qarma");return 1;} strcpy( lDialogString , "szAnswer=$(qarma" ) ; if ( !getenv("SSH_TTY") && tfd_xpropPresent() ) { strcat(lDialogString, " --attach=$(xprop -root 32x '\t$0' _NET_ACTIVE_WINDOW | cut -f 2)"); /* contribution: Paul Rouget */ } } strcat(lDialogString, " --"); if ( aDialogType && ! strcmp( "okcancel" , aDialogType ) ) { strcat( lDialogString , "question --ok-label=Ok --cancel-label=Cancel" ) ; } else if ( aDialogType && ! strcmp( "yesno" , aDialogType ) ) { strcat( lDialogString , "question" ) ; } else if ( aDialogType && ! strcmp( "yesnocancel" , aDialogType ) ) { strcat( lDialogString , "list --column \"\" --hide-header \"Yes\" \"No\"" ) ; } else if ( aIconType && ! strcmp( "error" , aIconType ) ) { strcat( lDialogString , "error" ) ; } else if ( aIconType && ! strcmp( "warning" , aIconType ) ) { strcat( lDialogString , "warning" ) ; } else { strcat( lDialogString , "info" ) ; } strcat(lDialogString, " --title=\""); if ( aTitle && strlen(aTitle) ) strcat(lDialogString, aTitle) ; strcat(lDialogString, "\""); if (strcmp("yesnocancel", aDialogType)) strcat(lDialogString, " --no-wrap"); strcat(lDialogString, " --text=\"") ; if (aMessage && strlen(aMessage)) strcat(lDialogString, aMessage) ; strcat(lDialogString, "\"") ; if ( (tfd_zenity3Present() >= 3) || (!tfd_zenityPresent() && (tfd_shellementaryPresent() || tfd_qarmaPresent()) ) ) { strcat( lDialogString , " --icon-name=dialog-" ) ; if ( aIconType && (! strcmp( "question" , aIconType ) || ! strcmp( "error" , aIconType ) || ! strcmp( "warning" , aIconType ) ) ) { strcat( lDialogString , aIconType ) ; } else { strcat( lDialogString , "information" ) ; } } if (tinyfd_silent) strcat( lDialogString , " 2>/dev/null "); if ( ! strcmp( "yesnocancel" , aDialogType ) ) { strcat( lDialogString , ");if [ $? = 1 ];then echo 0;elif [ $szAnswer = \"No\" ];then echo 2;else echo 1;fi"); } else { strcat( lDialogString , ");if [ $? = 0 ];then echo 1;else echo 0;fi"); } } else if (tfd_yadPresent()) { if (aTitle && !strcmp(aTitle, "tinyfd_query")) { strcpy(tinyfd_response, "yad"); return 1; } strcpy(lDialogString, "szAnswer=$(yad --"); if (aDialogType && !strcmp("ok", aDialogType)) { strcat(lDialogString,"button=Ok:1"); } else if (aDialogType && !strcmp("okcancel", aDialogType)) { strcat(lDialogString,"button=Ok:1 --button=Cancel:0"); } else if (aDialogType && !strcmp("yesno", aDialogType)) { strcat(lDialogString, "button=Yes:1 --button=No:0"); } else if (aDialogType && !strcmp("yesnocancel", aDialogType)) { strcat(lDialogString, "button=Yes:1 --button=No:2 --button=Cancel:0"); } else if (aIconType && !strcmp("error", aIconType)) { strcat(lDialogString, "error"); } else if (aIconType && !strcmp("warning", aIconType)) { strcat(lDialogString, "warning"); } else { strcat(lDialogString, "info"); } if (aTitle && strlen(aTitle)) { strcat(lDialogString, " --title=\""); strcat(lDialogString, aTitle); strcat(lDialogString, "\""); } if (aMessage && strlen(aMessage)) { strcat(lDialogString, " --text=\""); strcat(lDialogString, aMessage); strcat(lDialogString, "\""); } strcat(lDialogString, " --image=dialog-"); if (aIconType && (!strcmp("question", aIconType) || !strcmp("error", aIconType) || !strcmp("warning", aIconType))) { strcat(lDialogString, aIconType); } else { strcat(lDialogString, "information"); } if (tinyfd_silent) strcat(lDialogString, " 2>/dev/null "); strcat(lDialogString,");echo $?"); } else if ( !gxmessagePresent() && !gmessagePresent() && !gdialogPresent() && !xdialogPresent() && tkinter3Present() ) { if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"python3-tkinter");return 1;} strcpy( lDialogString , gPython3Name ) ; strcat( lDialogString , " -S -c \"import tkinter;from tkinter import messagebox;root=tkinter.Tk();root.withdraw();"); strcat( lDialogString ,"res=messagebox." ) ; if ( aDialogType && ! strcmp( "okcancel" , aDialogType ) ) { strcat( lDialogString , "askokcancel(" ) ; if ( aDefaultButton ) { strcat( lDialogString , "default=messagebox.OK," ) ; } else { strcat( lDialogString , "default=messagebox.CANCEL," ) ; } } else if ( aDialogType && ! strcmp( "yesno" , aDialogType ) ) { strcat( lDialogString , "askyesno(" ) ; if ( aDefaultButton ) { strcat( lDialogString , "default=messagebox.YES," ) ; } else { strcat( lDialogString , "default=messagebox.NO," ) ; } } else if ( aDialogType && ! strcmp( "yesnocancel" , aDialogType ) ) { strcat( lDialogString , "askyesnocancel(" ) ; switch ( aDefaultButton ) { case 1: strcat( lDialogString , "default=messagebox.YES," ); break; case 2: strcat( lDialogString , "default=messagebox.NO," ); break; case 0: strcat( lDialogString , "default=messagebox.CANCEL," ); break; } } else { strcat( lDialogString , "showinfo(" ) ; } strcat( lDialogString , "icon='" ) ; if ( aIconType && (! strcmp( "question" , aIconType ) || ! strcmp( "error" , aIconType ) || ! strcmp( "warning" , aIconType ) ) ) { strcat( lDialogString , aIconType ) ; } else { strcat( lDialogString , "info" ) ; } strcat(lDialogString, "',") ; if ( aTitle && strlen(aTitle) ) { strcat(lDialogString, "title='") ; strcat(lDialogString, aTitle) ; strcat(lDialogString, "',") ; } if ( aMessage && strlen(aMessage) ) { strcat(lDialogString, "message='") ; lpDialogString = lDialogString + strlen(lDialogString); tfd_replaceSubStr( aMessage , "\n" , "\\n" , lpDialogString ) ; strcat(lDialogString, "'") ; } if ( aDialogType && ! strcmp( "yesnocancel" , aDialogType ) ) { strcat(lDialogString, ");\n\ if res is None :\n\tprint(0)\n\ elif res is False :\n\tprint(2)\n\ else :\n\tprint (1)\n\"" ) ; } else { strcat(lDialogString, ");\n\ if res is False :\n\tprint(0)\n\ else :\n\tprint(1)\n\"" ) ; } } else if ( !gxmessagePresent() && !gmessagePresent() && !gdialogPresent() && !xdialogPresent() && tkinter2Present() ) { if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"python2-tkinter");return 1;} strcpy( lDialogString , "export PYTHONIOENCODING=utf-8;" ) ; strcat( lDialogString , gPython2Name ) ; if ( ! isTerminalRunning( ) && tfd_isDarwin( ) ) { strcat( lDialogString , " -i" ) ; /* for osx without console */ } strcat( lDialogString , " -S -c \"import Tkinter,tkMessageBox;root=Tkinter.Tk();root.withdraw();"); if ( tfd_isDarwin( ) ) { strcat( lDialogString , "import os;os.system('''/usr/bin/osascript -e 'tell app \\\"Finder\\\" to set \ frontmost of process \\\"Python\\\" to true' ''');"); } strcat( lDialogString ,"res=tkMessageBox." ) ; if ( aDialogType && ! strcmp( "okcancel" , aDialogType ) ) { strcat( lDialogString , "askokcancel(" ) ; if ( aDefaultButton ) { strcat( lDialogString , "default=tkMessageBox.OK," ) ; } else { strcat( lDialogString , "default=tkMessageBox.CANCEL," ) ; } } else if ( aDialogType && ! strcmp( "yesno" , aDialogType ) ) { strcat( lDialogString , "askyesno(" ) ; if ( aDefaultButton ) { strcat( lDialogString , "default=tkMessageBox.YES," ) ; } else { strcat( lDialogString , "default=tkMessageBox.NO," ) ; } } else if ( aDialogType && ! strcmp( "yesnocancel" , aDialogType ) ) { strcat( lDialogString , "askyesnocancel(" ) ; switch ( aDefaultButton ) { case 1: strcat( lDialogString , "default=tkMessageBox.YES," ); break; case 2: strcat( lDialogString , "default=tkMessageBox.NO," ); break; case 0: strcat( lDialogString , "default=tkMessageBox.CANCEL," ); break; } } else { strcat( lDialogString , "showinfo(" ) ; } strcat( lDialogString , "icon='" ) ; if ( aIconType && (! strcmp( "question" , aIconType ) || ! strcmp( "error" , aIconType ) || ! strcmp( "warning" , aIconType ) ) ) { strcat( lDialogString , aIconType ) ; } else { strcat( lDialogString , "info" ) ; } strcat(lDialogString, "',") ; if ( aTitle && strlen(aTitle) ) { strcat(lDialogString, "title='") ; strcat(lDialogString, aTitle) ; strcat(lDialogString, "',") ; } if ( aMessage && strlen(aMessage) ) { strcat(lDialogString, "message='") ; lpDialogString = lDialogString + strlen(lDialogString); tfd_replaceSubStr( aMessage , "\n" , "\\n" , lpDialogString ) ; strcat(lDialogString, "'") ; } if ( aDialogType && ! strcmp( "yesnocancel" , aDialogType ) ) { strcat(lDialogString, ");\n\ if res is None :\n\tprint 0\n\ elif res is False :\n\tprint 2\n\ else :\n\tprint 1\n\"" ) ; } else { strcat(lDialogString, ");\n\ if res is False :\n\tprint 0\n\ else :\n\tprint 1\n\"" ) ; } } else if ( gxmessagePresent() || gmessagePresent() || (!gdialogPresent() && !xdialogPresent() && xmessagePresent()) ) { if ( gxmessagePresent() ) { if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"gxmessage");return 1;} strcpy( lDialogString , "gxmessage"); } else if ( gmessagePresent() ) { if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"gmessage");return 1;} strcpy( lDialogString , "gmessage"); } else { if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"xmessage");return 1;} strcpy( lDialogString , "xmessage"); } if ( aDialogType && ! strcmp("okcancel" , aDialogType) ) { strcat( lDialogString , " -buttons Ok:1,Cancel:0"); switch ( aDefaultButton ) { case 1: strcat( lDialogString , " -default Ok"); break; case 0: strcat( lDialogString , " -default Cancel"); break; } } else if ( aDialogType && ! strcmp("yesno" , aDialogType) ) { strcat( lDialogString , " -buttons Yes:1,No:0"); switch ( aDefaultButton ) { case 1: strcat( lDialogString , " -default Yes"); break; case 0: strcat( lDialogString , " -default No"); break; } } else if ( aDialogType && ! strcmp("yesnocancel" , aDialogType) ) { strcat( lDialogString , " -buttons Yes:1,No:2,Cancel:0"); switch ( aDefaultButton ) { case 1: strcat( lDialogString , " -default Yes"); break; case 2: strcat( lDialogString , " -default No"); break; case 0: strcat( lDialogString , " -default Cancel"); break; } } else { strcat( lDialogString , " -buttons Ok:1"); strcat( lDialogString , " -default Ok"); } strcat( lDialogString , " -center \""); if ( aMessage && strlen(aMessage) ) { strcat( lDialogString , aMessage ) ; } strcat(lDialogString, "\"" ) ; if ( aTitle && strlen(aTitle) ) { strcat( lDialogString , " -title \""); strcat( lDialogString , aTitle ) ; strcat( lDialogString, "\"" ) ; } strcat( lDialogString , " ; echo $? "); } else if ( xdialogPresent() || gdialogPresent() || dialogName() || whiptailPresent() ) { if ( gdialogPresent( ) ) { if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"gdialog");return 1;} lWasGraphicDialog = 1 ; strcpy( lDialogString , "(gdialog " ) ; } else if ( xdialogPresent( ) ) { if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"xdialog");return 1;} lWasGraphicDialog = 1 ; strcpy( lDialogString , "(Xdialog " ) ; } else if ( dialogName( ) ) { if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"dialog");return 0;} if ( isTerminalRunning( ) ) { strcpy( lDialogString , "(dialog " ) ; } else { lWasXterm = 1 ; strcpy( lDialogString , terminalName() ) ; strcat( lDialogString , "'(" ) ; strcat( lDialogString , dialogName() ) ; strcat( lDialogString , " " ) ; } } else if ( isTerminalRunning( ) ) { if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"whiptail");return 0;} strcpy( lDialogString , "(whiptail " ) ; } else { if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"whiptail");return 0;} lWasXterm = 1 ; strcpy( lDialogString , terminalName() ) ; strcat( lDialogString , "'(whiptail " ) ; } if ( aTitle && strlen(aTitle) ) { strcat(lDialogString, "--title \"") ; strcat(lDialogString, aTitle) ; strcat(lDialogString, "\" ") ; } if ( !xdialogPresent() && !gdialogPresent() ) { if ( aDialogType && ( !strcmp( "okcancel" , aDialogType ) || !strcmp( "yesno" , aDialogType ) || !strcmp( "yesnocancel" , aDialogType ) ) ) { strcat(lDialogString, "--backtitle \"") ; strcat(lDialogString, "tab: move focus") ; strcat(lDialogString, "\" ") ; } } if ( aDialogType && ! strcmp( "okcancel" , aDialogType ) ) { if ( ! aDefaultButton ) { strcat( lDialogString , "--defaultno " ) ; } strcat( lDialogString , "--yes-label \"Ok\" --no-label \"Cancel\" --yesno " ) ; } else if ( aDialogType && ! strcmp( "yesno" , aDialogType ) ) { if ( ! aDefaultButton ) { strcat( lDialogString , "--defaultno " ) ; } strcat( lDialogString , "--yesno " ) ; } else if (aDialogType && !strcmp("yesnocancel", aDialogType)) { if (!aDefaultButton) { strcat(lDialogString, "--defaultno "); } strcat(lDialogString, "--menu "); } else { strcat( lDialogString , "--msgbox " ) ; } strcat( lDialogString , "\"" ) ; if ( aMessage && strlen(aMessage) ) { strcat(lDialogString, aMessage) ; } strcat(lDialogString, "\" "); if ( lWasGraphicDialog ) { if (aDialogType && !strcmp("yesnocancel", aDialogType)) { strcat(lDialogString,"0 60 0 Yes \"\" No \"\") 2>/tmp/tinyfd.txt;\ if [ $? = 0 ];then tinyfdBool=1;else tinyfdBool=0;fi;\ tinyfdRes=$(cat /tmp/tinyfd.txt);echo $tinyfdBool$tinyfdRes") ; } else { strcat(lDialogString, "10 60 ) 2>&1;if [ $? = 0 ];then echo 1;else echo 0;fi"); } } else { if (aDialogType && !strcmp("yesnocancel", aDialogType)) { strcat(lDialogString,"0 60 0 Yes \"\" No \"\" >/dev/tty ) 2>/tmp/tinyfd.txt;\ if [ $? = 0 ];then tinyfdBool=1;else tinyfdBool=0;fi;\ tinyfdRes=$(cat /tmp/tinyfd.txt);echo $tinyfdBool$tinyfdRes") ; if ( lWasXterm ) { strcat(lDialogString," >/tmp/tinyfd0.txt';cat /tmp/tinyfd0.txt"); } else { strcat(lDialogString, "; clear >/dev/tty") ; } } else { strcat(lDialogString, "10 60 >/dev/tty) 2>&1;if [ $? = 0 ];"); if ( lWasXterm ) { strcat( lDialogString , "then\n\techo 1\nelse\n\techo 0\nfi >/tmp/tinyfd.txt';cat /tmp/tinyfd.txt;rm /tmp/tinyfd.txt"); } else { strcat(lDialogString, "then echo 1;else echo 0;fi;clear >/dev/tty"); } } } } else if ( !isTerminalRunning() && terminalName() ) { if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"basicinput");return 0;} strcpy( lDialogString , terminalName() ) ; strcat( lDialogString , "'" ) ; if ( !gWarningDisplayed && !tinyfd_forceConsole) { gWarningDisplayed = 1 ; strcat( lDialogString , "echo \"" ) ; strcat( lDialogString, gTitle) ; strcat( lDialogString , "\";" ) ; strcat( lDialogString , "echo \"" ) ; strcat( lDialogString, tinyfd_needs) ; strcat( lDialogString , "\";echo;echo;" ) ; } if ( aTitle && strlen(aTitle) ) { strcat( lDialogString , "echo \"" ) ; strcat( lDialogString, aTitle) ; strcat( lDialogString , "\";echo;" ) ; } if ( aMessage && strlen(aMessage) ) { strcat( lDialogString , "echo \"" ) ; strcat( lDialogString, aMessage) ; strcat( lDialogString , "\"; " ) ; } if ( aDialogType && !strcmp("yesno",aDialogType) ) { strcat( lDialogString , "echo -n \"y/n: \"; " ) ; strcat( lDialogString , "stty sane -echo;" ) ; strcat( lDialogString , "answer=$( while ! head -c 1 | grep -i [ny];do true ;done);"); strcat( lDialogString , "if echo \"$answer\" | grep -iq \"^y\";then\n"); strcat( lDialogString , "\techo 1\nelse\n\techo 0\nfi" ) ; } else if ( aDialogType && !strcmp("okcancel",aDialogType) ) { strcat( lDialogString , "echo -n \"[O]kay/[C]ancel: \"; " ) ; strcat( lDialogString , "stty sane -echo;" ) ; strcat( lDialogString , "answer=$( while ! head -c 1 | grep -i [oc];do true ;done);"); strcat( lDialogString , "if echo \"$answer\" | grep -iq \"^o\";then\n"); strcat( lDialogString , "\techo 1\nelse\n\techo 0\nfi" ) ; } else if ( aDialogType && !strcmp("yesnocancel",aDialogType) ) { strcat( lDialogString , "echo -n \"[Y]es/[N]o/[C]ancel: \"; " ) ; strcat( lDialogString , "stty sane -echo;" ) ; strcat( lDialogString , "answer=$( while ! head -c 1 | grep -i [nyc];do true ;done);"); strcat( lDialogString , "if echo \"$answer\" | grep -iq \"^y\";then\n\techo 1\n"); strcat( lDialogString , "elif echo \"$answer\" | grep -iq \"^n\";then\n\techo 2\n" ) ; strcat( lDialogString , "else\n\techo 0\nfi" ) ; } else { strcat(lDialogString , "echo -n \"press enter to continue \"; "); strcat( lDialogString , "stty sane -echo;" ) ; strcat( lDialogString , "answer=$( while ! head -c 1;do true ;done);echo 1"); } strcat( lDialogString , " >/tmp/tinyfd.txt';cat /tmp/tinyfd.txt;rm /tmp/tinyfd.txt"); } else if ( !isTerminalRunning() && pythonDbusPresent() && !strcmp("ok" , aDialogType) ) { if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"python-dbus");return 1;} strcpy( lDialogString , gPythonName ) ; strcat( lDialogString ," -c \"import dbus;bus=dbus.SessionBus();"); strcat( lDialogString ,"notif=bus.get_object('org.freedesktop.Notifications','/org/freedesktop/Notifications');" ) ; strcat( lDialogString ,"notify=dbus.Interface(notif,'org.freedesktop.Notifications');" ) ; strcat( lDialogString ,"notify.Notify('',0,'" ) ; if ( aIconType && strlen(aIconType) ) { strcat( lDialogString , aIconType ) ; } strcat(lDialogString, "','") ; if ( aTitle && strlen(aTitle) ) { strcat(lDialogString, aTitle) ; } strcat(lDialogString, "','") ; if ( aMessage && strlen(aMessage) ) { lpDialogString = lDialogString + strlen(lDialogString); tfd_replaceSubStr( aMessage , "\n" , "\\n" , lpDialogString ) ; } strcat(lDialogString, "','','',5000)\"") ; } else if ( !isTerminalRunning() && (perlPresent() >= 2) && !strcmp("ok" , aDialogType) ) { if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"perl-dbus");return 1;} strcpy( lDialogString , "perl -e \"use Net::DBus;\ my \\$sessionBus = Net::DBus->session;\ my \\$notificationsService = \\$sessionBus->get_service('org.freedesktop.Notifications');\ my \\$notificationsObject = \\$notificationsService->get_object('/org/freedesktop/Notifications',\ 'org.freedesktop.Notifications');"); sprintf( lDialogString + strlen(lDialogString), "my \\$notificationId;\\$notificationId = \\$notificationsObject->Notify(shift, 0, '%s', '%s', '%s', [], {}, -1);\" ", aIconType?aIconType:"", aTitle?aTitle:"", aMessage?aMessage:"" ) ; } else if ( !isTerminalRunning() && notifysendPresent() && !strcmp("ok" , aDialogType) ) { if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"notifysend");return 1;} strcpy( lDialogString , "notify-send" ) ; if ( aIconType && strlen(aIconType) ) { strcat( lDialogString , " -i '" ) ; strcat( lDialogString , aIconType ) ; strcat( lDialogString , "'" ) ; } strcat( lDialogString , " \"" ) ; if ( aTitle && strlen(aTitle) ) { strcat(lDialogString, aTitle) ; strcat( lDialogString , " | " ) ; } if ( aMessage && strlen(aMessage) ) { tfd_replaceSubStr( aMessage , "\n\t" , " | " , lBuff ) ; tfd_replaceSubStr( aMessage , "\n" , " | " , lBuff ) ; tfd_replaceSubStr( aMessage , "\t" , " " , lBuff ) ; strcat(lDialogString, lBuff) ; } strcat( lDialogString , "\"" ) ; } else { if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"basicinput");return 0;} if ( !gWarningDisplayed && !tinyfd_forceConsole) { gWarningDisplayed = 1 ; printf("\n\n%s\n", gTitle); printf("%s\n\n", tinyfd_needs); } if ( aTitle && strlen(aTitle) ) { printf("\n%s\n", aTitle); } tcgetattr(0, &infoOri); tcgetattr(0, &info); info.c_lflag &= ~ICANON; info.c_cc[VMIN] = 1; info.c_cc[VTIME] = 0; tcsetattr(0, TCSANOW, &info); if ( aDialogType && !strcmp("yesno",aDialogType) ) { do { if ( aMessage && strlen(aMessage) ) { printf("\n%s\n",aMessage); } printf("y/n: "); fflush(stdout); lChar = (char) tolower( getchar() ) ; printf("\n\n"); } while ( lChar != 'y' && lChar != 'n' ); lResult = lChar == 'y' ? 1 : 0 ; } else if ( aDialogType && !strcmp("okcancel",aDialogType) ) { do { if ( aMessage && strlen(aMessage) ) { printf("\n%s\n",aMessage); } printf("[O]kay/[C]ancel: "); fflush(stdout); lChar = (char) tolower( getchar() ) ; printf("\n\n"); } while ( lChar != 'o' && lChar != 'c' ); lResult = lChar == 'o' ? 1 : 0 ; } else if ( aDialogType && !strcmp("yesnocancel",aDialogType) ) { do { if ( aMessage && strlen(aMessage) ) { printf("\n%s\n",aMessage); } printf("[Y]es/[N]o/[C]ancel: "); fflush(stdout); lChar = (char) tolower( getchar() ) ; printf("\n\n"); } while ( lChar != 'y' && lChar != 'n' && lChar != 'c' ); lResult = (lChar == 'y') ? 1 : (lChar == 'n') ? 2 : 0 ; } else { if ( aMessage && strlen(aMessage) ) { printf("\n%s\n\n",aMessage); } printf("press enter to continue "); fflush(stdout); getchar() ; printf("\n\n"); lResult = 1 ; } tcsetattr(0, TCSANOW, &infoOri); free(lDialogString); return lResult ; } if (tinyfd_verbose) printf( "lDialogString: %s\n" , lDialogString ) ; if ( ! ( lIn = popen( lDialogString , "r" ) ) ) { free(lDialogString); return 0 ; } while ( fgets( lBuff , sizeof( lBuff ) , lIn ) != NULL ) {} pclose( lIn ) ; /* printf( "lBuff: %s len: %lu \n" , lBuff , strlen(lBuff) ) ; */ if ( strlen( lBuff ) && lBuff[strlen( lBuff ) -1] == '\n' ) { lBuff[strlen( lBuff ) -1] = '\0' ; } /* printf( "lBuff1: %s len: %lu \n" , lBuff , strlen(lBuff) ) ; */ if (aDialogType && !strcmp("yesnocancel", aDialogType)) { if ( lBuff[0]=='1' ) { if ( !strcmp( lBuff+1 , "Yes" )) strcpy(lBuff,"1"); else if ( !strcmp( lBuff+1 , "No" )) strcpy(lBuff,"2"); } } /* printf( "lBuff2: %s len: %lu \n" , lBuff , strlen(lBuff) ) ; */ lResult = !strcmp( lBuff , "2" ) ? 2 : !strcmp( lBuff , "1" ) ? 1 : 0; /* printf( "lResult: %d\n" , lResult ) ; */ free(lDialogString); return lResult ; } /* return has only meaning for tinyfd_query */ int tinyfd_notifyPopup( char const * aTitle , /* NULL or "" */ char const * aMessage , /* NULL or "" may contain \n and \t */ char const * aIconType ) /* "info" "warning" "error" */ { char lBuff[MAX_PATH_OR_CMD]; char * lDialogString = NULL ; char * lpDialogString ; FILE * lIn ; size_t lTitleLen ; size_t lMessageLen ; if (tfd_quoteDetected(aTitle)) return tinyfd_notifyPopup("INVALID TITLE WITH QUOTES", aMessage, aIconType); if (tfd_quoteDetected(aMessage)) return tinyfd_notifyPopup(aTitle, "INVALID MESSAGE WITH QUOTES", aIconType); if ( getenv("SSH_TTY") && !dunstifyPresent() && !dunstPresent() ) { return tinyfd_messageBox(aTitle, aMessage, "ok", aIconType, 0); } lTitleLen = aTitle ? strlen(aTitle) : 0 ; lMessageLen = aMessage ? strlen(aMessage) : 0 ; if ( !aTitle || strcmp(aTitle,"tinyfd_query") ) { lDialogString = (char *) malloc( MAX_PATH_OR_CMD + lTitleLen + lMessageLen ); } if ( getenv("SSH_TTY") ) { if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"dunst");return 1;} strcpy( lDialogString , "notify-send \"" ) ; if ( aTitle && strlen(aTitle) ) { strcat( lDialogString , aTitle ) ; strcat( lDialogString , "\" \"" ) ; } if ( aMessage && strlen(aMessage) ) { strcat(lDialogString, aMessage) ; } strcat( lDialogString , "\"" ) ; } else if ( osascriptPresent( ) ) { if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"applescript");return 1;} strcpy( lDialogString , "osascript "); if ( ! osx9orBetter() ) strcat( lDialogString , " -e 'tell application \"System Events\"' -e 'Activate'"); strcat( lDialogString , " -e 'try' -e 'display notification \"") ; if ( aMessage && strlen(aMessage) ) { strcat(lDialogString, aMessage) ; } strcat(lDialogString, " \" ") ; if ( aTitle && strlen(aTitle) ) { strcat(lDialogString, "with title \"") ; strcat(lDialogString, aTitle) ; strcat(lDialogString, "\" ") ; } strcat( lDialogString, "' -e 'end try'") ; if ( ! osx9orBetter() ) strcat( lDialogString, " -e 'end tell'") ; } else if ( tfd_kdialogPresent() ) { if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"kdialog");return 1;} strcpy( lDialogString , "kdialog" ) ; if ( aIconType && strlen(aIconType) ) { strcat( lDialogString , " --icon '" ) ; strcat( lDialogString , aIconType ) ; strcat( lDialogString , "'" ) ; } if ( aTitle && strlen(aTitle) ) { strcat( lDialogString , " --title \"" ) ; strcat( lDialogString , aTitle ) ; strcat( lDialogString , "\"" ) ; } strcat( lDialogString , " --passivepopup" ) ; strcat( lDialogString , " \"" ) ; if ( aMessage ) { strcat( lDialogString , aMessage ) ; } strcat( lDialogString , " \" 5" ) ; } else if ( tfd_yadPresent() ) { if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"yad");return 1;} strcpy( lDialogString , "yad --notification"); if ( aIconType && strlen( aIconType ) ) { strcat( lDialogString , " --image=\""); strcat( lDialogString , aIconType ) ; strcat( lDialogString , "\"" ) ; } strcat( lDialogString , " --text=\"" ) ; if ( aTitle && strlen(aTitle) ) { strcat(lDialogString, aTitle) ; strcat(lDialogString, "\n") ; } if ( aMessage && strlen( aMessage ) ) { strcat( lDialogString , aMessage ) ; } strcat( lDialogString , " \"" ) ; } else if ( perlPresent() >= 2 ) { if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"perl-dbus");return 1;} strcpy( lDialogString , "perl -e \"use Net::DBus;\ my \\$sessionBus = Net::DBus->session;\ my \\$notificationsService = \\$sessionBus->get_service('org.freedesktop.Notifications');\ my \\$notificationsObject = \\$notificationsService->get_object('/org/freedesktop/Notifications',\ 'org.freedesktop.Notifications');"); sprintf( lDialogString + strlen(lDialogString) , "my \\$notificationId;\\$notificationId = \\$notificationsObject->Notify(shift, 0, '%s', '%s', '%s', [], {}, -1);\" ", aIconType?aIconType:"", aTitle?aTitle:"", aMessage?aMessage:"" ) ; } else if ( pythonDbusPresent( ) ) { if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"python-dbus");return 1;} strcpy( lDialogString , gPythonName ) ; strcat( lDialogString ," -c \"import dbus;bus=dbus.SessionBus();"); strcat( lDialogString ,"notif=bus.get_object('org.freedesktop.Notifications','/org/freedesktop/Notifications');" ) ; strcat( lDialogString ,"notify=dbus.Interface(notif,'org.freedesktop.Notifications');" ) ; strcat( lDialogString ,"notify.Notify('',0,'" ) ; if ( aIconType && strlen(aIconType) ) { strcat( lDialogString , aIconType ) ; } strcat(lDialogString, "','") ; if ( aTitle && strlen(aTitle) ) { strcat(lDialogString, aTitle) ; } strcat(lDialogString, "','") ; if ( aMessage && strlen(aMessage) ) { lpDialogString = lDialogString + strlen(lDialogString); tfd_replaceSubStr( aMessage , "\n" , "\\n" , lpDialogString ) ; } strcat(lDialogString, "','','',5000)\"") ; } else if ( notifysendPresent() ) { if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"notifysend");return 1;} strcpy( lDialogString , "notify-send" ) ; if ( aIconType && strlen(aIconType) ) { strcat( lDialogString , " -i '" ) ; strcat( lDialogString , aIconType ) ; strcat( lDialogString , "'" ) ; } strcat( lDialogString , " \"" ) ; if ( aTitle && strlen(aTitle) ) { strcat(lDialogString, aTitle) ; strcat( lDialogString , " | " ) ; } if ( aMessage && strlen(aMessage) ) { tfd_replaceSubStr( aMessage , "\n\t" , " | " , lBuff ) ; tfd_replaceSubStr( aMessage , "\n" , " | " , lBuff ) ; tfd_replaceSubStr( aMessage , "\t" , " " , lBuff ) ; strcat(lDialogString, lBuff) ; } strcat( lDialogString , "\"" ) ; } else if ( (tfd_zenity3Present()>=5) ) { /* zenity 2.32 & 3.14 has the notification but with a bug: it doesnt return from it */ /* zenity 3.8 show the notification as an alert ok cancel box */ /* zenity 3.44 doesn't have the notification (3.42 has it) */ if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"zenity");return 1;} strcpy( lDialogString , "zenity --notification"); if ( aIconType && strlen( aIconType ) ) { strcat( lDialogString , " --window-icon '"); strcat( lDialogString , aIconType ) ; strcat( lDialogString , "'" ) ; } strcat( lDialogString , " --text \"" ) ; if ( aTitle && strlen(aTitle) ) { strcat(lDialogString, aTitle) ; strcat(lDialogString, "\n") ; } if ( aMessage && strlen( aMessage ) ) { strcat( lDialogString , aMessage ) ; } strcat( lDialogString , " \"" ) ; } else { if (lDialogString) free(lDialogString); return tinyfd_messageBox(aTitle, aMessage, "ok", aIconType, 0); } if (tinyfd_verbose) printf( "lDialogString: %s\n" , lDialogString ) ; if ( ! ( lIn = popen( lDialogString , "r" ) ) ) { free(lDialogString); return 0 ; } pclose( lIn ) ; free(lDialogString); return 1; } /* returns NULL on cancel */ char * tinyfd_inputBox( char const * aTitle , /* NULL or "" */ char const * aMessage , /* NULL or "" (\n and \t have no effect) */ char const * aDefaultInput ) /* "" , if NULL it's a passwordBox */ { static char lBuff[MAX_PATH_OR_CMD]; char * lDialogString = NULL; char * lpDialogString; FILE * lIn ; int lResult ; int lWasGdialog = 0 ; int lWasGraphicDialog = 0 ; int lWasXterm = 0 ; int lWasBasicXterm = 0 ; struct termios oldt ; struct termios newt ; char * lEOF; size_t lTitleLen ; size_t lMessageLen ; if (!aTitle && !aMessage && !aDefaultInput) return lBuff; /* now I can fill lBuff from outside */ lBuff[0]='\0'; if (tfd_quoteDetected(aTitle)) return tinyfd_inputBox("INVALID TITLE WITH QUOTES", aMessage, aDefaultInput); if (tfd_quoteDetected(aMessage)) return tinyfd_inputBox(aTitle, "INVALID MESSAGE WITH QUOTES", aDefaultInput); if (tfd_quoteDetected(aDefaultInput)) return tinyfd_inputBox(aTitle, aMessage, "INVALID DEFAULT_INPUT WITH QUOTES: use the GRAVE ACCENT \\x60 instead."); lTitleLen = aTitle ? strlen(aTitle) : 0 ; lMessageLen = aMessage ? strlen(aMessage) : 0 ; if ( !aTitle || strcmp(aTitle,"tinyfd_query") ) { lDialogString = (char *) malloc( MAX_PATH_OR_CMD + lTitleLen + lMessageLen ); } if ( osascriptPresent( ) ) { if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"applescript");return (char *)1;} strcpy( lDialogString , "osascript "); if ( ! osx9orBetter() ) strcat( lDialogString , " -e 'tell application \"System Events\"' -e 'Activate'"); strcat( lDialogString , " -e 'try' -e 'display dialog \"") ; if ( aMessage && strlen(aMessage) ) { strcat(lDialogString, aMessage) ; } strcat(lDialogString, "\" ") ; strcat(lDialogString, "default answer \"") ; if ( aDefaultInput && strlen(aDefaultInput) ) { strcat(lDialogString, aDefaultInput) ; } strcat(lDialogString, "\" ") ; if ( ! aDefaultInput ) { strcat(lDialogString, "hidden answer true ") ; } if ( aTitle && strlen(aTitle) ) { strcat(lDialogString, "with title \"") ; strcat(lDialogString, aTitle) ; strcat(lDialogString, "\" ") ; } strcat(lDialogString, "with icon note' ") ; strcat(lDialogString, "-e '\"1\" & text returned of result' " ); strcat(lDialogString, "-e 'on error number -128' " ) ; strcat(lDialogString, "-e '0' " ); strcat(lDialogString, "-e 'end try'") ; if ( ! osx9orBetter() ) strcat(lDialogString, " -e 'end tell'") ; } else if ( tfd_kdialogPresent() ) { if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"kdialog");return (char *)1;} strcpy( lDialogString , "szAnswer=$(kdialog" ) ; if ( (tfd_kdialogPresent() == 2) && tfd_xpropPresent() ) { strcat(lDialogString, " --attach=$(xprop -root 32x '\t$0' _NET_ACTIVE_WINDOW | cut -f 2)"); /* contribution: Paul Rouget */ } if ( ! aDefaultInput ) { strcat(lDialogString, " --password ") ; } else { strcat(lDialogString, " --inputbox ") ; } strcat(lDialogString, "\"") ; if ( aMessage && strlen(aMessage) ) { strcat(lDialogString, aMessage ) ; } strcat(lDialogString , "\" \"" ) ; if ( aDefaultInput && strlen(aDefaultInput) ) { strcat(lDialogString, aDefaultInput ) ; } strcat(lDialogString , "\"" ) ; if ( aTitle && strlen(aTitle) ) { strcat(lDialogString, " --title \"") ; strcat(lDialogString, aTitle) ; strcat(lDialogString, "\"") ; } strcat( lDialogString , ");if [ $? = 0 ];then echo 1$szAnswer;else echo 0$szAnswer;fi"); } else if ( tfd_zenityPresent() || tfd_matedialogPresent() || tfd_shellementaryPresent() || tfd_qarmaPresent() ) { if ( tfd_zenityPresent() ) { if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"zenity");return (char *)1;} strcpy( lDialogString , "szAnswer=$(zenity" ) ; if ( (tfd_zenity3Present() >= 4) && !getenv("SSH_TTY") && tfd_xpropPresent() ) { strcat( lDialogString, " --attach=$(sleep .01;xprop -root 32x '\t$0' _NET_ACTIVE_WINDOW | cut -f 2)"); /* contribution: Paul Rouget */ } } else if ( tfd_matedialogPresent() ) { if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"matedialog");return (char *)1;} strcpy( lDialogString , "szAnswer=$(matedialog" ) ; } else if ( tfd_shellementaryPresent() ) { if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"shellementary");return (char *)1;} strcpy( lDialogString , "szAnswer=$(shellementary" ) ; } else { if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"qarma");return (char *)1;} strcpy( lDialogString , "szAnswer=$(qarma" ) ; if ( !getenv("SSH_TTY") && tfd_xpropPresent() ) { strcat(lDialogString, " --attach=$(xprop -root 32x '\t$0' _NET_ACTIVE_WINDOW | cut -f 2)"); /* contribution: Paul Rouget */ } } strcat( lDialogString ," --entry" ) ; strcat(lDialogString, " --title=\"") ; if (aTitle && strlen(aTitle)) strcat(lDialogString, aTitle) ; strcat(lDialogString, "\"") ; strcat(lDialogString, " --text=\"") ; if (aMessage && strlen(aMessage)) strcat(lDialogString, aMessage) ; strcat(lDialogString, "\"") ; if ( aDefaultInput ) { strcat(lDialogString, " --entry-text=\"") ; strcat(lDialogString, aDefaultInput) ; strcat(lDialogString, "\"") ; } else { strcat(lDialogString, " --hide-text") ; } if (tinyfd_silent) strcat( lDialogString , " 2>/dev/null "); strcat( lDialogString , ");if [ $? = 0 ];then echo 1$szAnswer;else echo 0$szAnswer;fi"); } else if (tfd_yadPresent()) { if (aTitle && !strcmp(aTitle, "tinyfd_query")) { strcpy(tinyfd_response, "yad"); return (char*)1; } strcpy(lDialogString, "szAnswer=$(yad --entry"); if (aTitle && strlen(aTitle)) { strcat(lDialogString, " --title=\""); strcat(lDialogString, aTitle); strcat(lDialogString, "\""); } if (aMessage && strlen(aMessage)) { strcat(lDialogString, " --text=\""); strcat(lDialogString, aMessage); strcat(lDialogString, "\""); } if (aDefaultInput && strlen(aDefaultInput)) { strcat(lDialogString, " --entry-text=\""); strcat(lDialogString, aDefaultInput); strcat(lDialogString, "\""); } else { strcat(lDialogString, " --hide-text"); } if (tinyfd_silent) strcat(lDialogString, " 2>/dev/null "); strcat(lDialogString, ");if [ $? = 0 ];then echo 1$szAnswer;else echo 0$szAnswer;fi"); } else if ( gxmessagePresent() || gmessagePresent() ) { if ( gxmessagePresent() ) { if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"gxmessage");return (char *)1;} strcpy( lDialogString , "szAnswer=$(gxmessage -buttons Ok:1,Cancel:0 -center \""); } else { if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"gmessage");return (char *)1;} strcpy( lDialogString , "szAnswer=$(gmessage -buttons Ok:1,Cancel:0 -center \""); } if ( aMessage && strlen(aMessage) ) { strcat( lDialogString , aMessage ) ; } strcat(lDialogString, "\"" ) ; if ( aTitle && strlen(aTitle) ) { strcat( lDialogString , " -title \""); strcat( lDialogString , aTitle ) ; strcat(lDialogString, "\" " ) ; } strcat(lDialogString, " -entrytext \"" ) ; if ( aDefaultInput && strlen(aDefaultInput) ) { strcat( lDialogString , aDefaultInput ) ; } strcat(lDialogString, "\"" ) ; strcat( lDialogString , ");echo $?$szAnswer"); } else if ( !gdialogPresent() && !xdialogPresent() && tkinter3Present( ) ) { if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"python3-tkinter");return (char *)1;} strcpy( lDialogString , gPython3Name ) ; strcat( lDialogString , " -S -c \"import tkinter; from tkinter import simpledialog;root=tkinter.Tk();root.withdraw();"); strcat( lDialogString ,"res=simpledialog.askstring(" ) ; if ( aTitle && strlen(aTitle) ) { strcat(lDialogString, "title='") ; strcat(lDialogString, aTitle) ; strcat(lDialogString, "',") ; } if ( aMessage && strlen(aMessage) ) { strcat(lDialogString, "prompt='") ; lpDialogString = lDialogString + strlen(lDialogString); tfd_replaceSubStr( aMessage , "\n" , "\\n" , lpDialogString ) ; strcat(lDialogString, "',") ; } if ( aDefaultInput ) { if ( strlen(aDefaultInput) ) { strcat(lDialogString, "initialvalue='") ; strcat(lDialogString, aDefaultInput) ; strcat(lDialogString, "',") ; } } else { strcat(lDialogString, "show='*'") ; } strcat(lDialogString, ");\nif res is None :\n\tprint(0)"); strcat(lDialogString, "\nelse :\n\tprint('1'+res)\n\"" ) ; } else if ( !gdialogPresent() && !xdialogPresent() && tkinter2Present( ) ) { if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"python2-tkinter");return (char *)1;} strcpy( lDialogString , "export PYTHONIOENCODING=utf-8;" ) ; strcat( lDialogString , gPython2Name ) ; if ( ! isTerminalRunning( ) && tfd_isDarwin( ) ) { strcat( lDialogString , " -i" ) ; /* for osx without console */ } strcat( lDialogString , " -S -c \"import Tkinter,tkSimpleDialog;root=Tkinter.Tk();root.withdraw();"); if ( tfd_isDarwin( ) ) { strcat( lDialogString , "import os;os.system('''/usr/bin/osascript -e 'tell app \\\"Finder\\\" to set \ frontmost of process \\\"Python\\\" to true' ''');"); } strcat( lDialogString ,"res=tkSimpleDialog.askstring(" ) ; if ( aTitle && strlen(aTitle) ) { strcat(lDialogString, "title='") ; strcat(lDialogString, aTitle) ; strcat(lDialogString, "',") ; } if ( aMessage && strlen(aMessage) ) { strcat(lDialogString, "prompt='") ; lpDialogString = lDialogString + strlen(lDialogString); tfd_replaceSubStr( aMessage , "\n" , "\\n" , lpDialogString ) ; strcat(lDialogString, "',") ; } if ( aDefaultInput ) { if ( strlen(aDefaultInput) ) { strcat(lDialogString, "initialvalue='") ; strcat(lDialogString, aDefaultInput) ; strcat(lDialogString, "',") ; } } else { strcat(lDialogString, "show='*'") ; } strcat(lDialogString, ");\nif res is None :\n\tprint 0"); strcat(lDialogString, "\nelse :\n\tprint '1'+res\n\"" ) ; } else if ( gdialogPresent() || xdialogPresent() || dialogName() || whiptailPresent() ) { if ( gdialogPresent( ) ) { if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"gdialog");return (char *)1;} lWasGraphicDialog = 1 ; lWasGdialog = 1 ; strcpy( lDialogString , "(gdialog " ) ; } else if ( xdialogPresent( ) ) { if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"xdialog");return (char *)1;} lWasGraphicDialog = 1 ; strcpy( lDialogString , "(Xdialog " ) ; } else if ( dialogName( ) ) { if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"dialog");return (char *)0;} if ( isTerminalRunning( ) ) { strcpy( lDialogString , "(dialog " ) ; } else { lWasXterm = 1 ; strcpy( lDialogString , terminalName() ) ; strcat( lDialogString , "'(" ) ; strcat( lDialogString , dialogName() ) ; strcat( lDialogString , " " ) ; } } else if ( isTerminalRunning( ) ) { if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"whiptail");return (char *)0;} strcpy( lDialogString , "(whiptail " ) ; } else { if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"whiptail");return (char *)0;} lWasXterm = 1 ; strcpy( lDialogString , terminalName() ) ; strcat( lDialogString , "'(whiptail " ) ; } if ( aTitle && strlen(aTitle) ) { strcat(lDialogString, "--title \"") ; strcat(lDialogString, aTitle) ; strcat(lDialogString, "\" ") ; } if ( !xdialogPresent() && !gdialogPresent() ) { strcat(lDialogString, "--backtitle \"") ; strcat(lDialogString, "tab: move focus") ; if ( ! aDefaultInput && !lWasGdialog ) { strcat(lDialogString, " (sometimes nothing, no blink nor star, is shown in text field)") ; } strcat(lDialogString, "\" ") ; } if ( aDefaultInput || lWasGdialog ) { strcat( lDialogString , "--inputbox" ) ; } else { if ( !lWasGraphicDialog && dialogName() && isDialogVersionBetter09b() ) { strcat( lDialogString , "--insecure " ) ; } strcat( lDialogString , "--passwordbox" ) ; } strcat( lDialogString , " \"" ) ; if ( aMessage && strlen(aMessage) ) { strcat(lDialogString, aMessage) ; } strcat(lDialogString,"\" 10 60 ") ; if ( aDefaultInput && strlen(aDefaultInput) ) { strcat(lDialogString, "\"") ; strcat(lDialogString, aDefaultInput) ; strcat(lDialogString, "\" ") ; } if ( lWasGraphicDialog ) { strcat(lDialogString,") 2>/tmp/tinyfd.txt;\ if [ $? = 0 ];then tinyfdBool=1;else tinyfdBool=0;fi;\ tinyfdRes=$(cat /tmp/tinyfd.txt);echo $tinyfdBool$tinyfdRes") ; } else { strcat(lDialogString,">/dev/tty ) 2>/tmp/tinyfd.txt;\ if [ $? = 0 ];then tinyfdBool=1;else tinyfdBool=0;fi;\ tinyfdRes=$(cat /tmp/tinyfd.txt);echo $tinyfdBool$tinyfdRes") ; if ( lWasXterm ) { strcat(lDialogString," >/tmp/tinyfd0.txt';cat /tmp/tinyfd0.txt"); } else { strcat(lDialogString, "; clear >/dev/tty") ; } } } else if ( ! isTerminalRunning( ) && terminalName() ) { if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"basicinput");return (char *)0;} lWasBasicXterm = 1 ; strcpy( lDialogString , terminalName() ) ; strcat( lDialogString , "'" ) ; if ( !gWarningDisplayed && !tinyfd_forceConsole) { gWarningDisplayed = 1 ; tinyfd_messageBox(gTitle,tinyfd_needs,"ok","warning",0); } if ( aTitle && strlen(aTitle) && !tinyfd_forceConsole) { strcat( lDialogString , "echo \"" ) ; strcat( lDialogString, aTitle) ; strcat( lDialogString , "\";echo;" ) ; } strcat( lDialogString , "echo \"" ) ; if ( aMessage && strlen(aMessage) ) { strcat( lDialogString, aMessage) ; } strcat( lDialogString , "\";read " ) ; if ( ! aDefaultInput ) { strcat( lDialogString , "-s " ) ; } strcat( lDialogString , "-p \"" ) ; strcat( lDialogString , "(esc+enter to cancel): \" ANSWER " ) ; strcat( lDialogString , ";echo 1$ANSWER >/tmp/tinyfd.txt';" ) ; strcat( lDialogString , "cat -v /tmp/tinyfd.txt"); } else if ( !gWarningDisplayed && ! isTerminalRunning( ) && ! terminalName() ) { gWarningDisplayed = 1 ; tinyfd_messageBox(gTitle,tinyfd_needs,"ok","warning",0); if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"no_solution");return (char *)0;} free(lDialogString); return NULL; } else { if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"basicinput");return (char *)0;} if ( !gWarningDisplayed && !tinyfd_forceConsole) { gWarningDisplayed = 1 ; tinyfd_messageBox(gTitle,tinyfd_needs,"ok","warning",0); } if ( aTitle && strlen(aTitle) ) { printf("\n%s\n", aTitle); } if ( aMessage && strlen(aMessage) ) { printf("\n%s\n",aMessage); } printf("(esc+enter to cancel): "); fflush(stdout); if ( ! aDefaultInput ) { tcgetattr(STDIN_FILENO, & oldt) ; newt = oldt ; newt.c_lflag &= ~ECHO ; tcsetattr(STDIN_FILENO, TCSANOW, & newt); } lEOF = fgets(lBuff, MAX_PATH_OR_CMD, stdin); /* printf("lbuff<%c><%d>\n",lBuff[0],lBuff[0]); */ if ( ! lEOF || (lBuff[0] == '\0') ) { free(lDialogString); return NULL; } if ( lBuff[0] == '\n' ) { lEOF = fgets(lBuff, MAX_PATH_OR_CMD, stdin); /* printf("lbuff<%c><%d>\n",lBuff[0],lBuff[0]); */ if ( ! lEOF || (lBuff[0] == '\0') ) { free(lDialogString); return NULL; } } if ( ! aDefaultInput ) { tcsetattr(STDIN_FILENO, TCSANOW, & oldt); printf("\n"); } printf("\n"); if ( strchr(lBuff,27) ) { free(lDialogString); return NULL ; } if ( lBuff[strlen( lBuff ) -1] == '\n' ) { lBuff[strlen( lBuff ) -1] = '\0' ; } free(lDialogString); return lBuff ; } if (tinyfd_verbose) printf( "lDialogString: %s\n" , lDialogString ) ; lIn = popen( lDialogString , "r" ); if ( ! lIn ) { if ( fileExists("/tmp/tinyfd.txt") ) { wipefile("/tmp/tinyfd.txt"); remove("/tmp/tinyfd.txt"); } if ( fileExists("/tmp/tinyfd0.txt") ) { wipefile("/tmp/tinyfd0.txt"); remove("/tmp/tinyfd0.txt"); } free(lDialogString); return NULL ; } while ( fgets( lBuff , sizeof( lBuff ) , lIn ) != NULL ) {} pclose( lIn ) ; if ( fileExists("/tmp/tinyfd.txt") ) { wipefile("/tmp/tinyfd.txt"); remove("/tmp/tinyfd.txt"); } if ( fileExists("/tmp/tinyfd0.txt") ) { wipefile("/tmp/tinyfd0.txt"); remove("/tmp/tinyfd0.txt"); } /* printf( "len Buff: %lu\n" , strlen(lBuff) ) ; */ /* printf( "lBuff0: %s\n" , lBuff ) ; */ if ( strlen( lBuff ) && lBuff[strlen( lBuff ) -1] == '\n' ) { lBuff[strlen( lBuff ) -1] = '\0' ; } /* printf( "lBuff1: %s len: %lu \n" , lBuff , strlen(lBuff) ) ; */ if ( lWasBasicXterm ) { if ( strstr(lBuff,"^[") ) /* esc was pressed */ { free(lDialogString); return NULL ; } } lResult = strncmp( lBuff , "1" , 1) ? 0 : 1 ; /* printf( "lResult: %d \n" , lResult ) ; */ if ( ! lResult ) { free(lDialogString); return NULL ; } /* printf( "lBuff+1: %s\n" , lBuff+1 ) ; */ free(lDialogString); return lBuff+1 ; } char * tinyfd_saveFileDialog( char const * aTitle , /* NULL or "" */ char const * aDefaultPathAndOrFile , /* NULL or "" , ends with / to set only a directory */ int aNumOfFilterPatterns , /* 0 */ char const * const * aFilterPatterns , /* NULL or {"*.txt","*.doc"} */ char const * aSingleFilterDescription ) /* NULL or "text files" */ { static char lBuff[MAX_PATH_OR_CMD] ; static char lLastDirectory[MAX_PATH_OR_CMD] = "$PWD" ; char lDialogString[MAX_PATH_OR_CMD] ; char lString[MAX_PATH_OR_CMD] ; int i ; int lWasGraphicDialog = 0 ; int lWasXterm = 0 ; char * p ; char * lPointerInputBox ; FILE * lIn ; lBuff[0]='\0'; if ( ! aFilterPatterns ) aNumOfFilterPatterns = 0 ; if (tfd_quoteDetected(aTitle)) return tinyfd_saveFileDialog("INVALID TITLE WITH QUOTES", aDefaultPathAndOrFile, aNumOfFilterPatterns, aFilterPatterns, aSingleFilterDescription); if (tfd_quoteDetected(aDefaultPathAndOrFile)) return tinyfd_saveFileDialog(aTitle, "INVALID DEFAULT_PATH WITH QUOTES", aNumOfFilterPatterns, aFilterPatterns, aSingleFilterDescription); if (tfd_quoteDetected(aSingleFilterDescription)) return tinyfd_saveFileDialog(aTitle, aDefaultPathAndOrFile, aNumOfFilterPatterns, aFilterPatterns, "INVALID FILTER_DESCRIPTION WITH QUOTES"); for (i = 0; i < aNumOfFilterPatterns; i++) { if (tfd_quoteDetected(aFilterPatterns[i])) return tinyfd_saveFileDialog("INVALID FILTER_PATTERN WITH QUOTES: use the GRAVE ACCENT \\x60 instead.", aDefaultPathAndOrFile, 0, NULL, NULL); } if ( osascriptPresent( ) ) { if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"applescript");return (char *)1;} strcpy( lDialogString , "osascript "); if ( ! osx9orBetter() ) strcat( lDialogString , " -e 'tell application \"Finder\"' -e 'Activate'"); strcat( lDialogString , " -e 'try' -e 'POSIX path of ( choose file name " ); if ( aTitle && strlen(aTitle) ) { strcat(lDialogString, "with prompt \"") ; strcat(lDialogString, aTitle) ; strcat(lDialogString, "\" ") ; } getPathWithoutFinalSlash( lString , aDefaultPathAndOrFile ) ; if ( strlen(lString) ) { strcat(lDialogString, "default location \"") ; strcat(lDialogString, lString ) ; strcat(lDialogString , "\" " ) ; } getLastName( lString , aDefaultPathAndOrFile ) ; if ( strlen(lString) ) { strcat(lDialogString, "default name \"") ; strcat(lDialogString, lString ) ; strcat(lDialogString , "\" " ) ; } strcat( lDialogString , ")' " ) ; strcat(lDialogString, "-e 'on error number -128' " ) ; strcat(lDialogString, "-e 'end try'") ; if ( ! osx9orBetter() ) strcat( lDialogString, " -e 'end tell'") ; } else if ( tfd_kdialogPresent() ) { if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"kdialog");return (char *)1;} strcpy( lDialogString , "kdialog" ) ; if ( (tfd_kdialogPresent() == 2) && tfd_xpropPresent() ) { strcat(lDialogString, " --attach=$(xprop -root 32x '\t$0' _NET_ACTIVE_WINDOW | cut -f 2)"); /* contribution: Paul Rouget */ } strcat( lDialogString , " --getsavefilename " ) ; if ( aDefaultPathAndOrFile && strlen(aDefaultPathAndOrFile) ) { if ( aDefaultPathAndOrFile[0] != '/' ) { strcat(lDialogString, lLastDirectory) ; strcat(lDialogString , "/" ) ; } strcat(lDialogString, "\"") ; strcat(lDialogString, aDefaultPathAndOrFile ) ; strcat(lDialogString , "\"" ) ; } else { strcat(lDialogString, lLastDirectory) ; strcat(lDialogString , "/" ) ; } if ( aNumOfFilterPatterns > 0 ) { strcat(lDialogString , " \"" ) ; strcat( lDialogString , aFilterPatterns[0] ) ; for ( i = 1 ; i < aNumOfFilterPatterns ; i ++ ) { strcat( lDialogString , " " ) ; strcat( lDialogString , aFilterPatterns[i] ) ; } if ( aSingleFilterDescription && strlen(aSingleFilterDescription) ) { strcat( lDialogString , " | " ) ; strcat( lDialogString , aSingleFilterDescription ) ; } strcat( lDialogString , "\"" ) ; } if ( aTitle && strlen(aTitle) ) { strcat(lDialogString, " --title \"") ; strcat(lDialogString, aTitle) ; strcat(lDialogString, "\"") ; } } else if ( tfd_zenityPresent() || tfd_matedialogPresent() || tfd_shellementaryPresent() || tfd_qarmaPresent() ) { if ( tfd_zenityPresent() ) { if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"zenity");return (char *)1;} strcpy( lDialogString , "zenity" ) ; if ( (tfd_zenity3Present() >= 4) && !getenv("SSH_TTY") && tfd_xpropPresent() ) { strcat( lDialogString, " --attach=$(sleep .01;xprop -root 32x '\t$0' _NET_ACTIVE_WINDOW | cut -f 2)"); /* contribution: Paul Rouget */ } } else if ( tfd_matedialogPresent() ) { if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"matedialog");return (char *)1;} strcpy( lDialogString , "matedialog" ) ; } else if ( tfd_shellementaryPresent() ) { if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"shellementary");return (char *)1;} strcpy( lDialogString , "shellementary" ) ; } else { if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"qarma");return (char *)1;} strcpy( lDialogString , "qarma" ) ; if ( !getenv("SSH_TTY") && tfd_xpropPresent() ) { strcat(lDialogString, " --attach=$(xprop -root 32x '\t$0' _NET_ACTIVE_WINDOW | cut -f 2)"); /* contribution: Paul Rouget */ } } strcat(lDialogString, " --file-selection --save --confirm-overwrite" ) ; strcat(lDialogString, " --title=\"") ; if (aTitle && strlen(aTitle)) strcat(lDialogString, aTitle) ; strcat(lDialogString, "\"") ; if ( aDefaultPathAndOrFile && strlen(aDefaultPathAndOrFile) ) { strcat(lDialogString, " --filename=\"") ; strcat(lDialogString, aDefaultPathAndOrFile) ; strcat(lDialogString, "\"") ; } if ( aNumOfFilterPatterns > 0 ) { strcat( lDialogString , " --file-filter='" ) ; if ( aSingleFilterDescription && strlen(aSingleFilterDescription) ) { strcat( lDialogString , aSingleFilterDescription ) ; strcat( lDialogString , " |" ) ; } for ( i = 0 ; i < aNumOfFilterPatterns ; i ++ ) { strcat( lDialogString , " " ) ; strcat( lDialogString , aFilterPatterns[i] ) ; } strcat( lDialogString , "' --file-filter='All files | *'" ) ; } if (tinyfd_silent) strcat( lDialogString , " 2>/dev/null "); } else if (tfd_yadPresent()) { if (aTitle && !strcmp(aTitle, "tinyfd_query")) { strcpy(tinyfd_response, "yad"); return (char*)1; } strcpy(lDialogString, "yad --file --save --confirm-overwrite"); if (aTitle && strlen(aTitle)) { strcat(lDialogString, " --title=\""); strcat(lDialogString, aTitle); strcat(lDialogString, "\""); } if (aDefaultPathAndOrFile && strlen(aDefaultPathAndOrFile)) { strcat(lDialogString, " --filename=\""); strcat(lDialogString, aDefaultPathAndOrFile); strcat(lDialogString, "\""); } if (aNumOfFilterPatterns > 0) { strcat(lDialogString, " --file-filter='"); if (aSingleFilterDescription && strlen(aSingleFilterDescription)) { strcat(lDialogString, aSingleFilterDescription); strcat(lDialogString, " |"); } for (i = 0; i < aNumOfFilterPatterns; i++) { strcat(lDialogString, " "); strcat(lDialogString, aFilterPatterns[i]); } strcat(lDialogString, "' --file-filter='All files | *'"); } if (tinyfd_silent) strcat(lDialogString, " 2>/dev/null "); } else if ( !xdialogPresent() && tkinter3Present( ) ) { if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"python3-tkinter");return (char *)1;} strcpy( lDialogString , gPython3Name ) ; strcat( lDialogString , " -S -c \"import tkinter;from tkinter import filedialog;root=tkinter.Tk();root.withdraw();"); strcat( lDialogString , "res=filedialog.asksaveasfilename("); if ( aTitle && strlen(aTitle) ) { strcat(lDialogString, "title='") ; strcat(lDialogString, aTitle) ; strcat(lDialogString, "',") ; } if ( aDefaultPathAndOrFile && strlen(aDefaultPathAndOrFile) ) { getPathWithoutFinalSlash( lString , aDefaultPathAndOrFile ) ; if ( strlen(lString) ) { strcat(lDialogString, "initialdir='") ; strcat(lDialogString, lString ) ; strcat(lDialogString , "'," ) ; } getLastName( lString , aDefaultPathAndOrFile ) ; if ( strlen(lString) ) { strcat(lDialogString, "initialfile='") ; strcat(lDialogString, lString ) ; strcat(lDialogString , "'," ) ; } } if ( ( aNumOfFilterPatterns > 1 ) || ( (aNumOfFilterPatterns == 1) /* test because poor osx behaviour */ && ( aFilterPatterns[0][strlen(aFilterPatterns[0])-1] != '*' ) ) ) { strcat(lDialogString , "filetypes=(" ) ; strcat( lDialogString , "('" ) ; if ( aSingleFilterDescription && strlen(aSingleFilterDescription) ) { strcat( lDialogString , aSingleFilterDescription ) ; } strcat( lDialogString , "',(" ) ; for ( i = 0 ; i < aNumOfFilterPatterns ; i ++ ) { strcat( lDialogString , "'" ) ; strcat( lDialogString , aFilterPatterns[i] ) ; strcat( lDialogString , "'," ) ; } strcat( lDialogString , "))," ) ; strcat( lDialogString , "('All files','*'))" ) ; } strcat( lDialogString, ");\nif not isinstance(res, tuple):\n\tprint(res)\n\"" ) ; } else if ( !xdialogPresent() && tkinter2Present( ) ) { if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"python2-tkinter");return (char *)1;} strcpy( lDialogString , "export PYTHONIOENCODING=utf-8;" ) ; strcat( lDialogString , gPython2Name ) ; if ( ! isTerminalRunning( ) && tfd_isDarwin( )) { strcat( lDialogString , " -i" ) ; /* for osx without console */ } strcat( lDialogString , " -S -c \"import Tkinter,tkFileDialog;root=Tkinter.Tk();root.withdraw();"); if ( tfd_isDarwin( ) ) { strcat( lDialogString , "import os;os.system('''/usr/bin/osascript -e 'tell app \\\"Finder\\\" to set\ frontmost of process \\\"Python\\\" to true' ''');"); } strcat( lDialogString , "res=tkFileDialog.asksaveasfilename("); if ( aTitle && strlen(aTitle) ) { strcat(lDialogString, "title='") ; strcat(lDialogString, aTitle) ; strcat(lDialogString, "',") ; } if ( aDefaultPathAndOrFile && strlen(aDefaultPathAndOrFile) ) { getPathWithoutFinalSlash( lString , aDefaultPathAndOrFile ) ; if ( strlen(lString) ) { strcat(lDialogString, "initialdir='") ; strcat(lDialogString, lString ) ; strcat(lDialogString , "'," ) ; } getLastName( lString , aDefaultPathAndOrFile ) ; if ( strlen(lString) ) { strcat(lDialogString, "initialfile='") ; strcat(lDialogString, lString ) ; strcat(lDialogString , "'," ) ; } } if ( ( aNumOfFilterPatterns > 1 ) || ( (aNumOfFilterPatterns == 1) /* test because poor osx behaviour */ && ( aFilterPatterns[0][strlen(aFilterPatterns[0])-1] != '*' ) ) ) { strcat(lDialogString , "filetypes=(" ) ; strcat( lDialogString , "('" ) ; if ( aSingleFilterDescription && strlen(aSingleFilterDescription) ) { strcat( lDialogString , aSingleFilterDescription ) ; } strcat( lDialogString , "',(" ) ; for ( i = 0 ; i < aNumOfFilterPatterns ; i ++ ) { strcat( lDialogString , "'" ) ; strcat( lDialogString , aFilterPatterns[i] ) ; strcat( lDialogString , "'," ) ; } strcat( lDialogString , "))," ) ; strcat( lDialogString , "('All files','*'))" ) ; } strcat( lDialogString, ");\nif not isinstance(res, tuple):\n\tprint res \n\"" ) ; } else if ( xdialogPresent() || dialogName() ) { if ( xdialogPresent( ) ) { if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"xdialog");return (char *)1;} lWasGraphicDialog = 1 ; strcpy( lDialogString , "(Xdialog " ) ; } else if ( isTerminalRunning( ) ) { if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"dialog");return (char *)0;} strcpy( lDialogString , "(dialog " ) ; } else { if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"dialog");return (char *)0;} lWasXterm = 1 ; strcpy( lDialogString , terminalName() ) ; strcat( lDialogString , "'(" ) ; strcat( lDialogString , dialogName() ) ; strcat( lDialogString , " " ) ; } if ( aTitle && strlen(aTitle) ) { strcat(lDialogString, "--title \"") ; strcat(lDialogString, aTitle) ; strcat(lDialogString, "\" ") ; } if ( !xdialogPresent() && !gdialogPresent() ) { strcat(lDialogString, "--backtitle \"") ; strcat(lDialogString, "tab: focus | /: populate | spacebar: fill text field | ok: TEXT FIELD ONLY") ; strcat(lDialogString, "\" ") ; } strcat( lDialogString , "--fselect \"" ) ; if ( aDefaultPathAndOrFile && strlen(aDefaultPathAndOrFile) ) { if ( ! strchr(aDefaultPathAndOrFile, '/') ) { strcat(lDialogString, "./") ; } strcat(lDialogString, aDefaultPathAndOrFile) ; } else if ( ! isTerminalRunning( ) && !lWasGraphicDialog ) { strcat(lDialogString, getenv("HOME")) ; strcat(lDialogString, "/") ; } else { strcat(lDialogString, "./") ; } if ( lWasGraphicDialog ) { strcat(lDialogString, "\" 0 60 ) 2>&1 ") ; } else { strcat(lDialogString, "\" 0 60 >/dev/tty) ") ; if ( lWasXterm ) { strcat( lDialogString , "2>/tmp/tinyfd.txt';cat /tmp/tinyfd.txt;rm /tmp/tinyfd.txt"); } else { strcat(lDialogString, "2>&1 ; clear >/dev/tty") ; } } } else { if (aTitle&&!strcmp(aTitle,"tinyfd_query")){return tinyfd_inputBox(aTitle,NULL,NULL);} strcpy(lBuff, "Save file in "); strcat(lBuff, getCurDir()); lPointerInputBox = tinyfd_inputBox(NULL, NULL, NULL); /* obtain a pointer on the current content of tinyfd_inputBox */ if (lPointerInputBox) strcpy(lString, lPointerInputBox); /* preserve the current content of tinyfd_inputBox */ p = tinyfd_inputBox(aTitle, lBuff, ""); if (p) strcpy(lBuff, p); else lBuff[0] = '\0'; if (lPointerInputBox) strcpy(lPointerInputBox, lString); /* restore its previous content to tinyfd_inputBox */ p = lBuff; getPathWithoutFinalSlash( lString , p ) ; if ( strlen( lString ) && ! dirExists( lString ) ) { return NULL ; } getLastName(lString,p); if ( ! strlen(lString) ) { return NULL; } return p ; } if (tinyfd_verbose) printf( "lDialogString: %s\n" , lDialogString ) ; if ( ! ( lIn = popen( lDialogString , "r" ) ) ) { return NULL ; } while ( fgets( lBuff , sizeof( lBuff ) , lIn ) != NULL ) {} pclose( lIn ) ; if ( strlen(lBuff) && lBuff[strlen( lBuff ) -1] == '\n' ) { lBuff[strlen( lBuff ) -1] = '\0' ; } /* printf( "lBuff: %s\n" , lBuff ) ; */ if ( ! strlen(lBuff) ) { return NULL; } getPathWithoutFinalSlash( lString , lBuff ) ; if ( strlen( lString ) && ! dirExists( lString ) ) { return NULL ; } strcpy(lLastDirectory, lString) ; getLastName(lString,lBuff); if ( ! filenameValid(lString) ) { return NULL; } return lBuff ; } /* in case of multiple files, the separator is | */ char * tinyfd_openFileDialog( char const * aTitle , /* NULL or "" */ char const * aDefaultPathAndOrFile , /* NULL or "" , ends with / to set only a directory */ int aNumOfFilterPatterns , /* 0 */ char const * const * aFilterPatterns , /* NULL or {"*.jpg","*.png"} */ char const * aSingleFilterDescription , /* NULL or "image files" */ int aAllowMultipleSelects ) /* 0 or 1 */ { static char * lBuff = NULL; static char lLastDirectory[MAX_PATH_OR_CMD] = "$PWD" ; char lDialogString[MAX_PATH_OR_CMD] ; char lString[MAX_PATH_OR_CMD] ; int i ; FILE * lIn ; char * p ; char * lPointerInputBox ; size_t lFullBuffLen ; int lWasKdialog = 0 ; int lWasGraphicDialog = 0 ; int lWasXterm = 0 ; if ( ! aFilterPatterns ) aNumOfFilterPatterns = 0 ; if (tfd_quoteDetected(aTitle)) return tinyfd_openFileDialog("INVALID TITLE WITH QUOTES", aDefaultPathAndOrFile, aNumOfFilterPatterns, aFilterPatterns, aSingleFilterDescription, aAllowMultipleSelects); if (tfd_quoteDetected(aDefaultPathAndOrFile)) return tinyfd_openFileDialog(aTitle, "INVALID DEFAULT_PATH WITH QUOTES", aNumOfFilterPatterns, aFilterPatterns, aSingleFilterDescription, aAllowMultipleSelects); if (tfd_quoteDetected(aSingleFilterDescription)) return tinyfd_openFileDialog(aTitle, aDefaultPathAndOrFile, aNumOfFilterPatterns, aFilterPatterns, "INVALID FILTER_DESCRIPTION WITH QUOTES", aAllowMultipleSelects); for (i = 0; i < aNumOfFilterPatterns; i++) { if (tfd_quoteDetected(aFilterPatterns[i])) return tinyfd_openFileDialog("INVALID FILTER_PATTERN WITH QUOTES: use the GRAVE ACCENT \\x60 instead.", aDefaultPathAndOrFile, 0, NULL, NULL, aAllowMultipleSelects); } free(lBuff); if (aTitle&&!strcmp(aTitle,"tinyfd_query")) { lBuff = NULL; } else { if (aAllowMultipleSelects) { lFullBuffLen = MAX_MULTIPLE_FILES * MAX_PATH_OR_CMD + 1; lBuff = (char *) malloc(lFullBuffLen * sizeof(char)); if (!lBuff) { lFullBuffLen = LOW_MULTIPLE_FILES * MAX_PATH_OR_CMD + 1; lBuff = (char *) malloc( lFullBuffLen * sizeof(char)); } } else { lFullBuffLen = MAX_PATH_OR_CMD + 1; lBuff = (char *) malloc(lFullBuffLen * sizeof(char)); } if (!lBuff) return NULL; lBuff[0]='\0'; } if ( osascriptPresent( ) ) { if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"applescript");return (char *)1;} strcpy( lDialogString , "osascript "); if ( ! osx9orBetter() ) strcat( lDialogString , " -e 'tell application \"System Events\"' -e 'Activate'"); strcat( lDialogString , " -e 'try' -e '" ); if ( ! aAllowMultipleSelects ) { strcat( lDialogString , "POSIX path of ( " ); } else { strcat( lDialogString , "set mylist to " ); } strcat( lDialogString , "choose file " ); if ( aTitle && strlen(aTitle) ) { strcat(lDialogString, "with prompt \"") ; strcat(lDialogString, aTitle) ; strcat(lDialogString, "\" ") ; } getPathWithoutFinalSlash( lString , aDefaultPathAndOrFile ) ; if ( strlen(lString) ) { strcat(lDialogString, "default location \"") ; strcat(lDialogString, lString ) ; strcat(lDialogString , "\" " ) ; } if ( aNumOfFilterPatterns > 0 ) { strcat(lDialogString , "of type {\"" ); strcat( lDialogString , aFilterPatterns[0] + 2 ) ; strcat( lDialogString , "\"" ) ; for ( i = 1 ; i < aNumOfFilterPatterns ; i ++ ) { strcat( lDialogString , ",\"" ) ; strcat( lDialogString , aFilterPatterns[i] + 2) ; strcat( lDialogString , "\"" ) ; } strcat( lDialogString , "} " ) ; } if ( aAllowMultipleSelects ) { strcat( lDialogString , "multiple selections allowed true ' " ) ; strcat( lDialogString , "-e 'set mystring to POSIX path of item 1 of mylist' " ); strcat( lDialogString , "-e 'repeat with i from 2 to the count of mylist' " ); strcat( lDialogString , "-e 'set mystring to mystring & \"|\"' " ); strcat( lDialogString , "-e 'set mystring to mystring & POSIX path of item i of mylist' " ); strcat( lDialogString , "-e 'end repeat' " ); strcat( lDialogString , "-e 'mystring' " ); } else { strcat( lDialogString , ")' " ) ; } strcat(lDialogString, "-e 'on error number -128' " ) ; strcat(lDialogString, "-e 'end try'") ; if ( ! osx9orBetter() ) strcat( lDialogString, " -e 'end tell'") ; } else if ( tfd_kdialogPresent() ) { if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"kdialog");return (char *)1;} lWasKdialog = 1 ; strcpy( lDialogString , "kdialog" ) ; if ( (tfd_kdialogPresent() == 2) && tfd_xpropPresent() ) { strcat(lDialogString, " --attach=$(xprop -root 32x '\t$0' _NET_ACTIVE_WINDOW | cut -f 2)"); /* contribution: Paul Rouget */ } strcat( lDialogString , " --getopenfilename " ) ; if ( aDefaultPathAndOrFile && strlen(aDefaultPathAndOrFile) ) { if ( aDefaultPathAndOrFile[0] != '/' ) { strcat(lDialogString, lLastDirectory) ; strcat(lDialogString , "/" ) ; } strcat(lDialogString, "\"") ; strcat(lDialogString, aDefaultPathAndOrFile ) ; strcat(lDialogString , "\"" ) ; } else { strcat(lDialogString, lLastDirectory) ; strcat(lDialogString , "/" ) ; } if ( aNumOfFilterPatterns > 0 ) { strcat(lDialogString , " \"" ) ; strcat( lDialogString , aFilterPatterns[0] ) ; for ( i = 1 ; i < aNumOfFilterPatterns ; i ++ ) { strcat( lDialogString , " " ) ; strcat( lDialogString , aFilterPatterns[i] ) ; } if ( aSingleFilterDescription && strlen(aSingleFilterDescription) ) { strcat( lDialogString , " | " ) ; strcat( lDialogString , aSingleFilterDescription ) ; } strcat( lDialogString , "\"" ) ; } if ( aAllowMultipleSelects ) { strcat( lDialogString , " --multiple --separate-output" ) ; } if ( aTitle && strlen(aTitle) ) { strcat(lDialogString, " --title \"") ; strcat(lDialogString, aTitle) ; strcat(lDialogString, "\"") ; } } else if ( tfd_zenityPresent() || tfd_matedialogPresent() || tfd_shellementaryPresent() || tfd_qarmaPresent() ) { if ( tfd_zenityPresent() ) { if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"zenity");return (char *)1;} strcpy( lDialogString , "zenity" ) ; if ( (tfd_zenity3Present() >= 4) && !getenv("SSH_TTY") && tfd_xpropPresent() ) { strcat( lDialogString, " --attach=$(sleep .01;xprop -root 32x '\t$0' _NET_ACTIVE_WINDOW | cut -f 2)"); /* contribution: Paul Rouget */ } } else if ( tfd_matedialogPresent() ) { if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"matedialog");return (char *)1;} strcpy( lDialogString , "matedialog" ) ; } else if ( tfd_shellementaryPresent() ) { if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"shellementary");return (char *)1;} strcpy( lDialogString , "shellementary" ) ; } else { if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"qarma");return (char *)1;} strcpy( lDialogString , "qarma" ) ; if ( !getenv("SSH_TTY") && tfd_xpropPresent() ) { strcat(lDialogString, " --attach=$(xprop -root 32x '\t$0' _NET_ACTIVE_WINDOW | cut -f 2)"); /* contribution: Paul Rouget */ } } strcat( lDialogString , " --file-selection" ) ; if ( aAllowMultipleSelects ) { strcat( lDialogString , " --multiple" ) ; } strcat(lDialogString, " --title=\"") ; if (aTitle && strlen(aTitle)) strcat(lDialogString, aTitle) ; strcat(lDialogString, "\"") ; if ( aDefaultPathAndOrFile && strlen(aDefaultPathAndOrFile) ) { strcat(lDialogString, " --filename=\"") ; strcat(lDialogString, aDefaultPathAndOrFile) ; strcat(lDialogString, "\"") ; } if ( aNumOfFilterPatterns > 0 ) { strcat( lDialogString , " --file-filter='" ) ; if ( aSingleFilterDescription && strlen(aSingleFilterDescription) ) { strcat( lDialogString , aSingleFilterDescription ) ; strcat( lDialogString , " |" ) ; } for ( i = 0 ; i < aNumOfFilterPatterns ; i ++ ) { strcat( lDialogString , " " ) ; strcat( lDialogString , aFilterPatterns[i] ) ; } strcat( lDialogString , "' --file-filter='All files | *'" ) ; } if (tinyfd_silent) strcat( lDialogString , " 2>/dev/null "); } else if (tfd_yadPresent()) { if (aTitle && !strcmp(aTitle, "tinyfd_query")) { strcpy(tinyfd_response, "yad"); return (char*)1; } strcpy(lDialogString, "yad --file"); if (aAllowMultipleSelects) { strcat(lDialogString, " --multiple"); } if (aTitle && strlen(aTitle)) { strcat(lDialogString, " --title=\""); strcat(lDialogString, aTitle); strcat(lDialogString, "\""); } if (aDefaultPathAndOrFile && strlen(aDefaultPathAndOrFile)) { strcat(lDialogString, " --filename=\""); strcat(lDialogString, aDefaultPathAndOrFile); strcat(lDialogString, "\""); } if (aNumOfFilterPatterns > 0) { strcat(lDialogString, " --file-filter='"); if (aSingleFilterDescription && strlen(aSingleFilterDescription)) { strcat(lDialogString, aSingleFilterDescription); strcat(lDialogString, " |"); } for (i = 0; i < aNumOfFilterPatterns; i++) { strcat(lDialogString, " "); strcat(lDialogString, aFilterPatterns[i]); } strcat(lDialogString, "' --file-filter='All files | *'"); } if (tinyfd_silent) strcat(lDialogString, " 2>/dev/null "); } else if ( tkinter3Present( ) ) { if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"python3-tkinter");return (char *)1;} strcpy( lDialogString , gPython3Name ) ; strcat( lDialogString , " -S -c \"import tkinter;from tkinter import filedialog;root=tkinter.Tk();root.withdraw();"); strcat( lDialogString , "lFiles=filedialog.askopenfilename("); if ( aAllowMultipleSelects ) { strcat( lDialogString , "multiple=1," ) ; } if ( aTitle && strlen(aTitle) ) { strcat(lDialogString, "title='") ; strcat(lDialogString, aTitle) ; strcat(lDialogString, "',") ; } if ( aDefaultPathAndOrFile && strlen(aDefaultPathAndOrFile) ) { getPathWithoutFinalSlash( lString , aDefaultPathAndOrFile ) ; if ( strlen(lString) ) { strcat(lDialogString, "initialdir='") ; strcat(lDialogString, lString ) ; strcat(lDialogString , "'," ) ; } getLastName( lString , aDefaultPathAndOrFile ) ; if ( strlen(lString) ) { strcat(lDialogString, "initialfile='") ; strcat(lDialogString, lString ) ; strcat(lDialogString , "'," ) ; } } if ( ( aNumOfFilterPatterns > 1 ) || ( ( aNumOfFilterPatterns == 1 ) /*test because poor osx behaviour*/ && ( aFilterPatterns[0][strlen(aFilterPatterns[0])-1] != '*' ) ) ) { strcat(lDialogString , "filetypes=(" ) ; strcat( lDialogString , "('" ) ; if ( aSingleFilterDescription && strlen(aSingleFilterDescription) ) { strcat( lDialogString , aSingleFilterDescription ) ; } strcat( lDialogString , "',(" ) ; for ( i = 0 ; i < aNumOfFilterPatterns ; i ++ ) { strcat( lDialogString , "'" ) ; strcat( lDialogString , aFilterPatterns[i] ) ; strcat( lDialogString , "'," ) ; } strcat( lDialogString , "))," ) ; strcat( lDialogString , "('All files','*'))" ) ; } strcat( lDialogString , ");\ \nif not isinstance(lFiles, tuple):\n\tprint(lFiles)\nelse:\ \n\tlFilesString=''\n\tfor lFile in lFiles:\n\t\tlFilesString+=str(lFile)+'|'\ \n\tprint(lFilesString[:-1])\n\"" ) ; } else if ( tkinter2Present( ) ) { if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"python2-tkinter");return (char *)1;} strcpy( lDialogString , "export PYTHONIOENCODING=utf-8;" ) ; strcat( lDialogString , gPython2Name ) ; if ( ! isTerminalRunning( ) && tfd_isDarwin( ) ) { strcat( lDialogString , " -i" ) ; /* for osx without console */ } strcat( lDialogString , " -S -c \"import Tkinter,tkFileDialog;root=Tkinter.Tk();root.withdraw();"); if ( tfd_isDarwin( ) ) { strcat( lDialogString , "import os;os.system('''/usr/bin/osascript -e 'tell app \\\"Finder\\\" to set \ frontmost of process \\\"Python\\\" to true' ''');"); } strcat( lDialogString , "lFiles=tkFileDialog.askopenfilename("); if ( aAllowMultipleSelects ) { strcat( lDialogString , "multiple=1," ) ; } if ( aTitle && strlen(aTitle) ) { strcat(lDialogString, "title='") ; strcat(lDialogString, aTitle) ; strcat(lDialogString, "',") ; } if ( aDefaultPathAndOrFile && strlen(aDefaultPathAndOrFile) ) { getPathWithoutFinalSlash( lString , aDefaultPathAndOrFile ) ; if ( strlen(lString) ) { strcat(lDialogString, "initialdir='") ; strcat(lDialogString, lString ) ; strcat(lDialogString , "'," ) ; } getLastName( lString , aDefaultPathAndOrFile ) ; if ( strlen(lString) ) { strcat(lDialogString, "initialfile='") ; strcat(lDialogString, lString ) ; strcat(lDialogString , "'," ) ; } } if ( ( aNumOfFilterPatterns > 1 ) || ( ( aNumOfFilterPatterns == 1 ) /*test because poor osx behaviour*/ && ( aFilterPatterns[0][strlen(aFilterPatterns[0])-1] != '*' ) ) ) { strcat(lDialogString , "filetypes=(" ) ; strcat( lDialogString , "('" ) ; if ( aSingleFilterDescription && strlen(aSingleFilterDescription) ) { strcat( lDialogString , aSingleFilterDescription ) ; } strcat( lDialogString , "',(" ) ; for ( i = 0 ; i < aNumOfFilterPatterns ; i ++ ) { strcat( lDialogString , "'" ) ; strcat( lDialogString , aFilterPatterns[i] ) ; strcat( lDialogString , "'," ) ; } strcat( lDialogString , "))," ) ; strcat( lDialogString , "('All files','*'))" ) ; } strcat( lDialogString , ");\ \nif not isinstance(lFiles, tuple):\n\tprint lFiles\nelse:\ \n\tlFilesString=''\n\tfor lFile in lFiles:\n\t\tlFilesString+=str(lFile)+'|'\ \n\tprint lFilesString[:-1]\n\"" ) ; } else if ( xdialogPresent() || dialogName() ) { if ( xdialogPresent( ) ) { if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"xdialog");return (char *)1;} lWasGraphicDialog = 1 ; strcpy( lDialogString , "(Xdialog " ) ; } else if ( isTerminalRunning( ) ) { if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"dialog");return (char *)0;} strcpy( lDialogString , "(dialog " ) ; } else { if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"dialog");return (char *)0;} lWasXterm = 1 ; strcpy( lDialogString , terminalName() ) ; strcat( lDialogString , "'(" ) ; strcat( lDialogString , dialogName() ) ; strcat( lDialogString , " " ) ; } if ( aTitle && strlen(aTitle) ) { strcat(lDialogString, "--title \"") ; strcat(lDialogString, aTitle) ; strcat(lDialogString, "\" ") ; } if ( !xdialogPresent() && !gdialogPresent() ) { strcat(lDialogString, "--backtitle \"") ; strcat(lDialogString, "tab: focus | /: populate | spacebar: fill text field | ok: TEXT FIELD ONLY") ; strcat(lDialogString, "\" ") ; } strcat( lDialogString , "--fselect \"" ) ; if ( aDefaultPathAndOrFile && strlen(aDefaultPathAndOrFile) ) { if ( ! strchr(aDefaultPathAndOrFile, '/') ) { strcat(lDialogString, "./") ; } strcat(lDialogString, aDefaultPathAndOrFile) ; } else if ( ! isTerminalRunning( ) && !lWasGraphicDialog ) { strcat(lDialogString, getenv("HOME")) ; strcat(lDialogString, "/"); } else { strcat(lDialogString, "./") ; } if ( lWasGraphicDialog ) { strcat(lDialogString, "\" 0 60 ) 2>&1 ") ; } else { strcat(lDialogString, "\" 0 60 >/dev/tty) ") ; if ( lWasXterm ) { strcat( lDialogString , "2>/tmp/tinyfd.txt';cat /tmp/tinyfd.txt;rm /tmp/tinyfd.txt"); } else { strcat(lDialogString, "2>&1 ; clear >/dev/tty") ; } } } else { if (aTitle&&!strcmp(aTitle,"tinyfd_query")){return tinyfd_inputBox(aTitle,NULL,NULL);} strcpy(lBuff, "Open file from "); strcat(lBuff, getCurDir()); lPointerInputBox = tinyfd_inputBox(NULL, NULL, NULL); /* obtain a pointer on the current content of tinyfd_inputBox */ if (lPointerInputBox) strcpy(lDialogString, lPointerInputBox); /* preserve the current content of tinyfd_inputBox */ p = tinyfd_inputBox(aTitle, lBuff, ""); if ( p ) strcpy(lBuff, p); else lBuff[0] = '\0'; if (lPointerInputBox) strcpy(lPointerInputBox, lDialogString); /* restore its previous content to tinyfd_inputBox */ if ( ! fileExists(lBuff) ) { free(lBuff); lBuff = NULL; } else { lBuff = (char *)( realloc( lBuff, (strlen(lBuff)+1) * sizeof(char))); } return lBuff ; } if (tinyfd_verbose) printf( "lDialogString: %s\n" , lDialogString ) ; if ( ! ( lIn = popen( lDialogString , "r" ) ) ) { free(lBuff); lBuff = NULL; return NULL ; } lBuff[0]='\0'; p = lBuff; while ( fgets( p , sizeof( lBuff ) , lIn ) != NULL ) { p += strlen( p ); } pclose( lIn ) ; if ( strlen( lBuff ) && lBuff[strlen( lBuff ) -1] == '\n' ) { lBuff[strlen( lBuff ) -1] = '\0' ; } /* printf( "strlen lBuff: %d\n" , strlen( lBuff ) ) ; */ if ( lWasKdialog && aAllowMultipleSelects ) { p = lBuff ; while ( ( p = strchr( p , '\n' ) ) ) * p = '|' ; } /* printf( "lBuff2: %s\n" , lBuff ) ; */ if ( ! strlen( lBuff ) ) { free(lBuff); lBuff = NULL; return NULL; } if ( aAllowMultipleSelects && strchr(lBuff, '|') ) { if( ! ensureFilesExist( lBuff , lBuff ) ) { free(lBuff); lBuff = NULL; return NULL; } } else if ( !fileExists(lBuff) ) { free(lBuff); lBuff = NULL; return NULL; } p = strrchr(lBuff, '|'); if ( !p ) p = lBuff ; else p ++ ; getPathWithoutFinalSlash( lString , p ) ; /* printf( "lString [%lu]: %s\n" , strlen(lString) , lString ) ; */ if ( strlen( lString ) && ! dirExists( lString ) ) { return NULL ; } strcpy(lLastDirectory, lString) ; lBuff = (char *)( realloc( lBuff, (strlen(lBuff)+1) * sizeof(char))); /*printf( "lBuff3 [%lu]: %s\n" , strlen(lBuff) , lBuff ) ; */ return lBuff ; } char * tinyfd_selectFolderDialog( char const * aTitle , /* "" */ char const * aDefaultPath ) /* "" */ { static char lBuff[MAX_PATH_OR_CMD] ; static char lLastDirectory[MAX_PATH_OR_CMD] = "$PWD" ; char lDialogString[MAX_PATH_OR_CMD] ; FILE * lIn ; char * p ; char * lPointerInputBox ; int lWasGraphicDialog = 0 ; int lWasXterm = 0 ; lBuff[0]='\0'; if (tfd_quoteDetected(aTitle)) return tinyfd_selectFolderDialog("INVALID TITLE WITH QUOTES", aDefaultPath); if (tfd_quoteDetected(aDefaultPath)) return tinyfd_selectFolderDialog(aTitle, "INVALID DEFAULT_PATH WITH QUOTES"); if ( osascriptPresent( )) { if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"applescript");return (char *)1;} strcpy( lDialogString , "osascript "); if ( ! osx9orBetter() ) strcat( lDialogString , " -e 'tell application \"System Events\"' -e 'Activate'"); strcat( lDialogString , " -e 'try' -e 'POSIX path of ( choose folder "); if ( aTitle && strlen(aTitle) ) { strcat(lDialogString, "with prompt \"") ; strcat(lDialogString, aTitle) ; strcat(lDialogString, "\" ") ; } if ( aDefaultPath && strlen(aDefaultPath) ) { strcat(lDialogString, "default location \"") ; strcat(lDialogString, aDefaultPath ) ; strcat(lDialogString , "\" " ) ; } strcat( lDialogString , ")' " ) ; strcat(lDialogString, "-e 'on error number -128' " ) ; strcat(lDialogString, "-e 'end try'") ; if ( ! osx9orBetter() ) strcat( lDialogString, " -e 'end tell'") ; } else if ( tfd_kdialogPresent() ) { if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"kdialog");return (char *)1;} strcpy( lDialogString , "kdialog" ) ; if ( (tfd_kdialogPresent() == 2) && tfd_xpropPresent() ) { strcat(lDialogString, " --attach=$(xprop -root 32x '\t$0' _NET_ACTIVE_WINDOW | cut -f 2)"); /* contribution: Paul Rouget */ } strcat( lDialogString , " --getexistingdirectory " ) ; if ( aDefaultPath && strlen(aDefaultPath) ) { if ( aDefaultPath[0] != '/' ) { strcat(lDialogString, lLastDirectory) ; strcat(lDialogString , "/" ) ; } strcat(lDialogString, "\"") ; strcat(lDialogString, aDefaultPath ) ; strcat(lDialogString , "\"" ) ; } else { strcat(lDialogString, lLastDirectory) ; strcat(lDialogString , "/" ) ; } if ( aTitle && strlen(aTitle) ) { strcat(lDialogString, " --title \"") ; strcat(lDialogString, aTitle) ; strcat(lDialogString, "\"") ; } } else if ( tfd_zenityPresent() || tfd_matedialogPresent() || tfd_shellementaryPresent() || tfd_qarmaPresent() ) { if ( tfd_zenityPresent() ) { if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"zenity");return (char *)1;} strcpy( lDialogString , "zenity" ) ; if ( (tfd_zenity3Present() >= 4) && !getenv("SSH_TTY") && tfd_xpropPresent() ) { strcat( lDialogString, " --attach=$(sleep .01;xprop -root 32x '\t$0' _NET_ACTIVE_WINDOW | cut -f 2)"); /* contribution: Paul Rouget */ } } else if ( tfd_matedialogPresent() ) { if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"matedialog");return (char *)1;} strcpy( lDialogString , "matedialog" ) ; } else if ( tfd_shellementaryPresent() ) { if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"shellementary");return (char *)1;} strcpy( lDialogString , "shellementary" ) ; } else { if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"qarma");return (char *)1;} strcpy( lDialogString , "qarma" ) ; if ( !getenv("SSH_TTY") && tfd_xpropPresent() ) { strcat(lDialogString, " --attach=$(xprop -root 32x '\t$0' _NET_ACTIVE_WINDOW | cut -f 2)"); /* contribution: Paul Rouget */ } } strcat( lDialogString , " --file-selection --directory" ) ; strcat(lDialogString, " --title=\"") ; if (aTitle && strlen(aTitle)) strcat(lDialogString, aTitle) ; strcat(lDialogString, "\"") ; if ( aDefaultPath && strlen(aDefaultPath) ) { strcat(lDialogString, " --filename=\"") ; strcat(lDialogString, aDefaultPath) ; strcat(lDialogString, "\"") ; } if (tinyfd_silent) strcat( lDialogString , " 2>/dev/null "); } else if (tfd_yadPresent()) { if (aTitle && !strcmp(aTitle, "tinyfd_query")) { strcpy(tinyfd_response, "yad"); return (char*)1; } strcpy(lDialogString, "yad --file --directory"); if (aTitle && strlen(aTitle)) { strcat(lDialogString, " --title=\""); strcat(lDialogString, aTitle); strcat(lDialogString, "\""); } if (aDefaultPath && strlen(aDefaultPath)) { strcat(lDialogString, " --filename=\""); strcat(lDialogString, aDefaultPath); strcat(lDialogString, "\""); } if (tinyfd_silent) strcat(lDialogString, " 2>/dev/null "); } else if ( !xdialogPresent() && tkinter3Present( ) ) { if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"python3-tkinter");return (char *)1;} strcpy( lDialogString , gPython3Name ) ; strcat( lDialogString , " -S -c \"import tkinter;from tkinter import filedialog;root=tkinter.Tk();root.withdraw();"); strcat( lDialogString , "res=filedialog.askdirectory("); if ( aTitle && strlen(aTitle) ) { strcat(lDialogString, "title='") ; strcat(lDialogString, aTitle) ; strcat(lDialogString, "',") ; } if ( aDefaultPath && strlen(aDefaultPath) ) { strcat(lDialogString, "initialdir='") ; strcat(lDialogString, aDefaultPath ) ; strcat(lDialogString , "'" ) ; } strcat( lDialogString, ");\nif not isinstance(res, tuple):\n\tprint(res)\n\"" ) ; } else if ( !xdialogPresent() && tkinter2Present( ) ) { if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"python2-tkinter");return (char *)1;} strcpy( lDialogString , "export PYTHONIOENCODING=utf-8;" ) ; strcat( lDialogString , gPython2Name ) ; if ( ! isTerminalRunning( ) && tfd_isDarwin( ) ) { strcat( lDialogString , " -i" ) ; /* for osx without console */ } strcat( lDialogString , " -S -c \"import Tkinter,tkFileDialog;root=Tkinter.Tk();root.withdraw();"); if ( tfd_isDarwin( ) ) { strcat( lDialogString , "import os;os.system('''/usr/bin/osascript -e 'tell app \\\"Finder\\\" to set \ frontmost of process \\\"Python\\\" to true' ''');"); } strcat( lDialogString , "print tkFileDialog.askdirectory("); if ( aTitle && strlen(aTitle) ) { strcat(lDialogString, "title='") ; strcat(lDialogString, aTitle) ; strcat(lDialogString, "',") ; } if ( aDefaultPath && strlen(aDefaultPath) ) { strcat(lDialogString, "initialdir='") ; strcat(lDialogString, aDefaultPath ) ; strcat(lDialogString , "'" ) ; } strcat( lDialogString , ")\"" ) ; } else if ( xdialogPresent() || dialogName() ) { if ( xdialogPresent( ) ) { if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"xdialog");return (char *)1;} lWasGraphicDialog = 1 ; strcpy( lDialogString , "(Xdialog " ) ; } else if ( isTerminalRunning( ) ) { if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"dialog");return (char *)0;} strcpy( lDialogString , "(dialog " ) ; } else { if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"dialog");return (char *)0;} lWasXterm = 1 ; strcpy( lDialogString , terminalName() ) ; strcat( lDialogString , "'(" ) ; strcat( lDialogString , dialogName() ) ; strcat( lDialogString , " " ) ; } if ( aTitle && strlen(aTitle) ) { strcat(lDialogString, "--title \"") ; strcat(lDialogString, aTitle) ; strcat(lDialogString, "\" ") ; } if ( !xdialogPresent() && !gdialogPresent() ) { strcat(lDialogString, "--backtitle \"") ; strcat(lDialogString, "tab: focus | /: populate | spacebar: fill text field | ok: TEXT FIELD ONLY") ; strcat(lDialogString, "\" ") ; } strcat( lDialogString , "--dselect \"" ) ; if ( aDefaultPath && strlen(aDefaultPath) ) { strcat(lDialogString, aDefaultPath) ; ensureFinalSlash(lDialogString); } else if ( ! isTerminalRunning( ) && !lWasGraphicDialog ) { strcat(lDialogString, getenv("HOME")) ; strcat(lDialogString, "/"); } else { strcat(lDialogString, "./") ; } if ( lWasGraphicDialog ) { strcat(lDialogString, "\" 0 60 ) 2>&1 ") ; } else { strcat(lDialogString, "\" 0 60 >/dev/tty) ") ; if ( lWasXterm ) { strcat( lDialogString , "2>/tmp/tinyfd.txt';cat /tmp/tinyfd.txt;rm /tmp/tinyfd.txt"); } else { strcat(lDialogString, "2>&1 ; clear >/dev/tty") ; } } } else { if (aTitle&&!strcmp(aTitle,"tinyfd_query")){return tinyfd_inputBox(aTitle,NULL,NULL);} strcpy(lBuff, "Select folder from "); strcat(lBuff, getCurDir()); lPointerInputBox = tinyfd_inputBox(NULL, NULL, NULL); /* obtain a pointer on the current content of tinyfd_inputBox */ if (lPointerInputBox) strcpy(lDialogString, lPointerInputBox); /* preserve the current content of tinyfd_inputBox */ p = tinyfd_inputBox(aTitle, lBuff, ""); if (p) strcpy(lBuff, p); else lBuff[0] = '\0'; if (lPointerInputBox) strcpy(lPointerInputBox, lDialogString); /* restore its previous content to tinyfd_inputBox */ p = lBuff; if ( !p || ! strlen( p ) || ! dirExists( p ) ) { return NULL ; } return p ; } if (tinyfd_verbose) printf( "lDialogString: %s\n" , lDialogString ) ; if ( ! ( lIn = popen( lDialogString , "r" ) ) ) { return NULL ; } while ( fgets( lBuff , sizeof( lBuff ) , lIn ) != NULL ) {} pclose( lIn ) ; if ( strlen( lBuff ) && lBuff[strlen( lBuff ) -1] == '\n' ) { lBuff[strlen( lBuff ) -1] = '\0' ; } /* printf( "lBuff: %s\n" , lBuff ) ; */ if ( ! strlen( lBuff ) || ! dirExists( lBuff ) ) { return NULL ; } getPathWithoutFinalSlash( lLastDirectory , lBuff ) ; return lBuff ; } /* aDefaultRGB is used only if aDefaultHexRGB is absent */ /* aDefaultRGB and aoResultRGB can be the same array */ /* returns NULL on cancel */ /* returns the hexcolor as a string "#FF0000" */ /* aoResultRGB also contains the result */ char * tinyfd_colorChooser( char const * aTitle , /* NULL or "" */ char const * aDefaultHexRGB , /* NULL or "#FF0000"*/ unsigned char const aDefaultRGB[3] , /* { 0 , 255 , 255 } */ unsigned char aoResultRGB[3] ) /* { 0 , 0 , 0 } */ { static char lDefaultHexRGB[16]; char lBuff[128] ; char lTmp[128] ; #if !((defined(__cplusplus ) && __cplusplus >= 201103L) || (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L) || defined(__clang__)) char * lTmp2 ; #endif char lDialogString[MAX_PATH_OR_CMD] ; unsigned char lDefaultRGB[3]; char * p; char * lPointerInputBox; FILE * lIn ; int i ; int lWasZenity3 = 0 ; int lWasOsascript = 0 ; int lWasXdialog = 0 ; lBuff[0]='\0'; if (tfd_quoteDetected(aTitle)) return tinyfd_colorChooser("INVALID TITLE WITH QUOTES", aDefaultHexRGB, aDefaultRGB, aoResultRGB); if (tfd_quoteDetected(aDefaultHexRGB)) return tinyfd_colorChooser(aTitle, "INVALID DEFAULT_HEX_RGB WITH QUOTES: use the GRAVE ACCENT \\x60 instead.", aDefaultRGB, aoResultRGB); if (aDefaultHexRGB && (strlen(aDefaultHexRGB)==7) ) { Hex2RGB(aDefaultHexRGB, lDefaultRGB); strcpy(lDefaultHexRGB, aDefaultHexRGB); } else { lDefaultRGB[0] = aDefaultRGB[0]; lDefaultRGB[1] = aDefaultRGB[1]; lDefaultRGB[2] = aDefaultRGB[2]; RGB2Hex(aDefaultRGB, lDefaultHexRGB); } if ( osascriptPresent( ) ) { if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"applescript");return (char *)1;} lWasOsascript = 1 ; strcpy( lDialogString , "osascript"); if ( ! osx9orBetter() ) { strcat( lDialogString , " -e 'tell application \"System Events\"' -e 'Activate'"); strcat( lDialogString , " -e 'try' -e 'set mycolor to choose color default color {"); } else { strcat( lDialogString , " -e 'try' -e 'tell app (path to frontmost application as Unicode text) \ to set mycolor to choose color default color {"); } sprintf(lTmp, "%d", 256 * lDefaultRGB[0] ) ; strcat(lDialogString, lTmp ) ; strcat(lDialogString, "," ) ; sprintf(lTmp, "%d", 256 * lDefaultRGB[1] ) ; strcat(lDialogString, lTmp ) ; strcat(lDialogString, "," ) ; sprintf(lTmp, "%d", 256 * lDefaultRGB[2] ) ; strcat(lDialogString, lTmp ) ; strcat(lDialogString, "}' " ) ; strcat( lDialogString , "-e 'set mystring to ((item 1 of mycolor) div 256 as integer) as string' " ); strcat( lDialogString , "-e 'repeat with i from 2 to the count of mycolor' " ); strcat( lDialogString , "-e 'set mystring to mystring & \" \" & ((item i of mycolor) div 256 as integer) as string' " ); strcat( lDialogString , "-e 'end repeat' " ); strcat( lDialogString , "-e 'mystring' "); strcat(lDialogString, "-e 'on error number -128' " ) ; strcat(lDialogString, "-e 'end try'") ; if ( ! osx9orBetter() ) strcat( lDialogString, " -e 'end tell'") ; } else if ( tfd_kdialogPresent() ) { if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"kdialog");return (char *)1;} strcpy( lDialogString , "kdialog" ) ; if ( (tfd_kdialogPresent() == 2) && tfd_xpropPresent() ) { strcat(lDialogString, " --attach=$(xprop -root 32x '\t$0' _NET_ACTIVE_WINDOW | cut -f 2)"); /* contribution: Paul Rouget */ } sprintf( lDialogString + strlen(lDialogString) , " --getcolor --default '%s'" , lDefaultHexRGB ) ; if ( aTitle && strlen(aTitle) ) { strcat(lDialogString, " --title \"") ; strcat(lDialogString, aTitle) ; strcat(lDialogString, "\"") ; } } else if ( tfd_zenity3Present() || tfd_matedialogPresent() || tfd_shellementaryPresent() || tfd_qarmaPresent() ) { lWasZenity3 = 1 ; if ( tfd_zenity3Present() ) { if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"zenity3");return (char *)1;} strcpy( lDialogString , "zenity" ); if ( (tfd_zenity3Present() >= 4) && !getenv("SSH_TTY") && tfd_xpropPresent() ) { strcat( lDialogString, " --attach=$(sleep .01;xprop -root 32x '\t$0' _NET_ACTIVE_WINDOW | cut -f 2)"); /* contribution: Paul Rouget */ } } else if ( tfd_matedialogPresent() ) { if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"matedialog");return (char *)1;} strcpy( lDialogString , "matedialog" ) ; } else if ( tfd_shellementaryPresent() ) { if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"shellementary");return (char *)1;} strcpy( lDialogString , "shellementary" ) ; } else { if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"qarma");return (char *)1;} strcpy( lDialogString , "qarma" ) ; if ( !getenv("SSH_TTY") && tfd_xpropPresent() ) { strcat(lDialogString, " --attach=$(xprop -root 32x '\t$0' _NET_ACTIVE_WINDOW | cut -f 2)"); /* contribution: Paul Rouget */ } } strcat( lDialogString , " --color-selection --show-palette" ) ; sprintf( lDialogString + strlen(lDialogString), " --color=%s" , lDefaultHexRGB ) ; strcat(lDialogString, " --title=\"") ; if (aTitle && strlen(aTitle)) strcat(lDialogString, aTitle) ; strcat(lDialogString, "\"") ; if (tinyfd_silent) strcat( lDialogString , " 2>/dev/null "); } else if (tfd_yadPresent()) { if (aTitle && !strcmp(aTitle, "tinyfd_query")) { strcpy(tinyfd_response, "yad"); return (char*)1; } strcpy(lDialogString, "yad --color"); sprintf(lDialogString + strlen(lDialogString), " --init-color=%s", lDefaultHexRGB); if (aTitle && strlen(aTitle)) { strcat(lDialogString, " --title=\""); strcat(lDialogString, aTitle); strcat(lDialogString, "\""); } if (tinyfd_silent) strcat(lDialogString, " 2>/dev/null "); } else if ( xdialogPresent() ) { if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"xdialog");return (char *)1;} lWasXdialog = 1 ; strcpy( lDialogString , "Xdialog --colorsel \"" ) ; if ( aTitle && strlen(aTitle) ) { strcat(lDialogString, aTitle) ; } strcat(lDialogString, "\" 0 60 ") ; #if (defined(__cplusplus ) && __cplusplus >= 201103L) || (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L) || defined(__clang__) sprintf(lTmp,"%hhu %hhu %hhu",lDefaultRGB[0],lDefaultRGB[1],lDefaultRGB[2]); #else sprintf(lTmp,"%hu %hu %hu",lDefaultRGB[0],lDefaultRGB[1],lDefaultRGB[2]); #endif strcat(lDialogString, lTmp) ; strcat(lDialogString, " 2>&1"); } else if ( tkinter3Present( ) ) { if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"python3-tkinter");return (char *)1;} strcpy( lDialogString , gPython3Name ) ; strcat( lDialogString , " -S -c \"import tkinter;from tkinter import colorchooser;root=tkinter.Tk();root.withdraw();"); strcat( lDialogString , "res=colorchooser.askcolor(color='" ) ; strcat(lDialogString, lDefaultHexRGB ) ; strcat(lDialogString, "'") ; if ( aTitle && strlen(aTitle) ) { strcat(lDialogString, ",title='") ; strcat(lDialogString, aTitle) ; strcat(lDialogString, "'") ; } strcat( lDialogString , ");\ \nif res[1] is not None:\n\tprint(res[1])\"" ) ; } else if ( tkinter2Present( ) ) { if (aTitle&&!strcmp(aTitle,"tinyfd_query")){strcpy(tinyfd_response,"python2-tkinter");return (char *)1;} strcpy( lDialogString , "export PYTHONIOENCODING=utf-8;" ) ; strcat( lDialogString , gPython2Name ) ; if ( ! isTerminalRunning( ) && tfd_isDarwin( ) ) { strcat( lDialogString , " -i" ) ; /* for osx without console */ } strcat( lDialogString , " -S -c \"import Tkinter,tkColorChooser;root=Tkinter.Tk();root.withdraw();"); if ( tfd_isDarwin( ) ) { strcat( lDialogString , "import os;os.system('''osascript -e 'tell app \\\"Finder\\\" to set \ frontmost of process \\\"Python\\\" to true' ''');"); } strcat( lDialogString , "res=tkColorChooser.askcolor(color='" ) ; strcat(lDialogString, lDefaultHexRGB ) ; strcat(lDialogString, "'") ; if ( aTitle && strlen(aTitle) ) { strcat(lDialogString, ",title='") ; strcat(lDialogString, aTitle) ; strcat(lDialogString, "'") ; } strcat( lDialogString , ");\ \nif res[1] is not None:\n\tprint res[1]\"" ) ; } else { if (aTitle&&!strcmp(aTitle,"tinyfd_query")){return tinyfd_inputBox(aTitle,NULL,NULL);} lPointerInputBox = tinyfd_inputBox(NULL, NULL, NULL); /* obtain a pointer on the current content of tinyfd_inputBox */ if (lPointerInputBox) strcpy(lDialogString, lPointerInputBox); /* preserve the current content of tinyfd_inputBox */ p = tinyfd_inputBox(aTitle, "Enter hex rgb color (i.e. #f5ca20)", lDefaultHexRGB); if ( !p || (strlen(p) != 7) || (p[0] != '#') ) { return NULL ; } for ( i = 1 ; i < 7 ; i ++ ) { if ( ! isxdigit( (int) p[i] ) ) { return NULL ; } } Hex2RGB(p,aoResultRGB); strcpy(lDefaultHexRGB, p); if (lPointerInputBox) strcpy(lPointerInputBox, lDialogString); /* restore its previous content to tinyfd_inputBox */ return lDefaultHexRGB; } if (tinyfd_verbose) printf( "lDialogString: %s\n" , lDialogString ) ; if ( ! ( lIn = popen( lDialogString , "r" ) ) ) { return NULL ; } while ( fgets( lBuff , sizeof( lBuff ) , lIn ) != NULL ) { } pclose( lIn ) ; if ( ! strlen( lBuff ) ) { return NULL ; } /* printf( "len Buff: %lu\n" , strlen(lBuff) ) ; */ /* printf( "lBuff0: %s\n" , lBuff ) ; */ if ( lBuff[strlen( lBuff ) -1] == '\n' ) { lBuff[strlen( lBuff ) -1] = '\0' ; } if ( lWasZenity3 ) { if ( lBuff[0] == '#' ) { if ( strlen(lBuff)>7 ) { lBuff[3]=lBuff[5]; lBuff[4]=lBuff[6]; lBuff[5]=lBuff[9]; lBuff[6]=lBuff[10]; lBuff[7]='\0'; } Hex2RGB(lBuff,aoResultRGB); } else if ( lBuff[3] == '(' ) { #if (defined(__cplusplus ) && __cplusplus >= 201103L) || (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L) || defined(__clang__) sscanf(lBuff,"rgb(%hhu,%hhu,%hhu", & aoResultRGB[0], & aoResultRGB[1],& aoResultRGB[2]); #else aoResultRGB[0] = (unsigned char) strtol(lBuff+4, & lTmp2, 10 ); aoResultRGB[1] = (unsigned char) strtol(lTmp2+1, & lTmp2, 10 ); aoResultRGB[2] = (unsigned char) strtol(lTmp2+1, NULL, 10 ); #endif RGB2Hex(aoResultRGB,lBuff); } else if ( lBuff[4] == '(' ) { #if (defined(__cplusplus ) && __cplusplus >= 201103L) || (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L) || defined(__clang__) sscanf(lBuff,"rgba(%hhu,%hhu,%hhu", & aoResultRGB[0], & aoResultRGB[1],& aoResultRGB[2]); #else aoResultRGB[0] = (unsigned char) strtol(lBuff+5, & lTmp2, 10 ); aoResultRGB[1] = (unsigned char) strtol(lTmp2+1, & lTmp2, 10 ); aoResultRGB[2] = (unsigned char) strtol(lTmp2+1, NULL, 10 ); #endif RGB2Hex(aoResultRGB,lBuff); } } else if ( lWasOsascript || lWasXdialog ) { /* printf( "lBuff: %s\n" , lBuff ) ; */ #if (defined(__cplusplus ) && __cplusplus >= 201103L) || (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L) || defined(__clang__) sscanf(lBuff,"%hhu %hhu %hhu", & aoResultRGB[0], & aoResultRGB[1],& aoResultRGB[2]); #else aoResultRGB[0] = (unsigned char) strtol(lBuff, & lTmp2, 10 ); aoResultRGB[1] = (unsigned char) strtol(lTmp2+1, & lTmp2, 10 ); aoResultRGB[2] = (unsigned char) strtol(lTmp2+1, NULL, 10 ); #endif RGB2Hex(aoResultRGB,lBuff); } else { Hex2RGB(lBuff,aoResultRGB); } /* printf("%d %d %d\n", aoResultRGB[0],aoResultRGB[1],aoResultRGB[2]); */ /* printf( "lBuff: %s\n" , lBuff ) ; */ strcpy(lDefaultHexRGB,lBuff); return lDefaultHexRGB ; } #endif /* _WIN32 */ /* Modified prototypes for R */ void tfd_messageBox( char const * aTitle , char const * aMessage , char const * aDialogType , char const * aIconType , int * aiDefaultButton ) { * aiDefaultButton = tinyfd_messageBox( aTitle , aMessage , aDialogType , aIconType , * aiDefaultButton ) ; } void tfd_inputBox( char const * aTitle , char const * aMessage , char * * aiDefaultInput ) { char * lReturnedInput ; if ( ! strcmp( * aiDefaultInput , "NULL") ) lReturnedInput = tinyfd_inputBox( aTitle , aMessage , NULL ) ; else lReturnedInput = tinyfd_inputBox( aTitle , aMessage , * aiDefaultInput ) ; if ( lReturnedInput ) strcpy ( * aiDefaultInput , lReturnedInput ) ; else strcpy ( * aiDefaultInput , "NULL" ) ; } void tfd_saveFileDialog( char const * aTitle , char * * aiDefaultPathAndFile , int const * aNumOfFilterPatterns , char const * const * aFilterPatterns , char const * aSingleFilterDescription ) { char * lSavefile ; /* printf( "aFilterPatterns %s\n" , aFilterPatterns [0]); */ lSavefile = tinyfd_saveFileDialog( aTitle , * aiDefaultPathAndFile , * aNumOfFilterPatterns , aFilterPatterns, aSingleFilterDescription ) ; if ( lSavefile ) strcpy ( * aiDefaultPathAndFile , lSavefile ) ; else strcpy ( * aiDefaultPathAndFile , "NULL" ) ; } void tfd_openFileDialog( char const * aTitle , char * * aiDefaultPathAndFile , int const * aNumOfFilterPatterns , char const * const * aFilterPatterns , char const * aSingleFilterDescription , int const * aAllowMultipleSelects ) { char * lOpenfile ; /* printf( "aFilterPatterns %s\n" , aFilterPatterns [0]); */ lOpenfile = tinyfd_openFileDialog( aTitle , * aiDefaultPathAndFile , * aNumOfFilterPatterns , aFilterPatterns , aSingleFilterDescription , * aAllowMultipleSelects ) ; if ( lOpenfile ) strcpy ( * aiDefaultPathAndFile , lOpenfile ) ; else strcpy ( * aiDefaultPathAndFile , "NULL" ) ; } void tfd_selectFolderDialog( char const * aTitle , char * * aiDefaultPath ) { char * lSelectedfolder ; lSelectedfolder = tinyfd_selectFolderDialog( aTitle, * aiDefaultPath ) ; if ( lSelectedfolder ) strcpy ( * aiDefaultPath , lSelectedfolder ) ; else strcpy ( * aiDefaultPath , "NULL" ) ; } void tfd_colorChooser( char const * aTitle , char * * aiDefaultHexRGB ) { unsigned char const aDefaultRGB [ 3 ] = {128,128,128} ; unsigned char aoResultRGB [ 3 ] = {128,128,128} ; char * lChosenColor ; lChosenColor = tinyfd_colorChooser( aTitle, * aiDefaultHexRGB, aDefaultRGB, aoResultRGB ) ; if ( lChosenColor ) strcpy ( * aiDefaultHexRGB , lChosenColor ) ; else strcpy ( * aiDefaultHexRGB , "NULL" ) ; } /* end of Modified prototypes for R */ /* int main( int argc , char * argv[] ) { char const * lTmp; char const * lTheSaveFileName; char const * lTheOpenFileName; char const * lTheSelectFolderName; char const * lTheHexColor; char const * lWillBeGraphicMode; unsigned char lRgbColor[3]; FILE * lIn; char lBuffer[1024]; char lString[1024]; char const * lFilterPatterns[2] = { "*.txt", "*.text" }; tinyfd_verbose = argc - 1; tinyfd_silent = 1; lWillBeGraphicMode = tinyfd_inputBox("tinyfd_query", NULL, NULL); strcpy(lBuffer, "v"); strcat(lBuffer, tinyfd_version); if (lWillBeGraphicMode) { strcat(lBuffer, "\ngraphic mode: "); } else { strcat(lBuffer, "\nconsole mode: "); } strcat(lBuffer, tinyfd_response); strcat(lBuffer, "\n"); strcat(lBuffer, tinyfd_needs+78); strcpy(lString, "tinyfiledialogs"); tinyfd_messageBox(lString, lBuffer, "ok", "info", 0); tinyfd_notifyPopup("the title", "the message\n\tfrom outer-space", "info"); if (lWillBeGraphicMode && !tinyfd_forceConsole) { tinyfd_forceConsole = ! tinyfd_messageBox("Hello World", "graphic dialogs [yes] / console mode [no]?", "yesno", "question", 1); } lTmp = tinyfd_inputBox( "a password box", "your password will be revealed", NULL); if (!lTmp) return 1; strcpy(lString, lTmp); lTheSaveFileName = tinyfd_saveFileDialog( "let us save this password", "passwordFile.txt", 2, lFilterPatterns, NULL); if (!lTheSaveFileName) { tinyfd_messageBox( "Error", "Save file name is NULL", "ok", "error", 1); return 1; } lIn = fopen(lTheSaveFileName, "w"); if (!lIn) { tinyfd_messageBox( "Error", "Can not open this file in write mode", "ok", "error", 1); return 1; } fputs(lString, lIn); fclose(lIn); lTheOpenFileName = tinyfd_openFileDialog( "let us read the password back", "", 2, lFilterPatterns, NULL, 0); if (!lTheOpenFileName) { tinyfd_messageBox( "Error", "Open file name is NULL", "ok", "error", 1); return 1; } lIn = fopen(lTheOpenFileName, "r"); if (!lIn) { tinyfd_messageBox( "Error", "Can not open this file in read mode", "ok", "error", 1); return(1); } lBuffer[0] = '\0'; fgets(lBuffer, sizeof(lBuffer), lIn); fclose(lIn); tinyfd_messageBox("your password is", lBuffer, "ok", "info", 1); lTheSelectFolderName = tinyfd_selectFolderDialog( "let us just select a directory", NULL); if (!lTheSelectFolderName) { tinyfd_messageBox( "Error", "Select folder name is NULL", "ok", "error", 1); return 1; } tinyfd_messageBox("The selected folder is", lTheSelectFolderName, "ok", "info", 1); lTheHexColor = tinyfd_colorChooser( "choose a nice color", "#FF0077", lRgbColor, lRgbColor); if (!lTheHexColor) { tinyfd_messageBox( "Error", "hexcolor is NULL", "ok", "error", 1); return 1; } tinyfd_messageBox("The selected hexcolor is", lTheHexColor, "ok", "info", 1); tinyfd_beep(); return 0; } */ #ifdef _MSC_VER #pragma warning(default:4996) #pragma warning(default:4100) #pragma warning(default:4706) #endif ================================================ FILE: external/tinyfiledialogs.h ================================================ /* SPDX-License-Identifier: Zlib Copyright (c) 2014 - 2024 Guillaume Vareille http://ysengrin.com ____________________________________________________________________ | | | 100% compatible C C++ -> You can rename tinfiledialogs.c as .cpp | |____________________________________________________________________| ********* TINY FILE DIALOGS OFFICIAL WEBSITE IS ON SOURCEFORGE ********* _________ / \ tinyfiledialogs.h v3.18.2 [Jun 8, 2024] |tiny file| Unique header file created [November 9, 2014] | dialogs | \____ ___/ http://tinyfiledialogs.sourceforge.net \| git clone http://git.code.sf.net/p/tinyfiledialogs/code tinyfd ____________________________________________ | | | email: tinyfiledialogs at ysengrin.com | |____________________________________________| ________________________________________________________________________________ | ____________________________________________________________________________ | | | | | | | - in tinyfiledialogs, char is UTF-8 by default (since v3.6) | | | | | | | | on windows: | | | | - for UTF-16, use the wchar_t functions at the bottom of the header file | | | | | | | | - _wfopen() requires wchar_t | | | | - fopen() uses char but expects ASCII or MBCS (not UTF-8) | | | | - if you want char to be MBCS: set tinyfd_winUtf8 to 0 | | | | | | | | - alternatively, tinyfiledialogs provides | | | | functions to convert between UTF-8, UTF-16 and MBCS | | | |____________________________________________________________________________| | |________________________________________________________________________________| If you like tinyfiledialogs, please upvote my stackoverflow answer https://stackoverflow.com/a/47651444 - License - This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. __________________________________________ | ______________________________________ | | | | | | | DO NOT USE USER INPUT IN THE DIALOGS | | | |______________________________________| | |__________________________________________| */ #ifndef TINYFILEDIALOGS_H #define TINYFILEDIALOGS_H #ifdef __cplusplus extern "C" { #endif /******************************************************************************************************/ /**************************************** UTF-8 on Windows ********************************************/ /******************************************************************************************************/ #ifdef _WIN32 /* On windows, if you want to use UTF-8 ( instead of the UTF-16/wchar_t functions at the end of this file ) Make sure your code is really prepared for UTF-8 (on windows, functions like fopen() expect MBCS and not UTF-8) */ extern int tinyfd_winUtf8; /* on windows char strings can be 1:UTF-8(default) or 0:MBCS */ /* for MBCS change this to 0, in tinyfiledialogs.c or in your code */ /* Here are some functions to help you convert between UTF-16 UTF-8 MBSC */ char * tinyfd_utf8toMbcs(char const * aUtf8string); char * tinyfd_utf16toMbcs(wchar_t const * aUtf16string); wchar_t * tinyfd_mbcsTo16(char const * aMbcsString); char * tinyfd_mbcsTo8(char const * aMbcsString); wchar_t * tinyfd_utf8to16(char const * aUtf8string); char * tinyfd_utf16to8(wchar_t const * aUtf16string); #endif /******************************************************************************************************/ /******************************************************************************************************/ /******************************************************************************************************/ /************* 3 funtions for C# (you don't need this in C or C++) : */ char const * tinyfd_getGlobalChar(char const * aCharVariableName); /* returns NULL on error */ int tinyfd_getGlobalInt(char const * aIntVariableName); /* returns -1 on error */ int tinyfd_setGlobalInt(char const * aIntVariableName, int aValue); /* returns -1 on error */ /* aCharVariableName: "tinyfd_version" "tinyfd_needs" "tinyfd_response" aIntVariableName : "tinyfd_verbose" "tinyfd_silent" "tinyfd_allowCursesDialogs" "tinyfd_forceConsole" "tinyfd_assumeGraphicDisplay" "tinyfd_winUtf8" **************/ extern char tinyfd_version[8]; /* contains tinyfd current version number */ extern char tinyfd_needs[]; /* info about requirements */ extern int tinyfd_verbose; /* 0 (default) or 1 : on unix, prints the command line calls */ extern int tinyfd_silent; /* 1 (default) or 0 : on unix, hide errors and warnings from called dialogs */ /** Curses dialogs are difficult to use and counter-intuitive. On windows they are only ascii and still uses the unix backslash ! **/ extern int tinyfd_allowCursesDialogs; /* 0 (default) or 1 */ extern int tinyfd_forceConsole; /* 0 (default) or 1 */ /* for unix & windows: 0 (graphic mode) or 1 (console mode). 0: try to use a graphic solution, if it fails then it uses console mode. 1: forces all dialogs into console mode even when an X server is present. if enabled, it can use the package Dialog or dialog.exe. on windows it only make sense for console applications */ extern int tinyfd_assumeGraphicDisplay; /* 0 (default) or 1 */ /* some systems don't set the environment variable DISPLAY even when a graphic display is present. set this to 1 to tell tinyfiledialogs to assume the existence of a graphic display */ extern char tinyfd_response[1024]; /* if you pass "tinyfd_query" as aTitle, the functions will not display the dialogs but will return 0 for console mode, 1 for graphic mode. tinyfd_response is then filled with the retain solution. possible values for tinyfd_response are (all lowercase) for graphic mode: windows_wchar windows applescript kdialog zenity zenity3 yad matedialog shellementary qarma python2-tkinter python3-tkinter python-dbus perl-dbus gxmessage gmessage xmessage xdialog gdialog dunst for console mode: dialog whiptail basicinput no_solution */ void tinyfd_beep(void); int tinyfd_notifyPopup( char const * aTitle, /* NULL or "" */ char const * aMessage, /* NULL or "" may contain \n \t */ char const * aIconType); /* "info" "warning" "error" */ /* return has only meaning for tinyfd_query */ int tinyfd_messageBox( char const * aTitle , /* NULL or "" */ char const * aMessage , /* NULL or "" may contain \n \t */ char const * aDialogType , /* "ok" "okcancel" "yesno" "yesnocancel" */ char const * aIconType , /* "info" "warning" "error" "question" */ int aDefaultButton ) ; /* 0 for cancel/no , 1 for ok/yes , 2 for no in yesnocancel */ char * tinyfd_inputBox( char const * aTitle , /* NULL or "" */ char const * aMessage , /* NULL or "" (\n and \t have no effect) */ char const * aDefaultInput ) ; /* NULL = passwordBox, "" = inputbox */ /* returns NULL on cancel */ char * tinyfd_saveFileDialog( char const * aTitle , /* NULL or "" */ char const * aDefaultPathAndOrFile , /* NULL or "" , ends with / to set only a directory */ int aNumOfFilterPatterns , /* 0 (1 in the following example) */ char const * const * aFilterPatterns , /* NULL or char const * lFilterPatterns[1]={"*.txt"} */ char const * aSingleFilterDescription ) ; /* NULL or "text files" */ /* returns NULL on cancel */ char * tinyfd_openFileDialog( char const * aTitle, /* NULL or "" */ char const * aDefaultPathAndOrFile, /* NULL or "" , ends with / to set only a directory */ int aNumOfFilterPatterns , /* 0 (2 in the following example) */ char const * const * aFilterPatterns, /* NULL or char const * lFilterPatterns[2]={"*.png","*.jpg"}; */ char const * aSingleFilterDescription, /* NULL or "image files" */ int aAllowMultipleSelects ) ; /* 0 or 1 */ /* in case of multiple files, the separator is | */ /* returns NULL on cancel */ char * tinyfd_selectFolderDialog( char const * aTitle, /* NULL or "" */ char const * aDefaultPath); /* NULL or "" */ /* returns NULL on cancel */ char * tinyfd_colorChooser( char const * aTitle, /* NULL or "" */ char const * aDefaultHexRGB, /* NULL or "" or "#FF0000" */ unsigned char const aDefaultRGB[3] , /* unsigned char lDefaultRGB[3] = { 0 , 128 , 255 }; */ unsigned char aoResultRGB[3] ) ; /* unsigned char lResultRGB[3]; */ /* aDefaultRGB is used only if aDefaultHexRGB is absent */ /* aDefaultRGB and aoResultRGB can be the same array */ /* returns NULL on cancel */ /* returns the hexcolor as a string "#FF0000" */ /* aoResultRGB also contains the result */ /************ WINDOWS ONLY SECTION ************************/ #ifdef _WIN32 /* windows only - utf-16 version */ int tinyfd_notifyPopupW( wchar_t const * aTitle, /* NULL or L"" */ wchar_t const * aMessage, /* NULL or L"" may contain \n \t */ wchar_t const * aIconType); /* L"info" L"warning" L"error" */ /* windows only - utf-16 version */ int tinyfd_messageBoxW( wchar_t const * aTitle, /* NULL or L"" */ wchar_t const * aMessage, /* NULL or L"" may contain \n \t */ wchar_t const * aDialogType, /* L"ok" L"okcancel" L"yesno" */ wchar_t const * aIconType, /* L"info" L"warning" L"error" L"question" */ int aDefaultButton ); /* 0 for cancel/no , 1 for ok/yes */ /* returns 0 for cancel/no , 1 for ok/yes */ /* windows only - utf-16 version */ wchar_t * tinyfd_inputBoxW( wchar_t const * aTitle, /* NULL or L"" */ wchar_t const * aMessage, /* NULL or L"" (\n nor \t not respected) */ wchar_t const * aDefaultInput); /* NULL passwordBox, L"" inputbox */ /* windows only - utf-16 version */ wchar_t * tinyfd_saveFileDialogW( wchar_t const * aTitle, /* NULL or L"" */ wchar_t const * aDefaultPathAndOrFile, /* NULL or L"" , ends with / to set only a directory */ int aNumOfFilterPatterns, /* 0 (1 in the following example) */ wchar_t const * const * aFilterPatterns, /* NULL or wchar_t const * lFilterPatterns[1]={L"*.txt"} */ wchar_t const * aSingleFilterDescription); /* NULL or L"text files" */ /* returns NULL on cancel */ /* windows only - utf-16 version */ wchar_t * tinyfd_openFileDialogW( wchar_t const * aTitle, /* NULL or L"" */ wchar_t const * aDefaultPathAndOrFile, /* NULL or L"" , ends with / to set only a directory */ int aNumOfFilterPatterns , /* 0 (2 in the following example) */ wchar_t const * const * aFilterPatterns, /* NULL or wchar_t const * lFilterPatterns[2]={L"*.png","*.jpg"} */ wchar_t const * aSingleFilterDescription, /* NULL or L"image files" */ int aAllowMultipleSelects ) ; /* 0 or 1 */ /* in case of multiple files, the separator is | */ /* returns NULL on cancel */ /* windows only - utf-16 version */ wchar_t * tinyfd_selectFolderDialogW( wchar_t const * aTitle, /* NULL or L"" */ wchar_t const * aDefaultPath); /* NULL or L"" */ /* returns NULL on cancel */ /* windows only - utf-16 version */ wchar_t * tinyfd_colorChooserW( wchar_t const * aTitle, /* NULL or L"" */ wchar_t const * aDefaultHexRGB, /* NULL or L"#FF0000" */ unsigned char const aDefaultRGB[3], /* unsigned char lDefaultRGB[3] = { 0 , 128 , 255 }; */ unsigned char aoResultRGB[3]); /* unsigned char lResultRGB[3]; */ /* returns the hexcolor as a string L"#FF0000" */ /* aoResultRGB also contains the result */ /* aDefaultRGB is used only if aDefaultHexRGB is NULL */ /* aDefaultRGB and aoResultRGB can be the same array */ /* returns NULL on cancel */ #endif /*_WIN32 */ #ifdef __cplusplus } /*extern "C"*/ #endif #endif /* TINYFILEDIALOGS_H */ /* ________________________________________________________________________________ | ____________________________________________________________________________ | | | | | | | on windows: | | | | - for UTF-16, use the wchar_t functions at the bottom of the header file | | | | - _wfopen() requires wchar_t | | | | | | | | - in tinyfiledialogs, char is UTF-8 by default (since v3.6) | | | | - but fopen() expects MBCS (not UTF-8) | | | | - if you want char to be MBCS: set tinyfd_winUtf8 to 0 | | | | | | | | - alternatively, tinyfiledialogs provides | | | | functions to convert between UTF-8, UTF-16 and MBCS | | | |____________________________________________________________________________| | |________________________________________________________________________________| - This is not for ios nor android (it works in termux though). - The files can be renamed with extension ".cpp" as the code is 100% compatible C C++ (just comment out << extern "C" >> in the header file) - Windows is fully supported from XP to 10 (maybe even older versions) - C# & LUA via dll, see files in the folder EXTRAS - OSX supported from 10.4 to latest (maybe even older versions) - Do not use " and ' as the dialogs will be displayed with a warning instead of the title, message, etc... - There's one file filter only, it may contain several patterns. - If no filter description is provided, the list of patterns will become the description. - On windows link against Comdlg32.lib and Ole32.lib (on windows the no linking claim is a lie) - On unix: it tries command line calls, so no such need (NO LINKING). - On unix you need one of the following: applescript, kdialog, zenity, matedialog, shellementary, qarma, yad, python (2 or 3)/tkinter/python-dbus (optional), Xdialog or curses dialogs (opens terminal if running without console). - One of those is already included on most (if not all) desktops. - In the absence of those it will use gdialog, gxmessage or whiptail with a textinputbox. If nothing is found, it switches to basic console input, it opens a console if needed (requires xterm + bash). - for curses dialogs you must set tinyfd_allowCursesDialogs=1 - You can query the type of dialog that will be used (pass "tinyfd_query" as aTitle) - String memory is preallocated statically for all the returned values. - File and path names are tested before return, they should be valid. - tinyfd_forceConsole=1; at run time, forces dialogs into console mode. - On windows, console mode only make sense for console applications. - On windows, console mode is not implemented for wchar_T UTF-16. - Mutiple selects are not possible in console mode. - The package dialog must be installed to run in curses dialogs in console mode. It is already installed on most unix systems. - On osx, the package dialog can be installed via http://macappstore.org/dialog or http://macports.org - On windows, for curses dialogs console mode, dialog.exe should be copied somewhere on your executable path. It can be found at the bottom of the following page: http://andrear.altervista.org/home/cdialog.php */ ================================================ FILE: scrap.desktop ================================================ [Desktop Entry] Name=Scrap Exec=AppRun Icon=scrap Type=Application Categories=Development; ================================================ FILE: scrap.rc ================================================ scrap_icon ICON "extras/icon.ico" ================================================ FILE: src/ast.c ================================================ // Scrap is a project that allows anyone to build software using simple, block based interface. // // Copyright (C) 2024-2026 Grisshink // // This software is provided 'as-is', without any express or implied // warranty. In no event will the authors be held liable for any damages // arising from the use of this software. // // Permission is granted to anyone to use this software for any purpose, // including commercial applications, and to alter it and redistribute it // freely, subject to the following restrictions: // // 1. The origin of this software must not be misrepresented; you must not // claim that you wrote the original software. If you use this software // in a product, an acknowledgment in the product documentation would be // appreciated but is not required. // 2. Altered source versions must be plainly marked as such, and must not be // misrepresented as being the original software. // 3. This notice may not be removed or altered from any source distribution. #include "ast.h" #include "vec.h" #include #include #include const char* type_to_str(DataType type) { switch (type) { case DATA_TYPE_NOTHING: return "nothing"; case DATA_TYPE_INTEGER: return "integer"; case DATA_TYPE_FLOAT: return "float"; case DATA_TYPE_STRING: return "str"; case DATA_TYPE_LITERAL: return "literal"; case DATA_TYPE_BOOL: return "bool"; case DATA_TYPE_LIST: return "list"; case DATA_TYPE_COLOR: return "color"; case DATA_TYPE_ANY: return "any"; case DATA_TYPE_BLOCKDEF: return "blockdef"; case DATA_TYPE_UNKNOWN: return "unknown"; } assert(false && "Unhandled type_to_str"); } Block block_new(Blockdef* blockdef) { Block block; block.blockdef = blockdef; block.arguments = vector_create(); block.parent = NULL; blockdef->ref_count++; for (size_t i = 0; i < vector_size(blockdef->inputs); i++) { Argument* arg; switch (blockdef->inputs[i].type) { case INPUT_ARGUMENT: arg = vector_add_dst(&block.arguments); arg->input_id = i; switch (blockdef->inputs[i].data.arg.constr) { case BLOCKCONSTR_UNLIMITED: arg->type = ARGUMENT_TEXT; break; case BLOCKCONSTR_STRING: arg->type = ARGUMENT_CONST_STRING; break; default: assert(false && "Unimplemented argument constraint"); break; } arg->data.text = vector_create(); for (char* pos = blockdef->inputs[i].data.arg.text; *pos; pos++) { vector_add(&arg->data.text, *pos); } vector_add(&arg->data.text, 0); break; case INPUT_DROPDOWN: arg = vector_add_dst(&block.arguments); arg->input_id = i; arg->type = ARGUMENT_CONST_STRING; size_t list_len = 0; char** list = blockdef->inputs[i].data.drop.list(&block, &list_len); if (!list || list_len == 0) break; arg->data.text = vector_create(); for (char* pos = list[0]; *pos; pos++) { vector_add(&arg->data.text, *pos); } vector_add(&arg->data.text, 0); break; case INPUT_BLOCKDEF_EDITOR: arg = vector_add_dst(&block.arguments); arg->input_id = i; arg->type = ARGUMENT_BLOCKDEF; arg->data.blockdef = blockdef_new("custom", BLOCKTYPE_NORMAL, blockdef->color, NULL); arg->data.blockdef->ref_count++; blockdef_add_text(arg->data.blockdef, gettext("My block")); break; case INPUT_COLOR: arg = vector_add_dst(&block.arguments); arg->input_id = i; arg->type = ARGUMENT_COLOR; arg->data.color = blockdef->inputs[i].data.color; break; case INPUT_TEXT_DISPLAY: case INPUT_IMAGE_DISPLAY: break; default: assert(false && "Unhandled add input argument"); break; } } return block; } Block block_copy(Block* block, Block* parent) { if (!block->arguments) return *block; Block new; new.blockdef = block->blockdef; new.parent = parent; new.arguments = vector_create(); new.blockdef->ref_count++; for (size_t i = 0; i < vector_size(block->arguments); i++) { Argument* arg = vector_add_dst((Argument**)&new.arguments); arg->type = block->arguments[i].type; arg->input_id = block->arguments[i].input_id; static_assert(ARGUMENT_LAST == 5, "Exhaustive argument type in block_copy"); switch (block->arguments[i].type) { case ARGUMENT_CONST_STRING: case ARGUMENT_TEXT: arg->data.text = vector_copy(block->arguments[i].data.text); break; case ARGUMENT_BLOCK: arg->data.block = block_copy(&block->arguments[i].data.block, &new); break; case ARGUMENT_BLOCKDEF: arg->data.blockdef = blockdef_copy(block->arguments[i].data.blockdef); arg->data.blockdef->ref_count++; break; case ARGUMENT_COLOR: arg->data.color = block->arguments[i].data.color; break; default: assert(false && "Unimplemented argument copy"); break; } } for (size_t i = 0; i < vector_size(new.arguments); i++) { if (new.arguments[i].type != ARGUMENT_BLOCK) continue; block_update_parent_links(&new.arguments[i].data.block); } return new; } void block_free(Block* block) { blockdef_free(block->blockdef); if (block->arguments) { for (size_t i = 0; i < vector_size(block->arguments); i++) { static_assert(ARGUMENT_LAST == 5, "Exhaustive argument type in block_free"); switch (block->arguments[i].type) { case ARGUMENT_CONST_STRING: case ARGUMENT_TEXT: vector_free(block->arguments[i].data.text); break; case ARGUMENT_BLOCK: block_free(&block->arguments[i].data.block); break; case ARGUMENT_BLOCKDEF: blockdef_free(block->arguments[i].data.blockdef); break; case ARGUMENT_COLOR: break; default: assert(false && "Unimplemented argument free"); break; } } vector_free((Argument*)block->arguments); } } void block_update_all_links(Block* block) { for (size_t i = 0; i < vector_size(block->arguments); i++) { if (block->arguments[i].type != ARGUMENT_BLOCK) continue; block->arguments[i].data.block.parent = block; block_update_all_links(&block->arguments[i].data.block); } } void block_update_parent_links(Block* block) { for (size_t i = 0; i < vector_size(block->arguments); i++) { if (block->arguments[i].type != ARGUMENT_BLOCK) continue; block->arguments[i].data.block.parent = block; } } BlockChain blockchain_new(void) { BlockChain chain; chain.x = 0; chain.y = 0; chain.blocks = vector_create(); chain.width = 0; chain.height = 0; return chain; } BlockChain blockchain_copy_single(BlockChain* chain, size_t pos) { assert(pos < vector_size(chain->blocks) || pos == 0); BlockChain new; new.x = chain->x; new.y = chain->y; new.blocks = vector_create(); BlockdefType block_type = chain->blocks[pos].blockdef->type; if (block_type == BLOCKTYPE_END) return new; if (block_type != BLOCKTYPE_CONTROL) { vector_add(&new.blocks, block_copy(&chain->blocks[pos], NULL)); blockchain_update_parent_links(&new); return new; } int layer = 0; for (size_t i = pos; i < vector_size(chain->blocks) && layer >= 0; i++) { block_type = chain->blocks[i].blockdef->type; vector_add(&new.blocks, block_copy(&chain->blocks[i], NULL)); if (block_type == BLOCKTYPE_CONTROL && i != pos) { layer++; } else if (block_type == BLOCKTYPE_END) { layer--; } } blockchain_update_parent_links(&new); return new; } BlockChain blockchain_copy(BlockChain* chain, size_t pos) { assert(pos < vector_size(chain->blocks) || pos == 0); BlockChain new; new.x = chain->x; new.y = chain->y; new.blocks = vector_create(); int pos_layer = 0; for (size_t i = 0; i < pos; i++) { BlockdefType block_type = chain->blocks[i].blockdef->type; if (block_type == BLOCKTYPE_CONTROL) { pos_layer++; } else if (block_type == BLOCKTYPE_END) { pos_layer--; if (pos_layer < 0) pos_layer = 0; } } int current_layer = pos_layer; vector_reserve(&new.blocks, vector_size(chain->blocks) - pos); for (vec_size_t i = pos; i < vector_size(chain->blocks); i++) { BlockdefType block_type = chain->blocks[i].blockdef->type; if ((block_type == BLOCKTYPE_END || (block_type == BLOCKTYPE_CONTROLEND && i != pos)) && pos_layer == current_layer && current_layer != 0) break; vector_add(&new.blocks, block_copy(&chain->blocks[i], NULL)); block_update_parent_links(&new.blocks[vector_size(new.blocks) - 1]); if (block_type == BLOCKTYPE_CONTROL) { current_layer++; } else if (block_type == BLOCKTYPE_END) { current_layer--; } } return new; } void blockchain_update_parent_links(BlockChain* chain) { for (size_t i = 0; i < vector_size(chain->blocks); i++) { block_update_parent_links(&chain->blocks[i]); } } void blockchain_add_block(BlockChain* chain, Block block) { vector_add(&chain->blocks, block); blockchain_update_parent_links(chain); } void blockchain_clear_blocks(BlockChain* chain) { for (size_t i = 0; i < vector_size(chain->blocks); i++) { block_free(&chain->blocks[i]); } vector_clear(chain->blocks); } void blockchain_insert(BlockChain* dst, BlockChain* src, size_t pos) { assert(pos < vector_size(dst->blocks)); vector_reserve(&dst->blocks, vector_size(dst->blocks) + vector_size(src->blocks)); for (ssize_t i = (ssize_t)vector_size(src->blocks) - 1; i >= 0; i--) { vector_insert(&dst->blocks, pos + 1, src->blocks[i]); } blockchain_update_parent_links(dst); vector_clear(src->blocks); } void blockchain_detach_single(BlockChain* dst, BlockChain* src, size_t pos) { assert(pos < vector_size(src->blocks)); BlockdefType block_type = src->blocks[pos].blockdef->type; if (block_type == BLOCKTYPE_END) return; if (block_type != BLOCKTYPE_CONTROL) { vector_add(&dst->blocks, src->blocks[pos]); blockchain_update_parent_links(dst); vector_remove(src->blocks, pos); for (size_t i = pos; i < vector_size(src->blocks); i++) block_update_parent_links(&src->blocks[i]); return; } int size = 0; int layer = 0; for (size_t i = pos; i < vector_size(src->blocks) && layer >= 0; i++) { BlockdefType block_type = src->blocks[i].blockdef->type; vector_add(&dst->blocks, src->blocks[i]); if (block_type == BLOCKTYPE_CONTROL && i != pos) { layer++; } else if (block_type == BLOCKTYPE_END) { layer--; } size++; } blockchain_update_parent_links(dst); vector_erase(src->blocks, pos, size); for (size_t i = pos; i < vector_size(src->blocks); i++) block_update_parent_links(&src->blocks[i]); } void blockchain_detach(BlockChain* dst, BlockChain* src, size_t pos) { assert(pos < vector_size(src->blocks)); int pos_layer = 0; for (size_t i = 0; i < pos; i++) { BlockdefType block_type = src->blocks[i].blockdef->type; if (block_type == BLOCKTYPE_CONTROL) { pos_layer++; } else if (block_type == BLOCKTYPE_END) { pos_layer--; if (pos_layer < 0) pos_layer = 0; } } int current_layer = pos_layer; int layer_size = 0; vector_reserve(&dst->blocks, vector_size(dst->blocks) + vector_size(src->blocks) - pos); for (size_t i = pos; i < vector_size(src->blocks); i++) { BlockdefType block_type = src->blocks[i].blockdef->type; if ((block_type == BLOCKTYPE_END || (block_type == BLOCKTYPE_CONTROLEND && i != pos)) && pos_layer == current_layer && current_layer != 0) break; vector_add(&dst->blocks, src->blocks[i]); if (block_type == BLOCKTYPE_CONTROL) { current_layer++; } else if (block_type == BLOCKTYPE_END) { current_layer--; } layer_size++; } blockchain_update_parent_links(dst); vector_erase(src->blocks, pos, layer_size); blockchain_update_parent_links(src); } void blockchain_free(BlockChain* chain) { blockchain_clear_blocks(chain); vector_free(chain->blocks); } void argument_set_block(Argument* block_arg, Block block) { if (block_arg->type == ARGUMENT_TEXT || block_arg->type == ARGUMENT_CONST_STRING) vector_free(block_arg->data.text); block_arg->type = ARGUMENT_BLOCK; block_arg->data.block = block; block_update_parent_links(&block_arg->data.block); } void argument_set_const_string(Argument* block_arg, char* text) { assert(block_arg->type == ARGUMENT_CONST_STRING); block_arg->type = ARGUMENT_CONST_STRING; vector_clear(block_arg->data.text); for (char* pos = text; *pos; pos++) { vector_add(&block_arg->data.text, *pos); } vector_add(&block_arg->data.text, 0); } void argument_set_text(Argument* block_arg, char* text) { block_arg->type = ARGUMENT_TEXT; block_arg->data.text = vector_create(); for (char* pos = text; *pos; pos++) { vector_add(&block_arg->data.text, *pos); } vector_add(&block_arg->data.text, 0); } void argument_set_color(Argument* block_arg, BlockdefColor color) { block_arg->type = ARGUMENT_COLOR; block_arg->data.color = color; } Blockdef* blockdef_new(const char* id, BlockdefType type, BlockdefColor color, void* func) { assert(id != NULL); Blockdef* blockdef = malloc(sizeof(Blockdef)); blockdef->id = strcpy(malloc((strlen(id) + 1) * sizeof(char)), id); blockdef->color = color; blockdef->type = type; blockdef->ref_count = 0; blockdef->inputs = vector_create(); blockdef->func = func; return blockdef; } Blockdef* blockdef_copy(Blockdef* blockdef) { Blockdef* new = malloc(sizeof(Blockdef)); new->id = strcpy(malloc((strlen(blockdef->id) + 1) * sizeof(char)), blockdef->id); new->color = blockdef->color; new->type = blockdef->type; new->ref_count = 0; new->inputs = vector_create(); new->func = blockdef->func; for (size_t i = 0; i < vector_size(blockdef->inputs); i++) { Input* input = vector_add_dst(&new->inputs); input->type = blockdef->inputs[i].type; switch (blockdef->inputs[i].type) { case INPUT_TEXT_DISPLAY: input->data = (InputData) { .text = vector_copy(blockdef->inputs[i].data.text), }; break; case INPUT_ARGUMENT: input->data = (InputData) { .arg = { .blockdef = blockdef_copy(blockdef->inputs[i].data.arg.blockdef), .text = blockdef->inputs[i].data.arg.text, .constr = blockdef->inputs[i].data.arg.constr, }, }; break; case INPUT_IMAGE_DISPLAY: input->data = (InputData) { .image = blockdef->inputs[i].data.image, }; break; case INPUT_DROPDOWN: input->data = (InputData) { .drop = { .source = blockdef->inputs[i].data.drop.source, .list = blockdef->inputs[i].data.drop.list, }, }; break; case INPUT_BLOCKDEF_EDITOR: input->data = (InputData) {0}; break; default: assert(false && "Unimplemented input copy"); break; } } return new; } void blockdef_add_text(Blockdef* blockdef, const char* text) { Input* input = vector_add_dst(&blockdef->inputs); input->type = INPUT_TEXT_DISPLAY; input->data = (InputData) { .text = vector_create(), }; for (size_t i = 0; text[i]; i++) vector_add(&input->data.text, text[i]); vector_add(&input->data.text, 0); } void blockdef_add_argument(Blockdef* blockdef, char* defualt_data, const char* hint_text, InputArgumentConstraint constraint) { Input* input = vector_add_dst(&blockdef->inputs); input->type = INPUT_ARGUMENT; input->data = (InputData) { .arg = { .blockdef = blockdef_new("custom_arg", BLOCKTYPE_NORMAL, blockdef->color, NULL), .text = defualt_data, .constr = constraint, .hint_text = hint_text, }, }; input->data.arg.blockdef->ref_count++; } void blockdef_add_blockdef_editor(Blockdef* blockdef) { Input* input = vector_add_dst(&blockdef->inputs); input->type = INPUT_BLOCKDEF_EDITOR; input->data = (InputData) {0}; } void blockdef_add_dropdown(Blockdef* blockdef, InputDropdownSource dropdown_source, ListAccessor accessor) { Input* input = vector_add_dst(&blockdef->inputs); input->type = INPUT_DROPDOWN; input->data = (InputData) { .drop = { .source = dropdown_source, .list = accessor, }, }; } void blockdef_add_color_input(Blockdef* blockdef, BlockdefColor color) { Input* input = vector_add_dst(&blockdef->inputs); input->type = INPUT_COLOR; input->data = (InputData) { .color = color, }; } void blockdef_add_image(Blockdef* blockdef, BlockdefImage image) { Input* input = vector_add_dst(&blockdef->inputs); input->type = INPUT_IMAGE_DISPLAY; input->data = (InputData) { .image = image, }; } void blockdef_set_id(Blockdef* blockdef, const char* new_id) { free((void*)blockdef->id); blockdef->id = strcpy(malloc((strlen(new_id) + 1) * sizeof(char)), new_id); } void blockdef_delete_input(Blockdef* blockdef, size_t input) { assert(input < vector_size(blockdef->inputs)); switch (blockdef->inputs[input].type) { case INPUT_TEXT_DISPLAY: vector_free(blockdef->inputs[input].data.text); break; case INPUT_ARGUMENT: blockdef_free(blockdef->inputs[input].data.arg.blockdef); break; default: assert(false && "Unimplemented input delete"); break; } vector_remove(blockdef->inputs, input); } void blockdef_free(Blockdef* blockdef) { blockdef->ref_count--; if (blockdef->ref_count > 0) return; for (vec_size_t i = 0; i < vector_size(blockdef->inputs); i++) { switch (blockdef->inputs[i].type) { case INPUT_TEXT_DISPLAY: vector_free(blockdef->inputs[i].data.text); break; case INPUT_ARGUMENT: blockdef_free(blockdef->inputs[i].data.arg.blockdef); break; default: break; } } vector_free(blockdef->inputs); free((void*)blockdef->id); free(blockdef); } ================================================ FILE: src/ast.h ================================================ // Scrap is a project that allows anyone to build software using simple, block based interface. // // Copyright (C) 2024-2026 Grisshink // // This software is provided 'as-is', without any express or implied // warranty. In no event will the authors be held liable for any damages // arising from the use of this software. // // Permission is granted to anyone to use this software for any purpose, // including commercial applications, and to alter it and redistribute it // freely, subject to the following restrictions: // // 1. The origin of this software must not be misrepresented; you must not // claim that you wrote the original software. If you use this software // in a product, an acknowledgment in the product documentation would be // appreciated but is not required. // 2. Altered source versions must be plainly marked as such, and must not be // misrepresented as being the original software. // 3. This notice may not be removed or altered from any source distribution. #ifndef AST_H #define AST_H #include #include typedef struct BlockdefColor BlockdefColor; typedef struct BlockdefImage BlockdefImage; typedef enum ArgumentType ArgumentType; typedef union ArgumentData ArgumentData; typedef struct Argument Argument; typedef struct Block Block; typedef enum InputArgumentConstraint InputArgumentConstraint; typedef enum InputDropdownSource InputDropdownSource; typedef struct InputArgument InputArgument; typedef struct InputDropdown InputDropdown; typedef enum InputType InputType; typedef union InputData InputData; typedef struct Input Input; typedef enum BlockdefType BlockdefType; typedef struct Blockdef Blockdef; typedef struct BlockChain BlockChain; typedef char** (*ListAccessor)(Block* block, size_t* list_len); typedef enum { DATA_TYPE_UNKNOWN = 0, DATA_TYPE_NOTHING, DATA_TYPE_INTEGER, DATA_TYPE_FLOAT, DATA_TYPE_LITERAL, // Literal string, stored in global memory DATA_TYPE_STRING, // Pointer to a string type, managed by the current memory allocator (GC) DATA_TYPE_BOOL, DATA_TYPE_LIST, DATA_TYPE_ANY, DATA_TYPE_BLOCKDEF, DATA_TYPE_COLOR, } DataType; struct BlockdefColor { unsigned char r, g, b, a; }; struct BlockdefImage { void* image_ptr; BlockdefColor image_color; }; enum InputArgumentConstraint { BLOCKCONSTR_UNLIMITED, // Can put anything as argument BLOCKCONSTR_STRING, // Can only put strings as argument }; struct InputArgument { Blockdef* blockdef; InputArgumentConstraint constr; char* text; const char* hint_text; }; enum InputDropdownSource { DROPDOWN_SOURCE_LISTREF, }; struct InputDropdown { InputDropdownSource source; ListAccessor list; }; union InputData { char* text; BlockdefImage image; InputArgument arg; InputDropdown drop; BlockdefColor color; }; enum InputType { INPUT_TEXT_DISPLAY, INPUT_ARGUMENT, INPUT_DROPDOWN, INPUT_BLOCKDEF_EDITOR, INPUT_IMAGE_DISPLAY, INPUT_COLOR, }; struct Input { InputType type; InputData data; }; enum BlockdefType { BLOCKTYPE_NORMAL, BLOCKTYPE_CONTROL, BLOCKTYPE_CONTROLEND, BLOCKTYPE_END, BLOCKTYPE_HAT, }; struct Blockdef { const char* id; int ref_count; BlockdefColor color; BlockdefType type; Input* inputs; void* func; }; struct Block { Blockdef* blockdef; Argument* arguments; Block* parent; }; union ArgumentData { char* text; BlockdefColor color; Block block; Blockdef* blockdef; }; enum ArgumentType { ARGUMENT_TEXT = 0, ARGUMENT_BLOCK, ARGUMENT_CONST_STRING, ARGUMENT_BLOCKDEF, ARGUMENT_COLOR, // Must be last in enum ARGUMENT_LAST, }; struct Argument { int input_id; ArgumentType type; ArgumentData data; }; struct BlockChain { int x, y; int width, height; Block* blocks; }; Blockdef* blockdef_new(const char* id, BlockdefType type, BlockdefColor color, void* func); Blockdef* blockdef_copy(Blockdef* blockdef); void blockdef_add_text(Blockdef* blockdef, const char* text); void blockdef_add_argument(Blockdef* blockdef, char* defualt_data, const char* hint_text, InputArgumentConstraint constraint); void blockdef_add_dropdown(Blockdef* blockdef, InputDropdownSource dropdown_source, ListAccessor accessor); void blockdef_add_color_input(Blockdef* blockdef, BlockdefColor color); void blockdef_add_image(Blockdef* blockdef, BlockdefImage image); void blockdef_add_blockdef_editor(Blockdef* blockdef); void blockdef_delete_input(Blockdef* blockdef, size_t input); void blockdef_set_id(Blockdef* blockdef, const char* new_id); void blockdef_free(Blockdef* blockdef); BlockChain blockchain_new(void); BlockChain blockchain_copy(BlockChain* chain, size_t ind); BlockChain blockchain_copy_single(BlockChain* chain, size_t pos); void blockchain_add_block(BlockChain* chain, Block block); void blockchain_clear_blocks(BlockChain* chain); void blockchain_insert(BlockChain* dst, BlockChain* src, size_t pos); void blockchain_update_parent_links(BlockChain* chain); // Splits off blockchain src in two at specified pos, placing lower half into blockchain dst void blockchain_detach(BlockChain* dst, BlockChain* src, size_t pos); void blockchain_detach_single(BlockChain* dst, BlockChain* src, size_t pos); void blockchain_free(BlockChain* chain); Block block_new(Blockdef* blockdef); Block block_copy(Block* block, Block* parent); void block_update_parent_links(Block* block); void block_update_all_links(Block* block); void block_free(Block* block); void argument_set_block(Argument* block_arg, Block block); void argument_set_const_string(Argument* block_arg, char* text); void argument_set_text(Argument* block_arg, char* text); void argument_set_color(Argument* block_arg, BlockdefColor color); const char* type_to_str(DataType type); #endif // AST_H ================================================ FILE: src/blocks.c ================================================ // Scrap is a project that allows anyone to build software using simple, block based interface. // // Copyright (C) 2024-202 Grisshink // // This software is provided 'as-is', without any express or implied // warranty. In no event will the authors be held liable for any damages // arising from the use of this software. // // Permission is granted to anyone to use this software for any purpose, // including commercial applications, and to alter it and redistribute it // freely, subject to the following restrictions: // // 1. The origin of this software must not be misrepresented; you must not // claim that you wrote the original software. If you use this software // in a product, an acknowledgment in the product documentation would be // appreciated but is not required. // 2. Altered source versions must be plainly marked as such, and must not be // misrepresented as being the original software. // 3. This notice may not be removed or altered from any source distribution. #include "term.h" #include "scrap.h" #include "vec.h" #include "util.h" #include "std.h" #include #include #include #include #include #include #define MATH_LIST_LEN 10 #define TERM_COLOR_LIST_LEN 8 typedef struct { char* str; size_t len; size_t cap; } String; typedef double (*MathFunc)(double); char* block_math_list[MATH_LIST_LEN] = { "sqrt", "round", "floor", "ceil", "sin", "cos", "tan", "asin", "acos", "atan", }; char** math_list_access(Block* block, size_t* list_len) { (void) block; *list_len = MATH_LIST_LEN; return block_math_list; } #ifdef USE_INTERPRETER static MathFunc block_math_func_list[MATH_LIST_LEN] = { sqrt, round, floor, ceil, sin, cos, tan, asin, acos, atan, }; #include "std.h" bool block_do_nothing(Exec* exec, Block* block, int argc, AnyValue* argv, AnyValue* return_val, ControlState control_state) { (void) control_state; (void) block; (void) exec; (void) argc; (void) argv; *return_val = DATA_NOTHING; return true; } bool block_noop(Exec* exec, Block* block, int argc, AnyValue* argv, AnyValue* return_val, ControlState control_state) { (void) control_state; (void) block; (void) exec; (void) argc; (void) argv; *return_val = DATA_NOTHING; return true; } bool block_on_start(Exec* exec, Block* block, int argc, AnyValue* argv, AnyValue* return_val, ControlState control_state) { (void) control_state; (void) block; (void) exec; (void) argc; (void) argv; *return_val = DATA_NOTHING; return true; } bool block_define_block(Exec* exec, Block* block, int argc, AnyValue* argv, AnyValue* return_val, ControlState control_state) { (void) control_state; (void) block; (void) exec; (void) argc; (void) argv; *return_val = DATA_NOTHING; return true; } bool block_loop(Exec* exec, Block* block, int argc, AnyValue* argv, AnyValue* return_val, ControlState control_state) { (void) block; (void) argc; (void) argv; if (control_state == CONTROL_STATE_BEGIN) { control_stack_push_data(exec->chain_stack[exec->chain_stack_len - 1].running_ind, size_t); } else if (control_state == CONTROL_STATE_END) { control_stack_pop_data(exec->chain_stack[exec->chain_stack_len - 1].running_ind, size_t); control_stack_push_data(exec->chain_stack[exec->chain_stack_len - 1].running_ind, size_t); } *return_val = DATA_BOOL(1); return true; } bool block_if(Exec* exec, Block* block, int argc, AnyValue* argv, AnyValue* return_val, ControlState control_state) { (void) block; if (control_state == CONTROL_STATE_BEGIN) { if (argc < 1 || !std_bool_from_any(&argv[0])) { exec_set_skip_block(exec); control_stack_push_data((int)0, int); } else { control_stack_push_data((int)1, int); } *return_val = DATA_NOTHING; } else if (control_state == CONTROL_STATE_END) { int is_success = 0; control_stack_pop_data(is_success, int); *return_val = DATA_BOOL(is_success); } else { exec_set_error(exec, block, "Invalid control state"); return false; } return true; } bool block_else_if(Exec* exec, Block* block, int argc, AnyValue* argv, AnyValue* return_val, ControlState control_state) { (void) block; if (control_state == CONTROL_STATE_BEGIN) { if (argc < 2 || std_bool_from_any(&argv[0])) { exec_set_skip_block(exec); control_stack_push_data((int)1, int); } else { int condition = std_bool_from_any(&argv[1]); if (!condition) exec_set_skip_block(exec); control_stack_push_data(condition, int); } *return_val = DATA_NOTHING; } else if (control_state == CONTROL_STATE_END) { int is_success = 0; control_stack_pop_data(is_success, int); *return_val = DATA_BOOL(is_success); } else { exec_set_error(exec, block, "Invalid control state"); return false; } return true; } bool block_else(Exec* exec, Block* block, int argc, AnyValue* argv, AnyValue* return_val, ControlState control_state) { (void) block; if (control_state == CONTROL_STATE_BEGIN) { if (argc < 1 || std_bool_from_any(&argv[0])) { exec_set_skip_block(exec); } *return_val = DATA_NOTHING; } else if (control_state == CONTROL_STATE_END) { *return_val = DATA_BOOL(1); } else { exec_set_error(exec, block, "Invalid control state"); return false; } return true; } // Visualization of control stack (stack grows downwards): // - loop block index // - cycles left to loop // - 1 <- indicator for end block to do looping // // If the loop should not loop then the stack will look like this: // - 0 <- indicator for end block that it should stop immediately bool block_repeat(Exec* exec, Block* block, int argc, AnyValue* argv, AnyValue* return_val, ControlState control_state) { (void) block; if (control_state == CONTROL_STATE_BEGIN) { int cycles = argc < 1 ? 0 : std_integer_from_any(&argv[0]); if (cycles <= 0) { exec_set_skip_block(exec); control_stack_push_data((int)0, int); // This indicates the end block that it should NOT loop *return_val = DATA_NOTHING; return true; } control_stack_push_data(exec->chain_stack[exec->chain_stack_len - 1].running_ind, size_t); control_stack_push_data(cycles - 1, int); control_stack_push_data((int)1, int); // This indicates the end block that it should loop } else if (control_state == CONTROL_STATE_END) { int should_loop = 0; control_stack_pop_data(should_loop, int); if (!should_loop) { *return_val = DATA_BOOL(0); return true; } int left = -1; control_stack_pop_data(left, int); if (left <= 0) { size_t bin; control_stack_pop_data(bin, size_t); (void) bin; // Cleanup stack *return_val = DATA_BOOL(1); return true; } control_stack_pop_data(exec->chain_stack[exec->chain_stack_len - 1].running_ind, size_t); control_stack_push_data(exec->chain_stack[exec->chain_stack_len - 1].running_ind, size_t); control_stack_push_data(left - 1, int); control_stack_push_data((int)1, int); } else { exec_set_error(exec, block, "Invalid control state"); return false; } *return_val = DATA_NOTHING; return true; } bool block_while(Exec* exec, Block* block, int argc, AnyValue* argv, AnyValue* return_val, ControlState control_state) { if (control_state == CONTROL_STATE_BEGIN) { if (argc < 1 || !std_bool_from_any(&argv[0])) { exec_set_skip_block(exec); } control_stack_push_data(exec->chain_stack[exec->chain_stack_len - 1].running_ind, size_t); } else if (control_state == CONTROL_STATE_END) { AnyValue out_val; if (!evaluate_argument(exec, &block->arguments[0], &out_val)) return false; if (vector_size(block->arguments) < 1 || !std_bool_from_any(&out_val)) { size_t bin; control_stack_pop_data(bin, size_t); (void) bin; *return_val = DATA_BOOL(1); return true; } control_stack_pop_data(exec->chain_stack[exec->chain_stack_len - 1].running_ind, size_t); control_stack_push_data(exec->chain_stack[exec->chain_stack_len - 1].running_ind, size_t); } else { exec_set_error(exec, block, "Invalid control state"); return false; } *return_val = DATA_NOTHING; return true; } bool block_sleep(Exec* exec, Block* block, int argc, AnyValue* argv, AnyValue* return_val, ControlState control_state) { (void) control_state; (void) block; (void) exec; (void) argc; int usecs = std_integer_from_any(&argv[0]); if (usecs < 0) { *return_val = DATA_INTEGER(0); return true; } struct timespec sleep_time = {0}; sleep_time.tv_sec = usecs / 1000000; sleep_time.tv_nsec = (usecs % 1000000) * 1000; if (nanosleep(&sleep_time, &sleep_time) == -1) { *return_val = DATA_INTEGER(0); return true; } *return_val = DATA_INTEGER(usecs); return true; } bool block_declare_var(Exec* exec, Block* block, int argc, AnyValue* argv, AnyValue* return_val, ControlState control_state) { (void) control_state; (void) argc; if (argv[0].type != DATA_TYPE_LITERAL) { exec_set_error(exec, block, gettext("Invalid data type %s, expected %s"), type_to_str(argv[0].type), type_to_str(DATA_TYPE_LITERAL)); return false; } if (block->parent) { exec_set_error(exec, block, gettext("Variable declarations are not allowed inside an expression")); return false; } Variable* var = variable_stack_push_var(exec, argv[0].data.literal_val, argv[1]); if (!var) { exec_set_error(exec, block, gettext("Cannot declare variable with empty name")); return false; } if (argv[1].type == DATA_TYPE_LIST || argv[1].type == DATA_TYPE_ANY || argv[1].type == DATA_TYPE_STRING) { gc_add_root(&exec->gc, &var->value_ptr); } *return_val = argv[1]; return true; } bool block_get_var(Exec* exec, Block* block, int argc, AnyValue* argv, AnyValue* return_val, ControlState control_state) { (void) control_state; (void) block; (void) argc; if (argv[0].type != DATA_TYPE_LITERAL) { exec_set_error(exec, block, gettext("Invalid data type %s, expected %s"), type_to_str(argv[0].type), type_to_str(DATA_TYPE_LITERAL)); return false; } char* var_name = argv[0].data.literal_val; Variable* var = variable_stack_get_variable(exec, var_name); if (!var) { exec_set_error(exec, block, gettext("Variable with name \"%s\" does not exist in the current scope"), var_name); return false; } *return_val = var->value; return true; } bool block_set_var(Exec* exec, Block* block, int argc, AnyValue* argv, AnyValue* return_val, ControlState control_state) { (void) control_state; (void) block; (void) argc; if (argv[0].type != DATA_TYPE_LITERAL) { exec_set_error(exec, block, gettext("Invalid data type %s, expected %s"), type_to_str(argv[0].type), type_to_str(DATA_TYPE_LITERAL)); return false; } char* var_name = argv[0].data.literal_val; Variable* var = variable_stack_get_variable(exec, var_name); if (!var) { exec_set_error(exec, block, gettext("Variable with name \"%s\" does not exist in the current scope"), var_name); return false; } var->value = argv[1]; *return_val = var->value; return true; } bool block_create_list(Exec* exec, Block* block, int argc, AnyValue* argv, AnyValue* return_val, ControlState control_state) { (void) control_state; (void) block; (void) exec; (void) argc; (void) argv; *return_val = DATA_LIST(std_list_new(&exec->gc)); return true; } bool block_list_add(Exec* exec, Block* block, int argc, AnyValue* argv, AnyValue* return_val, ControlState control_state) { (void) control_state; (void) argc; if (argv[0].type != DATA_TYPE_LIST) { exec_set_error(exec, block, gettext("Invalid data type %s, expected %s"), type_to_str(argv[0].type), type_to_str(DATA_TYPE_LIST)); return false; } std_list_add_any(&exec->gc, argv[0].data.list_val, argv[1]); *return_val = DATA_NOTHING; return true; } bool block_list_get(Exec* exec, Block* block, int argc, AnyValue* argv, AnyValue* return_val, ControlState control_state) { (void) control_state; (void) block; (void) argc; if (argv[0].type != DATA_TYPE_LIST) { exec_set_error(exec, block, gettext("Invalid data type %s, expected %s"), type_to_str(argv[0].type), type_to_str(DATA_TYPE_LIST)); return false; } List* list = argv[0].data.list_val; int index = std_integer_from_any(&argv[1]); if (index >= list->size || index < 0) { *return_val = DATA_NOTHING; return true; } *return_val = list->values[index]; return true; } bool block_list_length(Exec* exec, Block* block, int argc, AnyValue* argv, AnyValue* return_val, ControlState control_state) { (void) control_state; (void) block; (void) exec; (void) argc; if (argv[0].type != DATA_TYPE_LIST) { exec_set_error(exec, block, gettext("Invalid data type %s, expected %s"), type_to_str(argv[0].type), type_to_str(DATA_TYPE_LIST)); return false; } *return_val = DATA_INTEGER(argv[0].data.list_val->size); return true; } bool block_list_set(Exec* exec, Block* block, int argc, AnyValue* argv, AnyValue* return_val, ControlState control_state) { (void) control_state; (void) block; (void) exec; (void) argc; if (argv[0].type != DATA_TYPE_LIST) { exec_set_error(exec, block, gettext("Invalid data type %s, expected %s"), type_to_str(argv[0].type), type_to_str(DATA_TYPE_LIST)); return false; } int index = std_integer_from_any(&argv[1]); if (index >= argv[0].data.list_val->size || index < 0) { exec_set_error(exec, block, gettext("Tried to access index %d for list of length %d"), index, argv[0].data.list_val->size); return false; } argv[0].data.list_val->values[index] = argv[2]; *return_val = DATA_NOTHING; return true; } bool block_print(Exec* exec, Block* block, int argc, AnyValue* argv, AnyValue* return_val, ControlState control_state) { (void) exec; (void) block; (void) control_state; (void) argc; int bytes_sent = 0; switch (argv[0].type) { case DATA_TYPE_INTEGER: bytes_sent = term_print_integer(argv[0].data.integer_val); break; case DATA_TYPE_BOOL: bytes_sent = term_print_str(argv[0].data.integer_val ? "true" : "false"); break; case DATA_TYPE_LITERAL: bytes_sent = term_print_str(argv[0].data.literal_val); break; case DATA_TYPE_STRING: bytes_sent = term_print_str(argv[0].data.str_val->str); break; case DATA_TYPE_FLOAT: bytes_sent = term_print_float(argv[0].data.float_val); break; case DATA_TYPE_LIST: bytes_sent = term_print_str("*LIST ("); bytes_sent += term_print_integer(argv[0].data.list_val->size); bytes_sent += term_print_str(")*"); break; case DATA_TYPE_NOTHING: break; case DATA_TYPE_COLOR: bytes_sent = term_print_color(CONVERT_COLOR(argv[0].data.color_val, TermColor)); break; case DATA_TYPE_UNKNOWN: case DATA_TYPE_ANY: case DATA_TYPE_BLOCKDEF: exec_set_error(exec, block, gettext("Cannot print type %s"), type_to_str(argv[0].type)); return false; } *return_val = DATA_INTEGER(bytes_sent); return true; } bool block_println(Exec* exec, Block* block, int argc, AnyValue* argv, AnyValue* return_val, ControlState control_state) { if (!block_print(exec, block, argc, argv, return_val, control_state)) return false; term_print_str("\n"); return_val->data.integer_val++; return true; } bool block_cursor_x(Exec* exec, Block* block, int argc, AnyValue* argv, AnyValue* return_val, ControlState control_state) { (void) control_state; (void) block; (void) exec; (void) argv; (void) argc; mutex_lock(&term.lock); int cur_x = 0; if (term.char_w != 0) cur_x = term.cursor_pos % term.char_w; mutex_unlock(&term.lock); *return_val = DATA_INTEGER(cur_x); return true; } bool block_cursor_y(Exec* exec, Block* block, int argc, AnyValue* argv, AnyValue* return_val, ControlState control_state) { (void) control_state; (void) block; (void) exec; (void) argv; (void) argc; mutex_lock(&term.lock); int cur_y = 0; if (term.char_w != 0) cur_y = term.cursor_pos / term.char_w; mutex_unlock(&term.lock); *return_val = DATA_INTEGER(cur_y); return true; } bool block_cursor_max_x(Exec* exec, Block* block, int argc, AnyValue* argv, AnyValue* return_val, ControlState control_state) { (void) control_state; (void) block; (void) exec; (void) argv; (void) argc; mutex_lock(&term.lock); int cur_max_x = term.char_w; mutex_unlock(&term.lock); *return_val = DATA_INTEGER(cur_max_x); return true; } bool block_cursor_max_y(Exec* exec, Block* block, int argc, AnyValue* argv, AnyValue* return_val, ControlState control_state) { (void) control_state; (void) block; (void) exec; (void) argv; (void) argc; mutex_lock(&term.lock); int cur_max_y = term.char_h; mutex_unlock(&term.lock); *return_val = DATA_INTEGER(cur_max_y); return true; } bool block_set_cursor(Exec* exec, Block* block, int argc, AnyValue* argv, AnyValue* return_val, ControlState control_state) { (void) control_state; (void) block; (void) exec; (void) argc; mutex_lock(&term.lock); int x = CLAMP(std_integer_from_any(&argv[0]), 0, term.char_w - 1); int y = CLAMP(std_integer_from_any(&argv[1]), 0, term.char_h - 1); term.cursor_pos = x + y * term.char_w; mutex_unlock(&term.lock); *return_val = DATA_NOTHING; return true; } bool block_set_fg_color(Exec* exec, Block* block, int argc, AnyValue* argv, AnyValue* return_val, ControlState control_state) { (void) control_state; (void) block; (void) exec; (void) argc; term_set_fg_color(CONVERT_COLOR(std_color_from_any(&argv[0]), TermColor)); *return_val = DATA_NOTHING; return true; } bool block_set_bg_color(Exec* exec, Block* block, int argc, AnyValue* argv, AnyValue* return_val, ControlState control_state) { (void) control_state; (void) block; (void) exec; (void) argc; term_set_bg_color(CONVERT_COLOR(std_color_from_any(&argv[0]), TermColor)); *return_val = DATA_NOTHING; return true; } bool block_reset_color(Exec* exec, Block* block, int argc, AnyValue* argv, AnyValue* return_val, ControlState control_state) { (void) control_state; (void) block; (void) exec; (void) argv; (void) argc; term_set_fg_color(CONVERT_COLOR(WHITE, TermColor)); term_set_bg_color(CONVERT_COLOR(BLACK, TermColor)); *return_val = DATA_NOTHING; return true; } bool block_term_clear(Exec* exec, Block* block, int argc, AnyValue* argv, AnyValue* return_val, ControlState control_state) { (void) control_state; (void) block; (void) exec; (void) argv; (void) argc; term_clear(); *return_val = DATA_NOTHING; return true; } bool block_term_set_clear(Exec* exec, Block* block, int argc, AnyValue* argv, AnyValue* return_val, ControlState control_state) { (void) control_state; (void) block; (void) exec; (void) argc; term_set_clear_color(CONVERT_COLOR(std_color_from_any(&argv[0]), TermColor)); *return_val = DATA_NOTHING; return true; } bool block_input(Exec* exec, Block* block, int argc, AnyValue* argv, AnyValue* return_val, ControlState control_state) { (void) control_state; (void) block; (void) argv; (void) argc; *return_val = DATA_STRING(std_term_get_input(&exec->gc)); return true; } bool block_get_char(Exec* exec, Block* block, int argc, AnyValue* argv, AnyValue* return_val, ControlState control_state) { (void) control_state; (void) block; (void) argv; (void) argc; *return_val = DATA_STRING(std_term_get_char(&exec->gc)); return true; } bool block_random(Exec* exec, Block* block, int argc, AnyValue* argv, AnyValue* return_val, ControlState control_state) { (void) control_state; (void) block; (void) exec; (void) argc; int min = std_integer_from_any(&argv[0]); int max = std_integer_from_any(&argv[1]); if (min > max) { int temp = min; min = max; max = temp; } int val = GetRandomValue(min, max); *return_val = DATA_INTEGER(val); return true; } bool block_join(Exec* exec, Block* block, int argc, AnyValue* argv, AnyValue* return_val, ControlState control_state) { (void) control_state; (void) argc; (void) block; *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]))); return true; } bool block_ord(Exec* exec, Block* block, int argc, AnyValue* argv, AnyValue* return_val, ControlState control_state) { (void) control_state; (void) block; (void) exec; (void) argc; const char* str = std_any_string_from_any(&exec->gc, &argv[0]); int codepoint_size; int codepoint = GetCodepoint(str, &codepoint_size); *return_val = DATA_INTEGER(codepoint); return true; } bool block_chr(Exec* exec, Block* block, int argc, AnyValue* argv, AnyValue* return_val, ControlState control_state) { (void) control_state; (void) block; (void) argc; *return_val = DATA_STRING(std_string_chr(&exec->gc, std_integer_from_any(&argv[0]))); return true; } bool block_letter_in(Exec* exec, Block* block, int argc, AnyValue* argv, AnyValue* return_val, ControlState control_state) { (void) control_state; (void) block; (void) argc; *return_val = DATA_STRING(std_string_letter_in(&exec->gc, std_integer_from_any(&argv[0]), std_string_from_any(&exec->gc, &argv[1]))); return true; } bool block_substring(Exec* exec, Block* block, int argc, AnyValue* argv, AnyValue* return_val, ControlState control_state) { (void) control_state; (void) block; (void) argc; *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]))); return true; } bool block_length(Exec* exec, Block* block, int argc, AnyValue* argv, AnyValue* return_val, ControlState control_state) { (void) control_state; (void) block; (void) argc; *return_val = DATA_INTEGER(std_string_length(std_string_from_any(&exec->gc, &argv[0]))); return true; } bool block_unix_time(Exec* exec, Block* block, int argc, AnyValue* argv, AnyValue* return_val, ControlState control_state) { (void) control_state; (void) block; (void) exec; (void) argc; (void) argv; *return_val = DATA_INTEGER(time(NULL)); return true; } bool block_convert_int(Exec* exec, Block* block, int argc, AnyValue* argv, AnyValue* return_val, ControlState control_state) { (void) control_state; (void) block; (void) exec; (void) argc; *return_val = DATA_INTEGER(std_integer_from_any(&argv[0])); return true; } bool block_convert_float(Exec* exec, Block* block, int argc, AnyValue* argv, AnyValue* return_val, ControlState control_state) { (void) control_state; (void) block; (void) exec; (void) argc; *return_val = DATA_FLOAT(std_float_from_any(&argv[0])); return true; } bool block_convert_str(Exec* exec, Block* block, int argc, AnyValue* argv, AnyValue* return_val, ControlState control_state) { (void) control_state; (void) block; (void) argc; *return_val = DATA_STRING(std_string_from_any(&exec->gc, &argv[0])); return true; } bool block_convert_bool(Exec* exec, Block* block, int argc, AnyValue* argv, AnyValue* return_val, ControlState control_state) { (void) control_state; (void) block; (void) exec; (void) argc; *return_val = DATA_BOOL(std_bool_from_any(&argv[0])); return true; } bool block_convert_color(Exec* exec, Block* block, int argc, AnyValue* argv, AnyValue* return_val, ControlState control_state) { (void) control_state; (void) block; (void) exec; (void) argc; *return_val = DATA_COLOR(std_color_from_any(&argv[0])); return true; } bool block_typeof(Exec* exec, Block* block, int argc, AnyValue* argv, AnyValue* return_val, ControlState control_state) { (void) control_state; (void) block; (void) exec; (void) argc; *return_val = DATA_LITERAL((char*)type_to_str(argv[0].type)); return true; } bool block_plus(Exec* exec, Block* block, int argc, AnyValue* argv, AnyValue* return_val, ControlState control_state) { (void) control_state; (void) block; (void) exec; (void) argc; if (argv[0].type == DATA_TYPE_FLOAT) { *return_val = DATA_FLOAT(argv[0].data.float_val + std_float_from_any(&argv[1])); } else { *return_val = DATA_INTEGER(std_integer_from_any(&argv[0]) + std_integer_from_any(&argv[1])); } return true; } bool block_minus(Exec* exec, Block* block, int argc, AnyValue* argv, AnyValue* return_val, ControlState control_state) { (void) control_state; (void) block; (void) exec; (void) argc; if (argv[0].type == DATA_TYPE_FLOAT) { *return_val = DATA_FLOAT(argv[0].data.float_val - std_float_from_any(&argv[1])); } else { *return_val = DATA_INTEGER(std_integer_from_any(&argv[0]) - std_integer_from_any(&argv[1])); } return true; } bool block_mult(Exec* exec, Block* block, int argc, AnyValue* argv, AnyValue* return_val, ControlState control_state) { (void) control_state; (void) block; (void) exec; (void) argc; if (argv[0].type == DATA_TYPE_FLOAT) { *return_val = DATA_FLOAT(argv[0].data.float_val * std_float_from_any(&argv[1])); } else { *return_val = DATA_INTEGER(std_integer_from_any(&argv[0]) * std_integer_from_any(&argv[1])); } return true; } bool block_div(Exec* exec, Block* block, int argc, AnyValue* argv, AnyValue* return_val, ControlState control_state) { (void) control_state; (void) block; (void) exec; (void) argc; if (argv[0].type == DATA_TYPE_FLOAT) { *return_val = DATA_FLOAT(argv[0].data.float_val / std_float_from_any(&argv[1])); } else { int divisor = std_integer_from_any(&argv[1]); if (divisor == 0) { exec_set_error(exec, block, gettext("Division by zero")); return false; } *return_val = DATA_INTEGER(std_integer_from_any(&argv[0]) / divisor); } return true; } bool block_pow(Exec* exec, Block* block, int argc, AnyValue* argv, AnyValue* return_val, ControlState control_state) { (void) control_state; (void) block; (void) exec; (void) argc; if (argv[0].type == DATA_TYPE_FLOAT) { *return_val = DATA_FLOAT(pow(argv[0].data.float_val, std_float_from_any(&argv[1]))); return true; } int base = std_integer_from_any(&argv[0]); unsigned int exp = std_integer_from_any(&argv[1]); if (!exp) { *return_val = DATA_INTEGER(1); return true; } int result = 1; while (exp) { if (exp & 1) result *= base; exp >>= 1; base *= base; } *return_val = DATA_INTEGER(result); return true; } bool block_math(Exec* exec, Block* block, int argc, AnyValue* argv, AnyValue* return_val, ControlState control_state) { (void) control_state; (void) block; (void) exec; (void) argc; if (argv[0].type != DATA_TYPE_LITERAL) { exec_set_error(exec, block, gettext("Invalid data type %s, expected %s"), type_to_str(argv[0].type), type_to_str(DATA_TYPE_LITERAL)); return false; } for (int i = 0; i < MATH_LIST_LEN; i++) { if (strcmp(argv[0].data.literal_val, block_math_list[i])) continue; *return_val = DATA_FLOAT(block_math_func_list[i](std_float_from_any(&argv[1]))); return true; } exec_set_error(exec, block, gettext("Invalid argument %s"), argv[0].data.literal_val); return false; } bool block_pi(Exec* exec, Block* block, int argc, AnyValue* argv, AnyValue* return_val, ControlState control_state) { (void) control_state; (void) block; (void) exec; (void) argc; (void) argv; *return_val = DATA_FLOAT(M_PI); return true; } bool block_bit_not(Exec* exec, Block* block, int argc, AnyValue* argv, AnyValue* return_val, ControlState control_state) { (void) control_state; (void) block; (void) exec; (void) argc; *return_val = DATA_INTEGER(~std_integer_from_any(&argv[0])); return true; } bool block_bit_and(Exec* exec, Block* block, int argc, AnyValue* argv, AnyValue* return_val, ControlState control_state) { (void) control_state; (void) block; (void) exec; (void) argc; *return_val = DATA_INTEGER(std_integer_from_any(&argv[0]) & std_integer_from_any(&argv[1])); return true; } bool block_bit_xor(Exec* exec, Block* block, int argc, AnyValue* argv, AnyValue* return_val, ControlState control_state) { (void) control_state; (void) block; (void) exec; (void) argc; *return_val = DATA_INTEGER(std_integer_from_any(&argv[0]) ^ std_integer_from_any(&argv[1])); return true; } bool block_bit_or(Exec* exec, Block* block, int argc, AnyValue* argv, AnyValue* return_val, ControlState control_state) { (void) control_state; (void) block; (void) exec; (void) argc; *return_val = DATA_INTEGER(std_integer_from_any(&argv[0]) | std_integer_from_any(&argv[1])); return true; } bool block_rem(Exec* exec, Block* block, int argc, AnyValue* argv, AnyValue* return_val, ControlState control_state) { (void) control_state; (void) block; (void) exec; (void) argc; if (argv[0].type == DATA_TYPE_FLOAT) { *return_val = DATA_FLOAT(fmod(argv[0].data.float_val, std_float_from_any(&argv[1]))); } else { *return_val = DATA_INTEGER(std_integer_from_any(&argv[0]) % std_integer_from_any(&argv[1])); } return true; } bool block_less(Exec* exec, Block* block, int argc, AnyValue* argv, AnyValue* return_val, ControlState control_state) { (void) control_state; (void) block; (void) exec; (void) argc; if (argv[0].type == DATA_TYPE_FLOAT) { *return_val = DATA_BOOL(argv[0].data.float_val < std_float_from_any(&argv[1])); } else { *return_val = DATA_BOOL(std_integer_from_any(&argv[0]) < std_integer_from_any(&argv[1])); } return true; } bool block_less_eq(Exec* exec, Block* block, int argc, AnyValue* argv, AnyValue* return_val, ControlState control_state) { (void) control_state; (void) block; (void) exec; (void) argc; if (argv[0].type == DATA_TYPE_FLOAT) { *return_val = DATA_BOOL(argv[0].data.float_val <= std_float_from_any(&argv[1])); } else { *return_val = DATA_BOOL(std_integer_from_any(&argv[0]) <= std_integer_from_any(&argv[1])); } return true; } bool block_more(Exec* exec, Block* block, int argc, AnyValue* argv, AnyValue* return_val, ControlState control_state) { (void) control_state; (void) block; (void) exec; (void) argc; if (argv[0].type == DATA_TYPE_FLOAT) { *return_val = DATA_BOOL(argv[0].data.float_val > std_float_from_any(&argv[1])); } else { *return_val = DATA_BOOL(std_integer_from_any(&argv[0]) > std_integer_from_any(&argv[1])); } return true; } bool block_more_eq(Exec* exec, Block* block, int argc, AnyValue* argv, AnyValue* return_val, ControlState control_state) { (void) control_state; (void) block; (void) exec; (void) argc; if (argv[0].type == DATA_TYPE_FLOAT) { *return_val = DATA_BOOL(argv[0].data.float_val >= std_float_from_any(&argv[1])); } else { *return_val = DATA_BOOL(std_integer_from_any(&argv[0]) >= std_integer_from_any(&argv[1])); } return true; } bool block_not(Exec* exec, Block* block, int argc, AnyValue* argv, AnyValue* return_val, ControlState control_state) { (void) control_state; (void) block; (void) exec; (void) argc; *return_val = DATA_BOOL(!std_bool_from_any(&argv[0])); return true; } bool block_and(Exec* exec, Block* block, int argc, AnyValue* argv, AnyValue* return_val, ControlState control_state) { (void) control_state; (void) block; (void) exec; (void) argc; *return_val = DATA_BOOL(std_bool_from_any(&argv[0]) && std_bool_from_any(&argv[1])); return true; } bool block_or(Exec* exec, Block* block, int argc, AnyValue* argv, AnyValue* return_val, ControlState control_state) { (void) control_state; (void) block; (void) exec; (void) argc; *return_val = DATA_BOOL(std_bool_from_any(&argv[0]) || std_bool_from_any(&argv[1])); return true; } bool block_true(Exec* exec, Block* block, int argc, AnyValue* argv, AnyValue* return_val, ControlState control_state) { (void) control_state; (void) block; (void) exec; (void) argc; (void) argv; *return_val = DATA_BOOL(1); return true; } bool block_false(Exec* exec, Block* block, int argc, AnyValue* argv, AnyValue* return_val, ControlState control_state) { (void) control_state; (void) block; (void) exec; (void) argc; (void) argv; *return_val = DATA_BOOL(0); return true; } bool block_eq(Exec* exec, Block* block, int argc, AnyValue* argv, AnyValue* return_val, ControlState control_state) { (void) control_state; (void) block; (void) exec; (void) argc; if ((argv[0].type != DATA_TYPE_LITERAL && argv[0].type != DATA_TYPE_STRING) || (argv[1].type != DATA_TYPE_LITERAL && argv[1].type != DATA_TYPE_STRING)) { if (argv[0].type != argv[1].type) { *return_val = DATA_BOOL(0); return true; } } switch (argv[0].type) { case DATA_TYPE_BOOL: case DATA_TYPE_INTEGER: case DATA_TYPE_COLOR: *return_val = DATA_BOOL(argv[0].data.integer_val == argv[1].data.integer_val); break; case DATA_TYPE_FLOAT: *return_val = DATA_BOOL(argv[0].data.float_val == argv[1].data.float_val); break; case DATA_TYPE_LITERAL: *return_val = DATA_BOOL(!strcmp(argv[0].data.literal_val, argv[1].data.literal_val)); break; case DATA_TYPE_STRING: *return_val = DATA_BOOL(std_string_is_eq(argv[0].data.str_val, argv[1].data.str_val)); break; case DATA_TYPE_NOTHING: *return_val = DATA_BOOL(1); break; case DATA_TYPE_LIST: *return_val = DATA_BOOL(argv[0].data.list_val == argv[1].data.list_val); break; case DATA_TYPE_UNKNOWN: case DATA_TYPE_BLOCKDEF: case DATA_TYPE_ANY: exec_set_error(exec, block, gettext("Cannot compare type %s"), type_to_str(argv[0].type)); return false; } return true; } bool block_not_eq(Exec* exec, Block* block, int argc, AnyValue* argv, AnyValue* return_val, ControlState control_state) { if (!block_eq(exec, block, argc, argv, return_val, control_state)) return false; return_val->data.integer_val = !return_val->data.integer_val; return true; } bool block_exec_custom(Exec* exec, Block* block, int argc, AnyValue* argv, AnyValue* return_val, ControlState control_state) { (void) control_state; for (size_t i = 0; i < vector_size(exec->defined_functions); i++) { if (block->blockdef == exec->defined_functions[i].blockdef) { if (!exec_run_chain(exec, exec->defined_functions[i].run_chain, argc, argv, return_val)) return false; if (return_val->type == DATA_TYPE_LIST || return_val->type == DATA_TYPE_ANY || return_val->type == DATA_TYPE_STRING) { gc_add_temp_root(&exec->gc, (void*)return_val->data.literal_val); } return true; } } exec_set_error(exec, block, gettext("Unknown function id \"%s\""), block->blockdef->id); return false; } // Checks the arguments and returns the value from custom_argv at index if all conditions are met bool block_custom_arg(Exec* exec, Block* block, int argc, AnyValue* argv, AnyValue* return_val, ControlState control_state) { (void) argc; (void) argv; (void) control_state; for (size_t i = 0; i < vector_size(exec->defined_functions); i++) { for (size_t j = 0; j < vector_size(exec->defined_functions[i].args); j++) { DefineArgument arg = exec->defined_functions[i].args[j]; if (arg.blockdef == block->blockdef) { *return_val = exec->chain_stack[exec->chain_stack_len - 1].custom_argv[arg.arg_ind]; return true; } } } exec_set_error(exec, block, gettext("Unknown argument id \"%s\""), block->blockdef->id); return false; } // Modifies the internal state of the current code chain so that it returns early with the data written to .return_arg bool block_return(Exec* exec, Block* block, int argc, AnyValue* argv, AnyValue* return_val, ControlState control_state) { (void) control_state; (void) block; (void) argc; exec->chain_stack[exec->chain_stack_len - 1].return_arg = argv[0]; exec->chain_stack[exec->chain_stack_len - 1].is_returning = true; *return_val = DATA_NOTHING; return true; } bool block_gc_collect(Exec* exec, Block* block, int argc, AnyValue* argv, AnyValue* return_val, ControlState control_state) { (void) control_state; (void) block; (void) argc; (void) argv; gc_collect(&exec->gc); *return_val = DATA_NOTHING; return true; } #else #define MIN_ARG_COUNT(count) \ if (argc < count) { \ scrap_log(LOG_ERROR, "[LLVM] Not enough arguments! Expected: %d or more, Got: %d", count, argc); \ return false; \ } LLVMValueRef arg_to_value(Exec* exec, Block* block, FuncArg arg) { switch (arg.type) { case DATA_TYPE_LITERAL: return CONST_STRING_LITERAL(arg.data.str); case DATA_TYPE_LIST: case DATA_TYPE_STRING: case DATA_TYPE_NOTHING: case DATA_TYPE_INTEGER: case DATA_TYPE_FLOAT: case DATA_TYPE_BOOL: case DATA_TYPE_COLOR: case DATA_TYPE_ANY: return arg.data.value; case DATA_TYPE_BLOCKDEF: case DATA_TYPE_UNKNOWN: exec_set_error(exec, block, gettext("Cannot represent %s as LLVM value"), type_to_str(arg.type)); return NULL; } assert(false && "Unhandled arg_to_value"); } LLVMValueRef arg_to_bool(Exec* exec, Block* block, FuncArg arg) { switch (arg.type) { case DATA_TYPE_LITERAL: return CONST_BOOLEAN(*arg.data.str != 0); case DATA_TYPE_STRING: ; LLVMValueRef first_char = LLVMBuildLoad2(exec->builder, LLVMInt8Type(), build_call(exec, "std_string_get_data", arg.data.value), "bool_cast"); return LLVMBuildICmp(exec->builder, LLVMIntNE, first_char, LLVMConstInt(LLVMInt8Type(), 0, true), "bool_cast"); case DATA_TYPE_LIST: case DATA_TYPE_NOTHING: return CONST_BOOLEAN(0); case DATA_TYPE_INTEGER: return LLVMBuildICmp(exec->builder, LLVMIntNE, arg.data.value, CONST_INTEGER(0), "bool_cast"); case DATA_TYPE_BOOL: return arg.data.value; case DATA_TYPE_FLOAT: return LLVMBuildFCmp(exec->builder, LLVMRealONE, arg.data.value, CONST_FLOAT(0.0), "bool_cast"); case DATA_TYPE_ANY: return build_call(exec, "std_bool_from_any", arg.data.value); case DATA_TYPE_BLOCKDEF: case DATA_TYPE_UNKNOWN: case DATA_TYPE_COLOR: exec_set_error(exec, block, gettext("Cannot cast type %s into %s"), type_to_str(arg.type), type_to_str(DATA_TYPE_BOOL)); return NULL; } assert(false && "Unhandled cast to bool"); } LLVMValueRef arg_to_integer(Exec* exec, Block* block, FuncArg arg) { switch (arg.type) { case DATA_TYPE_LITERAL: return CONST_INTEGER(atoi(arg.data.str)); case DATA_TYPE_STRING: return build_call(exec, "atoi", build_call(exec, "std_string_get_data", arg.data.value)); case DATA_TYPE_LIST: case DATA_TYPE_NOTHING: return CONST_INTEGER(0); case DATA_TYPE_INTEGER: case DATA_TYPE_COLOR: return arg.data.value; case DATA_TYPE_BOOL: return LLVMBuildZExt(exec->builder, arg.data.value, LLVMInt32Type(), "int_cast"); case DATA_TYPE_FLOAT: return LLVMBuildFPToSI(exec->builder, arg.data.value, LLVMInt32Type(), "int_cast"); case DATA_TYPE_ANY: return build_call(exec, "std_integer_from_any", arg.data.value); case DATA_TYPE_BLOCKDEF: case DATA_TYPE_UNKNOWN: exec_set_error(exec, block, gettext("Cannot cast type %s into %s"), type_to_str(arg.type), type_to_str(DATA_TYPE_INTEGER)); return NULL; } assert(false && "Unhandled cast to integer"); } LLVMValueRef arg_to_float(Exec* exec, Block* block, FuncArg arg) { switch (arg.type) { case DATA_TYPE_LITERAL: return CONST_FLOAT(atof(arg.data.str)); case DATA_TYPE_STRING: return build_call(exec, "atof", build_call(exec, "std_string_get_data", arg.data.value)); case DATA_TYPE_LIST: case DATA_TYPE_NOTHING: return CONST_FLOAT(0.0); case DATA_TYPE_INTEGER: return LLVMBuildSIToFP(exec->builder, arg.data.value, LLVMDoubleType(), "float_cast"); case DATA_TYPE_BOOL: return LLVMBuildUIToFP(exec->builder, arg.data.value, LLVMDoubleType(), "float_cast"); case DATA_TYPE_FLOAT: return arg.data.value; case DATA_TYPE_ANY: return build_call(exec, "std_float_from_any", arg.data.value); case DATA_TYPE_COLOR: return LLVMBuildSIToFP(exec->builder, arg.data.value, LLVMDoubleType(), "float_cast"); case DATA_TYPE_BLOCKDEF: case DATA_TYPE_UNKNOWN: exec_set_error(exec, block, gettext("Cannot cast type %s into %s"), type_to_str(arg.type), type_to_str(DATA_TYPE_FLOAT)); return NULL; } assert(false && "Unhandled cast to float"); } LLVMValueRef arg_to_any_string(Exec* exec, Block* block, FuncArg arg) { switch (arg.type) { case DATA_TYPE_LITERAL: return CONST_STRING_LITERAL(arg.data.str); case DATA_TYPE_NOTHING: return CONST_STRING_LITERAL("nothing"); case DATA_TYPE_STRING: return arg.data.value; case DATA_TYPE_INTEGER: return build_call(exec, "std_string_from_integer", CONST_GC, arg.data.value); case DATA_TYPE_BOOL: return build_call(exec, "std_string_from_bool", CONST_GC, arg.data.value); case DATA_TYPE_FLOAT: return build_call(exec, "std_string_from_float", CONST_GC, arg.data.value); case DATA_TYPE_ANY: return build_call(exec, "std_string_from_any", CONST_GC, arg.data.value); case DATA_TYPE_LIST: return build_call(exec, "std_string_from_literal", CONST_GC, CONST_STRING_LITERAL(""), CONST_INTEGER(0)); case DATA_TYPE_COLOR: return build_call(exec, "std_string_from_color", CONST_GC, arg.data.value); case DATA_TYPE_BLOCKDEF: case DATA_TYPE_UNKNOWN: exec_set_error(exec, block, gettext("Cannot cast type %s into any string"), type_to_str(arg.type)); return NULL; } assert(false && "Unhandled cast to any string"); } LLVMValueRef arg_to_string_ref(Exec* exec, Block* block, FuncArg arg) { switch (arg.type) { case DATA_TYPE_LITERAL: return build_call(exec, "std_string_from_literal", CONST_GC, CONST_STRING_LITERAL(arg.data.str), CONST_INTEGER(strlen(arg.data.str))); case DATA_TYPE_NOTHING: return build_call(exec, "std_string_from_literal", CONST_GC, CONST_STRING_LITERAL("nothing"), CONST_INTEGER(sizeof("nothing") - 1)); case DATA_TYPE_LIST: return build_call(exec, "std_string_from_literal", CONST_GC, CONST_STRING_LITERAL(""), CONST_INTEGER(0)); case DATA_TYPE_STRING: return arg.data.value; case DATA_TYPE_INTEGER: return build_call(exec, "std_string_from_integer", CONST_GC, arg.data.value); case DATA_TYPE_BOOL: return build_call(exec, "std_string_from_bool", CONST_GC, arg.data.value); case DATA_TYPE_FLOAT: return build_call(exec, "std_string_from_float", CONST_GC, arg.data.value); case DATA_TYPE_ANY: return build_call(exec, "std_string_from_any", CONST_GC, arg.data.value); case DATA_TYPE_COLOR: return build_call(exec, "std_string_from_color", CONST_GC, arg.data.value); case DATA_TYPE_UNKNOWN: case DATA_TYPE_BLOCKDEF: exec_set_error(exec, block, gettext("Cannot cast type %s into %s"), type_to_str(arg.type), type_to_str(DATA_TYPE_STRING)); return NULL; } assert(false && "Unhandled cast to string ref"); } LLVMValueRef arg_to_color(Exec* exec, Block* block, FuncArg arg) { switch (arg.type) { case DATA_TYPE_LITERAL: ; StdColor col = std_parse_color(arg.data.str); return CONST_INTEGER(*(int*)&col); case DATA_TYPE_STRING: return build_call(exec, "std_parse_color", build_call(exec, "std_string_get_data", arg.data.value)); case DATA_TYPE_LIST: case DATA_TYPE_NOTHING: return CONST_INTEGER(0); case DATA_TYPE_INTEGER: case DATA_TYPE_COLOR: return arg.data.value; case DATA_TYPE_BOOL: return LLVMBuildSelect(exec->builder, arg.data.value, CONST_INTEGER(0xffffffff), CONST_INTEGER(0xff000000), "color_cast"); case DATA_TYPE_FLOAT: return LLVMBuildFPToSI(exec->builder, arg.data.value, LLVMInt32Type(), "color_cast"); case DATA_TYPE_ANY: return build_call(exec, "std_color_from_any", arg.data.value); case DATA_TYPE_BLOCKDEF: case DATA_TYPE_UNKNOWN: exec_set_error(exec, block, gettext("Cannot cast type %s into %s"), type_to_str(arg.type), type_to_str(DATA_TYPE_COLOR)); return NULL; } assert(false && "Unhandled cast to color"); } LLVMValueRef arg_to_list(Exec* exec, Block* block, FuncArg arg) { switch (arg.type) { case DATA_TYPE_BOOL: case DATA_TYPE_NOTHING: case DATA_TYPE_INTEGER: case DATA_TYPE_STRING: case DATA_TYPE_FLOAT: case DATA_TYPE_LITERAL: case DATA_TYPE_UNKNOWN: case DATA_TYPE_BLOCKDEF: case DATA_TYPE_COLOR: exec_set_error(exec, block, gettext("Cannot cast type %s into %s"), type_to_str(arg.type), type_to_str(DATA_TYPE_LIST)); return NULL; case DATA_TYPE_LIST: return arg.data.value; case DATA_TYPE_ANY: return build_call(exec, "std_list_from_any", CONST_GC, arg.data.value); } assert(false && "Unhandled cast to string ref"); } LLVMValueRef arg_to_any(Exec* exec, Block* block, FuncArg arg) { switch (arg.type) { case DATA_TYPE_NOTHING: return build_call_count(exec, "std_any_from_value", 2, CONST_GC, CONST_INTEGER(arg.type)); case DATA_TYPE_BOOL: case DATA_TYPE_INTEGER: case DATA_TYPE_STRING: case DATA_TYPE_FLOAT: case DATA_TYPE_LITERAL: case DATA_TYPE_LIST: case DATA_TYPE_COLOR: return build_call_count(exec, "std_any_from_value", 3, CONST_GC, CONST_INTEGER(arg.type), arg_to_value(exec, block, arg)); case DATA_TYPE_ANY: return arg.data.value; case DATA_TYPE_UNKNOWN: case DATA_TYPE_BLOCKDEF: exec_set_error(exec, block, gettext("Cannot cast type %s into %s"), type_to_str(arg.type), type_to_str(DATA_TYPE_ANY)); return NULL; } assert(false && "Unhandled cast to string ref"); } FuncArg arg_cast(Exec* exec, Block* block, FuncArg arg, DataType cast_to_type) { switch (cast_to_type) { case DATA_TYPE_LITERAL: if (arg.type == DATA_TYPE_LITERAL) return arg; assert(false && "Attempted to cast LLVM value to string literal"); case DATA_TYPE_STRING: return DATA_STRING(arg_to_string_ref(exec, block, arg)); case DATA_TYPE_INTEGER: return DATA_INTEGER(arg_to_integer(exec, block, arg)); case DATA_TYPE_BOOL: return DATA_BOOLEAN(arg_to_bool(exec, block, arg)); case DATA_TYPE_FLOAT: return DATA_FLOAT(arg_to_float(exec, block, arg)); case DATA_TYPE_LIST: return DATA_LIST(arg_to_list(exec, block, arg)); case DATA_TYPE_ANY: return DATA_ANY(arg_to_any(exec, block, arg)); case DATA_TYPE_COLOR: return DATA_COLOR(arg_to_color(exec, block, arg)); case DATA_TYPE_NOTHING: case DATA_TYPE_UNKNOWN: case DATA_TYPE_BLOCKDEF: exec_set_error(exec, block, gettext("Cannot cast type %s into %s"), type_to_str(arg.type), type_to_str(cast_to_type)); return DATA_UNKNOWN; } assert(false && "Unhandled cast to value typed"); } bool block_return(Exec* exec, Block* block, int argc, FuncArg* argv, FuncArg* return_val, ControlState control_state) { (void) control_state; (void) block; MIN_ARG_COUNT(1); LLVMBasicBlockRef current = LLVMGetInsertBlock(exec->builder); LLVMBasicBlockRef return_block = LLVMInsertBasicBlock(current, "return"); LLVMBasicBlockRef return_after_block = LLVMInsertBasicBlock(current, "return_after"); LLVMMoveBasicBlockAfter(return_after_block, current); LLVMMoveBasicBlockAfter(return_block, current); LLVMBuildBr(exec->builder, return_block); LLVMPositionBuilderAtEnd(exec->builder, return_block); build_call(exec, "gc_root_restore", CONST_GC); LLVMTypeRef data_type = LLVMTypeOf(argv[0].data.value); if (data_type == LLVMPointerType(LLVMInt8Type(), 0)) { build_call(exec, "gc_add_temp_root", CONST_GC, argv[0].data.value); } LLVMValueRef custom_return = arg_to_any(exec, block, argv[0]); if (!custom_return) return false; exec->gc_dirty = false; LLVMBuildRet(exec->builder, custom_return); LLVMPositionBuilderAtEnd(exec->builder, return_after_block); *return_val = DATA_NOTHING; return true; } bool block_custom_arg(Exec* exec, Block* block, int argc, FuncArg* argv, FuncArg* return_val, ControlState control_state) { (void) control_state; (void) argc; (void) argv; DefineFunction* func; DefineArgument* arg = get_custom_argument(exec, block->blockdef, &func); if (!arg) { exec_set_error(exec, block, gettext("Could not find function definition for argument")); return false; } if (LLVMGetBasicBlockParent(LLVMGetInsertBlock(exec->builder)) != func->func) { exec_set_error(exec, block, gettext("Function argument block used outside of function")); return false; } *return_val = DATA_ANY(arg->arg); return true; } bool block_exec_custom(Exec* exec, Block* block, int argc, FuncArg* argv, FuncArg* return_val, ControlState control_state) { (void) control_state; if (argc > 32) { exec_set_error(exec, block, gettext("Too many parameters passed into function. Got %d/32"), argc); return false; } DefineFunction* define = define_function(exec, block->blockdef); LLVMTypeRef func_type = LLVMGlobalGetValueType(define->func); LLVMValueRef func_param_list[32]; for (int i = 0; i < argc; i++) { func_param_list[i] = arg_to_any(exec, block, argv[i]); if (!func_param_list[i]) return false; } exec->gc_dirty = true; if (exec->gc_block_stack_len > 0) { exec->gc_block_stack[exec->gc_block_stack_len - 1].required = true; } *return_val = DATA_ANY(LLVMBuildCall2(exec->builder, func_type, define->func, func_param_list, argc, "")); return true; } bool block_not_eq(Exec* exec, Block* block, int argc, FuncArg* argv, FuncArg* return_val, ControlState control_state) { (void) control_state; (void) block; MIN_ARG_COUNT(2); if (argv[0].type == DATA_TYPE_LITERAL && argv[1].type == DATA_TYPE_LITERAL) { *return_val = DATA_BOOLEAN(CONST_BOOLEAN(!!strcmp(argv[0].data.str, argv[1].data.str))); return true; } FuncArg left; FuncArg right; if (argv[0].type == DATA_TYPE_LITERAL) { left = arg_cast(exec, block, argv[0], argv[1].type); if (!left.data.value) return false; right = argv[1]; } else if (argv[1].type == DATA_TYPE_LITERAL) { left = argv[0]; right = arg_cast(exec, block, argv[1], argv[0].type); if (!right.data.value) return false; } else if (argv[0].type != argv[1].type) { *return_val = DATA_BOOLEAN(CONST_BOOLEAN(1)); return true; } else { left = argv[0]; right = argv[1]; } switch (left.type) { case DATA_TYPE_NOTHING: *return_val = DATA_BOOLEAN(CONST_BOOLEAN(0)); break; case DATA_TYPE_STRING: ; LLVMValueRef eq_return = build_call(exec, "std_string_is_eq", left.data.value, right.data.value); *return_val = DATA_BOOLEAN(LLVMBuildXor(exec->builder, eq_return, CONST_BOOLEAN(1), "string_neq")); break; case DATA_TYPE_INTEGER: case DATA_TYPE_COLOR: *return_val = DATA_BOOLEAN(LLVMBuildICmp(exec->builder, LLVMIntNE, left.data.value, right.data.value, "int_neq")); break; case DATA_TYPE_BOOL: *return_val = DATA_BOOLEAN(LLVMBuildXor(exec->builder, left.data.value, right.data.value, "bool_neq")); break; case DATA_TYPE_FLOAT: *return_val = DATA_BOOLEAN(LLVMBuildFCmp(exec->builder, LLVMRealONE, left.data.value, right.data.value, "float_neq")); break; case DATA_TYPE_LIST: // Compare list pointers *return_val = DATA_BOOLEAN(LLVMBuildICmp(exec->builder, LLVMIntNE, left.data.value, right.data.value, "list_neq")); break; case DATA_TYPE_ANY: ; LLVMValueRef eq_any_return = build_call(exec, "std_any_is_eq", left.data.value, right.data.value); *return_val = DATA_BOOLEAN(LLVMBuildXor(exec->builder, eq_any_return, CONST_BOOLEAN(1), "any_neq")); break; case DATA_TYPE_UNKNOWN: case DATA_TYPE_LITERAL: case DATA_TYPE_BLOCKDEF: exec_set_error(exec, block, gettext("Cannot compare type %s"), type_to_str(left.type)); return false; } return true; } bool block_eq(Exec* exec, Block* block, int argc, FuncArg* argv, FuncArg* return_val, ControlState control_state) { (void) control_state; (void) block; MIN_ARG_COUNT(2); if (argv[0].type == DATA_TYPE_LITERAL && argv[1].type == DATA_TYPE_LITERAL) { *return_val = DATA_BOOLEAN(CONST_BOOLEAN(!strcmp(argv[0].data.str, argv[1].data.str))); return true; } FuncArg left; FuncArg right; if (argv[0].type == DATA_TYPE_LITERAL) { left = arg_cast(exec, block, argv[0], argv[1].type); if (!left.data.value) return false; right = argv[1]; } else if (argv[1].type == DATA_TYPE_LITERAL) { left = argv[0]; right = arg_cast(exec, block, argv[1], argv[0].type); if (!right.data.value) return false; } else if (argv[0].type != argv[1].type) { *return_val = DATA_BOOLEAN(CONST_BOOLEAN(0)); return true; } else { left = argv[0]; right = argv[1]; } switch (left.type) { case DATA_TYPE_NOTHING: *return_val = DATA_BOOLEAN(CONST_BOOLEAN(1)); break; case DATA_TYPE_STRING: *return_val = DATA_BOOLEAN(build_call(exec, "std_string_is_eq", left.data.value, right.data.value)); break; case DATA_TYPE_BOOL: case DATA_TYPE_INTEGER: case DATA_TYPE_COLOR: *return_val = DATA_BOOLEAN(LLVMBuildICmp(exec->builder, LLVMIntEQ, left.data.value, right.data.value, "int_eq")); break; case DATA_TYPE_FLOAT: *return_val = DATA_BOOLEAN(LLVMBuildFCmp(exec->builder, LLVMRealOEQ, left.data.value, right.data.value, "float_eq")); break; case DATA_TYPE_LIST: // Compare list pointers *return_val = DATA_BOOLEAN(LLVMBuildICmp(exec->builder, LLVMIntEQ, left.data.value, right.data.value, "list_eq")); break; case DATA_TYPE_ANY: *return_val = DATA_BOOLEAN(build_call(exec, "std_any_is_eq", left.data.value, right.data.value)); break; case DATA_TYPE_UNKNOWN: case DATA_TYPE_LITERAL: case DATA_TYPE_BLOCKDEF: exec_set_error(exec, block, gettext("Cannot compare type %s"), type_to_str(left.type)); return false; } return true; } bool block_false(Exec* exec, Block* block, int argc, FuncArg* argv, FuncArg* return_val, ControlState control_state) { (void) control_state; (void) block; (void) exec; (void) argc; (void) argv; *return_val = DATA_BOOLEAN(CONST_BOOLEAN(0)); return true; } bool block_true(Exec* exec, Block* block, int argc, FuncArg* argv, FuncArg* return_val, ControlState control_state) { (void) control_state; (void) block; (void) exec; (void) argc; (void) argv; *return_val = DATA_BOOLEAN(CONST_BOOLEAN(1)); return true; } bool block_or(Exec* exec, Block* block, int argc, FuncArg* argv, FuncArg* return_val, ControlState control_state) { (void) control_state; (void) block; MIN_ARG_COUNT(2); LLVMValueRef left = arg_to_bool(exec, block, argv[0]); if (!left) return false; LLVMValueRef right = arg_to_bool(exec, block, argv[1]); if (!right) return false; *return_val = DATA_BOOLEAN(LLVMBuildOr(exec->builder, left, right, "bool_or")); return true; } bool block_and(Exec* exec, Block* block, int argc, FuncArg* argv, FuncArg* return_val, ControlState control_state) { (void) control_state; (void) block; MIN_ARG_COUNT(2); LLVMValueRef left = arg_to_bool(exec, block, argv[0]); if (!left) return false; LLVMValueRef right = arg_to_bool(exec, block, argv[1]); if (!right) return false; *return_val = DATA_BOOLEAN(LLVMBuildAnd(exec->builder, left, right, "bool_and")); return true; } bool block_not(Exec* exec, Block* block, int argc, FuncArg* argv, FuncArg* return_val, ControlState control_state) { (void) control_state; (void) block; MIN_ARG_COUNT(1); LLVMValueRef value = arg_to_bool(exec, block, argv[0]); if (!value) return false; *return_val = DATA_BOOLEAN(LLVMBuildXor(exec->builder, value, CONST_BOOLEAN(1), "not")); return true; } bool block_more_eq(Exec* exec, Block* block, int argc, FuncArg* argv, FuncArg* return_val, ControlState control_state) { (void) control_state; (void) block; MIN_ARG_COUNT(2); if (argv[0].type != DATA_TYPE_FLOAT) { LLVMValueRef left = arg_to_integer(exec, block, argv[0]); if (!left) return false; LLVMValueRef right = arg_to_integer(exec, block, argv[1]); if (!right) return false; *return_val = DATA_BOOLEAN(LLVMBuildICmp(exec->builder, LLVMIntSGE, left, right, "int_more_or_eq")); } else { LLVMValueRef right = arg_to_float(exec, block, argv[1]); if (!right) return false; *return_val = DATA_BOOLEAN(LLVMBuildFCmp(exec->builder, LLVMRealOGE, argv[0].data.value, right, "float_more_or_eq")); } return true; } bool block_more(Exec* exec, Block* block, int argc, FuncArg* argv, FuncArg* return_val, ControlState control_state) { (void) control_state; (void) block; MIN_ARG_COUNT(2); if (argv[0].type != DATA_TYPE_FLOAT) { LLVMValueRef left = arg_to_integer(exec, block, argv[0]); if (!left) return false; LLVMValueRef right = arg_to_integer(exec, block, argv[1]); if (!right) return false; *return_val = DATA_BOOLEAN(LLVMBuildICmp(exec->builder, LLVMIntSGT, left, right, "int_more")); } else { LLVMValueRef right = arg_to_float(exec, block, argv[1]); if (!right) return false; *return_val = DATA_BOOLEAN(LLVMBuildFCmp(exec->builder, LLVMRealOGT, argv[0].data.value, right, "float_more")); } return true; } bool block_less_eq(Exec* exec, Block* block, int argc, FuncArg* argv, FuncArg* return_val, ControlState control_state) { (void) control_state; (void) block; MIN_ARG_COUNT(2); if (argv[0].type != DATA_TYPE_FLOAT) { LLVMValueRef left = arg_to_integer(exec, block, argv[0]); if (!left) return false; LLVMValueRef right = arg_to_integer(exec, block, argv[1]); if (!right) return false; *return_val = DATA_BOOLEAN(LLVMBuildICmp(exec->builder, LLVMIntSLE, left, right, "int_less_or_eq")); } else { LLVMValueRef right = arg_to_float(exec, block, argv[1]); if (!right) return false; *return_val = DATA_BOOLEAN(LLVMBuildFCmp(exec->builder, LLVMRealOLE, argv[0].data.value, right, "float_less_or_eq")); } return true; } bool block_less(Exec* exec, Block* block, int argc, FuncArg* argv, FuncArg* return_val, ControlState control_state) { (void) control_state; (void) block; MIN_ARG_COUNT(2); if (argv[0].type != DATA_TYPE_FLOAT) { LLVMValueRef left = arg_to_integer(exec, block, argv[0]); if (!left) return false; LLVMValueRef right = arg_to_integer(exec, block, argv[1]); if (!right) return false; *return_val = DATA_BOOLEAN(LLVMBuildICmp(exec->builder, LLVMIntSLT, left, right, "int_less")); } else { LLVMValueRef right = arg_to_float(exec, block, argv[1]); if (!right) return false; *return_val = DATA_BOOLEAN(LLVMBuildFCmp(exec->builder, LLVMRealOLT, argv[0].data.value, right, "float_less")); } return true; } bool block_bit_or(Exec* exec, Block* block, int argc, FuncArg* argv, FuncArg* return_val, ControlState control_state) { (void) control_state; (void) block; MIN_ARG_COUNT(2); LLVMValueRef left = arg_to_integer(exec, block, argv[0]); if (!left) return false; LLVMValueRef right = arg_to_integer(exec, block, argv[1]); if (!right) return false; *return_val = DATA_INTEGER(LLVMBuildOr(exec->builder, left, right, "or")); return true; } bool block_bit_xor(Exec* exec, Block* block, int argc, FuncArg* argv, FuncArg* return_val, ControlState control_state) { (void) control_state; (void) block; MIN_ARG_COUNT(2); LLVMValueRef left = arg_to_integer(exec, block, argv[0]); if (!left) return false; LLVMValueRef right = arg_to_integer(exec, block, argv[1]); if (!right) return false; *return_val = DATA_INTEGER(LLVMBuildXor(exec->builder, left, right, "xor")); return true; } bool block_bit_and(Exec* exec, Block* block, int argc, FuncArg* argv, FuncArg* return_val, ControlState control_state) { (void) control_state; (void) block; MIN_ARG_COUNT(2); LLVMValueRef left = arg_to_integer(exec, block, argv[0]); if (!left) return false; LLVMValueRef right = arg_to_integer(exec, block, argv[1]); if (!right) return false; *return_val = DATA_INTEGER(LLVMBuildAnd(exec->builder, left, right, "and")); return true; } bool block_bit_not(Exec* exec, Block* block, int argc, FuncArg* argv, FuncArg* return_val, ControlState control_state) { (void) control_state; (void) block; MIN_ARG_COUNT(1); LLVMValueRef integer_val = arg_to_integer(exec, block, argv[0]); if (!integer_val) return false; LLVMValueRef add_op = LLVMBuildAdd(exec->builder, integer_val, CONST_INTEGER(1), ""); *return_val = DATA_INTEGER(LLVMBuildNeg(exec->builder, add_op, "bit_not")); return true; } bool block_pi(Exec* exec, Block* block, int argc, FuncArg* argv, FuncArg* return_val, ControlState control_state) { (void) control_state; (void) block; (void) exec; (void) argc; (void) argv; *return_val = DATA_FLOAT(CONST_FLOAT(M_PI)); return true; } bool block_math(Exec* exec, Block* block, int argc, FuncArg* argv, FuncArg* return_val, ControlState control_state) { (void) control_state; (void) block; MIN_ARG_COUNT(2); if (argv[0].type != DATA_TYPE_LITERAL) return false; LLVMValueRef value = arg_to_float(exec, block, argv[1]); if (!value) return false; for (int i = 0; i < MATH_LIST_LEN; i++) { if (strcmp(argv[0].data.str, block_math_list[i])) continue; *return_val = DATA_FLOAT(build_call(exec, argv[0].data.str, value)); return true; } return false; } bool block_pow(Exec* exec, Block* block, int argc, FuncArg* argv, FuncArg* return_val, ControlState control_state) { (void) control_state; (void) block; MIN_ARG_COUNT(2); if (argv[0].type != DATA_TYPE_FLOAT) { LLVMValueRef left = arg_to_integer(exec, block, argv[0]); if (!left) return false; LLVMValueRef right = arg_to_integer(exec, block, argv[1]); if (!right) return false; *return_val = DATA_INTEGER(build_call(exec, "std_int_pow", left, right)); } else { LLVMValueRef right = arg_to_float(exec, block, argv[1]); if (!right) return false; *return_val = DATA_FLOAT(build_call(exec, "pow", argv[0].data.value, right)); } return true; } bool block_rem(Exec* exec, Block* block, int argc, FuncArg* argv, FuncArg* return_val, ControlState control_state) { (void) control_state; (void) block; MIN_ARG_COUNT(2); if (argv[0].type != DATA_TYPE_FLOAT) { LLVMValueRef left = arg_to_integer(exec, block, argv[0]); if (!left) return false; LLVMValueRef right = arg_to_integer(exec, block, argv[1]); if (!right) return false; if (!LLVMIsConstant(right)) { LLVMBasicBlockRef current_branch = LLVMGetInsertBlock(exec->builder); LLVMBasicBlockRef non_zero_branch = LLVMInsertBasicBlock(current_branch, "non_zero_cond"); LLVMBasicBlockRef zero_branch = LLVMInsertBasicBlock(current_branch, "zero_cond"); LLVMBasicBlockRef phi_branch = LLVMInsertBasicBlock(current_branch, "cond_after"); LLVMMoveBasicBlockAfter(phi_branch, current_branch); LLVMMoveBasicBlockAfter(zero_branch, current_branch); LLVMMoveBasicBlockAfter(non_zero_branch, current_branch); LLVMValueRef condition = LLVMBuildICmp(exec->builder, LLVMIntEQ, right, CONST_INTEGER(0), ""); LLVMBuildCondBr(exec->builder, condition, zero_branch, non_zero_branch); LLVMPositionBuilderAtEnd(exec->builder, non_zero_branch); LLVMValueRef out = LLVMBuildSRem(exec->builder, left, right, ""); LLVMBuildBr(exec->builder, phi_branch); LLVMPositionBuilderAtEnd(exec->builder, zero_branch); LLVMBuildBr(exec->builder, phi_branch); LLVMPositionBuilderAtEnd(exec->builder, phi_branch); *return_val = DATA_INTEGER(LLVMBuildPhi(exec->builder, LLVMInt32Type(), "div")); LLVMValueRef vals[] = { CONST_INTEGER(0), out }; LLVMBasicBlockRef blocks[] = { zero_branch, non_zero_branch }; LLVMAddIncoming(return_val->data.value, vals, blocks, ARRLEN(blocks)); } else { *return_val = DATA_INTEGER(LLVMBuildSRem(exec->builder, left, right, "rem")); } } else { LLVMValueRef right = arg_to_float(exec, block, argv[1]); if (!right) return false; *return_val = DATA_FLOAT(LLVMBuildFRem(exec->builder, argv[0].data.value, right, "rem")); } if (LLVMIsPoison(return_val->data.value)) { exec_set_error(exec, block, gettext("Division by zero")); return false; } return true; } bool block_div(Exec* exec, Block* block, int argc, FuncArg* argv, FuncArg* return_val, ControlState control_state) { (void) control_state; (void) block; MIN_ARG_COUNT(2); if (argv[0].type != DATA_TYPE_FLOAT) { LLVMValueRef left = arg_to_integer(exec, block, argv[0]); if (!left) return false; LLVMValueRef right = arg_to_integer(exec, block, argv[1]); if (!right) return false; if (!LLVMIsConstant(right)) { LLVMBasicBlockRef current_branch = LLVMGetInsertBlock(exec->builder); LLVMBasicBlockRef non_zero_branch = LLVMInsertBasicBlock(current_branch, "non_zero_cond"); LLVMBasicBlockRef zero_branch = LLVMInsertBasicBlock(current_branch, "zero_cond"); LLVMBasicBlockRef phi_branch = LLVMInsertBasicBlock(current_branch, "cond_after"); LLVMMoveBasicBlockAfter(phi_branch, current_branch); LLVMMoveBasicBlockAfter(zero_branch, current_branch); LLVMMoveBasicBlockAfter(non_zero_branch, current_branch); LLVMValueRef condition = LLVMBuildICmp(exec->builder, LLVMIntEQ, right, CONST_INTEGER(0), ""); LLVMBuildCondBr(exec->builder, condition, zero_branch, non_zero_branch); LLVMPositionBuilderAtEnd(exec->builder, non_zero_branch); LLVMValueRef out = LLVMBuildSDiv(exec->builder, left, right, ""); LLVMBuildBr(exec->builder, phi_branch); LLVMPositionBuilderAtEnd(exec->builder, zero_branch); LLVMBuildBr(exec->builder, phi_branch); LLVMPositionBuilderAtEnd(exec->builder, phi_branch); *return_val = DATA_INTEGER(LLVMBuildPhi(exec->builder, LLVMInt32Type(), "div")); LLVMValueRef vals[] = { CONST_INTEGER(0), out }; LLVMBasicBlockRef blocks[] = { zero_branch, non_zero_branch }; LLVMAddIncoming(return_val->data.value, vals, blocks, ARRLEN(blocks)); } else { *return_val = DATA_INTEGER(LLVMBuildSDiv(exec->builder, left, right, "div")); } } else { LLVMValueRef right = arg_to_float(exec, block, argv[1]); if (!right) return false; *return_val = DATA_FLOAT(LLVMBuildFDiv(exec->builder, argv[0].data.value, right, "div")); } if (LLVMIsPoison(return_val->data.value)) { exec_set_error(exec, block, gettext("Division by zero")); return false; } return true; } bool block_mult(Exec* exec, Block* block, int argc, FuncArg* argv, FuncArg* return_val, ControlState control_state) { (void) control_state; (void) block; MIN_ARG_COUNT(2); if (argv[0].type != DATA_TYPE_FLOAT) { LLVMValueRef left = arg_to_integer(exec, block, argv[0]); if (!left) return false; LLVMValueRef right = arg_to_integer(exec, block, argv[1]); if (!right) return false; *return_val = DATA_INTEGER(LLVMBuildMul(exec->builder, left, right, "mul")); } else { LLVMValueRef right = arg_to_float(exec, block, argv[1]); if (!right) return false; *return_val = DATA_FLOAT(LLVMBuildFMul(exec->builder, argv[0].data.value, right, "mul")); } return true; } bool block_minus(Exec* exec, Block* block, int argc, FuncArg* argv, FuncArg* return_val, ControlState control_state) { (void) control_state; (void) block; MIN_ARG_COUNT(2); if (argv[0].type != DATA_TYPE_FLOAT) { LLVMValueRef left = arg_to_integer(exec, block, argv[0]); if (!left) return false; LLVMValueRef right = arg_to_integer(exec, block, argv[1]); if (!right) return false; *return_val = DATA_INTEGER(LLVMBuildSub(exec->builder, left, right, "sub")); } else { LLVMValueRef right = arg_to_float(exec, block, argv[1]); if (!right) return false; *return_val = DATA_FLOAT(LLVMBuildFSub(exec->builder, argv[0].data.value, right, "sub")); } return true; } bool block_plus(Exec* exec, Block* block, int argc, FuncArg* argv, FuncArg* return_val, ControlState control_state) { (void) control_state; (void) block; MIN_ARG_COUNT(2); if (argv[0].type != DATA_TYPE_FLOAT) { LLVMValueRef left = arg_to_integer(exec, block, argv[0]); if (!left) return false; LLVMValueRef right = arg_to_integer(exec, block, argv[1]); if (!right) return false; *return_val = DATA_INTEGER(LLVMBuildAdd(exec->builder, left, right, "add")); } else { LLVMValueRef right = arg_to_float(exec, block, argv[1]); if (!right) return false; *return_val = DATA_FLOAT(LLVMBuildFAdd(exec->builder, argv[0].data.value, right, "add")); } return true; } // TODO: Make this block evaluate arguments lazily bool block_typeof(Exec* exec, Block* block, int argc, FuncArg* argv, FuncArg* return_val, ControlState control_state) { (void) control_state; (void) block; (void) exec; (void) argc; *return_val = (FuncArg) { .type = DATA_TYPE_LITERAL, .data = (FuncArgData) { .str = (char*)type_to_str(argv[0].type), }, }; return true; } bool block_convert_color(Exec* exec, Block* block, int argc, FuncArg* argv, FuncArg* return_val, ControlState control_state) { (void) control_state; (void) block; MIN_ARG_COUNT(1); LLVMValueRef value = arg_to_color(exec, block, argv[0]); if (!value) return false; *return_val = DATA_COLOR(value); return true; } bool block_convert_bool(Exec* exec, Block* block, int argc, FuncArg* argv, FuncArg* return_val, ControlState control_state) { (void) control_state; (void) block; MIN_ARG_COUNT(1); LLVMValueRef value = arg_to_bool(exec, block, argv[0]); if (!value) return false; *return_val = DATA_BOOLEAN(value); return true; } bool block_convert_str(Exec* exec, Block* block, int argc, FuncArg* argv, FuncArg* return_val, ControlState control_state) { (void) control_state; (void) block; MIN_ARG_COUNT(1); LLVMValueRef value = arg_to_string_ref(exec, block, argv[0]); if (!value) return false; *return_val = DATA_STRING(value); return true; } bool block_convert_float(Exec* exec, Block* block, int argc, FuncArg* argv, FuncArg* return_val, ControlState control_state) { (void) control_state; (void) block; MIN_ARG_COUNT(1); LLVMValueRef value = arg_to_float(exec, block, argv[0]); if (!value) return false; *return_val = DATA_FLOAT(value); return true; } bool block_convert_int(Exec* exec, Block* block, int argc, FuncArg* argv, FuncArg* return_val, ControlState control_state) { (void) control_state; (void) block; MIN_ARG_COUNT(1); LLVMValueRef value = arg_to_integer(exec, block, argv[0]); if (!value) return false; *return_val = DATA_INTEGER(value); return true; } bool block_unix_time(Exec* exec, Block* block, int argc, FuncArg* argv, FuncArg* return_val, ControlState control_state) { (void) control_state; (void) block; (void) argc; (void) argv; *return_val = DATA_INTEGER(build_call(exec, "time", LLVMConstPointerNull(LLVMPointerType(LLVMVoidType(), 0)))); return true; } bool block_length(Exec* exec, Block* block, int argc, FuncArg* argv, FuncArg* return_val, ControlState control_state) { (void) control_state; (void) block; MIN_ARG_COUNT(1); LLVMValueRef str = arg_to_string_ref(exec, block, argv[0]); if (!str) return false; *return_val = DATA_INTEGER(build_call(exec, "std_string_length", str)); return true; } bool block_substring(Exec* exec, Block* block, int argc, FuncArg* argv, FuncArg* return_val, ControlState control_state) { (void) control_state; (void) block; MIN_ARG_COUNT(3); LLVMValueRef begin = arg_to_integer(exec, block, argv[0]); if (!begin) return false; LLVMValueRef end = arg_to_integer(exec, block, argv[1]); if (!end) return false; LLVMValueRef str = arg_to_string_ref(exec, block, argv[2]); if (!str) return false; *return_val = DATA_STRING(build_call(exec, "std_string_substring", CONST_GC, begin, end, str)); return true; } bool block_letter_in(Exec* exec, Block* block, int argc, FuncArg* argv, FuncArg* return_val, ControlState control_state) { (void) control_state; (void) block; MIN_ARG_COUNT(2); LLVMValueRef target = arg_to_integer(exec, block, argv[0]); if (!target) return false; LLVMValueRef str = arg_to_string_ref(exec, block, argv[1]); if (!str) return false; *return_val = DATA_STRING(build_call(exec, "std_string_letter_in", CONST_GC, target, str)); return true; } bool block_chr(Exec* exec, Block* block, int argc, FuncArg* argv, FuncArg* return_val, ControlState control_state) { (void) control_state; (void) block; MIN_ARG_COUNT(1); LLVMValueRef value = arg_to_integer(exec, block, argv[0]); if (!value) return false; *return_val = DATA_STRING(build_call(exec, "std_string_chr", CONST_GC, value)); return true; } bool block_ord(Exec* exec, Block* block, int argc, FuncArg* argv, FuncArg* return_val, ControlState control_state) { (void) control_state; (void) block; MIN_ARG_COUNT(1); LLVMValueRef str = arg_to_any_string(exec, block, argv[0]); if (!str) return false; *return_val = DATA_INTEGER(build_call(exec, "std_string_ord", str)); return true; } bool block_join(Exec* exec, Block* block, int argc, FuncArg* argv, FuncArg* return_val, ControlState control_state) { (void) control_state; (void) block; MIN_ARG_COUNT(2); LLVMValueRef left = arg_to_string_ref(exec, block, argv[0]); if (!left) return false; LLVMValueRef right = arg_to_string_ref(exec, block, argv[1]); if (!right) return false; *return_val = DATA_STRING(build_call(exec, "std_string_join", CONST_GC, left, right)); return true; } bool block_random(Exec* exec, Block* block, int argc, FuncArg* argv, FuncArg* return_val, ControlState control_state) { (void) control_state; (void) block; MIN_ARG_COUNT(2); exec->build_random = true; LLVMValueRef min = arg_to_integer(exec, block, argv[0]); if (!min) return false; LLVMValueRef max = arg_to_integer(exec, block, argv[1]); if (!max) return false; *return_val = DATA_INTEGER(build_call(exec, "std_get_random", min, max)); return true; } bool block_get_char(Exec* exec, Block* block, int argc, FuncArg* argv, FuncArg* return_val, ControlState control_state) { (void) control_state; (void) block; (void) argc; (void) argv; *return_val = DATA_STRING(build_call(exec, "std_term_get_char", CONST_GC)); return true; } bool block_input(Exec* exec, Block* block, int argc, FuncArg* argv, FuncArg* return_val, ControlState control_state) { (void) control_state; (void) block; (void) argc; (void) argv; *return_val = DATA_STRING(build_call(exec, "std_term_get_input", CONST_GC)); return true; } bool block_term_set_clear(Exec* exec, Block* block, int argc, FuncArg* argv, FuncArg* return_val, ControlState control_state) { (void) control_state; (void) block; (void) argc; LLVMValueRef color = arg_to_color(exec, block, argv[0]); if (!color) return false; build_call(exec, "std_term_set_clear_color", color); *return_val = DATA_NOTHING; return true; } bool block_term_clear(Exec* exec, Block* block, int argc, FuncArg* argv, FuncArg* return_val, ControlState control_state) { (void) control_state; (void) block; (void) argc; (void) argv; build_call(exec, "std_term_clear"); *return_val = DATA_NOTHING; return true; } bool block_reset_color(Exec* exec, Block* block, int argc, FuncArg* argv, FuncArg* return_val, ControlState control_state) { (void) control_state; (void) block; (void) argc; (void) argv; // For some reason gcc throws a warning in release mode with constant color struct so it is passed as integer build_call(exec, "std_term_set_fg_color", CONST_INTEGER(0xffffffff)); build_call(exec, "std_term_set_bg_color", CONST_INTEGER(*(int*)&(BLACK))); *return_val = DATA_NOTHING; return true; } bool block_set_bg_color(Exec* exec, Block* block, int argc, FuncArg* argv, FuncArg* return_val, ControlState control_state) { (void) control_state; (void) block; MIN_ARG_COUNT(1); LLVMValueRef color = arg_to_color(exec, block, argv[0]); if (!color) return false; build_call(exec, "std_term_set_bg_color", color); *return_val = DATA_NOTHING; return true; } bool block_set_fg_color(Exec* exec, Block* block, int argc, FuncArg* argv, FuncArg* return_val, ControlState control_state) { (void) control_state; (void) block; MIN_ARG_COUNT(1); LLVMValueRef color = arg_to_color(exec, block, argv[0]); if (!color) return false; build_call(exec, "std_term_set_fg_color", color); *return_val = DATA_NOTHING; return true; } bool block_set_cursor(Exec* exec, Block* block, int argc, FuncArg* argv, FuncArg* return_val, ControlState control_state) { (void) control_state; (void) block; MIN_ARG_COUNT(2); LLVMValueRef x = arg_to_integer(exec, block, argv[0]); if (!x) return false; LLVMValueRef y = arg_to_integer(exec, block, argv[1]); if (!y) return false; build_call(exec, "std_term_set_cursor", x, y); *return_val = DATA_NOTHING; return true; } bool block_cursor_max_y(Exec* exec, Block* block, int argc, FuncArg* argv, FuncArg* return_val, ControlState control_state) { (void) control_state; (void) block; (void) argc; (void) argv; *return_val = DATA_INTEGER(build_call(exec, "std_term_cursor_max_y")); return true; } bool block_cursor_max_x(Exec* exec, Block* block, int argc, FuncArg* argv, FuncArg* return_val, ControlState control_state) { (void) control_state; (void) block; (void) argc; (void) argv; *return_val = DATA_INTEGER(build_call(exec, "std_term_cursor_max_x")); return true; } bool block_cursor_y(Exec* exec, Block* block, int argc, FuncArg* argv, FuncArg* return_val, ControlState control_state) { (void) control_state; (void) block; (void) argc; (void) argv; *return_val = DATA_INTEGER(build_call(exec, "std_term_cursor_y")); return true; } bool block_cursor_x(Exec* exec, Block* block, int argc, FuncArg* argv, FuncArg* return_val, ControlState control_state) { (void) control_state; (void) block; (void) argc; (void) argv; *return_val = DATA_INTEGER(build_call(exec, "std_term_cursor_x")); return true; } bool block_print(Exec* exec, Block* block, int argc, FuncArg* argv, FuncArg* return_val, ControlState control_state) { (void) control_state; (void) block; MIN_ARG_COUNT(1); switch (argv[0].type) { case DATA_TYPE_LITERAL: *return_val = DATA_INTEGER(*argv[0].data.str ? build_call(exec, "std_term_print_str", CONST_STRING_LITERAL(argv[0].data.str)) : CONST_INTEGER(0)); return true; case DATA_TYPE_STRING: *return_val = DATA_INTEGER(build_call(exec, "std_term_print_str", build_call(exec, "std_string_get_data", argv[0].data.value))); return true; case DATA_TYPE_NOTHING: *return_val = DATA_INTEGER(CONST_INTEGER(0)); return true; case DATA_TYPE_INTEGER: *return_val = DATA_INTEGER(build_call(exec, "std_term_print_integer", argv[0].data.value)); return true; case DATA_TYPE_BOOL: *return_val = DATA_INTEGER(build_call(exec, "std_term_print_bool", argv[0].data.value)); return true; case DATA_TYPE_FLOAT: *return_val = DATA_INTEGER(build_call(exec, "std_term_print_float", argv[0].data.value)); return true; case DATA_TYPE_LIST: *return_val = DATA_INTEGER(build_call(exec, "std_term_print_list", argv[0].data.value)); return true; case DATA_TYPE_ANY: *return_val = DATA_INTEGER(build_call(exec, "std_term_print_any", argv[0].data.value)); return true; case DATA_TYPE_COLOR: *return_val = DATA_INTEGER(build_call(exec, "std_term_print_color", argv[0].data.value)); return true; case DATA_TYPE_UNKNOWN: case DATA_TYPE_BLOCKDEF: exec_set_error(exec, block, gettext("Invalid type %s in print function"), type_to_str(argv[0].type)); return false; } exec_set_error(exec, block, gettext("Unhandled type %s in print function"), type_to_str(argv[0].type)); return false; } bool block_println(Exec* exec, Block* block, int argc, FuncArg* argv, FuncArg* return_val, ControlState control_state) { (void) control_state; (void) block; MIN_ARG_COUNT(1); block_print(exec, block, argc, argv, return_val, control_state); build_call(exec, "std_term_print_str", CONST_STRING_LITERAL("\n")); *return_val = DATA_INTEGER(LLVMBuildAdd(exec->builder, return_val->data.value, CONST_INTEGER(1), "add")); return true; } bool block_list_length(Exec* exec, Block* block, int argc, FuncArg* argv, FuncArg* return_val, ControlState control_state) { (void) control_state; MIN_ARG_COUNT(1); LLVMValueRef list = arg_to_list(exec, block, argv[0]); if (!list) return false; *return_val = DATA_INTEGER(build_call(exec, "std_list_length", list)); return true; } bool block_list_set(Exec* exec, Block* block, int argc, FuncArg* argv, FuncArg* return_val, ControlState control_state) { (void) control_state; MIN_ARG_COUNT(3); LLVMValueRef list = arg_to_list(exec, block, argv[0]); if (!list) return false; LLVMValueRef index = arg_to_integer(exec, block, argv[1]); if (!index) return false; if (argv[2].type == DATA_TYPE_NOTHING) { build_call_count(exec, "std_list_set", 3, list, index, CONST_INTEGER(argv[2].type)); } else { build_call_count(exec, "std_list_set", 4, list, index, CONST_INTEGER(argv[2].type), arg_to_value(exec, block, argv[2])); } *return_val = DATA_NOTHING; return true; } bool block_list_get(Exec* exec, Block* block, int argc, FuncArg* argv, FuncArg* return_val, ControlState control_state) { (void) control_state; (void) block; MIN_ARG_COUNT(2); LLVMValueRef list = arg_to_list(exec, block, argv[0]); if (!list) return false; LLVMValueRef index = arg_to_integer(exec, block, argv[1]); if (!index) return false; *return_val = DATA_ANY(build_call(exec, "std_list_get", CONST_GC, list, index)); return true; } bool block_list_add(Exec* exec, Block* block, int argc, FuncArg* argv, FuncArg* return_val, ControlState control_state) { (void) control_state; (void) block; MIN_ARG_COUNT(2); LLVMValueRef list = arg_to_list(exec, block, argv[0]); if (!list) return false; if (argv[1].type == DATA_TYPE_NOTHING) { build_call_count(exec, "std_list_add", 3, CONST_GC, list, CONST_INTEGER(argv[1].type)); } else { build_call_count(exec, "std_list_add", 4, CONST_GC, list, CONST_INTEGER(argv[1].type), arg_to_value(exec, block, argv[1])); } *return_val = DATA_NOTHING; return true; } bool block_create_list(Exec* exec, Block* block, int argc, FuncArg* argv, FuncArg* return_val, ControlState control_state) { (void) control_state; (void) block; (void) argc; (void) argv; *return_val = DATA_LIST(build_call(exec, "std_list_new", CONST_GC)); return true; } bool block_gc_collect(Exec* exec, Block* block, int argc, FuncArg* argv, FuncArg* return_val, ControlState control_state) { (void) control_state; (void) block; (void) argc; (void) argv; build_call(exec, "gc_collect", CONST_GC); *return_val = DATA_NOTHING; return true; } bool block_set_var(Exec* exec, Block* block, int argc, FuncArg* argv, FuncArg* return_val, ControlState control_state) { (void) control_state; (void) block; MIN_ARG_COUNT(2); if (argv[0].type != DATA_TYPE_LITERAL) { exec_set_error(exec, block, gettext("Invalid data type %s, expected %s"), type_to_str(argv[0].type), type_to_str(DATA_TYPE_LITERAL)); return false; } Variable* var = variable_get(exec, argv[0].data.str); if (!var) { exec_set_error(exec, block, gettext("Variable with name \"%s\" does not exist in the current scope"), argv[0].data.str); return false; } if (argv[1].type != var->value.type) { 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)); return false; } if (var->value.type == DATA_TYPE_LITERAL) { var->value = argv[1]; *return_val = argv[1]; return true; } LLVMBuildStore(exec->builder, argv[1].data.value, var->value.data.value); *return_val = argv[1]; return true; } bool block_get_var(Exec* exec, Block* block, int argc, FuncArg* argv, FuncArg* return_val, ControlState control_state) { (void) control_state; (void) block; MIN_ARG_COUNT(1); if (argv[0].type != DATA_TYPE_LITERAL) { exec_set_error(exec, block, gettext("Invalid data type %s, expected %s"), type_to_str(argv[0].type), type_to_str(DATA_TYPE_LITERAL)); return false; } Variable* var = variable_get(exec, argv[0].data.str); if (!var) { exec_set_error(exec, block, gettext("Variable with name \"%s\" does not exist in the current scope"), argv[0].data.str); return false; } if (var->value.type == DATA_TYPE_LITERAL) { *return_val = var->value; return true; } *return_val = (FuncArg) { .type = var->value.type, .data = (FuncArgData) { .value = LLVMBuildLoad2(exec->builder, var->type, var->value.data.value, "get_var"), }, }; return true; } bool block_declare_var(Exec* exec, Block* block, int argc, FuncArg* argv, FuncArg* return_val, ControlState control_state) { (void) control_state; (void) block; MIN_ARG_COUNT(2); if (block->parent) { exec_set_error(exec, block, gettext("Variable declarations are not allowed inside an argument")); return false; } if (argv[0].type != DATA_TYPE_LITERAL) { exec_set_error(exec, block, gettext("Invalid data type %s, expected %s"), type_to_str(argv[0].type), type_to_str(DATA_TYPE_LITERAL)); return false; } if (*argv[0].data.str == 0) { exec_set_error(exec, block, gettext("Cannot declare variable with empty name")); return false; } if (argv[1].type == DATA_TYPE_NOTHING) { exec_set_error(exec, block, gettext("Cannot declare a variable with zero sized type (i.e. Nothing)")); return false; } LLVMValueRef func_current = LLVMGetBasicBlockParent(LLVMGetInsertBlock(exec->builder)); LLVMValueRef func_main = LLVMGetNamedFunction(exec->module, MAIN_NAME); if (argv[1].type == DATA_TYPE_LITERAL) { Variable var = (Variable) { .type = LLVMVoidType(), .value = argv[1], .name = argv[0].data.str, }; if (exec->control_stack_len == 0 && func_current == func_main) { global_variable_add(exec, var); } else { if (!variable_stack_push(exec, block, var)) return false; } *return_val = argv[1]; return true; } LLVMTypeRef data_type = LLVMTypeOf(argv[1].data.value); Variable var = (Variable) { .type = data_type, .value = (FuncArg) { .type = argv[1].type, .data = (FuncArgData) { .value = NULL, }, }, .name = argv[0].data.str, }; if (exec->control_stack_len == 0 && func_current == func_main) { var.value.data.value = LLVMAddGlobal(exec->module, data_type, argv[0].data.str); LLVMSetInitializer(var.value.data.value, LLVMConstNull(LLVMTypeOf(argv[1].data.value))); global_variable_add(exec, var); } else { var.value.data.value = LLVMBuildAlloca(exec->builder, data_type, argv[0].data.str); variable_stack_push(exec, block, var); } LLVMBuildStore(exec->builder, argv[1].data.value, var.value.data.value); if (data_type == LLVMPointerType(LLVMInt8Type(), 0)) { build_call(exec, "gc_add_root", CONST_GC, var.value.data.value); } *return_val = argv[1]; return true; } bool block_sleep(Exec* exec, Block* block, int argc, FuncArg* argv, FuncArg* return_val, ControlState control_state) { (void) control_state; (void) block; MIN_ARG_COUNT(1); LLVMValueRef usecs = arg_to_integer(exec, block, argv[0]); if (!usecs) return false; *return_val = DATA_INTEGER(build_call(exec, "std_sleep", usecs)); return true; } bool block_while(Exec* exec, Block* block, int argc, FuncArg* argv, FuncArg* return_val, ControlState control_state) { (void) block; if (control_state == CONTROL_STATE_BEGIN) { MIN_ARG_COUNT(1); LLVMValueRef condition = arg_to_bool(exec, block, argv[0]); if (!condition) return false; LLVMBasicBlockRef control_block = LLVMGetInsertBlock(exec->builder); LLVMBasicBlockRef while_body_branch = LLVMInsertBasicBlock(control_block, "while"); LLVMBasicBlockRef while_end_branch = LLVMInsertBasicBlock(control_block, "while_end"); LLVMMoveBasicBlockAfter(while_end_branch, control_block); LLVMMoveBasicBlockAfter(while_body_branch, control_block); LLVMBuildCondBr(exec->builder, condition, while_body_branch, while_end_branch); LLVMPositionBuilderAtEnd(exec->builder, while_body_branch); if (!build_gc_root_begin(exec, block)) return false; control_data_stack_push_data(control_block, LLVMBasicBlockRef); control_data_stack_push_data(while_end_branch, LLVMBasicBlockRef); } else if (control_state == CONTROL_STATE_END) { LLVMBasicBlockRef control_block, while_end_branch; control_data_stack_pop_data(while_end_branch, LLVMBasicBlockRef); control_data_stack_pop_data(control_block, LLVMBasicBlockRef); if (!build_gc_root_end(exec, block)) return false; LLVMBuildBr(exec->builder, control_block); LLVMPositionBuilderAtEnd(exec->builder, while_end_branch); *return_val = DATA_BOOLEAN(CONST_BOOLEAN(1)); } else { exec_set_error(exec, block, "Invalid control state"); return false; } return true; } bool block_repeat(Exec* exec, Block* block, int argc, FuncArg* argv, FuncArg* return_val, ControlState control_state) { (void) block; if (control_state == CONTROL_STATE_BEGIN) { MIN_ARG_COUNT(1); LLVMValueRef counter = arg_to_integer(exec, block, argv[0]); if (!counter) return false; LLVMBasicBlockRef current = LLVMGetInsertBlock(exec->builder); LLVMBasicBlockRef repeat_branch = LLVMInsertBasicBlock(current, "repeat"); LLVMBasicBlockRef repeat_body_branch = LLVMInsertBasicBlock(current, "repeat_body"); LLVMBasicBlockRef repeat_end_branch = LLVMInsertBasicBlock(current, "repeat_end"); LLVMMoveBasicBlockAfter(repeat_end_branch, current); LLVMMoveBasicBlockAfter(repeat_body_branch, current); LLVMMoveBasicBlockAfter(repeat_branch, current); LLVMBuildBr(exec->builder, repeat_branch); LLVMPositionBuilderAtEnd(exec->builder, repeat_branch); LLVMValueRef phi_node = LLVMBuildPhi(exec->builder, LLVMInt32Type(), "repeat_phi"); LLVMValueRef index = LLVMBuildSub(exec->builder, phi_node, CONST_INTEGER(1), "repeat_index_sub"); LLVMValueRef index_test = LLVMBuildICmp(exec->builder, LLVMIntSLT, index, CONST_INTEGER(0), "repeat_loop_check"); LLVMBuildCondBr(exec->builder, index_test, repeat_end_branch, repeat_body_branch); LLVMValueRef vals[] = { counter }; LLVMBasicBlockRef blocks[] = { current }; LLVMAddIncoming(phi_node, vals, blocks, ARRLEN(blocks)); LLVMPositionBuilderAtEnd(exec->builder, repeat_body_branch); if (!build_gc_root_begin(exec, block)) return false; control_data_stack_push_data(phi_node, LLVMValueRef); control_data_stack_push_data(vals[0], LLVMValueRef); control_data_stack_push_data(index, LLVMValueRef); control_data_stack_push_data(repeat_branch, LLVMBasicBlockRef); control_data_stack_push_data(repeat_end_branch, LLVMBasicBlockRef); } else if (control_state == CONTROL_STATE_END) { LLVMBasicBlockRef current = LLVMGetInsertBlock(exec->builder); LLVMBasicBlockRef loop_end, loop; LLVMValueRef phi_node, index, start_index; control_data_stack_pop_data(loop_end, LLVMBasicBlockRef); control_data_stack_pop_data(loop, LLVMBasicBlockRef); control_data_stack_pop_data(index, LLVMValueRef); control_data_stack_pop_data(start_index, LLVMValueRef); control_data_stack_pop_data(phi_node, LLVMValueRef); if (!build_gc_root_end(exec, block)) return false; LLVMBuildBr(exec->builder, loop); LLVMValueRef vals[] = { index }; LLVMBasicBlockRef blocks[] = { current }; LLVMAddIncoming(phi_node, vals, blocks, ARRLEN(blocks)); LLVMPositionBuilderAtEnd(exec->builder, loop_end); *return_val = DATA_BOOLEAN(LLVMBuildICmp(exec->builder, LLVMIntSGT, start_index, CONST_INTEGER(0), "")); } else { exec_set_error(exec, block, "Invalid control state"); return false; } return true; } bool block_else(Exec* exec, Block* block, int argc, FuncArg* argv, FuncArg* return_val, ControlState control_state) { (void) block; if (control_state == CONTROL_STATE_BEGIN) { MIN_ARG_COUNT(1); LLVMValueRef value = arg_to_bool(exec, block, argv[0]); if (!value) return false; LLVMBasicBlockRef current_branch = LLVMGetInsertBlock(exec->builder); LLVMBasicBlockRef else_branch = LLVMInsertBasicBlock(current_branch, "else"); LLVMBasicBlockRef end_branch = LLVMInsertBasicBlock(current_branch, "end_else"); LLVMMoveBasicBlockAfter(end_branch, current_branch); LLVMMoveBasicBlockAfter(else_branch, current_branch); LLVMBuildCondBr(exec->builder, value, end_branch, else_branch); LLVMPositionBuilderAtEnd(exec->builder, else_branch); if (!build_gc_root_begin(exec, block)) return false; control_data_stack_push_data(end_branch, LLVMBasicBlockRef); } else if (control_state == CONTROL_STATE_END) { LLVMBasicBlockRef end_branch; control_data_stack_pop_data(end_branch, LLVMBasicBlockRef); if (!build_gc_root_end(exec, block)) return false; LLVMBuildBr(exec->builder, end_branch); LLVMPositionBuilderAtEnd(exec->builder, end_branch); *return_val = DATA_BOOLEAN(CONST_BOOLEAN(1)); } else { exec_set_error(exec, block, "Invalid control state"); return false; } return true; } bool block_else_if(Exec* exec, Block* block, int argc, FuncArg* argv, FuncArg* return_val, ControlState control_state) { (void) block; if (control_state == CONTROL_STATE_BEGIN) { MIN_ARG_COUNT(2); LLVMValueRef prev_val = arg_to_bool(exec, block, argv[0]); if (!prev_val) return false; LLVMValueRef condition = arg_to_bool(exec, block, argv[1]); if (!condition) return false; LLVMBasicBlockRef current_branch = LLVMGetInsertBlock(exec->builder); LLVMBasicBlockRef else_if_check_branch = LLVMInsertBasicBlock(current_branch, "else_if_check"); LLVMBasicBlockRef else_if_branch = LLVMInsertBasicBlock(current_branch, "else_if"); LLVMBasicBlockRef else_if_fail_branch = LLVMInsertBasicBlock(current_branch, "else_if_fail"); LLVMBasicBlockRef end_branch = LLVMInsertBasicBlock(current_branch, "end_else_if"); LLVMMoveBasicBlockAfter(end_branch, current_branch); LLVMMoveBasicBlockAfter(else_if_fail_branch, current_branch); LLVMMoveBasicBlockAfter(else_if_branch, current_branch); LLVMMoveBasicBlockAfter(else_if_check_branch, current_branch); LLVMBuildCondBr(exec->builder, prev_val, end_branch, else_if_check_branch); control_data_stack_push_data(current_branch, LLVMBasicBlockRef); LLVMPositionBuilderAtEnd(exec->builder, else_if_check_branch); LLVMBuildCondBr(exec->builder, condition, else_if_branch, else_if_fail_branch); LLVMPositionBuilderAtEnd(exec->builder, else_if_fail_branch); LLVMBuildBr(exec->builder, end_branch); LLVMPositionBuilderAtEnd(exec->builder, else_if_branch); if (!build_gc_root_begin(exec, block)) return false; control_data_stack_push_data(else_if_fail_branch, LLVMBasicBlockRef); control_data_stack_push_data(end_branch, LLVMBasicBlockRef); } else if (control_state == CONTROL_STATE_END) { LLVMBasicBlockRef else_if_branch = LLVMGetInsertBlock(exec->builder); LLVMBasicBlockRef top_branch, fail_branch, end_branch; control_data_stack_pop_data(end_branch, LLVMBasicBlockRef); control_data_stack_pop_data(fail_branch, LLVMBasicBlockRef); control_data_stack_pop_data(top_branch, LLVMBasicBlockRef); if (!build_gc_root_end(exec, block)) return false; LLVMBuildBr(exec->builder, end_branch); LLVMPositionBuilderAtEnd(exec->builder, end_branch); *return_val = DATA_BOOLEAN(LLVMBuildPhi(exec->builder, LLVMInt1Type(), "")); LLVMValueRef vals[] = { CONST_BOOLEAN(1), CONST_BOOLEAN(1), CONST_BOOLEAN(0) }; LLVMBasicBlockRef blocks[] = { top_branch, else_if_branch, fail_branch }; LLVMAddIncoming(return_val->data.value, vals, blocks, ARRLEN(blocks)); } else { exec_set_error(exec, block, "Invalid control state"); return false; } return true; } bool block_if(Exec* exec, Block* block, int argc, FuncArg* argv, FuncArg* return_val, ControlState control_state) { (void) block; if (control_state == CONTROL_STATE_BEGIN) { MIN_ARG_COUNT(1); LLVMValueRef condition = arg_to_bool(exec, block, argv[0]); if (!condition) return false; LLVMBasicBlockRef current_branch = LLVMGetInsertBlock(exec->builder); LLVMBasicBlockRef then_branch = LLVMInsertBasicBlock(current_branch, "if_cond"); // 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 LLVMBasicBlockRef fail_branch = LLVMInsertBasicBlock(current_branch, "if_fail"); LLVMBasicBlockRef end_branch = LLVMInsertBasicBlock(current_branch, "end_if"); LLVMMoveBasicBlockAfter(end_branch, current_branch); LLVMMoveBasicBlockAfter(fail_branch, current_branch); LLVMMoveBasicBlockAfter(then_branch, current_branch); LLVMBuildCondBr(exec->builder, condition, then_branch, fail_branch); LLVMPositionBuilderAtEnd(exec->builder, fail_branch); LLVMBuildBr(exec->builder, end_branch); LLVMPositionBuilderAtEnd(exec->builder, then_branch); if (!build_gc_root_begin(exec, block)) return false; control_data_stack_push_data(fail_branch, LLVMBasicBlockRef); control_data_stack_push_data(end_branch, LLVMBasicBlockRef); } else if (control_state == CONTROL_STATE_END) { LLVMBasicBlockRef then_branch = LLVMGetInsertBlock(exec->builder); LLVMBasicBlockRef fail_branch, end_branch; control_data_stack_pop_data(end_branch, LLVMBasicBlockRef); control_data_stack_pop_data(fail_branch, LLVMBasicBlockRef); if (!build_gc_root_end(exec, block)) return false; LLVMBuildBr(exec->builder, end_branch); LLVMPositionBuilderAtEnd(exec->builder, end_branch); *return_val = DATA_BOOLEAN(LLVMBuildPhi(exec->builder, LLVMInt1Type(), "")); LLVMValueRef vals[] = { CONST_BOOLEAN(1), CONST_BOOLEAN(0) }; LLVMBasicBlockRef blocks[] = { then_branch, fail_branch }; LLVMAddIncoming(return_val->data.value, vals, blocks, ARRLEN(blocks)); } else { exec_set_error(exec, block, "Invalid control state"); return false; } return true; } bool block_loop(Exec* exec, Block* block, int argc, FuncArg* argv, FuncArg* return_val, ControlState control_state) { (void) block; (void) argc; (void) argv; if (control_state == CONTROL_STATE_BEGIN) { LLVMBasicBlockRef current = LLVMGetInsertBlock(exec->builder); LLVMBasicBlockRef loop = LLVMInsertBasicBlock(current, "loop"); LLVMBasicBlockRef loop_end = LLVMInsertBasicBlock(current, "loop_end"); LLVMMoveBasicBlockAfter(loop_end, current); LLVMMoveBasicBlockAfter(loop, current); LLVMBuildBr(exec->builder, loop); LLVMPositionBuilderAtEnd(exec->builder, loop); if (!build_gc_root_begin(exec, block)) return false; control_data_stack_push_data(loop, LLVMBasicBlockRef); control_data_stack_push_data(loop_end, LLVMBasicBlockRef); } else if (control_state == CONTROL_STATE_END) { LLVMBasicBlockRef loop; LLVMBasicBlockRef loop_end; control_data_stack_pop_data(loop_end, LLVMBasicBlockRef); control_data_stack_pop_data(loop, LLVMBasicBlockRef); if (!build_gc_root_end(exec, block)) return false; LLVMBuildBr(exec->builder, loop); LLVMPositionBuilderAtEnd(exec->builder, loop_end); *return_val = DATA_BOOLEAN(CONST_BOOLEAN(0)); } else { exec_set_error(exec, block, "Invalid control state"); return false; } return true; } bool block_do_nothing(Exec* exec, Block* block, int argc, FuncArg* argv, FuncArg* return_val, ControlState control_state) { (void) block; (void) argc; (void) argv; if (control_state == CONTROL_STATE_BEGIN) { if (!build_gc_root_begin(exec, block)) return false; } else if (control_state == CONTROL_STATE_END) { if (!build_gc_root_end(exec, block)) return false; } else { exec_set_error(exec, block, "Invalid control state"); return false; } *return_val = DATA_NOTHING; return true; } bool block_noop(Exec* exec, Block* block, int argc, FuncArg* argv, FuncArg* return_val, ControlState control_state) { (void) control_state; (void) block; (void) exec; (void) argc; (void) argv; *return_val = DATA_NOTHING; return true; } bool block_define_block(Exec* exec, Block* block, int argc, FuncArg* argv, FuncArg* return_val, ControlState control_state) { (void) control_state; (void) block; MIN_ARG_COUNT(1); if (argv[0].type != DATA_TYPE_BLOCKDEF) { exec_set_error(exec, block, gettext("Invalid data type %s, expected %s"), type_to_str(argv[0].type), type_to_str(DATA_TYPE_BLOCKDEF)); return false; } DefineFunction* define = define_function(exec, argv[0].data.blockdef); LLVMBasicBlockRef entry = LLVMAppendBasicBlock(define->func, "entry"); LLVMPositionBuilderAtEnd(exec->builder, entry); exec->gc_value = LLVMBuildLoad2(exec->builder, LLVMInt64Type(), LLVMGetNamedGlobal(exec->module, "gc"), "get_gc"); build_call(exec, "gc_root_save", CONST_GC); if (!build_gc_root_begin(exec, NULL)) return false; *return_val = DATA_NOTHING; return true; } bool block_on_start(Exec* exec, Block* block, int argc, FuncArg* argv, FuncArg* return_val, ControlState control_state) { (void) control_state; (void) block; (void) argc; (void) argv; LLVMValueRef main_func = LLVMGetNamedFunction(exec->module, MAIN_NAME); LLVMBasicBlockRef last_block = LLVMGetLastBasicBlock(main_func); LLVMPositionBuilderAtEnd(exec->builder, last_block); *return_val = DATA_NOTHING; return true; } #endif // USE_INTERPRETER // Creates and registers blocks (commands) for the Vm/Exec virtual machine void register_blocks(Vm* vm) { BlockCategory cat; cat = block_category_new(gettext("Control"), (Color) CATEGORY_CONTROL_COLOR); BlockCategory* cat_control = block_category_register(cat); cat = block_category_new(gettext("Terminal"), (Color) CATEGORY_TERMINAL_COLOR); BlockCategory* cat_terminal = block_category_register(cat); cat = block_category_new(gettext("Math"), (Color) CATEGORY_MATH_COLOR); BlockCategory* cat_math = block_category_register(cat); cat = block_category_new(gettext("Logic"), (Color) CATEGORY_LOGIC_COLOR); BlockCategory* cat_logic = block_category_register(cat); cat = block_category_new(gettext("Data"), (Color) CATEGORY_DATA_COLOR); BlockCategory* cat_data = block_category_register(cat); cat = block_category_new(gettext("Misc."), (Color) CATEGORY_MISC_COLOR); BlockCategory* cat_misc = block_category_register(cat); BlockdefImage term_img = (BlockdefImage) { .image_ptr = &assets.textures.icon_term, .image_color = (BlockdefColor) { 0xff, 0xff, 0xff, 0xff }, }; BlockdefImage list_img = (BlockdefImage) { .image_ptr = &assets.textures.icon_list, .image_color = (BlockdefColor) { 0xff, 0xff, 0xff, 0xff }, }; Blockdef* sc_end = blockdef_new("end", BLOCKTYPE_END, (BlockdefColor) { 0x77, 0x77, 0x77, 0xff }, block_noop); blockdef_add_text(sc_end, gettext("End")); blockdef_register(vm, sc_end); Blockdef* on_start = blockdef_new("on_start", BLOCKTYPE_HAT, (BlockdefColor) { 0xff, 0x77, 0x00, 0xFF }, block_on_start); blockdef_add_text(on_start, gettext("When")); blockdef_add_image(on_start, (BlockdefImage) { .image_ptr = &assets.textures.button_run, .image_color = (BlockdefColor) { 0x60, 0xff, 0x00, 0xff } }); blockdef_add_text(on_start, gettext("clicked")); blockdef_register(vm, on_start); block_category_add_blockdef(cat_control, on_start); block_category_add_label(cat_control, gettext("Conditionals"), (Color) CATEGORY_CONTROL_COLOR); Blockdef* sc_if = blockdef_new("if", BLOCKTYPE_CONTROL, (BlockdefColor) CATEGORY_CONTROL_COLOR, block_if); blockdef_add_text(sc_if, gettext("If")); blockdef_add_argument(sc_if, "", gettext("cond."), BLOCKCONSTR_UNLIMITED); blockdef_add_text(sc_if, gettext(", then")); blockdef_register(vm, sc_if); block_category_add_blockdef(cat_control, sc_if); Blockdef* sc_else_if = blockdef_new("else_if", BLOCKTYPE_CONTROLEND, (BlockdefColor) CATEGORY_CONTROL_COLOR, block_else_if); blockdef_add_text(sc_else_if, gettext("Else if")); blockdef_add_argument(sc_else_if, "", gettext("cond."), BLOCKCONSTR_UNLIMITED); blockdef_add_text(sc_else_if, gettext(", then")); blockdef_register(vm, sc_else_if); block_category_add_blockdef(cat_control, sc_else_if); Blockdef* sc_else = blockdef_new("else", BLOCKTYPE_CONTROLEND, (BlockdefColor) CATEGORY_CONTROL_COLOR, block_else); blockdef_add_text(sc_else, gettext("Else")); blockdef_register(vm, sc_else); block_category_add_blockdef(cat_control, sc_else); block_category_add_label(cat_control, gettext("Loops"), (Color) CATEGORY_CONTROL_COLOR); Blockdef* sc_loop = blockdef_new("loop", BLOCKTYPE_CONTROL, (BlockdefColor) CATEGORY_CONTROL_COLOR, block_loop); blockdef_add_text(sc_loop, gettext("Loop")); blockdef_register(vm, sc_loop); block_category_add_blockdef(cat_control, sc_loop); Blockdef* sc_repeat = blockdef_new("repeat", BLOCKTYPE_CONTROL, (BlockdefColor) CATEGORY_CONTROL_COLOR, block_repeat); blockdef_add_text(sc_repeat, gettext("Repeat")); blockdef_add_argument(sc_repeat, "10", "0", BLOCKCONSTR_UNLIMITED); blockdef_add_text(sc_repeat, gettext("times")); blockdef_register(vm, sc_repeat); block_category_add_blockdef(cat_control, sc_repeat); Blockdef* sc_while = blockdef_new("while", BLOCKTYPE_CONTROL, (BlockdefColor) CATEGORY_CONTROL_COLOR, block_while); blockdef_add_text(sc_while, gettext("While")); blockdef_add_argument(sc_while, "", gettext("cond."), BLOCKCONSTR_UNLIMITED); blockdef_register(vm, sc_while); block_category_add_blockdef(cat_control, sc_while); block_category_add_label(cat_control, gettext("Functions"), (Color) { 0x99, 0x00, 0xff, 0xff }); Blockdef* sc_define_block = blockdef_new("define_block", BLOCKTYPE_HAT, (BlockdefColor) { 0x99, 0x00, 0xff, 0xff }, block_define_block); blockdef_add_image(sc_define_block, (BlockdefImage) { .image_ptr = &assets.textures.icon_special, .image_color = (BlockdefColor) { 0xff, 0xff, 0xff, 0xff } }); blockdef_add_text(sc_define_block, gettext("Define")); blockdef_add_blockdef_editor(sc_define_block); blockdef_register(vm, sc_define_block); block_category_add_blockdef(cat_control, sc_define_block); Blockdef* sc_return = blockdef_new("return", BLOCKTYPE_NORMAL, (BlockdefColor) { 0x99, 0x00, 0xff, 0xff }, block_return); blockdef_add_image(sc_return, (BlockdefImage) { .image_ptr = &assets.textures.icon_special, .image_color = (BlockdefColor) { 0xff, 0xff, 0xff, 0xff } }); blockdef_add_text(sc_return, gettext("Return")); blockdef_add_argument(sc_return, "", gettext("any"), BLOCKCONSTR_UNLIMITED); blockdef_register(vm, sc_return); block_category_add_blockdef(cat_control, sc_return); block_category_add_label(cat_terminal, gettext("Input/Output"), (Color) CATEGORY_TERMINAL_COLOR); Blockdef* sc_print = blockdef_new("print", BLOCKTYPE_NORMAL, (BlockdefColor) CATEGORY_TERMINAL_COLOR, block_print); blockdef_add_image(sc_print, term_img); blockdef_add_text(sc_print, gettext("Print")); blockdef_add_argument(sc_print, gettext("Hello, scrap!"), gettext("Abc"), BLOCKCONSTR_UNLIMITED); blockdef_register(vm, sc_print); block_category_add_blockdef(cat_terminal, sc_print); Blockdef* sc_println = blockdef_new("println", BLOCKTYPE_NORMAL, (BlockdefColor) CATEGORY_TERMINAL_COLOR, block_println); blockdef_add_image(sc_println, term_img); blockdef_add_text(sc_println, gettext("Print line")); blockdef_add_argument(sc_println, gettext("Hello, scrap!"), gettext("Abc"), BLOCKCONSTR_UNLIMITED); blockdef_register(vm, sc_println); block_category_add_blockdef(cat_terminal, sc_println); Blockdef* sc_input = blockdef_new("input", BLOCKTYPE_NORMAL, (BlockdefColor) CATEGORY_TERMINAL_COLOR, block_input); blockdef_add_image(sc_input, term_img); blockdef_add_text(sc_input, gettext("Get input")); blockdef_register(vm, sc_input); block_category_add_blockdef(cat_terminal, sc_input); Blockdef* sc_char = blockdef_new("get_char", BLOCKTYPE_NORMAL, (BlockdefColor) CATEGORY_TERMINAL_COLOR, block_get_char); blockdef_add_image(sc_char, term_img); blockdef_add_text(sc_char, gettext("Get char")); blockdef_register(vm, sc_char); block_category_add_blockdef(cat_terminal, sc_char); block_category_add_label(cat_terminal, gettext("Cursor"), (Color) CATEGORY_TERMINAL_COLOR); Blockdef* sc_set_cursor = blockdef_new("set_cursor", BLOCKTYPE_NORMAL, (BlockdefColor) CATEGORY_TERMINAL_COLOR, block_set_cursor); blockdef_add_image(sc_set_cursor, term_img); blockdef_add_text(sc_set_cursor, gettext("Set cursor X:")); blockdef_add_argument(sc_set_cursor, "0", "0", BLOCKCONSTR_UNLIMITED); blockdef_add_text(sc_set_cursor, gettext("Y:")); blockdef_add_argument(sc_set_cursor, "0", "0", BLOCKCONSTR_UNLIMITED); blockdef_register(vm, sc_set_cursor); block_category_add_blockdef(cat_terminal, sc_set_cursor); Blockdef* sc_cursor_x = blockdef_new("cursor_x", BLOCKTYPE_NORMAL, (BlockdefColor) CATEGORY_TERMINAL_COLOR, block_cursor_x); blockdef_add_image(sc_cursor_x, term_img); blockdef_add_text(sc_cursor_x, gettext("Cursor X")); blockdef_register(vm, sc_cursor_x); block_category_add_blockdef(cat_terminal, sc_cursor_x); Blockdef* sc_cursor_y = blockdef_new("cursor_y", BLOCKTYPE_NORMAL, (BlockdefColor) CATEGORY_TERMINAL_COLOR, block_cursor_y); blockdef_add_image(sc_cursor_y, term_img); blockdef_add_text(sc_cursor_y, gettext("Cursor Y")); blockdef_register(vm, sc_cursor_y); block_category_add_blockdef(cat_terminal, sc_cursor_y); Blockdef* sc_cursor_max_x = blockdef_new("cursor_max_x", BLOCKTYPE_NORMAL, (BlockdefColor) CATEGORY_TERMINAL_COLOR, block_cursor_max_x); blockdef_add_image(sc_cursor_max_x, term_img); blockdef_add_text(sc_cursor_max_x, gettext("Terminal width")); blockdef_register(vm, sc_cursor_max_x); block_category_add_blockdef(cat_terminal, sc_cursor_max_x); Blockdef* sc_cursor_max_y = blockdef_new("cursor_max_y", BLOCKTYPE_NORMAL, (BlockdefColor) CATEGORY_TERMINAL_COLOR, block_cursor_max_y); blockdef_add_image(sc_cursor_max_y, term_img); blockdef_add_text(sc_cursor_max_y, gettext("Terminal height")); blockdef_register(vm, sc_cursor_max_y); block_category_add_blockdef(cat_terminal, sc_cursor_max_y); block_category_add_label(cat_terminal, gettext("Colors"), (Color) CATEGORY_TERMINAL_COLOR); Blockdef* sc_set_fg_color = blockdef_new("set_fg_color", BLOCKTYPE_NORMAL, (BlockdefColor) CATEGORY_TERMINAL_COLOR, block_set_fg_color); blockdef_add_image(sc_set_fg_color, term_img); blockdef_add_text(sc_set_fg_color, gettext("Set text color")); blockdef_add_color_input(sc_set_fg_color, (BlockdefColor) { 0xff, 0xff, 0xff, 0xff }); blockdef_register(vm, sc_set_fg_color); block_category_add_blockdef(cat_terminal, sc_set_fg_color); Blockdef* sc_set_bg_color = blockdef_new("set_bg_color", BLOCKTYPE_NORMAL, (BlockdefColor) CATEGORY_TERMINAL_COLOR, block_set_bg_color); blockdef_add_image(sc_set_bg_color, term_img); blockdef_add_text(sc_set_bg_color, gettext("Set background color")); blockdef_add_color_input(sc_set_bg_color, (BlockdefColor) { 0x30, 0x30, 0x30, 0xff }); blockdef_register(vm, sc_set_bg_color); block_category_add_blockdef(cat_terminal, sc_set_bg_color); Blockdef* sc_reset_color = blockdef_new("reset_color", BLOCKTYPE_NORMAL, (BlockdefColor) CATEGORY_TERMINAL_COLOR, block_reset_color); blockdef_add_image(sc_reset_color, term_img); blockdef_add_text(sc_reset_color, gettext("Reset color")); blockdef_register(vm, sc_reset_color); block_category_add_blockdef(cat_terminal, sc_reset_color); Blockdef* sc_term_clear = blockdef_new("term_clear", BLOCKTYPE_NORMAL, (BlockdefColor) CATEGORY_TERMINAL_COLOR, block_term_clear); blockdef_add_image(sc_term_clear, term_img); blockdef_add_text(sc_term_clear, gettext("Clear terminal")); blockdef_register(vm, sc_term_clear); block_category_add_blockdef(cat_terminal, sc_term_clear); Blockdef* sc_term_set_clear = blockdef_new("term_set_clear", BLOCKTYPE_NORMAL, (BlockdefColor) CATEGORY_TERMINAL_COLOR, block_term_set_clear); blockdef_add_image(sc_term_set_clear, term_img); blockdef_add_text(sc_term_set_clear, gettext("Set clear color")); blockdef_add_color_input(sc_term_set_clear, (BlockdefColor) { 0x00, 0x00, 0x00, 0xff }); blockdef_register(vm, sc_term_set_clear); block_category_add_blockdef(cat_terminal, sc_term_set_clear); Blockdef* sc_plus = blockdef_new("plus", BLOCKTYPE_NORMAL, (BlockdefColor) CATEGORY_MATH_COLOR, block_plus); blockdef_add_argument(sc_plus, "9", "0", BLOCKCONSTR_UNLIMITED); blockdef_add_text(sc_plus, "+"); blockdef_add_argument(sc_plus, "10", "0", BLOCKCONSTR_UNLIMITED); blockdef_register(vm, sc_plus); block_category_add_blockdef(cat_math, sc_plus); Blockdef* sc_minus = blockdef_new("minus", BLOCKTYPE_NORMAL, (BlockdefColor) CATEGORY_MATH_COLOR, block_minus); blockdef_add_argument(sc_minus, "9", "0", BLOCKCONSTR_UNLIMITED); blockdef_add_text(sc_minus, "-"); blockdef_add_argument(sc_minus, "10", "0", BLOCKCONSTR_UNLIMITED); blockdef_register(vm, sc_minus); block_category_add_blockdef(cat_math, sc_minus); Blockdef* sc_mult = blockdef_new("mult", BLOCKTYPE_NORMAL, (BlockdefColor) CATEGORY_MATH_COLOR, block_mult); blockdef_add_argument(sc_mult, "9", "0", BLOCKCONSTR_UNLIMITED); blockdef_add_text(sc_mult, "*"); blockdef_add_argument(sc_mult, "10", "0", BLOCKCONSTR_UNLIMITED); blockdef_register(vm, sc_mult); block_category_add_blockdef(cat_math, sc_mult); Blockdef* sc_div = blockdef_new("div", BLOCKTYPE_NORMAL, (BlockdefColor) CATEGORY_MATH_COLOR, block_div); blockdef_add_argument(sc_div, "39", "0", BLOCKCONSTR_UNLIMITED); blockdef_add_text(sc_div, "/"); blockdef_add_argument(sc_div, "5", "0", BLOCKCONSTR_UNLIMITED); blockdef_register(vm, sc_div); block_category_add_blockdef(cat_math, sc_div); Blockdef* sc_rem = blockdef_new("rem", BLOCKTYPE_NORMAL, (BlockdefColor) CATEGORY_MATH_COLOR, block_rem); blockdef_add_argument(sc_rem, "39", "0", BLOCKCONSTR_UNLIMITED); blockdef_add_text(sc_rem, "%"); blockdef_add_argument(sc_rem, "5", "0", BLOCKCONSTR_UNLIMITED); blockdef_register(vm, sc_rem); block_category_add_blockdef(cat_math, sc_rem); Blockdef* sc_pow = blockdef_new("pow", BLOCKTYPE_NORMAL, (BlockdefColor) CATEGORY_MATH_COLOR, block_pow); blockdef_add_text(sc_pow, gettext("Pow")); blockdef_add_argument(sc_pow, "5", "0", BLOCKCONSTR_UNLIMITED); blockdef_add_argument(sc_pow, "5", "0", BLOCKCONSTR_UNLIMITED); blockdef_register(vm, sc_pow); block_category_add_blockdef(cat_math, sc_pow); Blockdef* sc_math = blockdef_new("math", BLOCKTYPE_NORMAL, (BlockdefColor) CATEGORY_MATH_COLOR, block_math); blockdef_add_dropdown(sc_math, DROPDOWN_SOURCE_LISTREF, math_list_access); blockdef_add_argument(sc_math, "", "0.0", BLOCKCONSTR_UNLIMITED); blockdef_register(vm, sc_math); block_category_add_blockdef(cat_math, sc_math); Blockdef* sc_pi = blockdef_new("pi", BLOCKTYPE_NORMAL, (BlockdefColor) CATEGORY_MATH_COLOR, block_pi); blockdef_add_image(sc_pi, (BlockdefImage) { .image_ptr = &assets.textures.icon_pi, .image_color = (BlockdefColor) { 0xff, 0xff, 0xff, 0xff } }); blockdef_register(vm, sc_pi); block_category_add_blockdef(cat_math, sc_pi); block_category_add_label(cat_logic, gettext("Comparisons"), (Color) CATEGORY_LOGIC_COLOR); Blockdef* sc_less = blockdef_new("less", BLOCKTYPE_NORMAL, (BlockdefColor) CATEGORY_LOGIC_COLOR, block_less); blockdef_add_argument(sc_less, "9", "0", BLOCKCONSTR_UNLIMITED); blockdef_add_text(sc_less, "<"); blockdef_add_argument(sc_less, "11", "0", BLOCKCONSTR_UNLIMITED); blockdef_register(vm, sc_less); block_category_add_blockdef(cat_logic, sc_less); Blockdef* sc_less_eq = blockdef_new("less_eq", BLOCKTYPE_NORMAL, (BlockdefColor) CATEGORY_LOGIC_COLOR, block_less_eq); blockdef_add_argument(sc_less_eq, "9", "0", BLOCKCONSTR_UNLIMITED); blockdef_add_text(sc_less_eq, "<="); blockdef_add_argument(sc_less_eq, "11", "0", BLOCKCONSTR_UNLIMITED); blockdef_register(vm, sc_less_eq); block_category_add_blockdef(cat_logic, sc_less_eq); Blockdef* sc_eq = blockdef_new("eq", BLOCKTYPE_NORMAL, (BlockdefColor) CATEGORY_LOGIC_COLOR, block_eq); blockdef_add_argument(sc_eq, "", gettext("any"), BLOCKCONSTR_UNLIMITED); blockdef_add_text(sc_eq, "="); blockdef_add_argument(sc_eq, "", gettext("any"), BLOCKCONSTR_UNLIMITED); blockdef_register(vm, sc_eq); block_category_add_blockdef(cat_logic, sc_eq); Blockdef* sc_not_eq = blockdef_new("not_eq", BLOCKTYPE_NORMAL, (BlockdefColor) CATEGORY_LOGIC_COLOR, block_not_eq); blockdef_add_argument(sc_not_eq, "", gettext("any"), BLOCKCONSTR_UNLIMITED); blockdef_add_text(sc_not_eq, "!="); blockdef_add_argument(sc_not_eq, "", gettext("any"), BLOCKCONSTR_UNLIMITED); blockdef_register(vm, sc_not_eq); block_category_add_blockdef(cat_logic, sc_not_eq); Blockdef* sc_more_eq = blockdef_new("more_eq", BLOCKTYPE_NORMAL, (BlockdefColor) CATEGORY_LOGIC_COLOR, block_more_eq); blockdef_add_argument(sc_more_eq, "9", "0", BLOCKCONSTR_UNLIMITED); blockdef_add_text(sc_more_eq, ">="); blockdef_add_argument(sc_more_eq, "11", "0", BLOCKCONSTR_UNLIMITED); blockdef_register(vm, sc_more_eq); block_category_add_blockdef(cat_logic, sc_more_eq); Blockdef* sc_more = blockdef_new("more", BLOCKTYPE_NORMAL, (BlockdefColor) CATEGORY_LOGIC_COLOR, block_more); blockdef_add_argument(sc_more, "9", "0", BLOCKCONSTR_UNLIMITED); blockdef_add_text(sc_more, ">"); blockdef_add_argument(sc_more, "11", "0", BLOCKCONSTR_UNLIMITED); blockdef_register(vm, sc_more); block_category_add_blockdef(cat_logic, sc_more); block_category_add_label(cat_logic, gettext("Boolean math"), (Color) CATEGORY_LOGIC_COLOR); Blockdef* sc_not = blockdef_new("not", BLOCKTYPE_NORMAL, (BlockdefColor) CATEGORY_LOGIC_COLOR, block_not); blockdef_add_text(sc_not, gettext("Not")); blockdef_add_argument(sc_not, "", gettext("cond."), BLOCKCONSTR_UNLIMITED); blockdef_register(vm, sc_not); block_category_add_blockdef(cat_logic, sc_not); Blockdef* sc_and = blockdef_new("and", BLOCKTYPE_NORMAL, (BlockdefColor) CATEGORY_LOGIC_COLOR, block_and); blockdef_add_argument(sc_and, "", gettext("cond."), BLOCKCONSTR_UNLIMITED); blockdef_add_text(sc_and, gettext("and")); blockdef_add_argument(sc_and, "", gettext("cond."), BLOCKCONSTR_UNLIMITED); blockdef_register(vm, sc_and); block_category_add_blockdef(cat_logic, sc_and); Blockdef* sc_or = blockdef_new("or", BLOCKTYPE_NORMAL, (BlockdefColor) CATEGORY_LOGIC_COLOR, block_or); blockdef_add_argument(sc_or, "", gettext("cond."), BLOCKCONSTR_UNLIMITED); blockdef_add_text(sc_or, gettext("or")); blockdef_add_argument(sc_or, "", gettext("cond."), BLOCKCONSTR_UNLIMITED); blockdef_register(vm, sc_or); block_category_add_blockdef(cat_logic, sc_or); Blockdef* sc_true = blockdef_new("true", BLOCKTYPE_NORMAL, (BlockdefColor) CATEGORY_LOGIC_COLOR, block_true); blockdef_add_text(sc_true, gettext("True")); blockdef_register(vm, sc_true); block_category_add_blockdef(cat_logic, sc_true); Blockdef* sc_false = blockdef_new("false", BLOCKTYPE_NORMAL, (BlockdefColor) CATEGORY_LOGIC_COLOR, block_false); blockdef_add_text(sc_false, gettext("False")); blockdef_register(vm, sc_false); block_category_add_blockdef(cat_logic, sc_false); block_category_add_label(cat_logic, gettext("Bitwise math"), (Color) CATEGORY_LOGIC_COLOR); Blockdef* sc_bit_not = blockdef_new("bit_not", BLOCKTYPE_NORMAL, (BlockdefColor) CATEGORY_LOGIC_COLOR, block_bit_not); blockdef_add_text(sc_bit_not, "~"); blockdef_add_argument(sc_bit_not, "39", "0", BLOCKCONSTR_UNLIMITED); blockdef_register(vm, sc_bit_not); block_category_add_blockdef(cat_logic, sc_bit_not); Blockdef* sc_bit_and = blockdef_new("bit_and", BLOCKTYPE_NORMAL, (BlockdefColor) CATEGORY_LOGIC_COLOR, block_bit_and); blockdef_add_argument(sc_bit_and, "39", "0", BLOCKCONSTR_UNLIMITED); blockdef_add_text(sc_bit_and, "&"); blockdef_add_argument(sc_bit_and, "5", "0", BLOCKCONSTR_UNLIMITED); blockdef_register(vm, sc_bit_and); block_category_add_blockdef(cat_logic, sc_bit_and); Blockdef* sc_bit_or = blockdef_new("bit_or", BLOCKTYPE_NORMAL, (BlockdefColor) CATEGORY_LOGIC_COLOR, block_bit_or); blockdef_add_argument(sc_bit_or, "39", "0", BLOCKCONSTR_UNLIMITED); blockdef_add_text(sc_bit_or, "|"); blockdef_add_argument(sc_bit_or, "5", "0", BLOCKCONSTR_UNLIMITED); blockdef_register(vm, sc_bit_or); block_category_add_blockdef(cat_logic, sc_bit_or); Blockdef* sc_bit_xor = blockdef_new("bit_xor", BLOCKTYPE_NORMAL, (BlockdefColor) CATEGORY_LOGIC_COLOR, block_bit_xor); blockdef_add_argument(sc_bit_xor, "39", "0", BLOCKCONSTR_UNLIMITED); blockdef_add_text(sc_bit_xor, "^"); blockdef_add_argument(sc_bit_xor, "5", "0", BLOCKCONSTR_UNLIMITED); blockdef_register(vm, sc_bit_xor); block_category_add_blockdef(cat_logic, sc_bit_xor); block_category_add_label(cat_misc, gettext("System"), (Color) CATEGORY_MISC_COLOR); Blockdef* sc_sleep = blockdef_new("sleep", BLOCKTYPE_NORMAL, (BlockdefColor) CATEGORY_MISC_COLOR, block_sleep); blockdef_add_text(sc_sleep, gettext("Sleep")); blockdef_add_argument(sc_sleep, "", "0", BLOCKCONSTR_UNLIMITED); blockdef_add_text(sc_sleep, gettext("μs")); blockdef_register(vm, sc_sleep); block_category_add_blockdef(cat_misc, sc_sleep); Blockdef* sc_random = blockdef_new("random", BLOCKTYPE_NORMAL, (BlockdefColor) CATEGORY_MISC_COLOR, block_random); blockdef_add_text(sc_random, gettext("Random from")); blockdef_add_argument(sc_random, "0", "0", BLOCKCONSTR_UNLIMITED); blockdef_add_text(sc_random, gettext("to")); blockdef_add_argument(sc_random, "10", "0", BLOCKCONSTR_UNLIMITED); blockdef_register(vm, sc_random); block_category_add_blockdef(cat_misc, sc_random); Blockdef* sc_unix_time = blockdef_new("unix_time", BLOCKTYPE_NORMAL, (BlockdefColor) CATEGORY_MISC_COLOR, block_unix_time); blockdef_add_text(sc_unix_time, gettext("Time since 1970")); blockdef_register(vm, sc_unix_time); block_category_add_blockdef(cat_misc, sc_unix_time); block_category_add_label(cat_misc, gettext("Type casting"), (Color) CATEGORY_MISC_COLOR); Blockdef* sc_int = blockdef_new("convert_int", BLOCKTYPE_NORMAL, (BlockdefColor) CATEGORY_MISC_COLOR, block_convert_int); blockdef_add_text(sc_int, gettext("Int")); blockdef_add_argument(sc_int, "", gettext("any"), BLOCKCONSTR_UNLIMITED); blockdef_register(vm, sc_int); block_category_add_blockdef(cat_misc, sc_int); Blockdef* sc_float = blockdef_new("convert_float", BLOCKTYPE_NORMAL, (BlockdefColor) CATEGORY_MISC_COLOR, block_convert_float); blockdef_add_text(sc_float, gettext("Float")); blockdef_add_argument(sc_float, "", gettext("any"), BLOCKCONSTR_UNLIMITED); blockdef_register(vm, sc_float); block_category_add_blockdef(cat_misc, sc_float); Blockdef* sc_str = blockdef_new("convert_str", BLOCKTYPE_NORMAL, (BlockdefColor) CATEGORY_MISC_COLOR, block_convert_str); blockdef_add_text(sc_str, gettext("Str")); blockdef_add_argument(sc_str, "", gettext("any"), BLOCKCONSTR_UNLIMITED); blockdef_register(vm, sc_str); block_category_add_blockdef(cat_misc, sc_str); Blockdef* sc_bool = blockdef_new("convert_bool", BLOCKTYPE_NORMAL, (BlockdefColor) CATEGORY_MISC_COLOR, block_convert_bool); blockdef_add_text(sc_bool, gettext("Bool")); blockdef_add_argument(sc_bool, "", gettext("any"), BLOCKCONSTR_UNLIMITED); blockdef_register(vm, sc_bool); block_category_add_blockdef(cat_misc, sc_bool); Blockdef* sc_color = blockdef_new("convert_color", BLOCKTYPE_NORMAL, (BlockdefColor) CATEGORY_MISC_COLOR, block_convert_color); blockdef_add_text(sc_color, gettext("Color")); blockdef_add_color_input(sc_color, (BlockdefColor) { 0x00, 0xff, 0x00, 0xff }); blockdef_register(vm, sc_color); block_category_add_blockdef(cat_misc, sc_color); Blockdef* sc_typeof = blockdef_new("typeof", BLOCKTYPE_NORMAL, (BlockdefColor) CATEGORY_MISC_COLOR, block_typeof); blockdef_add_text(sc_typeof, gettext("Type of")); blockdef_add_argument(sc_typeof, "", gettext("any"), BLOCKCONSTR_UNLIMITED); blockdef_register(vm, sc_typeof); block_category_add_blockdef(cat_misc, sc_typeof); block_category_add_label(cat_misc, gettext("Doing nothing"), (Color) { 0x77, 0x77, 0x77, 0xff }); Blockdef* sc_nothing = blockdef_new("nothing", BLOCKTYPE_NORMAL, (BlockdefColor) { 0x77, 0x77, 0x77, 0xff }, block_noop); blockdef_add_text(sc_nothing, gettext("Nothing")); blockdef_register(vm, sc_nothing); block_category_add_blockdef(cat_misc, sc_nothing); Blockdef* sc_do_nothing = blockdef_new("do_nothing", BLOCKTYPE_CONTROL, (BlockdefColor) { 0x77, 0x77, 0x77, 0xff }, block_do_nothing); blockdef_add_text(sc_do_nothing, gettext("Do nothing")); blockdef_register(vm, sc_do_nothing); block_category_add_blockdef(cat_misc, sc_do_nothing); Blockdef* sc_comment = blockdef_new("comment", BLOCKTYPE_NORMAL, (BlockdefColor) { 0x77, 0x77, 0x77, 0xff }, block_noop); blockdef_add_text(sc_comment, "//"); blockdef_add_argument(sc_comment, "", gettext("any"), BLOCKCONSTR_UNLIMITED); blockdef_register(vm, sc_comment); block_category_add_blockdef(cat_misc, sc_comment); #ifdef DEBUG block_category_add_label(cat_misc, gettext("Debug blocks"), (Color) { 0xa0, 0x70, 0x00, 0xff }); Blockdef* sc_gc_collect = blockdef_new("gc_collect", BLOCKTYPE_NORMAL, (BlockdefColor) { 0xa0, 0x70, 0x00, 0xff }, block_gc_collect); blockdef_add_text(sc_gc_collect, gettext("Collect garbage")); blockdef_register(vm, sc_gc_collect); block_category_add_blockdef(cat_misc, sc_gc_collect); #endif block_category_add_label(cat_data, gettext("Variables"), (Color) CATEGORY_DATA_COLOR); Blockdef* sc_decl_var = blockdef_new("decl_var", BLOCKTYPE_NORMAL, (BlockdefColor) CATEGORY_DATA_COLOR, block_declare_var); blockdef_add_image(sc_decl_var, (BlockdefImage) { .image_ptr = &assets.textures.icon_variable, .image_color = (BlockdefColor) { 0xff, 0xff, 0xff, 0xff } }); blockdef_add_text(sc_decl_var, gettext("Declare")); blockdef_add_argument(sc_decl_var, gettext("my variable"), gettext("Abc"), BLOCKCONSTR_STRING); blockdef_add_text(sc_decl_var, "="); blockdef_add_argument(sc_decl_var, "", gettext("any"), BLOCKCONSTR_UNLIMITED); blockdef_register(vm, sc_decl_var); block_category_add_blockdef(cat_data, sc_decl_var); Blockdef* sc_get_var = blockdef_new("get_var", BLOCKTYPE_NORMAL, (BlockdefColor) CATEGORY_DATA_COLOR, block_get_var); blockdef_add_image(sc_get_var, (BlockdefImage) { .image_ptr = &assets.textures.icon_variable, .image_color = (BlockdefColor) { 0xff, 0xff, 0xff, 0xff } }); blockdef_add_argument(sc_get_var, gettext("my variable"), gettext("Abc"), BLOCKCONSTR_STRING); blockdef_register(vm, sc_get_var); block_category_add_blockdef(cat_data, sc_get_var); Blockdef* sc_set_var = blockdef_new("set_var", BLOCKTYPE_NORMAL, (BlockdefColor) CATEGORY_DATA_COLOR, block_set_var); blockdef_add_image(sc_set_var, (BlockdefImage) { .image_ptr = &assets.textures.icon_variable, .image_color = (BlockdefColor) { 0xff, 0xff, 0xff, 0xff } }); blockdef_add_text(sc_set_var, gettext("Set")); blockdef_add_argument(sc_set_var, gettext("my variable"), gettext("Abc"), BLOCKCONSTR_STRING); blockdef_add_text(sc_set_var, "="); blockdef_add_argument(sc_set_var, "", gettext("any"), BLOCKCONSTR_UNLIMITED); blockdef_register(vm, sc_set_var); block_category_add_blockdef(cat_data, sc_set_var); block_category_add_label(cat_data, gettext("Strings"), (Color) CATEGORY_STRING_COLOR); Blockdef* sc_join = blockdef_new("join", BLOCKTYPE_NORMAL, (BlockdefColor) CATEGORY_STRING_COLOR, block_join); blockdef_add_text(sc_join, gettext("Join")); blockdef_add_argument(sc_join, gettext("left and "), gettext("Abc"), BLOCKCONSTR_UNLIMITED); blockdef_add_argument(sc_join, gettext("right"), gettext("Abc"), BLOCKCONSTR_UNLIMITED); blockdef_register(vm, sc_join); block_category_add_blockdef(cat_data, sc_join); Blockdef* sc_letter_in = blockdef_new("letter_in", BLOCKTYPE_NORMAL, (BlockdefColor) CATEGORY_STRING_COLOR, block_letter_in); blockdef_add_text(sc_letter_in, gettext("Letter")); blockdef_add_argument(sc_letter_in, "1", "0", BLOCKCONSTR_UNLIMITED); blockdef_add_text(sc_letter_in, gettext("in")); blockdef_add_argument(sc_letter_in, gettext("string"), gettext("Abc"), BLOCKCONSTR_UNLIMITED); blockdef_register(vm, sc_letter_in); block_category_add_blockdef(cat_data, sc_letter_in); Blockdef* sc_substring = blockdef_new("substring", BLOCKTYPE_NORMAL, (BlockdefColor) CATEGORY_STRING_COLOR, block_substring); blockdef_add_text(sc_substring, gettext("Substring")); blockdef_add_argument(sc_substring, "2", "0", BLOCKCONSTR_UNLIMITED); blockdef_add_text(sc_substring, gettext("to")); blockdef_add_argument(sc_substring, "4", "0", BLOCKCONSTR_UNLIMITED); blockdef_add_text(sc_substring, gettext("in")); blockdef_add_argument(sc_substring, gettext("string"), gettext("Abc"), BLOCKCONSTR_UNLIMITED); blockdef_register(vm, sc_substring); block_category_add_blockdef(cat_data, sc_substring); Blockdef* sc_length = blockdef_new("length", BLOCKTYPE_NORMAL, (BlockdefColor) CATEGORY_STRING_COLOR, block_length); blockdef_add_text(sc_length, gettext("Length")); blockdef_add_argument(sc_length, gettext("string"), gettext("Abc"), BLOCKCONSTR_UNLIMITED); blockdef_register(vm, sc_length); block_category_add_blockdef(cat_data, sc_length); Blockdef* sc_ord = blockdef_new("ord", BLOCKTYPE_NORMAL, (BlockdefColor) CATEGORY_STRING_COLOR, block_ord); blockdef_add_text(sc_ord, gettext("Ord")); blockdef_add_argument(sc_ord, "A", gettext("Abc"), BLOCKCONSTR_UNLIMITED); blockdef_register(vm, sc_ord); block_category_add_blockdef(cat_data, sc_ord); Blockdef* sc_chr = blockdef_new("chr", BLOCKTYPE_NORMAL, (BlockdefColor) CATEGORY_STRING_COLOR, block_chr); blockdef_add_text(sc_chr, gettext("Chr")); blockdef_add_argument(sc_chr, "65", "0", BLOCKCONSTR_UNLIMITED); blockdef_register(vm, sc_chr); block_category_add_blockdef(cat_data, sc_chr); block_category_add_label(cat_data, gettext("Lists"), (Color) { 0xff, 0x44, 0x00, 0xff }); Blockdef* sc_create_list = blockdef_new("create_list", BLOCKTYPE_NORMAL, (BlockdefColor) { 0xff, 0x44, 0x00, 0xff }, block_create_list); blockdef_add_image(sc_create_list, list_img); blockdef_add_text(sc_create_list, gettext("Empty list")); blockdef_register(vm, sc_create_list); block_category_add_blockdef(cat_data, sc_create_list); Blockdef* sc_list_add = blockdef_new("list_add", BLOCKTYPE_NORMAL, (BlockdefColor) { 0xff, 0x44, 0x00, 0xff }, block_list_add); blockdef_add_image(sc_list_add, list_img); blockdef_add_text(sc_list_add, gettext("Add")); blockdef_add_argument(sc_list_add, "", gettext("list"), BLOCKCONSTR_UNLIMITED); blockdef_add_text(sc_list_add, gettext("value")); blockdef_add_argument(sc_list_add, "", gettext("any"), BLOCKCONSTR_UNLIMITED); blockdef_register(vm, sc_list_add); block_category_add_blockdef(cat_data, sc_list_add); Blockdef* sc_list_get = blockdef_new("list_get", BLOCKTYPE_NORMAL, (BlockdefColor) { 0xff, 0x44, 0x00, 0xff }, block_list_get); blockdef_add_image(sc_list_get, list_img); blockdef_add_argument(sc_list_get, "", gettext("list"), BLOCKCONSTR_UNLIMITED); blockdef_add_text(sc_list_get, gettext("at")); blockdef_add_argument(sc_list_get, "0", "0", BLOCKCONSTR_UNLIMITED); blockdef_register(vm, sc_list_get); block_category_add_blockdef(cat_data, sc_list_get); Blockdef* sc_list_set = blockdef_new("list_set", BLOCKTYPE_NORMAL, (BlockdefColor) { 0xff, 0x44, 0x00, 0xff }, block_list_set); blockdef_add_image(sc_list_set, list_img); blockdef_add_text(sc_list_set, gettext("Set")); blockdef_add_argument(sc_list_set, "", gettext("list"), BLOCKCONSTR_UNLIMITED); blockdef_add_text(sc_list_set, gettext("at")); blockdef_add_argument(sc_list_set, "0", "0", BLOCKCONSTR_UNLIMITED); blockdef_add_text(sc_list_set, "="); blockdef_add_argument(sc_list_set, "", gettext("any"), BLOCKCONSTR_UNLIMITED); blockdef_register(vm, sc_list_set); block_category_add_blockdef(cat_data, sc_list_set); Blockdef* sc_list_len = blockdef_new("list_length", BLOCKTYPE_NORMAL, (BlockdefColor) { 0xff, 0x44, 0x00, 0xff }, block_list_length); blockdef_add_image(sc_list_len, list_img); blockdef_add_text(sc_list_len, gettext("Length")); blockdef_add_argument(sc_list_len, "", gettext("list"), BLOCKCONSTR_UNLIMITED); blockdef_register(vm, sc_list_len); block_category_add_blockdef(cat_data, sc_list_len); } ================================================ FILE: src/compiler.c ================================================ // Scrap is a project that allows anyone to build software using simple, block based interface. // // Copyright (C) 2024-2026 Grisshink // // This software is provided 'as-is', without any express or implied // warranty. In no event will the authors be held liable for any damages // arising from the use of this software. // // Permission is granted to anyone to use this software for any purpose, // including commercial applications, and to alter it and redistribute it // freely, subject to the following restrictions: // // 1. The origin of this software must not be misrepresented; you must not // claim that you wrote the original software. If you use this software // in a product, an acknowledgment in the product documentation would be // appreciated but is not required. // 2. Altered source versions must be plainly marked as such, and must not be // misrepresented as being the original software. // 3. This notice may not be removed or altered from any source distribution. #include "scrap.h" #include "term.h" #include "vec.h" #include "std.h" #include #include #include #include #include #include #include #include #include #include #ifndef _WIN32 #include #endif #ifdef _WIN32 #define TARGET_TRIPLE "x86_64-w64-windows-gnu" #else #define TARGET_TRIPLE "x86_64-pc-linux-gnu" #endif static bool compile_program(Exec* exec); static bool run_program(Exec* exec); static bool build_program(Exec* exec); static void free_defined_functions(Exec* exec); Exec exec_new(Thread* thread, CompilerMode mode) { Exec exec = (Exec) { .code = NULL, .thread = thread, .current_error_block = NULL, .current_mode = mode, }; exec.current_error[0] = 0; return exec; } void exec_free(Exec* exec) { (void) exec; } void exec_cleanup(void* e) { Exec* exec = e; switch (exec->current_state) { case STATE_NONE: break; case STATE_COMPILE: LLVMDisposeModule(exec->module); LLVMDisposeBuilder(exec->builder); vector_free(exec->gc_dirty_funcs); vector_free(exec->compile_func_list); vector_free(exec->global_variables); free_defined_functions(exec); break; case STATE_PRE_EXEC: LLVMDisposeModule(exec->module); vector_free(exec->compile_func_list); break; case STATE_EXEC: gc_free(&exec->gc); LLVMDisposeExecutionEngine(exec->engine); break; } } bool exec_run(void* e) { Exec* exec = e; exec->current_state = STATE_NONE; if (!compile_program(exec)) return false; if (exec->current_mode == COMPILER_MODE_JIT) { if (!run_program(exec)) return false; } else { if (!build_program(exec)) return false; } return true; } static void exec_handle_running_state(Exec* exec) { if (exec->thread->state != THREAD_STATE_STOPPING) return; longjmp(exec->run_jump_buf, 1); } void exec_set_error(Exec* exec, Block* block, const char* fmt, ...) { exec->current_error_block = block; va_list va; va_start(va, fmt); vsnprintf(exec->current_error, MAX_ERROR_LEN, fmt, va); va_end(va); scrap_log(LOG_ERROR, "[EXEC] %s", exec->current_error); } static bool control_stack_push(Exec* exec, Block* block) { if (exec->control_stack_len >= VM_CONTROL_STACK_SIZE) { exec_set_error(exec, block, gettext("Control stack overflow")); return false; } exec->control_stack[exec->control_stack_len++] = block; return true; } static Block* control_stack_pop(Exec* exec) { if (exec->control_stack_len == 0) { exec_set_error(exec, NULL, gettext("Control stack underflow")); return NULL; } return exec->control_stack[--exec->control_stack_len]; } void global_variable_add(Exec* exec, Variable variable) { vector_add(&exec->global_variables, variable); } bool variable_stack_push(Exec* exec, Block* block, Variable variable) { if (exec->variable_stack_len >= VM_CONTROL_STACK_SIZE) { exec_set_error(exec, block, gettext("Variable stack overflow")); return false; } exec->variable_stack[exec->variable_stack_len++] = variable; return true; } Variable* variable_get(Exec* exec, const char* var_name) { for (ssize_t i = exec->variable_stack_len - 1; i >= 0; i--) { if (!strcmp(var_name, exec->variable_stack[i].name)) return &exec->variable_stack[i]; } for (ssize_t i = vector_size(exec->global_variables) - 1; i >= 0; i--) { if (!strcmp(var_name, exec->global_variables[i].name)) return &exec->global_variables[i]; } return NULL; } static bool variable_stack_frame_push(Exec* exec) { if (exec->variable_stack_frames_len >= VM_CONTROL_STACK_SIZE) { exec_set_error(exec, NULL, gettext("Variable stack overflow")); return false; } VariableStackFrame frame; frame.base_size = exec->variable_stack_len; frame.base_stack = build_call(exec, "llvm.stacksave.p0"); exec->variable_stack_frames[exec->variable_stack_frames_len++] = frame; return true; } static bool variable_stack_frame_pop(Exec* exec) { if (exec->variable_stack_frames_len == 0) { exec_set_error(exec, NULL, gettext("Variable stack underflow")); return false; } VariableStackFrame frame = exec->variable_stack_frames[--exec->variable_stack_frames_len]; build_call(exec, "llvm.stackrestore.p0", frame.base_stack); exec->variable_stack_len = frame.base_size; return true; } static bool evaluate_block(Exec* exec, Block* block, FuncArg* return_val, ControlState control_state, FuncArg input_val) { if (!block->blockdef) { exec_set_error(exec, block, gettext("Tried to compile block without definition")); return false; } if (!block->blockdef->func) { exec_set_error(exec, block, gettext("Tried to compile block \"%s\" without implementation"), block->blockdef->id); return false; } BlockCompileFunc compile_block = block->blockdef->func; FuncArg* args = vector_create(); FuncArg* arg; if (control_state == CONTROL_STATE_BEGIN) { LLVMBasicBlockRef current = LLVMGetInsertBlock(exec->builder); LLVMBasicBlockRef control_block = LLVMInsertBasicBlock(current, "control_block"); LLVMMoveBasicBlockAfter(control_block, current); LLVMBuildBr(exec->builder, control_block); LLVMPositionBuilderAtEnd(exec->builder, control_block); variable_stack_frame_push(exec); } else if (control_state == CONTROL_STATE_END) { if (exec->current_mode == COMPILER_MODE_JIT) build_call(exec, "test_cancel", CONST_EXEC); variable_stack_frame_pop(exec); } if (block->blockdef->type == BLOCKTYPE_CONTROLEND && control_state == CONTROL_STATE_BEGIN) { vector_add(&args, input_val); } if (control_state != CONTROL_STATE_END) { for (size_t i = 0; i < vector_size(block->arguments); i++) { FuncArg block_return; static_assert(ARGUMENT_LAST == 5, "Exhaustive argument type in evaluate_block"); switch (block->arguments[i].type) { case ARGUMENT_TEXT: case ARGUMENT_CONST_STRING: arg = vector_add_dst(&args); arg->type = DATA_TYPE_LITERAL; arg->data.str = block->arguments[i].data.text; break; case ARGUMENT_BLOCK: if (!evaluate_block(exec, &block->arguments[i].data.block, &block_return, CONTROL_STATE_NORMAL, DATA_NOTHING)) { scrap_log(LOG_ERROR, "[LLVM] While compiling block id: \"%s\" (argument #%d) (at block %p)", block->blockdef->id, i + 1, block); vector_free(args); return false; } vector_add(&args, block_return); break; case ARGUMENT_BLOCKDEF: arg = vector_add_dst(&args); arg->type = DATA_TYPE_BLOCKDEF; arg->data.blockdef = block->arguments[i].data.blockdef; break; case ARGUMENT_COLOR: arg = vector_add_dst(&args); arg->type = DATA_TYPE_COLOR; arg->data.value = CONST_INTEGER(*(int*)&block->arguments[i].data.color); break; default: assert(false && "Unimplemented argument type in evaluate_block"); } } } if (control_state == CONTROL_STATE_BEGIN) { control_data_stack_push_data(exec->gc_dirty, bool); } if (!compile_block(exec, block, vector_size(args), args, return_val, control_state)) { vector_free(args); scrap_log(LOG_ERROR, "[LLVM] Got error while compiling block id: \"%s\" (at block %p)", block->blockdef->id, block); return false; } if (control_state == CONTROL_STATE_END) { control_data_stack_pop_data(exec->gc_dirty, bool); } if (!block->parent && exec->gc_dirty) { build_call(exec, "gc_flush", CONST_GC); exec->gc_dirty = false; } vector_free(args); return true; } static bool evaluate_chain(Exec* exec, BlockChain* chain) { if (vector_size(chain->blocks) == 0 || chain->blocks[0].blockdef->type != BLOCKTYPE_HAT) return true; exec->variable_stack_len = 0; exec->variable_stack_frames_len = 0; for (size_t i = 0; i < vector_size(chain->blocks); i++) { FuncArg block_return; Block* exec_block = &chain->blocks[i]; ControlState control_state = chain->blocks[i].blockdef->type == BLOCKTYPE_CONTROL ? CONTROL_STATE_BEGIN : CONTROL_STATE_NORMAL; if (chain->blocks[i].blockdef->type == BLOCKTYPE_END || chain->blocks[i].blockdef->type == BLOCKTYPE_CONTROLEND) { exec_block = control_stack_pop(exec); if (!exec_block) return false; control_state = CONTROL_STATE_END; } if (!evaluate_block(exec, exec_block, &block_return, control_state, DATA_NOTHING)) return false; if (chain->blocks[i].blockdef->type == BLOCKTYPE_CONTROLEND) { FuncArg bin; control_state = CONTROL_STATE_BEGIN; if (!evaluate_block(exec, &chain->blocks[i], &bin, control_state, block_return)) return false; } if (chain->blocks[i].blockdef->type == BLOCKTYPE_CONTROL || chain->blocks[i].blockdef->type == BLOCKTYPE_CONTROLEND) { if (!control_stack_push(exec, &chain->blocks[i])) return false; } } return true; } DefineArgument* get_custom_argument(Exec* exec, Blockdef* blockdef, DefineFunction** func) { for (size_t i = 0; i < vector_size(exec->defined_functions); i++) { for (size_t j = 0; j < vector_size(exec->defined_functions[i].args); j++) { if (exec->defined_functions[i].args[j].blockdef == blockdef) { *func = &exec->defined_functions[i]; return &exec->defined_functions[i].args[j]; } } } return NULL; } static void vector_add_str(char** vec, const char* str) { for (const char* str_val = str; *str_val; str_val++) vector_add(vec, *str_val); } DefineFunction* define_function(Exec* exec, Blockdef* blockdef) { for (size_t i = 0; i < vector_size(exec->defined_functions); i++) { if (exec->defined_functions[i].blockdef == blockdef) { return &exec->defined_functions[i]; } } LLVMTypeRef func_params[32]; Blockdef* func_params_blockdefs[32]; unsigned int func_params_count = 0; char* func_name = vector_create(); vector_add_str(&func_name, blockdef->id); vector_add(&func_name, ' '); for (size_t i = 0; i < vector_size(blockdef->inputs); i++) { switch (blockdef->inputs[i].type) { case INPUT_TEXT_DISPLAY: vector_add_str(&func_name, blockdef->inputs[i].data.text); vector_add(&func_name, ' '); break; case INPUT_BLOCKDEF_EDITOR: case INPUT_COLOR: case INPUT_DROPDOWN: vector_add_str(&func_name, "[] "); break; case INPUT_IMAGE_DISPLAY: vector_add_str(&func_name, "img "); break; case INPUT_ARGUMENT: func_params[func_params_count] = LLVMPointerType(LLVMInt8Type(), 0); func_params_blockdefs[func_params_count] = blockdef->inputs[i].data.arg.blockdef; func_params_count++; vector_add_str(&func_name, "[] "); break; } } func_name[vector_size(func_name) - 1] = 0; LLVMTypeRef func_type = LLVMFunctionType(LLVMPointerType(LLVMInt8Type(), 0), func_params, func_params_count, false); LLVMValueRef func = LLVMAddFunction(exec->module, func_name, func_type); vector_free(func_name); DefineFunction* define = vector_add_dst(&exec->defined_functions); define->blockdef = blockdef; define->func = func; define->args = vector_create(); LLVMValueRef func_params_values[32]; LLVMGetParams(func, func_params_values); for (unsigned int i = 0; i < func_params_count; i++) { DefineArgument* arg = vector_add_dst(&define->args); arg->blockdef = func_params_blockdefs[i]; arg->arg = func_params_values[i]; } return define; } LLVMValueRef build_gc_root_begin(Exec* exec, Block* block) { if (exec->gc_block_stack_len >= VM_CONTROL_STACK_SIZE) { exec_set_error(exec, block, "Gc stack overflow"); return NULL; } LLVMValueRef root_begin = build_call(exec, "gc_root_begin", CONST_GC); GcBlock gc_block = (GcBlock) { .root_begin = root_begin, .required = false, }; exec->gc_block_stack[exec->gc_block_stack_len++] = gc_block; return root_begin; } LLVMValueRef build_gc_root_end(Exec* exec, Block* block) { if (exec->gc_block_stack_len == 0) { exec_set_error(exec, block, "Gc stack underflow"); return NULL; } GcBlock gc_block = exec->gc_block_stack[--exec->gc_block_stack_len]; if (!gc_block.required) { LLVMInstructionEraseFromParent(gc_block.root_begin); return (LLVMValueRef)-1; } return build_call(exec, "gc_root_end", CONST_GC); } static LLVMValueRef get_function(Exec* exec, const char* func_name) { LLVMValueRef func = LLVMGetNamedFunction(exec->module, func_name); if (func) return func; for (size_t i = 0; i < vector_size(exec->compile_func_list); i++) { if (!strcmp(exec->compile_func_list[i].name, func_name)) { func = LLVMAddFunction(exec->module, func_name, exec->compile_func_list[i].type); if (exec->compile_func_list[i].dynamic) vector_add(&exec->gc_dirty_funcs, func); return func; } } exec_set_error(exec, NULL, gettext("Function with name \"%s\" does not exist"), func_name); return NULL; } static LLVMValueRef build_call_va(Exec* exec, const char* func_name, LLVMValueRef func, LLVMTypeRef func_type, size_t func_param_count, va_list va) { for (size_t i = 0; i < vector_size(exec->gc_dirty_funcs); i++) { if (func != exec->gc_dirty_funcs[i]) continue; exec->gc_dirty = true; if (exec->gc_block_stack_len > 0) { exec->gc_block_stack[exec->gc_block_stack_len - 1].required = true; } } // Should be enough for all functions assert(func_param_count <= 32); LLVMValueRef func_param_list[32]; for (unsigned int i = 0; i < func_param_count; i++) { func_param_list[i] = va_arg(va, LLVMValueRef); } if (LLVMGetTypeKind(LLVMGetReturnType(func_type)) == LLVMVoidTypeKind) { return LLVMBuildCall2(exec->builder, func_type, func, func_param_list, func_param_count, ""); } else { return LLVMBuildCall2(exec->builder, func_type, func, func_param_list, func_param_count, func_name); } } LLVMValueRef build_call_count(Exec* exec, const char* func_name, size_t func_param_count, ...) { LLVMValueRef func = get_function(exec, func_name); LLVMTypeRef func_type = LLVMGlobalGetValueType(func); LLVMValueRef out; va_list va; va_start(va, func_param_count); out = build_call_va(exec, func_name, func, func_type, func_param_count, va); va_end(va); return out; } LLVMValueRef build_call(Exec* exec, const char* func_name, ...) { LLVMValueRef func = get_function(exec, func_name); LLVMTypeRef func_type = LLVMGlobalGetValueType(func); unsigned int func_param_count = LLVMCountParamTypes(func_type); LLVMValueRef out; va_list va; va_start(va, func_name); out = build_call_va(exec, func_name, func, func_type, func_param_count, va); va_end(va); return out; } // Dynamic means the func calls gc_malloc at some point. This is needed for gc.root_temp_chunks cleanup static void add_function(Exec* exec, const char* name, LLVMTypeRef return_type, LLVMTypeRef* params, size_t params_len, void* func, bool dynamic, bool variadic) { CompileFunction* comp_func = vector_add_dst(&exec->compile_func_list); comp_func->func = func; comp_func->name = name; comp_func->type = LLVMFunctionType(return_type, params, params_len, variadic); comp_func->dynamic = dynamic; } static LLVMValueRef register_globals(Exec* exec) { LLVMTypeRef print_func_params[] = { LLVMPointerType(LLVMInt8Type(), 0) }; add_function(exec, "std_term_print_str", LLVMInt32Type(), print_func_params, ARRLEN(print_func_params), std_term_print_str, false, false); LLVMTypeRef print_integer_func_params[] = { LLVMInt32Type() }; add_function(exec, "std_term_print_integer", LLVMInt32Type(), print_integer_func_params, ARRLEN(print_integer_func_params), std_term_print_integer, false, false); LLVMTypeRef print_float_func_params[] = { LLVMDoubleType() }; add_function(exec, "std_term_print_float", LLVMInt32Type(), print_float_func_params, ARRLEN(print_float_func_params), std_term_print_float, false, false); LLVMTypeRef print_bool_func_params[] = { LLVMInt1Type() }; add_function(exec, "std_term_print_bool", LLVMInt32Type(), print_bool_func_params, ARRLEN(print_bool_func_params), std_term_print_bool, false, false); LLVMTypeRef print_list_func_params[] = { LLVMPointerType(LLVMInt8Type(), 0) }; add_function(exec, "std_term_print_list", LLVMInt32Type(), print_list_func_params, ARRLEN(print_list_func_params), std_term_print_list, false, false); LLVMTypeRef print_color_func_params[] = { LLVMInt32Type() }; add_function(exec, "std_term_print_color", LLVMInt32Type(), print_color_func_params, ARRLEN(print_color_func_params), std_term_print_color, false, false); LLVMTypeRef print_any_func_params[] = { LLVMPointerType(LLVMInt8Type(), 0) }; add_function(exec, "std_term_print_any", LLVMInt32Type(), print_any_func_params, ARRLEN(print_any_func_params), std_term_print_any, false, false); LLVMTypeRef string_literal_func_params[] = { LLVMInt64Type(), LLVMPointerType(LLVMInt8Type(), 0), LLVMInt32Type() }; 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); LLVMTypeRef string_integer_func_params[] = { LLVMInt64Type(), LLVMInt32Type() }; 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); LLVMTypeRef string_bool_func_params[] = { LLVMInt64Type(), LLVMInt1Type() }; 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); LLVMTypeRef string_float_func_params[] = { LLVMInt64Type(), LLVMDoubleType() }; 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); LLVMTypeRef string_color_func_params[] = { LLVMInt64Type(), LLVMInt32Type() }; 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); LLVMTypeRef string_any_func_params[] = { LLVMInt64Type(), LLVMPointerType(LLVMInt8Type(), 0) }; 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); LLVMTypeRef string_get_data_func_params[] = { LLVMPointerType(LLVMInt8Type(), 0) }; 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); LLVMTypeRef integer_any_func_params[] = { LLVMPointerType(LLVMInt8Type(), 0) }; add_function(exec, "std_integer_from_any", LLVMInt32Type(), integer_any_func_params, ARRLEN(integer_any_func_params), std_integer_from_any, false, false); LLVMTypeRef float_any_func_params[] = { LLVMPointerType(LLVMInt8Type(), 0) }; add_function(exec, "std_float_from_any", LLVMDoubleType(), float_any_func_params, ARRLEN(float_any_func_params), std_float_from_any, false, false); LLVMTypeRef bool_any_func_params[] = { LLVMPointerType(LLVMInt8Type(), 0) }; add_function(exec, "std_bool_from_any", LLVMInt1Type(), bool_any_func_params, ARRLEN(bool_any_func_params), std_bool_from_any, false, false); LLVMTypeRef color_any_func_params[] = { LLVMPointerType(LLVMInt8Type(), 0) }; add_function(exec, "std_color_from_any", LLVMInt32Type(), color_any_func_params, ARRLEN(color_any_func_params), std_color_from_any, false, false); LLVMTypeRef parse_color_func_params[] = { LLVMPointerType(LLVMInt8Type(), 0) }; add_function(exec, "std_parse_color", LLVMInt32Type(), parse_color_func_params, ARRLEN(parse_color_func_params), std_parse_color, false, false); LLVMTypeRef list_any_func_params[] = { LLVMInt64Type(), LLVMPointerType(LLVMInt8Type(), 0) }; 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); LLVMTypeRef any_cast_func_params[] = { LLVMInt64Type(), LLVMInt32Type() }; 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); LLVMTypeRef string_length_func_params[] = { LLVMPointerType(LLVMInt8Type(), 0) }; add_function(exec, "std_string_length", LLVMInt32Type(), string_length_func_params, ARRLEN(string_length_func_params), std_string_length, false, false); LLVMTypeRef string_join_func_params[] = { LLVMInt64Type(), LLVMPointerType(LLVMInt8Type(), 0), LLVMPointerType(LLVMInt8Type(), 0) }; add_function(exec, "std_string_join", LLVMPointerType(LLVMInt8Type(), 0), string_join_func_params, ARRLEN(string_join_func_params), std_string_join, true, false); LLVMTypeRef string_ord_func_params[] = { LLVMPointerType(LLVMInt8Type(), 0) }; add_function(exec, "std_string_ord", LLVMInt32Type(), string_ord_func_params, ARRLEN(string_ord_func_params), std_string_ord, false, false); LLVMTypeRef string_chr_func_params[] = { LLVMInt64Type(), LLVMInt32Type() }; add_function(exec, "std_string_chr", LLVMPointerType(LLVMInt8Type(), 0), string_chr_func_params, ARRLEN(string_chr_func_params), std_string_chr, true, false); LLVMTypeRef string_letter_in_func_params[] = { LLVMInt64Type(), LLVMInt32Type(), LLVMPointerType(LLVMInt8Type(), 0) }; 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); LLVMTypeRef string_substring_func_params[] = { LLVMInt64Type(), LLVMInt32Type(), LLVMInt32Type(), LLVMPointerType(LLVMInt8Type(), 0) }; add_function(exec, "std_string_substring", LLVMPointerType(LLVMInt8Type(), 0), string_substring_func_params, ARRLEN(string_substring_func_params), std_string_substring, true, false); LLVMTypeRef string_eq_func_params[] = { LLVMPointerType(LLVMInt8Type(), 0), LLVMPointerType(LLVMInt8Type(), 0) }; add_function(exec, "std_string_is_eq", LLVMInt1Type(), string_eq_func_params, ARRLEN(string_eq_func_params), std_string_is_eq, false, false); LLVMTypeRef any_eq_func_params[] = { LLVMPointerType(LLVMInt8Type(), 0), LLVMPointerType(LLVMInt8Type(), 0) }; add_function(exec, "std_any_is_eq", LLVMInt1Type(), any_eq_func_params, ARRLEN(any_eq_func_params), std_any_is_eq, false, false); LLVMTypeRef sleep_func_params[] = { LLVMInt32Type() }; add_function(exec, "std_sleep", LLVMInt32Type(), sleep_func_params, ARRLEN(sleep_func_params), std_sleep, false, false); LLVMTypeRef random_func_params[] = { LLVMInt32Type(), LLVMInt32Type() }; add_function(exec, "std_get_random", LLVMInt32Type(), random_func_params, ARRLEN(random_func_params), std_get_random, false, false); LLVMTypeRef set_seed_func_params[] = { LLVMInt32Type() }; add_function(exec, "std_set_random_seed", LLVMVoidType(), set_seed_func_params, ARRLEN(set_seed_func_params), std_set_random_seed, false, false); LLVMTypeRef atoi_func_params[] = { LLVMPointerType(LLVMInt8Type(), 0) }; add_function(exec, "atoi", LLVMInt32Type(), atoi_func_params, ARRLEN(atoi_func_params), atoi, false, false); LLVMTypeRef atof_func_params[] = { LLVMPointerType(LLVMInt8Type(), 0) }; add_function(exec, "atof", LLVMDoubleType(), atof_func_params, ARRLEN(atof_func_params), atof, false, false); LLVMTypeRef int_pow_func_params[] = { LLVMInt32Type(), LLVMInt32Type() }; add_function(exec, "std_int_pow", LLVMInt32Type(), int_pow_func_params, ARRLEN(int_pow_func_params), std_int_pow, false, false); LLVMTypeRef time_func_params[] = { LLVMPointerType(LLVMVoidType(), 0) }; add_function(exec, "time", LLVMInt32Type(), time_func_params, ARRLEN(time_func_params), time, false, false); LLVMTypeRef sin_func_params[] = { LLVMDoubleType() }; add_function(exec, "sin", LLVMDoubleType(), sin_func_params, ARRLEN(sin_func_params), sin, false, false); LLVMTypeRef cos_func_params[] = { LLVMDoubleType() }; add_function(exec, "cos", LLVMDoubleType(), cos_func_params, ARRLEN(cos_func_params), cos, false, false); LLVMTypeRef tan_func_params[] = { LLVMDoubleType() }; add_function(exec, "tan", LLVMDoubleType(), tan_func_params, ARRLEN(tan_func_params), tan, false, false); LLVMTypeRef asin_func_params[] = { LLVMDoubleType() }; add_function(exec, "asin", LLVMDoubleType(), asin_func_params, ARRLEN(asin_func_params), asin, false, false); LLVMTypeRef acos_func_params[] = { LLVMDoubleType() }; add_function(exec, "acos", LLVMDoubleType(), acos_func_params, ARRLEN(acos_func_params), acos, false, false); LLVMTypeRef atan_func_params[] = { LLVMDoubleType() }; add_function(exec, "atan", LLVMDoubleType(), atan_func_params, ARRLEN(atan_func_params), atan, false, false); LLVMTypeRef sqrt_func_params[] = { LLVMDoubleType() }; add_function(exec, "sqrt", LLVMDoubleType(), sqrt_func_params, ARRLEN(sqrt_func_params), sqrt, false, false); LLVMTypeRef round_func_params[] = { LLVMDoubleType() }; add_function(exec, "round", LLVMDoubleType(), round_func_params, ARRLEN(round_func_params), round, false, false); LLVMTypeRef floor_func_params[] = { LLVMDoubleType() }; add_function(exec, "floor", LLVMDoubleType(), floor_func_params, ARRLEN(floor_func_params), floor, false, false); LLVMTypeRef pow_func_params[] = { LLVMDoubleType(), LLVMDoubleType() }; add_function(exec, "pow", LLVMDoubleType(), pow_func_params, ARRLEN(pow_func_params), pow, false, false); LLVMTypeRef get_char_func_params[] = { LLVMInt64Type() }; 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); LLVMTypeRef get_input_func_params[] = { LLVMInt64Type() }; 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); LLVMTypeRef set_clear_color_func_params[] = { LLVMInt32Type() }; 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); LLVMTypeRef set_fg_color_func_params[] = { LLVMInt32Type() }; 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); LLVMTypeRef set_bg_color_func_params[] = { LLVMInt32Type() }; 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); LLVMTypeRef set_cursor_func_params[] = { LLVMInt32Type(), LLVMInt32Type() }; add_function(exec, "std_term_set_cursor", LLVMVoidType(), set_cursor_func_params, ARRLEN(set_cursor_func_params), std_term_set_cursor, false, false); add_function(exec, "std_term_cursor_x", LLVMInt32Type(), NULL, 0, std_term_cursor_x, false, false); add_function(exec, "std_term_cursor_y", LLVMInt32Type(), NULL, 0, std_term_cursor_y, false, false); add_function(exec, "std_term_cursor_max_x", LLVMInt32Type(), NULL, 0, std_term_cursor_max_x, false, false); add_function(exec, "std_term_cursor_max_y", LLVMInt32Type(), NULL, 0, std_term_cursor_max_y, false, false); add_function(exec, "std_term_clear", LLVMVoidType(), NULL, 0, std_term_clear, false, false); LLVMTypeRef list_new_func_params[] = { LLVMInt64Type() }; add_function(exec, "std_list_new", LLVMPointerType(LLVMInt8Type(), 0), list_new_func_params, ARRLEN(list_new_func_params), std_list_new, true, false); LLVMTypeRef list_add_func_params[] = { LLVMInt64Type(), LLVMPointerType(LLVMInt8Type(), 0), LLVMInt32Type() }; add_function(exec, "std_list_add", LLVMVoidType(), list_add_func_params, ARRLEN(list_add_func_params), std_list_add, true, true); LLVMTypeRef list_get_func_params[] = { LLVMInt64Type(), LLVMPointerType(LLVMInt8Type(), 0), LLVMInt32Type() }; add_function(exec, "std_list_get", LLVMPointerType(LLVMInt8Type(), 0), list_get_func_params, ARRLEN(list_get_func_params), std_list_get, true, false); LLVMTypeRef list_set_func_params[] = { LLVMPointerType(LLVMInt8Type(), 0), LLVMInt32Type(), LLVMInt32Type() }; add_function(exec, "std_list_set", LLVMPointerType(LLVMInt8Type(), 0), list_set_func_params, ARRLEN(list_set_func_params), std_list_set, false, true); LLVMTypeRef list_length_func_params[] = { LLVMPointerType(LLVMInt8Type(), 0) }; add_function(exec, "std_list_length", LLVMInt32Type(), list_length_func_params, ARRLEN(list_length_func_params), std_list_length, false, false); LLVMTypeRef ceil_func_params[] = { LLVMDoubleType() }; add_function(exec, "ceil", LLVMDoubleType(), ceil_func_params, ARRLEN(ceil_func_params), ceil, false, false); LLVMTypeRef test_cancel_func_params[] = { LLVMInt64Type() }; add_function(exec, "test_cancel", LLVMVoidType(), test_cancel_func_params, ARRLEN(test_cancel_func_params), exec_handle_running_state, false, false); LLVMTypeRef stack_save_func_type = LLVMFunctionType(LLVMPointerType(LLVMVoidType(), 0), NULL, 0, false); LLVMAddFunction(exec->module, "llvm.stacksave.p0", stack_save_func_type); LLVMTypeRef stack_restore_func_params[] = { LLVMPointerType(LLVMVoidType(), 0) }; LLVMTypeRef stack_restore_func_type = LLVMFunctionType(LLVMVoidType(), stack_restore_func_params, ARRLEN(stack_restore_func_params), false); LLVMAddFunction(exec->module, "llvm.stackrestore.p0", stack_restore_func_type); LLVMTypeRef gc_root_begin_func_params[] = { LLVMInt64Type() }; add_function(exec, "gc_root_begin", LLVMVoidType(), gc_root_begin_func_params, ARRLEN(gc_root_begin_func_params), gc_root_begin, false, false); LLVMTypeRef gc_root_end_func_params[] = { LLVMInt64Type() }; add_function(exec, "gc_root_end", LLVMVoidType(), gc_root_end_func_params, ARRLEN(gc_root_end_func_params), gc_root_end, false, false); LLVMTypeRef gc_flush_func_params[] = { LLVMInt64Type() }; add_function(exec, "gc_flush", LLVMVoidType(), gc_flush_func_params, ARRLEN(gc_flush_func_params), gc_flush, false, false); LLVMTypeRef gc_add_root_func_params[] = { LLVMInt64Type(), LLVMPointerType(LLVMInt8Type(), 0) }; add_function(exec, "gc_add_root", LLVMVoidType(), gc_add_root_func_params, ARRLEN(gc_add_root_func_params), gc_add_root, false, false); LLVMTypeRef gc_add_temp_root_func_params[] = { LLVMInt64Type(), LLVMPointerType(LLVMInt8Type(), 0) }; 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); LLVMTypeRef gc_collect_func_params[] = { LLVMInt64Type() }; add_function(exec, "gc_collect", LLVMVoidType(), gc_collect_func_params, ARRLEN(gc_collect_func_params), gc_collect, false, false); LLVMTypeRef gc_root_save_func_params[] = { LLVMInt64Type() }; add_function(exec, "gc_root_save", LLVMVoidType(), gc_root_save_func_params, ARRLEN(gc_root_save_func_params), gc_root_save, false, false); LLVMTypeRef gc_root_restore_func_params[] = { LLVMInt64Type() }; add_function(exec, "gc_root_restore", LLVMVoidType(), gc_root_restore_func_params, ARRLEN(gc_root_restore_func_params), gc_root_restore, false, false); LLVMAddGlobal(exec->module, LLVMInt64Type(), "gc"); LLVMTypeRef main_func_type = LLVMFunctionType(LLVMVoidType(), NULL, 0, false); LLVMValueRef main_func = LLVMAddFunction(exec->module, MAIN_NAME, main_func_type); return main_func; } static void free_defined_functions(Exec* exec) { for (size_t i = 0; i < vector_size(exec->defined_functions); i++) { vector_free(exec->defined_functions[i].args); } vector_free(exec->defined_functions); } static bool compile_program(Exec* exec) { exec->compile_func_list = vector_create(); exec->global_variables = vector_create(); exec->gc_block_stack_len = 0; exec->control_stack_len = 0; exec->control_data_stack_len = 0; exec->variable_stack_len = 0; exec->variable_stack_frames_len = 0; exec->build_random = false; exec->gc_dirty = false; exec->gc_dirty_funcs = vector_create(); exec->defined_functions = vector_create(); exec->current_state = STATE_COMPILE; exec->module = LLVMModuleCreateWithName("scrap_module"); LLVMSetTarget(exec->module, TARGET_TRIPLE); LLVMValueRef main_func = register_globals(exec); LLVMBasicBlockRef entry = LLVMAppendBasicBlock(main_func, "entry"); exec->builder = LLVMCreateBuilder(); LLVMPositionBuilderAtEnd(exec->builder, entry); exec->gc_value = LLVMBuildLoad2(exec->builder, LLVMInt64Type(), LLVMGetNamedGlobal(exec->module, "gc"), "get_gc"); if (!build_gc_root_begin(exec, NULL)) return false; for (size_t i = 0; i < vector_size(exec->code); i++) { if (strcmp(exec->code[i].blocks[0].blockdef->id, "on_start")) continue; if (!evaluate_chain(exec, &exec->code[i])) { return false; } } if (!build_gc_root_end(exec, NULL)) return false; LLVMBuildRetVoid(exec->builder); for (size_t i = 0; i < vector_size(exec->code); i++) { if (!strcmp(exec->code[i].blocks[0].blockdef->id, "on_start")) continue; if (!evaluate_chain(exec, &exec->code[i])) { return false; } if (vector_size(exec->code[i].blocks) != 0 && exec->code[i].blocks[0].blockdef->type == BLOCKTYPE_HAT) { if (!build_gc_root_end(exec, NULL)) return false; build_call(exec, "gc_root_restore", CONST_GC); LLVMValueRef val = build_call_count(exec, "std_any_from_value", 2, CONST_GC, CONST_INTEGER(DATA_TYPE_NOTHING)); LLVMBuildRet(exec->builder, val); } } if (exec->build_random) { LLVMBasicBlockRef random_block = LLVMInsertBasicBlock(entry, "rand_init"); LLVMPositionBuilderAtEnd(exec->builder, random_block); LLVMValueRef time_val = build_call(exec, "time", LLVMConstPointerNull(LLVMPointerType(LLVMVoidType(), 0))); build_call(exec, "std_set_random_seed", time_val); LLVMBuildBr(exec->builder, entry); } char *error = NULL; if (LLVMVerifyModule(exec->module, LLVMReturnStatusAction , &error)) { exec_set_error(exec, NULL, gettext("Failed to build module: %s"), error); return false; } LLVMDisposeMessage(error); LLVMDumpModule(exec->module); LLVMDisposeBuilder(exec->builder); vector_free(exec->gc_dirty_funcs); vector_free(exec->global_variables); free_defined_functions(exec); return true; } static void vector_append(char** vec, const char* str) { if (vector_size(*vec) > 0 && (*vec)[vector_size(*vec) - 1] == 0) vector_pop(*vec); for (size_t i = 0; str[i]; i++) vector_add(vec, str[i]); vector_add(vec, 0); } static bool file_exists(char* path) { struct stat s; if (stat(path, &s)) return false; return S_ISREG(s.st_mode); } #ifndef _WIN32 static char* find_path_glob(char* search_path, int file_len) { glob_t glob_buf; if (glob(search_path, 0, NULL, &glob_buf)) return NULL; char* path = glob_buf.gl_pathv[0]; size_t len = strlen(path); path[len - file_len] = 0; char* out = vector_create(); vector_append(&out, path); globfree(&glob_buf); return out; } static char* find_crt(void) { char* out; if (file_exists("/usr/lib/crt1.o")) { out = vector_create(); vector_append(&out, "/usr/lib/"); return out; } if (file_exists("/usr/lib64/crt1.o")) { out = vector_create(); vector_append(&out, "/usr/lib64/"); return out; } out = find_path_glob("/usr/lib/x86_64*linux*/crt1.o", sizeof("crt1.o") - 1); if (out) return out; return find_path_glob("/usr/lib64/x86_64*linux*/crt1.o", sizeof("crt1.o") - 1); } static char* find_crt_begin(void) { char* out = find_path_glob("/usr/lib/gcc/x86_64*linux*/*/crtbegin.o", sizeof("crtbegin.o") - 1); if (out) return out; return find_path_glob("/usr/lib64/gcc/x86_64*linux*/*/crtbegin.o", sizeof("crtbegin.o") - 1); } #endif static bool build_program(Exec* exec) { exec->current_state = STATE_PRE_EXEC; if (LLVMInitializeNativeTarget()) { exec_set_error(exec, NULL, "[LLVM] Native target initialization failed"); return false; } if (LLVMInitializeNativeAsmParser()) { exec_set_error(exec, NULL, "[LLVM] Native asm parser initialization failed"); return false; } if (LLVMInitializeNativeAsmPrinter()) { exec_set_error(exec, NULL, "[LLVM] Native asm printer initialization failed"); return false; } char *error = NULL; LLVMTargetRef target; if (LLVMGetTargetFromTriple(TARGET_TRIPLE, &target, &error)) { exec_set_error(exec, NULL, "[LLVM] Failed to get target: %s", error); LLVMDisposeMessage(error); return false; } LLVMTargetMachineOptionsRef machine_opts = LLVMCreateTargetMachineOptions(); LLVMTargetMachineOptionsSetCodeGenOptLevel(machine_opts, LLVMCodeGenLevelDefault); LLVMTargetMachineOptionsSetRelocMode(machine_opts, LLVMRelocPIC); LLVMTargetMachineRef machine = LLVMCreateTargetMachineWithOptions(target, TARGET_TRIPLE, machine_opts); if (!machine) { LLVMDisposeTargetMachineOptions(machine_opts); exec_set_error(exec, NULL, "[LLVM] Failed to create target machine"); return false; } LLVMDisposeTargetMachineOptions(machine_opts); if (LLVMTargetMachineEmitToFile(machine, exec->module, "output.o", LLVMObjectFile, &error)) { exec_set_error(exec, NULL, "[LLVM] Failed to save to file: %s", error); LLVMDisposeTargetMachine(machine); LLVMDisposeMessage(error); return false; } LLVMDisposeTargetMachine(machine); scrap_log(LOG_INFO, "Built object file successfully"); char link_error[1024]; char* command = vector_create(); #ifdef _WIN32 // Command for linking on Windows. This thing requires gcc, which is not ideal :/ 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())); #else char* crt_dir = find_crt(); if (!crt_dir) { exec_set_error(exec, NULL, "Could not find crt files for linking"); vector_free(command); return false; } char* crt_begin_dir = find_crt_begin(); scrap_log(LOG_INFO, "Crt dir: %s", crt_dir); if (crt_begin_dir) { scrap_log(LOG_INFO, "Crtbegin dir: %s", crt_begin_dir); } else { scrap_log(LOG_WARNING, "Crtbegin dir is not found!"); } vector_append(&command, TextFormat("%s ", project_config.linker_name)); vector_append(&command, "-dynamic-linker /lib64/ld-linux-x86-64.so.2 "); vector_append(&command, "-pie "); vector_append(&command, TextFormat("-o %s ", project_config.executable_name)); vector_append(&command, TextFormat("%scrti.o %sScrt1.o %scrtn.o ", crt_dir, crt_dir, crt_dir)); if (crt_begin_dir) vector_append(&command, TextFormat("%scrtbeginS.o %scrtendS.o ", crt_begin_dir, crt_begin_dir)); vector_append(&command, "output.o "); vector_append(&command, TextFormat("-L. -L%s -lscrapstd -L/usr/lib -L/lib -L/usr/local/lib -lm -lc", GetApplicationDirectory())); scrap_log(LOG_INFO, "Full command: \"%s\"", command); #endif bool res = spawn_process(command, link_error, 1024); if (res) { scrap_log(LOG_INFO, "Linked successfully"); } else { exec_set_error(exec, NULL, link_error); } vector_free(command); #ifndef _WIN32 vector_free(crt_dir); if (crt_begin_dir) vector_free(crt_begin_dir); #endif return res; } static bool run_program(Exec* exec) { exec->current_state = STATE_PRE_EXEC; if (LLVMInitializeNativeTarget()) { exec_set_error(exec, NULL, "[LLVM] Native target initialization failed"); return false; } if (LLVMInitializeNativeAsmParser()) { exec_set_error(exec, NULL, "[LLVM] Native asm parser initialization failed"); return false; } if (LLVMInitializeNativeAsmPrinter()) { exec_set_error(exec, NULL, "[LLVM] Native asm printer initialization failed"); return false; } LLVMLinkInMCJIT(); char *error = NULL; if (LLVMCreateExecutionEngineForModule(&exec->engine, exec->module, &error)) { exec_set_error(exec, NULL, "[LLVM] Failed to create execution engine: %s", error); LLVMDisposeMessage(error); return false; } for (size_t i = 0; i < vector_size(exec->compile_func_list); i++) { LLVMValueRef func = LLVMGetNamedFunction(exec->module, exec->compile_func_list[i].name); if (!func) continue; LLVMAddGlobalMapping(exec->engine, func, exec->compile_func_list[i].func); } vector_free(exec->compile_func_list); exec->gc = gc_new(MIN_MEMORY_LIMIT, MAX_MEMORY_LIMIT); Gc* gc_ref = &exec->gc; LLVMAddGlobalMapping(exec->engine, LLVMGetNamedGlobal(exec->module, "gc"), &gc_ref); // For some weird reason calling pthread_exit() inside LLVM results in segfault, so we avoid that by using setjmp. // This unfortunately leaks a little bit of memory inside LLVMRunFunction() though :P if (setjmp(exec->run_jump_buf)) { thread_exit(exec->thread, false); } else { memcpy(exec->gc.run_jump_buf, exec->run_jump_buf, sizeof(exec->gc.run_jump_buf)); exec->current_state = STATE_EXEC; LLVMGenericValueRef val = LLVMRunFunction(exec->engine, LLVMGetNamedFunction(exec->module, "llvm_main"), 0, NULL); LLVMDisposeGenericValue(val); } return true; } ================================================ FILE: src/compiler.h ================================================ // Scrap is a project that allows anyone to build software using simple, block based interface. // // Copyright (C) 2024-2026 Grisshink // // This software is provided 'as-is', without any express or implied // warranty. In no event will the authors be held liable for any damages // arising from the use of this software. // // Permission is granted to anyone to use this software for any purpose, // including commercial applications, and to alter it and redistribute it // freely, subject to the following restrictions: // // 1. The origin of this software must not be misrepresented; you must not // claim that you wrote the original software. If you use this software // in a product, an acknowledgment in the product documentation would be // appreciated but is not required. // 2. Altered source versions must be plainly marked as such, and must not be // misrepresented as being the original software. // 3. This notice may not be removed or altered from any source distribution. #ifndef COMPILER_H #define COMPILER_H #include "gc.h" #include "ast.h" #include "thread.h" #include #include #include #define VM_ARG_STACK_SIZE 1024 #define VM_CONTROL_STACK_SIZE 1024 #define VM_CONTROL_DATA_STACK_SIZE 32768 #define VM_VARIABLE_STACK_SIZE 1024 #define control_data_stack_push_data(data, type) \ if (exec->control_data_stack_len + sizeof(type) > VM_CONTROL_DATA_STACK_SIZE) { \ scrap_log(LOG_ERROR, "[LLVM] Control stack overflow"); \ return false; \ } \ *(type *)(exec->control_data_stack + exec->control_data_stack_len) = (data); \ exec->control_data_stack_len += sizeof(type); #define control_data_stack_pop_data(data, type) \ if (sizeof(type) > exec->control_data_stack_len) { \ scrap_log(LOG_ERROR, "[LLVM] Control stack underflow"); \ return false; \ } \ exec->control_data_stack_len -= sizeof(type); \ data = *(type*)(exec->control_data_stack + exec->control_data_stack_len); typedef enum { CONTROL_STATE_NORMAL = 0, CONTROL_STATE_BEGIN, CONTROL_STATE_END, } ControlState; typedef union { LLVMValueRef value; const char* str; Blockdef* blockdef; } FuncArgData; typedef struct { DataType type; FuncArgData data; } FuncArg; typedef struct { FuncArg value; LLVMTypeRef type; const char* name; } Variable; typedef struct { size_t base_size; LLVMValueRef base_stack; } VariableStackFrame; typedef struct { const char* name; void* func; LLVMTypeRef type; bool dynamic; } CompileFunction; typedef struct { Blockdef* blockdef; LLVMValueRef arg; } DefineArgument; typedef struct { Blockdef* blockdef; LLVMValueRef func; DefineArgument* args; } DefineFunction; typedef enum { STATE_NONE, STATE_COMPILE, STATE_PRE_EXEC, STATE_EXEC, } CompilerState; typedef enum { COMPILER_MODE_JIT = 0, COMPILER_MODE_BUILD, } CompilerMode; typedef struct { LLVMValueRef root_begin; bool required; } GcBlock; typedef struct { BlockChain* code; LLVMModuleRef module; LLVMBuilderRef builder; LLVMExecutionEngineRef engine; Block* control_stack[VM_CONTROL_STACK_SIZE]; size_t control_stack_len; GcBlock gc_block_stack[VM_CONTROL_STACK_SIZE]; size_t gc_block_stack_len; unsigned char control_data_stack[VM_CONTROL_DATA_STACK_SIZE]; size_t control_data_stack_len; Variable variable_stack[VM_VARIABLE_STACK_SIZE]; size_t variable_stack_len; Variable* global_variables; VariableStackFrame variable_stack_frames[VM_VARIABLE_STACK_SIZE]; size_t variable_stack_frames_len; CompileFunction* compile_func_list; DefineFunction* defined_functions; Gc gc; LLVMValueRef gc_value; CompilerState current_state; char current_error[MAX_ERROR_LEN]; Block* current_error_block; bool build_random; // Needed for compiler to determine if some block uses gc_malloc so we could call gc_flush afterwards bool gc_dirty; LLVMValueRef* gc_dirty_funcs; jmp_buf run_jump_buf; Thread* thread; CompilerMode current_mode; } Exec; #define MAIN_NAME "llvm_main" #define CONST_NOTHING LLVMConstPointerNull(LLVMVoidType()) #define CONST_INTEGER(val) LLVMConstInt(LLVMInt32Type(), val, true) #define CONST_BOOLEAN(val) LLVMConstInt(LLVMInt1Type(), val, false) #define CONST_FLOAT(val) LLVMConstReal(LLVMDoubleType(), val) #define CONST_STRING_LITERAL(val) LLVMBuildGlobalStringPtr(exec->builder, val, "") #define CONST_GC exec->gc_value #define CONST_EXEC LLVMConstInt(LLVMInt64Type(), (unsigned long long)exec, false) #define _DATA(t, val) (FuncArg) { \ .type = t, \ .data = (FuncArgData) { \ .value = val, \ }, \ } #define DATA_BOOLEAN(val) _DATA(DATA_TYPE_BOOL, val) #define DATA_STRING(val) _DATA(DATA_TYPE_STRING, val) #define DATA_INTEGER(val) _DATA(DATA_TYPE_INTEGER, val) #define DATA_FLOAT(val) _DATA(DATA_TYPE_FLOAT, val) #define DATA_LIST(val) _DATA(DATA_TYPE_LIST, val) #define DATA_COLOR(val) _DATA(DATA_TYPE_COLOR, val) #define DATA_ANY(val) _DATA(DATA_TYPE_ANY, val) #define DATA_UNKNOWN _DATA(DATA_TYPE_UNKNOWN, NULL) #define DATA_NOTHING _DATA(DATA_TYPE_NOTHING, CONST_NOTHING) typedef bool (*BlockCompileFunc)(Exec* exec, Block* block, int argc, FuncArg* argv, FuncArg* return_val, ControlState control_state); Exec exec_new(Thread* thread, CompilerMode mode); bool exec_run(void* e); void exec_cleanup(void* e); void exec_free(Exec* exec); void exec_set_error(Exec* exec, Block* block, const char* fmt, ...); bool variable_stack_push(Exec* exec, Block* block, Variable variable); Variable* variable_get(Exec* exec, const char* var_name); void global_variable_add(Exec* exec, Variable variable); LLVMValueRef build_gc_root_begin(Exec* exec, Block* block); LLVMValueRef build_gc_root_end(Exec* exec, Block* block); LLVMValueRef build_call(Exec* exec, const char* func_name, ...); LLVMValueRef build_call_count(Exec* exec, const char* func_name, size_t func_param_count, ...); DefineFunction* define_function(Exec* exec, Blockdef* blockdef); DefineArgument* get_custom_argument(Exec* exec, Blockdef* blockdef, DefineFunction** func); #endif // COMPILER_H ================================================ FILE: src/config.h ================================================ // Scrap is a project that allows anyone to build software using simple, block based interface. // // Copyright (C) 2024-2026 Grisshink // // This software is provided 'as-is', without any express or implied // warranty. In no event will the authors be held liable for any damages // arising from the use of this software. // // Permission is granted to anyone to use this software for any purpose, // including commercial applications, and to alter it and redistribute it // freely, subject to the following restrictions: // // 1. The origin of this software must not be misrepresented; you must not // claim that you wrote the original software. If you use this software // in a product, an acknowledgment in the product documentation would be // appreciated but is not required. // 2. Altered source versions must be plainly marked as such, and must not be // misrepresented as being the original software. // 3. This notice may not be removed or altered from any source distribution. #define SCRAP_SAVE_VERSION 4 #define EDITOR_DEFAULT_PROJECT_NAME "project.scrp" #define DROP_TEX_WIDTH ((float)(config.ui_size - BLOCK_OUTLINE_SIZE * 4) / (float)drop_tex.height * (float)drop_tex.width) #define FONT_PATH_MAX_SIZE 256 #define FONT_SYMBOLS_MAX_SIZE 1024 #define ACTION_BAR_MAX_SIZE 128 #define ELEMENT_GAP ((float)config.ui_size * 0.25) #define SHADOW_DISTANCE floorf(1.66 * (float)config.ui_size / 32.0) #define BLOCK_OUTLINE_SIZE (2.0 * (float)config.ui_size / 32.0) #define BLOCK_TEXT_SIZE floorf((float)config.ui_size * 0.6) #define BLOCK_IMAGE_SIZE (config.ui_size - BLOCK_OUTLINE_SIZE * 4) #define BLOCK_PADDING (5.0 * (float)config.ui_size / 32.0) #define BLOCK_STRING_PADDING (10.0 * (float)config.ui_size / 32.0) #define BLOCK_CONTROL_INDENT (16.0 * (float)config.ui_size / 32.0) #define BLOCK_GHOST_OPACITY 0x99 #define BLOCK_ARG_OPACITY 0xdd #define PANEL_BACKGROUND_COLOR { 0x10, 0x10, 0x10, 0xff } #define TEXT_SELECTION_COLOR { 0x00, 0x60, 0xff, 0x80 } #define DATA_PATH "data/" #define LOCALE_PATH "locale/" #define CONFIG_PATH "config.txt" #define CONFIG_FOLDER_NAME "scrap" #define LICENSE_URL "https://github.com/Grisshink/scrap/blob/main/LICENSE" #define CODEPOINT_REGION_COUNT 3 #define DEBUG_BUFFER_LINES 32 #define DEBUG_BUFFER_LINE_SIZE 256 #define CATEGORY_CONTROL_COLOR { 0xff, 0x99, 0x00, 0xff } #define CATEGORY_TERMINAL_COLOR { 0x00, 0xaa, 0x44, 0xff } #define CATEGORY_MATH_COLOR { 0x00, 0xcc, 0x77, 0xff } #define CATEGORY_LOGIC_COLOR { 0x77, 0xcc, 0x44, 0xff } #define CATEGORY_STRING_COLOR { 0xff, 0x00, 0x99, 0xff } #define CATEGORY_MISC_COLOR { 0x00, 0x99, 0xff, 0xff } #define CATEGORY_DATA_COLOR { 0xff, 0x77, 0x00, 0xff } #define UNIMPLEMENTED_BLOCK_COLOR { 0x66, 0x66, 0x66, 0xff } #define MAX_ERROR_LEN 512 #define MIN_MEMORY_LIMIT 4194304 // 4 MB #define MAX_MEMORY_LIMIT 4294967296 // 4 GB ================================================ FILE: src/gc.c ================================================ // Scrap is a project that allows anyone to build software using simple, block based interface. // // Copyright (C) 2024-2026 Grisshink // // This software is provided 'as-is', without any express or implied // warranty. In no event will the authors be held liable for any damages // arising from the use of this software. // // Permission is granted to anyone to use this software for any purpose, // including commercial applications, and to alter it and redistribute it // freely, subject to the following restrictions: // // 1. The origin of this software must not be misrepresented; you must not // claim that you wrote the original software. If you use this software // in a product, an acknowledgment in the product documentation would be // appreciated but is not required. // 2. Altered source versions must be plainly marked as such, and must not be // misrepresented as being the original software. // 3. This notice may not be removed or altered from any source distribution. #include #include #include #include #include "gc.h" #include "std.h" #ifdef STANDALONE_STD #define EXIT exit(1) #define TRACE_LOG(loglevel, ...) fprintf(stderr, __VA_ARGS__) #ifdef _WIN32 #include #endif // _WIN32 #else #include "util.h" #define EXIT longjmp(gc->run_jump_buf, 1) #define TRACE_LOG(loglevel, ...) scrap_log(loglevel, __VA_ARGS__) #endif // STANDALONE_STD #define MIN(x, y) ((x) < (y) ? (x) : (y)) #define MAX_TEXT_BUFFER_LENGTH 512 static void gc_mark_refs(Gc* gc, GcChunkData* chunk); static const char *text_format(const char *text, ...) { static char buffer[MAX_TEXT_BUFFER_LENGTH] = {0}; buffer[0] = 0; va_list args; va_start(args, text); int requiredByteCount = vsnprintf(buffer, MAX_TEXT_BUFFER_LENGTH, text, args); va_end(args); // If requiredByteCount is larger than the MAX_TEXT_BUFFER_LENGTH, then overflow occured if (requiredByteCount >= MAX_TEXT_BUFFER_LENGTH) { // Inserting "..." at the end of the string to mark as truncated char *truncBuffer = buffer + MAX_TEXT_BUFFER_LENGTH - 4; // Adding 4 bytes = "...\0" sprintf(truncBuffer, "..."); } return buffer; } Gc gc_new(size_t memory_min, size_t memory_max) { return (Gc) { .chunks = vector_create(), .roots_stack = vector_create(), .roots_bases = vector_create(), .root_chunks = vector_create(), .root_temp_chunks = vector_create(), .memory_used = 0, .memory_allocated = memory_min, .memory_max = memory_max, }; } void gc_free(Gc* gc) { #ifdef DEBUG TRACE_LOG(LOG_INFO, "[GC] gc_free: used %zu bytes, allocated %zu chunks", gc->memory_used, vector_size(gc->chunks)); #endif for (size_t i = 0; i < vector_size(gc->chunks); i++) { free(gc->chunks[i].ptr); } vector_free(gc->roots_bases); vector_free(gc->root_chunks); vector_free(gc->root_temp_chunks); vector_free(gc->roots_stack); vector_free(gc->chunks); gc->memory_max = 0; gc->memory_allocated = 0; gc->memory_used = 0; } static void gc_mark_any(Gc* gc, AnyValue* any) { GcChunkData* chunk_inner; if (any->type == DATA_TYPE_LIST) { chunk_inner = ((GcChunkData*)any->data.list_val) - 1; if (!chunk_inner->marked) { chunk_inner->marked = 1; gc_mark_refs(gc, chunk_inner); } } else if (any->type == DATA_TYPE_STRING) { chunk_inner = ((GcChunkData*)any->data.str_val) - 1; chunk_inner->marked = 1; } } static void gc_mark_refs(Gc* gc, GcChunkData* chunk) { switch (chunk->data_type) { case DATA_TYPE_LIST: ; List* list = (List*)chunk->data; if (!list->values) break; GcChunkData* list_values_chunk = ((GcChunkData*)list->values) - 1; list_values_chunk->marked = 1; for (long i = 0; i < list->size; i++) { gc_mark_any(gc, &list->values[i]); } break; case DATA_TYPE_ANY: gc_mark_any(gc, (AnyValue*)chunk->data); break; default: break; } } void gc_collect(Gc* gc) { #ifdef DEBUG #if defined(_WIN32) && defined(STANDALONE_STD) long t; long t_freq; QueryPerformanceCounter((LARGE_INTEGER*)&t); QueryPerformanceFrequency((LARGE_INTEGER*)&t_freq); #else struct timespec t; clock_gettime(CLOCK_MONOTONIC, &t); #endif // defined(_WIN32) && defined(STANDALONE_STD) #endif // DEBUG // Mark roots for (size_t i = 0; i < vector_size(gc->root_chunks); i++) { GcChunkData* chunk = (*gc->root_chunks[i]) - 1; if (chunk->marked) continue; chunk->marked = 1; gc_mark_refs(gc, chunk); } for (size_t i = 0; i < vector_size(gc->root_temp_chunks); i++) { if (gc->root_temp_chunks[i]->marked) continue; gc->root_temp_chunks[i]->marked = 1; gc_mark_refs(gc, gc->root_temp_chunks[i]); } // Find unmarked chunks size_t memory_freed = 0; size_t chunks_deleted = 0; for (int i = vector_size(gc->chunks) - 1; i >= 0; i--) { if (gc->chunks[i].ptr->marked) { gc->chunks[i].ptr->marked = false; continue; } free(gc->chunks[i].ptr); memory_freed += gc->chunks[i].len; chunks_deleted++; vector_remove(gc->chunks, i); } for (size_t i = 0; i < vector_size(gc->root_chunks); i++) { GcChunkData* chunk = (*gc->root_chunks[i]) - 1; chunk->marked = 0; } gc->memory_used -= memory_freed; #ifdef DEBUG #if defined(_WIN32) && defined(STANDALONE_STD) long end_time; QueryPerformanceCounter((LARGE_INTEGER*)&end_time); double gc_time = (double)(end_time - t) * 1e+6 / (double)t_freq; #else struct timespec end_time; clock_gettime(CLOCK_MONOTONIC, &end_time); double gc_time = (end_time.tv_sec - t.tv_sec) * 1e+6 + (end_time.tv_nsec - t.tv_nsec) * 1e-3; #endif // defined(_WIN32) && defined(STANDALONE_STD) TRACE_LOG(LOG_INFO, "[GC] gc_collect: freed %zu bytes, deleted %zu chunks, time: %.2fus", memory_freed, chunks_deleted, gc_time); #endif // DEBUG } void* gc_malloc(Gc* gc, size_t size, DataType data_type) { assert(vector_size(gc->roots_stack) > 0); if (size > gc->memory_max) { 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)); EXIT; } if (gc->memory_used + size > gc->memory_allocated) gc_collect(gc); if (gc->memory_used + size > gc->memory_allocated) { gc->memory_allocated = MIN(gc->memory_allocated * 2, gc->memory_max); #ifdef DEBUG TRACE_LOG(LOG_WARNING, "[GC] gc_malloc: raising memory limit to %zu bytes", gc->memory_allocated); #endif } if (gc->memory_used + size > gc->memory_allocated) { 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)); EXIT; } GcChunkData* chunk_data = malloc(size + sizeof(GcChunkData)); if (chunk_data == NULL) return NULL; chunk_data->marked = 0; chunk_data->data_type = data_type; GcChunk chunk = (GcChunk) { .ptr = chunk_data, .len = size, }; vector_add(&gc->chunks, chunk); vector_add(&gc->root_temp_chunks, chunk.ptr); gc->memory_used += size; return chunk_data->data; } void gc_root_begin(Gc* gc) { if (vector_size(gc->roots_stack) > 1024) { std_term_print_str("*[GC] Root stack overflow!*"); EXIT; } GcRoot* root = vector_add_dst(&gc->roots_stack); root->chunks_base = vector_size(gc->root_chunks); root->temp_chunks_base = vector_size(gc->root_temp_chunks); } void gc_root_end(Gc* gc) { assert(vector_size(gc->roots_stack) > 0); vector_get_header(gc->root_chunks)->size = gc->roots_stack[vector_size(gc->roots_stack) - 1].chunks_base; vector_get_header(gc->root_temp_chunks)->size = gc->roots_stack[vector_size(gc->roots_stack) - 1].temp_chunks_base; vector_pop(gc->roots_stack); } void gc_add_root(Gc* gc, void* stack_ptr) { vector_add(&gc->root_chunks, stack_ptr); } void gc_add_temp_root(Gc* gc, void* ptr) { GcChunkData* chunk = ((GcChunkData*)ptr) - 1; vector_add(&gc->root_temp_chunks, chunk); } void gc_root_save(Gc* gc) { if (vector_size(gc->roots_bases) > 1024) { std_term_print_str("*[GC] Root stack overflow!*"); EXIT; } vector_add(&gc->roots_bases, vector_size(gc->roots_stack)); } void gc_root_restore(Gc* gc) { if (vector_size(gc->roots_bases) == 0) { std_term_print_str("*[GC] Root stack underflow!*"); EXIT; } size_t* size = &vector_get_header(gc->roots_stack)->size; size_t prev_size = *size; *size = gc->roots_bases[vector_size(gc->roots_bases) - 1]; vector_pop(gc->roots_bases); if (*size == 0) { vector_clear(gc->root_chunks); vector_clear(gc->root_temp_chunks); } else if (*size < prev_size) { vector_get_header(gc->root_chunks)->size = gc->roots_stack[*size].chunks_base; vector_get_header(gc->root_temp_chunks)->size = gc->roots_stack[*size].temp_chunks_base; } } void gc_flush(Gc* gc) { vector_get_header(gc->root_temp_chunks)->size = gc->roots_stack[vector_size(gc->roots_stack) - 1].temp_chunks_base; } ================================================ FILE: src/gc.h ================================================ // Scrap is a project that allows anyone to build software using simple, block based interface. // // Copyright (C) 2024-2026 Grisshink // // This software is provided 'as-is', without any express or implied // warranty. In no event will the authors be held liable for any damages // arising from the use of this software. // // Permission is granted to anyone to use this software for any purpose, // including commercial applications, and to alter it and redistribute it // freely, subject to the following restrictions: // // 1. The origin of this software must not be misrepresented; you must not // claim that you wrote the original software. If you use this software // in a product, an acknowledgment in the product documentation would be // appreciated but is not required. // 2. Altered source versions must be plainly marked as such, and must not be // misrepresented as being the original software. // 3. This notice may not be removed or altered from any source distribution. #ifndef SCRAP_GC_H #define SCRAP_GC_H #include #ifndef STANDALONE_STD #include #endif #include "ast.h" #include "vec.h" typedef struct { unsigned char marked; DataType data_type; unsigned char data[]; } GcChunkData; typedef struct { GcChunkData* ptr; size_t len; } GcChunk; typedef struct { size_t chunks_base; size_t temp_chunks_base; } GcRoot; typedef struct { GcChunk* chunks; size_t* roots_bases; GcRoot* roots_stack; // NOTE: This variable stores a list of stack addresses pointing at gc_malloc'd memory, // so you need to offset a pointer by -1 before dereferencing GcChunkData GcChunkData*** root_chunks; GcChunkData** root_temp_chunks; size_t memory_used; size_t memory_allocated; size_t memory_max; #ifndef STANDALONE_STD jmp_buf run_jump_buf; #endif } Gc; Gc gc_new(size_t memory_min, size_t memory_max); void gc_free(Gc* gc); void gc_root_begin(Gc* gc); void gc_root_end(Gc* gc); void* gc_malloc(Gc* gc, size_t size, DataType data_type); void gc_collect(Gc* gc); void gc_flush(Gc* gc); void gc_add_root(Gc* gc, void* ptr); void gc_add_temp_root(Gc* gc, void* ptr); void gc_root_save(Gc* gc); void gc_root_restore(Gc* gc); #endif // SCRAP_GC_H ================================================ FILE: src/interpreter.c ================================================ // Scrap is a project that allows anyone to build software using simple, block based interface. // // Copyright (C) 2024-2026 Grisshink // // This software is provided 'as-is', without any express or implied // warranty. In no event will the authors be held liable for any damages // arising from the use of this software. // // Permission is granted to anyone to use this software for any purpose, // including commercial applications, and to alter it and redistribute it // freely, subject to the following restrictions: // // 1. The origin of this software must not be misrepresented; you must not // claim that you wrote the original software. If you use this software // in a product, an acknowledgment in the product documentation would be // appreciated but is not required. // 2. Altered source versions must be plainly marked as such, and must not be // misrepresented as being the original software. // 3. This notice may not be removed or altered from any source distribution. #include "scrap.h" #include "ast.h" #include "vec.h" #include #include #include #include #include void arg_stack_push_arg(Exec* exec, AnyValue data); void arg_stack_undo_args(Exec* exec, size_t count); void variable_stack_pop_layer(Exec* exec); void variable_stack_cleanup(Exec* exec); void chain_stack_push(Exec* exec, ChainStackData data); void chain_stack_pop(Exec* exec); bool exec_block(Exec* exec, Block* block, AnyValue* block_return, ControlState control_state, AnyValue control_arg); void define_function(Exec* exec, Blockdef* blockdef, BlockChain* chain) { DefineFunction* func = vector_add_dst(&exec->defined_functions); func->blockdef = blockdef; func->run_chain = chain; func->args = vector_create(); int arg_ind = 0; for (size_t i = 0; i < vector_size(blockdef->inputs); i++) { if (blockdef->inputs[i].type != INPUT_ARGUMENT) continue; DefineArgument* arg = vector_add_dst(&func->args); arg->blockdef = blockdef->inputs[i].data.arg.blockdef; arg->arg_ind = arg_ind++; } } Exec exec_new(Thread* thread) { Exec exec = (Exec) { .code = NULL, .arg_stack_len = 0, .control_stack_len = 0, .thread = thread, .current_error_block = NULL, }; exec.current_error[0] = 0; return exec; } void exec_free(Exec* exec) { (void) exec; } bool exec_run(void* e) { Exec* exec = e; exec->arg_stack_len = 0; exec->control_stack_len = 0; exec->chain_stack_len = 0; exec->running_chain = NULL; exec->defined_functions = vector_create(); exec->gc = gc_new(MIN_MEMORY_LIMIT, MAX_MEMORY_LIMIT); SetRandomSeed(time(NULL)); for (size_t i = 0; i < vector_size(exec->code); i++) { Block* block = &exec->code[i].blocks[0]; if (strcmp(block->blockdef->id, "define_block")) continue; for (size_t j = 0; j < vector_size(block->arguments); j++) { if (block->arguments[j].type != ARGUMENT_BLOCKDEF) continue; define_function(exec, block->arguments[j].data.blockdef, &exec->code[i]); } } for (size_t i = 0; i < vector_size(exec->code); i++) { Block* block = &exec->code[i].blocks[0]; if (block->blockdef->type != BLOCKTYPE_HAT) continue; bool cont = false; for (size_t j = 0; j < vector_size(block->arguments); j++) { if (block->arguments[j].type == ARGUMENT_BLOCKDEF) { cont = true; break; } } if (cont) continue; AnyValue bin; if (!exec_run_chain(exec, &exec->code[i], -1, NULL, &bin)) { exec->running_chain = NULL; return false; } exec->running_chain = NULL; } return true; } void exec_cleanup(void* e) { Exec* exec = e; for (size_t i = 0; i < vector_size(exec->defined_functions); i++) { vector_free(exec->defined_functions[i].args); } vector_free(exec->defined_functions); variable_stack_cleanup(exec); arg_stack_undo_args(exec, exec->arg_stack_len); gc_free(&exec->gc); } void exec_set_error(Exec* exec, Block* block, const char* fmt, ...) { exec->current_error_block = block; va_list va; va_start(va, fmt); vsnprintf(exec->current_error, MAX_ERROR_LEN, fmt, va); va_end(va); scrap_log(LOG_ERROR, "[EXEC] %s", exec->current_error); } bool evaluate_argument(Exec* exec, Argument* arg, AnyValue* return_val) { static_assert(ARGUMENT_LAST == 5, "Exhaustive argument type in evaluate_argument"); switch (arg->type) { case ARGUMENT_TEXT: case ARGUMENT_CONST_STRING: *return_val = DATA_LITERAL(arg->data.text); return true; case ARGUMENT_BLOCK: if (!exec_block(exec, &arg->data.block, return_val, CONTROL_STATE_NORMAL, (AnyValue) {0})) { return false; } return true; case ARGUMENT_BLOCKDEF: return true; case ARGUMENT_COLOR: *return_val = DATA_COLOR(CONVERT_COLOR(arg->data.color, StdColor)); return true; default: assert(false && "Unimplemented argument type in evaluate_argument"); } return false; } bool exec_block(Exec* exec, Block* block, AnyValue* block_return, ControlState control_state, AnyValue control_arg) { if (!block->blockdef) { exec_set_error(exec, block, gettext("Tried to execute block without definition")); return false; } if (!block->blockdef->func) { exec_set_error(exec, block, gettext("Tried to execute block \"%s\" without implementation"), block->blockdef->id); return false; } BlockFunc execute_block = block->blockdef->func; int stack_begin = exec->arg_stack_len; if (block->blockdef->type == BLOCKTYPE_CONTROLEND && control_state == CONTROL_STATE_BEGIN) { arg_stack_push_arg(exec, control_arg); } size_t last_temps = vector_size(exec->gc.root_temp_chunks); if (control_state != CONTROL_STATE_END) { for (vec_size_t i = 0; i < vector_size(block->arguments); i++) { AnyValue arg; if (!evaluate_argument(exec, &block->arguments[i], &arg)) { scrap_log(LOG_ERROR, "[VM] From block id: \"%s\" (at block %p)", block->blockdef->id, &block); return false; } arg_stack_push_arg(exec, arg); } } if (!execute_block(exec, block, exec->arg_stack_len - stack_begin, exec->arg_stack + stack_begin, block_return, control_state)) { scrap_log(LOG_ERROR, "[VM] Error from block id: \"%s\" (at block %p)", block->blockdef->id, &block); return false; } arg_stack_undo_args(exec, exec->arg_stack_len - stack_begin); if (!block->parent && vector_size(exec->gc.root_temp_chunks) > last_temps) gc_flush(&exec->gc); return true; } #define BLOCKDEF chain->blocks[i].blockdef bool exec_run_chain(Exec* exec, BlockChain* chain, int argc, AnyValue* argv, AnyValue* return_val) { size_t base_len = exec->control_stack_len; chain_stack_push(exec, (ChainStackData) { .skip_block = false, .layer = 0, .running_ind = 0, .custom_argc = argc, .custom_argv = argv, .is_returning = false, .return_arg = (AnyValue) {0}, }); gc_root_begin(&exec->gc); gc_root_save(&exec->gc); exec->running_chain = chain; AnyValue block_return; for (size_t i = 0; i < vector_size(chain->blocks); i++) { ChainStackData* chain_data = &exec->chain_stack[exec->chain_stack_len - 1]; if (chain_data->skip_block) { int layer = chain_data->layer; while (i < vector_size(chain->blocks)) { if (BLOCKDEF->type == BLOCKTYPE_END || BLOCKDEF->type == BLOCKTYPE_CONTROLEND) { layer--; if (layer < chain_data->layer) break; } if (BLOCKDEF->type == BLOCKTYPE_CONTROL || BLOCKDEF->type == BLOCKTYPE_CONTROLEND) { layer++; } i++; } chain_data->skip_block = false; } thread_handle_stopping_state(exec->thread); size_t block_ind = i; chain_data->running_ind = i; ControlState control_state = BLOCKDEF->type == BLOCKTYPE_CONTROL ? CONTROL_STATE_BEGIN : CONTROL_STATE_NORMAL; if (chain_data->is_returning) break; if (BLOCKDEF->type == BLOCKTYPE_END || BLOCKDEF->type == BLOCKTYPE_CONTROLEND) { if (chain_data->layer == 0) continue; variable_stack_pop_layer(exec); chain_data->layer--; control_stack_pop_data(block_ind, size_t); control_stack_pop_data(block_return, AnyValue); gc_root_end(&exec->gc); control_state = CONTROL_STATE_END; } if (!exec_block(exec, &chain->blocks[block_ind], &block_return, control_state, (AnyValue){0})) { chain_stack_pop(exec); return false; } exec->running_chain = chain; if (chain_data->running_ind != i) i = chain_data->running_ind; if (BLOCKDEF->type == BLOCKTYPE_CONTROLEND && block_ind != i) { control_state = CONTROL_STATE_BEGIN; if (!exec_block(exec, &chain->blocks[i], &block_return, control_state, block_return)) { chain_stack_pop(exec); return false; } if (chain_data->running_ind != i) i = chain_data->running_ind; } if (BLOCKDEF->type == BLOCKTYPE_CONTROL || BLOCKDEF->type == BLOCKTYPE_CONTROLEND) { control_stack_push_data(block_return, AnyValue); control_stack_push_data(i, size_t); gc_root_begin(&exec->gc); chain_data->layer++; } } gc_root_restore(&exec->gc); gc_root_end(&exec->gc); *return_val = exec->chain_stack[exec->chain_stack_len - 1].return_arg; while (exec->chain_stack[exec->chain_stack_len - 1].layer >= 0) { variable_stack_pop_layer(exec); exec->chain_stack[exec->chain_stack_len - 1].layer--; } exec->control_stack_len = base_len; chain_stack_pop(exec); return true; } #undef BLOCKDEF void exec_set_skip_block(Exec* exec) { exec->chain_stack[exec->chain_stack_len - 1].skip_block = true; } Variable* variable_stack_push_var(Exec* exec, const char* name, AnyValue arg) { if (exec->variable_stack_len >= VM_VARIABLE_STACK_SIZE) { scrap_log(LOG_ERROR, "[VM] Variable stack overflow"); thread_exit(exec->thread, false); } if (*name == 0) return NULL; Variable var; var.name = name; var.chunk_header.marked = 0; var.chunk_header.data_type = DATA_TYPE_ANY; var.value_ptr = &exec->variable_stack[exec->variable_stack_len].value; var.value = arg; var.chain_layer = exec->chain_stack_len - 1; var.layer = exec->chain_stack[var.chain_layer].layer; exec->variable_stack[exec->variable_stack_len++] = var; return &exec->variable_stack[exec->variable_stack_len - 1]; } void variable_stack_pop_layer(Exec* exec) { size_t count = 0; for (int i = exec->variable_stack_len - 1; i >= 0 && exec->variable_stack[i].layer == exec->chain_stack[exec->chain_stack_len - 1].layer && exec->variable_stack[i].chain_layer == exec->chain_stack_len - 1; i--) { count++; } exec->variable_stack_len -= count; } void variable_stack_cleanup(Exec* exec) { exec->variable_stack_len = 0; } Variable* variable_stack_get_variable(Exec* exec, const char* name) { for (int i = exec->variable_stack_len - 1; i >= 0; i--) { if (exec->variable_stack[i].chain_layer != exec->chain_stack_len - 1) break; if (!strcmp(exec->variable_stack[i].name, name)) return &exec->variable_stack[i]; } if (exec->chain_stack_len > 0) { for (size_t i = 0; i < exec->variable_stack_len; i++) { if (exec->variable_stack[i].layer != 0 || exec->variable_stack[i].chain_layer != 0) break; if (!strcmp(exec->variable_stack[i].name, name)) return &exec->variable_stack[i]; } } return NULL; } void chain_stack_push(Exec* exec, ChainStackData data) { if (exec->chain_stack_len >= VM_CHAIN_STACK_SIZE) { scrap_log(LOG_ERROR, "[VM] Chain stack overflow"); thread_exit(exec->thread, false); } exec->chain_stack[exec->chain_stack_len++] = data; } void chain_stack_pop(Exec* exec) { if (exec->chain_stack_len == 0) { scrap_log(LOG_ERROR, "[VM] Chain stack underflow"); thread_exit(exec->thread, false); } exec->chain_stack_len--; } void arg_stack_push_arg(Exec* exec, AnyValue arg) { if (exec->arg_stack_len >= VM_ARG_STACK_SIZE) { scrap_log(LOG_ERROR, "[VM] Arg stack overflow"); thread_exit(exec->thread, false); } exec->arg_stack[exec->arg_stack_len++] = arg; } void arg_stack_undo_args(Exec* exec, size_t count) { if (count > exec->arg_stack_len) { scrap_log(LOG_ERROR, "[VM] Arg stack underflow"); thread_exit(exec->thread, false); } exec->arg_stack_len -= count; } ================================================ FILE: src/interpreter.h ================================================ // Scrap is a project that allows anyone to build software using simple, block based interface. // // Copyright (C) 2024-2026 Grisshink // // This software is provided 'as-is', without any express or implied // warranty. In no event will the authors be held liable for any damages // arising from the use of this software. // // Permission is granted to anyone to use this software for any purpose, // including commercial applications, and to alter it and redistribute it // freely, subject to the following restrictions: // // 1. The origin of this software must not be misrepresented; you must not // claim that you wrote the original software. If you use this software // in a product, an acknowledgment in the product documentation would be // appreciated but is not required. // 2. Altered source versions must be plainly marked as such, and must not be // misrepresented as being the original software. // 3. This notice may not be removed or altered from any source distribution. #ifndef INTERPRETER_H #define INTERPRETER_H #include "raylib.h" #include "ast.h" #include "std.h" #include "thread.h" #define VM_ARG_STACK_SIZE 1024 #define VM_CONTROL_STACK_SIZE 32768 #define VM_VARIABLE_STACK_SIZE 1024 #define VM_CHAIN_STACK_SIZE 1024 typedef struct Variable Variable; typedef struct Exec Exec; typedef struct ChainStackData ChainStackData; typedef enum { CONTROL_STATE_NORMAL = 0, CONTROL_STATE_BEGIN, CONTROL_STATE_END, } ControlState; typedef bool (*BlockFunc)(Exec* exec, Block* block, int argc, AnyValue* argv, AnyValue* return_val, ControlState control_state); struct Variable { const char* name; // This is a pretty hacky way to make gc think this area of memory is allocated with // gc_malloc even though it is not. The data_type field in header should be set to // DATA_TYPE_ANY so that gc could check the potential heap references inside the any // value. This essentially allows interpreter to change variable type without // invalidating gc root pointers. AnyValue* value_ptr; GcChunkData chunk_header; AnyValue value; size_t chain_layer; int layer; }; struct ChainStackData { bool skip_block; int layer; size_t running_ind; int custom_argc; AnyValue* custom_argv; bool is_returning; AnyValue return_arg; }; typedef struct { Blockdef* blockdef; int arg_ind; } DefineArgument; typedef struct { Blockdef* blockdef; BlockChain* run_chain; DefineArgument* args; } DefineFunction; struct Exec { BlockChain* code; AnyValue arg_stack[VM_ARG_STACK_SIZE]; size_t arg_stack_len; unsigned char control_stack[VM_CONTROL_STACK_SIZE]; size_t control_stack_len; Variable variable_stack[VM_VARIABLE_STACK_SIZE]; size_t variable_stack_len; ChainStackData chain_stack[VM_CHAIN_STACK_SIZE]; size_t chain_stack_len; DefineFunction* defined_functions; char current_error[MAX_ERROR_LEN]; Block* current_error_block; Thread* thread; BlockChain* running_chain; Gc gc; }; #define control_stack_push_data(data, type) do { \ if (exec->control_stack_len + sizeof(type) > VM_CONTROL_STACK_SIZE) { \ scrap_log(LOG_ERROR, "[VM] Control stack overflow"); \ thread_exit(exec->thread, false); \ } \ *(type *)(exec->control_stack + exec->control_stack_len) = (data); \ exec->control_stack_len += sizeof(type); \ } while (0) #define control_stack_pop_data(data, type) do { \ if (sizeof(type) > exec->control_stack_len) { \ scrap_log(LOG_ERROR, "[VM] Control stack underflow"); \ thread_exit(exec->thread, false); \ } \ exec->control_stack_len -= sizeof(type); \ data = *(type*)(exec->control_stack + exec->control_stack_len); \ } while (0) #define DATA_NOTHING (AnyValue) { \ .type = DATA_TYPE_NOTHING, \ .data = (AnyValueData) {0}, \ } #define DATA_INTEGER(val) (AnyValue) { \ .type = DATA_TYPE_INTEGER, \ .data = (AnyValueData) { .integer_val = (val) }, \ } #define DATA_FLOAT(val) (AnyValue) { \ .type = DATA_TYPE_FLOAT, \ .data = (AnyValueData) { .float_val = (val) }, \ } #define DATA_BOOL(val) (AnyValue) { \ .type = DATA_TYPE_BOOL, \ .data = (AnyValueData) { .integer_val = (val) }, \ } #define DATA_LITERAL(val) (AnyValue) { \ .type = DATA_TYPE_LITERAL, \ .data = (AnyValueData) { .literal_val = (val) }, \ } #define DATA_STRING(val) (AnyValue) { \ .type = DATA_TYPE_STRING, \ .data = (AnyValueData) { .str_val = (val) }, \ } #define DATA_LIST(val) (AnyValue) { \ .type = DATA_TYPE_LIST, \ .data = (AnyValueData) { .list_val = (val) }, \ } #define DATA_COLOR(val) (AnyValue) { \ .type = DATA_TYPE_COLOR, \ .data = (AnyValueData) { .color_val = (val) }, \ } Exec exec_new(Thread* thread); bool exec_run(void* e); void exec_cleanup(void* e); void exec_free(Exec* exec); bool exec_run_chain(Exec* exec, BlockChain* chain, int argc, AnyValue* argv, AnyValue* return_val); void exec_set_skip_block(Exec* exec); void exec_set_error(Exec* exec, Block* block, const char* fmt, ...); bool evaluate_argument(Exec* exec, Argument* arg, AnyValue* return_val); Variable* variable_stack_push_var(Exec* exec, const char* name, AnyValue arg); Variable* variable_stack_get_variable(Exec* exec, const char* name); #endif // INTERPRETER_H ================================================ FILE: src/platform.c ================================================ // Scrap is a project that allows anyone to build software using simple, block based interface. // // Copyright (C) 2024-2026 Grisshink // // This software is provided 'as-is', without any express or implied // warranty. In no event will the authors be held liable for any damages // arising from the use of this software. // // Permission is granted to anyone to use this software for any purpose, // including commercial applications, and to alter it and redistribute it // freely, subject to the following restrictions: // // 1. The origin of this software must not be misrepresented; you must not // claim that you wrote the original software. If you use this software // in a product, an acknowledgment in the product documentation would be // appreciated but is not required. // 2. Altered source versions must be plainly marked as such, and must not be // misrepresented as being the original software. // 3. This notice may not be removed or altered from any source distribution. #ifdef _WIN32 #include #else #include #include #endif #include #include #include #include #include #include #include "term.h" void scrap_set_env(const char* name, const char* value) { #ifdef _WIN32 char buf[256]; snprintf(buf, 256, "%s=%s", name, value); putenv(buf); SetEnvironmentVariableA(name, value); #else setenv(name, value, false); #endif // _WIN32 } #ifndef USE_INTERPRETER #ifndef _WIN32 static size_t next_arg(char* cmd, size_t i, char** out_arg) { *out_arg = NULL; while (cmd[i] == ' ') i++; if (cmd[i] == 0) return i; if (cmd[i] == '"') { i++; *out_arg = cmd + i; while (cmd[i] != '"' && cmd[i] != 0) i++; } else { *out_arg = cmd + i; while (cmd[i] != ' ' && cmd[i] != 0) i++; } if (cmd[i] == 0) { return i; } else { cmd[i] = 0; return i + 1; } } #endif bool spawn_process(char* command, char* error, size_t error_len) { #ifdef _WIN32 HANDLE read_pipe, write_pipe; STARTUPINFO start_info = {0}; PROCESS_INFORMATION proc_info = {0}; SECURITY_ATTRIBUTES pipe_attrs = {0}; pipe_attrs.nLength = sizeof(pipe_attrs); pipe_attrs.bInheritHandle = TRUE; if (!CreatePipe(&read_pipe, &write_pipe, &pipe_attrs, 0)) { snprintf(error, error_len, gettext("Failed to create a pipe. Error code: %ld"), GetLastError()); return false; } start_info.cb = sizeof(start_info); start_info.hStdError = write_pipe; start_info.hStdOutput = write_pipe; start_info.dwFlags = STARTF_USESTDHANDLES; if(!CreateProcessA( NULL, command, NULL, NULL, // No security attributes TRUE, // Allow to inherit handles CREATE_NO_WINDOW, // Don't spawn cmd for a process NULL, NULL, // Just give me a process &start_info, // Give STARTUPINFO &proc_info) // Get PROCESS_INFORMATION ) { long last_error = GetLastError(); snprintf(error, error_len, gettext("Failed to create a process. Error code: %ld"), last_error); if (last_error == 2) { // File not found size_t i = 0; while (command[i] != 0 && command[i] != ' ') i++; command[i] = 0; term_print_str(command); 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")); } CloseHandle(write_pipe); CloseHandle(read_pipe); return false; } CloseHandle(write_pipe); unsigned long size = 0; char buf[1024]; for (;;) { if (!ReadFile(read_pipe, buf, 1024 - 1 /* Save space for null terminator */, &size, NULL)) { long last_error = GetLastError(); if (last_error == ERROR_BROKEN_PIPE) break; snprintf(error, error_len, gettext("Failed to read from pipe. Error code: %ld"), last_error); CloseHandle(proc_info.hProcess); CloseHandle(proc_info.hThread); return false; } buf[size] = 0; term_print_str(buf); } WaitForSingleObject(proc_info.hProcess, INFINITE); unsigned long exit_code; if (!GetExitCodeProcess(proc_info.hProcess, &exit_code)) { snprintf(error, error_len, gettext("Failed to get exit code. Error code: %ld"), GetLastError()); CloseHandle(proc_info.hProcess); CloseHandle(proc_info.hThread); CloseHandle(read_pipe); return false; } if (exit_code) { snprintf(error, error_len, gettext("Linker exited with exit code: %ld"), exit_code); CloseHandle(proc_info.hProcess); CloseHandle(proc_info.hThread); CloseHandle(read_pipe); return false; } CloseHandle(proc_info.hProcess); CloseHandle(proc_info.hThread); CloseHandle(read_pipe); #else int pipefd[2]; if (pipe(pipefd) == -1) { snprintf(error, error_len, gettext("Failed to create a pipe: %s"), strerror(errno)); return false; } pid_t pid = fork(); if (pid == -1) { snprintf(error, error_len, gettext("Failed to fork a process: %s"), strerror(errno)); return false; } if (pid == 0) { // We are newborn // Close duplicate read end if (close(pipefd[0]) == -1) { perror("close"); exit(1); } // Replace stdout and stderr with pipe if (dup2(pipefd[1], 1) == -1) { perror("dup2"); exit(1); } if (dup2(pipefd[1], 2) == -1) { perror("dup2"); exit(1); } // Parse command line char* name; char* args[256]; size_t arg_len = 1; size_t i = 0; i = next_arg(command, i, &name); args[0] = name; args[255] = NULL; while (args[arg_len - 1] && arg_len < 255) i = next_arg(command, i, &args[arg_len++]); printf("Name: %s\n", name); printf("Args: "); for (char** arg = args; *arg; arg++) { printf("\"%s\", ", *arg); } printf("\n"); // Replace da child with linker if (execvp(name, args) == -1) { perror("execvp"); exit(1); } } else { // We are parent // Close duplicate write end if (close(pipefd[1]) == -1) { perror("close"); exit(1); } ssize_t size = 0; char buf[1024]; while ((size = read(pipefd[0], buf, 1024 - 1 /* Save space for null terminator */))) { if (size == -1) { perror("read"); exit(1); } buf[size] = 0; term_print_str(buf); } if (close(pipefd[0]) == -1) { perror("close"); exit(1); } // Wait for child to terminate int status; waitpid(pid, &status, 0); if (WIFEXITED(status)) { int exit_code = WEXITSTATUS(status); if (exit_code == 0) { return true; } else { snprintf(error, error_len, gettext("Linker exited with exit code: %d"), exit_code); return false; } } else if (WIFSIGNALED(status)) { snprintf(error, error_len, gettext("Linker signaled with signal number: %d"), WTERMSIG(status)); return false; } else { snprintf(error, error_len, gettext("Received unknown child status :/")); return false; } } #endif return true; } #endif // USE_INTERPRETER ================================================ FILE: src/render.c ================================================ // Scrap is a project that allows anyone to build software using simple, block based interface. // // Copyright (C) 2024-2026 Grisshink // // This software is provided 'as-is', without any express or implied // warranty. In no event will the authors be held liable for any damages // arising from the use of this software. // // Permission is granted to anyone to use this software for any purpose, // including commercial applications, and to alter it and redistribute it // freely, subject to the following restrictions: // // 1. The origin of this software must not be misrepresented; you must not // claim that you wrote the original software. If you use this software // in a product, an acknowledgment in the product documentation would be // appreciated but is not required. // 2. Altered source versions must be plainly marked as such, and must not be // misrepresented as being the original software. // 3. This notice may not be removed or altered from any source distribution. #include "scrap.h" #include "vec.h" #include "term.h" #define NANOSVG_IMPLEMENTATION #include "../external/nanosvg.h" #define NANOSVGRAST_IMPLEMENTATION #include "../external/nanosvgrast.h" #include #include #include #include #include #include typedef enum { BORDER_NORMAL = 0, BORDER_CONTROL, BORDER_CONTROL_BODY, BORDER_END, BORDER_CONTROL_END, BORDER_NOTCHED, } BorderType; typedef enum { RECT_NORMAL = 0, RECT_NOTCHED, RECT_TERMINAL, // Terminal rendering is handled specially as it needs to synchronize with its buffer } RectType; typedef enum { IMAGE_NORMAL = 0, IMAGE_STRETCHED, } ImageType; static void draw_code(void); static GuiElement* draw_blockchain(BlockChain* chain, bool ghost, bool show_previews, bool editable_arguments); static void argument_on_hover(GuiElement* el); static void argument_on_render(GuiElement* el); bool rl_vec_equal(Color lhs, Color rhs) { return lhs.r == rhs.r && lhs.g == rhs.g && lhs.b == rhs.b && lhs.a == rhs.a; } void actionbar_show(const char* text) { scrap_log(LOG_INFO, "[ACTION] %s", text); strncpy(editor.actionbar.text, text, sizeof(editor.actionbar.text) - 1); editor.actionbar.show_time = 3.0; } const char* sgettext(const char* msgid) { const char* msgval = gettext(msgid); if (msgval == msgid) msgval = strrchr(msgid, '|') + 1; if (msgval == (void*)1) msgval = msgid; return msgval; } static void draw_dots(void) { int win_width = GetScreenWidth(); int win_height = GetScreenHeight(); for (int y = MOD(-(int)editor.camera_pos.y, config.ui_size * 2); y < win_height; y += config.ui_size * 2) { for (int x = MOD(-(int)editor.camera_pos.x, config.ui_size * 2); x < win_width; x += config.ui_size * 2) { DrawRectangle(x, y, 2, 2, (Color) { 0x40, 0x40, 0x40, 0xff }); } } if (ui.shader_time == 1.0) return; if (!IsShaderValid(assets.line_shader)) return; BeginShaderMode(assets.line_shader); for (int y = MOD(-(int)editor.camera_pos.y, config.ui_size * 2); y < win_height; y += config.ui_size * 2) { DrawRectangle(0, y, win_width, 2, (Color) { 0x40, 0x40, 0x40, 0xff }); } for (int x = MOD(-(int)editor.camera_pos.x, config.ui_size * 2); x < win_width; x += config.ui_size * 2) { DrawRectangle(x, 0, 2, win_height, (Color) { 0x40, 0x40, 0x40, 0xff }); } EndShaderMode(); } static void draw_term(int x, int y) { mutex_lock(&term.lock); if (term.char_w == 0 || term.char_h == 0) goto unlock_term; if (!term.buffer) goto unlock_term; Rectangle final_pos = { x, y, term.size.x, term.size.y }; DrawRectangleRec(final_pos, BLACK); if (IsShaderValid(assets.line_shader)) { BeginShaderMode(assets.line_shader); DrawRectangleLinesEx(final_pos, 2.0, (Color) { 0x60, 0x60, 0x60, 0xff }); EndShaderMode(); } Vector2 pos = (Vector2) { final_pos.x, final_pos.y }; for (int y = 0; y < term.char_h; y++) { pos.x = final_pos.x; for (int x = 0; x < term.char_w; x++) { TerminalChar buffer_char = term.buffer[x + y*term.char_w]; if (!rl_vec_equal(CONVERT_COLOR(buffer_char.bg_color, Color), BLACK)) { DrawRectangle(pos.x, pos.y, term.char_size.x, term.font_size, CONVERT_COLOR(buffer_char.bg_color, Color)); } pos.x += term.char_size.x; } pos.y += term.font_size; } pos = (Vector2) { final_pos.x, final_pos.y }; for (int y = 0; y < term.char_h; y++) { pos.x = final_pos.x; for (int x = 0; x < term.char_w; x++) { TerminalChar buffer_char = term.buffer[x + y*term.char_w]; if (!rl_vec_equal(CONVERT_COLOR(buffer_char.fg_color, Color), CONVERT_COLOR(buffer_char.bg_color, Color))) { DrawTextEx(assets.fonts.font_mono, buffer_char.ch, pos, term.font_size, 0.0, CONVERT_COLOR(buffer_char.fg_color, Color)); } pos.x += term.char_size.x; } pos.y += term.font_size; } if (fmod(GetTime(), 1.0) <= 0.5) { Vector2 cursor_pos = (Vector2) { final_pos.x + (term.cursor_pos % term.char_w) * term.char_size.x, final_pos.y + (term.cursor_pos / term.char_w) * term.font_size, }; DrawRectangle(cursor_pos.x, cursor_pos.y, BLOCK_OUTLINE_SIZE, term.font_size, CONVERT_COLOR(term.cursor_fg_color, Color)); } unlock_term: mutex_unlock(&term.lock); } void prerender_font_shadow(Font* font) { SetTextureFilter(font->texture, TEXTURE_FILTER_POINT); Image font_img = LoadImageFromTexture(font->texture); Image render_img = ImageCopy(font_img); ImageClearBackground(&render_img, BLANK); ImageDraw( &render_img, font_img, (Rectangle) { 0, 0, font_img.width, font_img.height }, (Rectangle) { SHADOW_DISTANCE, SHADOW_DISTANCE, font_img.width, font_img.height }, (Color) { 0x00, 0x00, 0x00, 0x88 } ); ImageDraw( &render_img, font_img, (Rectangle) { 0, 0, font_img.width, font_img.height }, (Rectangle) { 0, 0, font_img.width, font_img.height }, WHITE ); UnloadTexture(font->texture); font->texture = LoadTextureFromImage(render_img); SetTextureFilter(font->texture, TEXTURE_FILTER_BILINEAR); UnloadImage(font_img); UnloadImage(render_img); } static void blockdef_on_hover(GuiElement* el) { if (ui.hover.is_panel_edit_mode) return; if (gui_window_is_shown()) return; ui.hover.editor.part = EDITOR_BLOCKDEF; ui.hover.editor.blockdef = el->custom_data; } static void blockdef_input_on_hover(GuiElement* el) { if (ui.hover.is_panel_edit_mode) return; if (gui_window_is_shown()) return; ui.hover.editor.blockchain = ui.hover.editor.prev_blockchain; if (el->draw_type != DRAWTYPE_UNKNOWN) return; el->draw_type = DRAWTYPE_BORDER; el->color = (GuiColor) { 0xa0, 0xa0, 0xa0, 0xff }; el->data.border_width = BLOCK_OUTLINE_SIZE; el->draw_subtype = GUI_SUBTYPE_DEFAULT; } static void editor_del_button_on_hover(GuiElement* el) { if (ui.hover.is_panel_edit_mode) return; if (gui_window_is_shown()) return; if (ui.hover.button.handler) return; el->draw_type = DRAWTYPE_RECT; el->draw_subtype = GUI_SUBTYPE_DEFAULT; el->color = (GuiColor) { 0xff, 0xff, 0xff, 0x80 }; ui.hover.editor.blockdef_input = (size_t)el->custom_data; ui.hover.button.handler = handle_editor_del_arg_button; } static void editor_button_on_hover(GuiElement* el) { if (ui.hover.is_panel_edit_mode) return; if (gui_window_is_shown()) return; if (ui.hover.button.handler) return; el->draw_type = DRAWTYPE_RECT; el->draw_subtype = GUI_SUBTYPE_DEFAULT; el->color = (GuiColor) { 0xff, 0xff, 0xff, 0x80 }; ui.hover.button.handler = el->custom_data; } static void editor_color_on_hover(GuiElement* el) { if (ui.hover.is_panel_edit_mode) return; if (gui_window_is_shown()) return; if (ui.hover.button.handler) return; el->draw_type = DRAWTYPE_BORDER; el->draw_subtype = GUI_SUBTYPE_DEFAULT; el->color = (GuiColor) { 0xa0, 0xa0, 0xa0, 0xff }; el->data.border_width = BLOCK_OUTLINE_SIZE; ui.hover.button.handler = handle_editor_color_button; } static void draw_editor_button(Texture2D* texture, ButtonClickHandler handler) { gui_element_begin(gui); gui_set_rect(gui, (GuiColor) { 0xff, 0xff, 0xff, 0x40 }); gui_on_hover(gui, editor_button_on_hover); gui_set_custom_data(gui, handler); gui_image(gui, texture, BLOCK_IMAGE_SIZE, GUI_WHITE); gui_element_end(gui); } void input_on_hover(GuiElement* el) { if (ui.hover.button.handler) return; if (ui.hover.is_panel_edit_mode) return; ui.hover.input_info = *(InputHoverInfo*)gui_get_state(el); ui.hover.input_info.rel_pos = (Vector2) { gui->mouse_x - el->abs_x - ui.hover.input_info.rel_pos.x, gui->mouse_y - el->abs_y - ui.hover.input_info.rel_pos.y, }; } static const int input_cursor_scrolloff = 5; void input_selection_on_render(GuiElement* el) { if (!el->parent) return; if (!el->parent->scroll_value) return; int cursor_pos = ui.hover.select_input_cursor > ui.hover.select_input_mark ? el->x + el->w : el->x; int right_off_x = cursor_pos + input_cursor_scrolloff - el->parent->w; int left_off_x = -cursor_pos + input_cursor_scrolloff; if (left_off_x > 0) { *el->parent->scroll_value += left_off_x; ui.render_surface_redraw_next = true; } else if (right_off_x > 0) { *el->parent->scroll_value -= right_off_x; ui.render_surface_redraw_next = true; } } void input_cursor_on_render(GuiElement* el) { if (!el->parent) return; if (!el->parent->scroll_value) return; int right_off_x = el->x + el->w + input_cursor_scrolloff - el->parent->w; int left_off_x = -el->x + input_cursor_scrolloff; if (left_off_x > 0) { *el->parent->scroll_value += left_off_x; ui.render_surface_redraw_next = true; } else if (right_off_x > 0) { *el->parent->scroll_value -= right_off_x; ui.render_surface_redraw_next = true; } } void draw_input_text(Font* font, char** input, const char* hint, unsigned short font_size, GuiColor font_color) { if (ui.hover.select_input == input) { if (ui.hover.select_input_cursor == ui.hover.select_input_mark) ui.hover.select_input_mark = -1; if (ui.hover.select_input_mark == -1) { gui_text_slice(gui, font, *input, ui.hover.select_input_cursor, font_size, font_color); gui_element_begin(gui); gui_set_rect(gui, font_color); gui_set_min_size(gui, BLOCK_OUTLINE_SIZE, BLOCK_TEXT_SIZE); gui_on_render(gui, input_cursor_on_render); gui_element_end(gui); gui_text(gui, font, *input + ui.hover.select_input_cursor, font_size, font_color); } else { int select_start = MIN(ui.hover.select_input_cursor, ui.hover.select_input_mark), select_end = MAX(ui.hover.select_input_cursor, ui.hover.select_input_mark); gui_text_slice(gui, font, *input, select_start, font_size, font_color); gui_element_begin(gui); gui_set_rect(gui, (GuiColor) TEXT_SELECTION_COLOR); gui_text_slice(gui, font, *input + select_start, select_end - select_start, font_size, font_color); gui_on_render(gui, input_selection_on_render); gui_element_end(gui); gui_text(gui, font, *input + select_end, font_size, font_color); } } else { if (**input == 0) { gui_text(gui, font, hint, font_size, (GuiColor) { font_color.r, font_color.g, font_color.b, font_color.a * 0.3 }); } else { gui_text(gui, font, *input, font_size, font_color); } } } static void argument_input_on_hover(GuiElement* el) { if (el->custom_data) { argument_on_hover(el); } else { blockdef_input_on_hover(el); } input_on_hover(el); } static void draw_argument_input(Argument* arg, char** input, const char* hint, bool can_hover, bool editable, GuiColor font_color, GuiColor bg_color) { gui_element_begin(gui); gui_set_rect(gui, bg_color); gui_element_begin(gui); if (editable) { if ((arg && ui.hover.editor.select_argument == arg) || ui.hover.select_input == input) { gui_set_border(gui, (GuiColor) { 0x30, 0x30, 0x30, 0xff }, BLOCK_OUTLINE_SIZE); if (arg) gui_on_render(gui, argument_on_render); } InputHoverInfo info = (InputHoverInfo) { .input = input, .rel_pos = (Vector2) { BLOCK_STRING_PADDING / 2, 0 }, .font = &assets.fonts.font_cond_shadow, .font_size = BLOCK_TEXT_SIZE, }; gui_set_state(gui, &info, sizeof(info)); gui_set_custom_data(gui, arg); if (can_hover) gui_on_hover(gui, argument_input_on_hover); } gui_set_direction(gui, DIRECTION_HORIZONTAL); gui_set_align(gui, ALIGN_CENTER, ALIGN_CENTER); gui_set_padding(gui, BLOCK_STRING_PADDING / 2, 0); gui_set_min_size(gui, config.ui_size - BLOCK_OUTLINE_SIZE * 4, config.ui_size - BLOCK_OUTLINE_SIZE * 4); draw_input_text(&assets.fonts.font_cond, input, hint, BLOCK_TEXT_SIZE, font_color); gui_element_end(gui); gui_element_end(gui); } static void draw_blockdef(Blockdef* blockdef, bool editing) { bool collision = ui.hover.editor.prev_blockdef == blockdef; Color color = CONVERT_COLOR(blockdef->color, Color); Color block_color = ColorBrightness(color, collision ? 0.3 : 0.0); Color dropdown_color = ColorBrightness(color, collision ? 0.0 : -0.3); Color outline_color = ColorBrightness(color, collision ? 0.5 : -0.2); gui_element_begin(gui); gui_set_direction(gui, DIRECTION_HORIZONTAL); gui_set_rect(gui, CONVERT_COLOR(block_color, GuiColor)); gui_set_custom_data(gui, blockdef); gui_on_hover(gui, blockdef_on_hover); gui_element_begin(gui); gui_set_direction(gui, DIRECTION_HORIZONTAL); gui_set_border(gui, CONVERT_COLOR(outline_color, GuiColor), BLOCK_OUTLINE_SIZE); gui_set_align(gui, ALIGN_LEFT, ALIGN_CENTER); gui_set_min_size(gui, 0, config.ui_size); gui_set_padding(gui, BLOCK_OUTLINE_SIZE * 2, BLOCK_OUTLINE_SIZE * 2); gui_set_gap(gui, BLOCK_PADDING); for (size_t i = 0; i < vector_size(blockdef->inputs); i++) { Input* input = &blockdef->inputs[i]; if (ui.hover.editor.edit_blockdef == blockdef) { gui_element_begin(gui); gui_set_direction(gui, DIRECTION_HORIZONTAL); gui_set_rect(gui, CONVERT_COLOR(dropdown_color, GuiColor)); gui_set_align(gui, ALIGN_LEFT, ALIGN_CENTER); gui_set_padding(gui, BLOCK_PADDING, BLOCK_PADDING); gui_set_gap(gui, BLOCK_PADDING); } switch (input->type) { case INPUT_TEXT_DISPLAY: if (editing) { draw_argument_input(NULL, &input->data.text, "", true, true, GUI_BLACK, GUI_WHITE); } else { gui_text(gui, &assets.fonts.font_cond_shadow, input->data.text, BLOCK_TEXT_SIZE, GUI_WHITE); } break; case INPUT_IMAGE_DISPLAY: gui_image(gui, input->data.image.image_ptr, BLOCK_IMAGE_SIZE, GUI_WHITE); break; case INPUT_ARGUMENT: input->data.arg.blockdef->color = blockdef->color; draw_blockdef(input->data.arg.blockdef, editing); break; default: gui_text(gui, &assets.fonts.font_cond_shadow, "NODEF", BLOCK_TEXT_SIZE, GUI_WHITE); break; } if (ui.hover.editor.edit_blockdef == blockdef) { gui_element_begin(gui); gui_set_rect(gui, (GuiColor) { 0xff, 0xff, 0xff, 0x40 }); gui_on_hover(gui, editor_del_button_on_hover); gui_set_custom_data(gui, (void*)i); gui_image(gui, &assets.textures.button_del_arg, BLOCK_IMAGE_SIZE, GUI_WHITE); gui_element_end(gui); gui_element_end(gui); } } gui_element_end(gui); gui_element_end(gui); } static void block_on_hover(GuiElement* el) { if (ui.hover.button.handler) return; if (ui.hover.is_panel_edit_mode) return; if (gui_window_is_shown()) return; ui.hover.editor.block = el->custom_data; ui.hover.editor.blockchain = ui.hover.editor.prev_blockchain; if (!ui.hover.editor.block->parent) ui.hover.editor.parent_argument = NULL; } static void argument_on_render(GuiElement* el) { ui.hover.editor.select_block_pos = (Vector2) { el->abs_x, el->abs_y }; } static void block_on_render(GuiElement* el) { ui.hover.editor.select_block_pos = (Vector2) { el->abs_x, el->abs_y }; ui.hover.editor.select_valid = true; } static void block_argument_on_hover(GuiElement* el) { if (ui.hover.button.handler) return; if (ui.hover.is_panel_edit_mode) return; ui.hover.editor.parent_argument = el->custom_data; ui.hover.editor.blockchain = ui.hover.editor.prev_blockchain; } static void argument_on_hover(GuiElement* el) { if (ui.hover.button.handler) return; if (ui.hover.is_panel_edit_mode) return; if (gui_window_is_shown()) return; ui.hover.editor.argument = el->custom_data; ui.hover.editor.blockchain = ui.hover.editor.prev_blockchain; if (el->draw_type != DRAWTYPE_UNKNOWN) return; el->draw_type = DRAWTYPE_BORDER; el->draw_subtype = GUI_SUBTYPE_DEFAULT; el->color = (GuiColor) { 0xa0, 0xa0, 0xa0, 0xff }; el->data.border_width = BLOCK_OUTLINE_SIZE; } static void draw_block(Block* block, bool highlight, bool can_hover, bool ghost, bool editable) { bool collision = ui.hover.editor.prev_block == block || highlight; Color color = CONVERT_COLOR(block->blockdef->color, Color); if (!block->blockdef->func) color = (Color) UNIMPLEMENTED_BLOCK_COLOR; if (!thread_is_running(&vm.thread) && block == vm.compile_error_block) { double animation = fmod(-GetTime(), 1.0) * 0.5 + 1.0; color = (Color) { 0xff * animation, 0x20 * animation, 0x20 * animation, 0xff }; } if (ghost) color.a = BLOCK_GHOST_OPACITY; Color block_color = collision ? ColorBrightness(color, 0.3) : color; Color dropdown_color = collision ? color : ColorBrightness(color, -0.3); Color outline_color; if (ui.hover.editor.select_block == block) { outline_color = ColorBrightness(color, 0.7); } else { outline_color = ColorBrightness(color, collision ? 0.5 : -0.2); } gui_element_begin(gui); gui_set_direction(gui, DIRECTION_HORIZONTAL); gui_set_rect(gui, CONVERT_COLOR(block_color, GuiColor)); gui_set_custom_data(gui, block); if (block->blockdef->type == BLOCKTYPE_HAT) gui_set_draw_subtype(gui, RECT_NOTCHED); if (can_hover) gui_on_hover(gui, block_on_hover); if (ui.hover.editor.select_block == block) gui_on_render(gui, block_on_render); gui_element_begin(gui); gui_set_direction(gui, DIRECTION_VERTICAL); gui_set_border(gui, CONVERT_COLOR(outline_color, GuiColor), BLOCK_OUTLINE_SIZE); gui_set_align(gui, ALIGN_LEFT, ALIGN_CENTER); gui_set_padding(gui, BLOCK_OUTLINE_SIZE * 2, BLOCK_OUTLINE_SIZE * 2); gui_set_min_size(gui, 0, config.ui_size); gui_set_gap(gui, BLOCK_PADDING); if (block->blockdef->type == BLOCKTYPE_CONTROL) { gui_set_draw_subtype(gui, BORDER_CONTROL); } else if (block->blockdef->type == BLOCKTYPE_CONTROLEND) { gui_set_draw_subtype(gui, BORDER_CONTROL_END); } else if (block->blockdef->type == BLOCKTYPE_HAT) { gui_set_draw_subtype(gui, BORDER_NOTCHED); } gui_element_begin(gui); gui_set_direction(gui, DIRECTION_HORIZONTAL); gui_set_gap(gui, BLOCK_PADDING); gui_set_align(gui, ALIGN_LEFT, ALIGN_CENTER); size_t arg_id = 0; Input* inputs = block->blockdef->inputs; size_t inputs_size = vector_size(inputs); Argument default_argument = { .input_id = 0, .data = (ArgumentData) { .text = "", }, }; GuiElement* block_element; for (size_t i = 0; i < inputs_size; i++) { Input* input = &inputs[i]; Argument* arg = block->arguments ? &block->arguments[arg_id] : NULL; switch (input->type) { case INPUT_TEXT_DISPLAY: gui_text(gui, &assets.fonts.font_cond_shadow, input->data.text, BLOCK_TEXT_SIZE, (GuiColor) { 0xff, 0xff, 0xff, ghost ? BLOCK_GHOST_OPACITY : 0xff }); break; case INPUT_IMAGE_DISPLAY: ; GuiColor img_color = CONVERT_COLOR(input->data.image.image_color, GuiColor); if (ghost) img_color.a = BLOCK_GHOST_OPACITY; gui_image(gui, input->data.image.image_ptr, BLOCK_IMAGE_SIZE, img_color); break; case INPUT_ARGUMENT: if (!arg) { arg = &default_argument; arg->type = ARGUMENT_TEXT; } switch (arg->type) { case ARGUMENT_CONST_STRING: draw_argument_input( arg, &arg->data.text, input->data.arg.hint_text, can_hover, editable, (GuiColor) { 0xff, 0xff, 0xff, ghost ? BLOCK_GHOST_OPACITY : 0xff }, CONVERT_COLOR(dropdown_color, GuiColor) ); break; case ARGUMENT_TEXT: draw_argument_input( arg, &arg->data.text, input->data.arg.hint_text, can_hover, editable, (GuiColor) { 0x00, 0x00, 0x00, ghost ? BLOCK_GHOST_OPACITY : 0xff }, (GuiColor) { 0xff, 0xff, 0xff, ghost ? BLOCK_GHOST_OPACITY : BLOCK_ARG_OPACITY } ); break; case ARGUMENT_BLOCK: block_element = gui_element_begin(gui); if (can_hover) gui_on_hover(gui, block_argument_on_hover); gui_set_custom_data(gui, arg); draw_block(&arg->data.block, highlight, can_hover, ghost, editable); gui_element_end(gui); if (block_element->w > 500 && i + 1 < inputs_size) { gui_element_end(gui); gui_element_begin(gui); gui_set_direction(gui, DIRECTION_HORIZONTAL); gui_set_gap(gui, BLOCK_PADDING); gui_set_align(gui, ALIGN_LEFT, ALIGN_CENTER); } break; default: gui_text(gui, &assets.fonts.font_cond_shadow, "NODEF", BLOCK_TEXT_SIZE, (GuiColor) { 0xff, 0x00, 0x00, 0xff }); break; } arg_id++; break; case INPUT_DROPDOWN: if (!arg) { arg = &default_argument; arg->type = ARGUMENT_CONST_STRING; } assert(arg->type == ARGUMENT_CONST_STRING); gui_element_begin(gui); gui_set_rect(gui, CONVERT_COLOR(dropdown_color, GuiColor)); if (ui.dropdown.ref_object == arg) { ui.dropdown.element = gui_get_element(gui); } gui_element_begin(gui); gui_set_min_size(gui, 0, config.ui_size - BLOCK_OUTLINE_SIZE * 4); gui_set_align(gui, ALIGN_LEFT, ALIGN_CENTER); gui_set_padding(gui, BLOCK_STRING_PADDING / 2, 0); gui_set_direction(gui, DIRECTION_HORIZONTAL); if (editable) { if (ui.hover.editor.select_argument == arg) gui_set_border(gui, (GuiColor) { 0x30, 0x30, 0x30, 0xff }, BLOCK_OUTLINE_SIZE); if (can_hover) gui_on_hover(gui, argument_on_hover); gui_set_custom_data(gui, arg); } gui_text(gui, &assets.fonts.font_cond_shadow, arg->data.text, BLOCK_TEXT_SIZE, GUI_WHITE); gui_image(gui, &assets.textures.dropdown, BLOCK_IMAGE_SIZE, GUI_WHITE); gui_element_end(gui); gui_element_end(gui); arg_id++; break; case INPUT_BLOCKDEF_EDITOR: if (!arg) { arg_id++; break; } assert(arg->type == ARGUMENT_BLOCKDEF); gui_element_begin(gui); gui_set_direction(gui, DIRECTION_HORIZONTAL); gui_set_rect(gui, CONVERT_COLOR(dropdown_color, GuiColor)); gui_set_align(gui, ALIGN_LEFT, ALIGN_CENTER); gui_set_gap(gui, BLOCK_PADDING); gui_set_custom_data(gui, arg); if (can_hover) gui_on_hover(gui, argument_on_hover); draw_blockdef(arg->data.blockdef, ui.hover.editor.edit_blockdef == arg->data.blockdef); if (editable) { if (ui.hover.editor.edit_blockdef == arg->data.blockdef) { draw_editor_button(&assets.textures.button_add_arg, handle_editor_add_arg_button); draw_editor_button(&assets.textures.button_add_text, handle_editor_add_text_button); gui_element_begin(gui); if (can_hover) gui_on_hover(gui, editor_color_on_hover); if (ui.dropdown.ref_object == &arg->data.blockdef->color) { ui.dropdown.element = gui_get_element(gui); gui_set_border(gui, (GuiColor) { 0x30, 0x30, 0x30, 0xff }, BLOCK_OUTLINE_SIZE); gui_on_render(gui, argument_on_render); } gui_element_begin(gui); gui_set_fixed(gui, BLOCK_IMAGE_SIZE, BLOCK_IMAGE_SIZE); gui_set_rect(gui, CONVERT_COLOR(arg->data.blockdef->color, GuiColor)); gui_element_end(gui); gui_element_end(gui); draw_editor_button(&assets.textures.button_close, handle_editor_close_button); } else { draw_editor_button(&assets.textures.button_edit, handle_editor_edit_button); } gui_spacer(gui, 0, 0); } gui_element_end(gui); arg_id++; break; case INPUT_COLOR: if (!arg) { arg_id++; break; } if (arg->type == ARGUMENT_TEXT || arg->type == ARGUMENT_CONST_STRING) { const struct { char* text; Color color; } color_map[] = { { "black", BLACK }, { "red", RED }, { "yellow", YELLOW }, { "green", GREEN }, { "blue", BLUE }, { "purple", PURPLE }, { "cyan", (Color) { 0x00, 0xff, 0xff, 0xff } }, { "white", WHITE }, }; for (size_t i = 0; i < ARRLEN(color_map); i++) { if (!strcmp(arg->data.text, color_map[i].text)) { vector_free(arg->data.text); argument_set_color(arg, CONVERT_COLOR(color_map[i].color, BlockdefColor)); break; } } if (arg->type != ARGUMENT_COLOR) { vector_free(arg->data.text); argument_set_color(arg, (BlockdefColor) { 0x00, 0x00, 0x00, 0xff }); } } switch (arg->type) { case ARGUMENT_COLOR: gui_element_begin(gui); if (editable) { if (ui.hover.editor.select_argument == arg) { gui_set_border(gui, (GuiColor) { 0x30, 0x30, 0x30, 0xff }, BLOCK_OUTLINE_SIZE); gui_on_render(gui, argument_on_render); } gui_set_custom_data(gui, arg); if (can_hover) gui_on_hover(gui, argument_on_hover); } if (ui.dropdown.ref_object == arg) { ui.dropdown.element = gui_get_element(gui); } gui_element_begin(gui); gui_set_fixed(gui, BLOCK_IMAGE_SIZE, BLOCK_IMAGE_SIZE); gui_set_rect(gui, CONVERT_COLOR(arg->data.color, GuiColor)); gui_element_end(gui); gui_element_end(gui); break; case ARGUMENT_BLOCK: block_element = gui_element_begin(gui); if (can_hover) gui_on_hover(gui, block_argument_on_hover); gui_set_custom_data(gui, arg); draw_block(&arg->data.block, highlight, can_hover, ghost, editable); gui_element_end(gui); if (block_element->w > 500 && i + 1 < inputs_size) { gui_element_end(gui); gui_element_begin(gui); gui_set_direction(gui, DIRECTION_HORIZONTAL); gui_set_gap(gui, BLOCK_PADDING); gui_set_align(gui, ALIGN_LEFT, ALIGN_CENTER); } break; default: assert(false && "Invalid argument type in color input"); break; } arg_id++; break; default: gui_text(gui, &assets.fonts.font_cond_shadow, "NODEF", BLOCK_TEXT_SIZE, (GuiColor) { 0xff, 0x00, 0x00, 0xff }); break; } } gui_element_end(gui); gui_element_end(gui); gui_element_end(gui); } static void tab_button_add_on_hover(GuiElement* el) { if (gui_window_is_shown()) return; if (ui.hover.button.handler) return; if (el->draw_type == DRAWTYPE_RECT) return; el->draw_type = DRAWTYPE_RECT; el->draw_subtype = GUI_SUBTYPE_DEFAULT; el->color = (GuiColor) { 0x40, 0x40, 0x40, 0xff }; ui.hover.button.handler = handle_add_tab_button; ui.hover.button.data = el->custom_data; } static void tab_button_on_hover(GuiElement* el) { if (gui_window_is_shown()) return; if (ui.hover.button.handler) return; if (el->draw_type == DRAWTYPE_RECT) return; el->draw_type = DRAWTYPE_RECT; el->draw_subtype = GUI_SUBTYPE_DEFAULT; el->color = (GuiColor) { 0x40, 0x40, 0x40, 0xff }; ui.hover.button.handler = handle_tab_button; ui.hover.button.data = el->custom_data; } static void button_on_hover(GuiElement* el) { if (ui.hover.is_panel_edit_mode) return; if (gui_window_is_shown()) return; if (ui.hover.button.handler) return; if (el->draw_type == DRAWTYPE_RECT) return; el->draw_type = DRAWTYPE_RECT; el->draw_subtype = GUI_SUBTYPE_DEFAULT; el->color = (GuiColor) { 0x40, 0x40, 0x40, 0xff }; ui.hover.button.handler = el->custom_data; } static void panel_editor_button_on_hover(GuiElement* el) { if (!ui.hover.is_panel_edit_mode) return; if (ui.hover.button.handler) return; Color color = ColorBrightness(CONVERT_COLOR(el->color, Color), -0.13); el->color = CONVERT_COLOR(color, GuiColor); ui.hover.button.handler = el->custom_data; } static void draw_panel_editor_button(const char* text, int size, GuiColor color, ButtonClickHandler handler) { gui_element_begin(gui); gui_set_direction(gui, DIRECTION_HORIZONTAL); gui_set_align(gui, ALIGN_LEFT, ALIGN_CENTER); gui_set_min_size(gui, 0, size); gui_set_padding(gui, config.ui_size * 0.3, 0); gui_set_rect(gui, color); gui_on_hover(gui, panel_editor_button_on_hover); gui_set_custom_data(gui, handler); gui_text(gui, &assets.fonts.font_cond, text, BLOCK_TEXT_SIZE, GUI_BLACK); gui_element_end(gui); } static GuiElement* draw_button(const char* text, Texture2D* icon, int size, bool selected, GuiHandler on_hover, void* custom_data) { GuiElement* el; gui_element_begin(gui); gui_set_direction(gui, DIRECTION_HORIZONTAL); gui_set_align(gui, ALIGN_LEFT, ALIGN_CENTER); gui_set_min_size(gui, 0, size); gui_set_padding(gui, config.ui_size * 0.3, 0); gui_set_gap(gui, ELEMENT_GAP/2); if (selected) gui_set_rect(gui, GUI_WHITE); gui_on_hover(gui, on_hover); gui_set_custom_data(gui, custom_data); el = gui_get_element(gui); if (icon) gui_image(gui, icon, BLOCK_IMAGE_SIZE, selected ? GUI_BLACK : GUI_WHITE); if (text) gui_text(gui, &assets.fonts.font_cond, text, BLOCK_TEXT_SIZE, selected ? GUI_BLACK : GUI_WHITE); gui_element_end(gui); return el; } static void draw_top_bar(void) { const int top_bar_size = config.ui_size * 1.2; gui_element_begin(gui); gui_set_grow(gui, DIRECTION_HORIZONTAL); gui_set_direction(gui, DIRECTION_HORIZONTAL); gui_set_rect(gui, (GuiColor) { 0x30, 0x30, 0x30, 0xff }); gui_set_min_size(gui, 0, top_bar_size); gui_set_align(gui, ALIGN_LEFT, ALIGN_CENTER); gui_spacer(gui, 5, 0); gui_image(gui, &assets.textures.icon_logo, config.ui_size, CONVERT_COLOR(WHITE, GuiColor)); gui_spacer(gui, 10, 0); gui_text(gui, &assets.fonts.font_eb, gettext("Scrap"), config.ui_size * 0.8, CONVERT_COLOR(WHITE, GuiColor)); gui_spacer(gui, 10, 0); GuiElement* el = draw_button(gettext("File"), &assets.textures.icon_file, top_bar_size, false, button_on_hover, handle_file_button_click); if (ui.dropdown.handler == handle_file_menu_click) ui.dropdown.element = el; draw_button(gettext("Settings"), &assets.textures.icon_settings, top_bar_size, false, button_on_hover, handle_settings_button_click); draw_button(gettext("About"), &assets.textures.icon_about, top_bar_size, false, button_on_hover, handle_about_button_click); gui_element_end(gui); } static void draw_tab_bar(void) { const int tab_bar_size = config.ui_size; gui_element_begin(gui); gui_set_grow(gui, DIRECTION_HORIZONTAL); gui_set_direction(gui, DIRECTION_HORIZONTAL); gui_set_rect(gui, (GuiColor) { 0x2b, 0x2b, 0x2b, 0xff }); gui_set_min_size(gui, 0, tab_bar_size); gui_set_align(gui, ALIGN_LEFT, ALIGN_CENTER); if (ui.hover.is_panel_edit_mode && ui.hover.panels.mouse_panel != PANEL_NONE) { draw_button("+", NULL, tab_bar_size, false, tab_button_add_on_hover, (void*)0); } for (size_t i = 0; i < vector_size(editor.tabs); i++) { draw_button(gettext(editor.tabs[i].name), NULL, tab_bar_size, editor.current_tab == (int)i, tab_button_on_hover, (void*)i); if (ui.hover.is_panel_edit_mode && ui.hover.panels.mouse_panel != PANEL_NONE) { draw_button("+", NULL, tab_bar_size, false, tab_button_add_on_hover, (void*)(i + 1)); } } gui_grow(gui, DIRECTION_HORIZONTAL); gui_text(gui, &assets.fonts.font_cond, editor.project_name, BLOCK_TEXT_SIZE, (GuiColor) { 0x80, 0x80, 0x80, 0xff }); if (editor.project_modified) gui_text(gui, &assets.fonts.font_cond, "*", BLOCK_TEXT_SIZE, (GuiColor) { 0x80, 0x80, 0x80, 0xff }); gui_grow(gui, DIRECTION_HORIZONTAL); #ifndef USE_INTERPRETER gui_element_begin(gui); gui_on_hover(gui, button_on_hover); gui_set_custom_data(gui, handle_build_button_click); gui_image(gui, &assets.textures.button_build, tab_bar_size, (GuiColor) { 0xff, 0x99, 0x00, 0xff }); gui_element_end(gui); gui_spacer(gui, config.ui_size * 0.2, 0); #endif gui_element_begin(gui); gui_on_hover(gui, button_on_hover); gui_set_custom_data(gui, handle_stop_button_click); if (vm.thread.state == THREAD_STATE_STOPPING) { gui_set_rect(gui, GUI_WHITE); gui_image(gui, &assets.textures.button_stop, tab_bar_size, GUI_BLACK); } else { gui_image(gui, &assets.textures.button_stop, tab_bar_size, (GuiColor) { 0xff, 0x40, 0x30, 0xff }); } gui_element_end(gui); gui_spacer(gui, config.ui_size * 0.2, 0); gui_element_begin(gui); gui_on_hover(gui, button_on_hover); gui_set_custom_data(gui, handle_run_button_click); if (thread_is_running(&vm.thread)) { gui_set_rect(gui, GUI_WHITE); gui_image(gui, &assets.textures.button_run, tab_bar_size, GUI_BLACK); } else { gui_image(gui, &assets.textures.button_run, tab_bar_size, (GuiColor) { 0x60, 0xff, 0x00, 0xff }); } gui_element_end(gui); gui_element_end(gui); } static void blockchain_on_hover(GuiElement* el) { if (ui.hover.is_panel_edit_mode) return; ui.hover.editor.prev_blockchain = el->custom_data; } static void draw_block_preview(void) { if (vector_size(editor.mouse_blockchains) != 1) return; if (ui.hover.editor.prev_argument != NULL) return; if (editor.mouse_blockchains[0].blocks[0].blockdef->type == BLOCKTYPE_HAT) return; draw_blockchain(&editor.mouse_blockchains[0], true, false, false); } static GuiElement* draw_blockchain(BlockChain* chain, bool ghost, bool show_previews, bool editable_arguments) { int layer = 0; GuiElement* el = gui_element_begin(gui); gui_set_direction(gui, DIRECTION_VERTICAL); gui_on_hover(gui, blockchain_on_hover); gui_set_custom_data(gui, chain); for (size_t i = 0; i < vector_size(chain->blocks); i++) { Blockdef* blockdef = chain->blocks[i].blockdef; if (blockdef->type == BLOCKTYPE_END) { gui_element_end(gui); gui_element_end(gui); GuiElement* el = gui_get_element(gui); Block* block = el->custom_data; bool collision = ui.hover.editor.prev_block == &chain->blocks[i]; Color color = CONVERT_COLOR(block->blockdef->color, Color); if (ghost) color.a = BLOCK_GHOST_OPACITY; Color block_color = ColorBrightness(color, collision ? 0.3 : 0.0); Color outline_color; if (ui.hover.editor.select_block == &chain->blocks[i]) { outline_color = ColorBrightness(color, 0.7); } else { outline_color = ColorBrightness(color, collision ? 0.5 : -0.2); } gui_element_begin(gui); gui_set_min_size(gui, editor.blockchain_render_layer_widths[vector_size(editor.blockchain_render_layer_widths) - 1], config.ui_size); gui_set_rect(gui, CONVERT_COLOR(block_color, GuiColor)); gui_on_hover(gui, block_on_hover); if (ui.hover.editor.select_block == &chain->blocks[i]) gui_on_render(gui, block_on_render); gui_set_custom_data(gui, &chain->blocks[i]); gui_element_begin(gui); gui_set_grow(gui, DIRECTION_VERTICAL); gui_set_grow(gui, DIRECTION_HORIZONTAL); gui_set_border(gui, CONVERT_COLOR(outline_color, GuiColor), BLOCK_OUTLINE_SIZE); gui_set_draw_subtype(gui, BORDER_END); gui_element_end(gui); gui_element_end(gui); vector_pop(editor.blockchain_render_layer_widths); layer--; gui_element_end(gui); } else if (blockdef->type == BLOCKTYPE_CONTROLEND) { if (layer > 0) { gui_element_end(gui); gui_element_end(gui); gui_element_end(gui); layer--; } if (vector_size(editor.blockchain_render_layer_widths) > 0) vector_pop(editor.blockchain_render_layer_widths); gui_element_begin(gui); gui_set_direction(gui, DIRECTION_VERTICAL); gui_set_custom_data(gui, &chain->blocks[i]); } else if (blockdef->type == BLOCKTYPE_CONTROL) { gui_element_begin(gui); gui_set_direction(gui, DIRECTION_VERTICAL); gui_set_custom_data(gui, &chain->blocks[i]); } if (blockdef->type != BLOCKTYPE_END) { draw_block(&chain->blocks[i], false, true, ghost, editable_arguments); } if (blockdef->type == BLOCKTYPE_CONTROL || blockdef->type == BLOCKTYPE_CONTROLEND) { layer++; GuiElement* el = gui_get_element(gui); vector_add(&editor.blockchain_render_layer_widths, el->w); bool collision = ui.hover.editor.prev_block == &chain->blocks[i]; Color color = CONVERT_COLOR(blockdef->color, Color); if (ghost) color.a = BLOCK_GHOST_OPACITY; Color block_color = ColorBrightness(color, collision ? 0.3 : 0.0); Color outline_color; if (ui.hover.editor.select_block == &chain->blocks[i]) { outline_color = ColorBrightness(color, 0.7); } else { outline_color = ColorBrightness(color, collision ? 0.5 : -0.2); } gui_element_begin(gui); gui_set_direction(gui, DIRECTION_HORIZONTAL); gui_element_begin(gui); gui_set_grow(gui, DIRECTION_VERTICAL); gui_set_min_size(gui, BLOCK_CONTROL_INDENT, config.ui_size / 2); gui_set_rect(gui, CONVERT_COLOR(block_color, GuiColor)); gui_on_hover(gui, block_on_hover); gui_set_custom_data(gui, &chain->blocks[i]); gui_element_begin(gui); gui_set_grow(gui, DIRECTION_VERTICAL); gui_set_grow(gui, DIRECTION_HORIZONTAL); gui_set_border(gui, CONVERT_COLOR(outline_color, GuiColor), BLOCK_OUTLINE_SIZE); gui_set_draw_subtype(gui, BORDER_CONTROL_BODY); gui_element_end(gui); gui_element_end(gui); gui_element_begin(gui); gui_set_direction(gui, DIRECTION_VERTICAL); if (ui.hover.editor.prev_block == &chain->blocks[i] && show_previews) { draw_block_preview(); } } else { if (ui.hover.editor.prev_block == &chain->blocks[i] && show_previews) { draw_block_preview(); } } } while (layer > 0) { gui_element_end(gui); gui_element_end(gui); gui_element_end(gui); layer--; } gui_element_end(gui); return el; } static void category_on_hover(GuiElement* el) { if (ui.hover.is_panel_edit_mode) return; if (gui_window_is_shown()) return; if (ui.hover.button.handler) return; el->color.a = 0x80; ui.hover.button.handler = handle_category_click; ui.hover.category = el->custom_data; } static void draw_category(BlockCategory* category) { GuiColor color = CONVERT_COLOR(category->color, GuiColor); color.a = 0x40; gui_element_begin(gui); gui_set_scissor(gui); gui_set_grow(gui, DIRECTION_HORIZONTAL); gui_set_rect(gui, color); gui_on_hover(gui, category_on_hover); gui_set_custom_data(gui, category); color.a = 0xff; gui_element_begin(gui); gui_set_grow(gui, DIRECTION_HORIZONTAL); if (category == editor.palette.current_category) gui_set_border(gui, color, BLOCK_OUTLINE_SIZE); gui_set_direction(gui, DIRECTION_HORIZONTAL); gui_set_padding(gui, ELEMENT_GAP, 0); gui_set_min_size(gui, 0, config.ui_size); gui_set_align(gui, ALIGN_LEFT, ALIGN_CENTER); gui_set_gap(gui, ELEMENT_GAP); gui_element_begin(gui); gui_set_min_size(gui, ELEMENT_GAP * 2, ELEMENT_GAP * 2); gui_set_rect(gui, color); gui_element_end(gui); gui_text(gui, &assets.fonts.font_cond_shadow, category->name, BLOCK_TEXT_SIZE, GUI_WHITE); gui_element_end(gui); gui_element_end(gui); } static void draw_block_categories(void) { gui_element_begin(gui); gui_set_grow(gui, DIRECTION_VERTICAL); gui_set_grow(gui, DIRECTION_HORIZONTAL); gui_set_rect(gui, (GuiColor) PANEL_BACKGROUND_COLOR); gui_set_padding(gui, ELEMENT_GAP, ELEMENT_GAP); gui_set_gap(gui, ELEMENT_GAP); gui_set_scroll(gui, &ui.categories_scroll); gui_set_scissor(gui); BlockCategory* cat = editor.palette.categories_start; while (cat) { gui_element_begin(gui); gui_set_direction(gui, DIRECTION_HORIZONTAL); gui_set_grow(gui, DIRECTION_HORIZONTAL); gui_set_gap(gui, ELEMENT_GAP); draw_category(cat); cat = cat->next; if (cat) { draw_category(cat); cat = cat->next; } else { gui_grow(gui, DIRECTION_HORIZONTAL); } gui_element_end(gui); } gui_element_end(gui); } static void draw_block_palette(void) { gui_element_begin(gui); gui_set_grow(gui, DIRECTION_VERTICAL); gui_set_grow(gui, DIRECTION_HORIZONTAL); gui_set_rect(gui, (GuiColor) PANEL_BACKGROUND_COLOR); gui_set_padding(gui, ELEMENT_GAP, ELEMENT_GAP); gui_set_gap(gui, ELEMENT_GAP); gui_set_scroll(gui, &editor.palette.scroll_amount); gui_set_scroll_scaling(gui, config.ui_size * 4); gui_set_scissor(gui); BlockCategory* cat = editor.palette.current_category; if (cat) { for (size_t i = 0; i < vector_size(cat->items); i++) { switch (cat->items[i].type) { case CATEGORY_ITEM_CHAIN: draw_blockchain(&cat->items[i].data.chain, false, false, false); break; case CATEGORY_ITEM_LABEL: if (i != 0) gui_spacer(gui, 0, config.ui_size * 0.1); gui_element_begin(gui); gui_set_direction(gui, DIRECTION_HORIZONTAL); gui_set_gap(gui, BLOCK_OUTLINE_SIZE * 4); gui_set_align(gui, ALIGN_LEFT, ALIGN_CENTER); // gui_set_min_size(gui, 0, config.ui_size); gui_element_begin(gui); gui_set_fixed(gui, BLOCK_OUTLINE_SIZE * 2, config.ui_size * 0.75); gui_set_rect(gui, CONVERT_COLOR(cat->items[i].data.label.color, GuiColor)); gui_element_end(gui); gui_text(gui, &assets.fonts.font_cond_shadow, cat->items[i].data.label.text, BLOCK_TEXT_SIZE, GUI_WHITE); gui_element_end(gui); break; } } } else { gui_set_align(gui, ALIGN_CENTER, ALIGN_CENTER); gui_text(gui, &assets.fonts.font_cond_shadow, "No category currently selected", BLOCK_TEXT_SIZE, GUI_WHITE); } gui_element_end(gui); } static void spectrum_on_hover(GuiElement* el) { (void) el; ui.dropdown.as.color_picker.hover_part = COLOR_PICKER_SPECTRUM; } static void spectrum_on_render(GuiElement* el) { if (IsMouseButtonDown(MOUSE_BUTTON_LEFT) && ui.dropdown.as.color_picker.select_part == COLOR_PICKER_SPECTRUM) { ui.dropdown.as.color_picker.color.hue = CLAMP((gui->mouse_y - el->parent->abs_y) / el->parent->h * 360.0, 0.0, 360.0); editor.project_modified = true; } el->y = (ui.dropdown.as.color_picker.color.hue / 360.0) * el->parent->h - el->h / 2.0; } static void color_picker_on_hover(GuiElement* el) { (void) el; ui.hover.button.handler = handle_color_picker_click; } static void color_picker_sv_on_hover(GuiElement* el) { (void) el; ui.dropdown.as.color_picker.hover_part = COLOR_PICKER_SV; } static void color_picker_sv_on_render(GuiElement* el) { if (IsMouseButtonDown(MOUSE_BUTTON_LEFT) && ui.dropdown.as.color_picker.select_part == COLOR_PICKER_SV) { ui.dropdown.as.color_picker.color.saturation = CLAMP((gui->mouse_x - el->parent->abs_x) / el->parent->w, 0.0, 1.0); ui.dropdown.as.color_picker.color.value = CLAMP(1.0 - (gui->mouse_y - el->parent->abs_y) / el->parent->h, 0.0, 1.0); editor.project_modified = true; } el->x = ui.dropdown.as.color_picker.color.saturation * el->parent->w - el->w / 2.0; el->y = (1 - ui.dropdown.as.color_picker.color.value) * el->parent->h - el->h / 2.0; } static void draw_color_picker(void) { Color col = ColorFromHSV( ui.dropdown.as.color_picker.color.hue, ui.dropdown.as.color_picker.color.saturation, ui.dropdown.as.color_picker.color.value ); Color col_hue = ColorFromHSV( ui.dropdown.as.color_picker.color.hue, 1.0, 1.0 ); *ui.dropdown.as.color_picker.edit_color = col; gui_element_begin(gui); gui_set_rect(gui, (GuiColor) { 0x20, 0x20, 0x20, 0xff }); gui_on_hover(gui, color_picker_on_hover); gui_element_begin(gui); gui_set_border(gui, (GuiColor) { 0x40, 0x40, 0x40, 0xff }, 2); gui_set_gap(gui, config.ui_size * 0.25); gui_set_padding(gui, config.ui_size * 0.25, config.ui_size * 0.25); gui_set_direction(gui, DIRECTION_HORIZONTAL); gui_element_begin(gui); gui_set_rect(gui, CONVERT_COLOR(col_hue, GuiColor)); gui_set_fixed(gui, config.ui_size * 8.0, config.ui_size * 8.0); gui_set_shader(gui, &assets.gradient_shader); gui_on_hover(gui, color_picker_sv_on_hover); gui_element_begin(gui); gui_set_border(gui, (GuiColor) { 0xff - col.r, 0xff - col.g, 0xff - col.b, 0xff }, 2); gui_set_floating(gui); gui_set_position(gui, -config.ui_size * 0.125, -config.ui_size * 0.125); gui_set_fixed(gui, config.ui_size * 0.25, config.ui_size * 0.25); gui_on_render(gui, color_picker_sv_on_render); gui_element_end(gui); gui_element_end(gui); gui_element_begin(gui); gui_set_image(gui, &assets.textures.spectrum, 0, GUI_WHITE); gui_set_min_size(gui, config.ui_size * 0.75, 0); gui_set_grow(gui, DIRECTION_VERTICAL); gui_set_draw_subtype(gui, IMAGE_STRETCHED); gui_on_hover(gui, spectrum_on_hover); gui_element_begin(gui); gui_set_border(gui, (GuiColor) { 0xff - col_hue.r, 0xff - col_hue.g, 0xff - col_hue.b, 0xff }, 2); gui_set_floating(gui); gui_set_position(gui, -config.ui_size * 0.125, -config.ui_size * 0.125); gui_set_fixed(gui, config.ui_size, config.ui_size * 0.25); gui_on_render(gui, spectrum_on_render); gui_element_end(gui); gui_element_end(gui); gui_element_begin(gui); gui_set_gap(gui, config.ui_size * 0.25); gui_element_begin(gui); gui_set_rect(gui, (GuiColor) { 0x30, 0x30, 0x30, 0xff }); gui_set_padding(gui, config.ui_size * 0.2, config.ui_size * 0.2); snprintf(ui.dropdown.as.color_picker.color_hex, 10, "#%02x%02x%02x%02x", col.r, col.g, col.b, col.a); gui_text(gui, &assets.fonts.font_cond, ui.dropdown.as.color_picker.color_hex, BLOCK_TEXT_SIZE, GUI_WHITE); gui_element_end(gui); gui_element_begin(gui); gui_set_border(gui, (GuiColor) { 0x40, 0x40, 0x40, 0xff }, 2); gui_element_begin(gui); gui_set_fixed(gui, config.ui_size, config.ui_size); gui_set_rect(gui, CONVERT_COLOR(col, GuiColor)); gui_element_end(gui); gui_element_end(gui); gui_element_end(gui); gui_element_end(gui); gui_element_end(gui); } static void code_area_on_render(GuiElement* el) { ui.hover.panels.code_panel_bounds = (Rectangle) { el->abs_x, el->abs_y, el->w, el->h }; } static void draw_code_area(void) { gui_element_begin(gui); gui_set_grow(gui, DIRECTION_HORIZONTAL); gui_set_grow(gui, DIRECTION_VERTICAL); gui_set_direction(gui, DIRECTION_VERTICAL); gui_set_padding(gui, 0, 0); gui_set_align(gui, ALIGN_RIGHT, ALIGN_TOP); gui_set_scissor(gui); gui_on_render(gui, code_area_on_render); draw_code(); gui_element_begin(gui); gui_set_floating(gui); gui_set_position(gui, 0, 0); gui_set_padding(gui, config.ui_size * 0.2, config.ui_size * 0.2); for (int i = 0; i < DEBUG_BUFFER_LINES; i++) { 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 }); } gui_spacer(gui, 0, config.ui_size * 0.5); gui_element_end(gui); if (!thread_is_running(&vm.thread) && vector_size(vm.compile_error) > 0) { gui_element_begin(gui); gui_set_direction(gui, DIRECTION_HORIZONTAL); gui_set_align(gui, ALIGN_LEFT, ALIGN_CENTER); gui_set_gap(gui, config.ui_size * 0.5); gui_set_padding(gui, config.ui_size * 0.4, config.ui_size * 0.4); gui_set_rect(gui, (GuiColor) { 0x00, 0x00, 0x00, 0x80 }); double animation = (fmod(-GetTime(), 1.0) * 0.5 + 1.0) * 255.0; gui_element_begin(gui); gui_set_rect(gui, (GuiColor) { 0xff, 0x20, 0x20, animation }); gui_set_fixed(gui, config.ui_size, config.ui_size); gui_set_direction(gui, DIRECTION_VERTICAL); gui_set_align(gui, ALIGN_CENTER, ALIGN_CENTER); gui_text(gui, &assets.fonts.font_eb, "!", config.ui_size, GUI_WHITE); gui_element_end(gui); gui_element_begin(gui); gui_set_direction(gui, DIRECTION_VERTICAL); gui_text(gui, &assets.fonts.font_cond, gettext("Got compiler error!"), config.ui_size * 0.6, (GuiColor) { 0xff, 0x33, 0x33, 0xff }); for (size_t i = 0; i < vector_size(vm.compile_error); i++) { gui_text(gui, &assets.fonts.font_cond, vm.compile_error[i], config.ui_size * 0.6, GUI_WHITE); } gui_spacer(gui, 0, config.ui_size * 0.5); gui_element_begin(gui); gui_set_direction(gui, DIRECTION_HORIZONTAL); gui_set_gap(gui, config.ui_size * 0.5); if (vm.compile_error_block) { gui_element_begin(gui); gui_set_border(gui, (GuiColor) { 0x40, 0x40, 0x40, 0xff }, BLOCK_OUTLINE_SIZE); draw_button(gettext("Jump to block"), NULL, config.ui_size, false, button_on_hover, handle_jump_to_block_button_click); gui_element_end(gui); } gui_element_begin(gui); gui_set_border(gui, (GuiColor) { 0x40, 0x40, 0x40, 0xff }, BLOCK_OUTLINE_SIZE); draw_button(gettext("Close"), NULL, config.ui_size, false, button_on_hover, handle_error_window_close_button_click); gui_element_end(gui); gui_element_end(gui); gui_element_end(gui); gui_element_end(gui); } else { gui_spacer(gui, 0, config.ui_size * 1.5); } if (editor.actionbar.show_time > 0) { gui_element_begin(gui); gui_set_grow(gui, DIRECTION_HORIZONTAL); gui_set_direction(gui, DIRECTION_VERTICAL); gui_set_align(gui, ALIGN_CENTER, ALIGN_TOP); Color color = YELLOW; color.a = editor.actionbar.show_time / 3.0 * 255.0; gui_text(gui, &assets.fonts.font_eb, editor.actionbar.text, config.ui_size * 0.8, CONVERT_COLOR(color, GuiColor)); gui_element_end(gui); } gui_element_end(gui); } static void draw_split_preview(PanelTree* panel) { if (!ui.hover.is_panel_edit_mode) return; if (ui.hover.panels.prev_panel != panel) return; if (ui.hover.panels.mouse_panel == PANEL_NONE) { gui_element_begin(gui); gui_set_floating(gui); gui_set_position(gui, 0, 0); gui_set_grow(gui, DIRECTION_HORIZONTAL); gui_set_grow(gui, DIRECTION_VERTICAL); gui_set_rect(gui, (GuiColor) { 0x00, 0xff, 0xff, 0x20 }); gui_element_begin(gui); gui_set_grow(gui, DIRECTION_HORIZONTAL); gui_set_grow(gui, DIRECTION_VERTICAL); gui_set_border(gui, (GuiColor) { 0x00, 0xff, 0xff, 0x80 }, BLOCK_OUTLINE_SIZE); gui_element_end(gui); gui_element_end(gui); return; } if (ui.hover.panels.panel_side == SPLIT_SIDE_NONE) return; gui_element_begin(gui); gui_set_floating(gui); gui_set_position(gui, 0, 0); gui_set_grow(gui, DIRECTION_HORIZONTAL); gui_set_grow(gui, DIRECTION_VERTICAL); if (ui.hover.panels.panel_side == SPLIT_SIDE_LEFT || ui.hover.panels.panel_side == SPLIT_SIDE_RIGHT) gui_set_direction(gui, DIRECTION_HORIZONTAL); if (ui.hover.panels.panel_side == SPLIT_SIDE_BOTTOM) gui_grow(gui, DIRECTION_VERTICAL); if (ui.hover.panels.panel_side == SPLIT_SIDE_RIGHT) gui_grow(gui, DIRECTION_HORIZONTAL); gui_element_begin(gui); gui_set_grow(gui, DIRECTION_VERTICAL); gui_set_grow(gui, DIRECTION_HORIZONTAL); gui_set_rect(gui, (GuiColor) { 0x00, 0xff, 0xff, 0x20 }); gui_element_begin(gui); gui_set_grow(gui, DIRECTION_VERTICAL); gui_set_grow(gui, DIRECTION_HORIZONTAL); gui_set_border(gui, (GuiColor) { 0x00, 0xff, 0xff, 0x80 }, BLOCK_OUTLINE_SIZE); gui_element_end(gui); gui_element_end(gui); if (ui.hover.panels.panel_side == SPLIT_SIDE_TOP) gui_grow(gui, DIRECTION_VERTICAL); if (ui.hover.panels.panel_side == SPLIT_SIDE_LEFT) gui_grow(gui, DIRECTION_HORIZONTAL); gui_element_end(gui); } static void draw_term_panel(void) { gui_element_begin(gui); gui_set_grow(gui, DIRECTION_HORIZONTAL); gui_set_grow(gui, DIRECTION_VERTICAL); gui_set_padding(gui, ELEMENT_GAP, ELEMENT_GAP); gui_set_rect(gui, (GuiColor) PANEL_BACKGROUND_COLOR); gui_element_begin(gui); gui_set_grow(gui, DIRECTION_HORIZONTAL); gui_set_grow(gui, DIRECTION_VERTICAL); gui_set_rect(gui, GUI_WHITE); gui_set_draw_subtype(gui, RECT_TERMINAL); gui_element_end(gui); gui_element_end(gui); } static void panel_on_hover(GuiElement* el) { ui.hover.panels.panel = el->custom_data; ui.hover.panels.panel_size = (Rectangle) { el->abs_x, el->abs_y, el->w, el->h }; if (ui.hover.panels.panel->type == PANEL_SPLIT) return; int mouse_x = gui->mouse_x - el->abs_x; int mouse_y = gui->mouse_y - el->abs_y; bool is_top_right = mouse_y < ((float)el->h / (float)el->w) * mouse_x; bool is_top_left = mouse_y < -((float)el->h / (float)el->w * mouse_x) + el->h; if (is_top_right) { if (is_top_left) { ui.hover.panels.panel_side = SPLIT_SIDE_TOP; } else { ui.hover.panels.panel_side = SPLIT_SIDE_RIGHT; } } else { if (is_top_left) { ui.hover.panels.panel_side = SPLIT_SIDE_LEFT; } else { ui.hover.panels.panel_side = SPLIT_SIDE_BOTTOM; } } } static void draw_panel(PanelTree* panel) { if (panel->type != PANEL_SPLIT && !panel->parent) { gui_element_begin(gui); gui_set_grow(gui, DIRECTION_VERTICAL); gui_set_grow(gui, DIRECTION_HORIZONTAL); gui_on_hover(gui, panel_on_hover); gui_set_custom_data(gui, panel); } switch (panel->type) { case PANEL_NONE: assert(false && "Attempt to render panel with type PANEL_NONE"); break; case PANEL_BLOCK_PALETTE: draw_block_palette(); break; case PANEL_CODE: draw_code_area(); break; case PANEL_TERM: draw_term_panel(); break; case PANEL_BLOCK_CATEGORIES: draw_block_categories(); break; case PANEL_SPLIT: gui_element_begin(gui); gui_set_grow(gui, DIRECTION_VERTICAL); gui_set_grow(gui, DIRECTION_HORIZONTAL); gui_set_direction(gui, panel->direction); gui_on_hover(gui, panel_on_hover); gui_set_custom_data(gui, panel); gui_element_begin(gui); if (panel->direction == DIRECTION_VERTICAL) { gui_set_percent_size(gui, panel->split_percent, DIRECTION_VERTICAL); gui_set_grow(gui, DIRECTION_HORIZONTAL); } else { gui_set_grow(gui, DIRECTION_VERTICAL); gui_set_percent_size(gui, panel->split_percent, DIRECTION_HORIZONTAL); } if (panel->left->type != PANEL_SPLIT) { gui_on_hover(gui, panel_on_hover); gui_set_custom_data(gui, panel->left); } draw_panel(panel->left); gui_element_end(gui); if (ui.hover.is_panel_edit_mode) { gui_element_begin(gui); if (panel->direction == DIRECTION_HORIZONTAL) { gui_set_grow(gui, DIRECTION_VERTICAL); } else { gui_set_grow(gui, DIRECTION_HORIZONTAL); } gui_set_min_size(gui, 10, 10); gui_set_rect(gui, (GuiColor) { 0xff, 0xff, 0xff, ui.hover.panels.drag_panel == panel ? 0x20 : ui.hover.panels.prev_panel == panel ? 0x80 : 0x40 }); gui_element_end(gui); } gui_element_begin(gui); gui_set_grow(gui, DIRECTION_VERTICAL); gui_set_grow(gui, DIRECTION_HORIZONTAL); if (panel->right->type != PANEL_SPLIT) { gui_on_hover(gui, panel_on_hover); gui_set_custom_data(gui, panel->right); } draw_panel(panel->right); gui_element_end(gui); gui_element_end(gui); break; } if (panel->type != PANEL_SPLIT) draw_split_preview(panel); if (panel->type != PANEL_SPLIT && !panel->parent) gui_element_end(gui); } static void draw_code(void) { for (size_t i = 0; i < vector_size(editor.code); i++) { Vector2 chain_pos = (Vector2) { editor.code[i].x * config.ui_size / 32.0 - editor.camera_pos.x, editor.code[i].y * config.ui_size / 32.0 - editor.camera_pos.y, }; Rectangle code_size = ui.hover.panels.code_panel_bounds; if (&editor.code[i] != ui.hover.editor.select_blockchain) { if (chain_pos.x > code_size.width || chain_pos.y > code_size.height) continue; if (editor.code[i].width > 0 && editor.code[i].height > 0 && (chain_pos.x + editor.code[i].width < 0 || chain_pos.y + editor.code[i].height < 0)) continue; } GuiElement* el = gui_element_begin(gui); gui_set_floating(gui); gui_set_position(gui, chain_pos.x, chain_pos.y); draw_blockchain(&editor.code[i], false, config.show_blockchain_previews, true); gui_element_end(gui); editor.code[i].width = el->w; editor.code[i].height = el->h; } } static void list_dropdown_on_hover(GuiElement* el) { assert(ui.dropdown.type == DROPDOWN_LIST); el->draw_type = DRAWTYPE_RECT; el->draw_subtype = GUI_SUBTYPE_DEFAULT; el->color = (GuiColor) { 0x40, 0x40, 0x40, 0xff }; // Double cast to avoid warning. In our case this operation is safe because el->custom_data currently stores a value of type int ui.dropdown.as.list.select_ind = (int)(size_t)el->custom_data; ui.hover.button.handler = ui.dropdown.handler; } static void draw_list_dropdown(void) { const int max_list_size = 10; gui_element_begin(gui); gui_set_rect(gui, (GuiColor) { 0x40, 0x40, 0x40, 0xff }); gui_set_gap(gui, 2); gui_set_padding(gui, 2, 2); if (ui.dropdown.as.list.len > max_list_size) { gui_set_scissor(gui); gui_set_fixed(gui, ui.dropdown.element->w + 5, max_list_size * (config.ui_size + 2) + 4); gui_set_scroll(gui, &ui.dropdown.as.list.scroll); gui_set_scroll_scaling(gui, (config.ui_size + 2) * 2); } else { gui_set_min_size(gui, ui.dropdown.element->w, 0); } for (int i = 0; i < ui.dropdown.as.list.len; i++) { gui_element_begin(gui); gui_set_grow(gui, DIRECTION_HORIZONTAL); gui_set_direction(gui, DIRECTION_HORIZONTAL); gui_set_align(gui, ALIGN_LEFT, ALIGN_CENTER); gui_set_min_size(gui, 0, config.ui_size); gui_set_padding(gui, config.ui_size * 0.3, 0); gui_set_rect(gui, (GuiColor) { 0x2b, 0x2b, 0x2b, 0xff }); gui_on_hover(gui, list_dropdown_on_hover); gui_set_custom_data(gui, (void*)(size_t)i); const char* list_value = sgettext(ui.dropdown.as.list.data[i]); gui_text(gui, &assets.fonts.font_cond, list_value, BLOCK_TEXT_SIZE, GUI_WHITE); gui_element_end(gui); } gui_element_end(gui); } static void dropdown_on_render(GuiElement* el) { int off_x = MAX(el->abs_x + el->w - gui->win_w, 0); int off_y = MAX(el->abs_y + el->h - gui->win_h, 0); el->x -= off_x; el->abs_x -= off_x; el->y -= off_y; el->abs_y -= off_y; } static void draw_dropdown(void) { if (!ui.dropdown.shown) return; ui.hover.button.handler = handle_dropdown_close; if (!ui.dropdown.element) { scrap_log(LOG_WARNING, "[DROPDOWN] Anchor is not set or gone"); handle_dropdown_close(); return; } gui_element_begin(gui); gui_set_floating(gui); gui_set_parent_anchor(gui, ui.dropdown.element); gui_set_position(gui, 0, ui.dropdown.element->h); gui_on_render(gui, dropdown_on_render); switch (ui.dropdown.type) { case DROPDOWN_COLOR_PICKER: draw_color_picker(); break; case DROPDOWN_LIST: draw_list_dropdown(); break; default: assert(false && "Unhandled dropdown type"); break; } gui_element_end(gui); } static void search_on_hover(GuiElement* el) { el->color = (GuiColor) { 0x40, 0x40, 0x40, 0xff }; ui.hover.editor.blockdef = el->custom_data; } static void draw_search_list(void) { gui_element_begin(gui); gui_set_floating(gui); gui_set_position(gui, editor.search_list_pos.x, editor.search_list_pos.y); gui_set_rect(gui, (GuiColor) { 0x40, 0x40, 0x40, 0xff }); gui_set_gap(gui, BLOCK_OUTLINE_SIZE); gui_set_padding(gui, BLOCK_OUTLINE_SIZE, BLOCK_OUTLINE_SIZE); gui_element_begin(gui); gui_set_rect(gui, (GuiColor) { 0x2b, 0x2b, 0x2b, 0xff }); gui_set_grow(gui, DIRECTION_HORIZONTAL); gui_set_padding(gui, BLOCK_OUTLINE_SIZE, 0); gui_set_min_size(gui, 0, config.ui_size); gui_set_direction(gui, DIRECTION_HORIZONTAL); gui_set_align(gui, ALIGN_LEFT, ALIGN_CENTER); draw_input_text(&assets.fonts.font_cond, &editor.search_list_search, "Search...", BLOCK_TEXT_SIZE, GUI_WHITE); gui_element_end(gui); gui_element_begin(gui); gui_set_fixed(gui, 0, config.ui_size * 5); gui_set_fit(gui, DIRECTION_HORIZONTAL); gui_set_gap(gui, BLOCK_OUTLINE_SIZE); gui_set_scissor(gui); gui_set_scroll(gui, &ui.search_list_scroll); for (size_t i = 0; i < vector_size(editor.search_list); i++) { gui_element_begin(gui); gui_set_rect(gui, (GuiColor) { 0x2b, 0x2b, 0x2b, 0xff }); gui_set_grow(gui, DIRECTION_HORIZONTAL); gui_on_hover(gui, search_on_hover); gui_set_custom_data(gui, editor.search_list[i]); Block dummy_block = { .blockdef = editor.search_list[i], .arguments = NULL, .parent = NULL, }; draw_block(&dummy_block, ui.hover.editor.prev_blockdef == editor.search_list[i], false, false, false); gui_element_end(gui); } gui_element_end(gui); gui_element_end(gui); } static void panel_editor_on_hover(GuiElement* el) { (void) el; if (!ui.hover.is_panel_edit_mode) return; ui.hover.panels.panel = NULL; } void scrap_gui_process(void) { gui_begin(gui); draw_top_bar(); draw_tab_bar(); GuiElement* tab_bar_anchor = NULL; if (ui.hover.is_panel_edit_mode) { gui_element_begin(gui); tab_bar_anchor = gui_get_element(gui); gui_element_end(gui); } draw_panel(editor.tabs[editor.current_tab].root_panel); draw_window(); gui_element_begin(gui); gui_set_floating(gui); gui_set_position(gui, gui->mouse_x, gui->mouse_y); gui_set_gap(gui, config.ui_size); int x_i = 0, x_i_max = ceil(sqrt(vector_size(editor.mouse_blockchains))), y_max = 0; gui_element_begin(gui); gui_set_direction(gui, DIRECTION_HORIZONTAL); gui_set_gap(gui, config.ui_size); for (size_t i = 0; i < vector_size(editor.mouse_blockchains); i++) { GuiElement* el = draw_blockchain(&editor.mouse_blockchains[i], false, false, true); editor.mouse_blockchains[i].width = el->w; editor.mouse_blockchains[i].height = el->h; x_i++; y_max = MAX(y_max, editor.mouse_blockchains[i].height); if (x_i >= x_i_max) { gui_element_end(gui); gui_element_begin(gui); gui_set_direction(gui, DIRECTION_HORIZONTAL); gui_set_gap(gui, config.ui_size); y_max = 0; x_i = 0; } } gui_element_end(gui); gui_element_end(gui); if (ui.hover.select_input == &editor.search_list_search) { draw_search_list(); } else { editor.search_list_pos = (Vector2) { gui->mouse_x, gui->mouse_y }; } if (ui.hover.is_panel_edit_mode) { if (ui.hover.panels.mouse_panel != PANEL_NONE) { gui_element_begin(gui); gui_set_floating(gui); gui_set_fixed(gui, gui->win_w * 0.3, gui->win_h * 0.3); gui_set_position(gui, gui->mouse_x, gui->mouse_y); PanelTree panel = (PanelTree) { .type = ui.hover.panels.mouse_panel, .parent = NULL, .left = NULL, .right = NULL, }; draw_panel(&panel); gui_element_end(gui); } gui_element_begin(gui); gui_set_floating(gui); gui_set_grow(gui, DIRECTION_HORIZONTAL); gui_set_position(gui, 0, 0); gui_set_parent_anchor(gui, tab_bar_anchor); gui_set_align(gui, ALIGN_CENTER, ALIGN_TOP); gui_set_padding(gui, 0, config.ui_size); gui_element_begin(gui); gui_set_padding(gui, config.ui_size * 0.3, config.ui_size * 0.3); gui_set_rect(gui, (GuiColor) { 0x00, 0x00, 0x00, 0x80 }); gui_set_align(gui, ALIGN_CENTER, ALIGN_TOP); gui_on_hover(gui, panel_editor_on_hover); gui_text(gui, &assets.fonts.font_eb, gettext("Panel edit mode"), config.ui_size * 0.8, GUI_WHITE); gui_spacer(gui, 0, config.ui_size * 0.25); gui_text(gui, &assets.fonts.font_cond_shadow, gettext("Click on panels to reposition them"), BLOCK_TEXT_SIZE, GUI_WHITE); gui_text(gui, &assets.fonts.font_cond_shadow, gettext("Drag panel edges to resize them"), BLOCK_TEXT_SIZE, GUI_WHITE); gui_spacer(gui, 0, config.ui_size * 0.25); gui_element_begin(gui); gui_set_direction(gui, DIRECTION_HORIZONTAL); gui_set_gap(gui, config.ui_size * 0.25); draw_panel_editor_button(gettext("Save"), config.ui_size, (GuiColor) { 0x40, 0xff, 0x40, 0xff }, handle_panel_editor_save_button); draw_panel_editor_button(gettext("Done"), config.ui_size, (GuiColor) { 0x80, 0x80, 0x80, 0xff }, handle_panel_editor_cancel_button); gui_element_end(gui); gui_element_end(gui); gui_element_end(gui); } draw_dropdown(); gui_end(gui); } // Adopted from Raylib 5.0 bool svg_load(const char* file_name, size_t width, size_t height, Image* out_image) { if (!file_name) return false; // Bug in Raylib 5.0: // LoadFileData() does not return null-terminated string which nsvgParse expects, so // i am using nsvgParseFromFile() here instead NSVGimage* svg = nsvgParseFromFile(file_name, "px", 96.0); if (!svg) { scrap_log(LOG_WARNING, "[SVG] Could not load \"%s\"", file_name); return false; } unsigned char* image_data = malloc(width * height * 4); float scale_width = width / svg->width, scale_height = height / svg->height, scale = MAX(scale_width, scale_height); int offset_x = 0, offset_y = 0; if (scale_height > scale_width) { offset_y = (height - svg->height * scale) / 2; } else { offset_x = (width - svg->width * scale) / 2; } NSVGrasterizer *rast = nsvgCreateRasterizer(); nsvgRasterize(rast, svg, offset_x, offset_y, scale, image_data, width, height, width*4); out_image->data = image_data; out_image->width = width; out_image->height = height; out_image->mipmaps = 1; out_image->format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8; nsvgDeleteRasterizer(rast); nsvgDelete(svg); return true; } // Draw order for render_border_control() // // 1 // +---------------+ // 4 | | 2 // + +---------+ // 3 // static void render_border_control(GuiDrawCommand cmd) { unsigned short border_w = cmd.data.border_width; Color color = CONVERT_COLOR(cmd.color, Color); /* 1 */ DrawRectangle(cmd.pos_x, cmd.pos_y, cmd.width, border_w, color); /* 2 */ DrawRectangle(cmd.pos_x + cmd.width - border_w, cmd.pos_y, border_w, cmd.height, color); /* 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); /* 4 */ DrawRectangle(cmd.pos_x, cmd.pos_y, border_w, cmd.height, color); } // Draw order for render_border_control_body() // // + + // 1 | | 2 // + + // static void render_border_control_body(GuiDrawCommand cmd) { unsigned short border_w = cmd.data.border_width; Color color = CONVERT_COLOR(cmd.color, Color); /* 1 */ DrawRectangle(cmd.pos_x, cmd.pos_y, border_w, cmd.height, color); /* 2 */ DrawRectangle(cmd.pos_x + cmd.width - border_w, cmd.pos_y, border_w, cmd.height, color); } // Draw order for render_border_control_end() // // 1 // + +---------+ // 4 | | 2 // + +---------+ // 3 // static void render_border_control_end(GuiDrawCommand cmd) { unsigned short border_w = cmd.data.border_width; Color color = CONVERT_COLOR(cmd.color, Color); /* 1 */ DrawRectangle(cmd.pos_x + BLOCK_CONTROL_INDENT - border_w, cmd.pos_y, cmd.width - BLOCK_CONTROL_INDENT, border_w, color); /* 2 */ DrawRectangle(cmd.pos_x + cmd.width - border_w, cmd.pos_y, border_w, cmd.height, color); /* 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); /* 4 */ DrawRectangle(cmd.pos_x, cmd.pos_y, border_w, cmd.height, color); } // Draw order for render_border_end() // // 1 // + +---------+ // 4 | | 2 // +---------------+ // 3 static void render_border_end(GuiDrawCommand cmd) { unsigned short border_w = cmd.data.border_width; Color color = CONVERT_COLOR(cmd.color, Color); /* 1 */ DrawRectangle(cmd.pos_x + BLOCK_CONTROL_INDENT - border_w, cmd.pos_y, cmd.width - BLOCK_CONTROL_INDENT, border_w, color); /* 2 */ DrawRectangle(cmd.pos_x + cmd.width - border_w, cmd.pos_y, border_w, cmd.height, color); /* 3 */ DrawRectangle(cmd.pos_x, cmd.pos_y + cmd.height - border_w, cmd.width, border_w, color); /* 4 */ DrawRectangle(cmd.pos_x, cmd.pos_y, border_w, cmd.height, color); } // Draw order for render_border_notched() and render_rect_notched() // // 1 // +--------------+ 2 // | + // 5 | | 3 // +---------------+ // 4 static void render_border_notched(GuiDrawCommand cmd) { unsigned short border_w = cmd.data.border_width; Color color = CONVERT_COLOR(cmd.color, Color); int notch_size = config.ui_size / 4; /* 1 */ DrawRectangle(cmd.pos_x, cmd.pos_y, cmd.width - notch_size, border_w, color); /* 2 */ DrawRectanglePro((Rectangle) { cmd.pos_x + cmd.width - notch_size, cmd.pos_y, sqrtf((notch_size * notch_size) * 2), border_w, }, (Vector2) {0}, 45.0, color); /* 3 */ DrawRectangle(cmd.pos_x + cmd.width - border_w, cmd.pos_y + notch_size, border_w, cmd.height - notch_size, color); /* 4 */ DrawRectangle(cmd.pos_x, cmd.pos_y + cmd.height - border_w, cmd.width, border_w, color); /* 5 */ DrawRectangle(cmd.pos_x, cmd.pos_y, border_w, cmd.height, color); } static void render_rect_notched(GuiDrawCommand cmd) { Color color = CONVERT_COLOR(cmd.color, Color); int notch_size = config.ui_size / 4; DrawRectangle(cmd.pos_x, cmd.pos_y, cmd.width - notch_size, cmd.height, color); DrawRectangle(cmd.pos_x, cmd.pos_y + notch_size, cmd.width, cmd.height - notch_size, color); DrawTriangle( (Vector2) { cmd.pos_x + cmd.width - notch_size - 1, cmd.pos_y }, (Vector2) { cmd.pos_x + cmd.width - notch_size - 1, cmd.pos_y + notch_size }, (Vector2) { cmd.pos_x + cmd.width, cmd.pos_y + notch_size }, color ); } static void draw_text_slice(Font font, const char *text, float pos_x, float pos_y, unsigned int text_size, float font_size, Color color) { if (font.texture.id == 0) return; Vector2 pos = (Vector2) { pos_x, pos_y }; int codepoint, index; float scale_factor = font_size / font.baseSize; for (unsigned int i = 0; i < text_size;) { if (!text[i]) break; int next = 0; codepoint = GetCodepointNext(&text[i], &next); index = search_glyph(font, codepoint); i += next; if (codepoint != ' ') DrawTextCodepoint(font, codepoint, pos, font_size, color); if (font.glyphs[index].advanceX != 0) { pos.x += font.glyphs[index].advanceX * scale_factor; } else { pos.y += font.recs[index].width * scale_factor + font.glyphs[index].offsetX; } } } static void scrap_gui_render(void) { #ifdef DEBUG bool show_bounds = IsKeyDown(KEY_F4); #endif GuiDrawCommand command; GUI_GET_COMMANDS(gui, command) { Texture2D* image = command.data.image; switch (command.type) { case DRAWTYPE_UNKNOWN: assert(false && "Got unknown draw type"); break; case DRAWTYPE_BORDER: switch (command.subtype) { case BORDER_NORMAL: DrawRectangleLinesEx( (Rectangle) { floor(command.pos_x), floor(command.pos_y), command.width, command.height }, command.data.border_width, CONVERT_COLOR(command.color, Color) ); break; case BORDER_CONTROL: render_border_control(command); break; case BORDER_CONTROL_BODY: render_border_control_body(command); break; case BORDER_END: render_border_end(command); break; case BORDER_CONTROL_END: render_border_control_end(command); break; case BORDER_NOTCHED: render_border_notched(command); break; default: assert(false && "Unhandled draw border type"); break; } break; case DRAWTYPE_RECT: switch (command.subtype) { case RECT_NORMAL: DrawRectangle(command.pos_x, command.pos_y, command.width, command.height, CONVERT_COLOR(command.color, Color)); break; case RECT_NOTCHED: render_rect_notched(command); break; case RECT_TERMINAL: term_resize(command.width, command.height); draw_term(command.pos_x, command.pos_y); break; default: assert(false && "Unhandled draw rect type"); break; } break; case DRAWTYPE_TEXT: draw_text_slice( *(Font*)command.data.text.font, command.data.text.text, command.pos_x, command.pos_y, command.data.text.text_size, command.height, CONVERT_COLOR(command.color, Color) ); break; case DRAWTYPE_IMAGE: switch (command.subtype) { case IMAGE_NORMAL: DrawTextureEx( *image, (Vector2) { command.pos_x + SHADOW_DISTANCE, command.pos_y + SHADOW_DISTANCE }, 0.0, (float)command.height / (float)image->height, (Color) { 0x00, 0x00, 0x00, 0x80 } ); DrawTextureEx( *image, (Vector2) { command.pos_x, command.pos_y}, 0.0, (float)command.height / (float)image->height, CONVERT_COLOR(command.color, Color) ); break; case IMAGE_STRETCHED: DrawTexturePro( *image, (Rectangle) { 0, 0, image->width, image->height }, (Rectangle) { command.pos_x, command.pos_y, command.width, command.height }, (Vector2) {0}, 0.0, CONVERT_COLOR(command.color, Color) ); break; } break; case DRAWTYPE_SCISSOR_SET: BeginScissorMode(command.pos_x, command.pos_y, command.width, command.height); break; case DRAWTYPE_SCISSOR_RESET: EndScissorMode(); break; case DRAWTYPE_SHADER_BEGIN: BeginShaderMode(*(Shader*)command.data.shader); break; case DRAWTYPE_SHADER_END: EndShaderMode(); break; default: assert(false && "Unimplemented command render"); break; } #ifdef DEBUG if (show_bounds) DrawRectangleLinesEx((Rectangle) { command.pos_x, command.pos_y, command.width, command.height }, 1.0, (Color) { 0xff, 0x00, 0xff, 0x40 }); #endif } } static void print_debug(int* num, char* fmt, ...) { va_list va; va_start(va, fmt); vsnprintf(editor.debug_buffer[(*num)++], DEBUG_BUFFER_LINE_SIZE, fmt, va); va_end(va); } static void write_debug_buffer(void) { int i = 0; print_debug(&i, "Scrap " SCRAP_VERSION); print_debug(&i, "FPS: %d, Frame time: %.3f", GetFPS(), GetFrameTime()); #ifdef DEBUG if (!editor.show_debug) return; print_debug(&i, "UI time: %.3f", ui.ui_time); print_debug(&i, "Elements: %zu, Drawn: %zu", gui->elements_count, gui->command_list.size); print_debug(&i, "Mem: %.3f/%.3f MiB", (double)gui->arena->pos / (1024.0 * 1024.0), (double)gui->arena->reserve_size / (1024.0 * 1024.0)); print_debug(&i, " "); print_debug(&i, "Button handler: %p", ui.hover.button.handler); 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); print_debug(&i, "Argument: %p", ui.hover.editor.argument); print_debug(&i, "BlockChain: %p", ui.hover.editor.blockchain); 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); print_debug(&i, "Select block pos: (%.3f, %.3f)", ui.hover.editor.select_block_pos.x, ui.hover.editor.select_block_pos.y); 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); print_debug(&i, "Category: %p", ui.hover.category); 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); 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); print_debug(&i, "Drag cancelled: %d", ui.hover.drag_cancelled); 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); print_debug(&i, "Slider: %p, min: %d, max: %d", ui.hover.hover_slider.value, ui.hover.hover_slider.min, ui.hover.hover_slider.max); 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); print_debug(&i, "Panel: %p, side: %d", ui.hover.panels.panel, ui.hover.panels.panel_side); print_debug(&i, "Part: %d, Select: %d", ui.dropdown.as.color_picker.hover_part, ui.dropdown.as.color_picker.select_part); print_debug(&i, "Anchor: %p, Ref: %p", ui.dropdown.element, ui.dropdown.ref_object); #endif } void scrap_gui_process_render(void) { if (!ui.render_surface_needs_redraw) return; ui.render_surface_needs_redraw = false; ClearBackground(GetColor(0x202020ff)); draw_dots(); for (int i = 0; i < DEBUG_BUFFER_LINES; i++) editor.debug_buffer[i][0] = 0; write_debug_buffer(); scrap_gui_render(); if (vm.start_timeout == 0) { term_restart(); clear_compile_error(); #ifdef USE_INTERPRETER vm.exec = exec_new(&vm.thread); #else vm.exec = exec_new(&vm.thread, vm.start_mode); #endif vm.exec.code = editor.code; if (!thread_start(vm.exec.thread, &vm.exec)) { actionbar_show(gettext("Start failed!")); } else { actionbar_show(gettext("Started successfully!")); } } } ================================================ FILE: src/save.c ================================================ // Scrap is a project that allows anyone to build software using simple, block based interface. // // Copyright (C) 2024-2026 Grisshink // // This software is provided 'as-is', without any express or implied // warranty. In no event will the authors be held liable for any damages // arising from the use of this software. // // Permission is granted to anyone to use this software for any purpose, // including commercial applications, and to alter it and redistribute it // freely, subject to the following restrictions: // // 1. The origin of this software must not be misrepresented; you must not // claim that you wrote the original software. If you use this software // in a product, an acknowledgment in the product documentation would be // appreciated but is not required. // 2. Altered source versions must be plainly marked as such, and must not be // misrepresented as being the original software. // 3. This notice may not be removed or altered from any source distribution. #include "scrap.h" #include "vec.h" #include #include #include #include #include #include #include "../external/cfgpath.h" #define STR(v) #v #define SHARED_DIR_BUF_LEN 512 #define LOCALE_DIR_BUF_LEN 768 #define INT_TO_COLOR(v) ((BlockdefColor) { \ ((v) >> 0 ) & 255, \ ((v) >> 8 ) & 255, \ ((v) >> 16) & 255, \ ((v) >> 24) & 255, \ }) typedef struct { void* ptr; size_t size; size_t capacity; } SaveData; const int codepoint_regions[CODEPOINT_REGION_COUNT][2] = { { 0x20, 0x7e }, // All printable ASCII chars { 0x3bc, 0x3bc }, // Letter μ { 0x400, 0x4ff }, // Cyrillic letters }; int codepoint_start_ranges[CODEPOINT_REGION_COUNT] = {0}; char* language_list[5] = { "LanguageList|System", "English [en]", "Russian [ru]", "Kazakh [kk]", "Ukrainian [uk]", }; char scrap_ident[] = "SCRAP"; const char** save_block_ids = NULL; Blockdef** save_blockdefs = NULL; static unsigned int ver = 0; int save_find_id(const char* id); void save_code(const char* file_path, ProjectConfig* config, BlockChain* code); BlockChain* load_code(const char* file_path, ProjectConfig* out_config); void save_block(SaveData* save, Block* block); bool load_block(SaveData* save, Block* block); void save_blockdef(SaveData* save, Blockdef* blockdef); Blockdef* load_blockdef(SaveData* save); const char* language_to_code(Language lang) { switch (lang) { case LANG_SYSTEM: return "system"; case LANG_EN: return "en"; case LANG_RU: return "ru"; case LANG_KK: return "kk"; case LANG_UK: return "uk"; } assert(false && "Unreachable"); } Language code_to_language(const char* code) { if (!strcmp(code, "en")) { return LANG_EN; } else if (!strcmp(code, "ru")) { return LANG_RU; } else if (!strcmp(code, "kk")) { return LANG_KK; } else if (!strcmp(code, "uk")) { return LANG_UK; } else { return LANG_SYSTEM; } } const char* get_shared_dir_path(void) { static char out_path[SHARED_DIR_BUF_LEN] = {0}; if (*out_path) return out_path; #ifndef _WIN32 snprintf(out_path, SHARED_DIR_BUF_LEN, "%sdata", GetApplicationDirectory()); if (DirectoryExists(out_path)) { snprintf(out_path, SHARED_DIR_BUF_LEN, "%s", GetApplicationDirectory()); goto end; } snprintf(out_path, SHARED_DIR_BUF_LEN, "%s../share/scrap/", GetApplicationDirectory()); if (DirectoryExists(out_path)) goto end; snprintf(out_path, SHARED_DIR_BUF_LEN, "/usr/share/scrap/"); if (DirectoryExists(out_path)) goto end; snprintf(out_path, SHARED_DIR_BUF_LEN, "/usr/local/share/scrap/"); if (DirectoryExists(out_path)) goto end; #endif snprintf(out_path, SHARED_DIR_BUF_LEN, "%s", GetApplicationDirectory()); end: scrap_log(LOG_INFO, "Using \"%s\" as shared directory path", out_path); return out_path; } const char* get_locale_path(void) { static char out_path[LOCALE_DIR_BUF_LEN] = {0}; if (*out_path) return out_path; #ifndef _WIN32 snprintf(out_path, LOCALE_DIR_BUF_LEN, "%slocale", GetApplicationDirectory()); if (DirectoryExists(out_path)) goto end; #endif const char* shared_path = get_shared_dir_path(); if (!strcmp(shared_path, GetApplicationDirectory())) { snprintf(out_path, LOCALE_DIR_BUF_LEN, "%slocale", shared_path); } else { snprintf(out_path, LOCALE_DIR_BUF_LEN, "%s../locale", shared_path); } end: scrap_log(LOG_INFO, "Using \"%s\" as locale directory path", out_path); return out_path; } const char* into_shared_dir_path(const char* path) { return TextFormat("%s%s", get_shared_dir_path(), path); } // Returns the absolute path to the font, converting the relative path to a path inside the data directory const char* get_font_path(char* font_path) { return font_path[0] != '/' && font_path[1] != ':' ? into_shared_dir_path(font_path) : font_path; } void reload_fonts(void) { int* codepoints = vector_create(); for (int i = 0; i < CODEPOINT_REGION_COUNT; i++) { codepoint_start_ranges[i] = vector_size(codepoints); for (int j = codepoint_regions[i][0]; j <= codepoint_regions[i][1]; j++) { vector_add(&codepoints, j); } } int codepoints_count = vector_size(codepoints); if (IsFontValid(assets.fonts.font_cond)) UnloadFont(assets.fonts.font_cond); assets.fonts.font_cond = LoadFontEx(get_font_path(config.font_path), config.ui_size, codepoints, codepoints_count); SetTextureFilter(assets.fonts.font_cond.texture, TEXTURE_FILTER_BILINEAR); if (IsFontValid(assets.fonts.font_cond_shadow)) UnloadFont(assets.fonts.font_cond_shadow); assets.fonts.font_cond_shadow = LoadFontEx(get_font_path(config.font_path), BLOCK_TEXT_SIZE, codepoints, codepoints_count); SetTextureFilter(assets.fonts.font_cond_shadow.texture, TEXTURE_FILTER_BILINEAR); if (IsFontValid(assets.fonts.font_eb)) UnloadFont(assets.fonts.font_eb); assets.fonts.font_eb = LoadFontEx(get_font_path(config.font_bold_path), config.ui_size * 0.8, codepoints, codepoints_count); SetTextureFilter(assets.fonts.font_eb.texture, TEXTURE_FILTER_BILINEAR); if (IsFontValid(assets.fonts.font_mono)) UnloadFont(assets.fonts.font_mono); assets.fonts.font_mono = LoadFontEx(get_font_path(config.font_mono_path), config.ui_size, codepoints, codepoints_count); SetTextureFilter(assets.fonts.font_mono.texture, TEXTURE_FILTER_BILINEAR); vector_free(codepoints); prerender_font_shadow(&assets.fonts.font_cond_shadow); } void vector_set_string(char** vec, char* str) { vector_clear(*vec); for (char* i = str; *i; i++) vector_add(vec, *i); vector_add(vec, 0); } void config_new(Config* config) { config->font_path = vector_create(); config->font_bold_path = vector_create(); config->font_mono_path = vector_create(); } void config_free(Config* config) { vector_free(config->font_path); vector_free(config->font_bold_path); vector_free(config->font_mono_path); } void config_copy(Config* dst, Config* src) { dst->ui_size = src->ui_size; dst->fps_limit = src->fps_limit; dst->language = src->language; dst->block_size_threshold = src->block_size_threshold; dst->font_path = vector_copy(src->font_path); dst->font_bold_path = vector_copy(src->font_bold_path); dst->font_mono_path = vector_copy(src->font_mono_path); dst->show_blockchain_previews = src->show_blockchain_previews; } void set_default_config(Config* config) { config->ui_size = 32; config->fps_limit = 60; config->block_size_threshold = 1000; config->language = LANG_SYSTEM; vector_set_string(&config->font_path, DATA_PATH "nk57-cond.otf"); vector_set_string(&config->font_bold_path, DATA_PATH "nk57-eb.otf"); vector_set_string(&config->font_mono_path, DATA_PATH "nk57.otf"); config->show_blockchain_previews = true; } void project_config_new(ProjectConfig* config) { config->executable_name = vector_create(); config->linker_name = vector_create(); } void project_config_free(ProjectConfig* config) { vector_free(config->executable_name); vector_free(config->linker_name); } void project_config_set_default(ProjectConfig* config) { vector_set_string(&config->executable_name, "project"); vector_set_string(&config->linker_name, "ld"); } void apply_config(Config* dst, Config* src) { dst->fps_limit = src->fps_limit; SetTargetFPS(dst->fps_limit); dst->block_size_threshold = src->block_size_threshold; editor.camera_pos.x *= (float)src->ui_size / dst->ui_size; editor.camera_pos.y *= (float)src->ui_size / dst->ui_size; dst->ui_size = src->ui_size; vector_free(dst->font_path); vector_free(dst->font_bold_path); vector_free(dst->font_mono_path); dst->font_path = vector_copy(src->font_path); dst->font_bold_path = vector_copy(src->font_bold_path); dst->font_mono_path = vector_copy(src->font_mono_path); reload_fonts(); dst->show_blockchain_previews = src->show_blockchain_previews; } void save_panel_config(char* file_str, int* cursor, PanelTree* panel) { switch (panel->type) { case PANEL_NONE: *cursor += sprintf(file_str + *cursor, "PANEL_NONE "); break; case PANEL_CODE: *cursor += sprintf(file_str + *cursor, "PANEL_CODE "); break; case PANEL_TERM: *cursor += sprintf(file_str + *cursor, "PANEL_TERM "); break; case PANEL_BLOCK_PALETTE: *cursor += sprintf(file_str + *cursor, "PANEL_BLOCK_PALETTE "); break; case PANEL_SPLIT: *cursor += sprintf( file_str + *cursor, "PANEL_SPLIT %s %f ", panel->direction == DIRECTION_HORIZONTAL ? "DIRECTION_HORIZONTAL" : "DIRECTION_VERTICAL", panel->split_percent ); save_panel_config(file_str, cursor, panel->left); save_panel_config(file_str, cursor, panel->right); break; case PANEL_BLOCK_CATEGORIES: *cursor += sprintf(file_str + *cursor, "PANEL_BLOCK_CATEGORIES "); break; } } static char* read_panel_token(char** str, bool* is_eof) { if (*is_eof) return NULL; while (**str == ' ' || **str == '\0') { (*str)++; if (**str == '\0') return NULL; } char* out = *str; while (**str != ' ' && **str != '\0') (*str)++; if (**str == '\0') *is_eof = true; **str = '\0'; return out; } PanelTree* load_panel_config(char** config) { bool is_eof = false; char* name = read_panel_token(config, &is_eof); if (!name) return NULL; if (!strcmp(name, "PANEL_SPLIT")) { char* direction = read_panel_token(config, &is_eof); if (!direction) return NULL; char* split_percent = read_panel_token(config, &is_eof); if (!split_percent) return NULL; GuiElementDirection dir; if (!strcmp(direction, "DIRECTION_HORIZONTAL")) { dir = DIRECTION_HORIZONTAL; } else if (!strcmp(direction, "DIRECTION_VERTICAL")) { dir = DIRECTION_VERTICAL; } else { return NULL; } float percent = CLAMP(atof(split_percent), 0.0, 1.0); PanelTree* left = load_panel_config(config); if (!left) return NULL; PanelTree* right = load_panel_config(config); if (!right) { panel_delete(left); return NULL; } PanelTree* panel = malloc(sizeof(PanelTree)); panel->type = PANEL_SPLIT; panel->direction = dir; panel->parent = NULL; panel->split_percent = percent; panel->left = left; panel->right = right; left->parent = panel; right->parent = panel; return panel; } else if (!strcmp(name, "PANEL_NONE")) { return panel_new(PANEL_NONE); } else if (!strcmp(name, "PANEL_CODE")) { return panel_new(PANEL_CODE); } else if (!strcmp(name, "PANEL_TERM")) { return panel_new(PANEL_TERM); } else if (!strcmp(name, "PANEL_SIDEBAR")) { // Legacy panel name return panel_new(PANEL_BLOCK_PALETTE); } else if (!strcmp(name, "PANEL_BLOCK_PALETTE")) { return panel_new(PANEL_BLOCK_PALETTE); } else if (!strcmp(name, "PANEL_BLOCK_CATEGORIES")) { return panel_new(PANEL_BLOCK_CATEGORIES); } scrap_log(LOG_ERROR, "Unknown panel type: %s", name); return NULL; } void save_config(Config* config) { char* file_str = malloc(sizeof(char) * 32768); file_str[0] = 0; int cursor = 0; cursor += sprintf(file_str + cursor, "LANGUAGE=%s\n", language_to_code(config->language)); cursor += sprintf(file_str + cursor, "UI_SIZE=%u\n", config->ui_size); cursor += sprintf(file_str + cursor, "FPS_LIMIT=%u\n", config->fps_limit); cursor += sprintf(file_str + cursor, "BLOCK_SIZE_THRESHOLD=%u\n", config->block_size_threshold); cursor += sprintf(file_str + cursor, "FONT_PATH=%s\n", config->font_path); cursor += sprintf(file_str + cursor, "FONT_BOLD_PATH=%s\n", config->font_bold_path); cursor += sprintf(file_str + cursor, "FONT_MONO_PATH=%s\n", config->font_mono_path); cursor += sprintf(file_str + cursor, "SHOW_BLOCKCHAIN_PREVIEWS=%u\n", config->show_blockchain_previews); for (size_t i = 0; i < vector_size(editor.tabs); i++) { cursor += sprintf(file_str + cursor, "CONFIG_TAB_%s=", editor.tabs[i].name); save_panel_config(file_str, &cursor, editor.tabs[i].root_panel); cursor += sprintf(file_str + cursor, "\n"); } char config_path[MAX_PATH + 10]; get_user_config_folder(config_path, ARRLEN(config_path), CONFIG_FOLDER_NAME); strcat(config_path, CONFIG_PATH); SaveFileText(config_path, file_str); free(file_str); } PanelTree* find_panel_in_all_tabs(PanelType panel_type) { for (size_t i = 0; i < vector_size(editor.tabs); i++) { PanelTree* panel = find_panel(editor.tabs[i].root_panel, panel_type); if (panel) return panel; } return NULL; } void add_missing_panels(void) { PanelTree* categories = find_panel_in_all_tabs(PANEL_BLOCK_CATEGORIES); if (categories) return; PanelTree* palette = find_panel_in_all_tabs(PANEL_BLOCK_PALETTE); if (!palette) { scrap_log(LOG_ERROR, "Failed to insert missing panel PANEL_BLOCK_CATEGORIES: panel PANEL_BLOCK_PALETTE is missing"); return; } panel_split(palette, SPLIT_SIDE_TOP, PANEL_BLOCK_CATEGORIES, 0.35); } void load_config(Config* config) { delete_all_tabs(); char config_path[MAX_PATH + 10]; get_user_config_folder(config_path, ARRLEN(config_path), CONFIG_FOLDER_NAME); strcat(config_path, CONFIG_PATH); char* file = LoadFileText(config_path); if (!file) { init_panels(); editor.current_tab = 0; return; } int cursor = 0; bool has_lines = true; while (has_lines) { char* field = &file[cursor]; while(file[cursor] != '=' && file[cursor] != '\n' && file[cursor] != '\0') cursor++; if (file[cursor] == '\n') { cursor++; continue; }; if (file[cursor] == '\0') break; file[cursor++] = '\0'; char* value = &file[cursor]; int value_size = 0; while(file[cursor] != '\n' && file[cursor] != '\0') { cursor++; value_size++; } (void) value_size; if (file[cursor] == '\0') has_lines = false; file[cursor++] = '\0'; if (!strcmp(field, "UI_SIZE")) { int val = atoi(value); config->ui_size = val ? val : config->ui_size; } else if (!strcmp(field, "FPS_LIMIT")) { int val = atoi(value); config->fps_limit = val ? val : config->fps_limit; } else if (!strcmp(field, "BLOCK_SIZE_THRESHOLD")) { int val = atoi(value); config->block_size_threshold = val ? val : config->block_size_threshold; } else if (!strcmp(field, "FONT_PATH")) { vector_set_string(&config->font_path, value); } else if (!strcmp(field, "FONT_BOLD_PATH")) { vector_set_string(&config->font_bold_path, value); } else if (!strcmp(field, "FONT_MONO_PATH")) { vector_set_string(&config->font_mono_path, value); } else if (!strcmp(field, "SHOW_BLOCKCHAIN_PREVIEWS")) { config->show_blockchain_previews = atoi(value) != 0; } else if (!strncmp(field, "CONFIG_TAB_", sizeof("CONFIG_TAB_") - 1)) { char* panel_value = value; tab_new(field + sizeof("CONFIG_TAB_") - 1, load_panel_config(&panel_value)); } else if (!strcmp(field, "LANGUAGE")) { Language lang = code_to_language(value); config->language = lang; } else { scrap_log(LOG_WARNING, "Unknown key: %s", field); } } add_missing_panels(); if (vector_size(editor.tabs) == 0) init_panels(); if (editor.current_tab >= (int)vector_size(editor.tabs)) editor.current_tab = vector_size(editor.tabs) - 1; UnloadFileText(file); } #define save_add(save, data) save_add_item(save, &data, sizeof(data)) void* save_read_item(SaveData* save, size_t data_size) { if (save->size + data_size > save->capacity) { scrap_log(LOG_ERROR, "[LOAD] Unexpected EOF reading data"); return NULL; } void* ptr = save->ptr + save->size; save->size += data_size; return ptr; } bool save_read_varint(SaveData* save, unsigned int* out) { *out = 0; int pos = 0; unsigned char* chunk = NULL; do { chunk = save_read_item(save, sizeof(unsigned char)); if (!chunk) return false; *out |= (*chunk & 0x7f) << pos; pos += 7; } while ((*chunk & 0x80) == 0); return true; } void* save_read_array(SaveData* save, size_t data_size, unsigned int* array_len) { if (!save_read_varint(save, array_len)) return NULL; return save_read_item(save, data_size * *array_len); } void save_add_item(SaveData* save, const void* data, size_t data_size) { if (save->size + data_size > save->capacity) { save->capacity = save->capacity > 0 ? save->capacity * 2 : 256; save->ptr = realloc(save->ptr, save->capacity); } memcpy(save->ptr + save->size, data, data_size); save->size += data_size; } void save_add_varint(SaveData* save, unsigned int data) { unsigned char varint = 0; do { varint = data & 0x7f; data >>= 7; varint |= (data == 0) << 7; save_add(save, varint); } while (data); } void save_add_array(SaveData* save, const void* array, int array_size, size_t data_size) { save_add_varint(save, array_size); for (int i = 0; i < array_size; i++) save_add_item(save, array + data_size * i, data_size); } void free_save(SaveData* save) { free(save->ptr); save->size = 0; save->capacity = 0; } void save_blockdef_input(SaveData* save, Input* input) { save_add_varint(save, input->type); switch (input->type) { case INPUT_TEXT_DISPLAY: save_add_array(save, input->data.text, vector_size(input->data.text), sizeof(input->data.text[0])); break; case INPUT_ARGUMENT: save_add_varint(save, input->data.arg.constr); save_blockdef(save, input->data.arg.blockdef); break; default: assert(false && "Unimplemented input save"); break; } } void save_blockdef(SaveData* save, Blockdef* blockdef) { save_add_array(save, blockdef->id, strlen(blockdef->id) + 1, sizeof(blockdef->id[0])); save_add(save, blockdef->color); save_add_varint(save, blockdef->type); int input_count = vector_size(blockdef->inputs); save_add_varint(save, input_count); for (int i = 0; i < input_count; i++) save_blockdef_input(save, &blockdef->inputs[i]); } void save_block_arguments(SaveData* save, Argument* arg) { save_add_varint(save, arg->input_id); save_add_varint(save, arg->type); int string_id; static_assert(ARGUMENT_LAST == 5, "Exhaustive argument type in save_block_arguments"); switch (arg->type) { case ARGUMENT_TEXT: case ARGUMENT_CONST_STRING: string_id = save_find_id(arg->data.text); assert(string_id != -1); save_add_varint(save, string_id); break; case ARGUMENT_BLOCK: save_block(save, &arg->data.block); break; case ARGUMENT_BLOCKDEF: string_id = save_find_id(arg->data.blockdef->id); assert(string_id != -1); save_add_varint(save, string_id); break; case ARGUMENT_COLOR: save_add_varint(save, *(int*)&arg->data.color); break; default: assert(false && "Unimplemented argument save"); break; } } void save_block(SaveData* save, Block* block) { assert(block->blockdef->id != NULL); int arg_count = vector_size(block->arguments); int string_id = save_find_id(block->blockdef->id); assert(string_id != -1); save_add_varint(save, string_id); save_add_varint(save, arg_count); for (int i = 0; i < arg_count; i++) save_block_arguments(save, &block->arguments[i]); } void save_blockchain(SaveData* save, BlockChain* chain) { int blocks_count = vector_size(chain->blocks); save_add(save, chain->x); save_add(save, chain->y); save_add_varint(save, blocks_count); for (int i = 0; i < blocks_count; i++) save_block(save, &chain->blocks[i]); } void rename_blockdef(Blockdef* blockdef, int id) { blockdef_set_id(blockdef, TextFormat("custom%d", id)); int arg_id = 0; for (size_t i = 0; i < vector_size(blockdef->inputs); i++) { if (blockdef->inputs[i].type != INPUT_ARGUMENT) continue; blockdef_set_id(blockdef->inputs[i].data.arg.blockdef, TextFormat("custom%d_arg%d", id, arg_id++)); } } int save_find_id(const char* id) { for (size_t i = 0; i < vector_size(save_block_ids); i++) { if (!strcmp(save_block_ids[i], id)) return i; } return -1; } void save_add_id(const char* id) { if (save_find_id(id) != -1) return; vector_add(&save_block_ids, id); } void block_collect_ids(Block* block) { save_add_id(block->blockdef->id); for (size_t i = 0; i < vector_size(block->arguments); i++) { static_assert(ARGUMENT_LAST == 5, "Exhaustive argument type in block_collect_ids"); switch (block->arguments[i].type) { case ARGUMENT_TEXT: case ARGUMENT_CONST_STRING: save_add_id(block->arguments[i].data.text); break; case ARGUMENT_BLOCK: block_collect_ids(&block->arguments[i].data.block); break; case ARGUMENT_BLOCKDEF: save_add_id(block->arguments[i].data.blockdef->id); break; case ARGUMENT_COLOR: break; default: assert(false && "Unimplemented argument save id"); break; } } } void collect_all_code_ids(BlockChain* code) { for (size_t i = 0; i < vector_size(code); i++) { BlockChain* chain = &code[i]; for (size_t j = 0; j < vector_size(chain->blocks); j++) { block_collect_ids(&chain->blocks[j]); } } } void save_code(const char* file_path, ProjectConfig* config, BlockChain* code) { (void) config; SaveData save = {0}; ver = SCRAP_SAVE_VERSION; int chains_count = vector_size(code); Blockdef** blockdefs = vector_create(); save_block_ids = vector_create(); int id = 0; for (int i = 0; i < chains_count; i++) { Block* block = &code[i].blocks[0]; for (size_t j = 0; j < vector_size(block->arguments); j++) { if (block->arguments[j].type != ARGUMENT_BLOCKDEF) continue; rename_blockdef(block->arguments[j].data.blockdef, id++); vector_add(&blockdefs, block->arguments[j].data.blockdef); } } collect_all_code_ids(code); save_add_varint(&save, ver); save_add_array(&save, scrap_ident, ARRLEN(scrap_ident), sizeof(scrap_ident[0])); save_add_varint(&save, vector_size(save_block_ids)); for (size_t i = 0; i < vector_size(save_block_ids); i++) { save_add_array(&save, save_block_ids[i], strlen(save_block_ids[i]) + 1, sizeof(save_block_ids[i][0])); } save_add_varint(&save, id); for (size_t i = 0; i < vector_size(blockdefs); i++) save_blockdef(&save, blockdefs[i]); save_add_varint(&save, chains_count); for (int i = 0; i < chains_count; i++) save_blockchain(&save, &code[i]); SaveFileData(file_path, save.ptr, save.size); scrap_log(LOG_INFO, "%zu bytes written into %s", save.size, file_path); vector_free(save_block_ids); vector_free(blockdefs); free_save(&save); } Blockdef* find_blockdef(Blockdef** blockdefs, const char* id) { for (size_t i = 0; i < vector_size(blockdefs); i++) { if (!strcmp(id, blockdefs[i]->id)) return blockdefs[i]; } return NULL; } bool load_blockdef_input(SaveData* save, Input* input) { InputType type; if (!save_read_varint(save, (unsigned int*)&type)) return false; input->type = type; unsigned int text_len; InputArgumentConstraint constr; char* text; switch (input->type) { case INPUT_TEXT_DISPLAY: text = save_read_array(save, sizeof(char), &text_len); if (!text) return false; if (text[text_len - 1] != 0) return false; input->data.text = vector_create(); for (char* str = text; *str; str++) vector_add(&input->data.text, *str); vector_add(&input->data.text, 0); break; case INPUT_ARGUMENT: if (!save_read_varint(save, (unsigned int*)&constr)) return false; Blockdef* blockdef = load_blockdef(save); if (!blockdef) return false; input->data.arg.text = ""; input->data.arg.hint_text = gettext("any"); input->data.arg.constr = constr; input->data.arg.blockdef = blockdef; input->data.arg.blockdef->ref_count++; input->data.arg.blockdef->func = block_custom_arg; vector_add(&save_blockdefs, input->data.arg.blockdef); break; default: scrap_log(LOG_ERROR, "[LOAD] Unimplemented input load"); return false; break; } return true; } Blockdef* load_blockdef(SaveData* save) { unsigned int id_len; char* id = save_read_array(save, sizeof(char), &id_len); if (!id) return NULL; if (id_len == 0) return false; if (id[id_len - 1] != 0) return false; BlockdefColor* color = save_read_item(save, sizeof(BlockdefColor)); if (!color) return NULL; BlockdefType type; if (!save_read_varint(save, (unsigned int*)&type)) return NULL; if (ver < 3) { // Deprecated: Arg ids are now not needed for blockdefs int arg_id; if (!save_read_varint(save, (unsigned int*)&arg_id)) return NULL; } unsigned int input_count; if (!save_read_varint(save, &input_count)) return NULL; Blockdef* blockdef = malloc(sizeof(Blockdef)); blockdef->id = strcpy(malloc(id_len * sizeof(char)), id); blockdef->color = *color; blockdef->type = type; blockdef->ref_count = 0; blockdef->inputs = vector_create(); blockdef->func = block_exec_custom; for (unsigned int i = 0; i < input_count; i++) { Input input; if (!load_blockdef_input(save, &input)) { blockdef_free(blockdef); return NULL; } vector_add(&blockdef->inputs, input); } return blockdef; } bool load_block_argument(SaveData* save, Argument* arg) { unsigned int input_id; if (!save_read_varint(save, &input_id)) return false; ArgumentType arg_type; if (!save_read_varint(save, (unsigned int*)&arg_type)) return false; arg->type = arg_type; arg->input_id = input_id; unsigned int text_id; Block block; unsigned int blockdef_id; unsigned int color_int; static_assert(ARGUMENT_LAST == 5, "Exhaustive argument type in load_block_argument"); switch (arg_type) { case ARGUMENT_TEXT: case ARGUMENT_CONST_STRING: if (!save_read_varint(save, &text_id)) return false; arg->data.text = vector_create(); for (char* str = (char*)save_block_ids[text_id]; *str; str++) vector_add(&arg->data.text, *str); vector_add(&arg->data.text, 0); break; case ARGUMENT_BLOCK: if (!load_block(save, &block)) return false; arg->data.block = block; break; case ARGUMENT_BLOCKDEF: if (!save_read_varint(save, &blockdef_id)) return false; if (blockdef_id >= vector_size(save_block_ids)) { scrap_log(LOG_ERROR, "[LOAD] Out of bounds read of save_block_id at %u", blockdef_id); return false; } Blockdef* blockdef = find_blockdef(save_blockdefs, save_block_ids[blockdef_id]); if (!blockdef) return false; arg->data.blockdef = blockdef; arg->data.blockdef->ref_count++; break; case ARGUMENT_COLOR: if (!save_read_varint(save, &color_int)) return false; arg->data.color = INT_TO_COLOR(color_int); break; default: scrap_log(LOG_ERROR, "[LOAD] Unimplemented argument load"); return false; } return true; } bool load_block(SaveData* save, Block* block) { unsigned int block_id; if (!save_read_varint(save, &block_id)) return false; bool unknown_blockdef = false; Blockdef* blockdef = NULL; blockdef = find_blockdef(save_blockdefs, save_block_ids[block_id]); if (!blockdef) { blockdef = find_blockdef(vm.blockdefs, save_block_ids[block_id]); if (!blockdef) { scrap_log(LOG_WARNING, "[LOAD] No blockdef matched id: %s", save_block_ids[block_id]); unknown_blockdef = true; blockdef = blockdef_new(save_block_ids[block_id], BLOCKTYPE_NORMAL, (BlockdefColor) { 0x66, 0x66, 0x66, 0xff }, NULL); blockdef_add_text(blockdef, TextFormat(gettext("UNKNOWN %s"), save_block_ids[block_id])); } } unsigned int arg_count; if (!save_read_varint(save, &arg_count)) return false; block->blockdef = blockdef; block->arguments = vector_create(); block->parent = NULL; blockdef->ref_count++; for (unsigned int i = 0; i < arg_count; i++) { Argument arg; if (!load_block_argument(save, &arg)) { block_free(block); return false; } vector_add(&block->arguments, arg); if (unknown_blockdef) { blockdef_add_argument(blockdef, "", "", BLOCKCONSTR_UNLIMITED); } } return true; } bool load_blockchain(SaveData* save, BlockChain* chain) { int pos_x, pos_y; if (ver == 1) { struct { float x; float y; }* pos = save_read_item(save, sizeof(struct { float x; float y; })); if (!pos) return false; pos_x = pos->x; pos_y = pos->y; } else { int* pos = save_read_item(save, sizeof(int)); if (!pos) return false; pos_x = *pos; pos = save_read_item(save, sizeof(int)); if (!pos) return false; pos_y = *pos; } unsigned int blocks_count; if (!save_read_varint(save, &blocks_count)) return false; *chain = blockchain_new(); chain->x = pos_x; chain->y = pos_y; for (unsigned int i = 0; i < blocks_count; i++) { Block block; if (!load_block(save, &block)) { blockchain_free(chain); return false; } blockchain_add_block(chain, block); block_update_all_links(&chain->blocks[vector_size(chain->blocks) - 1]); } return true; } BlockChain* load_code(const char* file_path, ProjectConfig* out_config) { ProjectConfig config; project_config_new(&config); project_config_set_default(&config); BlockChain* code = vector_create(); save_blockdefs = vector_create(); save_block_ids = vector_create(); int save_size; void* file_data = LoadFileData(file_path, &save_size); if (!file_data) goto load_fail; scrap_log(LOG_INFO, "%zu bytes read from %s", save_size, file_path); SaveData save; save.ptr = file_data; save.size = 0; save.capacity = save_size; if (!save_read_varint(&save, &ver)) goto load_fail; if (ver < 1 || ver > SCRAP_SAVE_VERSION) { scrap_log(LOG_ERROR, "[LOAD] Unsupported version %d. Current scrap build expects save versions from 1 to " STR(SCRAP_SAVE_VERSION), ver); goto load_fail; } unsigned int ident_len; char* ident = save_read_array(&save, sizeof(char), &ident_len); if (!ident) goto load_fail; if (ident_len == 0) goto load_fail; if (ident[ident_len - 1] != 0 || ident_len != sizeof(scrap_ident) || strncmp(ident, scrap_ident, sizeof(scrap_ident))) { scrap_log(LOG_ERROR, "[LOAD] Not valid scrap save"); goto load_fail; } unsigned int block_ids_len; if (!save_read_varint(&save, &block_ids_len)) goto load_fail; for (unsigned int i = 0; i < block_ids_len; i++) { unsigned int id_len; char* id = save_read_array(&save, sizeof(char), &id_len); if (!id) goto load_fail; if (id_len == 0) goto load_fail; if (id[id_len - 1] != 0) goto load_fail; vector_add(&save_block_ids, id); } unsigned int custom_block_len; if (!save_read_varint(&save, &custom_block_len)) goto load_fail; for (unsigned int i = 0; i < custom_block_len; i++) { Blockdef* blockdef = load_blockdef(&save); if (!blockdef) goto load_fail; vector_add(&save_blockdefs, blockdef); } unsigned int code_len; if (!save_read_varint(&save, &code_len)) goto load_fail; for (unsigned int i = 0; i < code_len; i++) { BlockChain chain; if (!load_blockchain(&save, &chain)) goto load_fail; vector_add(&code, chain); } unsigned int len; char* executable_name = save_read_array(&save, sizeof(char), &len); if (executable_name) vector_set_string(&config.executable_name, executable_name); char* linker_name = save_read_array(&save, sizeof(char), &len); if (linker_name) vector_set_string(&config.linker_name, linker_name); *out_config = config; UnloadFileData(file_data); vector_free(save_block_ids); vector_free(save_blockdefs); return code; load_fail: if (file_data) UnloadFileData(file_data); for (size_t i = 0; i < vector_size(code); i++) blockchain_free(&code[i]); project_config_free(&config); vector_free(code); vector_free(save_block_ids); vector_free(save_blockdefs); return NULL; } ================================================ FILE: src/scrap-runtime.c ================================================ // Scrap is a project that allows anyone to build software using simple, block based interface. // // Copyright (C) 2024-2026 Grisshink // // This software is provided 'as-is', without any express or implied // warranty. In no event will the authors be held liable for any damages // arising from the use of this software. // // Permission is granted to anyone to use this software for any purpose, // including commercial applications, and to alter it and redistribute it // freely, subject to the following restrictions: // // 1. The origin of this software must not be misrepresented; you must not // claim that you wrote the original software. If you use this software // in a product, an acknowledgment in the product documentation would be // appreciated but is not required. // 2. Altered source versions must be plainly marked as such, and must not be // misrepresented as being the original software. // 3. This notice may not be removed or altered from any source distribution. #include "gc.h" #include "config.h" #ifdef _WIN32 #include #endif Gc* gc; void llvm_main(void); int main(void) { #ifdef _WIN32 SetConsoleOutputCP(65001); #endif Gc _gc = gc_new(MIN_MEMORY_LIMIT, MAX_MEMORY_LIMIT); gc = &_gc; llvm_main(); gc_free(gc); return 0; } ================================================ FILE: src/scrap.c ================================================ // Scrap is a project that allows anyone to build software using simple, block based interface. // // Copyright (C) 2024-2026 Grisshink // // This software is provided 'as-is', without any express or implied // warranty. In no event will the authors be held liable for any damages // arising from the use of this software. // // Permission is granted to anyone to use this software for any purpose, // including commercial applications, and to alter it and redistribute it // freely, subject to the following restrictions: // // 1. The origin of this software must not be misrepresented; you must not // claim that you wrote the original software. If you use this software // in a product, an acknowledgment in the product documentation would be // appreciated but is not required. // 2. Altered source versions must be plainly marked as such, and must not be // misrepresented as being the original software. // 3. This notice may not be removed or altered from any source distribution. #define SCRVM_IMPLEMENTATION #include "term.h" #include "scrap.h" #include "vec.h" #include "util.h" #include "rlgl.h" #include #include #include #include #include #include #define KiB(n) ((size_t)(n) << 10) #define MiB(n) ((size_t)(n) << 20) #define GiB(n) ((size_t)(n) << 30) // Global Variables Config config; ProjectConfig project_config; Assets assets; Vm vm; Gui* gui = NULL; Gui gui_val; Editor editor; UI ui; const char* line_shader_vertex = "#version 330\n" "in vec3 vertexPosition;\n" "in vec4 vertexColor;\n" "out vec2 fragCoord;\n" "out vec4 fragColor;\n" "uniform mat4 mvp;\n" "void main() {\n" " vec4 pos = mvp * vec4(vertexPosition, 1.0);\n" " fragCoord = pos.xy;\n" " fragColor = vertexColor;\n" " gl_Position = pos;\n" "}"; const char* line_shader_fragment = "#version 330\n" "in vec2 fragCoord;\n" "in vec4 fragColor;\n" "out vec4 finalColor;\n" "uniform float time = 0.0;\n" "void main() {\n" " vec2 coord = (fragCoord + 1.0) * 0.5;\n" " coord.y = 1.0 - coord.y;\n" " float pos = time * 4.0 - 1.0;\n" " float diff = clamp(1.0 - abs(coord.x + coord.y - pos), 0.0, 1.0);\n" " finalColor = vec4(fragColor.xyz, pow(diff, 2.0));\n" "}"; const char* gradient_shader_vertex = "#version 330\n" "in vec3 vertexPosition;\n" "in vec2 vertexTexCoord;\n" "in vec4 vertexColor;\n" "out vec2 fragCoord;\n" "out vec4 fragColor;\n" "uniform mat4 mvp;\n" "void main() {\n" " vec4 pos = mvp * vec4(vertexPosition, 1.0);\n" " fragCoord = vec2(vertexTexCoord.x, 1.0 - vertexTexCoord.y);\n" " fragColor = vertexColor;\n" " gl_Position = pos;\n" "}"; const char* gradient_shader_fragment = "#version 330\n" "in vec2 fragCoord;\n" "in vec4 fragColor;\n" "out vec4 finalColor;\n" "void main() {\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" " vec4 right = mix(vec4(0.0, 0.0, 0.0, 1.0), fragColor, fragCoord.y);\n" " finalColor = mix(left, right, fragCoord.x);\n" "}"; Image setup(void) { SetExitKey(KEY_NULL); ui.render_surface = LoadRenderTexture(GetScreenWidth(), GetScreenHeight()); SetTextureWrap(ui.render_surface.texture, TEXTURE_WRAP_MIRROR_REPEAT); assets.textures.dropdown = LoadTexture(into_shared_dir_path(DATA_PATH "drop.png")); SetTextureFilter(assets.textures.dropdown, TEXTURE_FILTER_BILINEAR); assets.textures.spectrum = LoadTexture(into_shared_dir_path(DATA_PATH "spectrum.png")); SetTextureFilter(assets.textures.spectrum, TEXTURE_FILTER_BILINEAR); Image window_icon; svg_load(into_shared_dir_path(DATA_PATH "logo.svg"), config.ui_size, config.ui_size, &window_icon); assets.textures.icon_logo = LoadTextureFromImage(window_icon); SetTextureFilter(assets.textures.icon_logo, TEXTURE_FILTER_BILINEAR); void* image_load_paths[] = { &assets.textures.button_add_arg, "add_arg.svg", &assets.textures.button_add_text, "add_text.svg", &assets.textures.button_arrow_left, "arrow_left.svg", &assets.textures.button_arrow_right, "arrow_right.svg", &assets.textures.button_build, "build.svg", &assets.textures.button_close, "close.svg", &assets.textures.button_del_arg, "del_arg.svg", &assets.textures.button_edit, "edit.svg", &assets.textures.button_run, "run.svg", &assets.textures.button_stop, "stop.svg", &assets.textures.icon_about, "about.svg", &assets.textures.icon_file, "file.svg", &assets.textures.icon_folder, "folder.svg", &assets.textures.icon_list, "list.svg", &assets.textures.icon_pi, "pi_symbol.svg", &assets.textures.icon_settings, "settings.svg", &assets.textures.icon_special, "special.svg", &assets.textures.icon_term, "term.svg", &assets.textures.icon_variable, "variable_symbol.svg", &assets.textures.icon_warning, "warning.svg", NULL, }; for (int i = 0; image_load_paths[i]; i += 2) { Image svg_img; 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)) { continue; } Texture2D* texture = image_load_paths[i]; *texture = LoadTextureFromImage(svg_img); SetTextureFilter(*texture, TEXTURE_FILTER_BILINEAR); UnloadImage(svg_img); } reload_fonts(); assets.line_shader = LoadShaderFromMemory(line_shader_vertex, line_shader_fragment); ui.shader_time_loc = GetShaderLocation(assets.line_shader, "time"); assets.gradient_shader = LoadShaderFromMemory(gradient_shader_vertex, gradient_shader_fragment); strncpy(editor.project_name, EDITOR_DEFAULT_PROJECT_NAME, 1024); editor.blockchain_select_counter = -1; ui.render_surface_needs_redraw = true; vm = vm_new(); register_blocks(&vm); editor.show_debug = true; editor.mouse_blockchains = vector_create(); editor.code = vector_create(); editor.search_list = vector_create(); editor.search_list_search = vector_create(); vector_add(&editor.search_list_search, 0); update_search(); term_init(term_measure_text, &assets.fonts.font_mono, config.ui_size * 0.6); // This fixes incorrect texture coordinates in gradient shader Texture2D texture = { .id = rlGetTextureIdDefault(), .width = 1, .height = 1, .mipmaps = 1, .format = PIXELFORMAT_UNCOMPRESSED_R8G8B8A8, }; SetShapesTexture(texture, (Rectangle){ 0.0f, 0.0f, 1.0f, 1.0f }); gui_val = gui_new(GiB(1)); gui = &gui_val; gui_set_measure_text_func(gui, scrap_gui_measure_text); gui_set_measure_image_func(gui, scrap_gui_measure_image); gui_update_window_size(gui, GetScreenWidth(), GetScreenHeight()); init_gui_window(); editor.blockchain_render_layer_widths = vector_create(); return window_icon; } void cleanup(void) { term_free(); for (size_t i = 0; i < vector_size(editor.mouse_blockchains); i++) blockchain_free(&editor.mouse_blockchains[i]); vector_free(editor.mouse_blockchains); for (vec_size_t i = 0; i < vector_size(editor.code); i++) blockchain_free(&editor.code[i]); vector_free(editor.code); vm_free(&vm); vector_free(editor.blockchain_render_layer_widths); gui_free(gui); delete_all_tabs(); vector_free(editor.tabs); vector_free(editor.search_list_search); vector_free(editor.search_list); vector_free(vm.compile_error); unregister_categories(); project_config_free(&project_config); config_free(&config); config_free(&window_config); CloseWindow(); } // Main function: Initializes configurations, sets up window, processes input, renders GUI, and cleans up resources on exit int main(void) { SetTraceLogCallback(scrap_log_va); config_new(&config); config_new(&window_config); project_config_new(&project_config); project_config_set_default(&project_config); editor.tabs = vector_create(); set_default_config(&config); load_config(&config); if (config.language != LANG_SYSTEM) { #ifdef _WIN32 scrap_set_env("LANG", language_to_code(config.language)); #else scrap_set_env("LANGUAGE", language_to_code(config.language)); #endif } setlocale(LC_MESSAGES, ""); textdomain("scrap"); bindtextdomain("scrap", get_locale_path()); #ifdef _WIN32 bind_textdomain_codeset("scrap", "UTF-8"); #endif SetConfigFlags(FLAG_MSAA_4X_HINT | FLAG_WINDOW_RESIZABLE); InitWindow(800, 600, "Scrap"); //SetWindowState(FLAG_VSYNC_HINT); SetTargetFPS(config.fps_limit); Image icon = setup(); SetWindowIcon(icon); // SetWindowIcon() copies the icon so we can safely unload it UnloadImage(icon); ui.scrap_running = true; while (ui.scrap_running) { if (WindowShouldClose()) { if (!editor.project_modified) { ui.scrap_running = false; break; } else { gui_window_show(draw_save_confirmation_window); } } vm_handle_running_thread(); scrap_gui_process_ui(); BeginTextureMode(ui.render_surface); scrap_gui_process_render(); EndTextureMode(); BeginDrawing(); Texture2D texture = ui.render_surface.texture; DrawTexturePro( texture, (Rectangle) { #ifdef ARABIC_MODE // Flip texture upside down and also mirror it ;) texture.width, texture.height, #else // Render everything just below the texture. This texture has wrapping mode set to TEXTURE_WRAP_MIRROR_REPEAT, // so this will have the effect of flipping the texture upside down 0, texture.height, #endif texture.width, texture.height, }, (Rectangle) { 0, 0, texture.width, texture.height, }, (Vector2) {0}, // Origin at 0,0 0.0, // No rotation WHITE // No tint ); EndDrawing(); } cleanup(); return 0; } ================================================ FILE: src/scrap.h ================================================ // Scrap is a project that allows anyone to build software using simple, block based interface. // // Copyright (C) 2024-2026 Grisshink // // This software is provided 'as-is', without any express or implied // warranty. In no event will the authors be held liable for any damages // arising from the use of this software. // // Permission is granted to anyone to use this software for any purpose, // including commercial applications, and to alter it and redistribute it // freely, subject to the following restrictions: // // 1. The origin of this software must not be misrepresented; you must not // claim that you wrote the original software. If you use this software // in a product, an acknowledgment in the product documentation would be // appreciated but is not required. // 2. Altered source versions must be plainly marked as such, and must not be // misrepresented as being the original software. // 3. This notice may not be removed or altered from any source distribution. #ifndef SCRAP_H #define SCRAP_H #include #include "ast.h" #include "raylib.h" #include "config.h" #include "scrap_gui.h" #include "util.h" #include "term.h" typedef struct Vm Vm; #ifdef USE_INTERPRETER #include "interpreter.h" #else #include "compiler.h" #endif typedef struct PanelTree PanelTree; typedef struct BlockCategory BlockCategory; typedef enum { LANG_SYSTEM = 0, LANG_EN, LANG_RU, LANG_KK, LANG_UK, } Language; typedef struct { int ui_size; int fps_limit; int block_size_threshold; Language language; char* font_path; char* font_bold_path; char* font_mono_path; bool show_blockchain_previews; } Config; typedef struct { char* executable_name; char* linker_name; } ProjectConfig; typedef bool (*ButtonClickHandler)(void); typedef enum { SPLIT_SIDE_NONE = 0, SPLIT_SIDE_TOP, SPLIT_SIDE_BOTTOM, SPLIT_SIDE_LEFT, SPLIT_SIDE_RIGHT, } SplitSide; typedef struct { SplitSide side; } SplitPreview; typedef enum { PANEL_NONE = 0, PANEL_SPLIT, PANEL_BLOCK_PALETTE, PANEL_CODE, PANEL_TERM, PANEL_BLOCK_CATEGORIES, } PanelType; struct PanelTree { PanelType type; GuiElementDirection direction; struct PanelTree* parent; float split_percent; struct PanelTree* left; // Becomes top when direction is DIRECTION_VERTICAL struct PanelTree* right; // Becomes bottom when direction is DIRECTION_VERTICAL }; typedef enum { EDITOR_NONE, EDITOR_BLOCKDEF, EDITOR_EDIT, EDITOR_ADD_ARG, EDITOR_DEL_ARG, EDITOR_ADD_TEXT, } EditorHoverPart; typedef struct { BlockChain* prev_blockchain; BlockChain* blockchain; Block* prev_block; Block* block; Argument* argument; Argument* prev_argument; Argument* parent_argument; Block* select_block; Argument* select_argument; BlockChain* select_blockchain; Vector2 select_block_pos; bool select_valid; EditorHoverPart part; Blockdef* edit_blockdef; Block* edit_block; Blockdef* prev_blockdef; Blockdef* blockdef; size_t blockdef_input; } EditorHoverInfo; typedef enum { DROPDOWN_LIST, DROPDOWN_COLOR_PICKER, } DropdownType; typedef struct { float hue, saturation, value; } HSV; typedef enum { COLOR_PICKER_NONE = 0, COLOR_PICKER_SV, COLOR_PICKER_SPECTRUM, } ColorPickerPartType; typedef struct { char** data; int len; int select_ind; int scroll; } ListDropdown; typedef struct { ColorPickerPartType hover_part, select_part; HSV color; Color* edit_color; char color_hex[10]; } ColorPickerDropdown; typedef struct { bool shown; void* ref_object; GuiElement* element; ButtonClickHandler handler; DropdownType type; union { ListDropdown list; ColorPickerDropdown color_picker; } as; } Dropdown; typedef struct { int* value; char** list; int list_len; } DropdownData; typedef struct { int min; int max; int* value; char value_str[16]; // Used to store value as string as gui does not store strings } SliderHoverInfo; typedef struct { char** input; Vector2 rel_pos; Font* font; float font_size; } InputHoverInfo; typedef enum { CATEGORY_ITEM_CHAIN, CATEGORY_ITEM_LABEL, } BlockCategoryItemType; typedef struct { BlockCategoryItemType type; union { BlockChain chain; struct { const char* text; Color color; } label; } data; } BlockCategoryItem; struct BlockCategory { const char* name; Color color; BlockCategoryItem* items; BlockCategory* next; BlockCategory* prev; }; typedef struct { PanelTree* panel; Rectangle panel_size; PanelTree* drag_panel; Rectangle drag_panel_size; PanelType mouse_panel; PanelTree* prev_panel; SplitSide panel_side; Rectangle code_panel_bounds; } PanelHoverInfo; typedef struct { ButtonClickHandler handler; void* data; } ButtonHoverInfo; typedef struct { bool is_panel_edit_mode; bool drag_cancelled; BlockCategory* category; InputHoverInfo input_info; char** select_input; int select_input_cursor; int select_input_mark; Vector2 mouse_click_pos; float time_at_last_pos; EditorHoverInfo editor; PanelHoverInfo panels; ButtonHoverInfo button; SliderHoverInfo hover_slider; SliderHoverInfo dragged_slider; int slider_last_val; DropdownData settings_dropdown_data; int* select_settings_dropdown_value; } HoverInfo; typedef struct { Texture2D button_add_arg; Texture2D button_add_text; Texture2D button_arrow_left; Texture2D button_arrow_right; Texture2D button_build; Texture2D button_close; Texture2D button_del_arg; Texture2D button_edit; Texture2D button_run; Texture2D button_stop; Texture2D dropdown; Texture2D icon_about; Texture2D icon_file; Texture2D icon_folder; Texture2D icon_list; Texture2D icon_logo; Texture2D icon_pi; Texture2D icon_settings; Texture2D icon_special; Texture2D icon_term; Texture2D icon_variable; Texture2D icon_warning; Texture2D spectrum; } TextureList; typedef struct { Font font_cond; Font font_cond_shadow; Font font_eb; Font font_mono; } Fonts; typedef struct { Fonts fonts; TextureList textures; Shader line_shader; Shader gradient_shader; } Assets; typedef struct { char* name; PanelTree* root_panel; } Tab; typedef struct { float show_time; char text[ACTION_BAR_MAX_SIZE]; } ActionBar; typedef struct { Vector2 min_pos; Vector2 max_pos; } BlockCode; typedef struct { int scroll_amount; BlockCategory* current_category; BlockCategory* categories_start; BlockCategory* categories_end; } BlockPalette; typedef void (*WindowGuiRenderFunc)(void); typedef struct { char project_name[1024]; bool project_modified; Tab* tabs; Vector2 camera_pos; Vector2 camera_click_pos; BlockChain* code; BlockPalette palette; char* search_list_search; Blockdef** search_list; Vector2 search_list_pos; ActionBar actionbar; BlockChain* mouse_blockchains; SplitPreview split_preview; int* blockchain_render_layer_widths; int current_tab; int blockchain_select_counter; char debug_buffer[DEBUG_BUFFER_LINES][DEBUG_BUFFER_LINE_SIZE]; bool show_debug; } Editor; typedef struct { bool scrap_running; RenderTexture2D render_surface; bool render_surface_needs_redraw; bool render_surface_redraw_next; int shader_time_loc; float shader_time; HoverInfo hover; int categories_scroll; int search_list_scroll; Dropdown dropdown; #ifdef DEBUG double ui_time; #endif } UI; struct Vm { Blockdef** blockdefs; size_t end_blockdef; Thread thread; Exec exec; char** compile_error; Block* compile_error_block; BlockChain* compile_error_blockchain; int start_timeout; // = -1; #ifndef USE_INTERPRETER CompilerMode start_mode; // = COMPILER_MODE_JIT; #endif }; extern Config config; extern Config window_config; extern ProjectConfig project_config; extern Assets assets; extern Vm vm; extern Gui* gui; extern Editor editor; extern UI ui; extern char* language_list[5]; extern const int codepoint_regions[CODEPOINT_REGION_COUNT][2]; extern int codepoint_start_ranges[CODEPOINT_REGION_COUNT]; // scrap.c // Nothing... // render.c void actionbar_show(const char* text); void process_render(void); void prerender_font_shadow(Font* font); void scrap_gui_process_render(void); void scrap_gui_process(void); bool svg_load(const char* file_name, size_t width, size_t height, Image* out_image); const char* sgettext(const char* msgid); void input_on_hover(GuiElement* el); void draw_input_text(Font* font, char** input, const char* hint, unsigned short font_size, GuiColor font_color); // input.c void scrap_gui_process_ui(void); PanelTree* find_panel(PanelTree* root, PanelType panel); void update_search(void); void show_list_dropdown(char** list, int list_len, void* ref_object, ButtonClickHandler handler); GuiMeasurement scrap_gui_measure_image(void* image, unsigned short size); GuiMeasurement scrap_gui_measure_text(void* font, const char* text, unsigned int text_size, unsigned short font_size); TermVec term_measure_text(void* font, const char* text, unsigned int text_size, unsigned short font_size); int search_glyph(Font font, int codepoint); size_t tab_new(char* name, PanelTree* root_panel); void delete_all_tabs(void); void init_panels(void); PanelTree* panel_new(PanelType type); void panel_split(PanelTree* panel, SplitSide side, PanelType new_panel_type, float split_percent); void panel_delete(PanelTree* panel); bool save_project(void); bool handle_file_button_click(void); bool handle_settings_button_click(void); bool handle_about_button_click(void); bool handle_run_button_click(void); bool handle_build_button_click(void); bool handle_stop_button_click(void); bool handle_code_tab_click(void); bool handle_output_tab_click(void); bool handle_dropdown_close(void); bool handle_file_menu_click(void); bool handle_editor_close_button(void); bool handle_editor_edit_button(void); bool handle_editor_add_arg_button(void); bool handle_editor_add_text_button(void); bool handle_editor_del_arg_button(void); bool handle_panel_editor_save_button(void); bool handle_panel_editor_cancel_button(void); bool handle_tab_button(void); bool handle_add_tab_button(void); bool handle_category_click(void); bool handle_jump_to_block_button_click(void); bool handle_error_window_close_button_click(void); bool handle_color_picker_click(void); bool handle_editor_color_button(void); // save.c void config_new(Config* config); void config_free(Config* config); void set_default_config(Config* config); void apply_config(Config* dst, Config* src); void save_config(Config* config); void load_config(Config* config); void config_copy(Config* dst, Config* src); void save_code(const char* file_path, ProjectConfig* config, BlockChain* code); BlockChain* load_code(const char* file_path, ProjectConfig* out_config); void project_config_new(ProjectConfig* config); void project_config_free(ProjectConfig* config); void project_config_set_default(ProjectConfig* config); const char* language_to_code(Language lang); Language code_to_language(const char* code); const char* get_locale_path(void); const char* get_shared_dir_path(void); const char* into_shared_dir_path(const char* path); void reload_fonts(void); // window.c void init_gui_window(void); void gui_window_show(WindowGuiRenderFunc func); void gui_window_hide(void); void gui_window_hide_immediate(void); WindowGuiRenderFunc gui_window_get_render_func(void); bool gui_window_is_shown(void); void handle_window(void); void draw_window(void); void draw_settings_window(void); void draw_project_settings_window(void); void draw_about_window(void); void draw_save_confirmation_window(void); // blocks.c void register_blocks(Vm* vm); #ifdef USE_INTERPRETER bool block_custom_arg(Exec* exec, Block* block, int argc, AnyValue* argv, AnyValue* return_val, ControlState control_state); bool block_exec_custom(Exec* exec, Block* block, int argc, AnyValue* argv, AnyValue* return_val, ControlState control_state); #else bool block_custom_arg(Exec* exec, Block* block, int argc, FuncArg* argv, FuncArg* return_val, ControlState control_state); bool block_exec_custom(Exec* exec, Block* block, int argc, FuncArg* argv, FuncArg* return_val, ControlState control_state); #endif // vm.c BlockCategory block_category_new(const char* name, Color color); BlockCategory* block_category_register(BlockCategory category); void block_category_add_blockdef(BlockCategory* category, Blockdef* blockdef); void block_category_add_label(BlockCategory* category, const char* label, Color color); size_t blockdef_register(Vm* vm, Blockdef* blockdef); void blockdef_unregister(Vm* vm, size_t block_id); void unregister_categories(void); Vm vm_new(void); void vm_free(Vm* vm); #ifdef USE_INTERPRETER bool vm_start(void); #else bool vm_start(CompilerMode mode); #endif bool vm_stop(void); void vm_handle_running_thread(void); void clear_compile_error(void); Block block_new_ms(Blockdef* blockdef); // platform.c void scrap_set_env(const char* name, const char* value); #ifndef USE_INTERPRETER bool spawn_process(char* command, char* error, size_t error_len); #endif #endif // SCRAP_H ================================================ FILE: src/scrap_gui.c ================================================ // Scrap is a project that allows anyone to build software using simple, block based interface. // // Copyright (C) 2024-2026 Grisshink // // This software is provided 'as-is', without any express or implied // warranty. In no event will the authors be held liable for any damages // arising from the use of this software. // // Permission is granted to anyone to use this software for any purpose, // including commercial applications, and to alter it and redistribute it // freely, subject to the following restrictions: // // 1. The origin of this software must not be misrepresented; you must not // claim that you wrote the original software. If you use this software // in a product, an acknowledgment in the product documentation would be // appreciated but is not required. // 2. Altered source versions must be plainly marked as such, and must not be // misrepresented as being the original software. // 3. This notice may not be removed or altered from any source distribution. #include "scrap_gui.h" #include #include #include #include #include #define KiB(n) ((size_t)(n) << 10) #define GUI_ALIGN_UP_POW2(n, p) (((size_t)(n) + ((size_t)(p) - 1)) & (~((size_t)(p) - 1))) #define GUI_ARENA_BASE_POS (sizeof(GuiMemArena)) #define GUI_ARENA_ALIGN (sizeof(void*)) #define MAX(x, y) ((x) > (y) ? (x) : (y)) #define MIN(x, y) ((x) < (y) ? (x) : (y)) #define CLAMP(x, min, max) (MIN(MAX(min, x), max)) #define SIZING_X(el) (GuiElementSizing)(el->sizing & 0x0f) #define SIZING_Y(el) (GuiElementSizing)((el->sizing >> 4) & 0x0f) #define ANCHOR_X(el) (GuiAlignmentType)(el->anchor & 0x0f) #define ANCHOR_Y(el) (GuiAlignmentType)((el->anchor >> 4) & 0x0f) #define NEED_RESIZE(el) ((el->flags >> 5) & 1) #define SCISSOR(el) ((el->flags >> 4) & 1) #define FLOATING(el) ((el->flags >> 3) & 1) #define ALIGN_X(el) (GuiAlignmentType)((el->flags >> 6) & 3) #define ALIGN_Y(el) (GuiAlignmentType)((el->flags >> 1) & 3) #define DIRECTION(el) (GuiElementDirection)(el->flags & 1) #define SET_SIZING_X(el, size) (el->sizing = (el->sizing & 0xf0) | size) #define SET_SIZING_Y(el, size) (el->sizing = (el->sizing & 0x0f) | (size << 4)) #define SET_NEED_RESIZE(el, resize) (el->flags = (el->flags & ~(1 << 5)) | ((resize & 1) << 5)) #define SET_SCISSOR(el, scissor) (el->flags = (el->flags & ~(1 << 4)) | ((scissor & 1) << 4)) #define SET_FLOATING(el, floating) (el->flags = (el->flags & ~(1 << 3)) | ((floating & 1) << 3)) #define SET_ALIGN_X(el, ali) (el->flags = (el->flags & 0x3f) | (ali << 6)) #define SET_ALIGN_Y(el, ali) (el->flags = (el->flags & 0xf9) | (ali << 1)) #define SET_DIRECTION(el, dir) (el->flags = (el->flags & 0xfe) | dir) #define SET_ANCHOR(el, x, y) (el->anchor = x | (y << 4)) static void gui_render(Gui* gui, GuiElement* el); static void flush_command_batch(Gui* gui); static unsigned long gui_plat_get_pagesize(void); static void* gui_plat_mem_reserve(size_t size); static bool gui_plat_mem_commit(void* ptr, size_t size); static bool gui_plat_mem_release(void* ptr, size_t size); static bool inside_scissor(GuiDrawBounds rect, GuiBounds scissor) { return (rect.x + rect.w > scissor.x) && (rect.x < scissor.x + scissor.w) && (rect.y + rect.h > scissor.y) && (rect.y < scissor.y + scissor.h); } static bool mouse_inside(Gui* gui, GuiBounds rect) { return ((gui->mouse_x > rect.x) && (gui->mouse_x < rect.x + rect.w) && (gui->mouse_y > rect.y) && (gui->mouse_y < rect.y + rect.h)); } Gui gui_new(size_t arena_size) { return (Gui) { .arena = gui_arena_new(MAX(arena_size, KiB(512)), KiB(512)), }; } void gui_free(Gui* gui) { gui_arena_free(gui->arena); } void gui_begin(Gui* gui) { gui_arena_clear(gui->arena); gui->command_list_iter = 0; gui->command_list_last_batch = 0; gui->elements_count = 0; gui->current_element = NULL; gui->command_list = (GuiDrawCommandList) {0}; gui->aux_command_list = (GuiDrawCommandList) {0}; gui->scissor_stack = (GuiScissorStack) {0}; gui->root_element = gui_element_begin(gui); gui_set_fixed(gui, gui->win_w, gui->win_h); } void gui_end(Gui* gui) { gui_element_end(gui); gui_render(gui, gui->root_element); flush_command_batch(gui); } void gui_set_measure_text_func(Gui* gui, GuiMeasureTextSliceFunc measure_text) { gui->measure_text = measure_text; } void gui_set_measure_image_func(Gui* gui, GuiMeasureImageFunc measure_image) { gui->measure_image = measure_image; } void gui_update_mouse_pos(Gui* gui, short mouse_x, short mouse_y) { gui->mouse_x = mouse_x; gui->mouse_y = mouse_y; } void gui_update_mouse_scroll(Gui* gui, int mouse_scroll) { gui->mouse_scroll = mouse_scroll; } void gui_update_window_size(Gui* gui, unsigned short win_w, unsigned short win_h) { gui->win_w = win_w; gui->win_h = win_h; } static bool is_command_lesseq(GuiDrawCommand* left, GuiDrawCommand* right) { if (left->type != right->type) return left->type <= right->type; switch (left->type) { case DRAWTYPE_IMAGE: return left->data.image <= right->data.image; case DRAWTYPE_TEXT: return left->data.text.font <= right->data.text.font; default: return true; // Equal } } static void merge(GuiDrawCommandList list, GuiDrawCommandList aux_list, int start, int middle, int end) { int left_pos = start, right_pos = middle; for (int i = start; i < end; i++) { if (left_pos < middle && (right_pos >= end || is_command_lesseq(&list.items[left_pos], &list.items[right_pos]))) { aux_list.items[i] = list.items[left_pos++]; } else { aux_list.items[i] = list.items[right_pos++]; } } } static void split_and_merge_commands(GuiDrawCommandList list, GuiDrawCommandList aux_list, int start, int end) { if (end - start <= 1) return; int middle = (start + end) / 2; split_and_merge_commands(aux_list, list, start, middle); split_and_merge_commands(aux_list, list, middle, end); merge(list, aux_list, start, middle, end); } static void sort_commands(GuiDrawCommandList list, GuiDrawCommandList aux_list, int start, int end) { for (int i = start; i < end; i++) aux_list.items[i] = list.items[i]; split_and_merge_commands(aux_list, list, start, end); } static void flush_command_batch(Gui* gui) { if (gui->command_list_last_batch >= gui->command_list.size) return; // Skip sorting unwanted elements while (gui->command_list.items[gui->command_list_last_batch].type > 4) gui->command_list_last_batch++; sort_commands(gui->command_list, gui->aux_command_list, gui->command_list_last_batch, gui->command_list.size); gui->command_list_last_batch = gui->command_list.size; } static void new_draw_command(Gui* gui, GuiDrawBounds bounds, GuiDrawType draw_type, unsigned char draw_subtype, GuiDrawData data, GuiColor color) { GuiDrawCommand command = { .pos_x = bounds.x, .pos_y = bounds.y, .width = bounds.w, .height = bounds.h, .type = draw_type, .data = data, .color = color, .subtype = draw_subtype, }; gui_arena_append(gui->arena, gui->command_list, command); gui_arena_append(gui->arena, gui->aux_command_list, command); } static GuiBounds scissor_rect(GuiBounds rect, GuiBounds scissor) { if (rect.x < scissor.x) { rect.w = MAX(0, rect.w - (scissor.x - rect.x)); rect.x = scissor.x; } if (rect.y < scissor.y) { rect.h = MAX(0, rect.h - (scissor.y - rect.y)); rect.y = scissor.y; } if (rect.x + rect.w > scissor.x + scissor.w) { rect.w = MAX(0, rect.w - ((rect.x + rect.w) - (scissor.x + scissor.w))); } if (rect.y + rect.h > scissor.y + scissor.h) { rect.h = MAX(0, rect.h - ((rect.y + rect.h) - (scissor.y + scissor.h))); } return rect; } static void gui_get_anchor_pos(GuiElement* el, float* anchor_x, float* anchor_y) { switch (ANCHOR_X(el)) { case ALIGN_CENTER: *anchor_x = (float)el->w * el->scaling / 2; break; case ALIGN_RIGHT: *anchor_x = (float)el->w * el->scaling; break; default: break; } switch (ANCHOR_Y(el)) { case ALIGN_CENTER: *anchor_y = (float)el->h * el->scaling / 2; break; case ALIGN_RIGHT: *anchor_y = (float)el->h * el->scaling; break; default: break; } } static void gui_render(Gui* gui, GuiElement* el) { 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 }; bool hover = false; float parent_pos_x = 0, parent_pos_y = 0, parent_scaling = 1.0; if (el->parent) { if (el->parent_anchor) { parent_pos_x = el->parent_anchor->abs_x; parent_pos_y = el->parent_anchor->abs_y; } else { parent_pos_x = el->parent->abs_x; parent_pos_y = el->parent->abs_y; } parent_scaling = el->parent->scaling; } float anchor_x = 0, anchor_y = 0; gui_get_anchor_pos(el, &anchor_x, &anchor_y); el->abs_x = ((float)el->x - anchor_x) * parent_scaling + parent_pos_x; el->abs_y = ((float)el->y - anchor_y) * parent_scaling + parent_pos_y; if (mouse_inside(gui, scissor_rect((GuiBounds) { (el->x - anchor_x) * parent_scaling + parent_pos_x, (el->y - anchor_y) * parent_scaling + parent_pos_y, el->w * el->scaling, el->h * el->scaling }, prev_scissor))) { if (el->handle_hover) el->handle_hover(el); hover = true; } if (el->handle_pre_render) el->handle_pre_render(el); GuiDrawBounds el_bounds = (GuiDrawBounds) { parent_pos_x + ((float)el->x - anchor_x) * parent_scaling, parent_pos_y + ((float)el->y - anchor_y) * parent_scaling, (float)el->w * el->scaling, (float)el->h * el->scaling, }; if (SCISSOR(el) || FLOATING(el) || el->shader) flush_command_batch(gui); GuiBounds scissor = prev_scissor; if (SCISSOR(el)) { scissor = scissor_rect(prev_scissor, (GuiBounds) { el_bounds.x, el_bounds.y, el_bounds.w, el_bounds.h }); if (scissor.w > 0 && scissor.h > 0) { new_draw_command(gui, (GuiDrawBounds) { scissor.x, scissor.y, scissor.w, scissor.h }, DRAWTYPE_SCISSOR_SET, GUI_SUBTYPE_DEFAULT, (GuiDrawData) {0}, (GuiColor) {0}); } gui_arena_append(gui->arena, gui->scissor_stack, scissor); } if (el->shader) new_draw_command(gui, el_bounds, DRAWTYPE_SHADER_BEGIN, GUI_SUBTYPE_DEFAULT, (GuiDrawData) { .shader = el->shader }, (GuiColor) {0}); if (el->draw_type != DRAWTYPE_UNKNOWN && inside_scissor(el_bounds, scissor)) { new_draw_command(gui, el_bounds, el->draw_type, el->draw_subtype, el->data, el->color); } if (el->shader) { flush_command_batch(gui); new_draw_command(gui, el_bounds, DRAWTYPE_SHADER_END, GUI_SUBTYPE_DEFAULT, (GuiDrawData) { .shader = el->shader }, (GuiColor) {0}); } for (GuiElement* iter = el->child_elements_begin; iter; iter = iter->next) { gui_render(gui, iter); } if (el->scroll_value) { int el_size = DIRECTION(el) == DIRECTION_HORIZONTAL ? el->w : el->h; int content_size = DIRECTION(el) == DIRECTION_HORIZONTAL ? el->cursor_x : el->cursor_y; int max = content_size - el_size; if (max > 0) { flush_command_batch(gui); GuiDrawCommand command; command.type = DRAWTYPE_RECT; command.subtype = GUI_SUBTYPE_DEFAULT; command.color = (GuiColor) { 0xff, 0xff, 0xff, 0x80 }; float scroll_size = (float)el_size / ((float)content_size / (float)el_size); float scroll_pos = (-(float)*el->scroll_value / (float)max) * ((float)el_size - scroll_size); if (DIRECTION(el) == DIRECTION_HORIZONTAL) { command.width = scroll_size * el->scaling; command.height = 5 * el->scaling; command.pos_x = el_bounds.x + scroll_pos * parent_scaling; command.pos_y = el_bounds.y + el_bounds.h - command.height; } else { command.width = 5 * el->scaling; command.height = scroll_size * el->scaling; command.pos_x = el_bounds.x + el_bounds.w - command.width; command.pos_y = el_bounds.y + scroll_pos * parent_scaling; } gui_arena_append(gui->arena, gui->command_list, command); gui_arena_append(gui->arena, gui->aux_command_list, command); } if (hover) *el->scroll_value += gui->mouse_scroll * el->scroll_scaling; if (*el->scroll_value < -max) *el->scroll_value = -max; if (*el->scroll_value > 0) *el->scroll_value = 0; } if (FLOATING(el) || SCISSOR(el)) flush_command_batch(gui); if (SCISSOR(el)) { gui->scissor_stack.size--; if (gui->scissor_stack.size == 0) { new_draw_command(gui, el_bounds, DRAWTYPE_SCISSOR_RESET, GUI_SUBTYPE_DEFAULT, (GuiDrawData) {0}, (GuiColor) {0}); } else { if (prev_scissor.w > 0 && prev_scissor.h > 0) { 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}); } } } } static GuiElement* gui_element_new(Gui* gui) { GuiElement* el = gui_arena_alloc(gui->arena, sizeof(GuiElement)); memset(el, 0, sizeof(*el)); el->scaling = 1.0; el->size_percentage = 1.0; el->scroll_scaling = 64; gui->elements_count++; return el; } GuiElement* gui_element_begin(Gui* gui) { GuiElement* parent = gui->current_element; GuiElement* el = gui_element_new(gui); gui->current_element = el; el->parent = parent; if (parent) { if (!parent->child_elements_end) { parent->child_elements_begin = el; parent->child_elements_end = el; } else { parent->child_elements_end->next = el; el->prev = parent->child_elements_end; parent->child_elements_end = el; } el->scaling = parent->scaling; el->x = parent->cursor_x; el->y = parent->cursor_y; } return el; } static void gui_element_offset(GuiElement* el, int offset_x, int offset_y) { for (GuiElement* iter = el->child_elements_begin; iter; iter = iter->next) { iter->x += offset_x; iter->y += offset_y; } } static void gui_element_realign(GuiElement* el) { int align_div; if (ALIGN_X(el) != ALIGN_TOP) { align_div = ALIGN_X(el) == ALIGN_CENTER ? 2 : 1; for (GuiElement* iter = el->child_elements_begin; iter; iter = iter->next) { if (FLOATING(iter)) continue; if (DIRECTION(el) == DIRECTION_VERTICAL) { iter->x = (el->w - iter->w) / align_div; } else { iter->x += MAX(0, (el->w - el->pad_w + el->gap - el->cursor_x) / align_div); } } } if (ALIGN_Y(el) != ALIGN_TOP) { align_div = ALIGN_Y(el) == ALIGN_CENTER ? 2 : 1; for (GuiElement* iter = el->child_elements_begin; iter; iter = iter->next) { if (FLOATING(iter)) continue; if (DIRECTION(el) == DIRECTION_VERTICAL) { iter->y += MAX(0, (el->h - el->pad_h + el->gap - el->cursor_y) / align_div); } else { iter->y = (el->h - iter->h) / align_div; } } } if (el->scroll_value) { if (DIRECTION(el) == DIRECTION_HORIZONTAL) { gui_element_offset(el, *el->scroll_value, 0); } else { gui_element_offset(el, 0, *el->scroll_value); } } } static void gui_element_resize(Gui* gui, GuiElement* el, unsigned short new_w, unsigned short new_h) { el->w = new_w; el->h = new_h; int left_w = el->w - el->pad_w * 2 + el->gap; int left_h = el->h - el->pad_h * 2 + el->gap; int grow_elements = 0; for (GuiElement* iter = el->child_elements_begin; iter; iter = iter->next) { if (FLOATING(iter)) continue; if (DIRECTION(el) == DIRECTION_VERTICAL) { if (SIZING_Y(iter) == SIZING_GROW) { grow_elements++; } else if (SIZING_Y(iter) == SIZING_PERCENT) { left_h -= el->h * iter->size_percentage; } else { left_h -= iter->h; } left_h -= el->gap; } else { if (SIZING_X(iter) == SIZING_GROW) { grow_elements++; } else if (SIZING_X(iter) == SIZING_PERCENT) { left_w -= el->w * iter->size_percentage; } else { left_w -= iter->w; } left_w -= el->gap; } } if (left_w < 0) left_w = 0; if (left_h < 0) left_h = 0; el->cursor_x = el->pad_w; el->cursor_y = el->pad_h; for (GuiElement* iter = el->child_elements_begin; iter; iter = iter->next) { bool is_floating = FLOATING(iter); if (!is_floating) { iter->x = el->cursor_x; iter->y = el->cursor_y; } int size_w = iter->w; int size_h = iter->h; GuiElementSizing sizing_x = SIZING_X(iter); GuiElementSizing sizing_y = SIZING_Y(iter); if (sizing_x == SIZING_PERCENT) size_w = el->w * iter->size_percentage; if (sizing_y == SIZING_PERCENT) size_h = el->h * iter->size_percentage; if (DIRECTION(el) == DIRECTION_VERTICAL) { if (sizing_x == SIZING_GROW) size_w = el->w - el->pad_w * 2; if (sizing_y == SIZING_GROW) size_h = left_h / grow_elements; 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); if (!is_floating) el->cursor_y += iter->h + el->gap; } else { if (sizing_x == SIZING_GROW) size_w = left_w / grow_elements; if (sizing_y == SIZING_GROW) size_h = el->h - el->pad_h * 2; 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); if (!is_floating) el->cursor_x += iter->w + el->gap; } } if (DIRECTION(el) == DIRECTION_HORIZONTAL) { el->cursor_x += el->pad_w - el->gap; } else { el->cursor_y += el->pad_h - el->gap; } gui_element_realign(el); } static void gui_element_advance(GuiElement* el, GuiMeasurement ms) { if (!el) return; if (DIRECTION(el) == DIRECTION_HORIZONTAL) { el->cursor_x += ms.w + el->gap; if (SIZING_X(el) != SIZING_FIXED) el->w = MAX(el->w, el->cursor_x + el->pad_w); if (SIZING_Y(el) != SIZING_FIXED) el->h = MAX(el->h, ms.h + el->pad_h * 2); } else { el->cursor_y += ms.h + el->gap; if (SIZING_X(el) != SIZING_FIXED) el->w = MAX(el->w, ms.w + el->pad_w * 2); if (SIZING_Y(el) != SIZING_FIXED) el->h = MAX(el->h, el->cursor_y + el->pad_h); } } void gui_element_end(Gui* gui) { GuiElement* el = gui->current_element; GuiElement* parent = gui->current_element->parent; if (DIRECTION(el) == DIRECTION_VERTICAL) { el->h = MAX(el->min_h, el->h - el->gap); } else { el->w = MAX(el->min_w, el->w - el->gap); } if (!FLOATING(el)) gui_element_advance(parent, (GuiMeasurement) { el->w, el->h }); GuiElementSizing sizing_x = SIZING_X(el), sizing_y = SIZING_Y(el); bool has_defined_size = sizing_x != SIZING_GROW && sizing_x != SIZING_PERCENT && sizing_y != SIZING_GROW && sizing_y != SIZING_PERCENT; if (!has_defined_size) SET_NEED_RESIZE(parent, 1); if (has_defined_size && NEED_RESIZE(el) && (sizing_x == SIZING_FIXED || sizing_y == SIZING_FIXED || sizing_x == SIZING_FIT || sizing_y == SIZING_FIT)) { gui_element_resize(gui, el, el->w, el->h); } else { gui_element_realign(el); } gui->current_element = parent; } GuiElement* gui_get_element(Gui* gui) { return gui->current_element; } void gui_on_hover(Gui* gui, GuiHandler handler) { GuiElement* el = gui->current_element; el->handle_hover = handler; } void gui_on_render(Gui* gui, GuiHandler handler) { GuiElement* el = gui->current_element; el->handle_pre_render = handler; } void gui_set_anchor(Gui* gui, GuiAlignmentType anchor_x, GuiAlignmentType anchor_y) { GuiElement* el = gui->current_element; SET_ANCHOR(el, anchor_x, anchor_y); } void gui_set_parent_anchor(Gui* gui, GuiElement* anchor) { GuiElement* el = gui->current_element; el->parent_anchor = anchor; } void gui_set_shader(Gui* gui, void* shader) { GuiElement* el = gui->current_element; el->shader = shader; } void gui_set_scroll_scaling(Gui* gui, int scroll_scaling) { GuiElement* el = gui->current_element; el->scroll_scaling = scroll_scaling; } void gui_set_scroll(Gui* gui, int* scroll_value) { GuiElement* el = gui->current_element; el->scroll_value = scroll_value; } void gui_set_scissor(Gui* gui) { GuiElement* el = gui->current_element; SET_SCISSOR(el, 1); } void gui_scale_element(Gui* gui, float scaling) { GuiElement* el = gui->current_element; el->scaling *= scaling; } void* gui_set_state(Gui* gui, void* state, unsigned short state_len) { GuiElement* el = gui->current_element; if (el->custom_state) return el->custom_state; void* el_state = gui_arena_alloc(gui->arena, state_len); memcpy(el_state, state, state_len); el->custom_state = el_state; return el->custom_state; } void* gui_get_state(GuiElement* el) { return el->custom_state; } void gui_set_floating(Gui* gui) { GuiElement* el = gui->current_element; SET_FLOATING(el, 1); } void gui_set_position(Gui* gui, int x, int y) { GuiElement* el = gui->current_element; el->x = x; el->y = y; } void gui_set_custom_data(Gui* gui, void* custom_data) { GuiElement* el = gui->current_element; el->custom_data = custom_data; } void gui_set_fixed(Gui* gui, unsigned short w, unsigned short h) { GuiElement* el = gui->current_element; el->sizing = SIZING_FIXED | (SIZING_FIXED << 4); // This sets both dimensions to SIZING_FIXED el->w = w; el->h = h; } void gui_set_fit(Gui* gui, GuiElementDirection direction) { GuiElement* el = gui->current_element; if (direction == DIRECTION_VERTICAL) { SET_SIZING_Y(el, SIZING_FIT); } else { SET_SIZING_X(el, SIZING_FIT); } } void gui_set_padding(Gui* gui, unsigned short pad_w, unsigned short pad_h) { GuiElement* el = gui->current_element; el->pad_w = pad_w; el->pad_h = pad_h; el->w = MAX(el->w, el->pad_w * 2); el->h = MAX(el->h, el->pad_h * 2); el->cursor_x = pad_w; el->cursor_y = pad_h; } void gui_set_gap(Gui* gui, unsigned short gap) { GuiElement* el = gui->current_element; el->gap = gap; } void gui_set_grow(Gui* gui, GuiElementDirection direction) { GuiElement* el = gui->current_element; if (direction == DIRECTION_VERTICAL) { SET_SIZING_Y(el, SIZING_GROW); el->h = 0; } else { SET_SIZING_X(el, SIZING_GROW); el->w = 0; } } void gui_set_percent_size(Gui* gui, float percentage, GuiElementDirection direction) { GuiElement* el = gui->current_element; el->size_percentage = percentage; if (direction == DIRECTION_VERTICAL) { SET_SIZING_Y(el, SIZING_PERCENT); el->h = 0; } else { SET_SIZING_X(el, SIZING_PERCENT); el->w = 0; } } void gui_set_draw_subtype(Gui* gui, unsigned char subtype) { GuiElement* el = gui->current_element; el->draw_subtype = subtype; } void gui_set_direction(Gui* gui, GuiElementDirection direction) { GuiElement* el = gui->current_element; SET_DIRECTION(el, direction); } void gui_set_rect(Gui* gui, GuiColor color) { GuiElement* el = gui->current_element; el->draw_type = DRAWTYPE_RECT; el->color = color; } void gui_set_border(Gui* gui, GuiColor color, unsigned int border_width) { GuiElement* el = gui->current_element; el->draw_type = DRAWTYPE_BORDER; el->color = color; el->data.border_width = border_width; } void gui_set_text_slice(Gui* gui, void* font, const char* text, unsigned int text_size, unsigned short font_size, GuiColor color) { if (text_size == 0) return; GuiElement* el = gui->current_element; GuiMeasurement text_bounds = gui->measure_text(font, text, text_size, font_size); el->draw_type = DRAWTYPE_TEXT; el->color = color; el->data.text.text = text; el->data.text.font = font; el->data.text.text_size = text_size; el->w = text_bounds.w; el->h = text_bounds.h; } inline void gui_set_text(Gui* gui, void* font, const char* text, unsigned short font_size, GuiColor color) { gui_set_text_slice(gui, font, text, strlen(text), font_size, color); } void gui_set_image(Gui* gui, void* image, unsigned short size, GuiColor color) { GuiElement* el = gui->current_element; GuiMeasurement image_size = gui->measure_image(image, size); el->draw_type = DRAWTYPE_IMAGE; el->color = color; el->data.image = image; el->w = image_size.w; el->h = image_size.h; } void gui_set_align(Gui* gui, GuiAlignmentType align_x, GuiAlignmentType align_y) { GuiElement* el = gui->current_element; SET_ALIGN_X(el, align_x); SET_ALIGN_Y(el, align_y); } void gui_set_min_size(Gui* gui, unsigned short min_w, unsigned short min_h) { GuiElement* el = gui->current_element; el->w = MAX(el->w, min_w); el->h = MAX(el->h, min_h); el->min_w = min_w; el->min_h = min_h; } inline void gui_text_slice(Gui* gui, void* font, const char* text, unsigned int text_size, unsigned short font_size, GuiColor color) { if (text_size == 0) return; gui_element_begin(gui); gui_set_text_slice(gui, font, text, text_size, font_size, color); gui_element_end(gui); } inline void gui_text(Gui* gui, void* font, const char* text, unsigned short size, GuiColor color) { gui_element_begin(gui); gui_set_text(gui, font, text, size, color); gui_element_end(gui); } inline void gui_image(Gui* gui, void* image, unsigned short size, GuiColor color) { gui_element_begin(gui); gui_set_image(gui, image, size, color); gui_element_end(gui); } inline void gui_grow(Gui* gui, GuiElementDirection direction) { gui_element_begin(gui); gui_set_grow(gui, direction); gui_element_end(gui); } inline void gui_spacer(Gui* gui, unsigned short w, unsigned short h) { gui_element_begin(gui); gui_set_min_size(gui, w, h); gui_element_end(gui); } GuiMemArena* gui_arena_new(size_t reserve_size, size_t commit_size) { unsigned long pagesize = gui_plat_get_pagesize(); reserve_size = GUI_ALIGN_UP_POW2(reserve_size, pagesize); commit_size = GUI_ALIGN_UP_POW2(commit_size, pagesize); GuiMemArena* arena = gui_plat_mem_reserve(reserve_size); if (!gui_plat_mem_commit(arena, commit_size)) return NULL; arena->reserve_size = reserve_size; arena->commit_size = commit_size; arena->pos = GUI_ARENA_BASE_POS; arena->commit_pos = commit_size; return arena; } void gui_arena_free(GuiMemArena* arena) { gui_plat_mem_release(arena, arena->reserve_size); } const char* gui_arena_sprintf(GuiMemArena* arena, size_t max_size, const char* fmt, ...) { char* str = gui_arena_alloc(arena, max_size); va_list va; va_start(va, fmt); int size = vsnprintf(str, max_size, fmt, va); va_end(va); gui_arena_pop(arena, max_size - size - 1); return str; } void* gui_arena_alloc(GuiMemArena* arena, size_t size) { size_t pos_aligned = GUI_ALIGN_UP_POW2(arena->pos, GUI_ARENA_ALIGN); size_t new_pos = pos_aligned + size; assert(new_pos <= arena->reserve_size); if (new_pos > arena->commit_pos) { size_t new_commit_pos = new_pos; new_commit_pos += arena->commit_size - 1; new_commit_pos -= new_commit_pos % arena->commit_size; new_commit_pos = MIN(new_commit_pos, arena->reserve_size); unsigned char* mem = (unsigned char*)arena + arena->commit_pos; size_t commit_size = new_commit_pos - arena->commit_pos; bool arena_memory_committed = gui_plat_mem_commit(mem, commit_size); assert(arena_memory_committed); arena->commit_pos = new_commit_pos; } arena->pos = new_pos; unsigned char* out = (unsigned char*)arena + pos_aligned; return out; } void* gui_arena_realloc(GuiMemArena* arena, void* ptr, size_t old_size, size_t new_size) { if (!ptr || old_size == 0) return gui_arena_alloc(arena, new_size); void* ret = gui_arena_alloc(arena, new_size); memcpy(ret, ptr, old_size); return ret; } void gui_arena_pop(GuiMemArena* arena, size_t size) { size = MIN(size, arena->pos - GUI_ARENA_BASE_POS); arena->pos -= size; } void gui_arena_pop_to(GuiMemArena* arena, size_t pos) { size_t size = pos < arena->pos ? arena->pos - pos : 0; gui_arena_pop(arena, size); } void gui_arena_clear(GuiMemArena* arena) { gui_arena_pop_to(arena, GUI_ARENA_BASE_POS); } #ifdef _WIN32 #include static unsigned long gui_plat_get_pagesize(void) { SYSTEM_INFO sysinfo = { 0 }; GetSystemInfo(&sysinfo); return sysinfo.dwPageSize; } static void* gui_plat_mem_reserve(size_t size) { return VirtualAlloc(NULL, size, MEM_RESERVE, PAGE_READWRITE); } static bool gui_plat_mem_commit(void* ptr, size_t size) { void* ret = VirtualAlloc(ptr, size, MEM_COMMIT, PAGE_READWRITE); return ret != NULL; } static bool gui_plat_mem_release(void* ptr, size_t size) { return VirtualFree(ptr, size, MEM_RELEASE); } #else #include #include static unsigned long gui_plat_get_pagesize(void) { return sysconf(_SC_PAGESIZE); } static void* gui_plat_mem_reserve(size_t size) { void* out = mmap(NULL, size, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); if (out == MAP_FAILED) return NULL; return out; } static bool gui_plat_mem_commit(void* ptr, size_t size) { return !mprotect(ptr, size, PROT_READ | PROT_WRITE); } static bool gui_plat_mem_release(void* ptr, size_t size) { return !munmap(ptr, size); } #endif // _WIN32 ================================================ FILE: src/scrap_gui.h ================================================ // Scrap is a project that allows anyone to build software using simple, block based interface. // // Copyright (C) 2024-2026 Grisshink // // This software is provided 'as-is', without any express or implied // warranty. In no event will the authors be held liable for any damages // arising from the use of this software. // // Permission is granted to anyone to use this software for any purpose, // including commercial applications, and to alter it and redistribute it // freely, subject to the following restrictions: // // 1. The origin of this software must not be misrepresented; you must not // claim that you wrote the original software. If you use this software // in a product, an acknowledgment in the product documentation would be // appreciated but is not required. // 2. Altered source versions must be plainly marked as such, and must not be // misrepresented as being the original software. // 3. This notice may not be removed or altered from any source distribution. #ifndef SCRAP_GUI_H #define SCRAP_GUI_H #include #define ELEMENT_STACK_SIZE 32768 #define COMMAND_STACK_SIZE 4096 #define STATE_STACK_SIZE 32768 typedef struct Gui Gui; typedef struct GuiElement GuiElement; typedef struct { unsigned short w, h; } GuiMeasurement; typedef struct { int x, y; unsigned short w, h; } GuiBounds; typedef struct { float x, y, w, h; } GuiDrawBounds; typedef struct { unsigned char r, g, b, a; } GuiColor; typedef struct { void* font; const char* text; unsigned int text_size; } GuiTextData; typedef enum { DRAWTYPE_UNKNOWN = 0, DRAWTYPE_RECT = 1, DRAWTYPE_BORDER = 2, DRAWTYPE_IMAGE = 3, DRAWTYPE_TEXT = 4, DRAWTYPE_SCISSOR_SET, DRAWTYPE_SCISSOR_RESET, DRAWTYPE_SHADER_BEGIN, DRAWTYPE_SHADER_END, } GuiDrawType; typedef union { GuiTextData text; void* image; void* custom_data; void* shader; unsigned int border_width; } GuiDrawData; typedef struct { unsigned char type; // DrawType unsigned char subtype; float pos_x, pos_y; float width, height; GuiColor color; GuiDrawData data; } GuiDrawCommand; typedef enum { ALIGN_LEFT = 0, ALIGN_CENTER, ALIGN_RIGHT, ALIGN_TOP = 0, ALIGN_BOTTOM = 2, } GuiAlignmentType; typedef enum { SIZING_FIT = 0, SIZING_FIXED, SIZING_GROW, SIZING_PERCENT, } GuiElementSizing; typedef enum { DIRECTION_VERTICAL = 0, DIRECTION_HORIZONTAL, } GuiElementDirection; typedef void (*GuiHandler)(GuiElement* el); struct GuiElement { int x, y; unsigned short w, h; unsigned short min_w, min_h; float abs_x, abs_y; int cursor_x, cursor_y; unsigned short pad_w, pad_h; unsigned short gap; float scaling; float size_percentage; unsigned char draw_type; // DrawType // Custom draw type, interpretation of this is defined by user // NOTE: Scrap gui always interprets 0 as no subtype, so don't do custom // rendering with this subtype value to not break things in unexpected way unsigned char draw_subtype; GuiDrawData data; GuiColor color; // Sizing layout: // YYYYXXXX // Where: // X - ElementSizing for element width // Y - ElementSizing for element height unsigned char sizing; // Anchor layout: // YYYYXXXX // Where: // X - GuiAlignmentType for horizontal alignment // Y - GuiAlignmentType for vertical alignment unsigned char anchor; // Flags layout: // XXGSFYYD // Where: // D - GuiElementDirection // X - GuiAlignmentType for horizontal alignment // Y - GuiAlignmentType for vertical alignment // F - Is floating element // S - Is scissoring on // G - Does this element need to be resized? (internal flag) // 0 - Unused unsigned char flags; GuiHandler handle_hover; GuiHandler handle_pre_render; int* scroll_value; short scroll_scaling; void* custom_data; void* custom_state; void* shader; GuiElement* child_elements_begin; GuiElement* child_elements_end; GuiElement* parent; GuiElement* parent_anchor; GuiElement* prev; GuiElement* next; }; typedef GuiMeasurement (*GuiMeasureTextSliceFunc)(void* font, const char* text, unsigned int text_size, unsigned short font_size); typedef GuiMeasurement (*GuiMeasureImageFunc)(void* image, unsigned short size); typedef struct { size_t reserve_size, commit_size, pos, commit_pos; } GuiMemArena; typedef struct { GuiDrawCommand* items; size_t size, capacity; } GuiDrawCommandList; typedef struct { GuiBounds* items; size_t size, capacity; } GuiScissorStack; struct Gui { GuiMemArena* arena; size_t elements_count; GuiDrawCommandList command_list; GuiDrawCommandList aux_command_list; size_t command_list_iter, command_list_last_batch; GuiScissorStack scissor_stack; GuiMeasureTextSliceFunc measure_text; GuiMeasureImageFunc measure_image; GuiElement *root_element, *current_element; unsigned short win_w, win_h; short mouse_x, mouse_y; int mouse_scroll; }; #define GUI_GET_COMMANDS(gui, command) for ( \ gui->command_list_iter = 0; \ command = gui->command_list.items[gui->command_list_iter], gui->command_list_iter < gui->command_list.size; \ gui->command_list_iter++ \ ) #define GUI_BLACK (GuiColor) { 0x00, 0x00, 0x00, 0xff } #define GUI_WHITE (GuiColor) { 0xff, 0xff, 0xff, 0xff } #define GUI_SUBTYPE_DEFAULT 0 Gui gui_new(size_t arena_size); void gui_free(Gui* gui); void gui_begin(Gui* gui); void gui_end(Gui* gui); void gui_update_window_size(Gui* gui, unsigned short win_w, unsigned short win_h); void gui_update_mouse_pos(Gui* gui, short mouse_x, short mouse_y); void gui_update_mouse_scroll(Gui* gui, int mouse_scroll); void gui_set_measure_text_func(Gui* gui, GuiMeasureTextSliceFunc measure_text); void gui_set_measure_image_func(Gui* gui, GuiMeasureImageFunc measure_image); GuiElement* gui_element_begin(Gui* gui); void gui_element_end(Gui* gui); void gui_set_fixed(Gui* gui, unsigned short w, unsigned short h); void gui_set_fit(Gui* gui, GuiElementDirection direction); void gui_set_grow(Gui* gui, GuiElementDirection diection); void gui_set_percent_size(Gui* gui, float percentage, GuiElementDirection direction); void gui_set_draw_subtype(Gui* gui, unsigned char subtype); void gui_set_rect(Gui* gui, GuiColor color); void gui_set_direction(Gui* gui, GuiElementDirection direction); void gui_set_border(Gui* gui, GuiColor color, unsigned int border_width); void gui_set_text_slice(Gui* gui, void* font, const char* text, unsigned int text_size, unsigned short font_size, GuiColor color); void gui_set_text(Gui* gui, void* font, const char* text, unsigned short size, GuiColor color); void gui_set_image(Gui* gui, void* image, unsigned short size, GuiColor color); void gui_set_min_size(Gui* gui, unsigned short min_w, unsigned short min_h); void gui_set_align(Gui* gui, GuiAlignmentType align_x, GuiAlignmentType align_y); void gui_set_padding(Gui* gui, unsigned short pad_w, unsigned short pad_h); void gui_set_gap(Gui* gui, unsigned short gap); void gui_set_custom_data(Gui* gui, void* custom_data); void gui_set_floating(Gui* gui); void gui_set_scissor(Gui* gui); void gui_set_position(Gui* gui, int x, int y); void gui_set_anchor(Gui* gui, GuiAlignmentType anchor_x, GuiAlignmentType anchor_y); void gui_set_parent_anchor(Gui* gui, GuiElement* parent_anchor); void gui_set_scroll(Gui* gui, int* scroll_value); void gui_set_scroll_scaling(Gui* gui, int scroll_scaling); void gui_set_shader(Gui* gui, void* shader); void gui_scale_element(Gui* gui, float scaling); void* gui_set_state(Gui* gui, void* state, unsigned short state_len); void* gui_get_state(GuiElement* el); GuiElement* gui_get_element(Gui* gui); void gui_on_hover(Gui* gui, GuiHandler handler); void gui_on_render(Gui* gui, GuiHandler handler); void gui_text_slice(Gui* gui, void* font, const char* text, unsigned int text_size, unsigned short font_size, GuiColor color); void gui_text(Gui* gui, void* font, const char* text, unsigned short size, GuiColor color); void gui_image(Gui* gui, void* image, unsigned short size, GuiColor color); void gui_grow(Gui* gui, GuiElementDirection direction); void gui_spacer(Gui* gui, unsigned short w, unsigned short h); #define gui_arena_append(_arena, _list, _val) do { \ if ((_list).size >= (_list).capacity) { \ size_t _old_cap = (_list).capacity * sizeof(*(_list).items); \ if ((_list).capacity == 0) (_list).capacity = 32; \ else (_list).capacity *= 2; \ (_list).items = gui_arena_realloc(_arena, (_list).items, _old_cap, (_list).capacity * sizeof(*(_list).items)); \ } \ (_list).items[(_list).size++] = (_val); \ } while (0) GuiMemArena* gui_arena_new(size_t reserve_size, size_t commit_size); void gui_arena_free(GuiMemArena* arena); void* gui_arena_alloc(GuiMemArena* arena, size_t size); void* gui_arena_realloc(GuiMemArena* arena, void* ptr, size_t old_size, size_t new_size); const char* gui_arena_sprintf(GuiMemArena* arena, size_t max_size, const char* fmt, ...); void gui_arena_pop(GuiMemArena* arena, size_t size); void gui_arena_pop_to(GuiMemArena* arena, size_t pos); void gui_arena_clear(GuiMemArena* arena); #endif // SCRAP_GUI_H ================================================ FILE: src/std.c ================================================ // Scrap is a project that allows anyone to build software using simple, block based interface. // // Copyright (C) 2024-2026 Grisshink // // This software is provided 'as-is', without any express or implied // warranty. In no event will the authors be held liable for any damages // arising from the use of this software. // // Permission is granted to anyone to use this software for any purpose, // including commercial applications, and to alter it and redistribute it // freely, subject to the following restrictions: // // 1. The origin of this software must not be misrepresented; you must not // claim that you wrote the original software. If you use this software // in a product, an acknowledgment in the product documentation would be // appreciated but is not required. // 2. Altered source versions must be plainly marked as such, and must not be // misrepresented as being the original software. // 3. This notice may not be removed or altered from any source distribution. #include #include #include #include #include "std.h" #include "util.h" #ifdef _WIN32 #include #else #include #endif // Explicitly including rprand.h here as this translation unit will soon need // to compile as standalone library, which means we should not depend on // raylib in any way #define RPRAND_IMPLEMENTATION #define RPRANDAPI static __attribute__ ((unused)) #include "../external/rprand.h" // NOTE: Shamelessly stolen from raylib codebase ;) // Get next codepoint in a UTF-8 encoded text, scanning until '\0' is found // When an invalid UTF-8 byte is encountered we exit as soon as possible and a '?'(0x3f) codepoint is returned // Total number of bytes processed are returned as a parameter // NOTE: The standard says U+FFFD should be returned in case of errors // but that character is not supported by the default font in raylib static int get_codepoint(const char *text, int *codepoint_size) { /* UTF-8 specs from https://www.ietf.org/rfc/rfc3629.txt Char. number range | UTF-8 octet sequence (hexadecimal) | (binary) --------------------+--------------------------------------------- 0000 0000-0000 007F | 0xxxxxxx 0000 0080-0000 07FF | 110xxxxx 10xxxxxx 0000 0800-0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx 0001 0000-0010 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx */ // NOTE: on decode errors we return as soon as possible int codepoint = 0x3f; // Codepoint (defaults to '?') int octet = (unsigned char)(text[0]); // The first UTF8 octet *codepoint_size = 1; if (octet <= 0x7f) { // Only one octet (ASCII range x00-7F) codepoint = text[0]; } else if ((octet & 0xe0) == 0xc0) { // Two octets // [0]xC2-DF [1]UTF8-tail(x80-BF) unsigned char octet1 = text[1]; if ((octet1 == '\0') || ((octet1 >> 6) != 2)) { // Unexpected sequence *codepoint_size = 2; return codepoint; } if ((octet >= 0xc2) && (octet <= 0xdf)) { codepoint = ((octet & 0x1f) << 6) | (octet1 & 0x3f); *codepoint_size = 2; } } else if ((octet & 0xf0) == 0xe0) { // Three octets unsigned char octet1 = text[1]; unsigned char octet2 = '\0'; if ((octet1 == '\0') || ((octet1 >> 6) != 2)) { // Unexpected sequence *codepoint_size = 2; return codepoint; } octet2 = text[2]; if ((octet2 == '\0') || ((octet2 >> 6) != 2)) { // Unexpected sequence *codepoint_size = 3; return codepoint; } // [0]xE0 [1]xA0-BF [2]UTF8-tail(x80-BF) // [0]xE1-EC [1]UTF8-tail [2]UTF8-tail(x80-BF) // [0]xED [1]x80-9F [2]UTF8-tail(x80-BF) // [0]xEE-EF [1]UTF8-tail [2]UTF8-tail(x80-BF) if (((octet == 0xe0) && !((octet1 >= 0xa0) && (octet1 <= 0xbf))) || ((octet == 0xed) && !((octet1 >= 0x80) && (octet1 <= 0x9f)))) { *codepoint_size = 2; return codepoint; } if ((octet >= 0xe0) && (octet <= 0xef)) { codepoint = ((octet & 0xf) << 12) | ((octet1 & 0x3f) << 6) | (octet2 & 0x3f); *codepoint_size = 3; } } else if ((octet & 0xf8) == 0xf0) { // Four octets if (octet > 0xf4) return codepoint; unsigned char octet1 = text[1]; unsigned char octet2 = '\0'; unsigned char octet3 = '\0'; if ((octet1 == '\0') || ((octet1 >> 6) != 2)) { // Unexpected sequence *codepoint_size = 2; return codepoint; } octet2 = text[2]; if ((octet2 == '\0') || ((octet2 >> 6) != 2)) { // Unexpected sequence *codepoint_size = 3; return codepoint; } octet3 = text[3]; if ((octet3 == '\0') || ((octet3 >> 6) != 2)) { // Unexpected sequence *codepoint_size = 4; return codepoint; } // [0]xF0 [1]x90-BF [2]UTF8-tail [3]UTF8-tail // [0]xF1-F3 [1]UTF8-tail [2]UTF8-tail [3]UTF8-tail // [0]xF4 [1]x80-8F [2]UTF8-tail [3]UTF8-tail if (((octet == 0xf0) && !((octet1 >= 0x90) && (octet1 <= 0xbf))) || ((octet == 0xf4) && !((octet1 >= 0x80) && (octet1 <= 0x8f)))) { // Unexpected sequence *codepoint_size = 2; return codepoint; } if (octet >= 0xf0) { codepoint = ((octet & 0x7) << 18) | ((octet1 & 0x3f) << 12) | ((octet2 & 0x3f) << 6) | (octet3 & 0x3f); *codepoint_size = 4; } } if (codepoint > 0x10ffff) codepoint = 0x3f; // Codepoints after U+10ffff are invalid return codepoint; } // NOTE: Shamelessly stolen from raylib codebase ;) // Encode codepoint into utf8 text (char array length returned as parameter) // NOTE: It uses a static array to store UTF-8 bytes const char *codepoint_to_utf8(int codepoint, int *utf8_size) { static char utf8[6] = { 0 }; int size = 0; // Byte size of codepoint if (codepoint <= 0x7f) { utf8[0] = (char)codepoint; size = 1; } else if (codepoint <= 0x7ff) { utf8[0] = (char)(((codepoint >> 6) & 0x1f) | 0xc0); utf8[1] = (char)((codepoint & 0x3f) | 0x80); size = 2; } else if (codepoint <= 0xffff) { utf8[0] = (char)(((codepoint >> 12) & 0x0f) | 0xe0); utf8[1] = (char)(((codepoint >> 6) & 0x3f) | 0x80); utf8[2] = (char)((codepoint & 0x3f) | 0x80); size = 3; } else if (codepoint <= 0x10ffff) { utf8[0] = (char)(((codepoint >> 18) & 0x07) | 0xf0); utf8[1] = (char)(((codepoint >> 12) & 0x3f) | 0x80); utf8[2] = (char)(((codepoint >> 6) & 0x3f) | 0x80); utf8[3] = (char)((codepoint & 0x3f) | 0x80); size = 4; } *utf8_size = size; return utf8; } static int leading_ones(unsigned char byte) { int out = 0; while (byte & 0x80) { out++; byte <<= 1; } return out; } int std_int_pow(int base, int exp) { if (exp == 0) return 1; int result = 1; while (exp) { if (exp & 1) result *= base; exp >>= 1; base *= base; } return result; } StdColor std_parse_color(const char* value) { if (*value == '#') value++; unsigned char r = 0x00, g = 0x00, b = 0x00, a = 0xff; sscanf(value, "%02hhx%02hhx%02hhx%02hhx", &r, &g, &b, &a); return (StdColor) { r, g, b, a }; } List* std_list_new(Gc* gc) { List* list = gc_malloc(gc, sizeof(List), DATA_TYPE_LIST); list->size = 0; list->capacity = 0; list->values = NULL; return list; } static AnyValue std_get_any(DataType data_type, va_list va) { AnyValueData data; switch (data_type) { case DATA_TYPE_BOOL: case DATA_TYPE_INTEGER: data.integer_val = va_arg(va, int); break; case DATA_TYPE_FLOAT: data.float_val = va_arg(va, double); break; case DATA_TYPE_STRING: data.str_val = va_arg(va, StringHeader*); break; case DATA_TYPE_LITERAL: data.literal_val = va_arg(va, char*); break; case DATA_TYPE_LIST: data.list_val = va_arg(va, List*); break; case DATA_TYPE_ANY: return *va_arg(va, AnyValue*); case DATA_TYPE_COLOR: data.color_val = va_arg(va, StdColor); break; default: data = (AnyValueData) {0}; break; } return (AnyValue) { .type = data_type, .data = data, }; } void std_list_add(Gc* gc, List* list, DataType data_type, ...) { AnyValue any; va_list va; va_start(va, data_type); any = std_get_any(data_type, va); va_end(va); std_list_add_any(gc, list, any); } void std_list_add_any(Gc* gc, List* list, AnyValue any) { if (!list->values) { list->values = gc_malloc(gc, sizeof(AnyValue), 0); list->capacity = 1; } if (list->size >= list->capacity) { AnyValue* new_list = gc_malloc(gc, sizeof(AnyValue) * list->size * 2, 0); memcpy(new_list, list->values, sizeof(AnyValue) * list->size); list->values = new_list; list->capacity = list->size * 2; } list->values[list->size++] = any; } void std_list_set(List* list, int index, DataType data_type, ...) { if (index >= list->size || index < 0) return; AnyValue any; va_list va; va_start(va, data_type); any = std_get_any(data_type, va); va_end(va); list->values[index] = any; } AnyValue* std_list_get(Gc* gc, List* list, int index) { AnyValue* out = gc_malloc(gc, sizeof(AnyValue), DATA_TYPE_ANY); *out = (AnyValue) { .type = DATA_TYPE_NOTHING }; if (index >= list->size || index < 0) return out; *out = list->values[index]; return out; } int std_list_length(List* list) { return list->size; } AnyValue* std_any_from_value(Gc* gc, DataType data_type, ...) { AnyValue any; va_list va; va_start(va, data_type); if (data_type == DATA_TYPE_ANY) { return va_arg(va, AnyValue*); } else { any = std_get_any(data_type, va); } va_end(va); AnyValue* value = gc_malloc(gc, sizeof(AnyValue), DATA_TYPE_ANY); *value = any; return value; } StringHeader* std_string_from_literal(Gc* gc, const char* literal, unsigned int size) { StringHeader* out_str = gc_malloc(gc, sizeof(StringHeader) + size + 1, DATA_TYPE_STRING); // Don't forget null terminator. It is not included in size memcpy(out_str->str, literal, size); out_str->size = size; out_str->capacity = size; out_str->str[size] = 0; return out_str; } char* std_string_get_data(StringHeader* str) { return str->str; } StringHeader* std_string_letter_in(Gc* gc, int target, StringHeader* input_str) { int pos = 0; if (target <= 0) return std_string_from_literal(gc, "", 0); for (char* str = input_str->str; *str; str++) { // Increment pos only on the beginning of multibyte char if ((*str & 0x80) == 0 || (*str & 0x40) != 0) pos++; if (pos == target) { int codepoint_size; get_codepoint(str, &codepoint_size); return std_string_from_literal(gc, str, codepoint_size); } } return std_string_from_literal(gc, "", 0); } StringHeader* std_string_substring(Gc* gc, int begin, int end, StringHeader* input_str) { if (begin <= 0) begin = 1; if (end <= 0) return std_string_from_literal(gc, "", 0); if (begin > end) return std_string_from_literal(gc, "", 0); char* substr_start = NULL; int substr_len = 0; int pos = 0; for (char* str = input_str->str; *str; str++) { // Increment pos only on the beginning of multibyte char if ((*str & 0x80) == 0 || (*str & 0x40) != 0) pos++; if (substr_start) substr_len++; if (pos == begin && !substr_start) { substr_start = str; substr_len = 1; } if (pos == end) { if (!substr_start) return std_string_from_literal(gc, "", 0); int codepoint_size; get_codepoint(str, &codepoint_size); substr_len += codepoint_size - 1; return std_string_from_literal(gc, substr_start, substr_len); } } if (substr_start) return std_string_from_literal(gc, substr_start, substr_len); return std_string_from_literal(gc, "", 0); } StringHeader* std_string_join(Gc* gc, StringHeader* left, StringHeader* right) { StringHeader* out_str = gc_malloc(gc, sizeof(StringHeader) + left->size + right->size + 1, DATA_TYPE_STRING); memcpy(out_str->str, left->str, left->size); memcpy(out_str->str + left->size, right->str, right->size); out_str->size = left->size + right->size; out_str->capacity = out_str->size; out_str->str[out_str->size] = 0; return out_str; } int std_string_length(StringHeader* str) { int len = 0; char* cur = str->str; while (*cur) { int mb_size = leading_ones(*cur); if (mb_size == 0) mb_size = 1; cur += mb_size; len++; } return len; } bool std_string_is_eq(StringHeader* left, StringHeader* right) { if (left->size != right->size) return false; for (unsigned int i = 0; i < left->size; i++) { if (left->str[i] != right->str[i]) return false; } return true; } StringHeader* std_string_chr(Gc* gc, int value) { int text_size; const char* text = codepoint_to_utf8(value, &text_size); return std_string_from_literal(gc, text, text_size); } int std_string_ord(StringHeader* str) { int codepoint_size; int codepoint = get_codepoint(str->str, &codepoint_size); (void) codepoint_size; return codepoint; } StringHeader* std_string_from_integer(Gc* gc, int value) { char str[20]; unsigned int len = snprintf(str, 20, "%d", value); return std_string_from_literal(gc, str, len); } StringHeader* std_string_from_bool(Gc* gc, bool value) { return value ? std_string_from_literal(gc, "true", 4) : std_string_from_literal(gc, "false", 5); } StringHeader* std_string_from_float(Gc* gc, double value) { char str[20]; unsigned int len = snprintf(str, 20, "%f", value); return std_string_from_literal(gc, str, len); } StringHeader* std_string_from_color(Gc* gc, StdColor value) { char str[20]; unsigned int len = snprintf(str, 20, "#%02x%02x%02x%02x", value.r, value.g, value.b, value.a); return std_string_from_literal(gc, str, len); } char* std_any_string_from_any(Gc* gc, AnyValue* value) { if (value->type == DATA_TYPE_LITERAL) return value->data.literal_val; return std_string_from_any(gc, value)->str; } StringHeader* std_string_from_any(Gc* gc, AnyValue* value) { if (!value) return std_string_from_literal(gc, "", 0); char str[32]; int size; switch (value->type) { case DATA_TYPE_INTEGER: return std_string_from_integer(gc, value->data.integer_val); case DATA_TYPE_FLOAT: return std_string_from_float(gc, value->data.float_val); case DATA_TYPE_LITERAL: return std_string_from_literal(gc, value->data.literal_val, strlen(value->data.literal_val)); case DATA_TYPE_STRING: return value->data.str_val; case DATA_TYPE_BOOL: return std_string_from_bool(gc, value->data.integer_val); case DATA_TYPE_LIST: size = snprintf(str, 32, "*LIST (%lu)*", value->data.list_val->size); return std_string_from_literal(gc, str, size); case DATA_TYPE_COLOR: 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); return std_string_from_literal(gc, str, size); default: return std_string_from_literal(gc, "", 0); } } int std_integer_from_any(AnyValue* value) { if (!value) return 0; switch (value->type) { case DATA_TYPE_BOOL: case DATA_TYPE_INTEGER: return value->data.integer_val; case DATA_TYPE_FLOAT: return (int)value->data.float_val; case DATA_TYPE_STRING: return atoi(value->data.str_val->str); case DATA_TYPE_LITERAL: return atoi(value->data.literal_val); case DATA_TYPE_COLOR: return *(int*)&value->data.color_val; default: return 0; } } double std_float_from_any(AnyValue* value) { if (!value) return 0; switch (value->type) { case DATA_TYPE_BOOL: case DATA_TYPE_INTEGER: return (double)value->data.integer_val; case DATA_TYPE_FLOAT: return value->data.float_val; case DATA_TYPE_STRING: return atof(value->data.str_val->str); case DATA_TYPE_LITERAL: return atof(value->data.literal_val); case DATA_TYPE_COLOR: return *(int*)&value->data.color_val; default: return 0; } } int std_bool_from_any(AnyValue* value) { if (!value) return 0; switch (value->type) { case DATA_TYPE_BOOL: case DATA_TYPE_INTEGER: return value->data.integer_val != 0; case DATA_TYPE_FLOAT: return value->data.float_val != 0.0; case DATA_TYPE_STRING: return value->data.str_val->size > 0; case DATA_TYPE_LITERAL: return *value->data.literal_val != 0; case DATA_TYPE_COLOR: return *(int*)&value->data.color_val != 0; default: return 0; } } #define INT_TO_COLOR(v) ((StdColor) { \ ((v) >> 0 ) & 255, \ ((v) >> 8 ) & 255, \ ((v) >> 16) & 255, \ ((v) >> 24) & 255, \ }) StdColor std_color_from_any(AnyValue* value) { if (!value) return (StdColor) { 0x00, 0x00, 0x00, 0xff }; switch (value->type) { case DATA_TYPE_BOOL: return value->data.integer_val ? (StdColor) { 0xff, 0xff, 0xff, 0xff } : (StdColor) { 0x00, 0x00, 0x00, 0xff }; case DATA_TYPE_INTEGER: return INT_TO_COLOR(value->data.integer_val); case DATA_TYPE_FLOAT: int int_val = value->data.float_val; return INT_TO_COLOR(int_val); case DATA_TYPE_STRING: return std_parse_color(value->data.str_val->str); case DATA_TYPE_LITERAL: return std_parse_color(value->data.literal_val); case DATA_TYPE_COLOR: return value->data.color_val; default: return (StdColor) { 0x00, 0x00, 0x00, 0xff }; } } List* std_list_from_any(Gc* gc, AnyValue* value) { if (!value) return 0; switch (value->type) { case DATA_TYPE_LIST: return value->data.list_val; default: return std_list_new(gc); } } bool std_any_is_eq(AnyValue* left, AnyValue* right) { if (left->type != right->type) return false; switch (left->type) { case DATA_TYPE_NOTHING: return true; case DATA_TYPE_LITERAL: return !strcmp(left->data.literal_val, right->data.literal_val); case DATA_TYPE_STRING: return std_string_is_eq(left->data.str_val, right->data.str_val); case DATA_TYPE_INTEGER: case DATA_TYPE_BOOL: return left->data.integer_val == right->data.integer_val; case DATA_TYPE_FLOAT: return left->data.float_val == right->data.float_val; case DATA_TYPE_LIST: return left->data.list_val == right->data.list_val; case DATA_TYPE_COLOR: return memcmp(&left->data.color_val, &right->data.color_val, sizeof(left->data.color_val)); default: return false; } } int std_sleep(int usecs) { if (usecs < 0) return 0; #ifdef _WIN32 Sleep(usecs / 1000); #else struct timespec sleep_time = {0}; sleep_time.tv_sec = usecs / 1000000; sleep_time.tv_nsec = (usecs % 1000000) * 1000; if (nanosleep(&sleep_time, &sleep_time) == -1) return 0; #endif return usecs; } void std_set_random_seed(int seed) { rprand_set_seed(seed); } int std_get_random(int min, int max) { if (min > max) { return rprand_get_value(max, min); } else { return rprand_get_value(min, max); } } int std_term_print_any(AnyValue* any) { if (!any) return 0; switch (any->type) { case DATA_TYPE_STRING: return std_term_print_str(any->data.str_val->str); case DATA_TYPE_LITERAL: return std_term_print_str(any->data.literal_val); case DATA_TYPE_NOTHING: return 0; case DATA_TYPE_INTEGER: return std_term_print_integer(any->data.integer_val); case DATA_TYPE_BOOL: return std_term_print_bool(any->data.integer_val); case DATA_TYPE_FLOAT: return std_term_print_float(any->data.float_val); case DATA_TYPE_LIST: return std_term_print_list(any->data.list_val); case DATA_TYPE_COLOR: return std_term_print_color(any->data.color_val); default: return 0; } } #ifdef STANDALONE_STD static int cursor_x = 0; static int cursor_y = 0; static Color clear_color = {0}; static Color bg_color = {0}; void test_cancel(void) {} int std_term_print_str(const char* str) { int len = printf("%s", str); fflush(stdout); return len; } int std_term_print_integer(int value) { int len = printf("%d", value); fflush(stdout); return len; } int std_term_print_float(double value) { int len = printf("%f", value); fflush(stdout); return len; } int std_term_print_bool(bool value) { int len = printf("%s", value ? "true" : "false"); fflush(stdout); return len; } int std_term_print_color(StdColor value) { int len = printf("[Color: #%02x%02x%02x%02x]", value.r, value.g, value.b, value.a); fflush(stdout); return len; } void std_term_set_fg_color(Color color) { // ESC[38;2;⟨r⟩;⟨g⟩;⟨b⟩m Select RGB foreground color printf("\033[38;2;%d;%d;%dm", color.r, color.g, color.b); fflush(stdout); } void std_term_set_bg_color(Color color) { // ESC[48;2;⟨r⟩;⟨g⟩;⟨b⟩m Select RGB background color printf("\033[48;2;%d;%d;%dm", color.r, color.g, color.b); bg_color = color; fflush(stdout); } void std_term_set_clear_color(Color color) { clear_color = color; } int std_term_cursor_max_y(void) { #ifdef _WIN32 CONSOLE_SCREEN_BUFFER_INFO csbi; if (!GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &csbi)) return 0; return csbi.srWindow.Bottom - csbi.srWindow.Top + 1; #else struct winsize w; if (ioctl(0, TIOCGWINSZ, &w)) return 0; return w.ws_row; #endif } int std_term_cursor_max_x(void) { #ifdef _WIN32 CONSOLE_SCREEN_BUFFER_INFO csbi; if (!GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &csbi)) return 0; return csbi.srWindow.Right - csbi.srWindow.Left + 1; #else struct winsize w; if (ioctl(0, TIOCGWINSZ, &w)) return 0; return w.ws_col; #endif } int std_term_cursor_x(void) { return cursor_x; } int std_term_cursor_y(void) { return cursor_y; } void std_term_clear(void) { // ESC[48;2;⟨r⟩;⟨g⟩;⟨b⟩m Select RGB background color printf("\033[48;2;%d;%d;%dm", clear_color.r, clear_color.g, clear_color.b); printf("\033[2J"); // ESC[48;2;⟨r⟩;⟨g⟩;⟨b⟩m Select RGB background color printf("\033[48;2;%d;%d;%dm", bg_color.r, bg_color.g, bg_color.b); fflush(stdout); } void std_term_set_cursor(int x, int y) { cursor_x = x; cursor_y = y; printf("\033[%d;%dH", y + 1, x + 1); fflush(stdout); } StringHeader* std_term_get_char(Gc* gc) { char input[10]; input[0] = (char)getchar(); if (input[0] == '\n') return std_string_from_literal(gc, "", 0); int mb_size = leading_ones(input[0]); if (mb_size == 0) mb_size = 1; for (int i = 1; i < mb_size && i < 10; i++) input[i] = (char)getchar(); input[mb_size] = 0; return std_string_from_literal(gc, input, mb_size); } StringHeader* std_term_get_input(Gc* gc) { char* string_buf = vector_create(); char last_char = 0; char buf[256]; while (last_char != '\n') { if (!fgets(buf, 256, stdin)) { vector_free(string_buf); return std_string_from_literal(gc, "", 0); } int size = strlen(buf); last_char = buf[size - 1]; if (last_char == '\n') buf[--size] = 0; for (char* str = buf; *str; str++) vector_add(&string_buf, *str); } StringHeader* out_string = std_string_from_literal(gc, string_buf, vector_size(string_buf)); vector_free(string_buf); return out_string; } int std_term_print_list(List* list) { int len = printf("*LIST (%lu)*", list->size); fflush(stdout); return len; } #else int std_term_print_str(const char* str) { return term_print_str(str); } int std_term_print_integer(int value) { return term_print_integer(value); } int std_term_print_float(double value) { return term_print_float(value); } int std_term_print_bool(bool value) { return term_print_bool(value); } int std_term_print_color(StdColor value) { return term_print_color(CONVERT_COLOR(value, TermColor)); } void std_term_set_fg_color(TermColor color) { return term_set_fg_color(color); } void std_term_set_bg_color(TermColor color) { return term_set_bg_color(color); } void std_term_set_clear_color(TermColor color) { return term_set_clear_color(color); } void std_term_clear(void) { term_clear(); } StringHeader* std_term_get_char(Gc* gc) { char input[10]; input[0] = term_input_get_char(); int mb_size = leading_ones(input[0]); if (mb_size == 0) mb_size = 1; for (int i = 1; i < mb_size && i < 10; i++) input[i] = term_input_get_char(); input[mb_size] = 0; return std_string_from_literal(gc, input, mb_size); } void std_term_set_cursor(int x, int y) { mutex_lock(&term.lock); x = CLAMP(x, 0, term.char_w - 1); y = CLAMP(y, 0, term.char_h - 1); term.cursor_pos = x + y * term.char_w; mutex_unlock(&term.lock); } int std_term_cursor_x(void) { mutex_lock(&term.lock); int cur_x = 0; if (term.char_w != 0) cur_x = term.cursor_pos % term.char_w; mutex_unlock(&term.lock); return cur_x; } int std_term_cursor_y(void) { mutex_lock(&term.lock); int cur_y = 0; if (term.char_w != 0) cur_y = term.cursor_pos / term.char_w; mutex_unlock(&term.lock); return cur_y; } int std_term_cursor_max_x(void) { mutex_lock(&term.lock); int cur_max_x = term.char_w; mutex_unlock(&term.lock); return cur_max_x; } int std_term_cursor_max_y(void) { mutex_lock(&term.lock); int cur_max_y = term.char_h; mutex_unlock(&term.lock); return cur_max_y; } StringHeader* std_term_get_input(Gc* gc) { char input_char = 0; char* string_buf = vector_create(); while (input_char != '\n') { char input[256]; int i = 0; for (; i < 255 && input_char != '\n'; i++) input[i] = (input_char = term_input_get_char()); if (input[i - 1] == '\n') input[i - 1] = 0; input[i] = 0; for (char* str = input; *str; str++) vector_add(&string_buf, *str); } StringHeader* out_string = std_string_from_literal(gc, string_buf, vector_size(string_buf)); vector_free(string_buf); return out_string; } int std_term_print_list(List* list) { char converted[32]; snprintf(converted, 32, "*LIST (%lu)*", list->size); return term_print_str(converted); } #endif ================================================ FILE: src/std.h ================================================ // Scrap is a project that allows anyone to build software using simple, block based interface. // // Copyright (C) 2024-2026 Grisshink // // This software is provided 'as-is', without any express or implied // warranty. In no event will the authors be held liable for any damages // arising from the use of this software. // // Permission is granted to anyone to use this software for any purpose, // including commercial applications, and to alter it and redistribute it // freely, subject to the following restrictions: // // 1. The origin of this software must not be misrepresented; you must not // claim that you wrote the original software. If you use this software // in a product, an acknowledgment in the product documentation would be // appreciated but is not required. // 2. Altered source versions must be plainly marked as such, and must not be // misrepresented as being the original software. // 3. This notice may not be removed or altered from any source distribution. #ifndef SCRAP_STD_H #define SCRAP_STD_H #include "gc.h" #include typedef struct { unsigned int size; unsigned int capacity; char str[]; } StringHeader; typedef struct { unsigned char r, g, b, a; } StdColor; typedef struct AnyValue AnyValue; typedef struct List List; typedef union { char* literal_val; StringHeader* str_val; int integer_val; double float_val; List* list_val; AnyValue* any_val; StdColor color_val; } AnyValueData; struct AnyValue { DataType type; AnyValueData data; }; struct List { long size; long capacity; AnyValue* values; }; // Math int std_int_pow(int base, int exp); // List operations List* std_list_new(Gc* gc); void std_list_add_any(Gc* gc, List* list, AnyValue any); void std_list_add(Gc* gc, List* list, DataType data_type, ...); void std_list_set(List* list, int index, DataType data_type, ...); AnyValue* std_list_get(Gc* gc, List* list, int index); int std_list_length(List* list); // Any operations AnyValue* std_any_from_value(Gc* gc, DataType data_type, ...); int std_integer_from_any(AnyValue* value); double std_float_from_any(AnyValue* value); int std_bool_from_any(AnyValue* value); List* std_list_from_any(Gc* gc, AnyValue* value); StdColor std_color_from_any(AnyValue* value); StringHeader* std_string_from_any(Gc* gc, AnyValue* value); char* std_any_string_from_any(Gc* gc, AnyValue* value); bool std_any_is_eq(AnyValue* left, AnyValue* right); // String operations StringHeader* std_string_from_literal(Gc* gc, const char* literal, unsigned int size); StringHeader* std_string_from_integer(Gc* gc, int value); StringHeader* std_string_from_bool(Gc* gc, bool value); StringHeader* std_string_from_float(Gc* gc, double value); StringHeader* std_string_from_color(Gc* gc, StdColor value); char* std_string_get_data(StringHeader* str); int std_string_length(StringHeader* str); StringHeader* std_string_letter_in(Gc* gc, int target, StringHeader* input_str); StringHeader* std_string_substring(Gc* gc, int begin, int end, StringHeader* input_str); StringHeader* std_string_join(Gc* gc, StringHeader* left, StringHeader* right); bool std_string_is_eq(StringHeader* left, StringHeader* right); StringHeader* std_string_chr(Gc* gc, int value); int std_string_ord(StringHeader* str); // Terminal control StringHeader* std_term_get_char(Gc* gc); void std_term_set_cursor(int x, int y); int std_term_cursor_x(void); int std_term_cursor_y(void); int std_term_cursor_max_x(void); int std_term_cursor_max_y(void); StringHeader* std_term_get_input(Gc* gc); int std_term_print_list(List* list); int std_term_print_any(AnyValue* any); int std_term_print_str(const char* str); int std_term_print_integer(int value); int std_term_print_float(double value); int std_term_print_bool(bool value); int std_term_print_color(StdColor value); void std_term_clear(void); #ifdef STANDALONE_STD typedef struct { unsigned char r, g, b, a; } Color; void std_term_set_fg_color(Color color); void std_term_set_bg_color(Color color); void std_term_set_clear_color(Color color); #else // TODO: Remove this dependency by doing stdio #include "term.h" void std_term_set_fg_color(TermColor color); void std_term_set_bg_color(TermColor color); void std_term_set_clear_color(TermColor color); #endif // Misc int std_sleep(int usecs); int std_get_random(int min, int max); void std_set_random_seed(int seed); StdColor std_parse_color(const char* value); #endif // SCRAP_STD_H ================================================ FILE: src/term.c ================================================ // Scrap is a project that allows anyone to build software using simple, block based interface. // // Copyright (C) 2024-2026 Grisshink // // This software is provided 'as-is', without any express or implied // warranty. In no event will the authors be held liable for any damages // arising from the use of this software. // // Permission is granted to anyone to use this software for any purpose, // including commercial applications, and to alter it and redistribute it // freely, subject to the following restrictions: // // 1. The origin of this software must not be misrepresented; you must not // claim that you wrote the original software. If you use this software // in a product, an acknowledgment in the product documentation would be // appreciated but is not required. // 2. Altered source versions must be plainly marked as such, and must not be // misrepresented as being the original software. // 3. This notice may not be removed or altered from any source distribution. #include "term.h" #include "util.h" #include #include #include #include #define TERM_WHITE (TermColor) { 0xff, 0xff, 0xff, 0xff } #define TERM_BLACK (TermColor) { 0x00, 0x00, 0x00, 0xff } Terminal term = {0}; int leading_ones(unsigned char byte) { int out = 0; while (byte & 0x80) { out++; byte <<= 1; } return out; } void term_init(MeasureTextSliceFunc measure_text, void* font, unsigned short font_size) { sem_init(&term.input_sem, 0, 0); term.lock = mutex_new(); term.is_buffer_dirty = true; term.cursor_fg_color = TERM_WHITE; term.cursor_bg_color = TERM_BLACK; term.measure_text = measure_text; term.font = font; term.font_size = font_size; term_resize(0, 0); } void term_restart(void) { sem_destroy(&term.input_sem); sem_init(&term.input_sem, 0, 0); term.buf_start = 0; term.buf_end = 0; term.cursor_fg_color = TERM_WHITE; term.cursor_bg_color = TERM_BLACK; term.clear_color = TERM_BLACK; term_clear(); } void term_free(void) { mutex_free(&term.lock); sem_destroy(&term.input_sem); } void term_input_put_char(char ch) { mutex_lock(&term.lock); term.input_buf[term.buf_end] = ch; term.buf_end = (term.buf_end + 1) % TERM_INPUT_BUF_SIZE; mutex_unlock(&term.lock); sem_post(&term.input_sem); } char term_input_get_char(void) { sem_wait(&term.input_sem); mutex_lock(&term.lock); int out = term.input_buf[term.buf_start]; term.buf_start = (term.buf_start + 1) % TERM_INPUT_BUF_SIZE; mutex_unlock(&term.lock); return out; } void term_scroll_down(void) { mutex_lock(&term.lock); memmove(term.buffer, term.buffer + term.char_w, term.char_w * (term.char_h - 1) * sizeof(*term.buffer)); for (int i = term.char_w * (term.char_h - 1); i < term.char_w * term.char_h; i++) { strncpy(term.buffer[i].ch, " ", ARRLEN(term.buffer[i].ch)); term.buffer[i].fg_color = TERM_WHITE; term.buffer[i].bg_color = term.clear_color; } mutex_unlock(&term.lock); } void term_set_fg_color(TermColor color) { mutex_lock(&term.lock); term.cursor_fg_color = color; mutex_unlock(&term.lock); } void term_set_bg_color(TermColor color) { mutex_lock(&term.lock); term.cursor_bg_color = color; mutex_unlock(&term.lock); } void term_set_clear_color(TermColor color) { mutex_lock(&term.lock); term.clear_color = color; mutex_unlock(&term.lock); } int term_print_str(const char* str) { int len = 0; assert(term.buffer != NULL); mutex_lock(&term.lock); if (*str) term.is_buffer_dirty = true; while (*str) { if (term.cursor_pos >= term.char_w * term.char_h) { term.cursor_pos = term.char_w * term.char_h - term.char_w; term_scroll_down(); } if (*str == '\t') { term_print_str(" "); str++; continue; } if (*str == '\n') { term.cursor_pos += term.char_w; term.cursor_pos -= term.cursor_pos % term.char_w; str++; if (term.cursor_pos >= term.char_w * term.char_h) { term.cursor_pos -= term.char_w; term_scroll_down(); } continue; } if (*str == '\r') { term.cursor_pos -= term.cursor_pos % term.char_w; str++; continue; } int mb_size = leading_ones(*str); if (mb_size == 0) mb_size = 1; int i = 0; for (; i < mb_size; i++) term.buffer[term.cursor_pos].ch[i] = str[i]; term.buffer[term.cursor_pos].ch[i] = 0; term.buffer[term.cursor_pos].fg_color = term.cursor_fg_color; term.buffer[term.cursor_pos].bg_color = term.cursor_bg_color; str += mb_size; term.cursor_pos++; len++; } mutex_unlock(&term.lock); return len; } int term_print_integer(int value) { char converted[12]; snprintf(converted, 12, "%d", value); return term_print_str(converted); } int term_print_float(double value) { char converted[20]; snprintf(converted, 20, "%f", value); return term_print_str(converted); } int term_print_bool(bool value) { return term_print_str(value ? "true" : "false"); } int term_print_color(TermColor value) { char converted[30]; snprintf(converted, 30, "[Color: #%02x%02x%02x%02x]", value.r, value.g, value.b, value.a); return term_print_str(converted); } void term_clear(void) { mutex_lock(&term.lock); for (int i = 0; i < term.char_w * term.char_h; i++) { strncpy(term.buffer[i].ch, " ", ARRLEN(term.buffer[i].ch)); term.buffer[i].fg_color = TERM_WHITE; term.buffer[i].bg_color = term.clear_color; } term.cursor_pos = 0; mutex_unlock(&term.lock); } void term_resize(float screen_w, float screen_h) { mutex_lock(&term.lock); term.size = (TermVec) { screen_w, screen_h }; term.char_size = term.measure_text(term.font, "A", 1, term.font_size); TermVec new_buffer_size = { term.size.x / term.char_size.x, term.size.y / term.char_size.y }; int new_char_w = (int)new_buffer_size.x, new_char_h = (int)new_buffer_size.y; if (term.char_w != new_char_w || term.char_h != new_char_h) { int buf_size = new_char_w * new_char_h * sizeof(*term.buffer); TerminalChar* new_buffer = malloc(buf_size); if (term.buffer) { for (int y = 0; y < new_char_h; y++) { for (int x = 0; x < new_char_w; x++) { TerminalChar* ch = &new_buffer[x + y * new_char_w]; if (x >= term.char_w || y >= term.char_h) { strncpy(ch->ch, " ", ARRLEN(ch->ch)); ch->fg_color = TERM_WHITE; ch->bg_color = term.clear_color; continue; } *ch = term.buffer[x + y * term.char_w]; } } int term_x = term.cursor_pos % term.char_w, term_y = term.cursor_pos / term.char_w; if (term_x >= new_char_w) term_x = new_char_w - 1; if (term_y >= new_char_h) term_y = new_char_h - 1; term.cursor_pos = term_x + term_y * new_char_w; free(term.buffer); term.char_w = new_char_w; term.char_h = new_char_h; term.buffer = new_buffer; } else { term.char_w = new_char_w; term.char_h = new_char_h; term.buffer = new_buffer; term_clear(); } } mutex_unlock(&term.lock); } ================================================ FILE: src/term.h ================================================ // Scrap is a project that allows anyone to build software using simple, block based interface. // // Copyright (C) 2024-2026 Grisshink // // This software is provided 'as-is', without any express or implied // warranty. In no event will the authors be held liable for any damages // arising from the use of this software. // // Permission is granted to anyone to use this software for any purpose, // including commercial applications, and to alter it and redistribute it // freely, subject to the following restrictions: // // 1. The origin of this software must not be misrepresented; you must not // claim that you wrote the original software. If you use this software // in a product, an acknowledgment in the product documentation would be // appreciated but is not required. // 2. Altered source versions must be plainly marked as such, and must not be // misrepresented as being the original software. // 3. This notice may not be removed or altered from any source distribution. #ifndef TERM_H #define TERM_H #include "thread.h" #include #include #define TERM_INPUT_BUF_SIZE 256 typedef struct { unsigned char r, g, b, a; } TermColor; typedef struct { char ch[5]; TermColor fg_color; TermColor bg_color; } TerminalChar; typedef struct { float x, y; } TermVec; typedef TermVec (*MeasureTextSliceFunc)(void* font, const char* text, unsigned int text_size, unsigned short font_size); typedef struct { MeasureTextSliceFunc measure_text; void* font; unsigned short font_size; Mutex lock; TermVec size; int char_w, char_h; int cursor_pos; TermColor cursor_fg_color, cursor_bg_color; TermVec char_size; TerminalChar *buffer; bool is_buffer_dirty; TermColor clear_color; sem_t input_sem; char input_buf[TERM_INPUT_BUF_SIZE]; int buf_start; int buf_end; } Terminal; extern Terminal term; void term_init(MeasureTextSliceFunc measure_text, void* font, unsigned short font_size); void term_input_put_char(char ch); char term_input_get_char(void); void term_scroll_down(void); void term_set_fg_color(TermColor color); void term_set_bg_color(TermColor color); void term_set_clear_color(TermColor color); int term_print_str(const char* str); int term_print_integer(int value); int term_print_float(double value); int term_print_bool(bool value); int term_print_color(TermColor value); void term_clear(void); void term_resize(float screen_w, float screen_h); void term_free(void); void term_restart(void); #endif // TERM_H ================================================ FILE: src/thread.c ================================================ // Scrap is a project that allows anyone to build software using simple, block based interface. // // Copyright (C) 2024-2026 Grisshink // // This software is provided 'as-is', without any express or implied // warranty. In no event will the authors be held liable for any damages // arising from the use of this software. // // Permission is granted to anyone to use this software for any purpose, // including commercial applications, and to alter it and redistribute it // freely, subject to the following restrictions: // // 1. The origin of this software must not be misrepresented; you must not // claim that you wrote the original software. If you use this software // in a product, an acknowledgment in the product documentation would be // appreciated but is not required. // 2. Altered source versions must be plainly marked as such, and must not be // misrepresented as being the original software. // 3. This notice may not be removed or altered from any source distribution. #include "thread.h" Mutex mutex_new(void) { Mutex mutex; pthread_mutexattr_t attr; pthread_mutexattr_init(&attr); pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); pthread_mutex_init(&mutex, &attr); return mutex; } void mutex_free(Mutex* mutex) { pthread_mutex_destroy(mutex); } void mutex_lock(Mutex* mutex) { pthread_mutex_lock(mutex); } void mutex_unlock(Mutex* mutex) { pthread_mutex_unlock(mutex); } Thread thread_new(ThreadEntry entry_func, ThreadCleanup cleanup_func) { return (Thread) { .state = THREAD_STATE_NOT_RUNNING, .entry = entry_func, .cleanup = cleanup_func, }; } static void* thread_entry(void* t) { Thread* thread = t; thread->state = THREAD_STATE_RUNNING; void* return_val = thread->entry(thread->entry_data) ? (void*)THREAD_RETURN_SUCCESS : (void*)THREAD_RETURN_FAILURE; if (thread->cleanup) thread->cleanup(thread->entry_data); thread->state = THREAD_STATE_DONE; return return_val; } bool thread_start(Thread* thread, void* data) { if (thread->state != THREAD_STATE_NOT_RUNNING) return false; thread->entry_data = data; thread->state = THREAD_STATE_STARTING; if (pthread_create(&thread->handle, NULL, thread_entry, thread)) { thread->state = THREAD_STATE_NOT_RUNNING; return false; } return true; } bool thread_is_running(Thread* thread) { return thread->state != THREAD_STATE_NOT_RUNNING; } void thread_handle_stopping_state(Thread* thread) { if (thread->state != THREAD_STATE_STOPPING) return; if (thread->cleanup) thread->cleanup(thread->entry_data); thread->state = THREAD_STATE_DONE; pthread_exit((void*)THREAD_RETURN_STOPPED); } void thread_exit(Thread* thread, bool success) { thread_handle_stopping_state(thread); if (thread->state != THREAD_STATE_RUNNING) return; if (thread->cleanup) thread->cleanup(thread->entry_data); thread->state = THREAD_STATE_DONE; pthread_exit(success ? (void*)THREAD_RETURN_SUCCESS : (void*)THREAD_RETURN_FAILURE); } bool thread_stop(Thread* thread) { if (thread->state != THREAD_STATE_RUNNING) return false; thread->state = THREAD_STATE_STOPPING; return true; } ThreadReturnCode thread_join(Thread* thread) { if (thread->state == THREAD_STATE_NOT_RUNNING) return THREAD_RETURN_FAILURE; void* return_val; if (pthread_join(thread->handle, &return_val)) return THREAD_RETURN_FAILURE; thread->state = THREAD_STATE_NOT_RUNNING; ThreadReturnCode thread_return = (ThreadReturnCode)return_val; switch (thread_return) { case THREAD_RETURN_SUCCESS: case THREAD_RETURN_STOPPED: return thread_return; default: return THREAD_RETURN_FAILURE; } } ThreadReturnCode thread_try_join(Thread* thread) { if (thread->state != THREAD_STATE_DONE) return THREAD_RETURN_RUNNING; return thread_join(thread); } ================================================ FILE: src/thread.h ================================================ // Scrap is a project that allows anyone to build software using simple, block based interface. // // Copyright (C) 2024-2026 Grisshink // // This software is provided 'as-is', without any express or implied // warranty. In no event will the authors be held liable for any damages // arising from the use of this software. // // Permission is granted to anyone to use this software for any purpose, // including commercial applications, and to alter it and redistribute it // freely, subject to the following restrictions: // // 1. The origin of this software must not be misrepresented; you must not // claim that you wrote the original software. If you use this software // in a product, an acknowledgment in the product documentation would be // appreciated but is not required. // 2. Altered source versions must be plainly marked as such, and must not be // misrepresented as being the original software. // 3. This notice may not be removed or altered from any source distribution. #ifndef THREAD_H #define THREAD_H #include #include typedef enum { THREAD_STATE_NOT_RUNNING = 0, THREAD_STATE_STARTING, THREAD_STATE_RUNNING, THREAD_STATE_STOPPING, THREAD_STATE_DONE, } ThreadState; typedef enum { THREAD_RETURN_FAILURE = 0, THREAD_RETURN_SUCCESS = 1, THREAD_RETURN_STOPPED, THREAD_RETURN_RUNNING, // Returned from thread_try_join to signify that thread is still running } ThreadReturnCode; // Return value indicates if the thread was executed successfully typedef bool (*ThreadEntry)(void*); typedef void (*ThreadCleanup)(void*); typedef struct { ThreadState state; ThreadEntry entry; ThreadCleanup cleanup; void* entry_data; pthread_t handle; } Thread; typedef pthread_mutex_t Mutex; Mutex mutex_new(void); void mutex_lock(Mutex* mutex); void mutex_unlock(Mutex* mutex); void mutex_free(Mutex* mutex); Thread thread_new(ThreadEntry entry_func, ThreadCleanup cleanup_func); bool thread_start(Thread* thread, void* data); bool thread_is_running(Thread* thread); void thread_handle_stopping_state(Thread* thread); void thread_exit(Thread* thread, bool success); bool thread_stop(Thread* thread); ThreadReturnCode thread_join(Thread* thread); ThreadReturnCode thread_try_join(Thread* thread); #endif // THREAD_H ================================================ FILE: src/ui.c ================================================ // Scrap is a project that allows anyone to build software using simple, block based interface. // // Copyright (C) 2024-2026 Grisshink // // This software is provided 'as-is', without any express or implied // warranty. In no event will the authors be held liable for any damages // arising from the use of this software. // // Permission is granted to anyone to use this software for any purpose, // including commercial applications, and to alter it and redistribute it // freely, subject to the following restrictions: // // 1. The origin of this software must not be misrepresented; you must not // claim that you wrote the original software. If you use this software // in a product, an acknowledgment in the product documentation would be // appreciated but is not required. // 2. Altered source versions must be plainly marked as such, and must not be // misrepresented as being the original software. // 3. This notice may not be removed or altered from any source distribution. #include "scrap.h" #include "term.h" #include "../external/tinyfiledialogs.h" #include "vec.h" #include "util.h" #include #include #include #include #include #include typedef enum { FILE_MENU_NEW_PROJECT = 0, FILE_MENU_SAVE_PROJECT, FILE_MENU_LOAD_PROJECT, } FileMenuInds; char* file_menu_list[] = { "New project", "Save project", "Load project", }; // Divides the panel into two parts along the specified side with the specified split percentage void panel_split(PanelTree* panel, SplitSide side, PanelType new_panel_type, float split_percent) { if (panel->type == PANEL_SPLIT) return; PanelTree* old_panel = malloc(sizeof(PanelTree)); old_panel->type = panel->type; old_panel->left = NULL; old_panel->right = NULL; old_panel->parent = panel; PanelTree* new_panel = malloc(sizeof(PanelTree)); new_panel->type = new_panel_type; new_panel->left = NULL; new_panel->right = NULL; new_panel->parent = panel; panel->type = PANEL_SPLIT; switch (side) { case SPLIT_SIDE_TOP: panel->direction = DIRECTION_VERTICAL; panel->left = new_panel; panel->right = old_panel; panel->split_percent = split_percent; break; case SPLIT_SIDE_BOTTOM: panel->direction = DIRECTION_VERTICAL; panel->left = old_panel; panel->right = new_panel; panel->split_percent = 1.0 - split_percent; break; case SPLIT_SIDE_LEFT: panel->direction = DIRECTION_HORIZONTAL; panel->left = new_panel; panel->right = old_panel; panel->split_percent = split_percent; break; case SPLIT_SIDE_RIGHT: panel->direction = DIRECTION_HORIZONTAL; panel->left = old_panel; panel->right = new_panel; panel->split_percent = 1.0 - split_percent; break; case SPLIT_SIDE_NONE: assert(false && "Got SPLIT_SIDE_NONE"); break; default: assert(false && "Got unknown split side"); break; } } PanelTree* panel_new(PanelType type) { PanelTree* panel = malloc(sizeof(PanelTree)); panel->type = type; panel->left = NULL; panel->right = NULL; panel->parent = NULL; return panel; } // Removes a panel and its child panels recursively, freeing memory void panel_delete(PanelTree* panel) { assert(panel != NULL); if (panel->type == PANEL_SPLIT) { panel_delete(panel->left); panel_delete(panel->right); panel->left = NULL; panel->right = NULL; } panel->type = PANEL_NONE; free(panel); } // Removes a tab by index and frees its resources void tab_delete(size_t tab) { assert(tab < vector_size(editor.tabs)); panel_delete(editor.tabs[tab].root_panel); vector_free(editor.tabs[tab].name); vector_remove(editor.tabs, tab); if (editor.current_tab >= (int)vector_size(editor.tabs)) editor.current_tab = vector_size(editor.tabs) - 1; } void delete_all_tabs(void) { for (ssize_t i = vector_size(editor.tabs) - 1; i >= 0; i--) tab_delete(i); } // Creates a new tab with the given name and panel, adding it to the list of tabs size_t tab_new(char* name, PanelTree* root_panel) { if (!root_panel) { scrap_log(LOG_WARNING, "Got root_panel == NULL, not adding"); return -1; } Tab* tab = vector_add_dst(&editor.tabs); tab->name = vector_create(); for (char* str = name; *str; str++) vector_add(&tab->name, *str); vector_add(&tab->name, 0); tab->root_panel = root_panel; return vector_size(editor.tabs) - 1; } // Inserts a new tab with the given name and panel at the specified position in the list of tabs void tab_insert(char* name, PanelTree* root_panel, size_t position) { if (!root_panel) { scrap_log(LOG_WARNING, "Got root_panel == NULL, not adding"); return; } Tab* tab = vector_insert_dst(&editor.tabs, position); tab->name = vector_create(); for (char* str = name; *str; str++) vector_add(&tab->name, *str); vector_add(&tab->name, 0); tab->root_panel = root_panel; } // Initializes codespace, using a default panel layout void init_panels(void) { PanelTree* code_panel = panel_new(PANEL_CODE); panel_split(code_panel, SPLIT_SIDE_LEFT, PANEL_BLOCK_PALETTE, 0.3); panel_split(code_panel->left, SPLIT_SIDE_TOP, PANEL_BLOCK_CATEGORIES, 0.35); tab_new("Code", code_panel); tab_new("Output", panel_new(PANEL_TERM)); } int search_glyph(Font font, int codepoint) { // We assume that ASCII region is the first region, so this index should correspond to char '?' in the glyph table const int fallback = 31; for (int i = 0; i < CODEPOINT_REGION_COUNT; i++) { if (codepoint < codepoint_regions[i][0] || codepoint > codepoint_regions[i][1]) continue; int glyph = codepoint - codepoint_regions[i][0] + codepoint_start_ranges[i]; if (glyph >= font.glyphCount) return fallback; return glyph; } return fallback; } static GuiMeasurement measure_slice(Font font, const char *text, unsigned int text_size, float font_size) { GuiMeasurement ms = {0}; if ((font.texture.id == 0) || !text) return ms; int codepoint = 0; // Current character int index = 0; // Index position in sprite font for (unsigned int i = 0; i < text_size;) { if (!text[i]) break; int next = 0; codepoint = GetCodepointNext(&text[i], &next); index = search_glyph(font, codepoint); i += next; if (font.glyphs[index].advanceX != 0) { ms.w += font.glyphs[index].advanceX; } else { ms.w += font.recs[index].width + font.glyphs[index].offsetX; } } ms.w *= font_size / (float)font.baseSize; ms.h = font_size; return ms; } GuiMeasurement scrap_gui_measure_image(void* image, unsigned short size) { Texture2D* img = image; return (GuiMeasurement) { img->width * ((float)size / (float)img->height), size }; } GuiMeasurement scrap_gui_measure_text(void* font, const char* text, unsigned int text_size, unsigned short font_size) { return measure_slice(*(Font*)font, text, text_size, font_size); } TermVec term_measure_text(void* font, const char* text, unsigned int text_size, unsigned short font_size) { GuiMeasurement m = measure_slice(*(Font*)font, text, text_size, font_size); return (TermVec) { .x = m.w, .y = m.h }; } #ifdef DEBUG static void sanitize_block(Block* block) { for (vec_size_t i = 0; i < vector_size(block->arguments); i++) { if (block->arguments[i].type != ARGUMENT_BLOCK) continue; if (block->arguments[i].data.block.parent != block) { scrap_log(LOG_ERROR, "Block %p detached from parent %p! (Got %p)", &block->arguments[i].data.block, block, block->arguments[i].data.block.parent); assert(false); return; } sanitize_block(&block->arguments[i].data.block); } } static void sanitize_links(void) { for (vec_size_t i = 0; i < vector_size(editor.code); i++) { Block* blocks = editor.code[i].blocks; for (vec_size_t j = 0; j < vector_size(blocks); j++) { sanitize_block(&blocks[j]); } } for (size_t i = 0; i < vector_size(editor.mouse_blockchains); i++) { BlockChain* chain = &editor.mouse_blockchains[i]; for (size_t j = 0; j < vector_size(chain->blocks); j++) { sanitize_block(&chain->blocks[j]); } } } #endif static void switch_tab_to_panel(PanelType panel) { for (size_t i = 0; i < vector_size(editor.tabs); i++) { if (find_panel(editor.tabs[i].root_panel, panel)) { if (editor.current_tab != (int)i) ui.shader_time = 0.0; editor.current_tab = i; ui.render_surface_needs_redraw = true; return; } } } static void set_mark(void) { if (IsKeyDown(KEY_LEFT_SHIFT) || IsKeyDown(KEY_RIGHT_SHIFT)) { if (ui.hover.select_input_mark == -1) ui.hover.select_input_mark = ui.hover.select_input_cursor; } else { ui.hover.select_input_mark = -1; } } static void copy_text(char* text, int start, int end) { char* clipboard = vector_create(); for (int i = start; i < end; i++) vector_add(&clipboard, text[i]); vector_add(&clipboard, 0); SetClipboardText(clipboard); vector_free(clipboard); } static void delete_region(char** text) { if (ui.hover.select_input_mark == -1) return; int remove_pos = MIN(ui.hover.select_input_cursor, ui.hover.select_input_mark), remove_size = ABS(ui.hover.select_input_cursor - ui.hover.select_input_mark); ui.hover.select_input_mark = -1; ui.hover.select_input_cursor = remove_pos; vector_erase(*text, remove_pos, remove_size); ui.render_surface_needs_redraw = true; } static bool edit_text(char** text) { if (!text) return false; if (IsKeyPressed(KEY_HOME)) { set_mark(); ui.hover.select_input_cursor = 0; ui.render_surface_needs_redraw = true; return false; } if (IsKeyPressed(KEY_END)) { set_mark(); ui.hover.select_input_cursor = vector_size(*text) - 1; ui.render_surface_needs_redraw = true; return false; } if ((IsKeyDown(KEY_LEFT_CONTROL) || IsKeyDown(KEY_RIGHT_CONTROL)) && IsKeyPressed(KEY_A)) { ui.hover.select_input_cursor = 0; ui.hover.select_input_mark = strlen(*text); ui.render_surface_needs_redraw = true; return false; } if ((IsKeyDown(KEY_LEFT_CONTROL) || IsKeyDown(KEY_RIGHT_CONTROL)) && IsKeyPressed(KEY_U)) { vector_clear(*text); vector_add(text, 0); ui.hover.select_input_cursor = 0; ui.hover.select_input_mark = -1; ui.render_surface_needs_redraw = true; return true; } if ((IsKeyDown(KEY_LEFT_CONTROL) || IsKeyDown(KEY_RIGHT_CONTROL)) && IsKeyPressed(KEY_C)) { if (ui.hover.select_input_mark != -1) { copy_text(*text, MIN(ui.hover.select_input_cursor, ui.hover.select_input_mark), MAX(ui.hover.select_input_cursor, ui.hover.select_input_mark)); } return false; } if ((IsKeyDown(KEY_LEFT_CONTROL) || IsKeyDown(KEY_RIGHT_CONTROL)) && IsKeyPressed(KEY_V)) { const char* clipboard = GetClipboardText(); if (clipboard) { delete_region(text); for (int i = 0; clipboard[i]; i++) { if (clipboard[i] == '\n' || clipboard[i] == '\r') continue; vector_insert(text, ui.hover.select_input_cursor++, clipboard[i]); } ui.render_surface_needs_redraw = true; return true; } return false; } if ((IsKeyDown(KEY_LEFT_CONTROL) || IsKeyDown(KEY_RIGHT_CONTROL)) && IsKeyPressed(KEY_X)) { if (ui.hover.select_input_mark != -1) { int sel_start = MIN(ui.hover.select_input_cursor, ui.hover.select_input_mark), sel_end = MAX(ui.hover.select_input_cursor, ui.hover.select_input_mark); copy_text(*text, sel_start, sel_end); delete_region(text); return true; } return false; } if (IsKeyPressed(KEY_LEFT) || IsKeyPressedRepeat(KEY_LEFT)) { set_mark(); ui.hover.select_input_cursor--; if (ui.hover.select_input_cursor < 0) { ui.hover.select_input_cursor = 0; } else { while (((unsigned char)(*text)[ui.hover.select_input_cursor] >> 6) == 2) ui.hover.select_input_cursor--; } ui.render_surface_needs_redraw = true; return false; } if (IsKeyPressed(KEY_RIGHT) || IsKeyPressedRepeat(KEY_RIGHT)) { set_mark(); ui.hover.select_input_cursor++; if (ui.hover.select_input_cursor >= (int)vector_size(*text)) { ui.hover.select_input_cursor = vector_size(*text) - 1; } else { while (((unsigned char)(*text)[ui.hover.select_input_cursor] >> 6) == 2) ui.hover.select_input_cursor++; } ui.render_surface_needs_redraw = true; return false; } if (IsKeyPressed(KEY_DELETE) || IsKeyPressedRepeat(KEY_DELETE)) { if (vector_size(*text) <= 1 || (ui.hover.select_input_cursor == (int)vector_size(*text) - 1 && ui.hover.select_input_mark == -1)) return false; if (ui.hover.select_input_mark != -1) { delete_region(text); } else { int remove_pos = ui.hover.select_input_cursor; int remove_size; GetCodepointNext(*text + remove_pos, &remove_size); vector_erase(*text, remove_pos, remove_size); ui.render_surface_needs_redraw = true; } return true; } if (IsKeyPressed(KEY_BACKSPACE) || IsKeyPressedRepeat(KEY_BACKSPACE)) { if (vector_size(*text) <= 1 || (ui.hover.select_input_cursor == 0 && ui.hover.select_input_mark == -1)) { return false; } if (ui.hover.select_input_mark != -1) { delete_region(text); } else { int remove_pos = ui.hover.select_input_cursor - 1; int remove_size = 1; while (((unsigned char)(*text)[remove_pos] >> 6) == 2) { // This checks if we are in the middle of UTF-8 char remove_pos--; remove_size++; } ui.hover.select_input_cursor -= remove_size; vector_erase(*text, remove_pos, remove_size); ui.render_surface_needs_redraw = true; } return true; } bool input_changed = false; int char_val; while ((char_val = GetCharPressed())) { delete_region(text); int utf_size = 0; const char* utf_char = CodepointToUTF8(char_val, &utf_size); for (int i = 0; i < utf_size; i++) { vector_insert(text, ui.hover.select_input_cursor++, utf_char[i]); } input_changed = true; ui.render_surface_needs_redraw = true; } return input_changed; } PanelTree* find_panel(PanelTree* root, PanelType panel) { if (root->type == panel) return root; if (root->type == PANEL_SPLIT) { PanelTree* out = NULL; out = find_panel(root->left, panel); if (out) return out; out = find_panel(root->right, panel); if (out) return out; } return NULL; } static void deselect_all(void) { ui.hover.editor.select_argument = NULL; ui.hover.select_input = NULL; if (ui.dropdown.type == DROPDOWN_LIST) ui.dropdown.as.list.scroll = 0; } void show_dropdown(DropdownType type, void* ref_object, ButtonClickHandler handler) { ui.dropdown.ref_object = ref_object; ui.dropdown.handler = handler; ui.dropdown.shown = true; ui.dropdown.type = type; } void show_list_dropdown(char** list, int list_len, void* ref_object, ButtonClickHandler handler) { show_dropdown(DROPDOWN_LIST, ref_object, handler); ui.dropdown.as.list.data = list; ui.dropdown.as.list.len = list_len; ui.dropdown.as.list.select_ind = 0; ui.dropdown.as.list.scroll = 0; } void show_color_picker_dropdown(Color* edit_color, void* ref_object, ButtonClickHandler handler) { assert(edit_color != NULL); show_dropdown(DROPDOWN_COLOR_PICKER, ref_object, handler); Vector3 hsv_vec = ColorToHSV(*edit_color); HSV hsv = (HSV) { hsv_vec.x, hsv_vec.y, hsv_vec.z }; ui.dropdown.as.color_picker.hover_part = COLOR_PICKER_NONE; ui.dropdown.as.color_picker.select_part = COLOR_PICKER_NONE; ui.dropdown.as.color_picker.color = hsv; ui.dropdown.as.color_picker.edit_color = edit_color; ui.dropdown.as.color_picker.color_hex[0] = 0; } bool handle_dropdown_close(void) { memset(&ui.dropdown.as, 0, sizeof(ui.dropdown.as)); ui.dropdown.ref_object = NULL; ui.dropdown.handler = NULL; ui.dropdown.shown = false; ui.hover.editor.select_block = NULL; ui.hover.select_input = NULL; ui.hover.editor.select_argument = NULL; return true; } static char* get_basename(char* path) { char* base_name = path; for (char* str = path; *str; str++) { if (*str == '/' || *str == '\\') { base_name = str + 1; } } return base_name; } bool save_project(void) { char const* filters[] = {"*.scrp"}; char* path = tinyfd_saveFileDialog(NULL, editor.project_name, ARRLEN(filters), filters, "Scrap project files (.scrp)"); if (!path) return false; save_code(path, &project_config, editor.code); char* base_path = get_basename(path); int i; for (i = 0; base_path[i]; i++) editor.project_name[i] = base_path[i]; editor.project_name[i] = 0; editor.project_modified = false; return true; } void load_project(void) { char const* filters[] = {"*.scrp"}; char* path = tinyfd_openFileDialog(NULL, editor.project_name, ARRLEN(filters), filters, "Scrap project files (.scrp)", 0); if (!path) return; ProjectConfig new_config; BlockChain* chain = load_code(path, &new_config); switch_tab_to_panel(PANEL_CODE); if (!chain) { actionbar_show(gettext("File load failed :(")); return; } project_config_free(&project_config); project_config = new_config; for (size_t i = 0; i < vector_size(editor.code); i++) blockchain_free(&editor.code[i]); vector_free(editor.code); vm.compile_error_block = NULL; vm.compile_error_blockchain = NULL; editor.code = chain; editor.blockchain_select_counter = 0; editor.camera_pos.x = editor.code[editor.blockchain_select_counter].x - 50; editor.camera_pos.y = editor.code[editor.blockchain_select_counter].y - 50; char* base_path = get_basename(path); int i; for (i = 0; base_path[i]; i++) editor.project_name[i] = base_path[i]; editor.project_name[i] = 0; actionbar_show(gettext("File load succeeded!")); editor.project_modified = false; } bool handle_file_menu_click(void) { assert(ui.dropdown.type == DROPDOWN_LIST); switch (ui.dropdown.as.list.select_ind) { case FILE_MENU_NEW_PROJECT: for (size_t i = 0; i < vector_size(editor.code); i++) blockchain_free(&editor.code[i]); vector_clear(editor.code); switch_tab_to_panel(PANEL_CODE); editor.project_modified = false; break; case FILE_MENU_SAVE_PROJECT: save_project(); break; case FILE_MENU_LOAD_PROJECT: load_project(); break; default: printf("idk\n"); break; } return handle_dropdown_close(); } bool handle_block_dropdown_click(void) { assert(ui.dropdown.type == DROPDOWN_LIST); argument_set_const_string(ui.hover.editor.select_argument, ui.dropdown.as.list.data[ui.dropdown.as.list.select_ind]); return handle_dropdown_close(); } bool handle_color_picker_click(void) { ui.dropdown.as.color_picker.select_part = ui.dropdown.as.color_picker.hover_part; return true; } bool handle_file_button_click(void) { if (thread_is_running(&vm.thread)) return true; show_list_dropdown(file_menu_list, ARRLEN(file_menu_list), NULL, handle_file_menu_click); return true; } bool handle_settings_button_click(void) { gui_window_show(draw_settings_window); return true; } bool handle_about_button_click(void) { gui_window_show(draw_about_window); return true; } bool handle_run_button_click(void) { #ifdef USE_INTERPRETER vm_start(); #else vm_start(COMPILER_MODE_JIT); #endif return true; } bool handle_build_button_click(void) { if (thread_is_running(&vm.thread)) return true; gui_window_show(draw_project_settings_window); return true; } bool handle_stop_button_click(void) { vm_stop(); return true; } bool handle_category_click(void) { editor.palette.current_category = ui.hover.category; return true; } bool handle_jump_to_block_button_click(void) { ui.hover.editor.select_block = vm.compile_error_block; ui.hover.editor.select_blockchain = vm.compile_error_blockchain; return true; } bool handle_error_window_close_button_click(void) { clear_compile_error(); return true; } bool handle_tab_button(void) { editor.current_tab = (int)(size_t)ui.hover.button.data; ui.shader_time = 0.0; return true; } bool handle_add_tab_button(void) { char* name = ""; switch (ui.hover.panels.mouse_panel) { case PANEL_NONE: name = "Unknown"; break; case PANEL_CODE: name = "Code"; break; case PANEL_BLOCK_PALETTE: name = "Block editor.palette"; break; case PANEL_TERM: name = "Output"; break; case PANEL_BLOCK_CATEGORIES: name = "Block categories"; break; case PANEL_SPLIT: name = "Multiple..."; break; } tab_insert(name, panel_new(ui.hover.panels.mouse_panel), (int)(size_t)ui.hover.button.data); ui.hover.panels.mouse_panel = PANEL_NONE; editor.current_tab = (int)(size_t)ui.hover.button.data; ui.shader_time = 0.0; return true; } bool handle_panel_editor_save_button(void) { ui.hover.is_panel_edit_mode = false; save_config(&config); return true; } bool handle_panel_editor_cancel_button(void) { ui.hover.is_panel_edit_mode = false; return true; } bool handle_editor_add_arg_button(void) { Blockdef* blockdef = ui.hover.editor.argument->data.blockdef; size_t last_input = vector_size(blockdef->inputs); char str[32]; // TODO: Update block arguments when new argument is added if (blockdef->ref_count > 1) { deselect_all(); return true; } for (size_t i = 0; i < vector_size(blockdef->inputs); i++) { if (blockdef->inputs[i].type != INPUT_ARGUMENT) continue; if (blockdef->inputs[i].data.arg.blockdef->ref_count > 1) { deselect_all(); return true; } } blockdef_add_argument(blockdef, "", gettext("any"), BLOCKCONSTR_UNLIMITED); sprintf(str, "arg%zu", last_input); Blockdef* arg_blockdef = blockdef->inputs[last_input].data.arg.blockdef; blockdef_add_text(arg_blockdef, str); arg_blockdef->func = block_custom_arg; deselect_all(); return true; } bool handle_editor_add_text_button(void) { Blockdef* blockdef = ui.hover.editor.argument->data.blockdef; size_t last_input = vector_size(blockdef->inputs); char str[32]; // TODO: Update block arguments when new argument is added if (blockdef->ref_count > 1) { deselect_all(); return true; } for (size_t i = 0; i < vector_size(blockdef->inputs); i++) { if (blockdef->inputs[i].type != INPUT_ARGUMENT) continue; if (blockdef->inputs[i].data.arg.blockdef->ref_count > 1) { deselect_all(); return true; } } sprintf(str, "text%zu", last_input); blockdef_add_text(blockdef, str); deselect_all(); return true; } bool handle_editor_del_arg_button(void) { Blockdef* blockdef = ui.hover.editor.argument->data.blockdef; assert(ui.hover.editor.blockdef_input != (size_t)-1); if (blockdef->ref_count > 1) { deselect_all(); return true; } for (size_t i = 0; i < vector_size(blockdef->inputs); i++) { if (blockdef->inputs[i].type != INPUT_ARGUMENT) continue; if (blockdef->inputs[i].data.arg.blockdef->ref_count > 1) { deselect_all(); return true; } } blockdef_delete_input(blockdef, ui.hover.editor.blockdef_input); deselect_all(); return true; } bool handle_editor_edit_button(void) { ui.hover.editor.edit_blockdef = ui.hover.editor.argument->data.blockdef; ui.hover.editor.edit_block = ui.hover.editor.block; deselect_all(); return true; } bool handle_editor_close_button(void) { ui.hover.editor.edit_blockdef = NULL; ui.hover.editor.edit_block = NULL; deselect_all(); return true; } bool handle_editor_color_button(void) { assert(ui.hover.editor.edit_blockdef != NULL); show_color_picker_dropdown((Color*)&ui.hover.editor.edit_blockdef->color, &ui.hover.editor.edit_blockdef->color, NULL); return true; } static void remove_blockdef(BlockChain* chain) { for (size_t i = 0; i < vector_size(chain->blocks); i++) { for (size_t j = 0; j < vector_size(chain->blocks[i].arguments); j++) { Argument* arg = &chain->blocks[i].arguments[j]; if (arg->type != ARGUMENT_BLOCKDEF) continue; arg->data.blockdef->func = NULL; for (size_t k = 0; k < vector_size(arg->data.blockdef->inputs); k++) { Input* input = &arg->data.blockdef->inputs[k]; if (input->type != INPUT_ARGUMENT) continue; input->data.arg.blockdef->func = NULL; } } } } static bool handle_block_palette_click(bool mouse_empty) { if (ui.hover.editor.select_argument) { deselect_all(); return true; } bool shift_down = IsKeyDown(KEY_LEFT_SHIFT) || IsKeyDown(KEY_RIGHT_SHIFT); if ((mouse_empty || shift_down) && ui.hover.editor.block) { // Pickup block scrap_log(LOG_INFO, "Pickup block"); assert(editor.palette.current_category != NULL); vector_add(&editor.mouse_blockchains, blockchain_copy(ui.hover.editor.blockchain, 0)); return true; } else if (!mouse_empty) { // Drop block scrap_log(LOG_INFO, "Drop block"); if (shift_down) { for (size_t i = 0; i < vector_size(editor.mouse_blockchains); i++) { remove_blockdef(&editor.mouse_blockchains[i]); blockchain_free(&editor.mouse_blockchains[i]); } vector_clear(editor.mouse_blockchains); } else { remove_blockdef(&editor.mouse_blockchains[0]); blockchain_free(&editor.mouse_blockchains[0]); vector_remove(editor.mouse_blockchains, 0); } return true; } return true; } static bool handle_blockdef_editor_click(void) { // Pickup blockdef scrap_log(LOG_INFO, "Pickup blockdef"); if (!ui.hover.editor.blockdef) return true; if (ui.hover.editor.edit_blockdef == ui.hover.editor.argument->data.blockdef) return false; vector_add(&editor.mouse_blockchains, blockchain_new()); blockchain_add_block(&editor.mouse_blockchains[vector_size(editor.mouse_blockchains) - 1], block_new_ms(ui.hover.editor.blockdef)); deselect_all(); return true; } static void code_put_blocks(bool single) { scrap_log(LOG_INFO, "Put block(s)"); if (single) { BlockChain* chain = &editor.mouse_blockchains[0]; chain->x += editor.camera_pos.x - ui.hover.panels.panel_size.x; chain->y += editor.camera_pos.y - ui.hover.panels.panel_size.y; chain->x /= config.ui_size / 32.0; chain->y /= config.ui_size / 32.0; vector_add(&editor.code, *chain); vector_remove(editor.mouse_blockchains, 0); } else { for (size_t i = 0; i < vector_size(editor.mouse_blockchains); i++) { BlockChain* chain = &editor.mouse_blockchains[i]; chain->x += editor.camera_pos.x - ui.hover.panels.panel_size.x; chain->y += editor.camera_pos.y - ui.hover.panels.panel_size.y; chain->x /= config.ui_size / 32.0; chain->y /= config.ui_size / 32.0; vector_add(&editor.code, *chain); } vector_clear(editor.mouse_blockchains); } ui.hover.editor.select_blockchain = &editor.code[vector_size(editor.code) - 1]; ui.hover.editor.select_block = &ui.hover.editor.select_blockchain->blocks[0]; editor.project_modified = true; } static void code_attach_to_argument(void) { scrap_log(LOG_INFO, "Attach to argument"); BlockChain* chain = &editor.mouse_blockchains[0]; if (vector_size(chain->blocks) > 1) return; if (chain->blocks[0].blockdef->type == BLOCKTYPE_CONTROLEND) return; if (chain->blocks[0].blockdef->type == BLOCKTYPE_HAT) return; if (ui.hover.editor.argument->type != ARGUMENT_TEXT && ui.hover.editor.argument->type != ARGUMENT_COLOR) return; chain->blocks[0].parent = ui.hover.editor.block; argument_set_block(ui.hover.editor.argument, chain->blocks[0]); vector_clear(chain->blocks); blockchain_free(chain); vector_remove(editor.mouse_blockchains, 0); ui.hover.editor.select_blockchain = ui.hover.editor.blockchain; ui.hover.editor.select_block = &ui.hover.editor.argument->data.block; ui.hover.select_input = NULL; editor.project_modified = true; } static void code_copy_argument(void) { scrap_log(LOG_INFO, "Copy argument"); vector_add(&editor.mouse_blockchains, blockchain_new()); blockchain_add_block(&editor.mouse_blockchains[vector_size(editor.mouse_blockchains) - 1], block_copy(ui.hover.editor.block, NULL)); } static void code_swap_argument(void) { scrap_log(LOG_INFO, "Swap argument"); BlockChain* chain = &editor.mouse_blockchains[0]; if (vector_size(chain->blocks) > 1) return; if (chain->blocks[0].blockdef->type == BLOCKTYPE_CONTROLEND) return; if (chain->blocks[0].blockdef->type == BLOCKTYPE_HAT) return; if (ui.hover.editor.parent_argument->type != ARGUMENT_BLOCK) return; chain->blocks[0].parent = ui.hover.editor.block->parent; Block temp = chain->blocks[0]; chain->blocks[0] = *ui.hover.editor.block; chain->blocks[0].parent = NULL; block_update_parent_links(&chain->blocks[0]); argument_set_block(ui.hover.editor.parent_argument, temp); ui.hover.editor.select_block = &ui.hover.editor.parent_argument->data.block; ui.hover.editor.select_blockchain = ui.hover.editor.blockchain; editor.project_modified = true; } static void code_detach_argument(void) { scrap_log(LOG_INFO, "Detach argument"); assert(ui.hover.editor.parent_argument != NULL); vector_add(&editor.mouse_blockchains, blockchain_new()); BlockChain* chain = &editor.mouse_blockchains[vector_size(editor.mouse_blockchains) - 1]; blockchain_add_block(chain, *ui.hover.editor.block); chain->blocks[0].parent = NULL; if (ui.hover.editor.block->parent->blockdef->inputs[ui.hover.editor.parent_argument->input_id].type == INPUT_COLOR) { argument_set_color(ui.hover.editor.parent_argument, (BlockdefColor) { 0xff, 0xff, 0xff, 0xff }); } else { argument_set_text(ui.hover.editor.parent_argument, ""); } ui.hover.editor.select_blockchain = NULL; ui.hover.editor.select_block = NULL; ui.hover.select_input = NULL; editor.project_modified = true; } static void code_copy_blocks(bool single) { scrap_log(LOG_INFO, "Copy block(s)"); int ind = ui.hover.editor.block - ui.hover.editor.blockchain->blocks; BlockChain new_chain = single ? blockchain_copy_single(ui.hover.editor.blockchain, ind) : blockchain_copy(ui.hover.editor.blockchain, ind); if (vector_size(new_chain.blocks) == 0) { blockchain_free(&new_chain); ui.hover.select_input = NULL; return; } vector_add(&editor.mouse_blockchains, new_chain); ui.hover.select_input = NULL; } static void code_detach_blocks(bool single) { scrap_log(LOG_INFO, "Detach block(s)"); int ind = ui.hover.editor.block - ui.hover.editor.blockchain->blocks; vector_add(&editor.mouse_blockchains, blockchain_new()); BlockChain* chain = &editor.mouse_blockchains[vector_size(editor.mouse_blockchains) - 1]; if (single) { blockchain_detach_single(chain, ui.hover.editor.blockchain, ind); } else { blockchain_detach(chain, ui.hover.editor.blockchain, ind); } if (vector_size(chain->blocks) == 0) { blockchain_free(chain); vector_pop(editor.mouse_blockchains); } if (vector_size(ui.hover.editor.blockchain->blocks) == 0) { blockchain_free(ui.hover.editor.blockchain); vector_remove(editor.code, ui.hover.editor.blockchain - editor.code); ui.hover.editor.block = NULL; } editor.project_modified = true; ui.hover.select_input = NULL; } static void code_attach_block(void) { scrap_log(LOG_INFO, "Attach block"); BlockChain* chain = &editor.mouse_blockchains[0]; if (chain->blocks[0].blockdef->type == BLOCKTYPE_HAT) return; int ind = ui.hover.editor.block - ui.hover.editor.blockchain->blocks; blockchain_insert(ui.hover.editor.blockchain, chain, ind); blockchain_free(chain); vector_remove(editor.mouse_blockchains, 0); ui.hover.editor.block = &ui.hover.editor.blockchain->blocks[ind]; ui.hover.editor.select_block = ui.hover.editor.block + 1; ui.hover.editor.select_blockchain = ui.hover.editor.blockchain; editor.project_modified = true; } static bool handle_code_editor_click(bool mouse_empty) { bool shift_down = IsKeyDown(KEY_LEFT_SHIFT) || IsKeyDown(KEY_RIGHT_SHIFT); bool alt_down = IsKeyDown(KEY_LEFT_ALT) || IsKeyDown(KEY_RIGHT_ALT); bool ctrl_down = IsKeyDown(KEY_LEFT_CONTROL) || IsKeyDown(KEY_RIGHT_CONTROL); if (!mouse_empty && !ui.hover.editor.block) { int x = 0, y = 0, x_i = 0, x_i_max = ceil(sqrt(vector_size(editor.mouse_blockchains))), y_max = 0; for (size_t i = 0; i < vector_size(editor.mouse_blockchains); i++) { editor.mouse_blockchains[i].x = gui->mouse_x + x; editor.mouse_blockchains[i].y = gui->mouse_y + y; x += editor.mouse_blockchains[i].width + config.ui_size; x_i++; y_max = MAX(y_max, editor.mouse_blockchains[i].height); if (x_i >= x_i_max) { x = 0; y += y_max + config.ui_size; y_max = 0; x_i = 0; } } code_put_blocks(!shift_down); return true; } if (!ui.hover.editor.block || !ui.hover.editor.blockchain) return false; if (ui.hover.editor.argument && !mouse_empty) { code_attach_to_argument(); return true; } if (ui.hover.editor.block->parent) { if (alt_down) { code_copy_argument(); return true; } if (!mouse_empty && !shift_down) { code_swap_argument(); return true; } code_detach_argument(); return true; } if (alt_down) { code_copy_blocks(ctrl_down); return true; } if (mouse_empty || shift_down) { code_detach_blocks(ctrl_down); ui.hover.editor.edit_blockdef = NULL; ui.hover.editor.edit_block = NULL; ui.hover.editor.select_blockchain = NULL; ui.hover.editor.select_block = NULL; } else { code_attach_block(); } return true; } static bool handle_editor_panel_click(void) { if (!ui.hover.panels.panel) return true; if (ui.hover.panels.panel->type == PANEL_SPLIT) { ui.hover.panels.drag_panel = ui.hover.panels.panel; ui.hover.panels.drag_panel_size = ui.hover.panels.panel_size; return false; } if (ui.hover.panels.mouse_panel == PANEL_NONE) { PanelTree* parent = ui.hover.panels.panel->parent; if (!parent) { if (vector_size(editor.tabs) > 1) { ui.hover.panels.mouse_panel = ui.hover.panels.panel->type; tab_delete(editor.current_tab); } return true; } ui.hover.panels.mouse_panel = ui.hover.panels.panel->type; free(ui.hover.panels.panel); PanelTree* other_panel = parent->left == ui.hover.panels.panel ? parent->right : parent->left; parent->type = other_panel->type; parent->split_percent = other_panel->split_percent; parent->direction = other_panel->direction; parent->left = other_panel->left; parent->right = other_panel->right; if (other_panel->type == PANEL_SPLIT) { parent->left->parent = parent; parent->right->parent = parent; } free(other_panel); } else { panel_split(ui.hover.panels.panel, ui.hover.panels.panel_side, ui.hover.panels.mouse_panel, 0.5); ui.hover.panels.mouse_panel = PANEL_NONE; } return true; } static void get_input_ind(void) { assert(ui.hover.input_info.font != NULL); assert(ui.hover.input_info.input != NULL); float width = 0.0; float prev_width = 0.0; int codepoint = 0; // Current character int index = 0; // Index position in sprite font int text_size = strlen(*ui.hover.input_info.input); float scale_factor = ui.hover.input_info.font_size / (float)ui.hover.input_info.font->baseSize; int prev_i = 0; int i = 0; while (i < text_size && (width * scale_factor) < ui.hover.input_info.rel_pos.x) { int next = 0; codepoint = GetCodepointNext(&(*ui.hover.input_info.input)[i], &next); index = search_glyph(*ui.hover.input_info.font, codepoint); prev_width = width; prev_i = i; if (ui.hover.input_info.font->glyphs[index].advanceX != 0) { width += ui.hover.input_info.font->glyphs[index].advanceX; } else { width += ui.hover.input_info.font->recs[index].width + ui.hover.input_info.font->glyphs[index].offsetX; } i += next; } prev_width *= scale_factor; width *= scale_factor; if (width - ui.hover.input_info.rel_pos.x < ui.hover.input_info.rel_pos.x - prev_width) { // Right side of char is closer ui.hover.select_input_cursor = i; } else { ui.hover.select_input_cursor = prev_i; } ui.hover.select_input_mark = -1; } // Return value indicates if we should cancel dragging static bool handle_mouse_click(void) { ui.hover.mouse_click_pos = (Vector2) { gui->mouse_x, gui->mouse_y }; editor.camera_click_pos = editor.camera_pos; ui.hover.dragged_slider.value = NULL; if (ui.hover.select_input == &editor.search_list_search) { if (ui.hover.editor.blockdef) { vector_add(&editor.mouse_blockchains, blockchain_new()); BlockChain* chain = &editor.mouse_blockchains[vector_size(editor.mouse_blockchains) - 1]; blockchain_add_block(chain, block_new_ms(ui.hover.editor.blockdef)); if (ui.hover.editor.blockdef->type == BLOCKTYPE_CONTROL && vm.end_blockdef != (size_t)-1) { blockchain_add_block(chain, block_new_ms(vm.blockdefs[vm.end_blockdef])); } } ui.hover.select_input = NULL; ui.hover.editor.block = NULL; return true; } if (ui.hover.button.handler) return ui.hover.button.handler(); if (ui.hover.hover_slider.value) { ui.hover.dragged_slider = ui.hover.hover_slider; ui.hover.slider_last_val = *ui.hover.dragged_slider.value; return false; } if (gui_window_is_shown()) { if (ui.hover.input_info.input) get_input_ind(); if (ui.hover.input_info.input != ui.hover.select_input) ui.hover.select_input = ui.hover.input_info.input; return true; } if (!ui.hover.panels.panel) return true; if (ui.hover.is_panel_edit_mode) return handle_editor_panel_click(); if (ui.hover.panels.panel->type == PANEL_TERM) return true; if (thread_is_running(&vm.thread)) return ui.hover.panels.panel->type != PANEL_CODE; if (ui.hover.input_info.input) get_input_ind(); if (ui.hover.input_info.input != ui.hover.select_input) ui.hover.select_input = ui.hover.input_info.input; bool mouse_empty = vector_size(editor.mouse_blockchains) == 0; if (ui.hover.panels.panel->type == PANEL_BLOCK_PALETTE) return handle_block_palette_click(mouse_empty); if (ui.hover.editor.argument && ui.hover.editor.argument->type == ARGUMENT_BLOCKDEF) { if (handle_blockdef_editor_click()) return true; } if (ui.dropdown.shown && ui.dropdown.type == DROPDOWN_COLOR_PICKER) { ui.dropdown.as.color_picker.select_part = COLOR_PICKER_NONE; } if (mouse_empty) { if (ui.hover.editor.block && ui.hover.editor.argument) { Input block_input = ui.hover.editor.block->blockdef->inputs[ui.hover.editor.argument->input_id]; if (block_input.type == INPUT_DROPDOWN) { size_t list_len = 0; char** list = block_input.data.drop.list(ui.hover.editor.block, &list_len); show_list_dropdown(list, list_len, ui.hover.editor.argument, handle_block_dropdown_click); } else if (block_input.type == INPUT_COLOR) { show_color_picker_dropdown((Color*)&ui.hover.editor.argument->data.color, ui.hover.editor.argument, NULL); } } if (ui.hover.editor.blockchain != ui.hover.editor.select_blockchain) { ui.hover.editor.select_blockchain = ui.hover.editor.blockchain; if (ui.hover.editor.select_blockchain) editor.blockchain_select_counter = ui.hover.editor.select_blockchain - editor.code; } if (ui.hover.editor.block != ui.hover.editor.select_block) { ui.hover.editor.select_block = ui.hover.editor.block; } if (ui.hover.editor.argument != ui.hover.editor.select_argument) { if (!ui.hover.editor.argument || ui.hover.input_info.input || ui.dropdown.shown) { ui.hover.editor.select_argument = ui.hover.editor.argument; } if (ui.dropdown.type == DROPDOWN_LIST) ui.dropdown.as.list.scroll = 0; return true; } if (ui.hover.editor.select_argument) { return true; } } if (ui.hover.panels.panel->type == PANEL_CODE && handle_code_editor_click(mouse_empty)) return true; return ui.hover.panels.panel->type != PANEL_CODE; } static void block_next_argument() { Argument* args = ui.hover.editor.select_block->arguments; Argument* arg = ui.hover.editor.select_argument ? ui.hover.editor.select_argument + 1 : &args[0]; if (arg - args >= (int)vector_size(args)) { if (ui.hover.editor.select_block->parent) { Argument* parent_args = ui.hover.editor.select_block->parent->arguments; for (size_t i = 0; i < vector_size(parent_args); i++) { 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]; } ui.hover.editor.select_block = ui.hover.editor.select_block->parent; block_next_argument(); } else { ui.hover.editor.select_argument = NULL; } return; } if (arg->type == ARGUMENT_TEXT || arg->type == ARGUMENT_CONST_STRING) { ui.hover.editor.select_argument = arg; } else if (arg->type == ARGUMENT_BLOCK) { ui.hover.editor.select_argument = NULL; ui.hover.editor.select_block = &arg->data.block; } } static void block_prev_argument() { Argument* args = ui.hover.editor.select_block->arguments; Argument* arg = ui.hover.editor.select_argument ? ui.hover.editor.select_argument - 1 : &args[-1]; if (arg - args < 0) { if (ui.hover.editor.select_argument) { ui.hover.editor.select_argument = NULL; return; } if (ui.hover.editor.select_block->parent) { Argument* parent_args = ui.hover.editor.select_block->parent->arguments; for (size_t i = 0; i < vector_size(parent_args); i++) { 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]; } ui.hover.editor.select_block = ui.hover.editor.select_block->parent; block_prev_argument(); } else { ui.hover.editor.select_argument = NULL; } return; } if (arg->type == ARGUMENT_TEXT || arg->type == ARGUMENT_CONST_STRING) { ui.hover.editor.select_argument = arg; } else if (arg->type == ARGUMENT_BLOCK) { ui.hover.editor.select_argument = NULL; ui.hover.editor.select_block = &arg->data.block; while (vector_size(ui.hover.editor.select_block->arguments) != 0) { arg = &ui.hover.editor.select_block->arguments[vector_size(ui.hover.editor.select_block->arguments) - 1]; if (arg->type == ARGUMENT_TEXT || arg->type == ARGUMENT_CONST_STRING) { ui.hover.editor.select_argument = arg; break; } else if (arg->type == ARGUMENT_BLOCK) { ui.hover.editor.select_block = &arg->data.block; } } } } static bool handle_code_panel_key_press(void) { if (ui.hover.editor.select_argument && !ui.hover.select_input) { if (IsKeyPressed(KEY_ENTER) || IsKeyPressed(KEY_KP_ENTER)) { ui.hover.select_input = &ui.hover.editor.select_argument->data.text; ui.hover.select_input_mark = 0; ui.hover.select_input_cursor = strlen(*ui.hover.select_input); ui.render_surface_needs_redraw = true; return true; } } if (IsKeyPressed(KEY_TAB) && vector_size(editor.code) > 0) { if (IsKeyDown(KEY_LEFT_SHIFT)) { editor.blockchain_select_counter--; if (editor.blockchain_select_counter < 0) editor.blockchain_select_counter = vector_size(editor.code) - 1; } else { editor.blockchain_select_counter++; if ((vec_size_t)editor.blockchain_select_counter >= vector_size(editor.code)) editor.blockchain_select_counter = 0; } ui.hover.select_input = NULL; ui.hover.editor.select_argument = NULL; ui.hover.editor.select_block = &editor.code[editor.blockchain_select_counter].blocks[0]; ui.hover.editor.select_blockchain = &editor.code[editor.blockchain_select_counter]; editor.camera_pos.x = editor.code[editor.blockchain_select_counter].x - 50; editor.camera_pos.y = editor.code[editor.blockchain_select_counter].y - 50; actionbar_show(TextFormat(gettext("Jump to chain (%d/%d)"), editor.blockchain_select_counter + 1, vector_size(editor.code))); ui.render_surface_needs_redraw = true; return true; } if (!ui.hover.editor.select_blockchain || !ui.hover.editor.select_block || ui.hover.select_input) return false; int bounds_x = MIN(200, ui.hover.panels.code_panel_bounds.width / 2); int bounds_y = MIN(200, ui.hover.panels.code_panel_bounds.height / 2); if (ui.hover.editor.select_block_pos.x - (ui.hover.panels.code_panel_bounds.x + ui.hover.panels.code_panel_bounds.width) > -bounds_x) { 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; ui.render_surface_needs_redraw = true; } if (ui.hover.editor.select_block_pos.x - ui.hover.panels.code_panel_bounds.x < bounds_x) { editor.camera_pos.x += ui.hover.editor.select_block_pos.x - ui.hover.panels.code_panel_bounds.x - bounds_x; ui.render_surface_needs_redraw = true; } if (ui.hover.editor.select_block_pos.y - (ui.hover.panels.code_panel_bounds.y + ui.hover.panels.code_panel_bounds.height) > -bounds_y) { 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; ui.render_surface_needs_redraw = true; } if (ui.hover.editor.select_block_pos.y - ui.hover.panels.code_panel_bounds.y < bounds_y) { editor.camera_pos.y += ui.hover.editor.select_block_pos.y - ui.hover.panels.code_panel_bounds.y - bounds_y; ui.render_surface_needs_redraw = true; } if (IsKeyPressed(KEY_RIGHT) || IsKeyPressedRepeat(KEY_RIGHT)) { block_next_argument(); ui.render_surface_needs_redraw = true; return true; } if (IsKeyPressed(KEY_LEFT) || IsKeyPressedRepeat(KEY_LEFT)) { block_prev_argument(); ui.render_surface_needs_redraw = true; return true; } if (IsKeyPressed(KEY_UP) || IsKeyPressedRepeat(KEY_UP)) { while (ui.hover.editor.select_block->parent) ui.hover.editor.select_block = ui.hover.editor.select_block->parent; ui.hover.editor.select_block--; ui.hover.editor.select_argument = NULL; if (ui.hover.editor.select_block < ui.hover.editor.select_blockchain->blocks) ui.hover.editor.select_block = ui.hover.editor.select_blockchain->blocks; ui.render_surface_needs_redraw = true; return true; } if (IsKeyPressed(KEY_DOWN) || IsKeyPressedRepeat(KEY_DOWN)) { while (ui.hover.editor.select_block->parent) ui.hover.editor.select_block = ui.hover.editor.select_block->parent; ui.hover.editor.select_block++; ui.hover.editor.select_argument = NULL; if (ui.hover.editor.select_block - ui.hover.editor.select_blockchain->blocks >= (int)vector_size(ui.hover.editor.select_blockchain->blocks)) { ui.hover.editor.select_block--; } ui.render_surface_needs_redraw = true; return true; } return false; } static bool search_string(const char* str, const char* substr) { if (*substr == 0) return true; int next_ch, next_subch, cur_ch, cur_subch; char* cur_substr = (char*)substr; char* cur_str = (char*)str; while (*cur_str != 0 && *cur_substr != 0) { cur_ch = GetCodepointNext(cur_str, &next_ch); cur_subch = GetCodepointNext(cur_substr, &next_subch); if (towlower(cur_ch) == towlower(cur_subch)) { cur_substr += next_subch; cur_str += next_ch; } else { if (cur_substr == substr) cur_str += next_ch; cur_substr = (char*)substr; } } return *cur_substr == 0; } static bool search_blockdef(Blockdef* blockdef) { if (search_string(blockdef->id, editor.search_list_search)) return true; for (size_t i = 0; i < vector_size(blockdef->inputs); i++) { if (blockdef->inputs[i].type != INPUT_TEXT_DISPLAY) continue; if (search_string(blockdef->inputs[i].data.text, editor.search_list_search)) return true; } return false; } void update_search(void) { vector_clear(editor.search_list); for (size_t i = 0; i < vector_size(vm.blockdefs); i++) { if (vm.blockdefs[i]->type == BLOCKTYPE_END) continue; if (!search_blockdef(vm.blockdefs[i])) continue; vector_add(&editor.search_list, vm.blockdefs[i]); } } static void handle_key_press(void) { if (vector_size(editor.mouse_blockchains) > 0) return; if (IsKeyPressed(KEY_F5)) { #ifdef USE_INTERPRETER vm_start(); #else vm_start(COMPILER_MODE_JIT); #endif return; } if (IsKeyPressed(KEY_F6)) { vm_stop(); return; } if (IsKeyPressed(KEY_S) && ui.hover.select_input != &editor.search_list_search && !ui.hover.is_panel_edit_mode && ui.hover.panels.panel && ui.hover.panels.panel->type == PANEL_CODE && !thread_is_running(&vm.thread) && !gui_window_is_shown() && !ui.hover.select_input) { vector_clear(editor.search_list_search); vector_add(&editor.search_list_search, 0); ui.hover.select_input = &editor.search_list_search; ui.hover.select_input_cursor = 0; ui.hover.select_input_mark = -1; ui.render_surface_needs_redraw = true; update_search(); return; } if (ui.hover.panels.panel) { if (ui.hover.panels.panel->type == PANEL_TERM) { if (!thread_is_running(&vm.thread)) return; if (IsKeyPressed(KEY_ENTER) || IsKeyPressed(KEY_KP_ENTER)) { term_input_put_char('\n'); term_print_str("\n"); ui.render_surface_needs_redraw = true; return; } int char_val; while ((char_val = GetCharPressed())) { int utf_size = 0; const char* utf_char = CodepointToUTF8(char_val, &utf_size); for (int i = 0; i < utf_size; i++) { term_input_put_char(utf_char[i]); } // CodepointToUTF8() returns an array, not a null terminated string, so we copy it to satisfy constraints char utf_str[7]; memcpy(utf_str, utf_char, utf_size); utf_str[utf_size] = 0; term_print_str(utf_str); ui.render_surface_needs_redraw = true; } return; } else if (ui.hover.panels.panel->type == PANEL_CODE) { if (handle_code_panel_key_press()) return; } } if (IsKeyPressed(KEY_ESCAPE)) { ui.hover.select_input = NULL; ui.hover.editor.select_argument = NULL; ui.render_surface_needs_redraw = true; return; } 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; if (edit_text(ui.hover.select_input)) { if (ui.hover.select_input == &editor.search_list_search) update_search(); } } static void handle_mouse_wheel(void) { if (!ui.hover.panels.panel) return; if (ui.hover.panels.panel->type != PANEL_CODE) return; if (ui.hover.editor.select_argument) return; if (ui.hover.is_panel_edit_mode) return; if (ui.hover.select_input) return; if (gui_window_is_shown()) return; Vector2 wheel = GetMouseWheelMoveV(); editor.camera_pos.x -= wheel.x * config.ui_size * 2; editor.camera_pos.y -= wheel.y * config.ui_size * 2; if (wheel.x != 0 || wheel.y != 0) { ui.hover.editor.select_block = NULL; ui.hover.editor.select_argument = NULL; ui.hover.select_input = NULL; ui.hover.editor.select_blockchain = NULL; } } static void handle_mouse_drag(void) { if (ui.hover.drag_cancelled) return; if (ui.hover.is_panel_edit_mode && ui.hover.panels.drag_panel && ui.hover.panels.drag_panel->type == PANEL_SPLIT) { if (ui.hover.panels.drag_panel->direction == DIRECTION_HORIZONTAL) { ui.hover.panels.drag_panel->split_percent = CLAMP( (gui->mouse_x - ui.hover.panels.drag_panel_size.x - 5) / ui.hover.panels.drag_panel_size.width, 0.0, 1.0 - (10.0 / ui.hover.panels.drag_panel_size.width) ); } else { ui.hover.panels.drag_panel->split_percent = CLAMP( (gui->mouse_y - ui.hover.panels.drag_panel_size.y - 5) / ui.hover.panels.drag_panel_size.height, 0.0, 1.0 - (10.0 / ui.hover.panels.drag_panel_size.height) ); } return; } if (ui.hover.dragged_slider.value) { *ui.hover.dragged_slider.value = CLAMP( ui.hover.slider_last_val + (gui->mouse_x - ui.hover.mouse_click_pos.x) / 2, ui.hover.dragged_slider.min, ui.hover.dragged_slider.max ); return; } editor.camera_pos.x = editor.camera_click_pos.x - (gui->mouse_x - ui.hover.mouse_click_pos.x); editor.camera_pos.y = editor.camera_click_pos.y - (gui->mouse_y - ui.hover.mouse_click_pos.y); } void scrap_gui_process_ui(void) { editor.actionbar.show_time -= GetFrameTime(); if (editor.actionbar.show_time < 0) { editor.actionbar.show_time = 0; } else { ui.render_surface_needs_redraw = true; } if (ui.shader_time_loc != -1) SetShaderValue(assets.line_shader, ui.shader_time_loc, &ui.shader_time, SHADER_UNIFORM_FLOAT); ui.shader_time += GetFrameTime() / 2.0; if (ui.shader_time >= 1.0) { ui.shader_time = 1.0; } else { ui.render_surface_needs_redraw = true; } int prev_mouse_scroll = gui->mouse_scroll; gui_update_mouse_scroll(gui, GetMouseWheelMove()); if (prev_mouse_scroll != gui->mouse_scroll) ui.render_surface_needs_redraw = true; if (IsWindowResized()) { ui.shader_time = 0.0; gui_update_window_size(gui, GetScreenWidth(), GetScreenHeight()); UnloadRenderTexture(ui.render_surface); ui.render_surface = LoadRenderTexture(GetScreenWidth(), GetScreenHeight()); SetTextureWrap(ui.render_surface.texture, TEXTURE_WRAP_MIRROR_REPEAT); ui.render_surface_needs_redraw = true; } Vector2 delta = GetMouseDelta(); if (delta.x != 0 || delta.y != 0) ui.render_surface_needs_redraw = true; if (GetMouseWheelMove() != 0.0) { handle_mouse_wheel(); ui.render_surface_needs_redraw = true; } #ifdef ARABIC_MODE gui_update_mouse_pos(gui, gui->win_w - GetMouseX(), GetMouseY()); #else gui_update_mouse_pos(gui, GetMouseX(), GetMouseY()); #endif if (IsMouseButtonPressed(MOUSE_BUTTON_LEFT)) { ui.hover.drag_cancelled = handle_mouse_click(); ui.render_surface_needs_redraw = true; #ifdef DEBUG // This will traverse through all blocks in codebase, which is expensive in large codebase. // Ideally all functions should not be broken in the first place. This helps with debugging invalid states sanitize_links(); #endif } else if (IsMouseButtonPressed(MOUSE_BUTTON_MIDDLE)) { ui.hover.mouse_click_pos = (Vector2) { gui->mouse_x, gui->mouse_y }; editor.camera_click_pos = editor.camera_pos; ui.hover.editor.select_block = NULL; ui.hover.editor.select_argument = NULL; ui.hover.select_input = NULL; ui.hover.editor.select_blockchain = NULL; ui.render_surface_needs_redraw = true; if (ui.dropdown.shown) handle_dropdown_close(); } else if (IsMouseButtonDown(MOUSE_BUTTON_MIDDLE) || IsMouseButtonDown(MOUSE_BUTTON_LEFT)) { handle_mouse_drag(); } else { ui.hover.drag_cancelled = false; ui.hover.dragged_slider.value = NULL; ui.hover.panels.drag_panel = NULL; handle_key_press(); } if (IsMouseButtonReleased(MOUSE_BUTTON_LEFT) || IsMouseButtonReleased(MOUSE_BUTTON_MIDDLE)) ui.render_surface_needs_redraw = true; if (ui.render_surface_redraw_next) { ui.render_surface_needs_redraw = true; ui.render_surface_redraw_next = false; } handle_window(); if (ui.render_surface_needs_redraw) { ui.hover.editor.block = NULL; ui.hover.editor.argument = NULL; ui.hover.input_info.input = NULL; ui.hover.category = NULL; ui.hover.editor.parent_argument = NULL; ui.hover.editor.prev_blockchain = NULL; ui.hover.editor.blockchain = NULL; ui.hover.editor.part = EDITOR_NONE; ui.hover.editor.blockdef = NULL; ui.hover.editor.blockdef_input = -1; ui.hover.button.handler = NULL; ui.hover.button.data = NULL; ui.hover.hover_slider.value = NULL; ui.hover.panels.panel = NULL; ui.hover.panels.panel_size = (Rectangle) {0}; ui.hover.editor.select_valid = false; ui.dropdown.element = NULL; if (ui.dropdown.shown && ui.dropdown.type == DROPDOWN_COLOR_PICKER) { ui.dropdown.as.color_picker.hover_part = COLOR_PICKER_NONE; } #ifdef DEBUG Timer t = start_timer("gui process"); #endif scrap_gui_process(); #ifdef DEBUG ui.ui_time = end_timer(t); #endif if (vm.start_timeout >= 0) vm.start_timeout--; // This fixes selecting wrong argument of a block when two blocks overlap if (ui.hover.editor.block && ui.hover.editor.argument) { int ind = ui.hover.editor.argument - ui.hover.editor.block->arguments; if (ind < 0 || ind > (int)vector_size(ui.hover.editor.block->arguments)) ui.hover.editor.argument = NULL; } if (ui.hover.editor.select_block && !ui.hover.editor.select_valid) { scrap_log(LOG_WARNING, "Invalid selection: %p", ui.hover.editor.select_block); ui.hover.editor.select_block = NULL; ui.hover.editor.select_blockchain = NULL; } if (vector_size(editor.mouse_blockchains) > 0 && // This small hack allows to transfer mouse blockchain contents between projects ui.hover.button.handler != handle_file_button_click && ui.hover.button.handler != handle_file_menu_click) { ui.hover.button.handler = NULL; } } ui.hover.editor.prev_block = ui.hover.editor.block; ui.hover.editor.prev_argument = ui.hover.editor.argument; ui.hover.editor.prev_blockdef = ui.hover.editor.blockdef; ui.hover.panels.prev_panel = ui.hover.panels.panel; } ================================================ FILE: src/util.c ================================================ // Scrap is a project that allows anyone to build software using simple, block based interface. // // Copyright (C) 2024-2026 Grisshink // // This software is provided 'as-is', without any express or implied // warranty. In no event will the authors be held liable for any damages // arising from the use of this software. // // Permission is granted to anyone to use this software for any purpose, // including commercial applications, and to alter it and redistribute it // freely, subject to the following restrictions: // // 1. The origin of this software must not be misrepresented; you must not // claim that you wrote the original software. If you use this software // in a product, an acknowledgment in the product documentation would be // appreciated but is not required. // 2. Altered source versions must be plainly marked as such, and must not be // misrepresented as being the original software. // 3. This notice may not be removed or altered from any source distribution. #include "vec.h" #include "util.h" #include #include #include Timer start_timer(const char* name) { Timer timer; timer.name = name; clock_gettime(CLOCK_MONOTONIC, &timer.start); return timer; } double end_timer(Timer timer) { struct timespec end; clock_gettime(CLOCK_MONOTONIC, &end); double time_taken = (end.tv_sec - timer.start.tv_sec) * 1e+6 + (end.tv_nsec - timer.start.tv_nsec) * 1e-3; return time_taken; } #define CSI_DARK_GRAY "\e[90m" #define CSI_YELLOW "\e[93m" #define CSI_RED "\e[91m" #define CSI_RESET "\e[0m" void scrap_log(int log_level, const char *text, ...) { va_list va; va_start(va, text); scrap_log_va(log_level, text, va); va_end(va); } void scrap_log_va(int log_level, const char *text, va_list args) { switch (log_level) { case LOG_TRACE: printf(CSI_DARK_GRAY "[TRACE] "); break; case LOG_DEBUG: printf("[DEBUG] "); break; case LOG_INFO: printf("[INFO] "); break; case LOG_WARNING: printf(CSI_YELLOW "[WARN] "); break; case LOG_ERROR: printf(CSI_RED "[ERROR] "); break; case LOG_FATAL: printf(CSI_RED "[FATAL] "); break; default: printf(CSI_RED "[UNKNOWN] "); break; } vprintf(text, args); printf(CSI_RESET "\n"); } ================================================ FILE: src/util.h ================================================ // Scrap is a project that allows anyone to build software using simple, block based interface. // // Copyright (C) 2024-2026 Grisshink // // This software is provided 'as-is', without any express or implied // warranty. In no event will the authors be held liable for any damages // arising from the use of this software. // // Permission is granted to anyone to use this software for any purpose, // including commercial applications, and to alter it and redistribute it // freely, subject to the following restrictions: // // 1. The origin of this software must not be misrepresented; you must not // claim that you wrote the original software. If you use this software // in a product, an acknowledgment in the product documentation would be // appreciated but is not required. // 2. Altered source versions must be plainly marked as such, and must not be // misrepresented as being the original software. // 3. This notice may not be removed or altered from any source distribution. #ifndef SCRAP_UTIL_H #define SCRAP_UTIL_H #include #include #define ARRLEN(arr) (sizeof(arr) / sizeof(arr[0])) #define ABS(x) ((x) < 0 ? -(x) : (x)) #define MAX(x, y) ((x) > (y) ? (x) : (y)) #define MIN(x, y) ((x) < (y) ? (x) : (y)) #define CLAMP(x, min, max) (MIN(MAX(min, x), max)) #define CONVERT_COLOR(color, type) (type) { color.r, color.g, color.b, color.a } #define MOD(x, y) (((x) % (y) + (y)) % (y)) #define LERP(min, max, t) (((max) - (min)) * (t) + (min)) #define UNLERP(min, max, v) (((float)(v) - (float)(min)) / ((float)(max) - (float)(min))) #define LOG_ALL 0 #define LOG_TRACE 1 #define LOG_DEBUG 2 #define LOG_INFO 3 #define LOG_WARNING 4 #define LOG_ERROR 5 #define LOG_FATAL 6 #define LOG_NONE 7 typedef struct { struct timespec start; const char* name; } Timer; Timer start_timer(const char* name); double end_timer(Timer timer); void scrap_log(int log_level, const char *text, ...); void scrap_log_va(int log_level, const char *text, va_list args); #endif // SCRAP_UTIL_H ================================================ FILE: src/vec.c ================================================ /* BSD 3-Clause License Copyright (c) 2024, Mashpoe All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "vec.h" #include #include vector_header* vector_get_header(vector vec) { return &((vector_header*)vec)[-1]; } vector vector_create(void) { vector_header* h = (vector_header*)malloc(sizeof(vector_header)); h->capacity = 0; h->size = 0; return &h->data; } void vector_free(vector vec) { free(vector_get_header(vec)); } vec_size_t vector_size(vector vec) { return vector_get_header(vec)->size; } vec_size_t vector_capacity(vector vec) { return vector_get_header(vec)->capacity; } vector_header* vector_realloc(vector_header* h, vec_type_t type_size) { vec_size_t new_capacity = (h->capacity == 0) ? 1 : h->capacity * 2; vector_header* new_h = (vector_header*)realloc(h, sizeof(vector_header) + new_capacity * type_size); new_h->capacity = new_capacity; return new_h; } bool vector_has_space(vector_header* h) { return h->capacity - h->size > 0; } void* _vector_add_dst(vector* vec_addr, vec_type_t type_size) { vector_header* h = vector_get_header(*vec_addr); if (!vector_has_space(h)) { h = vector_realloc(h, type_size); *vec_addr = h->data; } return &h->data[type_size * h->size++]; } void* _vector_insert_dst(vector* vec_addr, vec_type_t type_size, vec_size_t pos) { vector_header* h = vector_get_header(*vec_addr); vec_size_t new_length = h->size + 1; // make sure there is enough room for the new element if (!vector_has_space(h)) { h = vector_realloc(h, type_size); *vec_addr = h->data; } // move trailing elements memmove(&h->data[(pos + 1) * type_size], &h->data[pos * type_size], (h->size - pos) * type_size); h->size = new_length; return &h->data[pos * type_size]; } void _vector_erase(vector vec, vec_type_t type_size, vec_size_t pos, vec_size_t len) { vector_header* h = vector_get_header(vec); memmove(&h->data[pos * type_size], &h->data[(pos + len) * type_size], (h->size - pos - len) * type_size); h->size -= len; } void _vector_remove(vector vec, vec_type_t type_size, vec_size_t pos) { _vector_erase(vec, type_size, pos, 1); } void vector_pop(vector vec) { --vector_get_header(vec)->size; } void vector_clear(vector vec) { vector_get_header(vec)->size = 0; } void _vector_reserve(vector* vec_addr, vec_type_t type_size, vec_size_t capacity) { vector_header* h = vector_get_header(*vec_addr); if (h->capacity >= capacity) { return; } h = (vector_header*)realloc(h, sizeof(vector_header) + capacity * type_size); h->capacity = capacity; *vec_addr = &h->data; } vector _vector_copy(vector vec, vec_type_t type_size) { vector_header* h = vector_get_header(vec); size_t alloc_size = sizeof(vector_header) + h->size * type_size; vector_header* copy_h = (vector_header*)malloc(alloc_size); memcpy(copy_h, h, alloc_size); copy_h->capacity = copy_h->size; return ©_h->data; } ================================================ FILE: src/vec.h ================================================ /* BSD 3-Clause License Copyright (c) 2024, Mashpoe All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef vec_h #define vec_h #ifdef __cpp_decltype #include #define typeof(T) std::remove_reference::type>::type #endif #ifdef __cplusplus extern "C" { #endif #include // generic type for internal use typedef void* vector; // number of elements in a vector typedef size_t vec_size_t; // number of bytes for a type typedef size_t vec_type_t; typedef struct { vec_size_t size; vec_size_t capacity; unsigned char data[]; } vector_header; // TODO: more rigorous check for typeof support with different compilers #if _MSC_VER == 0 || __STDC_VERSION__ >= 202311L || defined __cpp_decltype // shortcut defines // vec_addr is a vector* (aka type**) #define vector_add_dst(vec_addr)\ ((__typeof__(*vec_addr))(\ _vector_add_dst((vector*)vec_addr, sizeof(**vec_addr))\ )) #define vector_insert_dst(vec_addr, pos)\ ((__typeof__(*vec_addr))(\ _vector_insert_dst((vector*)vec_addr, sizeof(**vec_addr), pos))) #define vector_add(vec_addr, value)\ (*vector_add_dst(vec_addr) = value) #define vector_insert(vec_addr, pos, value)\ (*vector_insert_dst(vec_addr, pos) = value) #else #define vector_add_dst(vec_addr, type)\ ((type*)_vector_add_dst((vector*)vec_addr, sizeof(type))) #define vector_insert_dst(vec_addr, type, pos)\ ((type*)_vector_insert_dst((vector*)vec_addr, sizeof(type), pos)) #define vector_add(vec_addr, type, value)\ (*vector_add_dst(vec_addr, type) = value) #define vector_insert(vec_addr, type, pos, value)\ (*vector_insert_dst(vec_addr, type, pos) = value) #endif // vec is a vector (aka type*) #define vector_erase(vec, pos, len)\ (_vector_erase((vector)vec, sizeof(*vec), pos, len)) #define vector_remove(vec, pos)\ (_vector_remove((vector)vec, sizeof(*vec), pos)) #define vector_reserve(vec_addr, capacity)\ (_vector_reserve((vector*)vec_addr, sizeof(**vec_addr), capacity)) #define vector_copy(vec)\ (_vector_copy((vector)vec, sizeof(*vec))) vector vector_create(void); void vector_free(vector vec); void* _vector_add_dst(vector* vec_addr, vec_type_t type_size); void* _vector_insert_dst(vector* vec_addr, vec_type_t type_size, vec_size_t pos); void _vector_erase(vector vec_addr, vec_type_t type_size, vec_size_t pos, vec_size_t len); void _vector_remove(vector vec_addr, vec_type_t type_size, vec_size_t pos); void vector_pop(vector vec); void vector_clear(vector vec); void _vector_reserve(vector* vec_addr, vec_type_t type_size, vec_size_t capacity); vector _vector_copy(vector vec, vec_type_t type_size); vec_size_t vector_size(vector vec); vec_size_t vector_capacity(vector vec); vector_header* vector_get_header(vector vec); // closing bracket for extern "C" #ifdef __cplusplus } #endif #endif /* vec_h */ ================================================ FILE: src/vm.c ================================================ // Scrap is a project that allows anyone to build software using simple, block based interface. // // Copyright (C) 2024-202 Grisshink // // This software is provided 'as-is', without any express or implied // warranty. In no event will the authors be held liable for any damages // arising from the use of this software. // // Permission is granted to anyone to use this software for any purpose, // including commercial applications, and to alter it and redistribute it // freely, subject to the following restrictions: // // 1. The origin of this software must not be misrepresented; you must not // claim that you wrote the original software. If you use this software // in a product, an acknowledgment in the product documentation would be // appreciated but is not required. // 2. Altered source versions must be plainly marked as such, and must not be // misrepresented as being the original software. // 3. This notice may not be removed or altered from any source distribution. #include "scrap.h" #include #include #include static BlockChain* find_blockchain(Block* block) { if (!block) return NULL; while (block->parent) block = block->parent; for (size_t i = 0; i < vector_size(editor.code); i++) { if (block >= editor.code[i].blocks && block < editor.code[i].blocks + vector_size(editor.code[i].blocks)) { return &editor.code[i]; } } return NULL; } Block block_new_ms(Blockdef* blockdef) { Block block = block_new(blockdef); for (size_t i = 0; i < vector_size(block.arguments); i++) { if (block.arguments[i].type != ARGUMENT_BLOCKDEF) continue; block.arguments[i].data.blockdef->func = block_exec_custom; } return block; } size_t blockdef_register(Vm* vm, Blockdef* blockdef) { if (!blockdef->func) scrap_log(LOG_WARNING, "[VM] Block \"%s\" has not defined its implementation!", blockdef->id); vector_add(&vm->blockdefs, blockdef); blockdef->ref_count++; if (blockdef->type == BLOCKTYPE_END && vm->end_blockdef == (size_t)-1) { vm->end_blockdef = vector_size(vm->blockdefs) - 1; } return vector_size(vm->blockdefs) - 1; } void blockdef_unregister(Vm* vm, size_t block_id) { blockdef_free(vm->blockdefs[block_id]); vector_remove(vm->blockdefs, block_id); } BlockCategory block_category_new(const char* name, Color color) { return (BlockCategory) { .name = name, .color = color, .items = vector_create(), .next = NULL, .prev = NULL, }; } BlockCategory* block_category_register(BlockCategory category) { BlockCategory* cat = malloc(sizeof(category)); assert(cat != NULL); *cat = category; if (!editor.palette.categories_end) { editor.palette.categories_end = cat; editor.palette.categories_start = cat; editor.palette.current_category = editor.palette.categories_start; return cat; } editor.palette.categories_end->next = cat; cat->prev = editor.palette.categories_end; editor.palette.categories_end = cat; return cat; } void block_category_unregister(BlockCategory* category) { for (size_t i = 0; i < vector_size(category->items); i++) { switch (category->items[i].type) { case CATEGORY_ITEM_CHAIN: blockchain_free(&category->items[i].data.chain); break; case CATEGORY_ITEM_LABEL: break; } } vector_free(category->items); if (category->next) category->next->prev = NULL; if (category->prev) category->prev->next = NULL; if (editor.palette.categories_start == category) editor.palette.categories_start = category->next; if (editor.palette.categories_end == category) editor.palette.categories_end = category->prev; if (editor.palette.current_category == category) editor.palette.current_category = editor.palette.categories_start; free(category); } void block_category_add_blockdef(BlockCategory* category, Blockdef* blockdef) { BlockChain chain = blockchain_new(); blockchain_add_block(&chain, block_new_ms(blockdef)); if (blockdef->type == BLOCKTYPE_CONTROL && vm.end_blockdef != (size_t)-1) { blockchain_add_block(&chain, block_new(vm.blockdefs[vm.end_blockdef])); } BlockCategoryItem* item = vector_add_dst(&category->items); item->type = CATEGORY_ITEM_CHAIN; item->data.chain = chain; } void block_category_add_label(BlockCategory* category, const char* label, Color color) { BlockCategoryItem* item = vector_add_dst(&category->items); item->type = CATEGORY_ITEM_LABEL; item->data.label.text = label; item->data.label.color = color; } void unregister_categories(void) { if (!editor.palette.categories_start) return; BlockCategory* cat = editor.palette.categories_start; while (cat) { BlockCategory* next = cat->next; block_category_unregister(cat); cat = next; } editor.palette.categories_start = NULL; editor.palette.categories_end = NULL; } void clear_compile_error(void) { vm.compile_error_block = NULL; vm.compile_error_blockchain = NULL; for (size_t i = 0; i < vector_size(vm.compile_error); i++) vector_free(vm.compile_error[i]); vector_clear(vm.compile_error); } Vm vm_new(void) { Vm vm = (Vm) { .blockdefs = vector_create(), .end_blockdef = -1, .thread = thread_new(exec_run, exec_cleanup), .exec = (Exec) {0}, .compile_error = vector_create(), .compile_error_block = NULL, .compile_error_blockchain = NULL, .start_timeout = -1, #ifndef USE_INTERPRETER .start_mode = COMPILER_MODE_JIT, #endif }; return vm; } void vm_free(Vm* vm) { if (thread_is_running(&vm->thread)) { thread_stop(&vm->thread); thread_join(&vm->thread); exec_free(&vm->exec); } for (ssize_t i = (ssize_t)vector_size(vm->blockdefs) - 1; i >= 0 ; i--) { blockdef_unregister(vm, i); } vector_free(vm->blockdefs); } #ifdef USE_INTERPRETER bool vm_start(void) { #else bool vm_start(CompilerMode mode) { #endif if (thread_is_running(&vm.thread)) return false; for (size_t i = 0; i < vector_size(editor.tabs); i++) { if (find_panel(editor.tabs[i].root_panel, PANEL_TERM)) { #ifndef USE_INTERPRETER vm.start_mode = mode; #endif if (editor.current_tab != (int)i) { ui.shader_time = 0.0; // Delay vm startup until next frame. Because this handler only runs after the layout is computed and // before the actual rendering begins, we need to add delay to vm startup to make sure the terminal buffer // is initialized and vm does not try to write to uninitialized buffer vm.start_timeout = 2; } else { vm.start_timeout = 1; } editor.current_tab = i; ui.render_surface_needs_redraw = true; break; } } return true; } bool vm_stop(void) { if (!thread_is_running(&vm.thread)) return false; scrap_log(LOG_INFO, "STOP"); thread_stop(&vm.thread); ui.render_surface_needs_redraw = true; return true; } void vm_handle_running_thread(void) { ThreadReturnCode thread_return = thread_try_join(&vm.thread); if (thread_return != THREAD_RETURN_RUNNING) { switch (thread_return) { case THREAD_RETURN_SUCCESS: actionbar_show(gettext("Vm executed successfully")); break; case THREAD_RETURN_FAILURE: actionbar_show(gettext("Vm shitted and died :(")); break; case THREAD_RETURN_STOPPED: actionbar_show(gettext("Vm stopped >:(")); break; default: break; } size_t i = 0; while (vm.exec.current_error[i]) { vector_add(&vm.compile_error, vector_create()); size_t line_len = 0; while (line_len < 50 && vm.exec.current_error[i]) { if (((unsigned char)vm.exec.current_error[i] >> 6) != 2) line_len++; if (line_len >= 50) break; vector_add(&vm.compile_error[vector_size(vm.compile_error) - 1], vm.exec.current_error[i++]); } vector_add(&vm.compile_error[vector_size(vm.compile_error) - 1], 0); } vm.compile_error_block = vm.exec.current_error_block; vm.compile_error_blockchain = find_blockchain(vm.compile_error_block); exec_free(&vm.exec); ui.render_surface_needs_redraw = true; } else if (thread_is_running(&vm.thread)) { mutex_lock(&term.lock); if (find_panel(editor.tabs[editor.current_tab].root_panel, PANEL_TERM) && term.is_buffer_dirty) { ui.render_surface_needs_redraw = true; term.is_buffer_dirty = false; } mutex_unlock(&term.lock); } else { if (vector_size(vm.compile_error) > 0) ui.render_surface_needs_redraw = true; } } ================================================ FILE: src/window.c ================================================ // Scrap is a project that allows anyone to build software using simple, block based interface. // // Copyright (C) 2024-2026 Grisshink // // This software is provided 'as-is', without any express or implied // warranty. In no event will the authors be held liable for any damages // arising from the use of this software. // // Permission is granted to anyone to use this software for any purpose, // including commercial applications, and to alter it and redistribute it // freely, subject to the following restrictions: // // 1. The origin of this software must not be misrepresented; you must not // claim that you wrote the original software. If you use this software // in a product, an acknowledgment in the product documentation would be // appreciated but is not required. // 2. Altered source versions must be plainly marked as such, and must not be // misrepresented as being the original software. // 3. This notice may not be removed or altered from any source distribution. #include "scrap.h" #include "../external/tinyfiledialogs.h" #include #include #include #include #include #include #include typedef struct { bool shown; float animation_time; float animation_ease; bool is_fading; bool is_hiding; WindowGuiRenderFunc render; } WindowGui; Config window_config; static WindowGui window = {0}; static bool settings_tooltip = false; static bool settings_applied = false; static char** about_text_split = NULL; static void draw_button(const char* label, Texture2D* icon, ButtonClickHandler handler, void* data); // https://easings.net/#easeOutExpo float ease_out_expo(float x) { return x == 1.0 ? 1.0 : 1 - powf(2.0, -10.0 * x); } static bool about_on_license_button_click(void) { OpenURL(LICENSE_URL); return true; } static bool window_on_close_button_click(void) { gui_window_hide(); return true; } static void vector_append(char** vec, const char* str) { if (vector_size(*vec) > 0 && (*vec)[vector_size(*vec) - 1] == 0) vector_pop(*vec); for (size_t i = 0; str[i]; i++) vector_add(vec, str[i]); vector_add(vec, 0); } static bool settings_on_browse_button_click(void) { char const* filters[] = { "*.ttf", "*.otf" }; char** path_input = ui.hover.button.data; char* path = tinyfd_openFileDialog(NULL, *path_input, ARRLEN(filters), filters, "Font files", 0); if (!path) return true; vector_clear(*path_input); vector_append(path_input, path); ui.hover.select_input_cursor = 0; ui.hover.select_input_mark = -1; ui.render_surface_needs_redraw = true; return true; } static bool settings_on_left_slider_button_click(void) { settings_applied = false; *ui.hover.hover_slider.value = MAX(*ui.hover.hover_slider.value - 1, ui.hover.hover_slider.min); return true; } static bool settings_on_right_slider_button_click(void) { settings_applied = false; *ui.hover.hover_slider.value = MIN(*ui.hover.hover_slider.value + 1, ui.hover.hover_slider.max); return true; } static bool settings_on_dropdown_button_click(void) { settings_applied = false; *(int*)ui.dropdown.ref_object = ui.dropdown.as.list.select_ind; return handle_dropdown_close(); } static bool settings_on_dropdown_click(void) { 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); return true; } static bool settings_on_panel_editor_button_click(void) { gui_window_hide(); ui.hover.is_panel_edit_mode = true; ui.hover.select_input = NULL; ui.hover.editor.select_argument = NULL; ui.hover.editor.select_block = NULL; ui.hover.editor.select_blockchain = NULL; return true; } static bool settings_on_reset_button_click(void) { set_default_config(&window_config); settings_applied = false; return true; } static bool settings_on_reset_panels_button_click(void) { delete_all_tabs(); init_panels(); editor.current_tab = 0; settings_applied = false; return true; } static bool settings_on_apply_button_click(void) { apply_config(&config, &window_config); save_config(&window_config); settings_applied = true; return true; } static bool settings_on_toggle_button_click(void) { bool* toggle_value = ui.hover.button.data; *toggle_value = !*toggle_value; return true; } static bool project_settings_on_build_button_click(void) { #ifdef USE_INTERPRETER vm_start(); #else vm_start(COMPILER_MODE_BUILD); #endif gui_window_hide(); return true; } static bool save_confirmation_on_yes_button_click(void) { if (save_project()) { ui.scrap_running = false; } else { gui_window_hide(); } return true; } static bool save_confirmation_on_no_button_click(void) { ui.scrap_running = false; return true; } static bool save_confirmation_on_cancel_button_click(void) { gui_window_hide(); return true; } void init_gui_window(void) { window.is_fading = true; } bool gui_window_is_shown(void) { return window.shown; } WindowGuiRenderFunc gui_window_get_render_func(void) { return window.render; } void gui_window_show(WindowGuiRenderFunc func) { config_free(&window_config); // Drop old strings and replace with new config_copy(&window_config, &config); window.is_fading = false; window.render = func; ui.shader_time = -0.2; settings_applied = false; } void gui_window_hide(void) { ui.hover.select_input = NULL; window.is_fading = true; } void gui_window_hide_immediate(void) { gui_window_hide(); window.is_hiding = true; } static void settings_button_on_hover(GuiElement* el) { if (ui.hover.button.handler) return; el->color = (GuiColor) { 0x40, 0x40, 0x40, 0xff }; ui.hover.button = *(ButtonHoverInfo*)gui_get_state(el); } static void close_button_on_hover(GuiElement* el) { if (ui.hover.button.handler) return; if (el->draw_type == DRAWTYPE_RECT) return; el->draw_type = DRAWTYPE_RECT; el->draw_subtype = GUI_SUBTYPE_DEFAULT; el->color = (GuiColor) { 0x40, 0x40, 0x40, 0xff }; ui.hover.button.handler = window_on_close_button_click; } static void window_on_hover(GuiElement* el) { (void) el; if (!ui.dropdown.shown) ui.hover.button.handler = NULL; } static void begin_window(const char* title, Texture2D* icon, int w, int h, float scaling) { ui.hover.button.handler = window_on_close_button_click; gui_element_begin(gui); gui_set_floating(gui); gui_set_rect(gui, (GuiColor) { 0x00, 0x00, 0x00, 0x40 * scaling }); gui_set_position(gui, 0, 0); gui_set_fixed(gui, gui->win_w, gui->win_h); gui_element_end(gui); gui_element_begin(gui); gui_scale_element(gui, scaling); gui_set_floating(gui); gui_set_position(gui, gui->win_w / 2, gui->win_h / 2); gui_set_anchor(gui, ALIGN_CENTER, ALIGN_CENTER); gui_set_fixed(gui, w, h); if (w == 0) gui_set_fit(gui, DIRECTION_HORIZONTAL); if (h == 0) gui_set_fit(gui, DIRECTION_VERTICAL); gui_set_rect(gui, (GuiColor) { 0x20, 0x20, 0x20, 0xff }); gui_set_direction(gui, DIRECTION_VERTICAL); gui_on_hover(gui, window_on_hover); gui_element_begin(gui); gui_set_grow(gui, DIRECTION_HORIZONTAL); gui_set_min_size(gui, 0, config.ui_size * 1.2); gui_set_rect(gui, (GuiColor) { 0x30, 0x30, 0x30, 0xff }); gui_set_direction(gui, DIRECTION_HORIZONTAL); gui_set_align(gui, ALIGN_LEFT, ALIGN_CENTER); gui_set_gap(gui, ELEMENT_GAP/2); gui_grow(gui, DIRECTION_HORIZONTAL); if (icon) gui_image(gui, icon, config.ui_size, GUI_WHITE); gui_text(gui, &assets.fonts.font_eb, title, config.ui_size * 0.8, GUI_WHITE); gui_grow(gui, DIRECTION_HORIZONTAL); gui_element_end(gui); gui_element_begin(gui); gui_set_direction(gui, DIRECTION_VERTICAL); gui_set_padding(gui, config.ui_size * 0.5, config.ui_size * 0.5); gui_set_grow(gui, DIRECTION_HORIZONTAL); gui_set_grow(gui, DIRECTION_VERTICAL); gui_set_gap(gui, ELEMENT_GAP); } static void end_window(void) { gui_element_end(gui); GuiElement* el = gui_get_element(gui); gui_element_begin(gui); gui_set_floating(gui); if (IsShaderValid(assets.line_shader)) { gui_set_border(gui, (GuiColor) { 0x60, 0x60, 0x60, 0xff }, 2); gui_set_shader(gui, &assets.line_shader); } gui_set_position(gui, 0, 0); gui_set_fixed(gui, el->w, el->h); gui_element_end(gui); gui_element_begin(gui); gui_set_floating(gui); gui_set_position(gui, el->w - config.ui_size * 1.2, 0); gui_set_fixed(gui, config.ui_size * 1.2, config.ui_size * 1.2); gui_set_align(gui, ALIGN_CENTER, ALIGN_CENTER); gui_on_hover(gui, close_button_on_hover); gui_text(gui, &assets.fonts.font_cond, "X", config.ui_size * 0.8, GUI_WHITE); gui_element_end(gui); gui_element_end(gui); } static void warning_on_hover(GuiElement* el) { if (ui.hover.button.handler) return; (void) el; settings_tooltip = true; } static void begin_setting(const char* name, bool warning) { gui_element_begin(gui); gui_set_grow(gui, DIRECTION_HORIZONTAL); gui_set_direction(gui, DIRECTION_HORIZONTAL); gui_set_gap(gui, ELEMENT_GAP); gui_set_min_size(gui, 0, config.ui_size); gui_element_begin(gui); gui_set_grow(gui, DIRECTION_HORIZONTAL); gui_set_grow(gui, DIRECTION_VERTICAL); gui_set_direction(gui, DIRECTION_HORIZONTAL); gui_set_align(gui, ALIGN_RIGHT, ALIGN_CENTER); gui_text(gui, &assets.fonts.font_cond, name, config.ui_size * 0.6, GUI_WHITE); gui_element_end(gui); if (warning) { gui_element_begin(gui); gui_set_image(gui, &assets.textures.icon_warning, config.ui_size, GUI_WHITE); gui_on_hover(gui, warning_on_hover); gui_element_end(gui); } else { gui_spacer(gui, config.ui_size, config.ui_size); } gui_element_begin(gui); gui_set_grow(gui, DIRECTION_HORIZONTAL); gui_set_direction(gui, DIRECTION_HORIZONTAL); gui_set_gap(gui, ELEMENT_GAP); gui_set_min_size(gui, 0, config.ui_size); } static void slider_on_hover(GuiElement* el) { if (ui.hover.button.handler) return; ui.hover.hover_slider = *(SliderHoverInfo*)gui_get_state(el); if (ui.hover.hover_slider.value == ui.hover.dragged_slider.value) { el->color = (GuiColor) { 0x2b, 0x2b, 0x2b, 0xff }; settings_applied = false; } else { el->color = (GuiColor) { 0x40, 0x40, 0x40, 0xff }; } } static void slider_button_on_hover(GuiElement* el) { if (ui.hover.button.handler) return; el->draw_type = DRAWTYPE_RECT; el->color = (GuiColor) { 0x60, 0x60, 0x60, 0xff }; el->draw_subtype = GUI_SUBTYPE_DEFAULT; ui.hover.button.handler = el->custom_data; } static void draw_slider(int min, int max, int* value) { gui_element_begin(gui); gui_set_grow(gui, DIRECTION_HORIZONTAL); gui_set_grow(gui, DIRECTION_VERTICAL); gui_set_direction(gui, DIRECTION_HORIZONTAL); gui_set_rect(gui, (GuiColor) { 0x30, 0x30, 0x30, 0xff }); gui_on_hover(gui, slider_on_hover); SliderHoverInfo info = (SliderHoverInfo) { .min = min, .max = max, .value = value, }; SliderHoverInfo* state = gui_set_state(gui, &info, sizeof(info)); gui_element_begin(gui); gui_set_grow(gui, DIRECTION_HORIZONTAL); gui_set_grow(gui, DIRECTION_VERTICAL); gui_set_direction(gui, DIRECTION_HORIZONTAL); gui_set_align(gui, ALIGN_LEFT, ALIGN_CENTER); if (IsShaderValid(assets.line_shader)) { gui_set_border(gui, (GuiColor) { 0x60, 0x60, 0x60, 0xff }, 2); gui_set_shader(gui, &assets.line_shader); } snprintf(state->value_str, 16, "%d", *state->value); gui_element_begin(gui); gui_on_hover(gui, slider_button_on_hover); gui_set_custom_data(gui, settings_on_left_slider_button_click); gui_set_grow(gui, DIRECTION_VERTICAL); gui_set_direction(gui, DIRECTION_HORIZONTAL); gui_set_align(gui, ALIGN_LEFT, ALIGN_CENTER); gui_image(gui, &assets.textures.button_arrow_left, BLOCK_IMAGE_SIZE, GUI_WHITE); gui_element_end(gui); gui_grow(gui, DIRECTION_HORIZONTAL); gui_text(gui, &assets.fonts.font_cond, state->value_str, config.ui_size * 0.6, GUI_WHITE); gui_grow(gui, DIRECTION_HORIZONTAL); gui_element_begin(gui); gui_on_hover(gui, slider_button_on_hover); gui_set_custom_data(gui, settings_on_right_slider_button_click); gui_set_grow(gui, DIRECTION_VERTICAL); gui_set_direction(gui, DIRECTION_HORIZONTAL); gui_set_align(gui, ALIGN_LEFT, ALIGN_CENTER); gui_image(gui, &assets.textures.button_arrow_right, BLOCK_IMAGE_SIZE, GUI_WHITE); gui_element_end(gui); gui_element_end(gui); gui_element_end(gui); } static void end_setting(void) { gui_element_end(gui); gui_element_end(gui); } static void text_input_on_hover(GuiElement* el) { if (ui.hover.button.handler) return; el->color = (GuiColor) { 0x40, 0x40, 0x40, 0xff }; } static void dropdown_input_on_hover(GuiElement* el) { if (ui.hover.button.handler) return; ui.hover.settings_dropdown_data = *(DropdownData*)gui_get_state(el); ui.hover.button.handler = settings_on_dropdown_click; if (el->color.r == 0x30) el->color = (GuiColor) { 0x40, 0x40, 0x40, 0xff }; } static void draw_dropdown_input(int* value, char** list, int list_len) { gui_element_begin(gui); gui_set_grow(gui, DIRECTION_HORIZONTAL); gui_set_grow(gui, DIRECTION_VERTICAL); gui_set_direction(gui, DIRECTION_HORIZONTAL); gui_set_rect(gui, (GuiColor) { 0x30, 0x30, 0x30, 0xff }); gui_on_hover(gui, dropdown_input_on_hover); DropdownData data = (DropdownData) { .value = value, .list = list, .list_len = list_len, }; gui_set_state(gui, &data, sizeof(data)); if ((int*)ui.dropdown.ref_object == value) { ui.dropdown.element = gui_get_element(gui); gui_set_rect(gui, (GuiColor) { 0x2b, 0x2b, 0x2b, 0xff }); } gui_element_begin(gui); gui_set_grow(gui, DIRECTION_HORIZONTAL); gui_set_grow(gui, DIRECTION_VERTICAL); gui_set_direction(gui, DIRECTION_HORIZONTAL); gui_set_align(gui, ALIGN_LEFT, ALIGN_CENTER); if (IsShaderValid(assets.line_shader)) { gui_set_border(gui, (GuiColor) { 0x60, 0x60, 0x60, 0xff }, 2); gui_set_shader(gui, &assets.line_shader); } gui_set_padding(gui, ELEMENT_GAP, 0); gui_set_scissor(gui); gui_grow(gui, DIRECTION_HORIZONTAL); gui_text(gui, &assets.fonts.font_cond, sgettext(list[*value]), config.ui_size * 0.6, GUI_WHITE); gui_grow(gui, DIRECTION_HORIZONTAL); gui_image(gui, &assets.textures.dropdown, BLOCK_IMAGE_SIZE, GUI_WHITE); gui_element_end(gui); gui_element_end(gui); } static void draw_text_input(char** input, const char* hint, int* scroll, bool editable, bool path_input) { gui_element_begin(gui); gui_set_grow(gui, DIRECTION_HORIZONTAL); gui_set_grow(gui, DIRECTION_VERTICAL); gui_set_direction(gui, DIRECTION_HORIZONTAL); gui_set_rect(gui, (GuiColor) { 0x30, 0x30, 0x30, 0xff }); gui_on_hover(gui, text_input_on_hover); gui_set_custom_data(gui, input); if (input == ui.hover.select_input) gui_set_rect(gui, (GuiColor) { 0x2b, 0x2b, 0x2b, 0xff }); gui_element_begin(gui); gui_set_grow(gui, DIRECTION_HORIZONTAL); gui_set_grow(gui, DIRECTION_VERTICAL); gui_set_direction(gui, DIRECTION_HORIZONTAL); gui_set_align(gui, ALIGN_LEFT, ALIGN_CENTER); if (IsShaderValid(assets.line_shader)) { gui_set_border(gui, (GuiColor) { 0x60, 0x60, 0x60, 0xff }, 2); gui_set_shader(gui, &assets.line_shader); } gui_set_padding(gui, ELEMENT_GAP, 0); gui_set_scroll(gui, scroll); gui_set_scissor(gui); if (editable) { InputHoverInfo info = (InputHoverInfo) { .input = input, .rel_pos = (Vector2) { ELEMENT_GAP + *scroll, 0 }, .font = &assets.fonts.font_cond, .font_size = config.ui_size * 0.6, }; gui_set_state(gui, &info, sizeof(info)); gui_on_hover(gui, input_on_hover); } draw_input_text(&assets.fonts.font_cond, input, hint, config.ui_size * 0.6, GUI_WHITE); gui_element_end(gui); gui_element_end(gui); if (path_input) draw_button(NULL, &assets.textures.icon_folder, settings_on_browse_button_click, input); } static void draw_button(const char* label, Texture2D* icon, ButtonClickHandler handler, void* data) { gui_element_begin(gui); gui_set_min_size(gui, 0, config.ui_size); gui_set_rect(gui, (GuiColor) { 0x30, 0x30, 0x30, 0xff }); gui_on_hover(gui, settings_button_on_hover); ButtonHoverInfo info = (ButtonHoverInfo) { .handler = handler, .data = data, }; gui_set_state(gui, &info, sizeof(info)); gui_element_begin(gui); gui_set_grow(gui, DIRECTION_HORIZONTAL); gui_set_grow(gui, DIRECTION_VERTICAL); gui_set_padding(gui, ELEMENT_GAP, 0); gui_set_gap(gui, ELEMENT_GAP/2); gui_set_direction(gui, DIRECTION_HORIZONTAL); gui_set_align(gui, ALIGN_LEFT, ALIGN_CENTER); if (IsShaderValid(assets.line_shader)) { gui_set_border(gui, (GuiColor) { 0x60, 0x60, 0x60, 0xff }, 2); gui_set_shader(gui, &assets.line_shader); } if (icon) gui_image(gui, icon, BLOCK_IMAGE_SIZE, GUI_WHITE); if (label) gui_text(gui, &assets.fonts.font_cond, label, config.ui_size * 0.6, GUI_WHITE); gui_element_end(gui); gui_element_end(gui); } static void toggle_on_hover(GuiElement* el) { el->color = (GuiColor) { 0x40, 0x40, 0x40, 0xff }; ui.hover.button.handler = settings_on_toggle_button_click; ui.hover.button.data = el->custom_data; } static void draw_toggle(bool* value) { gui_element_begin(gui); gui_set_border(gui, (GuiColor) { 0x30, 0x30, 0x30, 0xff }, 2); gui_element_begin(gui); gui_set_fixed(gui, config.ui_size * 2, config.ui_size); gui_set_rect(gui, (GuiColor) { 0x20, 0x20, 0x20, 0xff }); gui_set_direction(gui, DIRECTION_HORIZONTAL); gui_on_hover(gui, toggle_on_hover); gui_set_custom_data(gui, value); if (*value) gui_spacer(gui, config.ui_size, 0); gui_element_begin(gui); gui_set_fixed(gui, config.ui_size, config.ui_size); gui_set_rect(gui, *value ? (GuiColor) { 0x30, 0xff, 0x30, 0xff } : (GuiColor) { 0xff, 0x30, 0x30, 0xff }); gui_element_end(gui); gui_element_end(gui); gui_element_end(gui); } void handle_window(void) { if (window.is_hiding) { window.shown = false; window.is_hiding = false; } if (window.is_fading) { window.animation_time -= GetFrameTime() * 2.0; if (window.animation_time < 0.0) { window.animation_time = 0.0; if (window.shown) ui.render_surface_needs_redraw = true; window.shown = false; if (about_text_split) { for (size_t i = 0; i < vector_size(about_text_split); i++) { vector_free(about_text_split[i]); } vector_free(about_text_split); about_text_split = NULL; } } else { ui.render_surface_needs_redraw = true; } } else { window.shown = true; window.animation_time += GetFrameTime() * 2.0; if (window.animation_time > 1.0) { window.animation_time = 1.0; } else { ui.render_surface_needs_redraw = true; } } } void draw_settings_window(void) { static int font_path_scroll = 0; static int font_bold_path_scroll = 0; static int font_mono_path_scroll = 0; 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); begin_setting(gettext("Language"), true); draw_dropdown_input((int*)&window_config.language, language_list, ARRLEN(language_list)); end_setting(); begin_setting(gettext("UI size"), false); draw_slider(8, 64, &window_config.ui_size); end_setting(); begin_setting(gettext("FPS limit"), false); draw_slider(0, 240, &window_config.fps_limit); end_setting(); begin_setting(gettext("Font path"), false); draw_text_input(&window_config.font_path, gettext("path"), &font_path_scroll, true, true); end_setting(); begin_setting(gettext("Bold font path"), false); draw_text_input(&window_config.font_bold_path, gettext("path"), &font_bold_path_scroll, true, true); end_setting(); begin_setting(gettext("Monospaced font path"), false); draw_text_input(&window_config.font_mono_path, gettext("path"), &font_mono_path_scroll, true, true); end_setting(); begin_setting(gettext("Panel layout"), false); gui_element_begin(gui); gui_set_grow(gui, DIRECTION_HORIZONTAL); gui_set_grow(gui, DIRECTION_VERTICAL); gui_set_direction(gui, DIRECTION_HORIZONTAL); draw_button(gettext("Edit"), &assets.textures.button_edit, settings_on_panel_editor_button_click, NULL); gui_element_end(gui); end_setting(); begin_setting(gettext("Show block previews"), false); gui_element_begin(gui); gui_set_grow(gui, DIRECTION_HORIZONTAL); gui_set_grow(gui, DIRECTION_VERTICAL); gui_set_direction(gui, DIRECTION_HORIZONTAL); draw_toggle(&window_config.show_blockchain_previews); gui_element_end(gui); end_setting(); #ifdef DEBUG begin_setting(gettext("Show debug info"), false); gui_element_begin(gui); gui_set_grow(gui, DIRECTION_HORIZONTAL); gui_set_grow(gui, DIRECTION_VERTICAL); gui_set_direction(gui, DIRECTION_HORIZONTAL); draw_toggle(&editor.show_debug); gui_element_end(gui); end_setting(); #endif gui_grow(gui, DIRECTION_VERTICAL); gui_element_begin(gui); gui_set_grow(gui, DIRECTION_HORIZONTAL); gui_set_direction(gui, DIRECTION_HORIZONTAL); gui_set_min_size(gui, 0, config.ui_size * 0.6); gui_grow(gui, DIRECTION_HORIZONTAL); if (settings_applied) gui_text(gui, &assets.fonts.font_cond, gettext("Settings applied"), config.ui_size * 0.6, GUI_WHITE); gui_element_end(gui); gui_element_begin(gui); gui_set_grow(gui, DIRECTION_HORIZONTAL); gui_set_direction(gui, DIRECTION_HORIZONTAL); gui_set_gap(gui, ELEMENT_GAP); gui_grow(gui, DIRECTION_HORIZONTAL); draw_button(gettext("Reset panels"), NULL, settings_on_reset_panels_button_click, NULL); draw_button(gettext("Reset"), NULL, settings_on_reset_button_click, NULL); draw_button(gettext("Apply"), NULL, settings_on_apply_button_click, NULL); gui_element_end(gui); end_window(); if (settings_tooltip) { gui_element_begin(gui); gui_set_floating(gui); gui_set_rect(gui, (GuiColor) { 0x00, 0x00, 0x00, 0x80 }); gui_set_position(gui, gui->mouse_x + 10, gui->mouse_y + 10); gui_set_padding(gui, ELEMENT_GAP * 0.5, ELEMENT_GAP * 0.5); gui_text(gui, &assets.fonts.font_cond, gettext("Needs restart for changes to take effect"), config.ui_size * 0.6, GUI_WHITE); gui_element_end(gui); } settings_tooltip = false; } void draw_project_settings_window(void) { static int executable_name_scroll = 0; static int linker_name_scroll = 0; 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); begin_setting(gettext("Executable name"), false); draw_text_input(&project_config.executable_name, gettext("name"), &executable_name_scroll, true, false); end_setting(); begin_setting(gettext("Linker name (Linux only)"), false); draw_text_input(&project_config.linker_name, gettext("name"), &linker_name_scroll, true, false); end_setting(); gui_grow(gui, DIRECTION_VERTICAL); gui_element_begin(gui); gui_set_grow(gui, DIRECTION_HORIZONTAL); gui_set_direction(gui, DIRECTION_HORIZONTAL); gui_set_gap(gui, ELEMENT_GAP); gui_grow(gui, DIRECTION_HORIZONTAL); draw_button(gettext("Build!"), &assets.textures.button_build, project_settings_on_build_button_click, NULL); gui_element_end(gui); end_window(); } void draw_about_window(void) { if (!about_text_split) { scrap_log(LOG_INFO, "Split about text"); about_text_split = vector_create(); const char* about_text = gettext("Scrap is a project that allows anyone to build\n" "software using simple, block based interface."); size_t about_text_len = strlen(about_text); char* current_text = vector_create(); for (size_t i = 0; i < about_text_len; i++) { if (about_text[i] == '\n') { vector_add(&about_text_split, current_text); current_text = vector_create(); continue; } vector_add(¤t_text, about_text[i]); } vector_add(&about_text_split, current_text); } begin_window(gettext("About"), &assets.textures.icon_about, 500 * config.ui_size / 32.0, 0, window.animation_ease); gui_element_begin(gui); gui_set_direction(gui, DIRECTION_HORIZONTAL); gui_set_align(gui, ALIGN_LEFT, ALIGN_CENTER); gui_set_gap(gui, ELEMENT_GAP); gui_image(gui, &assets.textures.icon_logo, config.ui_size, GUI_WHITE); gui_text(gui, &assets.fonts.font_eb, "Scrap " SCRAP_VERSION, config.ui_size * 0.8, GUI_WHITE); gui_element_end(gui); gui_element_begin(gui); if (about_text_split) { for (size_t i = 0; i < vector_size(about_text_split); i++) { 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); } } else { gui_text(gui, &assets.fonts.font_cond, "ERROR", config.ui_size * 0.6, (GuiColor) { 0xff, 0x20, 0x20, 0xff }); } gui_element_end(gui); gui_grow(gui, DIRECTION_VERTICAL); gui_element_begin(gui); gui_set_grow(gui, DIRECTION_HORIZONTAL); gui_set_direction(gui, DIRECTION_HORIZONTAL); gui_set_gap(gui, ELEMENT_GAP); gui_grow(gui, DIRECTION_HORIZONTAL); draw_button(gettext("License"), &assets.textures.icon_file, about_on_license_button_click, NULL); gui_element_end(gui); end_window(); } void draw_save_confirmation_window(void) { begin_window(gettext("Confirm save"), &assets.textures.icon_about, 500 * config.ui_size / 32.0, 0, window.animation_ease); gui_text(gui, &assets.fonts.font_cond, gettext("Project is modified. Save the changes before quitting?"), config.ui_size * 0.6, GUI_WHITE); gui_grow(gui, DIRECTION_VERTICAL); gui_element_begin(gui); gui_set_grow(gui, DIRECTION_HORIZONTAL); gui_set_direction(gui, DIRECTION_HORIZONTAL); gui_set_gap(gui, ELEMENT_GAP); gui_grow(gui, DIRECTION_HORIZONTAL); draw_button(gettext("Yes"), NULL, save_confirmation_on_yes_button_click, NULL); draw_button(gettext("No"), NULL, save_confirmation_on_no_button_click, NULL); draw_button(gettext("Cancel"), NULL, save_confirmation_on_cancel_button_click, NULL); gui_element_end(gui); end_window(); } void draw_window(void) { if (!window.shown) return; window.animation_ease = ease_out_expo(window.animation_time); window.render(); } ================================================ FILE: translations/kk/LC_MESSAGES/scrap.po ================================================ msgid "" msgstr "" "Language: kz\n" "Content-Type: text/plain; charset=UTF-8\n" #: blocks.c msgid "Hello, scrap!" msgstr "Сәлем, scrap!" #: blocks.c msgid "Control" msgstr "Бақылау" #: blocks.c msgid "Terminal" msgstr "Терминал" #: blocks.c msgid "Math" msgstr "Математика" #: blocks.c msgid "Logic" msgstr "Логика" #: blocks.c msgid "Strings" msgstr "Жолдар" #: blocks.c msgid "Misc." msgstr "Әртүрлі." #: blocks.c msgid "Data" msgstr "Данные" #: blocks.c msgid "When" msgstr "Қашан" #: blocks.c msgid "clicked" msgstr "басылды" #: blocks.c msgid "Loop" msgstr "Цикл" #: blocks.c msgid "Repeat" msgstr "Қайталау" #: blocks.c msgid "times" msgstr "рет" #: blocks.c msgid "While" msgstr "Әзірге" #: blocks.c msgid "If" msgstr "Егер" #: blocks.c msgid ", then" msgstr ", содан кейін" #: blocks.c msgid "Else if" msgstr "Әйтпесе, егер" #: blocks.c msgid "Else" msgstr "Басқа" #: blocks.c msgid "Do nothing" msgstr "Ештеңе жасама" #: blocks.c msgid "Sleep" msgstr "Ұйқы" #: blocks.c msgid "μs" msgstr "мкс" #: blocks.c msgid "Define" msgstr "Анықтаңыз" #: blocks.c msgid "Return" msgstr "Қайту" #: blocks.c msgid "Get input" msgstr "Кіріс алыңыз" #: blocks.c msgid "Get char" msgstr "Шарды алыңыз" #: blocks.c msgid "Print" msgstr "Басып шығару" #: blocks.c msgid "Print line" msgstr "Басып шығару сызығы" #: blocks.c msgid "Cursor X" msgstr "Меңзер X" #: blocks.c msgid "Cursor Y" msgstr "Меңзер Y" #: blocks.c msgid "Terminal width" msgstr "Терминал ені" #: blocks.c msgid "Terminal height" msgstr "Терминал биіктігі" #: blocks.c msgid "Set cursor X:" msgstr "курсорын орнату X:" #: blocks.c msgid "Y:" msgstr "Y:" #: blocks.c msgid "Set text color" msgstr "Мәтін түсін орнату" #: blocks.c msgid "Set background color" msgstr "Фон түсін орнату" #: blocks.c msgid "Reset color" msgstr "Түсті қалпына келтіру" #: blocks.c msgid "Clear terminal" msgstr "Терминалды тазалау" #: blocks.c msgid "Set clear color" msgstr "Ашық түсті орнатыңыз" #: blocks.c msgid "Pow" msgstr "Дәреже" #: blocks.c msgid "Pi" msgstr "Пи" #: blocks.c msgid "Not" msgstr "Жоқ" #: blocks.c msgid "and" msgstr "және" #: blocks.c msgid "or" msgstr "немесе" #: blocks.c msgid "True" msgstr "Рас" #: blocks.c msgid "False" msgstr "Жалған" #: blocks.c msgid "Random" msgstr "Кездейсоқ" #: blocks.c msgid "to" msgstr "дейін" #: blocks.c msgid "Join" msgstr "Қосылыңыз" #: blocks.c msgid "left and " msgstr "солға және " #: blocks.c msgid "right" msgstr "дұрыс" #: blocks.c msgid "Ord" msgstr "Оrd" #: blocks.c msgid "Chr" msgstr "Chr" #: blocks.c msgid "Letter" msgstr "Хат" #: blocks.c msgid "in" msgstr "жылы" #: blocks.c msgid "Substring" msgstr "Ішкі жол" #: blocks.c msgid "Length" msgstr "Ұзындығы" #: blocks.c msgid "string" msgstr "жол" #: blocks.c msgid "Time since 1970" msgstr "1970 жылдан бері уақыт" #: blocks.c msgid "Int" msgstr "Бүтін сан" #: blocks.c msgid "Float" msgstr "Қалқымалы" #: blocks.c msgid "Str" msgstr "Кіші әріп" #: blocks.c msgid "Bool" msgstr "Булевое" #: blocks.c msgid "Nothing" msgstr "Ештеңе" #: blocks.c msgid "Declare" msgstr "Мәлімдеу" #: blocks.c msgid "my variable" msgstr "менің айнымалым" #: blocks.c msgid "Get" msgstr "Алу" #: blocks.c msgid "Set" msgstr "Орнату" #: blocks.c msgid "Empty list" msgstr "Бос тізім" #: blocks.c msgid "Add" msgstr "Қосу" #: blocks.c msgid "value" msgstr "мән" #: blocks.c msgid "get at" msgstr "жету" #: blocks.c msgid "set at" msgstr "орнатыңыз" #: blocks.c msgid "My block" msgstr "Менің блогым" #: render.c msgid "Scrap" msgstr "Scrap" #: input.c msgid "Code" msgstr "Код" #: input.c msgid "Output" msgstr "Шығару" #: input.c msgid "Block categories" msgstr "Код сәтті орындалды" #: input.c msgid "Block palette" msgstr "Блок палитрасы" #: render.c msgid "File" msgstr "Файл" #: render.c msgid "Settings" msgstr "Параметрлер" #: render.c msgid "About" msgstr "Туралы" #: scrap.c msgid "Vm executed successfully" msgstr "Код сәтті орындалды" #: scrap.c msgid "Vm stopped >:(" msgstr "Код тоқтатылды >:(" #: scrap.c msgid "Vm shitted and died :(" msgstr "Код бұзылды және өлді :(" #: input.c msgid "File load failed :(" msgstr "Файл жүктелмеді :(" #: input.c msgid "File load succeeded!" msgstr "Файл жүктелді!" #: input.c msgid "Jump to chain (%d/%d)" msgstr "Переход на цепочку (%d/%d)" #: render.c msgid "Start failed!" msgstr "Кодты бастау сәтсіз аяқталды!" #: render.c msgid "Started successfully!" msgstr "Код сәтті басталды!" #: render.c msgid "New project" msgstr "Жаңа жоба" #: render.c msgid "Save project" msgstr "Жобаны сақтау" #: render.c msgid "Load project" msgstr "Жобаны жүктеңіз" #: window.c msgid "UI size" msgstr "Пайдаланушы интерфейсінің өлшемі" #: window.c msgid "FPS limit" msgstr "FPS шегі" #: window.c msgid "Font path" msgstr "Қаріп жолы" #: window.c msgid "Bold font path" msgstr "Қалың шрифт жолы" #: window.c msgid "Monospaced font path" msgstr "Бір кеңістікті қаріп жолы" #: window.c msgid "Panel editor" msgstr "Панель редакторы" #: window.c msgid "Open" msgstr "Ашық" #: window.c msgid "Reset panels" msgstr "Панельдерді қалпына келтіру" #: window.c msgid "Reset" msgstr "Қалпына келтіру" #: window.c msgid "Apply" msgstr "Қолдану" #: window.c msgid "Needs restart for changes to take effect" msgstr "Өзгерістер күшіне енуі үшін қайта іске қосу қажет" #: window.c msgid "Scrap is a project that allows anyone to build\n" "software using simple, block based interface." msgstr "Scrap - бұл кез келген адамға салуға мүмкіндік беретін\n" "жоба қарапайым, блок негізіндегі интерфейсті\n" "пайдаланатын бағдарламалық қамтамасыз ету." #: window.c msgid "License" msgstr "Лицензия" #: render.c msgid "Panel edit mode" msgstr "Панельді өңдеу режимі" #: render.c msgid "Click on panels to reposition them" msgstr "Олардың орнын өзгерту үшін панельдерді басыңыз" #: render.c msgid "Drag panel edges to resize them" msgstr "Өлшемдерін өзгерту үшін панель жиектерін сүйреңіз" #: render.c msgid "Done" msgstr "Дайын" #: render.c msgid "Save" msgstr "Сақтау" ================================================ FILE: translations/ru/LC_MESSAGES/scrap.po ================================================ msgid "" msgstr "" "Language: ru\n" "Content-Type: text/plain; charset=UTF-8\n" #: blocks.c msgid "Hello, scrap!" msgstr "Привет, мусороид!" #: blocks.c msgid "Control" msgstr "Управление" #: blocks.c msgid "Terminal" msgstr "Терминал" #: blocks.c msgid "Math" msgstr "Математика" #: blocks.c msgid "Logic" msgstr "Логика" #: blocks.c msgid "Strings" msgstr "Строки" #: blocks.c msgid "Misc." msgstr "Другое" #: blocks.c msgid "Data" msgstr "Данные" #: blocks.c msgid "When" msgstr "Когда" #: blocks.c msgid "clicked" msgstr "нажат" #: blocks.c msgid "Loop" msgstr "Цикл" #: blocks.c msgid "Repeat" msgstr "Повторить" #: blocks.c msgid "times" msgstr "раз" #: blocks.c msgid "While" msgstr "Пока" #: blocks.c msgid "If" msgstr "Если" #: blocks.c msgid ", then" msgstr ", то" #: blocks.c msgid "Else if" msgstr "Иначе если" #: blocks.c msgid "Else" msgstr "Иначе" #: blocks.c msgid "Do nothing" msgstr "Делать ничего" #: blocks.c msgid "Sleep" msgstr "Ждать" #: blocks.c msgid "μs" msgstr "мкс" #: blocks.c msgid "Define" msgstr "Определить" #: blocks.c msgid "Return" msgstr "Вернуть" #: blocks.c msgid "Get input" msgstr "Получить ввод" #: blocks.c msgid "Get char" msgstr "Получить символ" #: blocks.c msgid "Print" msgstr "Напечатать" #: blocks.c msgid "Print line" msgstr "Напечатать линию" #: blocks.c msgid "Cursor X" msgstr "X курсора" #: blocks.c msgid "Cursor Y" msgstr "Y курсора" #: blocks.c msgid "Terminal width" msgstr "Ширина терминала" #: blocks.c msgid "Terminal height" msgstr "Высота терминала" #: blocks.c msgid "Set cursor X:" msgstr "Задать позицию курсора X:" #: blocks.c msgid "Y:" msgstr "Y:" #: blocks.c msgid "Set text color" msgstr "Задать цвет текста" #: blocks.c msgid "Set background color" msgstr "Задать цвет фона" #: blocks.c msgid "Reset color" msgstr "Сбросить цвет" #: blocks.c msgid "Clear terminal" msgstr "Очистить терминал" #: blocks.c msgid "Set clear color" msgstr "Задать цвет очистки" #: blocks.c msgid "Pow" msgstr "Степень" #: blocks.c msgid "Not" msgstr "Не" #: blocks.c msgid "and" msgstr "и" #: blocks.c msgid "or" msgstr "или" #: blocks.c msgid "True" msgstr "Истина" #: blocks.c msgid "False" msgstr "Ложь" #: blocks.c msgid "Random from" msgstr "Случайное целое от" #: blocks.c msgid "to" msgstr "до" #: blocks.c msgid "Join" msgstr "Склеить" #: blocks.c msgid "left and " msgstr "левое и " #: blocks.c msgid "right" msgstr "правое" #: blocks.c msgid "Ord" msgstr "Ord" #: blocks.c msgid "Chr" msgstr "Chr" #: blocks.c msgid "Letter" msgstr "Буква" #: blocks.c msgid "in" msgstr "в" #: blocks.c msgid "Substring" msgstr "Подстрока" #: blocks.c msgid "Length" msgstr "Длина" #: blocks.c msgid "string" msgstr "строка" #: blocks.c msgid "Time since 1970" msgstr "Время с 1970" #: blocks.c msgid "Int" msgstr "Целое" #: blocks.c msgid "Float" msgstr "Вещ-ое" #: blocks.c msgid "Str" msgstr "Строчное" #: blocks.c msgid "Bool" msgstr "Булевое" #: blocks.c msgid "Color" msgstr "Цвет" #: blocks.c msgid "Nothing" msgstr "Ничего" #: blocks.c msgid "Declare" msgstr "Объявить" #: blocks.c msgid "my variable" msgstr "моя переменная" #: blocks.c msgid "list" msgstr "список" #: blocks.c msgid "Collect garbage" msgstr "Собрать мусор" #: blocks.c msgid "Get" msgstr "Получить" #: blocks.c msgid "Set" msgstr "Задать" #: blocks.c msgid "Empty list" msgstr "Пустой список" #: blocks.c msgid "Add" msgstr "Добавить" #: blocks.c msgid "value" msgstr "значение" #: blocks.c msgid "get at" msgstr "получить по индексу" #: blocks.c msgid "set at" msgstr "задать по индексу" #: blocks.c msgid "My block" msgstr "Мой блок" #: blocks.c msgid "Type of" msgstr "Тип от" #: blocks.c msgid "Invalid data type %s, expected %s" msgstr "Неверный тип данных %s, ожидался %s" #: blocks.c msgid "Variable with name \"%s\" does not exist in the current scope" msgstr "Переменная с именем \"%s\" не существует в данном окружении" #: blocks.c msgid "Assign to variable \"%s\" of type %s with incompatible type %s" msgstr "Присвоение в переменную \"%s\" с типом %s значением несовместимого типа %s" #: blocks.c msgid "Variable declarations are not allowed inside an argument" msgstr "Объявления переменных запрещены внутри аргументов" #: blocks.c msgid "Cannot declare variable with empty name" msgstr "Нельзя объявлять переменную с пустым именем" #: blocks.c msgid "Tried to access index %d for list of length %d" msgstr "Попытка получить элемент по индексу %d для списка размером %d" #: blocks.c msgid "Division by zero" msgstr "Деление на ноль" #: blocks.c msgid "Invalid argument %s" msgstr "Неверный аргумент %s" #: blocks.c msgid "Unknown function id \"%s\"" msgstr "Неизвестный id функции\"%s\"" #: blocks.c msgid "Unknown argument id \"%s\"" msgstr "Неизвестный id аргумента\"%s\"" #: blocks.c msgid "Cannot represent %s as LLVM value" msgstr "Невозможно представить %s как значение LLVM" #: blocks.c msgid "Cannot cast type %s into %s" msgstr "Невозможно преобразовать тип %s в %s" #: blocks.c msgid "Cannot cast type %s into any string" msgstr "Невозможно преобразовать тип %s в any string" #: blocks.c msgid "Could not find function definition for argument" msgstr "Не удалось найти определение функции для аргумента" #: blocks.c msgid "Function argument block used outside of function" msgstr "Использование блока аргумента функции вне функции" #: blocks.c msgid "Too many parameters passed into function. Got %d/32" msgstr "Слишком много аргументов передано в функцию. Передано %d/32" #: blocks.c msgid "Invalid type %s in print function" msgstr "Неверный тип %s в функции печати" #: blocks.c msgid "Unhandled type %s in print function" msgstr "Необработанный тип %s в функции печати" #: blocks.c msgid "Cannot declare a variable with zero sized type (i.e. Nothing)" msgstr "Невозможно объявить переменную с типом нулевого размера (т.е. Ничего)" #: blocks.c msgid "at" msgstr "в" #: save.c msgid "System" msgstr "Система" #: input.c msgid "Code" msgstr "Код" #: input.c msgid "Output" msgstr "Вывод" #: input.c msgid "Block categories" msgstr "Категории блоков" #: input.c msgid "Block palette" msgstr "Палитра блоков" #: render.c msgid "Scrap" msgstr "Scrap" #: render.c msgid "File" msgstr "Файл" #: render.c msgid "Settings" msgstr "Настройки" #: render.c msgid "About" msgstr "О программе" #: render.c msgid "Jump to block" msgstr "Перейти к блоку" #: render.c msgid "Close" msgstr "Закрыть" #: scrap.c msgid "Vm executed successfully" msgstr "Код выполнен успешно" #: scrap.c msgid "Vm stopped >:(" msgstr "Код остановлен >:(" #: scrap.c msgid "Vm shitted and died :(" msgstr "Код высрал и сдох :(" #: input.c msgid "File load failed :(" msgstr "Загрузка файла не удалась :(" #: input.c msgid "File load succeeded!" msgstr "Загрузка файла удалась!" #: input.c msgid "Jump to chain (%d/%d)" msgstr "Переход на цепочку (%d/%d)" #: render.c msgid "Start failed!" msgstr "Запуск кода провалился!" #: render.c msgid "Started successfully!" msgstr "Код успешно запущен!" #: render.c msgid "New project" msgstr "Новый проект" #: render.c msgid "Save project" msgstr "Сохранить проект" #: render.c msgid "Load project" msgstr "Загрузить проект" #: window.c msgid "UI size" msgstr "Размер интерфейса" #: window.c msgid "FPS limit" msgstr "Ограничение FPS" #: window.c msgid "Font path" msgstr "Путь шрифта" #: window.c msgid "Bold font path" msgstr "Путь жирного шрифта" #: window.c msgid "Monospaced font path" msgstr "Путь моноширинного шрифта" #: window.c msgid "Panel layout" msgstr "Расположение панелей" #: window.c msgid "Open" msgstr "Открыть" #: window.c msgid "Reset panels" msgstr "Сбросить панели" #: window.c msgid "Reset" msgstr "Сбросить" #: window.c msgid "Apply" msgstr "Применить" #: window.c msgid "Needs restart for changes to take effect" msgstr "Требуется перезагрузка, чтобы изменения вступили в силу" #: window.c msgid "Scrap is a project that allows anyone to build\n" "software using simple, block based interface." msgstr "Scrap - это проект, который позволяет любому\n" "создавать софт, используя простой, блочный интерфейс" #: window.c msgid "License" msgstr "Лицензия" #: window.c msgid "Build settings" msgstr "Настройки сборки" #: window.c msgid "Executable name" msgstr "Имя приложения" #: window.c msgid "Linker name (Linux only)" msgstr "Имя линковщика (только для Linux)" #: window.c msgid "Build!" msgstr "Собрать!" #: window.c msgid "Edit" msgstr "Изменить" #: render.c msgid "Panel edit mode" msgstr "Режим редактирования панелей" #: render.c msgid "Click on panels to reposition them" msgstr "Нажимайте на панели чтобы их передвигать" #: render.c msgid "Drag panel edges to resize them" msgstr "Перетягивайте края панелей, чтобы изменить размер" #: render.c msgid "Done" msgstr "Готово" #: render.c msgid "Save" msgstr "Сохранить" #: render.c msgid "Got compiler error!" msgstr "Ошибка компиляции!" #: blocks.c msgid "any" msgstr "любое" #: blocks.c msgid "cond." msgstr "усл." #: blocks.c msgid "Abc" msgstr "Абв" #: blocks.c msgid "Conditionals" msgstr "Условия" #: blocks.c msgid "Loops" msgstr "Циклы" #: blocks.c msgid "Functions" msgstr "Функции" #: blocks.c msgid "Input/Output" msgstr "Ввод/Вывод" #: blocks.c msgid "Cursor" msgstr "Курсор" #: blocks.c msgid "Colors" msgstr "Цвета" #: blocks.c msgid "Comparisons" msgstr "Сравнения" #: blocks.c msgid "Boolean math" msgstr "Булевая математика" #: blocks.c msgid "Bitwise math" msgstr "Побитовые операторы" #: blocks.c msgid "Variables" msgstr "Переменные" #: blocks.c msgid "Lists" msgstr "Списки" #: blocks.c msgid "Type casting" msgstr "Преобразование типов" #: blocks.c msgid "Doing nothing" msgstr "Уголок бездельничества" #: blocks.c msgid "Debug blocks" msgstr "Дебаг-блоки" #: window.c msgid "path" msgstr "путь" #: window.c msgid "Language" msgstr "Язык" #: window.c msgid "Show block previews" msgstr "Показывать предпросмотр блоков" #: window.c msgid "Show debug info" msgstr "Показывать отладочную информацию" #: window.c msgid "Settings applied" msgstr "Настройки применены" #: window.c msgid "Confirm save" msgstr "Подтвердите сохранение" #: window.c msgid "Project is modified. Save the changes before quitting?" msgstr "Проект изменён. Сохранить изменения перед выходом?" #: window.c msgid "Yes" msgstr "Да" #: window.c msgid "No" msgstr "Нет" #: window.c msgid "Cancel" msgstr "Отмена" #: save.c msgid "LanguageList|System" msgstr "Системный" #: save.c msgid "English [en]" msgstr "Английский [en]" #: save.c msgid "Russian [ru]" msgstr "Русский [ru]" #: save.c msgid "Kazakh [kk]" msgstr "Казахский [kk]" #: save.c msgid "Ukrainian [uk]" msgstr "Украинский [uk]" #: save.c msgid "UNKNOWN %s" msgstr "НЕИЗВЕСТНЫЙ %s" #: compiler.c msgid "Control stack overflow" msgstr "Переполнение стека контроля" #: compiler.c msgid "Variable stack overflow" msgstr "Переполнение стека переменных" #: compiler.c msgid "Tried to compile block without definition" msgstr "Попытка скомпилировать блок без определения" #: compiler.c msgid "Tried to compile block \"%s\" without implementation" msgstr "Попытка скомпилировать блок \"%s\" без реализации" #: compiler.c msgid "Function with name \"%s\" does not exist" msgstr "Функции с именем \"%s\" не существует" #: compiler.c msgid "Failed to build module: %s" msgstr "Не удалось собрать модуль: %s" #: interpreter.c msgid "Tried to execute block without definition" msgstr "Попытка выполнить блок без определения" #: interpreter.c msgid "Tried to execute block \"%s\" without implementation" msgstr "Попытка выполнить блок \"%s\" без реализации" #: platform.c msgid "Failed to create a pipe. Error code: %ld" msgstr "Не удалось создать канал. Код ошибки: %ld" #: platform.c msgid "Failed to create a process. Error code: %ld" msgstr "Не удалось создать процесс. Код ошибки: %ld" #: platform.c msgid "Failed to read from pipe. Error code: %ld" msgstr "Не удалось прочитать данные из канала. Код ошибки: %ld" #: platform.c msgid "Failed to get exit code. Error code: %ld" msgstr "Не удалось получить код выхода. Код ошибки: %ld" #: platform.c msgid "Linker exited with exit code: %ld" msgstr "Линковщик завершился с кодом ошибки: %ld" #: platform.c msgid "Failed to fork a process: %s" msgstr "Не удалось форкнуть процесс: %s" #: platform.c msgid "Linker signaled with signal number: %d" msgstr "Линковщик завершился с кодом сигнала: %d" #: platform.c msgid "Received unknown child status :/" msgstr "Получен неизвестный статус от дочернего процесса :/" #: platform.c msgid " 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" msgstr " не установлен в системе или установлен неправильно. Пожалуйста, установите его и убедитесь что путь до него добавлен в переменную среды PATH, чтобы можно было собирать проекты" ================================================ FILE: translations/uk/LC_MESSAGES/scrap.po ================================================ msgid "" msgstr "" "Language: uk\n" "Content-Type: text/plain; charset=UTF-8\n" #: blocks.c msgid "Hello, scrap!" msgstr "Привіт, сміттеїде!" #: blocks.c msgid "Control" msgstr "Керування" #: blocks.c msgid "Terminal" msgstr "Термінал" #: blocks.c msgid "Math" msgstr "Математика" #: blocks.c msgid "Logic" msgstr "Логіка" #: blocks.c msgid "Strings" msgstr "Рядки" #: blocks.c msgid "Misc." msgstr "Інше" #: blocks.c msgid "Data" msgstr "Дані" #: blocks.c msgid "When" msgstr "Коли" #: blocks.c msgid "clicked" msgstr "натиснутий" #: blocks.c msgid "Loop" msgstr "Цикл" #: blocks.c msgid "Repeat" msgstr "Повторити" #: blocks.c msgid "times" msgstr "разів" #: blocks.c msgid "While" msgstr "Поки" #: blocks.c msgid "If" msgstr "Якщо" #: blocks.c msgid ", then" msgstr ", тоді" #: blocks.c msgid "Else if" msgstr "Інакше якщо" #: blocks.c msgid "Else" msgstr "Інакше" #: blocks.c msgid "Do nothing" msgstr "Нічого не робити" #: blocks.c msgid "Sleep" msgstr "Чекати" #: blocks.c msgid "μs" msgstr "мкс" #: blocks.c msgid "Define" msgstr "Визначити" #: blocks.c msgid "Return" msgstr "Повернути" #: blocks.c msgid "Get input" msgstr "Отримати ввід" #: blocks.c msgid "Get char" msgstr "Отримати символ" #: blocks.c msgid "Print" msgstr "Вивести" #: blocks.c msgid "Print line" msgstr "Вивести лінію" #: blocks.c msgid "Cursor X" msgstr "X курсора" #: blocks.c msgid "Cursor Y" msgstr "Y курсора" #: blocks.c msgid "Terminal width" msgstr "Ширина терміналу" #: blocks.c msgid "Terminal height" msgstr "Висота терміналу" #: blocks.c msgid "Set cursor X:" msgstr "Задати позицію курсора X:" #: blocks.c msgid "Y:" msgstr "Y:" #: blocks.c msgid "Set text color" msgstr "Задати колір тексту" #: blocks.c msgid "Set background color" msgstr "Задати колір фону" #: blocks.c msgid "Reset color" msgstr "Скинути колір" #: blocks.c msgid "Clear terminal" msgstr "Очистити термінал" #: blocks.c msgid "Set clear color" msgstr "Задати колір очистки" #: blocks.c msgid "Pow" msgstr "Степінь" #: blocks.c msgid "Not" msgstr "Не" #: blocks.c msgid "and" msgstr "і" #: blocks.c msgid "or" msgstr "або" #: blocks.c msgid "True" msgstr "Істина" #: blocks.c msgid "False" msgstr "Хибний" #: blocks.c msgid "Random" msgstr "Випадкове ціле від" #: blocks.c msgid "to" msgstr "до" #: blocks.c msgid "Join" msgstr "З'єднати" #: blocks.c msgid "left and " msgstr "ліве і " #: blocks.c msgid "right" msgstr "праве" #: blocks.c msgid "Ord" msgstr "Ord" #: blocks.c msgid "Chr" msgstr "Chr" #: blocks.c msgid "Letter" msgstr "Буква" #: blocks.c msgid "in" msgstr "у" #: blocks.c msgid "Substring" msgstr "Підрядок" #: blocks.c msgid "Length" msgstr "Довжина" #: blocks.c msgid "string" msgstr "рядок" #: blocks.c msgid "Time since 1970" msgstr "Час після 1970" #: blocks.c msgid "Int" msgstr "Ціле" #: blocks.c msgid "Float" msgstr "Речове" #: blocks.c msgid "Str" msgstr "Рядкове" #: blocks.c msgid "Bool" msgstr "Булеве" #: blocks.c msgid "Nothing" msgstr "Нічого" #: blocks.c msgid "Declare" msgstr "Оголосити" #: blocks.c msgid "my variable" msgstr "моя змінна" #: blocks.c msgid "Get" msgstr "Отримати" #: blocks.c msgid "Set" msgstr "Надати" #: blocks.c msgid "Empty list" msgstr "Порожній список" #: blocks.c msgid "Add" msgstr "Додати" #: blocks.c msgid "value" msgstr "значення" #: blocks.c msgid "get at" msgstr "отримати по індексу" #: blocks.c msgid "set at" msgstr "задати по індексу" #: blocks.c msgid "My block" msgstr "Мій блок" #: render.c msgid "Scrap" msgstr "Scrap" #: input.c msgid "Code" msgstr "Код" #: input.c msgid "Output" msgstr "Вивід" #: input.c msgid "Block categories" msgstr "Категорії блоків" #: input.c msgid "Block palette" msgstr "Палітра блоків" #: render.c msgid "File" msgstr "Файл" #: render.c msgid "Settings" msgstr "Налаштування" #: render.c msgid "About" msgstr "Про програму" #: scrap.c msgid "Vm executed successfully" msgstr "Код виконаний успішно" #: scrap.c msgid "Vm stopped >:(" msgstr "Код зупинений >:(" #: scrap.c msgid "Vm shitted and died :(" msgstr "Код не витримав навантаження та помер :(" #: input.c msgid "File load failed :(" msgstr "Завантаження файлу не вдалось :(" #: input.c msgid "File load succeeded!" msgstr "Завантаження файлу вдалось!" #: input.c msgid "Jump to chain (%d/%d)" msgstr "Перехід на ланцюжок (%d/%d)" #: render.c msgid "Start failed!" msgstr "Запуск коду провалений!" #: render.c msgid "Started successfully!" msgstr "Код успішно запущений!" #: render.c msgid "New project" msgstr "Новий проект" #: render.c msgid "Save project" msgstr "Зберегти проект" #: render.c msgid "Load project" msgstr "Завантажити проект" #: window.c msgid "UI size" msgstr "Розмір інтерфейсу" #: window.c msgid "FPS limit" msgstr "Обмеження FPS" #: window.c msgid "Font path" msgstr "Шлях шрифта" #: window.c msgid "Bold font path" msgstr "Шлях жирного шрифта" #: window.c msgid "Monospaced font path" msgstr "Шлях моноширинного шрифта" #: window.c msgid "Panel editor" msgstr "Редактор панелей" #: window.c msgid "Open" msgstr "Відкрити" #: window.c msgid "Reset panels" msgstr "Скидання панелей" #: window.c msgid "Reset" msgstr "Скинути" #: window.c msgid "Apply" msgstr "Застосувати" #: window.c msgid "Needs restart for changes to take effect" msgstr "Потрібне перезавантаження, щоб зміни вступили у силу" #: window.c msgid "Scrap is a project that allows anyone to build\n" "software using simple, block based interface." msgstr "Scrap - це проект, який надає можливість кожному\n" "створювати програми, використовуючи легкий,\n" "блоковий інтерфейс" #: window.c msgid "License" msgstr "Ліцензія" #: render.c msgid "Panel edit mode" msgstr "Режим редагування панелей" #: render.c msgid "Click on panels to reposition them" msgstr "Натискайте на панелі щоб переміщати їх" #: render.c msgid "Drag panel edges to resize them" msgstr "Переміщайте края панелей, щоб змінити розмір" #: render.c msgid "Done" msgstr "Готово" #: render.c msgid "Save" msgstr "Зберегти" #: blocks.c msgid "any" msgstr "будь-яке" #: blocks.c msgid "cond." msgstr "умова" #: blocks.c msgid "Abc" msgstr "Абв" #: window.c msgid "path" msgstr "шлях" #: window.c msgid "Language" msgstr "Мова" #: save.c msgid "System" msgstr "Системна" #: save.c msgid "English [en]" msgstr "Англійська [en]" #: save.c msgid "Russian [ru]" msgstr "Російська [ru]" #: save.c msgid "Kazakh [kk]" msgstr "Казахська [kk]" #: save.c msgid "Ukrainian [uk]" msgstr "Українська [uk]"