diff --git a/.github/workflows/build-examples.yml b/.github/workflows/build-examples.yml index 5aa83d426..e0511e532 100644 --- a/.github/workflows/build-examples.yml +++ b/.github/workflows/build-examples.yml @@ -77,11 +77,6 @@ jobs: echo "✅ Solid dist copied" fi - if [ -d "npm-packages/vue-dist" ]; then - cp -r npm-packages/vue-dist packages/vue/dist - echo "✅ Vue dist copied" - fi - echo "" echo "Package extraction complete:" ls -lah packages/core/dist/ diff --git a/.github/workflows/build-native.yml b/.github/workflows/build-native.yml index 40ae24475..bc63283b4 100644 --- a/.github/workflows/build-native.yml +++ b/.github/workflows/build-native.yml @@ -165,13 +165,6 @@ jobs: echo "⚠️ Solid dist not found (skipping)" fi - if [ -d packages/vue/dist ]; then - cp -r packages/vue/dist artifacts/npm-packages/vue-dist - echo "✅ Vue dist packaged" - else - echo "⚠️ Vue dist not found (skipping)" - fi - cd artifacts zip -r npm-packages.zip npm-packages/ test -f npm-packages.zip || (echo "❌ Failed to create npm-packages zip" && exit 1) diff --git a/.github/workflows/npm-latest-release.yml b/.github/workflows/npm-latest-release.yml index 9ca34c622..427f75b42 100644 --- a/.github/workflows/npm-latest-release.yml +++ b/.github/workflows/npm-latest-release.yml @@ -72,11 +72,6 @@ jobs: echo "Copied solid dist" fi - if [ -d "npm-packages/vue-dist" ]; then - cp -r npm-packages/vue-dist packages/vue/dist - echo "Copied vue dist" - fi - echo "Package extraction complete" - name: Publish packages (dry-run) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 155afce64..3b7e10cf5 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -66,15 +66,9 @@ jobs: TAG_VERSION="${{ needs.prepare.outputs.version }}" echo "Validating version: $TAG_VERSION" - # Check packages/*/package.json versions (excluding vue by default) + # Check packages/*/package.json versions FAILED=false for pkg in packages/*/; do - # Skip vue package - if [[ "$pkg" == "packages/vue/" ]]; then - echo "Skipping vue package version check" - continue - fi - if [ -f "$pkg/package.json" ]; then PKG_VERSION=$(node -p "require('./$pkg/package.json').version") if [ "$PKG_VERSION" != "$TAG_VERSION" ]; then diff --git a/README.md b/README.md index 19f9c5938..908363794 100644 --- a/README.md +++ b/README.md @@ -21,8 +21,6 @@ This monorepo contains the following packages: - [`@opentui/core`](packages/core) - The core library works completely standalone, providing an imperative API and all the primitives. - [`@opentui/solid`](packages/solid) - The SolidJS reconciler for OpenTUI. - [`@opentui/react`](packages/react) - The React reconciler for OpenTUI. -- [`@opentui/vue`](packages/vue) - The Vue reconciler (unmaintained) -- [`@opentui/go`](packages/go) - Go bindings (unmaintained) ## Install diff --git a/bun.lock b/bun.lock index 3d6bd5bdb..d6d3ef009 100644 --- a/bun.lock +++ b/bun.lock @@ -90,23 +90,6 @@ "solid-js": "1.9.9", }, }, - "packages/vue": { - "name": "@opentui/vue", - "version": "0.1.25", - "dependencies": { - "@opentui/core": "workspace:*", - "@vue/runtime-core": "^3.5.18", - }, - "devDependencies": { - "bun-plugin-vue3": "^1.0.0-beta.2", - "typescript": "^5", - "vue": "^3.5.18", - }, - "peerDependencies": { - "typescript": "^5", - "vue": "^3.5.18", - }, - }, }, "packages": { "@ampproject/remapping": ["@ampproject/remapping@2.3.0", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw=="], @@ -241,8 +224,6 @@ "@opentui/solid": ["@opentui/solid@workspace:packages/solid"], - "@opentui/vue": ["@opentui/vue@workspace:packages/vue"], - "@tokenizer/token": ["@tokenizer/token@0.3.0", "", {}, "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A=="], "@tweenjs/tween.js": ["@tweenjs/tween.js@23.1.3", "", {}, "sha512-vJmvvwFxYuGnF2axRtPYocag6Clbb5YS7kLL+SO/TeVFzHqDIWrNKYtcsPMibjDx9O+bu+psAy9NKfWklassUA=="], @@ -271,24 +252,6 @@ "@types/ws": ["@types/ws@8.18.1", "", { "dependencies": { "@types/node": "*" } }, "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg=="], - "@vue/compiler-core": ["@vue/compiler-core@3.5.26", "", { "dependencies": { "@babel/parser": "^7.28.5", "@vue/shared": "3.5.26", "entities": "^7.0.0", "estree-walker": "^2.0.2", "source-map-js": "^1.2.1" } }, "sha512-vXyI5GMfuoBCnv5ucIT7jhHKl55Y477yxP6fc4eUswjP8FG3FFVFd41eNDArR+Uk3QKn2Z85NavjaxLxOC19/w=="], - - "@vue/compiler-dom": ["@vue/compiler-dom@3.5.26", "", { "dependencies": { "@vue/compiler-core": "3.5.26", "@vue/shared": "3.5.26" } }, "sha512-y1Tcd3eXs834QjswshSilCBnKGeQjQXB6PqFn/1nxcQw4pmG42G8lwz+FZPAZAby6gZeHSt/8LMPfZ4Rb+Bd/A=="], - - "@vue/compiler-sfc": ["@vue/compiler-sfc@3.5.26", "", { "dependencies": { "@babel/parser": "^7.28.5", "@vue/compiler-core": "3.5.26", "@vue/compiler-dom": "3.5.26", "@vue/compiler-ssr": "3.5.26", "@vue/shared": "3.5.26", "estree-walker": "^2.0.2", "magic-string": "^0.30.21", "postcss": "^8.5.6", "source-map-js": "^1.2.1" } }, "sha512-egp69qDTSEZcf4bGOSsprUr4xI73wfrY5oRs6GSgXFTiHrWj4Y3X5Ydtip9QMqiCMCPVwLglB9GBxXtTadJ3mA=="], - - "@vue/compiler-ssr": ["@vue/compiler-ssr@3.5.26", "", { "dependencies": { "@vue/compiler-dom": "3.5.26", "@vue/shared": "3.5.26" } }, "sha512-lZT9/Y0nSIRUPVvapFJEVDbEXruZh2IYHMk2zTtEgJSlP5gVOqeWXH54xDKAaFS4rTnDeDBQUYDtxKyoW9FwDw=="], - - "@vue/reactivity": ["@vue/reactivity@3.5.26", "", { "dependencies": { "@vue/shared": "3.5.26" } }, "sha512-9EnYB1/DIiUYYnzlnUBgwU32NNvLp/nhxLXeWRhHUEeWNTn1ECxX8aGO7RTXeX6PPcxe3LLuNBFoJbV4QZ+CFQ=="], - - "@vue/runtime-core": ["@vue/runtime-core@3.5.26", "", { "dependencies": { "@vue/reactivity": "3.5.26", "@vue/shared": "3.5.26" } }, "sha512-xJWM9KH1kd201w5DvMDOwDHYhrdPTrAatn56oB/LRG4plEQeZRQLw0Bpwih9KYoqmzaxF0OKSn6swzYi84e1/Q=="], - - "@vue/runtime-dom": ["@vue/runtime-dom@3.5.26", "", { "dependencies": { "@vue/reactivity": "3.5.26", "@vue/runtime-core": "3.5.26", "@vue/shared": "3.5.26", "csstype": "^3.2.3" } }, "sha512-XLLd/+4sPC2ZkN/6+V4O4gjJu6kSDbHAChvsyWgm1oGbdSO3efvGYnm25yCjtFm/K7rrSDvSfPDgN1pHgS4VNQ=="], - - "@vue/server-renderer": ["@vue/server-renderer@3.5.26", "", { "dependencies": { "@vue/compiler-ssr": "3.5.26", "@vue/shared": "3.5.26" }, "peerDependencies": { "vue": "3.5.26" } }, "sha512-TYKLXmrwWKSodyVuO1WAubucd+1XlLg4set0YoV+Hu8Lo79mp/YMwWV5mC5FgtsDxX3qo1ONrxFaTP1OQgy1uA=="], - - "@vue/shared": ["@vue/shared@3.5.26", "", {}, "sha512-7Z6/y3uFI5PRoKeorTOSXKcDj0MSasfNNltcslbFrPpcw6aXRUALq4IfJlaTRspiWIUOEZbrpM+iQGmCOiWe4A=="], - "@webgpu/types": ["@webgpu/types@0.1.68", "", {}, "sha512-3ab1B59Ojb6RwjOspYLsTpCzbNB3ZaamIAxBMmvnNkiDoLTZUOBXZ9p5nAYVEkQlDdf6qAZWi1pqj9+ypiqznA=="], "abort-controller": ["abort-controller@3.0.0", "", { "dependencies": { "event-target-shim": "^5.0.0" } }, "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg=="], @@ -319,8 +282,6 @@ "bun-ffi-structs": ["bun-ffi-structs@0.1.2", "", { "peerDependencies": { "typescript": "^5" } }, "sha512-Lh1oQAYHDcnesJauieA4UNkWGXY9hYck7OA5IaRwE3Bp6K2F2pJSNYqq+hIy7P3uOvo3km3oxS8304g5gDMl/w=="], - "bun-plugin-vue3": ["bun-plugin-vue3@1.0.0-beta.2", "", { "peerDependencies": { "vue": ">=3.0.0" } }, "sha512-FBBt+2yFrklOLtXzL79bwaohyjsXc/UUMfFVANolAT7kYnpvI1KgzmOJyS0ubB4jAuW81/cR6aWV+oIC3j5+3A=="], - "bun-types": ["bun-types@1.3.5", "", { "dependencies": { "@types/node": "*" } }, "sha512-inmAYe2PFLs0SUbFOWSVD24sg1jFlMPxOjOSSCYqUgn4Hsc3rDc7dFvfVYjFPNHtov6kgUeulV4SxbuIV/stPw=="], "bun-webgpu": ["bun-webgpu@0.1.4", "", { "dependencies": { "@webgpu/types": "^0.1.60" }, "optionalDependencies": { "bun-webgpu-darwin-arm64": "^0.1.4", "bun-webgpu-darwin-x64": "^0.1.4", "bun-webgpu-linux-x64": "^0.1.4", "bun-webgpu-win32-x64": "^0.1.4" } }, "sha512-Kw+HoXl1PMWJTh9wvh63SSRofTA8vYBFCw0XEP1V1fFdQEDhI8Sgf73sdndE/oDpN/7CMx0Yv/q8FCvO39ROMQ=="], @@ -351,8 +312,6 @@ "escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="], - "estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="], - "event-target-shim": ["event-target-shim@5.0.1", "", {}, "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ=="], "events": ["events@3.3.0", "", {}, "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q=="], @@ -401,8 +360,6 @@ "lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="], - "magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="], - "meshoptimizer": ["meshoptimizer@0.18.1", "", {}, "sha512-ZhoIoL7TNV4s5B6+rx5mC//fw8/POGyNxS/DZyCJeiZ12ScLfVwRE/GfsxwiTkMYYD5DmK2/JXnEVXqL4rF+Sw=="], "mime": ["mime@3.0.0", "", { "bin": { "mime": "cli.js" } }, "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A=="], @@ -413,8 +370,6 @@ "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], - "nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="], - "node-releases": ["node-releases@2.0.27", "", {}, "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA=="], "omggif": ["omggif@1.0.10", "", {}, "sha512-LMJTtvgc/nugXj0Vcrrs68Mn2D1r0zf630VNtqtpI1FEO7e+O9FP4gqs9AcnBaSEeoHIPm28u6qgPR0oyEpGSw=="], @@ -453,8 +408,6 @@ "pngjs": ["pngjs@7.0.0", "", {}, "sha512-LKWqWJRhstyYo9pGvgor/ivk2w94eSjE3RGVuzLGlr3NmD8bf7RcYGze1mNdEHRP6TRP6rMuDHk5t44hnTRyow=="], - "postcss": ["postcss@8.5.6", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg=="], - "prettier": ["prettier@3.6.2", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ=="], "process": ["process@0.11.10", "", {}, "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A=="], @@ -493,8 +446,6 @@ "solid-js": ["solid-js@1.9.9", "", { "dependencies": { "csstype": "^3.1.0", "seroval": "~1.3.0", "seroval-plugins": "~1.3.0" } }, "sha512-A0ZBPJQldAeGCTW0YRYJmt7RCeh5rbFfPZ2aOttgYnctHE7HgKeHCBB/PVc2P7eOfmNXqMFFFoYYdm3S4dcbkA=="], - "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], - "stage-js": ["stage-js@1.0.0-alpha.17", "", {}, "sha512-AzlMO+t51v6cFvKZ+Oe9DJnL1OXEH5s9bEy6di5aOrUpcP7PCzI/wIeXF0u3zg0L89gwnceoKxrLId0ZpYnNXw=="], "string_decoder": ["string_decoder@1.3.0", "", { "dependencies": { "safe-buffer": "~5.2.0" } }, "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA=="], @@ -517,8 +468,6 @@ "utif2": ["utif2@4.1.0", "", { "dependencies": { "pako": "^1.0.11" } }, "sha512-+oknB9FHrJ7oW7A2WZYajOcv4FcDR4CfoGB0dPNfxbi4GO05RRnFmt5oa23+9w32EanrYcSJWspUiJkLMs+37w=="], - "vue": ["vue@3.5.26", "", { "dependencies": { "@vue/compiler-dom": "3.5.26", "@vue/compiler-sfc": "3.5.26", "@vue/runtime-dom": "3.5.26", "@vue/server-renderer": "3.5.26", "@vue/shared": "3.5.26" }, "peerDependencies": { "typescript": "*" }, "optionalPeers": ["typescript"] }, "sha512-SJ/NTccVyAoNUJmkM9KUqPcYlY+u8OVL1X5EW9RIs3ch5H2uERxyyIUI4MRxVCSOiEcupX9xNGde1tL9ZKpimA=="], - "web-tree-sitter": ["web-tree-sitter@0.25.10", "", { "peerDependencies": { "@types/emscripten": "^1.40.0" }, "optionalPeers": ["@types/emscripten"] }, "sha512-Y09sF44/13XvgVKgO2cNDw5rGk6s26MgoZPXLESvMXeefBf7i6/73eFurre0IsTW6E14Y0ArIzhUMmjoc7xyzA=="], "ws": ["ws@8.18.3", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg=="], @@ -535,8 +484,6 @@ "zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], - "@vue/compiler-core/entities": ["entities@7.0.0", "", {}, "sha512-FDWG5cmEYf2Z00IkYRhbFrwIwvdFKH07uV8dvNy0omp/Qb1xcyCWp2UDtcwJF4QZZvk0sLudP6/hAu42TaqVhQ=="], - "babel-plugin-jsx-dom-expressions/@babel/helper-module-imports": ["@babel/helper-module-imports@7.18.6", "", { "dependencies": { "@babel/types": "^7.18.6" } }, "sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA=="], "image-q/@types/node": ["@types/node@16.9.1", "", {}, "sha512-QpLcX9ZSsq3YYUUnD3nFDY8H7wctAhQj/TFKL8Ya8v5fMm3CFXxo8zStsLAl780ltoYoo1WvKUVGBQK+1ifr7g=="], diff --git a/install.sh b/install.sh deleted file mode 100755 index 4bda8a7ad..000000000 --- a/install.sh +++ /dev/null @@ -1,150 +0,0 @@ -#!/bin/bash -set -e - -# OpenTUI System Installation Script -# Installs OpenTUI headers and libraries system-wide - -REPO="sst/opentui" -RELEASE_URL="https://github.com/$REPO/releases/latest/download" - -# Colors for output -RED='\033[0;31m' -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -NC='\033[0m' # No Color - -echo -e "${GREEN}OpenTUI System Installer${NC}" -echo "Installing OpenTUI headers and libraries..." - -# Detect platform -OS=$(uname -s | tr '[:upper:]' '[:lower:]') -ARCH=$(uname -m) - -case "$ARCH" in - x86_64|amd64) ARCH="x86_64" ;; - arm64|aarch64) ARCH="aarch64" ;; - *) echo -e "${RED}Error: Unsupported architecture: $ARCH${NC}"; exit 1 ;; -esac - -case "$OS" in - darwin) OS="macos" ;; - linux) OS="linux" ;; - mingw*|cygwin*|msys*) OS="windows" ;; - *) echo -e "${RED}Error: Unsupported OS: $OS${NC}"; exit 1 ;; -esac - -PLATFORM="${ARCH}-${OS}" -echo "Detected platform: $PLATFORM" - -# Determine installation paths -if [[ "$OS" == "macos" ]]; then - INCLUDE_DIR="/opt/homebrew/include" - LIB_DIR="/opt/homebrew/lib" - if [[ ! -d "$INCLUDE_DIR" ]]; then - INCLUDE_DIR="/usr/local/include" - LIB_DIR="/usr/local/lib" - fi -else - INCLUDE_DIR="/usr/local/include" - LIB_DIR="/usr/local/lib" -fi - -echo "Installation paths:" -echo " Headers: $INCLUDE_DIR" -echo " Libraries: $LIB_DIR" - -# Check for sudo if needed -if [[ ! -w "$INCLUDE_DIR" ]] || [[ ! -w "$LIB_DIR" ]]; then - if [[ $EUID -ne 0 ]]; then - echo -e "${YELLOW}Administrator privileges required for system installation${NC}" - echo "Please run with sudo or as administrator" - exit 1 - fi -fi - -# Create temporary directory -TEMP_DIR=$(mktemp -d) -trap "rm -rf $TEMP_DIR" EXIT - -echo "Downloading assets..." - -# Download header file -echo " - opentui.h" -curl -L -o "$TEMP_DIR/opentui.h" "$RELEASE_URL/opentui.h" || { - echo -e "${RED}Error: Failed to download header file${NC}" - exit 1 -} - -# Download library for current platform -case "$OS" in - macos) - LIB_FILE="libopentui.dylib" - ASSET_NAME="libopentui-$PLATFORM.dylib" - ;; - linux) - LIB_FILE="libopentui.so" - ASSET_NAME="libopentui-$PLATFORM.so" - ;; - windows) - LIB_FILE="opentui.dll" - ASSET_NAME="opentui-$PLATFORM.dll" - ;; -esac - -echo " - $ASSET_NAME" -curl -L -o "$TEMP_DIR/$LIB_FILE" "$RELEASE_URL/$ASSET_NAME" || { - echo -e "${RED}Error: Failed to download library for $PLATFORM${NC}" - exit 1 -} - -# Install files -echo "Installing files..." - -echo " - Installing header to $INCLUDE_DIR/opentui.h" -cp "$TEMP_DIR/opentui.h" "$INCLUDE_DIR/opentui.h" || { - echo -e "${RED}Error: Failed to install header file${NC}" - exit 1 -} - -echo " - Installing library to $LIB_DIR/$LIB_FILE" -cp "$TEMP_DIR/$LIB_FILE" "$LIB_DIR/$LIB_FILE" || { - echo -e "${RED}Error: Failed to install library file${NC}" - exit 1 -} - -# Set permissions -chmod 644 "$INCLUDE_DIR/opentui.h" -chmod 755 "$LIB_DIR/$LIB_FILE" - -# Install pkg-config file -PKG_CONFIG_DIR="/usr/local/lib/pkgconfig" -if [[ "$OS" == "macos" ]] && [[ -d "/opt/homebrew/lib/pkgconfig" ]]; then - PKG_CONFIG_DIR="/opt/homebrew/lib/pkgconfig" -fi - -echo " - Installing pkg-config file to $PKG_CONFIG_DIR/opentui.pc" -mkdir -p "$PKG_CONFIG_DIR" - -# Download and install pkg-config template -curl -L -o "$TEMP_DIR/opentui.pc.in" "$RELEASE_URL/opentui.pc.in" || { - echo -e "${YELLOW}Warning: Could not download pkg-config template${NC}" -} - -if [[ -f "$TEMP_DIR/opentui.pc.in" ]]; then - sed "s|@PREFIX@|${INCLUDE_DIR%/include}|g; s|@VERSION@|latest|g" "$TEMP_DIR/opentui.pc.in" > "$PKG_CONFIG_DIR/opentui.pc" - chmod 644 "$PKG_CONFIG_DIR/opentui.pc" -fi - -# Update library cache on Linux -if [[ "$OS" == "linux" ]]; then - echo "Updating library cache..." - ldconfig 2>/dev/null || true -fi - -echo -e "${GREEN}✓ OpenTUI installed successfully!${NC}" -echo "" -echo "You can now use OpenTUI in your Go projects:" -echo " go get github.com/dnakov/opentui/packages/go" -echo "" -echo "To uninstall:" -echo " sudo rm $INCLUDE_DIR/opentui.h $LIB_DIR/$LIB_FILE" \ No newline at end of file diff --git a/package.json b/package.json index a1ace05fc..13516d5c6 100644 --- a/package.json +++ b/package.json @@ -6,15 +6,12 @@ ], "scripts": { "build": "cd packages/core && bun run build && cd ../solid && bun run build && cd ../react && bun run build", - "build:go": "cd packages/go && go build ./...", "pre-publish": "bun scripts/pre-publish.ts", "publish": "bun run pre-publish && bun run publish:core && bun run publish:react && bun run publish:solid", "publish:core": "cd packages/core && bun run publish", "publish:react": "cd packages/react && bun run publish", "publish:solid": "cd packages/solid && bun run publish", - "publish:vue": "cd packages/vue && bun run publish", "prepare-release": "bun scripts/prepare-release.ts", - "test:go": "cd packages/go && go test -v . && go build ./examples/...", "prettier:write": "prettier --write .", "test": "bun run --filter '@opentui/core' --filter '@opentui/solid' --filter '@opentui/react' --if-present test" }, diff --git a/packages/go/.gitignore b/packages/go/.gitignore deleted file mode 100644 index e69de29bb..000000000 diff --git a/packages/go/README.md b/packages/go/README.md deleted file mode 100644 index 71cbb53b2..000000000 --- a/packages/go/README.md +++ /dev/null @@ -1,256 +0,0 @@ -# OpenTUI Go - -Go bindings for [OpenTUI](https://github.com/sst/opentui), a high-performance terminal user interface library built with Zig. - -## Features - -- **High Performance**: Direct bindings to optimized Zig code -- **Memory Safe**: Automatic resource management with Go finalizers -- **Cross-Platform**: Support for Linux, macOS, and Windows -- **Full Feature Set**: Complete API coverage including mouse support -- **Idiomatic Go**: Proper error handling and Go conventions - -## Installation - -### 1. Install OpenTUI System Dependencies - -First, install OpenTUI headers and libraries system-wide: - -```bash -curl -L https://github.com/sst/opentui/releases/latest/download/install.sh | sh -``` - -This downloads the latest compiled libraries and headers for your platform and installs them to standard system locations. - -### 2. Install Go Package - -Then use in your Go projects: - -```bash -go get github.com/sst/opentui/packages/go -``` - -### Requirements - -- Go 1.21 or later -- CGO enabled -- pkg-config (usually pre-installed on most systems) - -## Quick Start - -```go -package main - -import ( - "time" - - "github.com/sst/opentui/packages/go" -) - -func main() { - // Create renderer - renderer := opentui.NewRenderer(80, 24) - defer renderer.Close() - - // Get buffer and draw - buffer, _ := renderer.GetNextBuffer() - buffer.Clear(opentui.NewRGB(0.1, 0.1, 0.3)) - buffer.DrawText("Hello, OpenTUI!", 10, 5, opentui.White, nil, 0) - - // Render to terminal - renderer.Render(true) - time.Sleep(2 * time.Second) -} -``` - -## API Reference - -### Core Types - -#### Renderer - -The main rendering engine that manages terminal output. - -```go -renderer := opentui.NewRenderer(width, height) -defer renderer.Close() - -// Basic rendering -buffer, err := renderer.GetNextBuffer() -renderer.Render(false) - -// Mouse support -renderer.EnableMouse(true) // Enable mouse tracking -renderer.DisableMouse() // Disable mouse tracking - -// Terminal control -renderer.ClearTerminal() -renderer.Resize(newWidth, newHeight) -``` - -#### Buffer - -A 2D array of terminal cells for efficient rendering. - -```go -buffer := opentui.NewBuffer(80, 24, false, opentui.WidthMethodUnicode) -defer buffer.Close() - -// Drawing operations -buffer.Clear(opentui.Black) -buffer.DrawText("Hello", 0, 0, opentui.White, nil, 0) -buffer.FillRect(10, 10, 20, 5, opentui.Blue) - -// Box drawing -options := opentui.BoxOptions{ - Sides: opentui.BorderSides{Top: true, Right: true, Bottom: true, Left: true}, - Fill: true, - Title: "My Box", - BorderChars: opentui.DefaultBoxChars, -} -buffer.DrawBox(5, 5, 30, 10, options, opentui.White, opentui.Gray) -``` - -#### TextBuffer - -Efficient handling of styled text with line tracking. - -```go -textBuffer := opentui.NewTextBuffer(1024, opentui.WidthMethodUnicode) -defer textBuffer.Close() - -// Write styled text -chunk := opentui.TextChunk{ - Text: "Hello, World!", - Foreground: &opentui.Red, - Background: &opentui.Black, -} -written, err := textBuffer.WriteChunk(chunk) - -// Finalize and get line info -textBuffer.FinalizeLineInfo() -lines, err := textBuffer.GetLineInfo() -``` - -### Colors and Styling - -#### RGBA Colors - -```go -// Predefined colors -opentui.Black, opentui.White, opentui.Red, opentui.Green, opentui.Blue -opentui.Yellow, opentui.Cyan, opentui.Magenta, opentui.Gray, opentui.Transparent - -// Custom colors -color := opentui.NewRGBA(1.0, 0.5, 0.0, 1.0) // Orange -rgb := opentui.NewRGB(0.2, 0.8, 0.4) // Green (alpha = 1.0) -``` - -#### Text Attributes - -```go -opentui.AttrBold // Bold text -opentui.AttrItalic // Italic text -opentui.AttrUnderline // Underlined text -opentui.AttrBlink // Blinking text -opentui.AttrReverse // Reverse video -opentui.AttrStrike // Strikethrough -opentui.AttrDim // Dimmed text - -// Combine attributes -attributes := opentui.AttrBold | opentui.AttrItalic -``` - -### Global Cursor Control - -```go -// Position and visibility -opentui.SetCursorPosition(10, 5, true) - -// Cursor style -opentui.SetCursorStyle(opentui.CursorBlock, true) // Blinking block -opentui.SetCursorStyle(opentui.CursorUnderline, false) // Static underline -opentui.SetCursorStyle(opentui.CursorBar, true) // Blinking bar - -// Cursor color -opentui.SetCursorColor(opentui.Green) -``` - -### Advanced Features - -#### Direct Buffer Access - -For performance-critical operations, you can access buffer arrays directly: - -```go -directAccess, err := buffer.GetDirectAccess() -if err != nil { - panic(err) -} - -// Direct manipulation of buffer data -cell, err := directAccess.GetCell(x, y) -directAccess.SetCell(x, y, opentui.Cell{ - Char: 'A', - Foreground: opentui.Red, - Background: opentui.Black, - Attributes: opentui.AttrBold, -}) -``` - -#### Hit Testing - -For mouse interaction support: - -```go -// Add hit areas -renderer.AddToHitGrid(10, 10, 20, 5, 42) // x, y, width, height, id - -// Check for mouse hits -hitID, err := renderer.CheckHit(mouseX, mouseY) -if hitID == 42 { - fmt.Println("Button was clicked!") -} -``` - -## Examples - -See the `examples/` directory for complete working examples: - -- `basic/` - Simple "Hello World" example -- `console/` - Interactive console demo with mouse support - -To run examples: - -```bash -cd examples/basic && go run . -cd examples/console && go run . -``` - -## Building from Source - -To build with a custom OpenTUI library: - -```bash -# Build Zig library (requires Zig 0.15.2+) -cd ../../core/src/zig -zig build -Doptimize=ReleaseFast - -# Install locally instead of using releases -sudo cp lib/$(uname -m)-$(uname -s | tr '[:upper:]' '[:lower:]')/libopentui.* /usr/local/lib/ -sudo cp ../../go/opentui.h /usr/local/include/ - -# Test Go bindings -cd ../../go -go test -``` - -## Platform Support - -- ✅ macOS (Intel and Apple Silicon) -- ✅ Linux (x64 and ARM64) -- ✅ Windows (x64 and ARM64) - -## License - -MIT License - see main repository for details. diff --git a/packages/go/buffer.go b/packages/go/buffer.go deleted file mode 100644 index b56a8c67f..000000000 --- a/packages/go/buffer.go +++ /dev/null @@ -1,327 +0,0 @@ -package opentui - -/* -#include "opentui.h" -#include -*/ -import "C" -import ( - "unsafe" -) - -// Buffer wraps the OptimizedBuffer from the C library. -// It represents a 2D array of terminal cells for efficient rendering. -type Buffer struct { - ptr *C.OptimizedBuffer - managed bool // true if buffer is managed by renderer -} - -// WidthMethod constants for Unicode width calculation -const ( - WidthMethodWCWidth = 0 // Use wcwidth for width calculation - WidthMethodUnicode = 1 // Use Unicode standard width calculation -) - -// NewBuffer creates a new buffer with the specified dimensions. -// If respectAlpha is true, the buffer will handle alpha blending. -// The widthMethod parameter controls how text width is calculated (use WidthMethodUnicode for full Unicode support). -func NewBuffer(width, height uint32, respectAlpha bool, widthMethod uint8) *Buffer { - if width == 0 || height == 0 { - return nil - } - - ptr := C.createOptimizedBuffer(C.uint32_t(width), C.uint32_t(height), C.bool(respectAlpha), C.uint8_t(widthMethod)) - if ptr == nil { - return nil - } - - b := &Buffer{ptr: ptr, managed: false} - setFinalizer(b, func(b *Buffer) { b.Close() }) - return b -} - -// Close releases the buffer's resources. -// After calling Close, the buffer should not be used. -// Note: Buffers obtained from a renderer are managed automatically and don't need to be closed. -func (b *Buffer) Close() error { - if b.ptr != nil && !b.managed { - clearFinalizer(b) - C.destroyOptimizedBuffer(b.ptr) - b.ptr = nil - } - return nil -} - -// Width returns the buffer width in cells. -func (b *Buffer) Width() (uint32, error) { - if b.ptr == nil { - return 0, newError("buffer is closed") - } - return uint32(C.getBufferWidth(b.ptr)), nil -} - -// Height returns the buffer height in cells. -func (b *Buffer) Height() (uint32, error) { - if b.ptr == nil { - return 0, newError("buffer is closed") - } - return uint32(C.getBufferHeight(b.ptr)), nil -} - -// Size returns the buffer dimensions. -func (b *Buffer) Size() (uint32, uint32, error) { - if b.ptr == nil { - return 0, 0, newError("buffer is closed") - } - w := uint32(C.getBufferWidth(b.ptr)) - h := uint32(C.getBufferHeight(b.ptr)) - return w, h, nil -} - -// Clear fills the entire buffer with the specified background color. -func (b *Buffer) Clear(bg RGBA) error { - if b.ptr == nil { - return newError("buffer is closed") - } - C.bufferClear(b.ptr, bg.toCFloat()) - return nil -} - -// GetRespectAlpha returns whether the buffer respects alpha values. -func (b *Buffer) GetRespectAlpha() (bool, error) { - if b.ptr == nil { - return false, newError("buffer is closed") - } - return bool(C.bufferGetRespectAlpha(b.ptr)), nil -} - -// SetRespectAlpha sets whether the buffer should respect alpha values. -func (b *Buffer) SetRespectAlpha(respectAlpha bool) error { - if b.ptr == nil { - return newError("buffer is closed") - } - C.bufferSetRespectAlpha(b.ptr, C.bool(respectAlpha)) - return nil -} - -// DrawText draws text at the specified position with the given colors and attributes. -func (b *Buffer) DrawText(text string, x, y uint32, fg RGBA, bg *RGBA, attributes uint8) error { - if b.ptr == nil { - return newError("buffer is closed") - } - - textPtr, textLen := stringToC(text) - if textPtr == nil { - return nil // Empty string, nothing to draw - } - - var bgPtr *C.float - if bg != nil { - bgPtr = bg.toCFloat() - } - - C.bufferDrawText(b.ptr, textPtr, textLen, C.uint32_t(x), C.uint32_t(y), fg.toCFloat(), bgPtr, C.uint8_t(attributes)) - return nil -} - -// SetCellWithAlphaBlending sets a single cell with alpha blending support. -func (b *Buffer) SetCellWithAlphaBlending(x, y uint32, char rune, fg, bg RGBA, attributes uint8) error { - if b.ptr == nil { - return newError("buffer is closed") - } - C.bufferSetCellWithAlphaBlending(b.ptr, C.uint32_t(x), C.uint32_t(y), C.uint32_t(char), fg.toCFloat(), bg.toCFloat(), C.uint8_t(attributes)) - return nil -} - -// FillRect fills a rectangular area with the specified background color. -func (b *Buffer) FillRect(x, y, width, height uint32, bg RGBA) error { - if b.ptr == nil { - return newError("buffer is closed") - } - C.bufferFillRect(b.ptr, C.uint32_t(x), C.uint32_t(y), C.uint32_t(width), C.uint32_t(height), bg.toCFloat()) - return nil -} - -// DrawPackedBuffer draws packed buffer data at the specified position. -func (b *Buffer) DrawPackedBuffer(data []byte, posX, posY, terminalWidthCells, terminalHeightCells uint32) error { - if b.ptr == nil { - return newError("buffer is closed") - } - if len(data) == 0 { - return nil - } - - dataPtr, dataLen := sliceToC(data) - C.bufferDrawPackedBuffer(b.ptr, (*C.uint8_t)(unsafe.Pointer(dataPtr)), dataLen, - C.uint32_t(posX), C.uint32_t(posY), C.uint32_t(terminalWidthCells), C.uint32_t(terminalHeightCells)) - return nil -} - -// DrawSuperSampleBuffer draws super-sampled pixel data for high-resolution graphics. -func (b *Buffer) DrawSuperSampleBuffer(x, y uint32, pixelData []byte, format SuperSampleFormat, alignedBytesPerRow uint32) error { - if b.ptr == nil { - return newError("buffer is closed") - } - if len(pixelData) == 0 { - return nil - } - - dataPtr, dataLen := sliceToC(pixelData) - C.bufferDrawSuperSampleBuffer(b.ptr, C.uint32_t(x), C.uint32_t(y), - (*C.uint8_t)(unsafe.Pointer(dataPtr)), dataLen, C.uint8_t(format), C.uint32_t(alignedBytesPerRow)) - return nil -} - -// DrawBox draws a box with optional borders and title. -func (b *Buffer) DrawBox(x, y int32, width, height uint32, options BoxOptions, borderColor, backgroundColor RGBA) error { - if b.ptr == nil { - return newError("buffer is closed") - } - - // Convert border characters to C array - borderChars := runesToC(options.BorderChars[:]) - - // Pack options - packed := packBorderOptions(options.Sides, options.Fill, uint8(options.TitleAlignment)) - - // Handle title - var titlePtr *C.uint8_t - var titleLen C.uint32_t - if options.Title != "" { - ptr, len := stringToC(options.Title) - titlePtr = ptr - titleLen = C.uint32_t(len) - } - - C.bufferDrawBox(b.ptr, C.int32_t(x), C.int32_t(y), C.uint32_t(width), C.uint32_t(height), - borderChars, packed, borderColor.toCFloat(), backgroundColor.toCFloat(), titlePtr, titleLen) - return nil -} - -// Resize changes the buffer dimensions. -// This may invalidate any existing content. -func (b *Buffer) Resize(width, height uint32) error { - if b.ptr == nil { - return newError("buffer is closed") - } - if width == 0 || height == 0 { - return newError("invalid dimensions") - } - C.bufferResize(b.ptr, C.uint32_t(width), C.uint32_t(height)) - return nil -} - -// DrawFrameBuffer draws another buffer onto this buffer at the specified position. -func (b *Buffer) DrawFrameBuffer(destX, destY int32, frameBuffer *Buffer, sourceX, sourceY, sourceWidth, sourceHeight uint32) error { - if b.ptr == nil { - return newError("buffer is closed") - } - if frameBuffer == nil || frameBuffer.ptr == nil { - return newError("frame buffer is nil or closed") - } - - C.drawFrameBuffer(b.ptr, C.int32_t(destX), C.int32_t(destY), frameBuffer.ptr, - C.uint32_t(sourceX), C.uint32_t(sourceY), C.uint32_t(sourceWidth), C.uint32_t(sourceHeight)) - return nil -} - -// DrawTextBuffer draws a text buffer onto this buffer with optional clipping. -func (b *Buffer) DrawTextBuffer(textBuffer *TextBuffer, x, y int32, clipRect *ClipRect) error { - if b.ptr == nil { - return newError("buffer is closed") - } - if textBuffer == nil || textBuffer.ptr == nil { - return newError("text buffer is nil or closed") - } - - var clipX, clipY C.int32_t - var clipWidth, clipHeight C.uint32_t - var hasClip C.bool - - if clipRect != nil { - clipX = C.int32_t(clipRect.X) - clipY = C.int32_t(clipRect.Y) - clipWidth = C.uint32_t(clipRect.Width) - clipHeight = C.uint32_t(clipRect.Height) - hasClip = C.bool(true) - } - - C.bufferDrawTextBuffer(b.ptr, textBuffer.ptr, C.int32_t(x), C.int32_t(y), - clipX, clipY, clipWidth, clipHeight, hasClip) - return nil -} - -// GetDirectAccess returns direct access to the buffer's internal arrays. -// This is an advanced feature for performance-critical operations. -// The returned slices are valid until the buffer is resized or closed. -func (b *Buffer) GetDirectAccess() (*DirectAccess, error) { - if b.ptr == nil { - return nil, newError("buffer is closed") - } - - width, height, err := b.Size() - if err != nil { - return nil, err - } - - size := int(width * height) - - charPtr := C.bufferGetCharPtr(b.ptr) - fgPtr := C.bufferGetFgPtr(b.ptr) - bgPtr := C.bufferGetBgPtr(b.ptr) - attrPtr := C.bufferGetAttributesPtr(b.ptr) - - return &DirectAccess{ - Chars: cArrayToSlice((*uint32)(charPtr), size), - Foreground: cArrayToSlice((*RGBA)(unsafe.Pointer(fgPtr)), size), - Background: cArrayToSlice((*RGBA)(unsafe.Pointer(bgPtr)), size), - Attributes: cArrayToSlice((*uint8)(attrPtr), size), - Width: width, - Height: height, - }, nil -} - -// DirectAccess provides direct access to buffer internal arrays for performance-critical operations. -// Warning: This is an advanced feature. Modifying these slices directly bypasses normal safety checks. -type DirectAccess struct { - Chars []uint32 // Character codes (Unicode code points) - Foreground []RGBA // Foreground colors - Background []RGBA // Background colors - Attributes []uint8 // Text attributes - Width uint32 // Buffer width - Height uint32 // Buffer height -} - -// GetCell returns the cell at the specified coordinates using direct access. -func (da *DirectAccess) GetCell(x, y uint32) (*Cell, error) { - if x >= da.Width || y >= da.Height { - return nil, newError("coordinates out of bounds") - } - - index := y*da.Width + x - return &Cell{ - Char: rune(da.Chars[index]), - Foreground: da.Foreground[index], - Background: da.Background[index], - Attributes: da.Attributes[index], - }, nil -} - -// SetCell sets the cell at the specified coordinates using direct access. -func (da *DirectAccess) SetCell(x, y uint32, cell Cell) error { - if x >= da.Width || y >= da.Height { - return newError("coordinates out of bounds") - } - - index := y*da.Width + x - da.Chars[index] = uint32(cell.Char) - da.Foreground[index] = cell.Foreground - da.Background[index] = cell.Background - da.Attributes[index] = cell.Attributes - return nil -} - -// Valid checks if the buffer is still valid (not closed). -func (b *Buffer) Valid() bool { - return b.ptr != nil -} \ No newline at end of file diff --git a/packages/go/examples/basic/main.go b/packages/go/examples/basic/main.go deleted file mode 100644 index 0ea4b9538..000000000 --- a/packages/go/examples/basic/main.go +++ /dev/null @@ -1,156 +0,0 @@ -package main - -import ( - "fmt" - "time" - - opentui "github.com/sst/opentui/packages/go" -) - -func main() { - fmt.Println("Starting OpenTUI Go Basic Example...") - - // Create a new renderer with 80x24 dimensions - renderer := opentui.NewRenderer(80, 24) - if renderer == nil { - panic("Failed to create renderer - make sure the OpenTUI library is available") - } - defer renderer.Close() - - // Set a dark blue background - err := renderer.SetBackgroundColor(opentui.NewRGB(0.1, 0.1, 0.3)) - if err != nil { - panic(fmt.Sprintf("Failed to set background color: %v", err)) - } - - // Clear the terminal first - err = renderer.ClearTerminal() - if err != nil { - panic(fmt.Sprintf("Failed to clear terminal: %v", err)) - } - - // Get the buffer for drawing - buffer, err := renderer.GetNextBuffer() - if err != nil { - panic(fmt.Sprintf("Failed to get buffer: %v", err)) - } - - // Clear the buffer with the background color - err = buffer.Clear(opentui.NewRGB(0.1, 0.1, 0.3)) - if err != nil { - panic(fmt.Sprintf("Failed to clear buffer: %v", err)) - } - - // Draw a title - err = buffer.DrawText("OpenTUI Go Demo", 30, 2, - opentui.Yellow, nil, opentui.AttrBold) - if err != nil { - panic(fmt.Sprintf("Failed to draw title: %v", err)) - } - - // Draw some colored text - colors := []opentui.RGBA{ - opentui.Red, - opentui.Green, - opentui.Blue, - opentui.Cyan, - opentui.Magenta, - opentui.Yellow, - } - - messages := []string{ - "Hello, World!", - "This is red text", - "This is green text", - "This is blue text", - "This is cyan text", - "This is magenta text", - } - - for i, msg := range messages { - color := opentui.White - if i > 0 { - color = colors[i-1] - } - - attrs := uint8(0) - if i == 0 { - attrs = opentui.AttrBold | opentui.AttrUnderline - } - - err = buffer.DrawText(msg, 10, uint32(5+i*2), color, nil, attrs) - if err != nil { - panic(fmt.Sprintf("Failed to draw text: %v", err)) - } - } - - // Draw a box around some content - boxOptions := opentui.BoxOptions{ - Sides: opentui.BorderSides{ - Top: true, - Right: true, - Bottom: true, - Left: true, - }, - Fill: true, - Title: "Information Box", - TitleAlignment: opentui.AlignCenter, - BorderChars: opentui.DefaultBoxChars, - } - - err = buffer.DrawBox(50, 6, 25, 8, boxOptions, - opentui.White, opentui.NewRGB(0.2, 0.2, 0.4)) - if err != nil { - panic(fmt.Sprintf("Failed to draw box: %v", err)) - } - - // Add content inside the box - err = buffer.DrawText("Terminal UI Demo", 52, 8, - opentui.Green, nil, opentui.AttrBold) - if err != nil { - panic(fmt.Sprintf("Failed to draw box content: %v", err)) - } - - err = buffer.DrawText("Built with OpenTUI", 52, 9, - opentui.Cyan, nil, 0) - if err != nil { - panic(fmt.Sprintf("Failed to draw box content: %v", err)) - } - - err = buffer.DrawText("Go Bindings v1.0", 52, 10, - opentui.Yellow, nil, 0) - if err != nil { - panic(fmt.Sprintf("Failed to draw box content: %v", err)) - } - - // Fill a colored rectangle - err = buffer.FillRect(10, 18, 60, 3, opentui.NewRGB(0.8, 0.2, 0.2)) - if err != nil { - panic(fmt.Sprintf("Failed to fill rectangle: %v", err)) - } - - err = buffer.DrawText("Press Ctrl+C to exit", 25, 19, - opentui.White, nil, opentui.AttrBold) - if err != nil { - panic(fmt.Sprintf("Failed to draw exit message: %v", err)) - } - - // Render the buffer to the screen - err = renderer.Render(true) - if err != nil { - panic(fmt.Sprintf("Failed to render: %v", err)) - } - - fmt.Println("Demo rendered successfully! The display will remain for 10 seconds...") - - // Keep the display visible for a while - time.Sleep(10 * time.Second) - - // Clear terminal before exit - err = renderer.ClearTerminal() - if err != nil { - fmt.Printf("Warning: Failed to clear terminal on exit: %v\n", err) - } - - fmt.Println("OpenTUI Go Basic Example completed!") -} \ No newline at end of file diff --git a/packages/go/examples/console/README.md b/packages/go/examples/console/README.md deleted file mode 100644 index 87de81cf1..000000000 --- a/packages/go/examples/console/README.md +++ /dev/null @@ -1,114 +0,0 @@ -# Console Demo - -This demo recreates the OpenTUI console logging demo from TypeScript, showcasing interactive buttons and console logging functionality. - -## Features - -- **Interactive Buttons**: 5 colored buttons for different log levels (LOG, INFO, WARN, ERROR, DEBUG) -- **Visual Effects**: Sparkle animations when buttons are clicked -- **Mouse Support**: Mouse tracking enabled for clickable interactions -- **Keyboard Controls**: Fallback keyboard controls for button activation -- **Console Logging**: Different log levels with structured output -- **Statistics Tracking**: Click counters for each button type -- **Beautiful UI**: Bordered buttons, decorative elements, and colored text - -## Controls - -### Keyboard Controls - -- **1-5**: Trigger buttons (LOG, INFO, WARN, ERROR, DEBUG) -- **q/Q**: Quit demo -- **ESC**: Exit - -### Mouse Controls (if supported) - -- **Click**: Click on buttons to trigger them -- **Hover**: Buttons change color on hover - -## Running the Demo - -```bash -# Build the demo -go build . - -# Run the demo -./console -``` - -## Implementation Highlights - -### ConsoleButton Struct - -```go -type ConsoleButton struct { - ID string - X, Y int32 - Width, Height uint32 - Label string - LogType string - - // Colors for different states - OriginalBg opentui.RGBA - HoverBg opentui.RGBA - PressBg opentui.RGBA - BorderColor opentui.RGBA - - // State tracking - IsHovered bool - IsPressed bool - LastClickTime time.Time - ClickCount int -} -``` - -### Visual Effects - -- **Sparkle Animation**: ✦ symbols appear briefly when buttons are clicked -- **Color States**: Buttons change color based on hover/press state -- **Alpha Blending**: Smooth color transitions and transparency effects - -### Console Logging - -Each button type produces different log output: - -- **LOG**: Regular console.log output -- **INFO**: Informational messages -- **WARN**: Warning messages with additional context -- **ERROR**: Error messages with error codes -- **DEBUG**: Debug information with variables - -### Input Handling - -The demo supports multiple input modes: - -1. **Raw Terminal Input**: Direct key reading for responsive controls -2. **Simple Line Input**: Fallback mode for terminals without raw input support -3. **Mouse Events**: ANSI mouse tracking (where supported) - -## Terminal Compatibility - -Works best in terminals that support: - -- ANSI escape sequences -- Mouse tracking (optional) -- 24-bit color (optional, fallback to 8-bit) - -Tested terminals: - -- ✅ macOS Terminal.app -- ✅ iTerm2 -- ✅ VSCode Terminal -- ✅ Most Linux terminals - -## Architecture - -The demo showcases several OpenTUI Go wrapper features: - -1. **Renderer Management**: Creating, configuring, and managing the terminal renderer -2. **Buffer Operations**: Drawing text, boxes, and visual elements -3. **Mouse Support**: Enabling/disabling mouse tracking -4. **Color Management**: Using RGBA colors with alpha blending -5. **Text Attributes**: Bold, italic, and other text styling -6. **Event Loop**: Handling input and rendering in a game-style loop - -This serves as both a functional demo and a reference implementation for building interactive terminal applications with the OpenTUI Go wrapper. diff --git a/packages/go/examples/console/input.go b/packages/go/examples/console/input.go deleted file mode 100644 index 11d687d98..000000000 --- a/packages/go/examples/console/input.go +++ /dev/null @@ -1,209 +0,0 @@ -package main - -import ( - "bufio" - "fmt" - "os" - "os/exec" - "strconv" - "strings" -) - -// TerminalInput handles raw terminal input using external stty command -type TerminalInput struct { - stdin *os.File - reader *bufio.Reader -} - -// NewTerminalInput creates a new terminal input handler -func NewTerminalInput() (*TerminalInput, error) { - stdin := os.Stdin - - // Try to set raw mode using stty - err := SetTerminalRaw() - if err != nil { - return nil, fmt.Errorf("failed to set terminal to raw mode: %v", err) - } - - return &TerminalInput{ - stdin: stdin, - reader: bufio.NewReader(stdin), - }, nil -} - -// Close restores the terminal to its original state -func (t *TerminalInput) Close() error { - return RestoreTerminal() -} - -// InputEvent represents different types of input events -type InputEvent struct { - Type string // "key", "mouse_move", "mouse_click", "mouse_release" - Key rune - MouseX uint32 - MouseY uint32 - Button int -} - -// ReadInput reads input events from the terminal -func (t *TerminalInput) ReadInput() (*InputEvent, error) { - b, err := t.reader.ReadByte() - if err != nil { - return nil, err - } - - // Handle escape sequences - if b == 27 { // ESC - // Check if there's more data - next, err := t.reader.ReadByte() - if err != nil { - // Just ESC key - return &InputEvent{Type: "key", Key: 27}, nil - } - - if next == '[' { - // ANSI escape sequence - return t.parseAnsiSequence() - } else { - // Put back the byte and return ESC - // Note: This is simplified - proper implementation would use unread - return &InputEvent{Type: "key", Key: 27}, nil - } - } - - // Regular key - return &InputEvent{Type: "key", Key: rune(b)}, nil -} - -// parseAnsiSequence parses ANSI escape sequences for mouse events -func (t *TerminalInput) parseAnsiSequence() (*InputEvent, error) { - // Read the sequence - var seq strings.Builder - for { - b, err := t.reader.ReadByte() - if err != nil { - return nil, err - } - seq.WriteByte(b) - - // Mouse sequences typically end with 'M' or 'm' - if b == 'M' || b == 'm' { - break - } - - // Other sequences might end with letters - if b >= 'A' && b <= 'Z' || b >= 'a' && b <= 'z' { - break - } - - // Prevent infinite loops - if seq.Len() > 20 { - break - } - } - - sequence := seq.String() - - // Parse mouse events (simplified) - // Real mouse parsing is more complex - if strings.Contains(sequence, "M") { - // Mouse click/release - return &InputEvent{Type: "mouse_click", MouseX: 10, MouseY: 10, Button: 1}, nil - } - - // Default to unknown key sequence - return &InputEvent{Type: "key", Key: 0}, nil -} - -// KeyboardOnlyInput provides a simpler keyboard-only input for the demo -type KeyboardOnlyInput struct { - input *TerminalInput -} - -// NewKeyboardOnlyInput creates a keyboard-only input handler -func NewKeyboardOnlyInput() (*KeyboardOnlyInput, error) { - input, err := NewTerminalInput() - if err != nil { - return nil, err - } - - return &KeyboardOnlyInput{input: input}, nil -} - -// Close restores terminal state -func (k *KeyboardOnlyInput) Close() error { - return k.input.Close() -} - -// ReadKey reads a single key press -func (k *KeyboardOnlyInput) ReadKey() (rune, error) { - event, err := k.input.ReadInput() - if err != nil { - return 0, err - } - - if event.Type == "key" { - return event.Key, nil - } - - // Non-key events, try again - return k.ReadKey() -} - -// SimpleInput provides a cross-platform simple input solution -type SimpleInput struct { - reader *bufio.Scanner -} - -// NewSimpleInput creates a simple line-based input handler -func NewSimpleInput() *SimpleInput { - return &SimpleInput{ - reader: bufio.NewScanner(os.Stdin), - } -} - -// ReadLine reads a line of input -func (s *SimpleInput) ReadLine() (string, error) { - if s.reader.Scan() { - return s.reader.Text(), nil - } - return "", s.reader.Err() -} - -// SetTerminalRaw attempts to set the terminal to raw mode (Unix only) -func SetTerminalRaw() error { - cmd := exec.Command("stty", "-echo", "cbreak") - cmd.Stdin = os.Stdin - return cmd.Run() -} - -// RestoreTerminal attempts to restore normal terminal mode (Unix only) -func RestoreTerminal() error { - cmd := exec.Command("stty", "echo", "-cbreak") - cmd.Stdin = os.Stdin - return cmd.Run() -} - -// GetTerminalSize gets the terminal dimensions -func GetTerminalSize() (int, int, error) { - cmd := exec.Command("stty", "size") - cmd.Stdin = os.Stdin - out, err := cmd.Output() - if err != nil { - return 80, 24, nil // Default fallback - } - - parts := strings.Fields(strings.TrimSpace(string(out))) - if len(parts) != 2 { - return 80, 24, nil - } - - rows, err1 := strconv.Atoi(parts[0]) - cols, err2 := strconv.Atoi(parts[1]) - - if err1 != nil || err2 != nil { - return 80, 24, nil - } - - return cols, rows, nil -} \ No newline at end of file diff --git a/packages/go/examples/console/main.go b/packages/go/examples/console/main.go deleted file mode 100644 index a9e711232..000000000 --- a/packages/go/examples/console/main.go +++ /dev/null @@ -1,561 +0,0 @@ -package main - -import ( - "fmt" - "log" - "strings" - "time" - - "github.com/sst/opentui/packages/go" -) - -// ConsoleButton represents a clickable button with hover and press states -type ConsoleButton struct { - ID string - X, Y int32 - Width, Height uint32 - Label string - LogType string - - // Colors - OriginalBg opentui.RGBA - HoverBg opentui.RGBA - PressBg opentui.RGBA - BorderColor opentui.RGBA - - // State - IsHovered bool - IsPressed bool - LastClickTime time.Time - - // Statistics - ClickCount int -} - -// NewConsoleButton creates a new console button -func NewConsoleButton(id string, x, y int32, width, height uint32, color opentui.RGBA, label, logType string) *ConsoleButton { - // Create brighter border color - borderColor := opentui.NewRGBA( - min(color.R*1.3, 1.0), - min(color.G*1.3, 1.0), - min(color.B*1.3, 1.0), - 1.0, - ) - - // Create hover and press colors - hoverBg := opentui.NewRGBA( - min(color.R*1.2, 1.0), - min(color.G*1.2, 1.0), - min(color.B*1.2, 1.0), - color.A, - ) - - pressBg := opentui.NewRGBA( - color.R*0.8, - color.G*0.8, - color.B*0.8, - color.A, - ) - - return &ConsoleButton{ - ID: id, - X: x, - Y: y, - Width: width, - Height: height, - Label: label, - LogType: logType, - OriginalBg: color, - HoverBg: hoverBg, - PressBg: pressBg, - BorderColor: borderColor, - IsHovered: false, - IsPressed: false, - ClickCount: 0, - } -} - -// Contains checks if a point is within the button bounds -func (b *ConsoleButton) Contains(x, y uint32) bool { - return x >= uint32(b.X) && x < uint32(b.X)+b.Width && - y >= uint32(b.Y) && y < uint32(b.Y)+b.Height -} - -// Render draws the button to the buffer -func (b *ConsoleButton) Render(buffer *opentui.Buffer) error { - // Choose background color based on state - var bgColor opentui.RGBA - if b.IsPressed { - bgColor = b.PressBg - } else if b.IsHovered { - bgColor = b.HoverBg - } else { - bgColor = b.OriginalBg - } - - // Draw the button box - boxOptions := opentui.BoxOptions{ - Sides: opentui.BorderSides{ - Top: true, - Right: true, - Bottom: true, - Left: true, - }, - Fill: true, - Title: b.Label, - TitleAlignment: opentui.AlignCenter, - BorderChars: opentui.DefaultBoxChars, - } - - err := buffer.DrawBox(b.X, b.Y, b.Width, b.Height, boxOptions, b.BorderColor, bgColor) - if err != nil { - return fmt.Errorf("failed to draw button box: %v", err) - } - - // Draw sparkle effect if recently clicked - timeSinceClick := time.Since(b.LastClickTime) - if timeSinceClick < 300*time.Millisecond { - alpha := 1.0 - float32(timeSinceClick.Milliseconds())/300.0 - sparkleColor := opentui.NewRGBA(1, 1, 1, alpha) - - centerX := uint32(b.X) + b.Width/2 - centerY := uint32(b.Y) + b.Height/2 - - // Draw sparkles - buffer.SetCellWithAlphaBlending(centerX-1, centerY, '✦', sparkleColor, bgColor, 0) - buffer.SetCellWithAlphaBlending(centerX+1, centerY, '✦', sparkleColor, bgColor, 0) - } - - return nil -} - -// Click handles a button click -func (b *ConsoleButton) Click() { - b.IsPressed = true - b.LastClickTime = time.Now() - b.ClickCount++ - b.TriggerConsoleLog() -} - -// TriggerConsoleLog simulates console logging based on the button type -func (b *ConsoleButton) TriggerConsoleLog() { - timestamp := time.Now().Format("15:04:05") - - switch b.LogType { - case "log": - fmt.Printf("Console Log #%d triggered at %s\n", b.ClickCount, timestamp) - fmt.Printf(" Data: This is a regular log message\n") - fmt.Printf(" Count: %d\n", b.ClickCount) - fmt.Printf(" Metadata: {source: console-demo, type: log}\n\n") - - case "info": - log.Printf("INFO: Info Log #%d triggered at %s", b.ClickCount, timestamp) - log.Printf("INFO: Message: This is an informational message") - log.Printf("INFO: Details: Info messages are used for general information") - log.Printf("INFO: Count: %d\n", b.ClickCount) - - case "warn": - log.Printf("WARN: Warning Log #%d triggered at %s", b.ClickCount, timestamp) - log.Printf("WARN: Warning: This is a warning message") - log.Printf("WARN: Reason: Something might need attention") - log.Printf("WARN: Count: %d\n", b.ClickCount) - - case "error": - log.Printf("ERROR: Error Log #%d triggered at %s", b.ClickCount, timestamp) - log.Printf("ERROR: Error: This is an error message") - log.Printf("ERROR: Details: Something went wrong (simulated)") - log.Printf("ERROR: ErrorCode: ERR_%d", b.ClickCount) - log.Printf("ERROR: Count: %d\n", b.ClickCount) - - case "debug": - log.Printf("DEBUG: Debug Log #%d triggered at %s", b.ClickCount, timestamp) - log.Printf("DEBUG: Debug: This is a debug message") - log.Printf("DEBUG: Variables: {count: %d}", b.ClickCount) - log.Printf("DEBUG: State: debugging\n") - } -} - -// DemoState holds the state of the demo -type DemoState struct { - Renderer *opentui.Renderer - Buffer *opentui.Buffer - Buttons []*ConsoleButton - StatusText string - Running bool - MouseX uint32 - MouseY uint32 -} - -// NewDemoState creates a new demo state -func NewDemoState() (*DemoState, error) { - renderer := opentui.NewRenderer(80, 30) - if renderer == nil { - return nil, fmt.Errorf("failed to create renderer") - } - - // Enable mouse tracking - err := renderer.EnableMouse(true) - if err != nil { - renderer.Close() - return nil, fmt.Errorf("failed to enable mouse: %v", err) - } - - // Set background color - backgroundColor := opentui.NewRGBA(18.0/255, 22.0/255, 35.0/255, 1.0) - err = renderer.SetBackgroundColor(backgroundColor) - if err != nil { - renderer.Close() - return nil, fmt.Errorf("failed to set background color: %v", err) - } - - buffer, err := renderer.GetNextBuffer() - if err != nil { - renderer.Close() - return nil, fmt.Errorf("failed to get buffer: %v", err) - } - - // Create buttons - logColor := opentui.NewRGBA(160.0/255, 160.0/255, 170.0/255, 1.0) - infoColor := opentui.NewRGBA(100.0/255, 180.0/255, 200.0/255, 1.0) - warnColor := opentui.NewRGBA(220.0/255, 180.0/255, 100.0/255, 1.0) - errorColor := opentui.NewRGBA(200.0/255, 120.0/255, 120.0/255, 1.0) - debugColor := opentui.NewRGBA(140.0/255, 140.0/255, 150.0/255, 1.0) - - startY := int32(8) - buttonWidth := uint32(14) - buttonHeight := uint32(5) - spacing := int32(16) - - buttons := []*ConsoleButton{ - NewConsoleButton("log", 2, startY, buttonWidth, buttonHeight, logColor, "LOG", "log"), - NewConsoleButton("info", 2+spacing, startY, buttonWidth, buttonHeight, infoColor, "INFO", "info"), - NewConsoleButton("warn", 2+spacing*2, startY, buttonWidth, buttonHeight, warnColor, "WARN", "warn"), - NewConsoleButton("error", 2+spacing*3, startY, buttonWidth, buttonHeight, errorColor, "ERROR", "error"), - NewConsoleButton("debug", 2+spacing*4, startY, buttonWidth, buttonHeight, debugColor, "DEBUG", "debug"), - } - - return &DemoState{ - Renderer: renderer, - Buffer: buffer, - Buttons: buttons, - StatusText: "Click any button to start logging...", - Running: true, - }, nil -} - -// Close cleans up the demo state -func (d *DemoState) Close() { - if d.Renderer != nil { - d.Renderer.DisableMouse() - d.Renderer.ClearTerminal() - d.Renderer.Close() - } -} - -// Render draws the demo interface -func (d *DemoState) Render() error { - // Clear buffer - backgroundColor := opentui.NewRGBA(18.0/255, 22.0/255, 35.0/255, 1.0) - err := d.Buffer.Clear(backgroundColor) - if err != nil { - return fmt.Errorf("failed to clear buffer: %v", err) - } - - // Draw title - titleColor := opentui.NewRGBA(255.0/255, 215.0/255, 135.0/255, 1.0) - err = d.Buffer.DrawText("Console Logging Demo", 2, 1, titleColor, nil, opentui.AttrBold) - if err != nil { - return fmt.Errorf("failed to draw title: %v", err) - } - - // Draw instructions - instrColor := opentui.NewRGBA(176.0/255, 196.0/255, 222.0/255, 1.0) - instructions := "Click buttons to trigger different console log levels • Press 'q' to quit • ESC to exit" - err = d.Buffer.DrawText(instructions, 2, 2, instrColor, nil, 0) - if err != nil { - return fmt.Errorf("failed to draw instructions: %v", err) - } - - // Draw mouse position (for debugging) - mouseInfo := fmt.Sprintf("Mouse: (%d, %d)", d.MouseX, d.MouseY) - err = d.Buffer.DrawText(mouseInfo, 2, 3, opentui.Gray, nil, 0) - if err != nil { - return fmt.Errorf("failed to draw mouse info: %v", err) - } - - // Draw status - statusColor := opentui.NewRGBA(144.0/255, 238.0/255, 144.0/255, 1.0) - err = d.Buffer.DrawText(d.StatusText, 2, 5, statusColor, nil, opentui.AttrItalic) - if err != nil { - return fmt.Errorf("failed to draw status: %v", err) - } - - // Draw buttons - for _, button := range d.Buttons { - err = button.Render(d.Buffer) - if err != nil { - return fmt.Errorf("failed to render button %s: %v", button.ID, err) - } - } - - // Draw decorations - decorColor := opentui.NewRGBA(100.0/255, 120.0/255, 150.0/255, 120.0/255) - decoration := "✦ ✧ ✦ ✧ ✦ ✧ ✦ ✧ ✦ ✧ ✦ ✧ ✦ ✧ ✦ ✧ ✦" - err = d.Buffer.DrawText(decoration, 2, 16, decorColor, nil, 0) - if err != nil { - return fmt.Errorf("failed to draw decoration: %v", err) - } - - // Draw console info - consoleInfoColor := opentui.NewRGBA(120.0/255, 140.0/255, 160.0/255, 200.0/255) - consoleInfo := "Console output appears in the terminal. Check your terminal for log messages." - err = d.Buffer.DrawText(consoleInfo, 2, 18, consoleInfoColor, nil, opentui.AttrItalic) - if err != nil { - return fmt.Errorf("failed to draw console info: %v", err) - } - - // Draw button stats - statsY := uint32(22) - for i, button := range d.Buttons { - stats := fmt.Sprintf("%s: %d clicks", button.LogType, button.ClickCount) - statsColor := opentui.NewRGBA(200.0/255, 200.0/255, 200.0/255, 1.0) - err = d.Buffer.DrawText(stats, uint32(2+i*15), statsY, statsColor, nil, 0) - if err != nil { - return fmt.Errorf("failed to draw stats: %v", err) - } - } - - // Render to screen - return d.Renderer.Render(false) -} - -// HandleMouseMove processes mouse movement -func (d *DemoState) HandleMouseMove(x, y uint32) { - d.MouseX = x - d.MouseY = y - - // Update hover states - for _, button := range d.Buttons { - wasHovered := button.IsHovered - button.IsHovered = button.Contains(x, y) - - // Reset press state when mouse leaves - if wasHovered && !button.IsHovered { - button.IsPressed = false - } - } -} - -// HandleMouseClick processes mouse clicks -func (d *DemoState) HandleMouseClick(x, y uint32) { - for _, button := range d.Buttons { - if button.Contains(x, y) { - button.Click() - timestamp := time.Now().Format("15:04:05") - d.StatusText = fmt.Sprintf("Last triggered: %s #%d at %s", - button.LogType, button.ClickCount, timestamp) - break - } - } -} - -func min(a, b float32) float32 { - if a < b { - return a - } - return b -} - -func main() { - fmt.Println("🎮 OpenTUI Console Demo") - fmt.Println("======================") - fmt.Println() - fmt.Println("Controls:") - fmt.Println(" 1-5: Click buttons (LOG, INFO, WARN, ERROR, DEBUG)") - fmt.Println(" q/Q: Quit demo") - fmt.Println(" ESC: Exit") - fmt.Println() - fmt.Println("Mouse support is enabled - try clicking in supported terminals!") - fmt.Println("Log output will appear in this terminal window.") - fmt.Println() - - // Try to set terminal to raw mode for better input handling - SetTerminalRaw() - defer RestoreTerminal() - - // Create demo state - demo, err := NewDemoState() - if err != nil { - log.Fatalf("Failed to initialize demo: %v", err) - } - defer demo.Close() - - // Create input handler - input, err := NewKeyboardOnlyInput() - if err != nil { - log.Printf("Failed to create input handler, using simple input: %v", err) - runSimpleDemo(demo) - return - } - defer input.Close() - - // Print initial console message - fmt.Println("✨ Console Demo initialized! Use keyboard controls or try clicking the buttons.") - fmt.Println() - - // Channel for input events - inputChan := make(chan rune, 1) - - // Start input goroutine - go func() { - for { - key, err := input.ReadKey() - if err != nil { - return - } - select { - case inputChan <- key: - default: - // Buffer full, skip - } - } - }() - - // Main demo loop - lastRender := time.Now() - renderInterval := 50 * time.Millisecond - - for demo.Running { - // Handle input - select { - case key := <-inputChan: - if !handleInput(demo, key) { - demo.Running = false - continue - } - default: - // No input available - } - - // Render at regular intervals - if time.Since(lastRender) >= renderInterval { - err := demo.Render() - if err != nil { - log.Printf("Render error: %v", err) - break - } - lastRender = time.Now() - } - - // Small sleep to prevent busy waiting - time.Sleep(10 * time.Millisecond) - } - - fmt.Println("\n🎉 Console Demo completed!") - fmt.Println("Thanks for trying OpenTUI Go!") -} - -// handleInput processes keyboard input and returns false to exit -func handleInput(demo *DemoState, key rune) bool { - switch key { - case 'q', 'Q': - return false - case 27: // ESC - return false - case '1': - if len(demo.Buttons) > 0 { - demo.Buttons[0].Click() - demo.StatusText = fmt.Sprintf("Triggered: %s #%d", - demo.Buttons[0].LogType, demo.Buttons[0].ClickCount) - } - case '2': - if len(demo.Buttons) > 1 { - demo.Buttons[1].Click() - demo.StatusText = fmt.Sprintf("Triggered: %s #%d", - demo.Buttons[1].LogType, demo.Buttons[1].ClickCount) - } - case '3': - if len(demo.Buttons) > 2 { - demo.Buttons[2].Click() - demo.StatusText = fmt.Sprintf("Triggered: %s #%d", - demo.Buttons[2].LogType, demo.Buttons[2].ClickCount) - } - case '4': - if len(demo.Buttons) > 3 { - demo.Buttons[3].Click() - demo.StatusText = fmt.Sprintf("Triggered: %s #%d", - demo.Buttons[3].LogType, demo.Buttons[3].ClickCount) - } - case '5': - if len(demo.Buttons) > 4 { - demo.Buttons[4].Click() - demo.StatusText = fmt.Sprintf("Triggered: %s #%d", - demo.Buttons[4].LogType, demo.Buttons[4].ClickCount) - } - } - return true -} - -// runSimpleDemo runs a simplified version with line-based input -func runSimpleDemo(demo *DemoState) { - input := NewSimpleInput() - - fmt.Println("Simple input mode - type commands and press Enter:") - fmt.Println("Commands: 1-5 (buttons), q (quit)") - - for demo.Running { - // Render interface - err := demo.Render() - if err != nil { - log.Printf("Render error: %v", err) - break - } - - fmt.Print("> ") - line, err := input.ReadLine() - if err != nil { - break - } - - line = strings.TrimSpace(line) - if line == "q" || line == "quit" { - break - } - - // Handle button commands - switch line { - case "1": - if len(demo.Buttons) > 0 { - demo.Buttons[0].Click() - demo.StatusText = fmt.Sprintf("Triggered: %s #%d", - demo.Buttons[0].LogType, demo.Buttons[0].ClickCount) - } - case "2": - if len(demo.Buttons) > 1 { - demo.Buttons[1].Click() - demo.StatusText = fmt.Sprintf("Triggered: %s #%d", - demo.Buttons[1].LogType, demo.Buttons[1].ClickCount) - } - case "3": - if len(demo.Buttons) > 2 { - demo.Buttons[2].Click() - demo.StatusText = fmt.Sprintf("Triggered: %s #%d", - demo.Buttons[2].LogType, demo.Buttons[2].ClickCount) - } - case "4": - if len(demo.Buttons) > 3 { - demo.Buttons[3].Click() - demo.StatusText = fmt.Sprintf("Triggered: %s #%d", - demo.Buttons[3].LogType, demo.Buttons[3].ClickCount) - } - case "5": - if len(demo.Buttons) > 4 { - demo.Buttons[4].Click() - demo.StatusText = fmt.Sprintf("Triggered: %s #%d", - demo.Buttons[4].LogType, demo.Buttons[4].ClickCount) - } - default: - fmt.Println("Unknown command. Try 1-5 for buttons, or 'q' to quit.") - } - } -} \ No newline at end of file diff --git a/packages/go/go.mod b/packages/go/go.mod deleted file mode 100644 index 74bf69ae9..000000000 --- a/packages/go/go.mod +++ /dev/null @@ -1,3 +0,0 @@ -module github.com/sst/opentui/packages/go - -go 1.21 diff --git a/packages/go/opentui.go b/packages/go/opentui.go deleted file mode 100644 index ad7382afe..000000000 --- a/packages/go/opentui.go +++ /dev/null @@ -1,152 +0,0 @@ -package opentui - -/* -#cgo pkg-config: opentui -#include -#include -*/ -import "C" -import ( - "runtime" - "unsafe" -) - -// Package opentui provides Go bindings for the OpenTUI terminal UI library. -// -// OpenTUI is a high-performance terminal user interface library built with Zig, -// providing advanced features like mouse support, transparency, and hardware-accelerated -// rendering through WebGPU. - -// init ensures proper library initialization and cleanup -func init() { - runtime.LockOSThread() -} - -// RGBA represents a color with red, green, blue, and alpha components. -// Each component is a float32 value between 0.0 and 1.0. -type RGBA struct { - R, G, B, A float32 -} - -// NewRGBA creates a new RGBA color. -func NewRGBA(r, g, b, a float32) RGBA { - return RGBA{R: r, G: g, B: b, A: a} -} - -// NewRGB creates a new RGBA color with alpha set to 1.0 (fully opaque). -func NewRGB(r, g, b float32) RGBA { - return RGBA{R: r, G: g, B: b, A: 1.0} -} - -// toCFloat converts RGBA to C float array -func (c RGBA) toCFloat() *C.float { - arr := [4]C.float{C.float(c.R), C.float(c.G), C.float(c.B), C.float(c.A)} - return (*C.float)(unsafe.Pointer(&arr[0])) -} - -// Common colors -var ( - Black = NewRGB(0, 0, 0) - White = NewRGB(1, 1, 1) - Red = NewRGB(1, 0, 0) - Green = NewRGB(0, 1, 0) - Blue = NewRGB(0, 0, 1) - Yellow = NewRGB(1, 1, 0) - Cyan = NewRGB(0, 1, 1) - Magenta = NewRGB(1, 0, 1) - Gray = NewRGB(0.5, 0.5, 0.5) - Transparent = NewRGBA(0, 0, 0, 0) -) - -// CursorStyle defines the cursor appearance -type CursorStyle string - -const ( - CursorBlock CursorStyle = "block" - CursorUnderline CursorStyle = "underline" - CursorBar CursorStyle = "bar" -) - -// DebugOverlayCorner defines where to position the debug overlay -type DebugOverlayCorner uint8 - -const ( - DebugTopLeft DebugOverlayCorner = iota - DebugTopRight - DebugBottomLeft - DebugBottomRight -) - -// SetCursorPosition sets the cursor position and visibility for a specific renderer. -func SetCursorPosition(renderer *Renderer, x, y int32, visible bool) { - if renderer == nil || renderer.ptr == nil { - return - } - C.setCursorPosition(renderer.ptr, C.int32_t(x), C.int32_t(y), C.bool(visible)) -} - -// SetCursorStyle sets the cursor style and blinking state for a specific renderer. -func SetCursorStyle(renderer *Renderer, style CursorStyle, blinking bool) { - if renderer == nil || renderer.ptr == nil { - return - } - cStyle := C.CString(string(style)) - defer C.free(unsafe.Pointer(cStyle)) - C.setCursorStyle(renderer.ptr, (*C.uint8_t)(unsafe.Pointer(cStyle)), C.size_t(len(style)), C.bool(blinking)) -} - -// SetCursorColor sets the cursor color for a specific renderer. -func SetCursorColor(renderer *Renderer, color RGBA) { - if renderer == nil || renderer.ptr == nil { - return - } - C.setCursorColor(renderer.ptr, color.toCFloat()) -} - -// stringToC converts a Go string to C string parameters -func stringToC(s string) (*C.uint8_t, C.size_t) { - if len(s) == 0 { - return nil, 0 - } - bytes := []byte(s) - return (*C.uint8_t)(unsafe.Pointer(&bytes[0])), C.size_t(len(bytes)) -} - -// BorderSides represents which sides of a box border to draw -type BorderSides struct { - Top bool - Right bool - Bottom bool - Left bool -} - -// packBorderOptions packs border options into a single uint32 -func packBorderOptions(sides BorderSides, fill bool, titleAlignment uint8) C.uint32_t { - var packed C.uint32_t - if sides.Top { - packed |= 0b1000 - } - if sides.Right { - packed |= 0b0100 - } - if sides.Bottom { - packed |= 0b0010 - } - if sides.Left { - packed |= 0b0001 - } - if fill { - packed |= (1 << 4) - } - packed |= C.uint32_t(titleAlignment&0b11) << 5 - return packed -} - -// TextAlignment defines text alignment options -type TextAlignment uint8 - -const ( - AlignLeft TextAlignment = iota - AlignCenter - AlignRight -) \ No newline at end of file diff --git a/packages/go/opentui.h b/packages/go/opentui.h deleted file mode 100644 index 77f12274b..000000000 --- a/packages/go/opentui.h +++ /dev/null @@ -1,120 +0,0 @@ -#ifndef OPENTUI_H -#define OPENTUI_H - -#ifdef __cplusplus -extern "C" { -#endif - -#include -#include -#include - -// Opaque type definitions -typedef struct CliRenderer CliRenderer; -typedef struct OptimizedBuffer OptimizedBuffer; -typedef struct TextBuffer TextBuffer; - -// Terminal capabilities structure -typedef struct { - bool supports_truecolor; - bool supports_mouse; - bool supports_kitty_keyboard; - bool supports_alternate_screen; -} Capabilities; - -// RGBA color type - array of 4 floats [r, g, b, a] -typedef float RGBA[4]; - -// Renderer management functions -CliRenderer* createRenderer(uint32_t width, uint32_t height); -void setUseThread(CliRenderer* renderer, bool useThread); -void destroyRenderer(CliRenderer* renderer, bool useAlternateScreen, uint32_t splitHeight); -void setBackgroundColor(CliRenderer* renderer, const float* color); -void setRenderOffset(CliRenderer* renderer, uint32_t offset); -void updateStats(CliRenderer* renderer, double time, uint32_t fps, double frameCallbackTime); -void updateMemoryStats(CliRenderer* renderer, uint32_t heapUsed, uint32_t heapTotal, uint32_t arrayBuffers); -OptimizedBuffer* getNextBuffer(CliRenderer* renderer); -OptimizedBuffer* getCurrentBuffer(CliRenderer* renderer); -void render(CliRenderer* renderer, bool force); -void resizeRenderer(CliRenderer* renderer, uint32_t width, uint32_t height); -void enableMouse(CliRenderer* renderer, bool enableMovement); -void disableMouse(CliRenderer* renderer); - -// Buffer management functions -OptimizedBuffer* createOptimizedBuffer(uint32_t width, uint32_t height, bool respectAlpha, uint8_t widthMethod); -void destroyOptimizedBuffer(OptimizedBuffer* buffer); -void destroyFrameBuffer(OptimizedBuffer* frameBuffer); -uint32_t getBufferWidth(OptimizedBuffer* buffer); -uint32_t getBufferHeight(OptimizedBuffer* buffer); - -// Buffer drawing functions -void bufferClear(OptimizedBuffer* buffer, const float* bg); -uint32_t* bufferGetCharPtr(OptimizedBuffer* buffer); -float* bufferGetFgPtr(OptimizedBuffer* buffer); -float* bufferGetBgPtr(OptimizedBuffer* buffer); -uint8_t* bufferGetAttributesPtr(OptimizedBuffer* buffer); -bool bufferGetRespectAlpha(OptimizedBuffer* buffer); -void bufferSetRespectAlpha(OptimizedBuffer* buffer, bool respectAlpha); -void bufferDrawText(OptimizedBuffer* buffer, const uint8_t* text, size_t textLen, uint32_t x, uint32_t y, const float* fg, const float* bg, uint8_t attributes); -void bufferSetCellWithAlphaBlending(OptimizedBuffer* buffer, uint32_t x, uint32_t y, uint32_t char_code, const float* fg, const float* bg, uint8_t attributes); -void bufferFillRect(OptimizedBuffer* buffer, uint32_t x, uint32_t y, uint32_t width, uint32_t height, const float* bg); -void bufferDrawPackedBuffer(OptimizedBuffer* buffer, const uint8_t* data, size_t dataLen, uint32_t posX, uint32_t posY, uint32_t terminalWidthCells, uint32_t terminalHeightCells); -void bufferDrawSuperSampleBuffer(OptimizedBuffer* buffer, uint32_t x, uint32_t y, const uint8_t* pixelData, size_t len, uint8_t format, uint32_t alignedBytesPerRow); -void bufferDrawBox(OptimizedBuffer* buffer, int32_t x, int32_t y, uint32_t width, uint32_t height, const uint32_t* borderChars, uint32_t packedOptions, const float* borderColor, const float* backgroundColor, const uint8_t* title, uint32_t titleLen); -void bufferResize(OptimizedBuffer* buffer, uint32_t width, uint32_t height); -void drawFrameBuffer(OptimizedBuffer* target, int32_t destX, int32_t destY, OptimizedBuffer* frameBuffer, uint32_t sourceX, uint32_t sourceY, uint32_t sourceWidth, uint32_t sourceHeight); - -// Cursor functions -void setCursorPosition(CliRenderer* renderer, int32_t x, int32_t y, bool visible); -void setCursorStyle(CliRenderer* renderer, const uint8_t* style, size_t styleLen, bool blinking); -void setCursorColor(CliRenderer* renderer, const float* color); - -// Terminal capability functions -void getTerminalCapabilities(CliRenderer* renderer, Capabilities* caps); -void processCapabilityResponse(CliRenderer* renderer, const uint8_t* response, size_t responseLen); - -// Debug and utility functions -void setDebugOverlay(CliRenderer* renderer, bool enabled, uint8_t corner); -void clearTerminal(CliRenderer* renderer); -void addToHitGrid(CliRenderer* renderer, int32_t x, int32_t y, uint32_t width, uint32_t height, uint32_t id); -uint32_t checkHit(CliRenderer* renderer, uint32_t x, uint32_t y); -void dumpHitGrid(CliRenderer* renderer); -void dumpBuffers(CliRenderer* renderer, int64_t timestamp); -void dumpStdoutBuffer(CliRenderer* renderer, int64_t timestamp); - -// Keyboard and terminal setup functions -void enableKittyKeyboard(CliRenderer* renderer, uint8_t flags); -void disableKittyKeyboard(CliRenderer* renderer); -void setupTerminal(CliRenderer* renderer, bool useAlternateScreen); - -// TextBuffer functions -TextBuffer* createTextBuffer(uint32_t length, uint8_t widthMethod); -void destroyTextBuffer(TextBuffer* textBuffer); -uint32_t* textBufferGetCharPtr(TextBuffer* textBuffer); -float* textBufferGetFgPtr(TextBuffer* textBuffer); -float* textBufferGetBgPtr(TextBuffer* textBuffer); -uint16_t* textBufferGetAttributesPtr(TextBuffer* textBuffer); -uint32_t textBufferGetLength(TextBuffer* textBuffer); -void textBufferSetCell(TextBuffer* textBuffer, uint32_t index, uint32_t char_code, const float* fg, const float* bg, uint16_t attr); -TextBuffer* textBufferConcat(TextBuffer* tb1, TextBuffer* tb2); -void textBufferResize(TextBuffer* textBuffer, uint32_t newLength); -void textBufferReset(TextBuffer* textBuffer); -void textBufferSetSelection(TextBuffer* textBuffer, uint32_t start, uint32_t end, const float* bgColor, const float* fgColor); -void textBufferResetSelection(TextBuffer* textBuffer); -void textBufferSetDefaultFg(TextBuffer* textBuffer, const float* fg); -void textBufferSetDefaultBg(TextBuffer* textBuffer, const float* bg); -void textBufferSetDefaultAttributes(TextBuffer* textBuffer, const uint8_t* attr); -void textBufferResetDefaults(TextBuffer* textBuffer); -uint32_t textBufferWriteChunk(TextBuffer* textBuffer, const uint8_t* textBytes, uint32_t textLen, const float* fg, const float* bg, const uint8_t* attr); -uint32_t textBufferGetCapacity(TextBuffer* textBuffer); -void textBufferFinalizeLineInfo(TextBuffer* textBuffer); -const uint32_t* textBufferGetLineStartsPtr(TextBuffer* textBuffer); -const uint32_t* textBufferGetLineWidthsPtr(TextBuffer* textBuffer); -uint32_t textBufferGetLineCount(TextBuffer* textBuffer); -void bufferDrawTextBuffer(OptimizedBuffer* buffer, TextBuffer* textBuffer, int32_t x, int32_t y, int32_t clipX, int32_t clipY, uint32_t clipWidth, uint32_t clipHeight, bool hasClipRect); - -#ifdef __cplusplus -} -#endif - -#endif // OPENTUI_H \ No newline at end of file diff --git a/packages/go/opentui_test.go b/packages/go/opentui_test.go deleted file mode 100644 index b42bf3126..000000000 --- a/packages/go/opentui_test.go +++ /dev/null @@ -1,356 +0,0 @@ -package opentui - -import ( - "testing" -) - -func TestRGBA(t *testing.T) { - // Test RGBA creation - color := NewRGBA(1.0, 0.5, 0.25, 0.8) - if color.R != 1.0 || color.G != 0.5 || color.B != 0.25 || color.A != 0.8 { - t.Errorf("RGBA values incorrect: got %+v", color) - } - - // Test RGB creation (alpha should be 1.0) - rgb := NewRGB(0.2, 0.4, 0.6) - if rgb.R != 0.2 || rgb.G != 0.4 || rgb.B != 0.6 || rgb.A != 1.0 { - t.Errorf("RGB values incorrect: got %+v", rgb) - } - - // Test predefined colors - if Black.R != 0 || Black.G != 0 || Black.B != 0 || Black.A != 1.0 { - t.Errorf("Black color incorrect: got %+v", Black) - } - - if White.R != 1 || White.G != 1 || White.B != 1 || White.A != 1.0 { - t.Errorf("White color incorrect: got %+v", White) - } -} - -func TestBorderSides(t *testing.T) { - sides := BorderSides{Top: true, Right: false, Bottom: true, Left: false} - packed := packBorderOptions(sides, true, uint8(AlignCenter)) - - // Check that the packing worked (this is internal but we can verify the function doesn't crash) - if packed == 0 { - t.Error("packBorderOptions returned 0, which seems incorrect") - } -} - -func TestRenderer(t *testing.T) { - // Test renderer creation - renderer := NewRenderer(80, 24) - if renderer == nil { - t.Skip("Skipping renderer test - OpenTUI library not available (this is expected in CI)") - } - defer renderer.Close() - - // Test that renderer is valid - if !renderer.Valid() { - t.Error("Renderer should be valid after creation") - } - - // Test basic operations - err := renderer.SetBackgroundColor(Blue) - if err != nil { - t.Errorf("SetBackgroundColor failed: %v", err) - } - - err = renderer.SetRenderOffset(1) - if err != nil { - t.Errorf("SetRenderOffset failed: %v", err) - } - - // Test getting buffer - buffer, err := renderer.GetNextBuffer() - if err != nil { - t.Errorf("GetNextBuffer failed: %v", err) - } - if buffer == nil { - t.Error("GetNextBuffer returned nil buffer") - } - - // Test buffer operations - if buffer != nil { - width, height, err := buffer.Size() - if err != nil { - t.Errorf("Buffer Size failed: %v", err) - } - if width != 80 || height != 24 { - t.Errorf("Buffer size incorrect: got %dx%d, want 80x24", width, height) - } - - // Test buffer clear - err = buffer.Clear(Green) - if err != nil { - t.Errorf("Buffer Clear failed: %v", err) - } - } - - // Test mouse functions (should work now with the updated library) - err = renderer.EnableMouse(true) - if err != nil { - t.Errorf("EnableMouse failed: %v", err) - } - - err = renderer.DisableMouse() - if err != nil { - t.Errorf("DisableMouse failed: %v", err) - } - - // Test renderer close - err = renderer.Close() - if err != nil { - t.Errorf("Renderer Close failed: %v", err) - } - - // Test that renderer is invalid after close - if renderer.Valid() { - t.Error("Renderer should be invalid after close") - } -} - -func TestRendererInvalidDimensions(t *testing.T) { - // Test creation with invalid dimensions - renderer := NewRenderer(0, 24) - if renderer != nil { - defer renderer.Close() - t.Error("NewRenderer should return nil for zero width") - } - - renderer = NewRenderer(80, 0) - if renderer != nil { - defer renderer.Close() - t.Error("NewRenderer should return nil for zero height") - } -} - -func TestBuffer(t *testing.T) { - // Test buffer creation - buffer := NewBuffer(40, 20, true, WidthMethodUnicode) - if buffer == nil { - t.Skip("Skipping buffer test - OpenTUI library not available") - } - defer buffer.Close() - - // Test buffer is valid - if !buffer.Valid() { - t.Error("Buffer should be valid after creation") - } - - // Test buffer dimensions - width, height, err := buffer.Size() - if err != nil { - t.Errorf("Buffer Size failed: %v", err) - } - if width != 40 || height != 20 { - t.Errorf("Buffer size incorrect: got %dx%d, want 40x20", width, height) - } - - // Test alpha respect setting - respectAlpha, err := buffer.GetRespectAlpha() - if err != nil { - t.Errorf("GetRespectAlpha failed: %v", err) - } - if !respectAlpha { - t.Error("Buffer should respect alpha as requested in constructor") - } - - // Test setting alpha respect - err = buffer.SetRespectAlpha(false) - if err != nil { - t.Errorf("SetRespectAlpha failed: %v", err) - } - - respectAlpha, err = buffer.GetRespectAlpha() - if err != nil { - t.Errorf("GetRespectAlpha failed after set: %v", err) - } - if respectAlpha { - t.Error("Buffer should not respect alpha after setting to false") - } - - // Test buffer operations - err = buffer.Clear(Red) - if err != nil { - t.Errorf("Buffer Clear failed: %v", err) - } - - err = buffer.DrawText("Test", 5, 5, White, &Black, AttrBold) - if err != nil { - t.Errorf("DrawText failed: %v", err) - } - - err = buffer.FillRect(10, 10, 5, 3, Blue) - if err != nil { - t.Errorf("FillRect failed: %v", err) - } - - err = buffer.SetCellWithAlphaBlending(15, 15, 'A', Yellow, Green, AttrItalic) - if err != nil { - t.Errorf("SetCellWithAlphaBlending failed: %v", err) - } - - // Test buffer close - err = buffer.Close() - if err != nil { - t.Errorf("Buffer Close failed: %v", err) - } - - // Test that buffer is invalid after close - if buffer.Valid() { - t.Error("Buffer should be invalid after close") - } -} - -func TestBufferInvalidDimensions(t *testing.T) { - // Test creation with invalid dimensions - buffer := NewBuffer(0, 20, false, WidthMethodUnicode) - if buffer != nil { - defer buffer.Close() - t.Error("NewBuffer should return nil for zero width") - } - - buffer = NewBuffer(40, 0, false, WidthMethodUnicode) - if buffer != nil { - defer buffer.Close() - t.Error("NewBuffer should return nil for zero height") - } -} - -func TestTextBuffer(t *testing.T) { - // Test text buffer creation - textBuffer := NewTextBuffer(100, WidthMethodUnicode) - if textBuffer == nil { - t.Skip("Skipping text buffer test - OpenTUI library not available") - } - defer textBuffer.Close() - - // Test text buffer is valid - if !textBuffer.Valid() { - t.Error("TextBuffer should be valid after creation") - } - - // Test initial state - length, err := textBuffer.Length() - if err != nil { - t.Errorf("TextBuffer Length failed: %v", err) - } - if length != 0 { - t.Errorf("TextBuffer should start empty, got length %d", length) - } - - capacity, err := textBuffer.Capacity() - if err != nil { - t.Errorf("TextBuffer Capacity failed: %v", err) - } - if capacity < 100 { - t.Errorf("TextBuffer capacity should be at least 100, got %d", capacity) - } - - // Test writing chunks - chunk := TextChunk{ - Text: "Hello, ", - Foreground: &Red, - Background: &Black, - } - written, err := textBuffer.WriteChunk(chunk) - if err != nil { - t.Errorf("WriteChunk failed: %v", err) - } - if written == 0 { - t.Error("WriteChunk should have written some characters") - } - - // Test writing string - written2, err := textBuffer.WriteString("World!") - if err != nil { - t.Errorf("WriteString failed: %v", err) - } - if written2 == 0 { - t.Error("WriteString should have written some characters") - } - - // Test final length - finalLength, err := textBuffer.Length() - if err != nil { - t.Errorf("TextBuffer Length failed after writes: %v", err) - } - expectedLength := written + written2 - if finalLength != expectedLength { - // Length might differ due to UTF-8 encoding - just check that something was written - t.Logf("TextBuffer length: expected %d, got %d (this may be due to UTF-8 encoding)", expectedLength, finalLength) - if finalLength == 0 { - t.Error("TextBuffer should not be empty after writing text") - } - } - - // Test reset - err = textBuffer.Reset() - if err != nil { - t.Errorf("TextBuffer Reset failed: %v", err) - } - - lengthAfterReset, err := textBuffer.Length() - if err != nil { - t.Errorf("TextBuffer Length failed after reset: %v", err) - } - if lengthAfterReset != 0 { - t.Errorf("TextBuffer should be empty after reset, got length %d", lengthAfterReset) - } - - // Test text buffer close - err = textBuffer.Close() - if err != nil { - t.Errorf("TextBuffer Close failed: %v", err) - } - - // Test that text buffer is invalid after close - if textBuffer.Valid() { - t.Error("TextBuffer should be invalid after close") - } -} - -func TestGlobalCursorFunctions(t *testing.T) { - // Test that cursor functions don't panic - // We can't easily test their effects, but we can ensure they don't crash - renderer := NewRenderer(80, 24) - if renderer == nil { - t.Skip("Skipping cursor test - OpenTUI library not available") - } - defer renderer.Close() - - SetCursorPosition(renderer, 10, 5, true) - SetCursorStyle(renderer, CursorBlock, false) - SetCursorColor(renderer, Green) - - // Also test renderer methods - renderer.SetCursorPosition(15, 10, true) - renderer.SetCursorStyle(CursorUnderline, true) - renderer.SetCursorColor(Red) - - // If we get here without panicking, the test passes -} - -func TestConstants(t *testing.T) { - // Test that text attribute constants have expected values - if AttrBold == 0 { - t.Error("AttrBold should not be 0") - } - if AttrItalic == 0 { - t.Error("AttrItalic should not be 0") - } - if AttrUnderline == 0 { - t.Error("AttrUnderline should not be 0") - } - - // Test that different attributes have different values - if AttrBold == AttrItalic { - t.Error("AttrBold and AttrItalic should have different values") - } - - // Test cursor style constants - if CursorBlock == CursorUnderline { - t.Error("CursorBlock and CursorUnderline should have different values") - } -} \ No newline at end of file diff --git a/packages/go/renderer.go b/packages/go/renderer.go deleted file mode 100644 index 990a7cabf..000000000 --- a/packages/go/renderer.go +++ /dev/null @@ -1,336 +0,0 @@ -package opentui - -/* -#include "opentui.h" -#include -*/ -import "C" -import ( - "unsafe" -) - -// Renderer wraps the CliRenderer from the C library. -// It provides high-level access to terminal rendering functionality. -type Renderer struct { - ptr *C.CliRenderer -} - -// NewRenderer creates a new renderer with the specified dimensions. -// Returns nil if the renderer could not be created. -func NewRenderer(width, height uint32) *Renderer { - if width == 0 || height == 0 { - return nil - } - - ptr := C.createRenderer(C.uint32_t(width), C.uint32_t(height)) - if ptr == nil { - return nil - } - - r := &Renderer{ptr: ptr} - setFinalizer(r, func(r *Renderer) { r.Close() }) - return r -} - -// Close destroys the renderer and releases its resources. -// After calling Close, the renderer should not be used. -func (r *Renderer) Close() error { - if r.ptr != nil { - clearFinalizer(r) - C.destroyRenderer(r.ptr, C.bool(false), C.uint32_t(0)) - r.ptr = nil - } - return nil -} - -// CloseWithOptions destroys the renderer with specific cleanup options. -func (r *Renderer) CloseWithOptions(useAlternateScreen bool, splitHeight uint32) error { - if r.ptr != nil { - clearFinalizer(r) - C.destroyRenderer(r.ptr, C.bool(useAlternateScreen), C.uint32_t(splitHeight)) - r.ptr = nil - } - return nil -} - -// SetUseThread enables or disables threaded rendering. -func (r *Renderer) SetUseThread(useThread bool) error { - if r.ptr == nil { - return newError("renderer is closed") - } - C.setUseThread(r.ptr, C.bool(useThread)) - return nil -} - -// SetBackgroundColor sets the global background color for the renderer. -func (r *Renderer) SetBackgroundColor(color RGBA) error { - if r.ptr == nil { - return newError("renderer is closed") - } - C.setBackgroundColor(r.ptr, color.toCFloat()) - return nil -} - -// SetRenderOffset sets the vertical offset for rendering. -func (r *Renderer) SetRenderOffset(offset uint32) error { - if r.ptr == nil { - return newError("renderer is closed") - } - C.setRenderOffset(r.ptr, C.uint32_t(offset)) - return nil -} - -// UpdateStats updates the renderer's performance statistics. -func (r *Renderer) UpdateStats(stats Stats) error { - if r.ptr == nil { - return newError("renderer is closed") - } - C.updateStats(r.ptr, C.double(stats.Time), C.uint32_t(stats.FPS), C.double(stats.FrameCallbackTime)) - return nil -} - -// UpdateMemoryStats updates the renderer's memory usage statistics. -func (r *Renderer) UpdateMemoryStats(stats MemoryStats) error { - if r.ptr == nil { - return newError("renderer is closed") - } - C.updateMemoryStats(r.ptr, C.uint32_t(stats.HeapUsed), C.uint32_t(stats.HeapTotal), C.uint32_t(stats.ArrayBuffers)) - return nil -} - -// GetNextBuffer returns the next buffer for rendering. -// This buffer can be used to draw content that will be displayed on the next render. -func (r *Renderer) GetNextBuffer() (*Buffer, error) { - if r.ptr == nil { - return nil, newError("renderer is closed") - } - - bufferPtr := C.getNextBuffer(r.ptr) - if bufferPtr == nil { - return nil, newError("failed to get next buffer") - } - - // Don't set a finalizer for buffers obtained from renderer, - // they are managed by the renderer itself - return &Buffer{ptr: bufferPtr, managed: true}, nil -} - -// GetCurrentBuffer returns the current buffer being rendered. -func (r *Renderer) GetCurrentBuffer() (*Buffer, error) { - if r.ptr == nil { - return nil, newError("renderer is closed") - } - - bufferPtr := C.getCurrentBuffer(r.ptr) - if bufferPtr == nil { - return nil, newError("failed to get current buffer") - } - - return &Buffer{ptr: bufferPtr, managed: true}, nil -} - -// Render renders the current buffer to the terminal. -// If force is true, forces a complete re-render even if nothing has changed. -func (r *Renderer) Render(force bool) error { - if r.ptr == nil { - return newError("renderer is closed") - } - C.render(r.ptr, C.bool(force)) - return nil -} - -// Resize changes the renderer dimensions. -func (r *Renderer) Resize(width, height uint32) error { - if r.ptr == nil { - return newError("renderer is closed") - } - if width == 0 || height == 0 { - return newError("invalid dimensions") - } - C.resizeRenderer(r.ptr, C.uint32_t(width), C.uint32_t(height)) - return nil -} - -// EnableMouse enables mouse tracking. -// If enableMovement is true, also tracks mouse movement events. -func (r *Renderer) EnableMouse(enableMovement bool) error { - if r.ptr == nil { - return newError("renderer is closed") - } - C.enableMouse(r.ptr, C.bool(enableMovement)) - return nil -} - -// DisableMouse disables mouse tracking. -func (r *Renderer) DisableMouse() error { - if r.ptr == nil { - return newError("renderer is closed") - } - C.disableMouse(r.ptr) - return nil -} - -// SetDebugOverlay enables or disables the debug overlay. -func (r *Renderer) SetDebugOverlay(enabled bool, corner DebugOverlayCorner) error { - if r.ptr == nil { - return newError("renderer is closed") - } - C.setDebugOverlay(r.ptr, C.bool(enabled), C.uint8_t(corner)) - return nil -} - -// ClearTerminal clears the terminal screen. -func (r *Renderer) ClearTerminal() error { - if r.ptr == nil { - return newError("renderer is closed") - } - C.clearTerminal(r.ptr) - return nil -} - -// AddToHitGrid adds a rectangular area to the mouse hit testing grid. -// When the mouse is clicked in this area, the specified ID will be returned. -func (r *Renderer) AddToHitGrid(x, y int32, width, height, id uint32) error { - if r.ptr == nil { - return newError("renderer is closed") - } - C.addToHitGrid(r.ptr, C.int32_t(x), C.int32_t(y), C.uint32_t(width), C.uint32_t(height), C.uint32_t(id)) - return nil -} - -// CheckHit performs a hit test at the specified coordinates. -// Returns the ID of the hit area, or 0 if no hit was found. -func (r *Renderer) CheckHit(x, y uint32) (uint32, error) { - if r.ptr == nil { - return 0, newError("renderer is closed") - } - id := C.checkHit(r.ptr, C.uint32_t(x), C.uint32_t(y)) - return uint32(id), nil -} - -// DumpHitGrid outputs debug information about the hit testing grid. -func (r *Renderer) DumpHitGrid() error { - if r.ptr == nil { - return newError("renderer is closed") - } - C.dumpHitGrid(r.ptr) - return nil -} - -// DumpBuffers outputs debug information about the renderer buffers. -func (r *Renderer) DumpBuffers(timestamp int64) error { - if r.ptr == nil { - return newError("renderer is closed") - } - C.dumpBuffers(r.ptr, C.int64_t(timestamp)) - return nil -} - -// DumpStdoutBuffer outputs debug information about the stdout buffer. -func (r *Renderer) DumpStdoutBuffer(timestamp int64) error { - if r.ptr == nil { - return newError("renderer is closed") - } - C.dumpStdoutBuffer(r.ptr, C.int64_t(timestamp)) - return nil -} - -// GetTerminalCapabilities returns the current terminal capabilities. -func (r *Renderer) GetTerminalCapabilities() (*Capabilities, error) { - if r.ptr == nil { - return nil, newError("renderer is closed") - } - - var caps C.Capabilities - C.getTerminalCapabilities(r.ptr, &caps) - - return &Capabilities{ - SupportsTruecolor: bool(caps.supports_truecolor), - SupportsMouse: bool(caps.supports_mouse), - SupportsKittyKeyboard: bool(caps.supports_kitty_keyboard), - SupportsAlternateScreen: bool(caps.supports_alternate_screen), - }, nil -} - -// ProcessCapabilityResponse processes a terminal capability response. -func (r *Renderer) ProcessCapabilityResponse(response []byte) error { - if r.ptr == nil { - return newError("renderer is closed") - } - if len(response) == 0 { - return nil - } - - responsePtr, responseLen := sliceToC(response) - C.processCapabilityResponse(r.ptr, (*C.uint8_t)(responsePtr), C.size_t(responseLen)) - return nil -} - -// EnableKittyKeyboard enables the Kitty keyboard protocol with the specified flags. -func (r *Renderer) EnableKittyKeyboard(flags uint8) error { - if r.ptr == nil { - return newError("renderer is closed") - } - C.enableKittyKeyboard(r.ptr, C.uint8_t(flags)) - return nil -} - -// DisableKittyKeyboard disables the Kitty keyboard protocol. -func (r *Renderer) DisableKittyKeyboard() error { - if r.ptr == nil { - return newError("renderer is closed") - } - C.disableKittyKeyboard(r.ptr) - return nil -} - -// SetupTerminal sets up the terminal with optional alternate screen buffer. -func (r *Renderer) SetupTerminal(useAlternateScreen bool) error { - if r.ptr == nil { - return newError("renderer is closed") - } - C.setupTerminal(r.ptr, C.bool(useAlternateScreen)) - return nil -} - -// SetCursorPosition sets the cursor position and visibility. -func (r *Renderer) SetCursorPosition(x, y int32, visible bool) error { - if r.ptr == nil { - return newError("renderer is closed") - } - C.setCursorPosition(r.ptr, C.int32_t(x), C.int32_t(y), C.bool(visible)) - return nil -} - -// SetCursorStyle sets the cursor style and blinking state. -func (r *Renderer) SetCursorStyle(style CursorStyle, blinking bool) error { - if r.ptr == nil { - return newError("renderer is closed") - } - cStyle := C.CString(string(style)) - defer C.free(unsafe.Pointer(cStyle)) - C.setCursorStyle(r.ptr, (*C.uint8_t)(unsafe.Pointer(cStyle)), C.size_t(len(style)), C.bool(blinking)) - return nil -} - -// SetCursorColor sets the cursor color. -func (r *Renderer) SetCursorColor(color RGBA) error { - if r.ptr == nil { - return newError("renderer is closed") - } - C.setCursorColor(r.ptr, color.toCFloat()) - return nil -} - -// Valid checks if the renderer is still valid (not closed). -func (r *Renderer) Valid() bool { - return r.ptr != nil -} - -// ensureRenderer is a helper that checks if renderer is valid -func (r *Renderer) ensureValid() error { - if r.ptr == nil { - return newError("renderer is closed") - } - return nil -} \ No newline at end of file diff --git a/packages/go/textbuffer.go b/packages/go/textbuffer.go deleted file mode 100644 index 0ca9b81fa..000000000 --- a/packages/go/textbuffer.go +++ /dev/null @@ -1,362 +0,0 @@ -package opentui - -/* -#include "opentui.h" -#include -*/ -import "C" -import ( - "unsafe" -) - -// TextBuffer wraps the TextBuffer from the C library. -// It represents a buffer of styled text fragments with efficient line tracking. -type TextBuffer struct { - ptr *C.TextBuffer -} - -// NewTextBuffer creates a new text buffer with the specified initial capacity. -// The widthMethod parameter controls how text width is calculated (use WidthMethodUnicode for full Unicode support). -func NewTextBuffer(length uint32, widthMethod uint8) *TextBuffer { - if length == 0 { - length = 1024 // Default capacity - } - - ptr := C.createTextBuffer(C.uint32_t(length), C.uint8_t(widthMethod)) - if ptr == nil { - return nil - } - - tb := &TextBuffer{ptr: ptr} - setFinalizer(tb, func(tb *TextBuffer) { tb.Close() }) - return tb -} - -// Close releases the text buffer's resources. -// After calling Close, the text buffer should not be used. -func (tb *TextBuffer) Close() error { - if tb.ptr != nil { - clearFinalizer(tb) - C.destroyTextBuffer(tb.ptr) - tb.ptr = nil - } - return nil -} - -// Length returns the current length of the text buffer in characters. -func (tb *TextBuffer) Length() (uint32, error) { - if tb.ptr == nil { - return 0, newError("text buffer is closed") - } - return uint32(C.textBufferGetLength(tb.ptr)), nil -} - -// Capacity returns the current capacity of the text buffer. -func (tb *TextBuffer) Capacity() (uint32, error) { - if tb.ptr == nil { - return 0, newError("text buffer is closed") - } - return uint32(C.textBufferGetCapacity(tb.ptr)), nil -} - -// SetCell sets a single character at the specified index with styling. -func (tb *TextBuffer) SetCell(index uint32, char rune, fg, bg RGBA, attributes uint16) error { - if tb.ptr == nil { - return newError("text buffer is closed") - } - C.textBufferSetCell(tb.ptr, C.uint32_t(index), C.uint32_t(char), fg.toCFloat(), bg.toCFloat(), C.uint16_t(attributes)) - return nil -} - -// WriteChunk appends a text chunk with optional styling to the buffer. -// Returns the number of characters written. -func (tb *TextBuffer) WriteChunk(chunk TextChunk) (uint32, error) { - if tb.ptr == nil { - return 0, newError("text buffer is closed") - } - - textPtr, textLen := stringToC(chunk.Text) - if textPtr == nil { - return 0, nil // Empty string - } - - var fgPtr, bgPtr *C.float - var attrPtr *C.uint8_t - - if chunk.Foreground != nil { - fgPtr = chunk.Foreground.toCFloat() - } - if chunk.Background != nil { - bgPtr = chunk.Background.toCFloat() - } - if chunk.Attributes != nil { - attrPtr = (*C.uint8_t)(unsafe.Pointer(chunk.Attributes)) - } - - written := C.textBufferWriteChunk(tb.ptr, textPtr, C.uint32_t(textLen), fgPtr, bgPtr, attrPtr) - return uint32(written), nil -} - -// WriteString is a convenience method to write a string with default styling. -func (tb *TextBuffer) WriteString(text string) (uint32, error) { - return tb.WriteChunk(TextChunk{Text: text}) -} - -// WriteStyledString writes a string with the specified colors and attributes. -func (tb *TextBuffer) WriteStyledString(text string, fg, bg *RGBA, attributes *uint8) (uint32, error) { - return tb.WriteChunk(TextChunk{ - Text: text, - Foreground: fg, - Background: bg, - Attributes: attributes, - }) -} - -// Concat concatenates this text buffer with another text buffer. -// Returns a new text buffer containing the combined content. -func (tb *TextBuffer) Concat(other *TextBuffer) (*TextBuffer, error) { - if tb.ptr == nil { - return nil, newError("text buffer is closed") - } - if other == nil || other.ptr == nil { - return nil, newError("other text buffer is nil or closed") - } - - resultPtr := C.textBufferConcat(tb.ptr, other.ptr) - if resultPtr == nil { - return nil, newError("failed to concatenate text buffers") - } - - result := &TextBuffer{ptr: resultPtr} - setFinalizer(result, func(tb *TextBuffer) { tb.Close() }) - return result, nil -} - -// Resize changes the capacity of the text buffer. -func (tb *TextBuffer) Resize(newLength uint32) error { - if tb.ptr == nil { - return newError("text buffer is closed") - } - C.textBufferResize(tb.ptr, C.uint32_t(newLength)) - return nil -} - -// Reset clears the text buffer content while preserving capacity. -func (tb *TextBuffer) Reset() error { - if tb.ptr == nil { - return newError("text buffer is closed") - } - C.textBufferReset(tb.ptr) - return nil -} - -// SetSelection sets a text selection range with optional highlighting colors. -func (tb *TextBuffer) SetSelection(start, end uint32, bgColor, fgColor *RGBA) error { - if tb.ptr == nil { - return newError("text buffer is closed") - } - - var bgPtr, fgPtr *C.float - if bgColor != nil { - bgPtr = bgColor.toCFloat() - } - if fgColor != nil { - fgPtr = fgColor.toCFloat() - } - - C.textBufferSetSelection(tb.ptr, C.uint32_t(start), C.uint32_t(end), bgPtr, fgPtr) - return nil -} - -// ResetSelection clears any active text selection. -func (tb *TextBuffer) ResetSelection() error { - if tb.ptr == nil { - return newError("text buffer is closed") - } - C.textBufferResetSelection(tb.ptr) - return nil -} - -// SetDefaultForeground sets the default foreground color for new text. -func (tb *TextBuffer) SetDefaultForeground(fg *RGBA) error { - if tb.ptr == nil { - return newError("text buffer is closed") - } - - var fgPtr *C.float - if fg != nil { - fgPtr = fg.toCFloat() - } - - C.textBufferSetDefaultFg(tb.ptr, fgPtr) - return nil -} - -// SetDefaultBackground sets the default background color for new text. -func (tb *TextBuffer) SetDefaultBackground(bg *RGBA) error { - if tb.ptr == nil { - return newError("text buffer is closed") - } - - var bgPtr *C.float - if bg != nil { - bgPtr = bg.toCFloat() - } - - C.textBufferSetDefaultBg(tb.ptr, bgPtr) - return nil -} - -// SetDefaultAttributes sets the default text attributes for new text. -func (tb *TextBuffer) SetDefaultAttributes(attributes *uint8) error { - if tb.ptr == nil { - return newError("text buffer is closed") - } - - var attrPtr *C.uint8_t - if attributes != nil { - attrPtr = (*C.uint8_t)(unsafe.Pointer(attributes)) - } - - C.textBufferSetDefaultAttributes(tb.ptr, attrPtr) - return nil -} - -// ResetDefaults clears all default styling settings. -func (tb *TextBuffer) ResetDefaults() error { - if tb.ptr == nil { - return newError("text buffer is closed") - } - C.textBufferResetDefaults(tb.ptr) - return nil -} - -// FinalizeLineInfo processes the text buffer to generate line information. -// This should be called after adding text and before querying line information. -func (tb *TextBuffer) FinalizeLineInfo() error { - if tb.ptr == nil { - return newError("text buffer is closed") - } - C.textBufferFinalizeLineInfo(tb.ptr) - return nil -} - -// LineCount returns the number of lines in the text buffer. -// FinalizeLineInfo must be called first. -func (tb *TextBuffer) LineCount() (uint32, error) { - if tb.ptr == nil { - return 0, newError("text buffer is closed") - } - return uint32(C.textBufferGetLineCount(tb.ptr)), nil -} - -// GetLineInfo returns information about all lines in the text buffer. -// FinalizeLineInfo must be called first. -func (tb *TextBuffer) GetLineInfo() ([]LineInfo, error) { - if tb.ptr == nil { - return nil, newError("text buffer is closed") - } - - lineCount := uint32(C.textBufferGetLineCount(tb.ptr)) - if lineCount == 0 { - return []LineInfo{}, nil - } - - startsPtr := C.textBufferGetLineStartsPtr(tb.ptr) - widthsPtr := C.textBufferGetLineWidthsPtr(tb.ptr) - - starts := cArrayToSlice((*uint32)(startsPtr), int(lineCount)) - widths := cArrayToSlice((*uint32)(widthsPtr), int(lineCount)) - - lines := make([]LineInfo, lineCount) - for i := uint32(0); i < lineCount; i++ { - lines[i] = LineInfo{ - StartIndex: starts[i], - Width: widths[i], - } - } - - return lines, nil -} - -// GetDirectAccess returns direct access to the text buffer's internal arrays. -// This is an advanced feature for performance-critical operations. -func (tb *TextBuffer) GetDirectAccess() (*TextBufferDirectAccess, error) { - if tb.ptr == nil { - return nil, newError("text buffer is closed") - } - - length := uint32(C.textBufferGetLength(tb.ptr)) - if length == 0 { - return &TextBufferDirectAccess{ - Chars: []uint32{}, - Foreground: []RGBA{}, - Background: []RGBA{}, - Attributes: []uint16{}, - Length: 0, - }, nil - } - - charPtr := C.textBufferGetCharPtr(tb.ptr) - fgPtr := C.textBufferGetFgPtr(tb.ptr) - bgPtr := C.textBufferGetBgPtr(tb.ptr) - attrPtr := C.textBufferGetAttributesPtr(tb.ptr) - - return &TextBufferDirectAccess{ - Chars: cArrayToSlice((*uint32)(charPtr), int(length)), - Foreground: cArrayToSlice((*RGBA)(unsafe.Pointer(fgPtr)), int(length)), - Background: cArrayToSlice((*RGBA)(unsafe.Pointer(bgPtr)), int(length)), - Attributes: cArrayToSlice((*uint16)(attrPtr), int(length)), - Length: length, - }, nil -} - -// TextBufferDirectAccess provides direct access to text buffer internal arrays. -type TextBufferDirectAccess struct { - Chars []uint32 // Character codes (Unicode code points) - Foreground []RGBA // Foreground colors - Background []RGBA // Background colors - Attributes []uint16 // Text attributes - Length uint32 // Buffer length -} - -// GetChar returns the character at the specified index. -func (da *TextBufferDirectAccess) GetChar(index uint32) (rune, error) { - if index >= da.Length { - return 0, newError("index out of bounds") - } - return rune(da.Chars[index]), nil -} - -// SetChar sets the character at the specified index. -func (da *TextBufferDirectAccess) SetChar(index uint32, char rune) error { - if index >= da.Length { - return newError("index out of bounds") - } - da.Chars[index] = uint32(char) - return nil -} - -// GetStyle returns the styling at the specified index. -func (da *TextBufferDirectAccess) GetStyle(index uint32) (RGBA, RGBA, uint16, error) { - if index >= da.Length { - return RGBA{}, RGBA{}, 0, newError("index out of bounds") - } - return da.Foreground[index], da.Background[index], da.Attributes[index], nil -} - -// SetStyle sets the styling at the specified index. -func (da *TextBufferDirectAccess) SetStyle(index uint32, fg, bg RGBA, attributes uint16) error { - if index >= da.Length { - return newError("index out of bounds") - } - da.Foreground[index] = fg - da.Background[index] = bg - da.Attributes[index] = attributes - return nil -} - -// Valid checks if the text buffer is still valid (not closed). -func (tb *TextBuffer) Valid() bool { - return tb.ptr != nil -} \ No newline at end of file diff --git a/packages/go/types.go b/packages/go/types.go deleted file mode 100644 index f328eb52d..000000000 --- a/packages/go/types.go +++ /dev/null @@ -1,215 +0,0 @@ -package opentui - -/* -#include "opentui.h" -*/ -import "C" -import ( - "runtime" - "unsafe" -) - -// Cell represents a single terminal cell with character, colors, and attributes -type Cell struct { - Char rune // Unicode character - Foreground RGBA // Foreground color - Background RGBA // Background color - Attributes uint8 // Text attributes (bold, italic, etc.) -} - -// Text attributes constants -const ( - AttrBold uint8 = 1 << 0 - AttrDim uint8 = 1 << 1 - AttrItalic uint8 = 1 << 2 - AttrUnderline uint8 = 1 << 3 - AttrBlink uint8 = 1 << 4 - AttrReverse uint8 = 1 << 5 - AttrStrike uint8 = 1 << 6 -) - -// ClipRect defines a rectangular clipping region -type ClipRect struct { - X int32 - Y int32 - Width uint32 - Height uint32 -} - -// Stats holds renderer statistics -type Stats struct { - Time float64 - FPS uint32 - FrameCallbackTime float64 -} - -// MemoryStats holds memory usage statistics -type MemoryStats struct { - HeapUsed uint32 - HeapTotal uint32 - ArrayBuffers uint32 -} - -// BoxOptions holds options for drawing boxes -type BoxOptions struct { - Sides BorderSides - Fill bool - Title string - TitleAlignment TextAlignment - BorderChars [8]rune // Top-left, top, top-right, right, bottom-right, bottom, bottom-left, left -} - -// DefaultBoxChars provides default Unicode box drawing characters -var DefaultBoxChars = [8]rune{ - '┌', '─', '┐', - '│', '│', - '└', '─', '┘', -} - -// SuperSampleFormat defines pixel formats for super-sampling -type SuperSampleFormat uint8 - -const ( - FormatRGBA SuperSampleFormat = iota - FormatRGB - FormatBGRA - FormatBGR -) - -// TextChunk represents a styled text fragment -type TextChunk struct { - Text string - Foreground *RGBA - Background *RGBA - Attributes *uint8 -} - -// LineInfo represents information about a line in a text buffer -type LineInfo struct { - StartIndex uint32 - Width uint32 -} - -// HitTestResult represents the result of a mouse hit test -type HitTestResult struct { - ID uint32 - Found bool -} - -// Error represents an OpenTUI error -type Error struct { - Message string -} - -func (e *Error) Error() string { - return e.Message -} - -// newError creates a new OpenTUI error -func newError(msg string) error { - return &Error{Message: msg} -} - -// finalizer is a helper to set up automatic cleanup for CGO objects -func setFinalizer[T any](obj *T, cleanup func(*T)) { - if obj != nil { - runtime.SetFinalizer(obj, func(o *T) { cleanup(o) }) - } -} - -// clearFinalizer removes the finalizer from an object -func clearFinalizer[T any](obj *T) { - if obj != nil { - runtime.SetFinalizer(obj, nil) - } -} - -// sliceToC converts a Go slice to C array parameters -func sliceToC[T any](slice []T) (*T, C.size_t) { - if len(slice) == 0 { - return nil, 0 - } - return (*T)(unsafe.Pointer(&slice[0])), C.size_t(len(slice)) -} - -// cArrayToSlice converts a C array to a Go slice (read-only view) -func cArrayToSlice[T any](ptr *T, length int) []T { - if ptr == nil || length == 0 { - return nil - } - return unsafe.Slice(ptr, length) -} - -// runesToC converts a rune slice to uint32 C array -func runesToC(runes []rune) *C.uint32_t { - if len(runes) == 0 { - return nil - } - // Convert runes to uint32 - uint32s := make([]uint32, len(runes)) - for i, r := range runes { - uint32s[i] = uint32(r) - } - return (*C.uint32_t)(unsafe.Pointer(&uint32s[0])) -} - -// Position represents a 2D coordinate -type Position struct { - X int32 - Y int32 -} - -// Size represents 2D dimensions -type Size struct { - Width uint32 - Height uint32 -} - -// Rect combines position and size -type Rect struct { - Position - Size -} - -// Contains checks if a point is inside the rectangle -func (r Rect) Contains(x, y int32) bool { - return x >= r.X && x < r.X+int32(r.Width) && - y >= r.Y && y < r.Y+int32(r.Height) -} - -// Overlaps checks if two rectangles overlap -func (r Rect) Overlaps(other Rect) bool { - return r.X < other.X+int32(other.Width) && - r.X+int32(r.Width) > other.X && - r.Y < other.Y+int32(other.Height) && - r.Y+int32(r.Height) > other.Y -} - -// MouseEvent represents a mouse interaction -type MouseEvent struct { - Position Position - Button uint8 - Pressed bool -} - -// KeyEvent represents a keyboard interaction -type KeyEvent struct { - Key rune - Modifiers uint8 -} - -// Key modifier constants -const ( - ModShift uint8 = 1 << 0 - ModCtrl uint8 = 1 << 1 - ModAlt uint8 = 1 << 2 - ModSuper uint8 = 1 << 3 -) - -// Capabilities represents terminal capabilities -type Capabilities struct { - SupportsTruecolor bool // Terminal supports 24-bit color - SupportsMouse bool // Terminal supports mouse events - SupportsKittyKeyboard bool // Terminal supports Kitty keyboard protocol - SupportsAlternateScreen bool // Terminal supports alternate screen buffer -} \ No newline at end of file diff --git a/packages/vue/.gitignore b/packages/vue/.gitignore deleted file mode 100644 index a14702c40..000000000 --- a/packages/vue/.gitignore +++ /dev/null @@ -1,34 +0,0 @@ -# dependencies (bun install) -node_modules - -# output -out -dist -*.tgz - -# code coverage -coverage -*.lcov - -# logs -logs -_.log -report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json - -# dotenv environment variable files -.env -.env.development.local -.env.test.local -.env.production.local -.env.local - -# caches -.eslintcache -.cache -*.tsbuildinfo - -# IntelliJ based IDEs -.idea - -# Finder (MacOS) folder config -.DS_Store diff --git a/packages/vue/README.md b/packages/vue/README.md deleted file mode 100644 index b1ffc81d7..000000000 --- a/packages/vue/README.md +++ /dev/null @@ -1,309 +0,0 @@ -# @opentui/vue - -Vue.js support for [OpenTUI](https://github.com/sst/opentui). - -## Run examples locally - -```bash -bun install -bun run start:example -``` - -## Setup Guide - -### 1. Create a project folder and initialize it - -```bash -mkdir my-tui-app && cd my-tui-app -bun init -y -``` - -### 2. Add dependencies - -```bash -bun add vue @opentui/core @opentui/vue -``` - -### 3. Create type definition for vue files - -create env.d.ts file at root of project - -```typescript -declare module "*.vue" { - import { DefineComponent } from "vue" - // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types - const component: DefineComponent<{}, {}, any> - export default component -} -``` - -### 4. Create your main application component App.vue. - -```jsx - - - - -``` - -### 5. Create the entry point index.ts. - -```ts -// index.ts -import { createApp } from "vue" -import { render } from "@opentui/vue" -import App from "./App.vue" - -render(createApp(App)) -``` - -### 6. Create a build script build.ts. - -```bash -bun add -D bun-plugin-vue3 -``` - -```ts -// build.ts -import { pluginVue3 } from "bun-plugin-vue3" - -const result = await Bun.build({ - entrypoints: ["./index.ts"], - outdir: "./dist", - target: "bun", - plugins: [pluginVue3()], -}) - -if (!result.success) { - console.error("Build failed") - for (const message of result.logs) { - console.error(message) - } - process.exit(1) -} -``` - -### 7. Build and run your application. - -#### Build the app - -```bash -bun run build.ts -``` - -#### Run the output - -```bash -bun run dist/index.js -``` - -## Note - -Important Note on - -The component only accepts plain text as a direct child. For styled text or text chunks, you must use the content prop. - -```jsx - - - -``` - -## Composables - -@opentui/vue provides a set of composables to interact with the terminal and respond to events. - -### useCliRenderer - -This composable returns the underlying CliRenderer instance from @opentui/core. - -```ts -import { useCliRenderer } from "@opentui/vue" - -const renderer = useCliRenderer() -``` - -### useKeyboard - -Listen to keypress events in your components. - -```jsx - - - -``` - -### useOnResize - -Execute a callback function whenever the terminal window is resized. - -```vue - -``` - -### useTerminalDimensions - -Get the current terminal dimensions as a reactive object. The dimensions will automatically update when the terminal is resized. - -```jsx - - - -``` - -## Extending with Custom Components - -`@opentui/vue` allows you to create and use your own custom components in your TUI applications. This is useful for creating reusable UI elements with specific styles and behaviors. - -The `extend` function is used to register your custom components with the Vue renderer. - -### How to create and use a custom component - -Here's a step-by-step guide to creating a custom button component. - -#### 1. Create the custom renderable - -First, create a class that extends one of the base renderables from `@opentui/core`. For our custom button, we'll extend `BoxRenderable`. - -`CustomButtonRenderable.ts`: - -```typescript -import { BoxRenderable, OptimizedBuffer, RGBA, type BoxOptions, type RenderContext } from "@opentui/core" - -export class ConsoleButtonRenderable extends BoxRenderable { - private _label: string = "Button" - - constructor(ctx: RenderContext, options: BoxOptions & { label?: string }) { - super(ctx, options) - - if (options.label) { - this._label = options.label - } - - // Set some default styling for buttons - this.borderStyle = "single" - this.padding = 2 - } - - protected override renderSelf(buffer: OptimizedBuffer): void { - super.renderSelf(buffer) - - const centerX = this.x + Math.floor(this.width / 2 - this._label.length / 2) - const centerY = this.y + Math.floor(this.height / 2) - - buffer.drawText(this._label, centerX, centerY, RGBA.fromInts(255, 255, 255, 255)) - } - - get label(): string { - return this._label - } - - set label(value: string) { - this._label = value - this.requestRender() - } -} -``` - -#### 2. Register the new component - -In your application's entry point (e.g., `main.ts`), import the `extend` function and your custom component. Then, call `extend` with an object where the key is the component's tag name (in camelCase) and the value is the component class. - -`main.ts`: - -```typescript -import { render, extend } from "@opentui/vue" -import { ConsoleButtonRenderable } from "./CustomButtonRenderable" -import App from "./App.vue" - -// Register the custom component -extend({ consoleButtonRenderable: ConsoleButtonRenderable }) - -// Render the app -render(App) -``` - -> **Important:** The `extend` function should be called in your main application entry file (e.g., `main.ts` or `index.js`). Calling it inside the ` -``` diff --git a/packages/vue/bunconfig.toml b/packages/vue/bunconfig.toml deleted file mode 100644 index 259a77f03..000000000 --- a/packages/vue/bunconfig.toml +++ /dev/null @@ -1,9 +0,0 @@ -[jsx] -runtime = "automatic" -importSource = "vue" - -[build] -target = "bun" -format = "esm" -splitting = false -outdir = "dist" \ No newline at end of file diff --git a/packages/vue/example/ASCII.vue b/packages/vue/example/ASCII.vue deleted file mode 100644 index 9be77dd30..000000000 --- a/packages/vue/example/ASCII.vue +++ /dev/null @@ -1,60 +0,0 @@ - - - diff --git a/packages/vue/example/App.vue b/packages/vue/example/App.vue deleted file mode 100644 index bd2fbf609..000000000 --- a/packages/vue/example/App.vue +++ /dev/null @@ -1,61 +0,0 @@ - - - diff --git a/packages/vue/example/Counter.vue b/packages/vue/example/Counter.vue deleted file mode 100644 index 19280f4da..000000000 --- a/packages/vue/example/Counter.vue +++ /dev/null @@ -1,43 +0,0 @@ - - - diff --git a/packages/vue/example/CustomButtonRenderable.ts b/packages/vue/example/CustomButtonRenderable.ts deleted file mode 100644 index 0f69d85af..000000000 --- a/packages/vue/example/CustomButtonRenderable.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { BoxRenderable, OptimizedBuffer, RGBA, type BoxOptions, type RenderContext } from "@opentui/core" - -export class ConsoleButtonRenderable extends BoxRenderable { - private _label: string = "Button" - - constructor(ctx: RenderContext, options: BoxOptions & { label?: string }) { - super(ctx, options) - if (options.label) { - this._label = options.label - } - this.borderStyle = "single" - this.border = true - this.padding = 2 - } - - protected override renderSelf(buffer: OptimizedBuffer): void { - super.renderSelf(buffer) - const centerX = this.x + Math.floor(this.width / 2 - this._label.length / 2) - const centerY = this.y + Math.floor(this.height / 2) - buffer.drawText(this._label, centerX, centerY, RGBA.fromInts(255, 255, 255, 255)) - } - - get label(): string { - return this._label - } - - set label(value: string) { - this._label = value - this.requestRender() - } -} diff --git a/packages/vue/example/ExtendExample.vue b/packages/vue/example/ExtendExample.vue deleted file mode 100644 index 94c67f926..000000000 --- a/packages/vue/example/ExtendExample.vue +++ /dev/null @@ -1,13 +0,0 @@ - - - diff --git a/packages/vue/example/LoginForm.vue b/packages/vue/example/LoginForm.vue deleted file mode 100644 index 0110a82bb..000000000 --- a/packages/vue/example/LoginForm.vue +++ /dev/null @@ -1,90 +0,0 @@ - - - diff --git a/packages/vue/example/ScrollBox.vue b/packages/vue/example/ScrollBox.vue deleted file mode 100644 index 33c611dd3..000000000 --- a/packages/vue/example/ScrollBox.vue +++ /dev/null @@ -1,31 +0,0 @@ - - - diff --git a/packages/vue/example/Styled-Text.vue b/packages/vue/example/Styled-Text.vue deleted file mode 100644 index 968e9d641..000000000 --- a/packages/vue/example/Styled-Text.vue +++ /dev/null @@ -1,21 +0,0 @@ - - - diff --git a/packages/vue/example/TabSelect.vue b/packages/vue/example/TabSelect.vue deleted file mode 100644 index 7fea34c7a..000000000 --- a/packages/vue/example/TabSelect.vue +++ /dev/null @@ -1,38 +0,0 @@ - - - diff --git a/packages/vue/example/main.ts b/packages/vue/example/main.ts deleted file mode 100644 index 35ceec33c..000000000 --- a/packages/vue/example/main.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { render } from "@opentui/vue" -import { extend } from "@opentui/vue" -import { ConsoleButtonRenderable } from "./CustomButtonRenderable" -import App from "./App.vue" - -extend({ consoleButtonRenderable: ConsoleButtonRenderable }) - -render(App) diff --git a/packages/vue/index.ts b/packages/vue/index.ts deleted file mode 100644 index df3ad1f4b..000000000 --- a/packages/vue/index.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { CliRenderer, createCliRenderer, type CliRendererConfig } from "@opentui/core" -import { createOpenTUIRenderer } from "./src/renderer" -import type { InjectionKey } from "vue" -export * from "./src/composables/index" -export * from "./src/extend" - -export const cliRendererKey: InjectionKey = Symbol("cliRenderer") - -export async function render(component: any, rendererConfig: CliRendererConfig = {}): Promise { - const cliRenderer = await createCliRenderer(rendererConfig) - const renderer = createOpenTUIRenderer(cliRenderer) - const app = renderer.createApp(component) - app.provide(cliRendererKey, cliRenderer) - app.mount(cliRenderer.root) -} diff --git a/packages/vue/package.json b/packages/vue/package.json deleted file mode 100644 index 9b8e65d07..000000000 --- a/packages/vue/package.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "name": "@opentui/vue", - "version": "0.1.25", - "description": "Vue renderer for OpenTUI", - "repository": { - "type": "git", - "url": "https://github.com/sst/opentui", - "directory": "packages/vue" - }, - "module": "index.ts", - "type": "module", - "private": true, - "main": "index.ts", - "license": "MIT", - "exports": { - ".": { - "types": "./index.ts", - "import": "./index.ts" - } - }, - "scripts": { - "build": "bun scripts/build.ts", - "publish": "bun scripts/publish.ts", - "start:example": "bun scripts/build-examples.ts && bun run example/dist/main.js" - }, - "dependencies": { - "@opentui/core": "workspace:*", - "@vue/runtime-core": "^3.5.18" - }, - "peerDependencies": { - "vue": "^3.5.18", - "typescript": "^5" - }, - "devDependencies": { - "bun-plugin-vue3": "^1.0.0-beta.2", - "typescript": "^5", - "vue": "^3.5.18" - } -} diff --git a/packages/vue/scripts/build-examples.ts b/packages/vue/scripts/build-examples.ts deleted file mode 100644 index 027c5d50c..000000000 --- a/packages/vue/scripts/build-examples.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { pluginVue3 } from "bun-plugin-vue3" - -const result = await Bun.build({ - entrypoints: ["./example/main.ts"], - outdir: "./example/dist", - target: "bun", - format: "esm", - // splitting: false, - external: ["@opentui/core", "@opentui/vue", "vue"], - plugins: [ - pluginVue3({ - isProduction: false, - }), - ], - minify: process.env.NODE_ENV === "production", - sourcemap: process.env.NODE_ENV !== "production" ? "external" : "none", -}) - -if (!result.success) { - console.error("Build failed") - for (const message of result.logs) { - console.error(message) - } - process.exit(1) -} - -console.log("✅ Build completed successfully!") -console.log("Files generated:") -result.outputs.forEach((output) => { - console.log(` ${output.path}`) -}) diff --git a/packages/vue/scripts/build.ts b/packages/vue/scripts/build.ts deleted file mode 100644 index 53733add3..000000000 --- a/packages/vue/scripts/build.ts +++ /dev/null @@ -1,183 +0,0 @@ -import { spawnSync, type SpawnSyncReturns } from "node:child_process" -import { copyFileSync, existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "fs" -import { dirname, join, resolve } from "path" -import { fileURLToPath } from "url" -import process from "process" - -interface PackageJson { - name: string - version: string - license?: string - repository?: any - description?: string - homepage?: string - author?: string - bugs?: any - keywords?: string[] - module?: string - main?: string - types?: string - type?: string - exports?: any - dependencies?: Record - devDependencies?: Record - peerDependencies?: Record -} - -const __filename = fileURLToPath(import.meta.url) -const __dirname = dirname(__filename) -const rootDir = resolve(__dirname, "..") -const projectRootDir = resolve(rootDir, "../..") -const licensePath = join(projectRootDir, "LICENSE") -const packageJson: PackageJson = JSON.parse(readFileSync(join(rootDir, "package.json"), "utf8")) - -const args = process.argv.slice(2) -const isDev = args.includes("--dev") - -const replaceLinks = (text: string): string => { - return packageJson.homepage - ? text.replace( - /(\[.*?\]\()(\.\/.*?\))/g, - (_, p1: string, p2: string) => `${p1}${packageJson.homepage}/blob/HEAD/${p2.replace("./", "")}`, - ) - : text -} - -const requiredFields: (keyof PackageJson)[] = ["name", "version", "description"] -const missingRequired = requiredFields.filter((field) => !packageJson[field]) -if (missingRequired.length > 0) { - console.error(`Error: Missing required fields in package.json: ${missingRequired.join(", ")}`) - process.exit(1) -} - -console.log(`Building @opentui/vue library${isDev ? " (dev mode)" : ""}...`) - -const distDir = join(rootDir, "dist") -rmSync(distDir, { recursive: true, force: true }) -mkdirSync(distDir, { recursive: true }) - -const externalDeps: string[] = [ - ...Object.keys(packageJson.dependencies || {}), - ...Object.keys(packageJson.peerDependencies || {}), -] - -if (!packageJson.module) { - console.error("Error: 'module' field not found in package.json") - process.exit(1) -} - -console.log("Building main entry point...") -const mainBuildResult = await Bun.build({ - entrypoints: [join(rootDir, packageJson.module)], - target: "bun", - outdir: join(rootDir, "dist"), - external: externalDeps, - // splitting: true, -}) - -if (!mainBuildResult.success) { - console.error("Build failed for main entry point:", mainBuildResult.logs) - process.exit(1) -} - -console.log("Generating TypeScript declarations...") - -const tsconfigBuildPath = join(rootDir, "tsconfig.build.json") -const tsconfigBuild = { - extends: "./tsconfig.json", - compilerOptions: { - declaration: true, - emitDeclarationOnly: true, - outDir: "./dist", - noEmit: false, - rootDir: ".", - types: ["bun", "node"], - skipLibCheck: true, - jsx: "preserve", - moduleResolution: "bundler", - baseUrl: ".", - paths: { - "@opentui/core": ["../core/dist"], - "@opentui/core/*": ["../core/dist/*"], - }, - }, - include: ["index.ts", "src/**/*", "types/opentui.d.ts"], - exclude: ["**/*.test.ts", "**/*.spec.ts", "example/**/*", "scripts/**/*", "node_modules/**/*", "../core/**/*"], -} - -writeFileSync(tsconfigBuildPath, JSON.stringify(tsconfigBuild, null, 2)) - -const tscResult: SpawnSyncReturns = spawnSync("bunx", ["tsc", "-p", tsconfigBuildPath], { - cwd: rootDir, - stdio: "inherit", -}) - -rmSync(tsconfigBuildPath, { force: true }) - -if (tscResult.status !== 0) { - console.warn("Warning: TypeScript declaration generation failed") -} else { - console.log("TypeScript declarations generated") -} - -// Manually copy the opentui.d.ts file -console.log("Copying custom declaration files...") -const typesDir = join(distDir, "types") -if (!existsSync(typesDir)) { - mkdirSync(typesDir, { recursive: true }) -} -copyFileSync(join(rootDir, "types/opentui.d.ts"), join(typesDir, "opentui.d.ts")) - -const exports = { - ".": { - types: "./index.d.ts", - import: "./index.js", - require: "./index.js", - }, -} - -const processedDependencies = { ...packageJson.dependencies } -if (processedDependencies["@opentui/core"] === "workspace:*") { - processedDependencies["@opentui/core"] = packageJson.version -} - -writeFileSync( - join(distDir, "package.json"), - JSON.stringify( - { - name: packageJson.name, - module: "index.js", - main: "index.js", - types: "index.d.ts", - type: packageJson.type, - version: packageJson.version, - description: packageJson.description, - keywords: packageJson.keywords, - license: packageJson.license, - author: packageJson.author, - homepage: packageJson.homepage, - repository: packageJson.repository, - bugs: packageJson.bugs, - exports, - dependencies: processedDependencies, - peerDependencies: packageJson.peerDependencies, - }, - null, - 2, - ), -) - -const readmePath = join(rootDir, "README.md") -if (existsSync(readmePath)) { - writeFileSync(join(distDir, "README.md"), replaceLinks(readFileSync(readmePath, "utf8"))) -} else { - console.warn("Warning: README.md not found in vue package") -} - -if (existsSync(licensePath)) { - copyFileSync(licensePath, join(distDir, "LICENSE")) -} else { - console.warn("Warning: LICENSE file not found in project root") -} - -console.log("Library built at:", distDir) diff --git a/packages/vue/scripts/publish.ts b/packages/vue/scripts/publish.ts deleted file mode 100644 index d371f7aa7..000000000 --- a/packages/vue/scripts/publish.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { spawnSync, type SpawnSyncReturns } from "node:child_process" -import { readFileSync } from "node:fs" -import { dirname, join, resolve } from "node:path" -import process from "node:process" -import { fileURLToPath } from "node:url" - -interface PackageJson { - name: string - version: string - dependencies?: Record -} - -const __filename = fileURLToPath(import.meta.url) -const __dirname = dirname(__filename) -const rootDir = resolve(__dirname, "..") - -const packageJson: PackageJson = JSON.parse(readFileSync(join(rootDir, "package.json"), "utf8")) - -console.log(`Publishing @opentui/vue@${packageJson.version}...`) -console.log("Make sure you've run the pre-publish validation script first!") - -const distDir = join(rootDir, "dist") - -console.log(`\nPublishing ${packageJson.name}@${packageJson.version}...`) - -const isSnapshot = packageJson.version.includes("-snapshot") || /^0\.0\.0-\d{8}-[a-f0-9]{8}$/.test(packageJson.version) -const publishArgs = ["publish", "--access=public"] - -if (isSnapshot) { - publishArgs.push("--tag", "snapshot") - console.log(` Publishing as snapshot (--tag snapshot)`) -} - -const publish: SpawnSyncReturns = spawnSync("npm", publishArgs, { - cwd: distDir, - stdio: "inherit", -}) - -if (publish.status !== 0) { - console.error(`Failed to publish '${packageJson.name}@${packageJson.version}'.`) - process.exit(1) -} - -console.log(`Successfully published '${packageJson.name}@${packageJson.version}'`) diff --git a/packages/vue/src/cli-renderer-ref.ts b/packages/vue/src/cli-renderer-ref.ts deleted file mode 100644 index 860c02bb8..000000000 --- a/packages/vue/src/cli-renderer-ref.ts +++ /dev/null @@ -1,14 +0,0 @@ -import type { CliRenderer } from "@opentui/core" - -let currentCliRenderer: CliRenderer | null = null - -export function setCurrentCliRenderer(renderer: CliRenderer) { - currentCliRenderer = renderer -} - -export function getCurrentCliRenderer(): CliRenderer { - if (!currentCliRenderer) { - throw new Error("No CLI renderer available. Make sure to call setCurrentCliRenderer.") - } - return currentCliRenderer -} diff --git a/packages/vue/src/composables/index.ts b/packages/vue/src/composables/index.ts deleted file mode 100644 index 0c24fdee8..000000000 --- a/packages/vue/src/composables/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -export { useTerminalDimensions } from "./useTerminalDimensions" -export { useOnResize } from "./useOnResize" -export { useKeyboard } from "./useKeyboard" -export { useCliRenderer } from "./useCliRenderer" diff --git a/packages/vue/src/composables/useCliRenderer.ts b/packages/vue/src/composables/useCliRenderer.ts deleted file mode 100644 index aff26b663..000000000 --- a/packages/vue/src/composables/useCliRenderer.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { inject } from "@vue/runtime-core" -import type { CliRenderer } from "@opentui/core" -import { cliRendererKey } from "../.." - -export function useCliRenderer(): CliRenderer { - const renderer = inject(cliRendererKey) - - if (!renderer) { - throw new Error("Could not find CliRenderer instance. Was it provided by the app?") - } - - return renderer -} diff --git a/packages/vue/src/composables/useKeyboard.ts b/packages/vue/src/composables/useKeyboard.ts deleted file mode 100644 index 64264eca9..000000000 --- a/packages/vue/src/composables/useKeyboard.ts +++ /dev/null @@ -1,16 +0,0 @@ -// packages/vue/src/composables/useKeyboard.ts -import { type KeyEvent } from "@opentui/core" -import { onMounted, onUnmounted } from "vue" -import { useCliRenderer } from "./useCliRenderer" - -export const useKeyboard = (handler: (key: KeyEvent) => void) => { - const renderer = useCliRenderer() - - onMounted(() => { - renderer.keyInput.on("keypress", handler) - }) - - onUnmounted(() => { - renderer.keyInput.off("keypress", handler) - }) -} diff --git a/packages/vue/src/composables/useOnResize.ts b/packages/vue/src/composables/useOnResize.ts deleted file mode 100644 index ae4da5c73..000000000 --- a/packages/vue/src/composables/useOnResize.ts +++ /dev/null @@ -1,15 +0,0 @@ -// packages/vue/src/composables/useOnResize.ts -import { onMounted, onUnmounted } from "vue" -import { useCliRenderer } from "./useCliRenderer" - -export const useOnResize = (callback: (width: number, height: number) => void) => { - const renderer = useCliRenderer() - - onMounted(() => { - renderer.on("resize", callback) - }) - - onUnmounted(() => { - renderer.off("resize", callback) - }) -} diff --git a/packages/vue/src/composables/useTerminalDimensions.ts b/packages/vue/src/composables/useTerminalDimensions.ts deleted file mode 100644 index 45c802b76..000000000 --- a/packages/vue/src/composables/useTerminalDimensions.ts +++ /dev/null @@ -1,21 +0,0 @@ -// packages/vue/src/composables/useTerminalDimensions.ts -import { ref } from "vue" -import { useCliRenderer } from "./useCliRenderer" -import { useOnResize } from "./useOnResize" - -export const useTerminalDimensions = () => { - const renderer = useCliRenderer() - - const dimensions = ref({ - width: renderer.width, - height: renderer.height, - }) - - const onResize = (width: number, height: number) => { - dimensions.value = { width, height } - } - - useOnResize(onResize) - - return dimensions -} diff --git a/packages/vue/src/elements.ts b/packages/vue/src/elements.ts deleted file mode 100644 index 2bdec94a3..000000000 --- a/packages/vue/src/elements.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { - ASCIIFontRenderable, - BoxRenderable, - InputRenderable, - SelectRenderable, - TabSelectRenderable, - TextRenderable, - ScrollBoxRenderable, -} from "@opentui/core" - -export const elements = { - asciiFontRenderable: ASCIIFontRenderable, - boxRenderable: BoxRenderable, - inputRenderable: InputRenderable, - selectRenderable: SelectRenderable, - tabSelectRenderable: TabSelectRenderable, - textRenderable: TextRenderable, - scrollBoxRenderable: ScrollBoxRenderable, -} -export type Element = keyof typeof elements diff --git a/packages/vue/src/extend.ts b/packages/vue/src/extend.ts deleted file mode 100644 index 13963cb39..000000000 --- a/packages/vue/src/extend.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { elements } from "./elements" -import type { OpenTUIComponents } from "../types/opentui" - -export function extend>(newElements: T) { - Object.assign(elements, newElements) -} diff --git a/packages/vue/src/noOps.ts b/packages/vue/src/noOps.ts deleted file mode 100644 index 2eef1ec9a..000000000 --- a/packages/vue/src/noOps.ts +++ /dev/null @@ -1,124 +0,0 @@ -import { Renderable, TextRenderable } from "@opentui/core" -import { TextNode, type OpenTUINode, ChunkToTextNodeMap } from "./nodes" -import { getNextId } from "./utils" - -const GHOST_NODE_TAG = "text-ghost" as const - -function getOrCreateTextGhostNode(parent: Renderable, anchor?: OpenTUINode | null): TextRenderable { - if (anchor instanceof TextNode && anchor.textParent) { - return anchor.textParent - } - - const children = parent.getChildren() - - if (anchor instanceof Renderable) { - const anchorIndex = children.findIndex((el) => el.id === anchor.id) - const beforeAnchor = children[anchorIndex - 1] - if (beforeAnchor instanceof TextRenderable && beforeAnchor.id.startsWith(GHOST_NODE_TAG)) { - return beforeAnchor - } - } - - const lastChild = children.at(-1) - if (lastChild instanceof TextRenderable && lastChild.id.startsWith(GHOST_NODE_TAG)) { - return lastChild - } - - const ghostNode = new TextRenderable(parent.ctx, { id: getNextId(GHOST_NODE_TAG) }) - insertNode(parent, ghostNode, anchor) - return ghostNode -} - -function insertTextNode(parent: OpenTUINode, node: TextNode, anchor?: OpenTUINode | null): void { - if (!(parent instanceof Renderable)) { - console.warn(`[WARN] Attempted to attach text node ${node.id} to a non-renderable parent ${parent.id}.`) - return - } - - let textParent: TextRenderable - if (!(parent instanceof TextRenderable)) { - textParent = getOrCreateTextGhostNode(parent, anchor) - } else { - textParent = parent - } - - node.textParent = textParent - let styledText = textParent.content - - if (anchor && anchor instanceof TextNode) { - const anchorIndex = styledText.chunks.indexOf(anchor.chunk) - if (anchorIndex === -1) { - console.warn(`[WARN] TextNode anchor not found for node ${node.id}.`) - return - } - styledText = styledText.insert(node.chunk, anchorIndex) - } else { - const firstChunk = textParent.content.chunks[0] - if (firstChunk && !ChunkToTextNodeMap.has(firstChunk)) { - styledText = styledText.replace(node.chunk, firstChunk) - } else { - styledText = styledText.insert(node.chunk) - } - } - - textParent.content = styledText - node.parent = parent - ChunkToTextNodeMap.set(node.chunk, node) -} - -function removeTextNode(parent: OpenTUINode, node: TextNode): void { - if (!(parent instanceof Renderable)) { - ChunkToTextNodeMap.delete(node.chunk) - return - } - - if (parent === node.textParent && parent instanceof TextRenderable) { - ChunkToTextNodeMap.delete(node.chunk) - parent.content = parent.content.remove(node.chunk) - } else if (node.textParent) { - ChunkToTextNodeMap.delete(node.chunk) - let styledText = node.textParent.content - styledText = styledText.remove(node.chunk) - - if (styledText.chunks.length > 0) { - node.textParent.content = styledText - } else { - node.parent?.remove(node.textParent.id) - node.textParent.destroyRecursively() - } - } -} - -export function insertNode(parent: OpenTUINode, node: OpenTUINode, anchor?: OpenTUINode | null): void { - if (node instanceof TextNode) { - return insertTextNode(parent, node, anchor) - } - - if (!(parent instanceof Renderable)) { - console.warn(`[WARN] Attempted to insert node ${node.id} into a non-renderable parent ${parent.id}.`) - return - } - - if (anchor) { - const anchorIndex = parent.getChildren().findIndex((el) => { - if (anchor instanceof TextNode) { - return el.id === anchor.textParent?.id - } - return el.id === anchor.id - }) - parent.add(node, anchorIndex) - } else { - parent.add(node) - } -} - -export function removeNode(parent: OpenTUINode, node: OpenTUINode): void { - if (node instanceof TextNode) { - return removeTextNode(parent, node) - } - - if (parent instanceof Renderable && node instanceof Renderable) { - parent.remove(node.id) - node.destroyRecursively() - } -} diff --git a/packages/vue/src/nodes.ts b/packages/vue/src/nodes.ts deleted file mode 100644 index 4deb8dcbd..000000000 --- a/packages/vue/src/nodes.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { Renderable, TextRenderable, RootRenderable, type TextChunk, type CliRenderer } from "@opentui/core" -import { getNextId } from "./utils" -import type { elements } from "./elements" - -export const ChunkToTextNodeMap = new WeakMap() - -export class TextNode { - id: string - chunk: TextChunk - parent?: Renderable - textParent?: TextRenderable - - constructor(chunk: TextChunk) { - this.id = getNextId("text-node") - this.chunk = chunk - } -} - -export class WhiteSpaceNode extends Renderable { - constructor(cliRenderer: CliRenderer) { - super(cliRenderer, { id: getNextId("whitespace") }) - } -} - -export type OpenTUINode = Renderable | TextNode -type ElementConstructor = (typeof elements)[keyof typeof elements] -export type OpenTUIElement = InstanceType | RootRenderable diff --git a/packages/vue/src/renderer.ts b/packages/vue/src/renderer.ts deleted file mode 100644 index ac45259ef..000000000 --- a/packages/vue/src/renderer.ts +++ /dev/null @@ -1,254 +0,0 @@ -import { createRenderer } from "@vue/runtime-core" -import { - InputRenderable, - InputRenderableEvents, - SelectRenderable, - SelectRenderableEvents, - TabSelectRenderable, - TabSelectRenderableEvents, - TextRenderable, - StyledText, - type TextChunk, - Renderable, - type CliRenderer, -} from "@opentui/core" -import { getNextId } from "./utils" -import { type OpenTUINode, type OpenTUIElement, TextNode, WhiteSpaceNode, ChunkToTextNodeMap } from "./nodes" -import { elements, type Element } from "./elements" -import { insertNode, removeNode } from "./noOps" - -export function createOpenTUIRenderer(cliRenderer: CliRenderer) { - function createText(value: string | number | boolean | TextChunk): OpenTUINode { - const plainText = typeof value === "object" ? (value as TextChunk).text : String(value) - - if (plainText?.trim() === "") { - return new WhiteSpaceNode(cliRenderer) - } - - const chunk: TextChunk = - typeof value === "object" && "__isChunk" in value - ? value - : { - __isChunk: true, - text: `${value}`, - } - const textNode = new TextNode(chunk) - ChunkToTextNodeMap.set(chunk, textNode) - return textNode - } - - return createRenderer({ - createElement(type: string, _isSVG: undefined, _anchor: any, props) { - const RenderableClass = elements[type as Element] - if (!RenderableClass) throw new Error(`${type} is not a valid element`) - - const id = getNextId(type) - //we don't pass content directly, we handle it in patchProp - const { style = {}, content, ...options } = props || {} - return new RenderableClass(cliRenderer, { id, ...style, ...options }) - }, - - createText, - - insert(el, parent, anchor) { - if (!el) { - console.log(`insert: SKIPPING null element.`) - return - } - insertNode(parent, el, anchor) - }, - - patchProp(el, key, prevValue, nextValue) { - if (el instanceof TextNode) { - return - } - - switch (key) { - case "focused": - if (nextValue) { - el.focus() - } else { - el.blur() - } - break - - case "onChange": - let changeEvent: string | undefined = undefined - if (el instanceof SelectRenderable) { - changeEvent = SelectRenderableEvents.SELECTION_CHANGED - } else if (el instanceof TabSelectRenderable) { - changeEvent = TabSelectRenderableEvents.SELECTION_CHANGED - } else if (el instanceof InputRenderable) { - changeEvent = InputRenderableEvents.CHANGE - } - if (changeEvent) { - if (prevValue) { - el.off(changeEvent, prevValue) - } - if (nextValue) { - el.on(changeEvent, nextValue) - } - } - break - - case "onSelect": - let selectEvent: SelectRenderableEvents.ITEM_SELECTED | undefined = undefined - if (el instanceof SelectRenderable) { - selectEvent = SelectRenderableEvents.ITEM_SELECTED - } - if (selectEvent) { - if (prevValue) { - el.off(selectEvent, prevValue) - } - if (nextValue) { - el.on(selectEvent, nextValue) - } - } - break - - case "onInput": - if (el instanceof InputRenderable) { - if (prevValue) { - el.off(InputRenderableEvents.INPUT, prevValue) - } - if (nextValue) { - el.on(InputRenderableEvents.INPUT, nextValue) - } - } - break - - case "onSubmit": - if (el instanceof InputRenderable) { - if (prevValue) { - el.off(InputRenderableEvents.ENTER, prevValue) - } - if (nextValue) { - el.on(InputRenderableEvents.ENTER, nextValue) - } - } - break - - case "style": - if (nextValue && typeof nextValue === "object") { - for (const prop in nextValue) { - const propVal = nextValue[prop] - if (prevValue && typeof prevValue === "object" && propVal === prevValue[prop]) { - continue - } - // @ts-expect-error - Dynamic property assignment - el[prop] = propVal - } - } - - break - - case "content": - const textInstance = el as TextRenderable - if (nextValue == null) { - textInstance.content = "" - return - } - if (Array.isArray(nextValue)) { - const chunks: TextChunk[] = [] - for (const child of nextValue) { - if (typeof child === "string") { - chunks.push({ - __isChunk: true, - text: child, - }) - } else if (child && typeof child === "object" && "__isChunk" in child) { - chunks.push(child as TextChunk) - } else if (child instanceof StyledText) { - chunks.push(...child.chunks) - } else if (child != null) { - const stringValue = String(child) - chunks.push({ - __isChunk: true, - text: stringValue, - }) - } - } - textInstance.content = new StyledText(chunks) - return - } - - if (typeof nextValue === "string") { - textInstance.content = nextValue - } else if (nextValue instanceof StyledText) { - textInstance.content = nextValue - } else if (nextValue && typeof nextValue === "object" && "__isChunk" in nextValue) { - textInstance.content = new StyledText([nextValue as TextChunk]) - } else { - textInstance.content = String(nextValue) - } - break - - default: - // @ts-expect-error - Dynamic property assignment - el[key] = nextValue - } - }, - - remove(el) { - const parent = el.parent - if (parent) { - removeNode(parent, el) - } else { - console.log(`-- remove called on detached node: ${el.id}`) - } - }, - - setElementText(node, text) { - if (node instanceof TextRenderable) { - node.content = text - } else if (node instanceof Renderable) { - const children = node.getChildren() - children.forEach((child) => node.remove(child.id)) - const textChild = new TextRenderable(cliRenderer, { id: getNextId("text"), content: text }) - node.add(textChild) - } - }, - - setText(node, text) { - if (node instanceof TextNode) { - const textParent = node.textParent - if (textParent instanceof TextRenderable) { - textParent.content = text - textParent.requestRender() - } - } - }, - - parentNode: (node) => node.parent! as OpenTUIElement, - - nextSibling(node) { - const parent = node.parent - if (!parent) return null - - if (node instanceof TextNode && parent instanceof TextRenderable) { - const siblings = parent.content.chunks - const index = siblings.indexOf(node.chunk) - const nextChunk = siblings[index + 1] - return nextChunk ? ChunkToTextNodeMap.get(nextChunk) || null : null - } - - const siblings = parent.getChildren() - const index = siblings.findIndex((child) => child.id === node.id) - return siblings[index + 1] || null - }, - - cloneNode(el) { - if (el instanceof TextNode) { - return new TextNode(el.chunk) - } - - const Constructor = el.constructor as new (ctx: CliRenderer, props: any) => typeof el - const cloned = new Constructor(cliRenderer, { id: getNextId(el.id.split("-")[0] || "cloned") }) - - return cloned - }, - - //@ts-expect-error : we don't do anything we comments - createComment: () => null, - }) -} diff --git a/packages/vue/src/utils.ts b/packages/vue/src/utils.ts deleted file mode 100644 index d799832d6..000000000 --- a/packages/vue/src/utils.ts +++ /dev/null @@ -1,11 +0,0 @@ -const idCounter = new Map() - -export function getNextId(elementType: string): string { - if (!idCounter.has(elementType)) { - idCounter.set(elementType, 0) - } - - const value = idCounter.get(elementType)! + 1 - idCounter.set(elementType, value) - return `${elementType}-${value}` -} diff --git a/packages/vue/tsconfig.json b/packages/vue/tsconfig.json deleted file mode 100644 index 2589b5c34..000000000 --- a/packages/vue/tsconfig.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "compilerOptions": { - // Environment setup & latest features - "lib": ["ESNext"], - "target": "ESNext", - "module": "Preserve", - "moduleDetection": "force", - "allowJs": true, - - "jsx": "preserve", - - // Bundler mode - "moduleResolution": "bundler", - "allowImportingTsExtensions": true, - "verbatimModuleSyntax": true, - "noEmit": true, - - // Best practices - "strict": true, - "noFallthroughCasesInSwitch": true, - "noUncheckedIndexedAccess": true, - "noImplicitOverride": true, - - // Some stricter flags (disabled by default) - "noUnusedLocals": false, - "noUnusedParameters": false, - "noPropertyAccessFromIndexSignature": false, - - "types": ["node"], - "skipLibCheck": true - }, - "include": ["src/**/*", "example/**/*", "types"], - "files": ["types/opentui.d.ts"] -} diff --git a/packages/vue/types/opentui.d.ts b/packages/vue/types/opentui.d.ts deleted file mode 100644 index 8d58e7c13..000000000 --- a/packages/vue/types/opentui.d.ts +++ /dev/null @@ -1,97 +0,0 @@ -export {} - -import type { DefineComponent } from "vue" -import type { - ASCIIFontOptions, - BoxOptions, - InputRenderableOptions, - RenderableOptions, - SelectOption, - SelectRenderableOptions, - StyledText, - TabSelectOption, - TabSelectRenderableOptions, - TextChunk, - TextOptions, - ScrollBoxOptions, -} from "@opentui/core" - -type NonStyledProps = "buffered" | "live" | "enableLayout" | "selectable" - -type ContainerProps = TOptions - -type VueComponentProps = TOptions & { - style?: Partial> -} - -export type TextProps = Omit, "content"> & { - children?: - | string - | number - | boolean - | null - | undefined - | StyledText - | Array - content?: string | StyledText | TextChunk -} - -export type BoxProps = VueComponentProps, NonStyledProps | "title"> - -export type ScrollBoxProps = VueComponentProps, NonStyledProps | "title"> - -export type InputProps = VueComponentProps & { - focused?: boolean - onInput?: (value: string) => void - onChange?: (value: string) => void - onSubmit?: (value: string) => void -} - -export type SelectProps = VueComponentProps & { - focused?: boolean - onChange?: (index: number, option: SelectOption | null) => void - onSelect?: (index: number, option: SelectOption | null) => void -} - -export type AsciiFontProps = VueComponentProps - -export type TabSelectProps = VueComponentProps & { - focused?: boolean - onChange?: (index: number, option: TabSelectOption | null) => void - onSelect?: (index: number, option: TabSelectOption | null) => void -} - -export type ExtendedIntrinsicElements> = { - [TComponentName in keyof TComponentCatalogue]: ExtendedComponentProps -} - -export interface OpenTUIComponents { - [componentName: string]: RenderableConstructor -} - -export function extend>(components: T): void - -declare module "@vue/runtime-core" { - export interface GlobalComponents extends ExtendedIntrinsicElements { - asciiFontRenderable: DefineComponent - boxRenderable: DefineComponent - inputRenderable: DefineComponent - selectRenderable: DefineComponent - tabSelectRenderable: DefineComponent - textRenderable: DefineComponent - scrollBoxRenderable: DefineComponent - } -} - -// Augment for JSX/TSX support in Vue -declare module "@vue/runtime-dom" { - export interface IntrinsicElementAttributes extends ExtendedIntrinsicElements { - asciiFontRenderable: AsciiFontProps - boxRenderable: BoxProps - inputRenderable: InputProps - selectRenderable: SelectProps - tabSelectRenderable: TabSelectProps - textRenderable: TextProps - scrollBoxRenderable: ScrollBoxProps - } -} diff --git a/scripts/pre-publish.ts b/scripts/pre-publish.ts index 06b1b0111..9eec877e5 100644 --- a/scripts/pre-publish.ts +++ b/scripts/pre-publish.ts @@ -48,20 +48,9 @@ const ALL_PACKAGES: PackageConfig[] = [ distDir: join(rootDir, "packages", "solid", "dist"), requiresCore: true, }, - { - name: "@opentui/vue", - rootDir: join(rootDir, "packages", "vue"), - distDir: join(rootDir, "packages", "vue", "dist"), - requiresCore: true, - }, ] -// Parse command line arguments -const args = process.argv.slice(2) -const includeVue = args.includes("--include-vue") - -// Filter packages based on flags -const PACKAGES = includeVue ? ALL_PACKAGES : ALL_PACKAGES.filter((pkg) => pkg.name !== "@opentui/vue") +const PACKAGES = ALL_PACKAGES function setupNpmAuth(): void { if (!process.env.NPM_AUTH_TOKEN) { @@ -266,10 +255,6 @@ function main(): void { console.log("OpenTUI Pre-Publish Validation") console.log("=".repeat(50)) - if (!includeVue) { - console.log("INFO: Skipping @opentui/vue (use --include-vue to include)") - } - // Setup NPM authentication once console.log("\nINFO: Setting up NPM authentication...") setupNpmAuth() diff --git a/scripts/prepare-release.ts b/scripts/prepare-release.ts index 33c5b0038..ecb7f06ca 100644 --- a/scripts/prepare-release.ts +++ b/scripts/prepare-release.ts @@ -17,15 +17,13 @@ const __dirname = dirname(__filename) const rootDir = resolve(__dirname, "..") const args = process.argv.slice(2) -const includeVue = args.includes("--include-vue") let version = args.find((arg) => !arg.startsWith("--")) if (!version) { console.error("Error: Please provide a version number") - console.error("Usage: bun scripts/prepare-release.ts [--include-vue]") + console.error("Usage: bun scripts/prepare-release.ts ") console.error("Example: bun scripts/prepare-release.ts 0.2.0") console.error(" bun scripts/prepare-release.ts '*' (auto-increment patch)") - console.error(" bun scripts/prepare-release.ts 0.2.0 --include-vue") process.exit(1) } @@ -61,8 +59,7 @@ if (!/^\d+\.\d+\.\d+(-[a-zA-Z0-9.-]+)?$/.test(version)) { process.exit(1) } -const packagesText = includeVue ? "all packages" : "core, react, and solid packages" -console.log(`\nPreparing release ${version} for ${packagesText}...\n`) +console.log(`\nPreparing release ${version} for core, react, and solid packages...\n`) const corePackageJsonPath = join(rootDir, "packages", "core", "package.json") console.log("Updating @opentui/core...") @@ -120,26 +117,6 @@ try { process.exit(1) } -if (includeVue) { - const vuePackageJsonPath = join(rootDir, "packages", "vue", "package.json") - console.log("\nUpdating @opentui/vue...") - - try { - const vuePackageJson: PackageJson = JSON.parse(readFileSync(vuePackageJsonPath, "utf8")) - - vuePackageJson.version = version - - writeFileSync(vuePackageJsonPath, JSON.stringify(vuePackageJson, null, 2) + "\n") - console.log(` @opentui/vue updated to version ${version}`) - console.log(` Note: @opentui/core dependency will be set to ${version} during build`) - } catch (error) { - console.error(` Failed to update @opentui/vue: ${error}`) - process.exit(1) - } -} else { - console.log("\nSkipping @opentui/vue (use --include-vue to include)") -} - console.log("\nUpdating bun.lock...") try { execSync("bun install", { cwd: rootDir, stdio: "inherit" }) @@ -149,15 +126,13 @@ try { process.exit(1) } -const publishCmd = includeVue ? "bun run publish --include-vue" : "bun run publish" - console.log(` -Successfully prepared release ${version} for ${packagesText}! +Successfully prepared release ${version} for core, react, and solid packages! Next steps: 1. Review the changes: git diff 2. Build the packages: bun run build 3. Commit the changes: git add -A && git commit -m "Release v${version}" && git push -4. Publish to npm: ${publishCmd} +4. Publish to npm: bun run publish 5. Push to GitHub: git push -`) + `)