diff --git a/.github/workflows/development-buildandtestupmrelease.yml b/.github/workflows/development-buildandtestupmrelease.yml index 5d726a9..9e79151 100644 --- a/.github/workflows/development-buildandtestupmrelease.yml +++ b/.github/workflows/development-buildandtestupmrelease.yml @@ -4,24 +4,17 @@ on: pull_request: branches-ignore: - 'main' - # Ignore PRs targeting main - # Allows you to run this workflow manually from the Actions tab workflow_dispatch: concurrency: group: ${{ github.ref }} cancel-in-progress: true -# Ensure default token scopes and inherit org-level secrets via env mapping permissions: contents: write packages: read -env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - GIT_PAT: ${{ secrets.GIT_PAT }} - jobs: test-unity-build: name: Test Unity UPM Build @@ -30,15 +23,21 @@ jobs: strategy: fail-fast: false matrix: - os: [ubuntu-latest, windows-latest, macos-latest] + # os: [ubuntu-latest, windows-latest, macos-latest] + os: [windows-latest, macos-latest] unity-version: - - 6000.0.x - - 6000 + - '6000.0.x' + - '6000.1' + - '6000.2' + - '6000.3' + - '6000.5' + - '6000.6' + - '6000.7' include: - - os: ubuntu-latest - build-targets: StandaloneLinux64, Android + # - os: ubuntu-latest + # build-targets: StandaloneLinux64, Android - os: windows-latest - build-targets: StandaloneWindows64 + build-targets: StandaloneWindows64, Android - os: macos-latest build-targets: StandaloneOSX, iOS steps: diff --git a/.github/workflows/getpackageversionfrompackage.yml b/.github/workflows/getpackageversionfrompackage.yml new file mode 100644 index 0000000..42fb5e1 --- /dev/null +++ b/.github/workflows/getpackageversionfrompackage.yml @@ -0,0 +1,51 @@ +name: Get the package version from a UPM package.json file + +on: + workflow_call: + inputs: + build-host: + required: true + type: string + target-branch: + description: Branch to read package.json from. Defaults to the triggering ref. + required: false + type: string + default: ${{ github.ref }} + version-file-path: + description: Optional path to the package.json to read. Defaults to package.json in the repo root. + required: false + type: string + default: package.json + outputs: + packageversion: + description: The version field of the UPM package + value: ${{ jobs.get_package_version.outputs.upmpackageversion }} + +jobs: + get_package_version: + name: Get package version from UPM package + runs-on: ${{ inputs.build-host }} + outputs: + upmpackageversion: ${{ steps.getVersion.outputs.packageversion }} + steps: + - uses: actions/checkout@v7 + with: + ref: ${{ inputs.target-branch }} + + - id: getVersion + name: Read package version + shell: bash + env: + VERSION_FILE: ${{ inputs.version-file-path }} + run: | + if [ ! -f "$VERSION_FILE" ]; then + echo "::error::No package.json found at $VERSION_FILE" + exit 1 + fi + version=$(jq -r '.version // empty' "$VERSION_FILE") + if [ -z "$version" ]; then + echo "::error::package.json at $VERSION_FILE has no version" + exit 1 + fi + echo "Detected package version $version" + echo "packageversion=$version" >> "$GITHUB_OUTPUT" diff --git a/.github/workflows/main-publish.yml b/.github/workflows/main-publish.yml new file mode 100644 index 0000000..ad8ef57 --- /dev/null +++ b/.github/workflows/main-publish.yml @@ -0,0 +1,115 @@ +name: Publish main branch and increment version + +# Runs when a pull request into main is merged. The PR title selects the release type: +# - contains "no-ver" - tag the version already in package.json, no bump +# - contains "major-release" - bump major (1.x.x -> 2.0.0) +# - contains "minor-release" - bump minor (1.0.x -> 1.1.0) +# - anything else - patch release: strip the pre-release suffix (1.0.0-pre.1 -> 1.0.0), +# or bump patch if there is no suffix (1.0.0 -> 1.0.1) +# +# After tagging, development is refreshed from main and moved to the next pre-release (1.0.1-pre.1). +# +# All commits and tags are pushed by the ui-extensions-bot GitHub App using the org secrets +# RELEASE_APP_CLIENT_ID and RELEASE_APP_PRIVATE_KEY. No personal access token is involved. + +on: + pull_request: + types: + - closed + branches: + - main + +permissions: + contents: read + +concurrency: + group: release-${{ github.event.pull_request.base.ref }} + cancel-in-progress: false + +jobs: + # Read the version to tag when the PR title contains "no-ver" (no version bump) + validate-environment: + if: github.event.pull_request.merged == true && contains(github.event.pull_request.title, 'no-ver') + name: Get version from UPM package + uses: ./.github/workflows/getpackageversionfrompackage.yml + with: + build-host: ubuntu-latest + target-branch: ${{ github.event.pull_request.base.ref }} + + release-package-only: + needs: validate-environment + name: Release package only, no upversion + uses: ./.github/workflows/tagrelease.yml + with: + build-host: ubuntu-latest + target-branch: ${{ github.event.pull_request.base.ref }} + version: ${{ needs.validate-environment.outputs.packageversion }} + secrets: inherit + + upversion-major-package: + if: github.event.pull_request.merged == true && contains(github.event.pull_request.title, 'no-ver') == false && contains(github.event.pull_request.title, 'major-release') + name: Major version package and release + uses: ./.github/workflows/upversionandtagrelease.yml + with: + build-host: ubuntu-latest + build-type: major + target-branch: ${{ github.event.pull_request.base.ref }} + secrets: inherit + + upversion-minor-package: + if: github.event.pull_request.merged == true && contains(github.event.pull_request.title, 'no-ver') == false && contains(github.event.pull_request.title, 'minor-release') + name: Minor version package and release + uses: ./.github/workflows/upversionandtagrelease.yml + with: + build-host: ubuntu-latest + build-type: minor + target-branch: ${{ github.event.pull_request.base.ref }} + secrets: inherit + + # Default path when no release keyword is in the PR title + upversion-patch-package: + if: github.event.pull_request.merged == true && contains(github.event.pull_request.title, 'no-ver') == false && contains(github.event.pull_request.title, 'minor-release') == false && contains(github.event.pull_request.title, 'major-release') == false + name: Patch version package and release + uses: ./.github/workflows/upversionandtagrelease.yml + with: + build-host: ubuntu-latest + build-type: patch-release + target-branch: ${{ github.event.pull_request.base.ref }} + secrets: inherit + + release-complete: + # Runs only for a merged PR and only if no release job failed. The release jobs that did not + # match the PR title are skipped, which is fine. A real failure skips this job so the + # development refresh never runs from a half-finished release. + if: ${{ github.event.pull_request.merged == true && !failure() && !cancelled() }} + needs: [upversion-major-package, upversion-minor-package, upversion-patch-package, release-package-only] + name: Release complete + runs-on: ubuntu-latest + steps: + - name: Release done + run: echo "Release done, refreshing development" + + # Merge the released main branch back into development + refresh-development: + if: ${{ needs.release-complete.result == 'success' }} + needs: [release-complete] + name: Refresh development branch + uses: ./.github/workflows/refreshbranch.yml + with: + build-host: ubuntu-latest + target-branch: development + source-branch: ${{ github.event.pull_request.base.ref }} + secrets: inherit + + # Move development to the next pre-release version, no tag + upversion-development: + if: ${{ needs.refresh-development.result == 'success' }} + needs: [refresh-development] + name: Upversion the development branch for the next release + uses: ./.github/workflows/upversionandtagrelease.yml + with: + build-host: ubuntu-latest + build-type: patch-pre + target-branch: development + createTag: false + secrets: inherit diff --git a/.github/workflows/refreshbranch.yml b/.github/workflows/refreshbranch.yml new file mode 100644 index 0000000..2a9bc27 --- /dev/null +++ b/.github/workflows/refreshbranch.yml @@ -0,0 +1,71 @@ +name: Refresh branch + +# Merges one branch into another and pushes the result. Used after a release to bring +# the version bump on main back into development. + +on: + workflow_call: + inputs: + build-host: + required: true + type: string + target-branch: + description: Branch that receives the merge, for example development + required: true + type: string + source-branch: + description: Branch merged into the target, for example main + required: true + type: string + secrets: + RELEASE_APP_CLIENT_ID: + required: true + RELEASE_APP_PRIVATE_KEY: + required: true + +jobs: + refreshBranch: + name: Refresh ${{ inputs.target-branch }} from ${{ inputs.source-branch }} + runs-on: ${{ inputs.build-host }} + steps: + - name: Create release App token + uses: actions/create-github-app-token@v3 + id: app-token + with: + client-id: ${{ secrets.RELEASE_APP_CLIENT_ID }} + private-key: ${{ secrets.RELEASE_APP_PRIVATE_KEY }} + + - name: Get App bot user id + id: bot + shell: bash + env: + GH_TOKEN: ${{ steps.app-token.outputs.token }} + APP_SLUG: ${{ steps.app-token.outputs.app-slug }} + run: echo "id=$(gh api "/users/${APP_SLUG}[bot]" --jq .id)" >> "$GITHUB_OUTPUT" + + - uses: actions/checkout@v7 + with: + ref: ${{ inputs.target-branch }} + fetch-depth: 0 + clean: true + token: ${{ steps.app-token.outputs.token }} + + - name: Configure git identity + shell: bash + env: + APP_SLUG: ${{ steps.app-token.outputs.app-slug }} + BOT_ID: ${{ steps.bot.outputs.id }} + run: | + git config --global user.name "${APP_SLUG}[bot]" + git config --global user.email "${BOT_ID}+${APP_SLUG}[bot]@users.noreply.github.com" + + - name: Merge source branch into target branch + shell: bash + env: + TARGET_BRANCH: ${{ inputs.target-branch }} + SOURCE_BRANCH: ${{ inputs.source-branch }} + run: | + git fetch origin "$SOURCE_BRANCH" + git merge --no-edit -m "Refresh $TARGET_BRANCH from $SOURCE_BRANCH [skip ci]" FETCH_HEAD + git push origin "HEAD:$TARGET_BRANCH" + echo "Branch $TARGET_BRANCH updated with changes from $SOURCE_BRANCH" diff --git a/.github/workflows/release-preflight.yml b/.github/workflows/release-preflight.yml new file mode 100644 index 0000000..b2c9174 --- /dev/null +++ b/.github/workflows/release-preflight.yml @@ -0,0 +1,94 @@ +name: Release preflight + +# Runs on pull requests into main (the release PRs) and on demand. It publishes nothing. +# It confirms the ui-extensions-bot GitHub App can mint a token with push access to this repo, +# and reports the version main-publish will produce when the PR is merged. + +on: + pull_request: + branches: + - main + workflow_dispatch: + +permissions: + contents: read + +jobs: + validate-release-app: + name: Validate release App and version + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7 + with: + # Do not store the workflow token in the repo config. The push probe below must send only the App token. + persist-credentials: false + + - name: Create release App token + uses: actions/create-github-app-token@v3 + id: app-token + with: + client-id: ${{ secrets.RELEASE_APP_CLIENT_ID }} + private-key: ${{ secrets.RELEASE_APP_PRIVATE_KEY }} + + - name: Check the App can push to this repo + shell: bash + env: + GH_TOKEN: ${{ steps.app-token.outputs.token }} + APP_SLUG: ${{ steps.app-token.outputs.app-slug }} + TARGET_BRANCH: ${{ github.event.pull_request.base.ref || github.ref_name }} + run: | + botid=$(gh api "/users/${APP_SLUG}[bot]" --jq .id) + echo "App $APP_SLUG resolves to bot user id $botid" + echo "Repository permissions block as seen by the token (all false for an installation token, not meaningful): $(gh api "repos/$GITHUB_REPOSITORY" --jq '.permissions // "absent"')" + + # The repository object's permissions block describes a user, so it cannot test an App token. + # A dry-run push exercises write access on the server without writing anything. It targets a + # branch name that does not exist, because the runner has a shallow checkout and a push to an + # existing branch fails the client-side fast-forward check before proving anything. The empty + # extraheader clears any Authorization header left in the repo config so only the App token is sent. + auth=$(printf 'x-access-token:%s' "$GH_TOKEN" | base64 -w 0) + probe_ref="refs/heads/preflight-probe-$GITHUB_RUN_ID" + if output=$(git -c "http.https://github.com/.extraheader=" -c "http.https://github.com/.extraheader=AUTHORIZATION: basic $auth" push --dry-run "https://github.com/$GITHUB_REPOSITORY.git" "HEAD:$probe_ref" 2>&1); then + echo "$output" + echo "App $APP_SLUG can push to $GITHUB_REPOSITORY. Releases will be pushed to $TARGET_BRANCH." + else + echo "$output" + if echo "$output" | grep -qE "403|not granted|Permission"; then + echo "::error::GitHub refused write access for App $APP_SLUG on $GITHUB_REPOSITORY. Check the App installation covers this repository and has Contents read and write." + else + echo "::error::The push probe failed for a reason other than permissions. See the git output above." + fi + exit 1 + fi + + - name: Report the version this PR will release + shell: bash + env: + PR_TITLE: ${{ github.event.pull_request.title }} + run: | + version=$(jq -r .version package.json) + base="${version%%-*}" + IFS=. read -r major minor patch <<< "$base" + case "$PR_TITLE" in + *no-ver*) kind="no bump"; next="$version" ;; + *major-release*) kind="major bump"; next="$((major + 1)).0.0" ;; + *minor-release*) kind="minor bump"; next="$major.$((minor + 1)).0" ;; + *) + kind="patch release" + if [ "$base" != "$version" ]; then next="$base"; else next="$major.$minor.$((patch + 1))"; fi + ;; + esac + echo "package.json version: $version" + echo "PR title: $PR_TITLE" + echo "On merge, main-publish will do a $kind and tag v$next" + if git ls-remote --exit-code --tags origin "refs/tags/v$next" > /dev/null 2>&1; then + echo "::error::Tag v$next already exists. The release would fail at the tag step." + exit 1 + fi + { + echo "## Release preflight" + echo "" + echo "- package.json version: \`$version\`" + echo "- release type: $kind" + echo "- tag on merge: \`v$next\`" + } >> "$GITHUB_STEP_SUMMARY" diff --git a/.github/workflows/tagrelease.yml b/.github/workflows/tagrelease.yml new file mode 100644 index 0000000..0fa395e --- /dev/null +++ b/.github/workflows/tagrelease.yml @@ -0,0 +1,85 @@ +name: Tag Release + +# Tags the head of a branch with v without changing package.json. +# Called by main-publish for PRs whose title contains "no-ver". + +on: + workflow_call: + inputs: + build-host: + required: true + type: string + version: + description: Version to tag, without the leading v. Must match package.json. + required: true + type: string + target-branch: + description: Branch whose head is tagged. Defaults to the triggering ref. + required: false + type: string + default: ${{ github.ref }} + secrets: + RELEASE_APP_CLIENT_ID: + required: true + RELEASE_APP_PRIVATE_KEY: + required: true + +jobs: + packageRelease: + name: Tag UPM package release + runs-on: ${{ inputs.build-host }} + steps: + - name: Create release App token + uses: actions/create-github-app-token@v3 + id: app-token + with: + client-id: ${{ secrets.RELEASE_APP_CLIENT_ID }} + private-key: ${{ secrets.RELEASE_APP_PRIVATE_KEY }} + + - name: Get App bot user id + id: bot + shell: bash + env: + GH_TOKEN: ${{ steps.app-token.outputs.token }} + APP_SLUG: ${{ steps.app-token.outputs.app-slug }} + run: echo "id=$(gh api "/users/${APP_SLUG}[bot]" --jq .id)" >> "$GITHUB_OUTPUT" + + - uses: actions/checkout@v7 + with: + ref: ${{ inputs.target-branch }} + fetch-depth: 0 + clean: true + token: ${{ steps.app-token.outputs.token }} + + - name: Configure git identity + shell: bash + env: + APP_SLUG: ${{ steps.app-token.outputs.app-slug }} + BOT_ID: ${{ steps.bot.outputs.id }} + run: | + git config --global user.name "${APP_SLUG}[bot]" + git config --global user.email "${BOT_ID}+${APP_SLUG}[bot]@users.noreply.github.com" + + - name: Check if tag exists + shell: pwsh + env: + TAG_NAME: v${{ inputs.version }} + run: | + if (git tag --list $env:TAG_NAME) { + Write-Error "$env:TAG_NAME tag already exists" + exit 1 + } + Write-Host "$env:TAG_NAME is available" + + - name: Create tag and push + shell: pwsh + env: + TAG_NAME: v${{ inputs.version }} + run: | + git tag -a $env:TAG_NAME -m "$env:TAG_NAME Release [skip ci]" + git push origin $env:TAG_NAME + if ($LASTEXITCODE -ne 0) { + Write-Error "Failed to push tag $env:TAG_NAME" + exit 1 + } + Write-Host "Tagged $(git rev-parse --short HEAD) as $env:TAG_NAME" diff --git a/.github/workflows/upversionandtagrelease.yml b/.github/workflows/upversionandtagrelease.yml new file mode 100644 index 0000000..38f4d6f --- /dev/null +++ b/.github/workflows/upversionandtagrelease.yml @@ -0,0 +1,173 @@ +name: UpVersion UPM package and create release tag + +# Bumps the version in package.json on a branch, commits and pushes the bump, and optionally tags it. +# Called by main-publish for the release itself (on main) and for the follow-up bump on development. + +on: + workflow_call: + inputs: + build-host: + required: true + type: string + build-type: + description: | + How to change the version in package.json. One of: + build - 1.0.0-pre.1+1 increment build metadata + pre-release - 1.0.0-pre.2 increment the pre-release number + patch-release - 1.0.0 strip the pre-release suffix, or bump patch if there is none + patch - 1.0.1 bump patch + patch-pre - 1.0.1-pre.1 bump patch and start a new pre-release + minor - 1.1.0 bump minor + major - 2.0.0 bump major + required: false + default: pre-release + type: string + target-branch: + description: Branch name to bump and push. Must be a branch name such as main or development. + required: false + type: string + default: ${{ github.ref }} + createTag: + description: Tag the bump commit as v and push the tag. + required: false + type: boolean + default: true + outputs: + packageversion: + description: The new version written to package.json + value: ${{ jobs.packageRelease.outputs.packageversion }} + secrets: + RELEASE_APP_CLIENT_ID: + required: true + RELEASE_APP_PRIVATE_KEY: + required: true + +jobs: + packageRelease: + name: Bump UPM package version and tag + runs-on: ${{ inputs.build-host }} + outputs: + packageversion: ${{ steps.getpackageversion.outputs.packageversion }} + steps: + - name: Create release App token + uses: actions/create-github-app-token@v3 + id: app-token + with: + client-id: ${{ secrets.RELEASE_APP_CLIENT_ID }} + private-key: ${{ secrets.RELEASE_APP_PRIVATE_KEY }} + + - name: Get App bot user id + id: bot + shell: bash + env: + GH_TOKEN: ${{ steps.app-token.outputs.token }} + APP_SLUG: ${{ steps.app-token.outputs.app-slug }} + run: echo "id=$(gh api "/users/${APP_SLUG}[bot]" --jq .id)" >> "$GITHUB_OUTPUT" + + - uses: actions/checkout@v7 + with: + ref: ${{ inputs.target-branch }} + fetch-depth: 0 + clean: true + token: ${{ steps.app-token.outputs.token }} + + - name: Configure git identity + shell: bash + env: + APP_SLUG: ${{ steps.app-token.outputs.app-slug }} + BOT_ID: ${{ steps.bot.outputs.id }} + run: | + git config --global user.name "${APP_SLUG}[bot]" + git config --global user.email "${BOT_ID}+${APP_SLUG}[bot]@users.noreply.github.com" + + - id: getpackageversion + name: Bump UPM package version + shell: pwsh + env: + BUILD_TYPE: ${{ inputs.build-type }} + TARGET_BRANCH: ${{ inputs.target-branch }} + run: | + $packageFile = 'package.json' + if (-not (Test-Path $packageFile)) { + Write-Error "No package.json found at $packageFile" + exit 1 + } + + $content = Get-Content $packageFile -Raw + $packageInfo = $content | ConvertFrom-Json + $current = [System.Management.Automation.SemanticVersion]$packageInfo.version + Write-Host "Current package version: $current" + + $major = $current.Major + $minor = $current.Minor + $patch = $current.Patch + $preLabel = $current.PreReleaseLabel + $preNumber = if ($preLabel) { [int]($preLabel -replace '^pre\.', '') } else { 0 } + $build = if ($current.BuildLabel) { [int]$current.BuildLabel } else { 0 } + + $semver = [System.Management.Automation.SemanticVersion] + switch ($env:BUILD_TYPE) { + 'build' { $new = $semver::new($major, $minor, $patch, $preLabel, "$($build + 1)") } + 'pre-release' { $new = $semver::new($major, $minor, $patch, "pre.$($preNumber + 1)") } + 'patch-release' { + if ($preLabel) { $new = $semver::new($major, $minor, $patch) } + else { $new = $semver::new($major, $minor, $patch + 1) } + } + 'patch' { $new = $semver::new($major, $minor, $patch + 1) } + 'patch-pre' { $new = $semver::new($major, $minor, $patch + 1, 'pre.1') } + 'minor' { $new = $semver::new($major, $minor + 1, 0) } + 'major' { $new = $semver::new($major + 1, 0, 0) } + default { + Write-Error "Unknown build-type '$env:BUILD_TYPE'" + exit 1 + } + } + + $newVersion = $new.ToString() + Write-Host "Upgrading package version [$current] to [$newVersion]" + + # Replace only the top-level version field so the rest of package.json keeps its formatting + $pattern = [regex]'"version"\s*:\s*"[^"]*"' + $updated = $pattern.Replace($content, ('"version": "' + $newVersion + '"'), 1) + [System.IO.File]::WriteAllText((Resolve-Path $packageFile).Path, $updated) + + if (git status --porcelain -- $packageFile) { + git add $packageFile + git commit -m "Auto increment package version to $newVersion [skip ci]" + git push origin "HEAD:$env:TARGET_BRANCH" + if ($LASTEXITCODE -ne 0) { + Write-Error "Failed to push version bump to $env:TARGET_BRANCH" + exit 1 + } + } + else { + Write-Host "package.json already at $newVersion, nothing to commit" + } + + "packageversion=$newVersion" >> $env:GITHUB_OUTPUT + + - name: Check if tag exists + if: ${{ inputs.createTag == true }} + shell: pwsh + env: + TAG_NAME: v${{ steps.getpackageversion.outputs.packageversion }} + run: | + if (git tag --list $env:TAG_NAME) { + Write-Error "$env:TAG_NAME tag already exists" + exit 1 + } + Write-Host "$env:TAG_NAME is available" + + - name: Publish package tag + if: ${{ inputs.createTag == true }} + shell: pwsh + env: + TAG_NAME: v${{ steps.getpackageversion.outputs.packageversion }} + run: | + git tag -a $env:TAG_NAME -m "$env:TAG_NAME Release" + git push origin $env:TAG_NAME + if ($LASTEXITCODE -ne 0) { + Write-Error "Failed to push tag $env:TAG_NAME" + exit 1 + } + Write-Host "Tagged $(git rev-parse --short HEAD) as $env:TAG_NAME" diff --git a/CHANGELOG.md b/CHANGELOG.md index 14b74ab..8a44199 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,103 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/). -## Release 1.0.0 - Launch - tbc +## Release 1.0.0 - Launch - 2026/09 -Coming Soon. +The first release of **Unity UI Toolkit Extensions** - a brand-new companion to the long-running uGUI Extensions library, built from the ground up for Unity 6 and the UI Toolkit (UITK) system. + +> **Two packages. One ecosystem.** These notes cover the **UI Toolkit** package (`com.unity.uitoolkitextensions`). It launches alongside the V3 relaunch of its sibling under the shared 3.0 banner: [UI Extensions (uGUI)](https://github.com/Unity-UI-Extensions/com.unity.uiextensions). + +### Highlights + +- **Built for Unity 6 + UI Toolkit** - a native UITK control library targeting Unity `6000.0` and above, distributed via the Unity Package Manager / OpenUPM. +- **25 controls out of the box** - covering navigation, layout, forms, media, toggles, and feedback, all using the `Unity.UI.Extensions` namespace. +- **Editor menu and starter templates** - add any control to a scene or document from the `GameObject/UI Toolkit/Extensions` and `Assets/Create/UI Toolkit/Extensions` menus, with demo content and styling already wired up. +- **Reusable utilities** - helpers for code-first VisualElement construction, attention animations, and procedural textures. +- **12 ready-to-run sample scenes** - included as a UPM sample, demonstrating the controls in realistic combinations. + +### Added + +The initial release contains 25 controls and a handful of extensions plus utilities to help making UI Toolkit UX far easier. + +#### Editor integration + +- **Editor menu integration** - a new `GameObject/UI Toolkit/Extensions` menu adds any of the 22 UXML-placeable controls to the scene in basic form with demo content, automatically creating a `UIDocument` and `PanelSettings` (with the default runtime theme) if the scene has none. The UI Toolkit counterpart of the uGUI Extensions `GameObject/UI/Extensions` menu. +- **Starter templates** - per-control starter UXML templates with demo content, also available from `Assets/Create/UI Toolkit/Extensions` for dropping into existing documents. Instantiated copies are placed under `Assets/UI Toolkit Extensions` so they remain freely editable. +- **Self-applying control styles** - while authoring (UI Builder) and in Play Mode, controls attach the shared extensions stylesheet automatically on construction, so they render correctly without manually referencing the stylesheet. Overlay controls (`DropDownControl`, `DropDownMenuControl`, `ImageCropOverlayControl`) also style their root-attached panels directly. In player builds, styling comes from the explicit stylesheet references in the starter templates and from the theme generated by the editor menu (which imports the shared stylesheet panel-wide). + +#### Controls + +##### Navigation & Layout + +- **ScrollSnap** - Page-based snap scroller with manual/swipe modes, validation gating, and restricted-movement events. +- **QuadrantStepper** - Segmented sliding-overlay step selector for tab bars, mode switchers, and category filters. +- **CollapsibleSection** - Accordion panel with animated max-height expand/collapse, ideal for FAQs and settings groups. +- **PageDotIndicator** - Row of pagination dots; all dots up to and including the current page are highlighted. +- **StepProgressBar** - Horizontal gradient fill bar driven by step counts. +- **ScreenHeader** - Configurable top app-bar with title, notch spacer, and up to four edge action buttons (events only, no app state). +- **ElasticListView** - Vertical list with iOS-style elastic overscroll and an optional swipe-up "load more" trigger. Items can be declared as child elements in UXML or added with plain `Add()`, as well as from code with `AddItem()`/`SetItems()`. +- **DropDownMenuControl** - Anchored overlay action menu with large tappable rows and backdrop-tap dismiss (distinct from the value-picker `DropDownControl`). + +##### Inputs & Forms + +- **PillInputField** - Mobile-aware labeled text input with password mode, multiline support, and validation events. +- **RoundedInputField** - Rounded input field with custom placeholder rendering. +- **PillSelector** - Read-only tap-to-open selector row with chevron icon. +- **PillButton** - Pill-shaped gradient CTA button with flash feedback animation. +- **IconLabelButton** - Row button with a 24 × 24 icon and label, ideal for menu items and list actions. +- **DropDownControl** - Custom dropdown selector with a scrollable list of selectable entries. +- **SocialLinkContainer** - Editable list of platform-labelled social-link fields with add/remove and a platform picker. + +##### Media & Images + +- **CircularImageButton** - Circular tappable image with no-image overlay, ideal for avatars and profile photos. +- **GrayscaleImage** - Immediate-mode image renderer with a toggleable greyscale shader effect. +- **LoadingIcon** - Rotating spinner with configurable speed and optional interaction blocking. +- **ImageCropOverlayControl** - Interactive overlay for framing and cropping an image. + +##### Toggles & Selection + +- **ToggleButton** - Binary image toggle that fires an event on every press. +- **ColorToggleButton** - Tint-colored toggle with ripple animation and selected overlay (extends `ToggleButton`). +- **ColorToggleGroup** - Single-selection group of `ColorToggleButton` items with tap and drag-to-select support. + +##### Feedback & Utility + +- **ToastSwipeDismissManipulator** - Pointer manipulator that adds swipe-to-dismiss gesture handling to any element. +- **ComingSoonMessage** - Centered placeholder panel for in-progress features. +- **NotificationBadge** - Small rounded unread-count badge that auto-hides at zero and clamps to "99+". + +#### Utilities + +- **UIToolkitExtensions** - Static helper for creating, parenting, and wiring VisualElements from code. +- **VisualElementShakeUtility** - Horizontal shake animation for validation and attention feedback. +- **ProceduralTextureUtility** - Generates procedural textures (e.g. rounded rects and gradients) for control styling. + +#### Examples + +Included as the **UI Toolkit Extensions Samples** package sample: + +- **ScrollSnap + PageDotIndicator** - Horizontal paging with dot indicator and a ComingSoonMessage page. +- **Registration Form** - Full form using PillInputField, RoundedInputField, PillButton, PillSelector, and shake validation. +- **Step Wizard** - Multi-step flow using QuadrantStepper and StepProgressBar. +- **Content Explorer** - LoadingIcon reveal with CollapsibleSection and IconLabelButton items. +- **Profile Editor** - CircularImageButton, GrayscaleImage, ToggleButton, and ColorToggleGroup. +- **Toast Notifications** - Swipe-to-dismiss toast stack using ToastSwipeDismissManipulator. +- **Dropdown Phone Entry** - Phone-number entry pairing a country-code DropDownControl with PillInputFields. +- **Image Crop Overlay** - Pan / pinch-zoom crop flow using ImageCropOverlayControl and CircularImageButton. +- **Notification List** - Elastic notification feed using ElasticListView, NotificationBadge, and PillButton. +- **Screen Header** - Top app-bar demo wiring ScreenHeader's title and action events. +- **Scroll Snap (Split Views)** - The same ScrollSnap built three ways - C#, UXML, and a split layout. +- **Social Links** - Editable social-links section using SocialLinkContainer and PillButton. + +### Changed + +Relative to the `1.0-preview.1` pre-release: + +- Package dependency corrected from the deprecated `com.unity.ui` preview package to the built-in `com.unity.modules.uielements` module. +- Removed leftover uGUI assembly references from the Runtime and Editor assembly definitions, and the placeholder `DumbScript` from the Editor assembly. + +### Contributors + +Huge thanks to everyone who helped bring the inaugural UI Toolkit Extensions release together: +[@SimonDarksideJ](https://github.com/SimonDarksideJ), and the wider Unity UI Extensions community. diff --git a/Documentation~/ScrollSnap.md b/Documentation~/ScrollSnap.md deleted file mode 100644 index f3dead3..0000000 --- a/Documentation~/ScrollSnap.md +++ /dev/null @@ -1,220 +0,0 @@ -# ScrollSnap - -## Summary - -`ScrollSnap` is a page-based UI Toolkit container that arranges children as pages and snaps to a page boundary after gesture or wheel input. It supports: - -- Horizontal or vertical paging -- Programmatic page navigation (`GoToPage`, `MoveNext`, `MovePrevious`) -- Optional touch/pointer gesture control -- Optional validation/restriction flow for swipe attempts -- Smooth snap animation with configurable easing -- Page spacing via per-page paddings - -Typical use cases: - -- Onboarding or wizard flows -- Carousel-like page navigation -- Validation-gated step transitions (for example: required fields before moving forward) - -## Properties - -| Name | Description | Options | -|---|---|---| -| `Orientation` | Paging axis for layout and swipe direction. | `Horizontal` (default), `Vertical` | -| `PageSize` | Explicit page size in pixels. If `<= 0`, uses viewport resolved size (`width` for horizontal, `height` for vertical). | `float` (`<= 0` means auto) | -| `ManualMovementEnabled` | Enables pointer/touch and wheel gesture handling by the control. If disabled, input gestures are ignored and only programmatic navigation changes pages. | `true` / `false` (default `false`) | -| `OnlySinglePageSwipeAllowed` | Limits swipe transitions to one page max per completed gesture. Keeps behavior predictable for step-based flows. | `true` / `false` (default `true`) | -| `ValidatePageChange` | Enables swipe validation behavior using direction flags and optional async validator callback. | `true` / `false` (default `false`) | -| `CanMoveNextPage` | Forward movement gate when validation is enabled. Typically set externally before allowing the next forward step. | `true` / `false` (default `true`, reset to `false` after page change when validation is enabled) | -| `CanMoveBackPage` | Backward movement gate when validation is enabled. | `true` / `false` (default `true`, reset to `false` after page change when validation is enabled) | -| `AllowMoveBack` | Master backward permission gate when validation is enabled. If `false`, backward swipe attempts are restricted and snap back. | `true` / `false` (default `true`) | -| `ValidationDragLimit` | Maximum blocked-drag preview distance as a fraction of page size (`0..1`). Example: `0.2` allows 20% drag before clamp while restricted. | `float` in `0..1` (default `0.2`) | -| `IsValidatingPageChange` | Read-only state indicating an async page validation callback is currently running. | `bool` (read-only) | -| `PagePaddingLeft` | Left padding/gap per page (applied as margin). | `float` pixels, `>= 0` | -| `PagePaddingRight` | Right padding/gap per page (applied as margin). | `float` pixels, `>= 0` | -| `PagePaddingTop` | Top padding/gap per page (applied as margin). | `float` pixels, `>= 0` | -| `PagePaddingBottom` | Bottom padding/gap per page (applied as margin). | `float` pixels, `>= 0` | -| `PageCount` | Number of child pages in `contentContainer`. | `int` (read-only) | -| `CurrentPageIndex` | Current snapped page index. | `int` (read-only) | - -### USS Custom Properties - -| Name | Description | Default | -|---|---|---| -| `--scrollsnap-easing` | Snap animation easing function name. | `Linear` | -| `--scrollsnap-page-padding-left` | Left page margin/padding from USS. | `0px` | -| `--scrollsnap-page-padding-right` | Right page margin/padding from USS. | `0px` | -| `--scrollsnap-page-padding-top` | Top page margin/padding from USS. | `0px` | -| `--scrollsnap-page-padding-bottom` | Bottom page margin/padding from USS. | `0px` | -| `--scrollsnap-validation-drag-limit` | Optional validation drag-limit value (fraction). Use values like `0.2`, `0.15`, `0.3`. | Not set unless provided | - -## Events - -| Name | Description | Arguments | -|---|---|---| -| `PageChanged` | Fired when `CurrentPageIndex` changes after a completed transition/snap. | `(int currentPageIndex)` | -| `OnPageStartChange` | Fired when a swipe attempt resolves to a target page and validation logic begins. Host can pre-load target content and inspect whether movement is currently allowed by flags. | `(int targetPage, bool moveAllowed)` | -| `OnPageChangeRestricted` | Fired when a swipe attempt is denied and the control has completed returning/snapping back to the active page. Useful for user feedback animations/toasts. The argument is the attempted destination page index. | `(int attemptedTargetPage)` | -| `OnValidatePageTransition` | Optional async validator callback. Called when validation mode is enabled and direction flags allow movement. Return `true` to proceed, `false` to restrict and snap back. | `(int targetPage) => Task` | - -Note: `OnPageChangePrevented` is not a built-in `ScrollSnap` event name. It is a consumer-defined callback method name commonly subscribed to `OnPageChangeRestricted`, for example: `scrollSnap.OnPageChangeRestricted += OnPageChangePrevented;`. - -## Using the Control in Manual Mode - -Manual mode means gesture input is disabled and page movement is API-driven. - -- Set `ManualMovementEnabled = false` -- Navigate only through `GoToPage`, `MoveNext`, `MovePrevious` -- Use `force: true` when you want to bypass validation gates - -Example: - -```csharp -var snap = new ScrollSnap(); -snap.ManualMovementEnabled = false; - -// Programmatic navigation -snap.GoToPage(2, animate: true); -snap.MoveNext(animate: true); - -// Always move, even if validation mode and flags are restrictive -snap.MoveNext(animate: true, force: true); -``` - -Recommended when: - -- Navigation is fully controlled by external UI buttons -- You need deterministic transitions with no user drag gestures -- Validation/permissions are handled outside swipe interaction - -## Using the Control in Swipe Mode - -Swipe mode means gesture input is enabled and users can drag/swipe pages directly. - -- Set `ManualMovementEnabled = true` -- Optionally keep `OnlySinglePageSwipeAllowed = true` for step-by-step flows -- Enable `ValidatePageChange = true` to gate movement with restriction behavior - -Example: - -```csharp -var snap = new ScrollSnap(); -snap.ManualMovementEnabled = true; -snap.OnlySinglePageSwipeAllowed = true; -snap.ValidatePageChange = true; - -// Host-controlled movement gates -snap.CanMoveNextPage = false; -snap.CanMoveBackPage = true; -snap.AllowMoveBack = true; - -snap.OnPageStartChange += (targetPage, moveAllowed) => -{ - // Prepare content for target page, optionally show pre-transition UI -}; - -snap.OnPageChangeRestricted += targetPage => -{ - // Show why movement is blocked -}; - -snap.OnValidatePageTransition = async targetPage => -{ - // Async validation (API call, form checks, etc.) - await Task.Yield(); - return true; -}; -``` - -## Restricted Movement and Event Behavior - -When `ValidatePageChange = true`, swipe transitions follow this flow: - -1. User swipes and a target page is derived. -2. Direction gates are checked: - - Forward: `CanMoveNextPage` - - Backward: `AllowMoveBack` and `CanMoveBackPage` -3. `OnPageStartChange(targetPage, moveAllowed)` is fired. -4. If blocked by gates: - - Drag is clamped by `ValidationDragLimit` - - On release, the control snaps back - - `OnPageChangeRestricted(targetPage)` is fired after snap-back completes -5. If allowed by gates and `OnValidatePageTransition` is assigned: - - Async callback runs - - `true` => transition proceeds - - `false` => restricted snap-back and `OnPageChangeRestricted` (after snap-back completes) -6. On successful transition completion, `PageChanged(newPageIndex)` is fired. - -## Authorization Flow (Validation-Gated Navigation) - -Use this flow when page movement must be authorized by host logic (for example onboarding step completion, server-side checks, required field validation). - -### Core Idea - -- `OnPageStartChange` is the early signal: user attempted a transition. -- `moveAllowed` indicates whether current direction gates allow a possible transition. -- `OnValidatePageTransition` is the async authorization hook. -- `OnPageChangeRestricted` is the post-snap-back signal (fires after return to the original page is complete). - -### Direction Detection - -At `OnPageStartChange(targetPage, moveAllowed)`, compare: - -- `CurrentPageIndex` (current/origin page) -- `targetPage` (attempted destination) - -Rules: - -- `targetPage > CurrentPageIndex` => forward -- `targetPage < CurrentPageIndex` => backward -- `targetPage == CurrentPageIndex` => no effective move - -`CurrentPageIndex` does not advance until a successful page transition completes and `PageChanged` is raised. - -### Event Sequence: Authorized Success - -1. `OnPageStartChange(targetPage, true)` -2. Optional `OnValidatePageTransition(targetPage)` returns `true` -3. Transition animates to target -4. `PageChanged(newPageIndex)` - -### Event Sequence: Restricted by Gates - -1. `OnPageStartChange(targetPage, false)` -2. Control animates back to origin page -3. `OnPageChangeRestricted(attemptedTargetPage)` fires after snap-back completion - -### Event Sequence: Async Authorization Denied - -1. `OnPageStartChange(targetPage, true)` -2. `OnValidatePageTransition(targetPage)` returns `false` -3. Control animates back to origin page -4. `OnPageChangeRestricted(attemptedTargetPage)` fires after snap-back completion - -### Recommended Host Pattern - -- In `OnPageStartChange`: detect direction and optionally do lightweight target prep only when `moveAllowed` is true. -- In `OnPageChangeRestricted`: start validation/error animation (this now runs when the control has already returned to the origin page). -- In `PageChanged`: finalize UI state for the newly active page. - -### Important Notes - -- While async validation is running, `IsValidatingPageChange` is `true` and additional swipe gesture processing is ignored. -- `CanMoveNextPage` and `CanMoveBackPage` are reset to `false` after a page change when validation mode is enabled. -- Use fraction values for drag limit configuration (for example `0.2` for 20%). -- Programmatic movement remains available at all times via the `force` override: - -```csharp -snap.MoveNext(animate: true, force: true); -``` - -### Programmatic API Summary - -| Method | Purpose | -|---|---| -| `GoToPage(int index, bool animate = true, bool force = false)` | Navigate to a specific page | -| `MoveNext(bool animate = true, bool force = false)` | Navigate to next page | -| `MovePrevious(bool animate = true, bool force = false)` | Navigate to previous page | - -Use `force: true` when a host flow has completed external validation and wants to advance immediately regardless of current swipe-gate state. diff --git a/Documentation~/VisualElementShakeUtility.md b/Documentation~/VisualElementShakeUtility.md deleted file mode 100644 index 84e981f..0000000 --- a/Documentation~/VisualElementShakeUtility.md +++ /dev/null @@ -1,70 +0,0 @@ -# VisualElementShakeUtility - -## Summary - -VisualElementShakeUtility provides a reusable horizontal shake animation for any target VisualElement. It is intended for validation and attention feedback scenarios where movement needs to be short, configurable, and deterministic. - -Typical use cases: - -- Invalid form step feedback -- Restricted page-transition feedback -- Highlighting a specific field or section after failed validation - -## Public API - -| Name | Description | Options | -|---|---|---| -| Shake(VisualElement target, int wobbleCount = 3, int wobbleDurationMs = 70, float amplitudePixels = 10f, Func easingCurve = null, Action onCompleted = null) | Starts a horizontal wobble sequence on the target element. If another shake is already active on the same target, the current animation is stopped and replaced with a new one from the base position. | target required, wobbleCount >= 1, wobbleDurationMs >= 1, amplitudePixels >= 0 | -| StopShake(VisualElement target, bool resetToBasePosition = true) | Stops the active shake on the target element. Optionally restores the original translate position captured when the shake started. | target required, resetToBasePosition true/false | - -## Behavior Notes - -- The effect animates translate X and restores the original translate state at completion. -- Repeated calls on the same target are deterministic: replace current shake, then restart. -- The default easing curve is Easing.OutCubic when no curve is provided. -- The utility unregisters its detach callback when stopping or completing. - -## Usage - -```csharp -using UnityEngine.UIElements; -using UnityUIToolkit.Extensions; - -// Example: 4 wobble cycles, 60ms per segment, 12px amplitude -VisualElementShakeUtility.Shake( - target: myElement, - wobbleCount: 4, - wobbleDurationMs: 60, - amplitudePixels: 12f); - -// Optional: stop manually -VisualElementShakeUtility.StopShake(myElement); -``` - -## ScrollSnap Restricted Transition Example - -Use this pattern when `ScrollSnap` rejects a page transition and you want immediate visual feedback on a specific element. - -```csharp -using UnityEngine.UIElements; -using UnityUIToolkit.Extensions; - -// Example setup references -// scrollSnap: your ScrollSnap instance -// validationPanel: the VisualElement to shake on restriction - -scrollSnap.OnPageChangeRestricted += _ => -{ - VisualElementShakeUtility.Shake( - target: validationPanel, - wobbleCount: 3, - wobbleDurationMs: 70, - amplitudePixels: 10f); -}; -``` - -Notes: - -- Keep wobble duration short so feedback feels responsive. -- Shake the smallest relevant container (for example one section panel) to keep intent clear. -- Repeated restrictions are safe; the utility replaces any in-progress shake on the same target. diff --git a/Documentation~/com.unity.uitoolkiyextensions.md b/Documentation~/com.unity.uitoolkiyextensions.md index 1765b42..c4d5425 100644 --- a/Documentation~/com.unity.uitoolkiyextensions.md +++ b/Documentation~/com.unity.uitoolkiyextensions.md @@ -6,7 +6,7 @@ The Unity UI Toolkit Extensions project is a collection of extension scripts/eff You can follow the UI Toolkit Extensions team for updates and news on: -### [Twitter - #unityuiextensions](https://twitter.com/search?q=%23unityuiextensions) / [Facebook](https://www.facebook.com/UnityUIExtensions/) / [YouTube](https://www.youtube.com/@UnityUIExtensions) +## [Twitter - #unityuiextensions](https://twitter.com/search?q=%23unityuiextensions) / [Facebook](https://www.facebook.com/UnityUIExtensions/) / [YouTube](https://www.youtube.com/@UnityUIExtensions) > Ways to get in touch: > @@ -29,10 +29,75 @@ For a full list of the controls and how they are used, please see the [online do ## Control References -ScrollSnap is a page-based UI Toolkit navigation control that arranges child elements as discrete pages and snaps cleanly between them using swipe, wheel, or programmatic movement. It is designed for flows such as onboarding, step-by-step forms, and carousel-style interfaces where predictable page boundaries, optional movement validation, restricted swipe feedback, and smooth animated transitions are important. +### Navigation & Layout -- [ScrollSnap](ScrollSnap.md) - Page-based snap scroller with manual/sweep modes, validation gating, and restricted-movement events. -- [VisualElementShakeUtility](VisualElementShakeUtility.md) - Generic UI Toolkit utility to shake a target VisualElement with configurable wobble count and wobble speed. +- **ScrollSnap** — Page-based snap scroller with manual/swipe modes, validation gating, and restricted-movement events. +- **QuadrantStepper** — Segmented sliding-overlay step selector for tab bars, mode switchers, and category filters. +- **CollapsibleSection** — Accordion panel with animated max-height expand/collapse, ideal for FAQs and settings groups. +- **PageDotIndicator** — Row of pagination dots; all dots up to and including the current page are highlighted. +- **StepProgressBar** — Horizontal gradient fill bar driven by step counts. +- **ScreenHeader** — Configurable top app-bar with title, notch spacer, and up to four edge action buttons. +- **ElasticListView** — Vertical list with iOS-style elastic overscroll and an optional swipe-up "load more" trigger. +- **DropDownMenuControl** — Anchored overlay action menu with large tappable rows and backdrop-tap dismiss. + +### Inputs & Forms + +- **PillInputField** — Mobile-aware labeled text input with password mode, multiline support, and validation events. +- **RoundedInputField** — Rounded input field with custom placeholder rendering. +- **PillSelector** — Read-only tap-to-open selector row with chevron icon. +- **PillButton** — Pill-shaped gradient CTA button with flash feedback animation. +- **IconLabelButton** — Row button with a 24 × 24 icon and label, ideal for menu items and list actions. +- **DropDownControl** — Custom dropdown selector with a scrollable list of selectable entries. +- **SocialLinkContainer** — Editable list of platform-labelled social-link fields with add/remove and a platform picker. + +### Media & Images + +- **CircularImageButton** — Circular tappable image with no-image overlay, ideal for avatars and profile photos. +- **GrayscaleImage** — Immediate-mode image renderer with a toggleable greyscale shader effect. +- **LoadingIcon** — Rotating spinner with configurable speed and optional interaction blocking. +- **ImageCropOverlayControl** — Interactive overlay for framing and cropping an image. + +### Toggles & Selection + +- **ToggleButton** — Binary image toggle that fires an event on every press. +- **ColorToggleButton** — Tint-colored toggle with ripple animation and selected overlay (extends `ToggleButton`). +- **ColorToggleGroup** — Single-selection group of `ColorToggleButton` items with tap and drag-to-select support. + +### Feedback & Utility + +- **ToastSwipeDismissManipulator** — Pointer manipulator that adds swipe-to-dismiss gesture handling to any element. +- **ComingSoonMessage** — Centered placeholder panel for in-progress features. +- **NotificationBadge** — Small rounded unread-count badge that auto-hides at zero and clamps to "99+". + +### Utilities + +- **UIToolkitExtensions** — Static helper for creating, parenting, and wiring VisualElements from code. +- **VisualElementShakeUtility** — Horizontal shake animation for validation and attention feedback. +- **ProceduralTextureUtility** — Generates procedural textures (e.g. rounded rects and gradients) for control styling. + +## Examples + +Ready-to-run sample scenes demonstrating controls in realistic combinations, included as the **UI Toolkit Extensions Samples** package sample: + +- **ScrollSnap + PageDotIndicator** — Horizontal paging with dot indicator and a ComingSoonMessage page. +- **Registration Form** — Full form using PillInputField, RoundedInputField, PillButton, PillSelector, and shake validation. +- **Step Wizard** — Multi-step flow using QuadrantStepper and StepProgressBar. +- **Content Explorer** — LoadingIcon reveal with CollapsibleSection and IconLabelButton items. +- **Profile Editor** — CircularImageButton, GrayscaleImage, ToggleButton, and ColorToggleGroup. +- **Toast Notifications** — Swipe-to-dismiss toast stack using ToastSwipeDismissManipulator. +- **Dropdown Phone Entry** — Phone-number entry pairing a country-code DropDownControl with PillInputFields. +- **Image Crop Overlay** — Pan / pinch-zoom crop flow using ImageCropOverlayControl and CircularImageButton. +- **Notification List** — Elastic notification feed using ElasticListView, NotificationBadge, and PillButton. +- **Screen Header** — Top app-bar demo wiring ScreenHeader's title and action events. +- **Scroll Snap (Split Views)** — The same ScrollSnap built three ways — C#, UXML, and a split layout. +- **Social Links** — Editable social-links section using SocialLinkContainer and PillButton. + +## Latest documentation + +The lists above are a snapshot for offline reference. For the most accurate and up-to-date documentation — including full API references, usage guides, and code examples for every control and sample — always refer to the **[Unity UI Extensions website](https://unity-ui-extensions.github.io/)**: + +- **[UI Toolkit Controls](https://unity-ui-extensions.github.io/uitoolkit/#controls)** — searchable control reference. +- **[Example scenes](https://unity-ui-extensions.github.io/uitoolkit/examples/)** — walkthroughs of every sample. ## Technical details @@ -42,6 +107,13 @@ This version of the Unity UI Toolkit Extensions is compatible with the following - 6000 and above - the recommended path is to use the Unity Package Manager to get access to the package. Full details for installing via UPM can be [found here](https://unity-ui-extensions.github.io/UPMInstallation.html). +> [!NOTE] +> The package comes with some default `svg` assets for use in the controls and demonstration scenes which requires the `com.unity.vectorgraphics` installed to make use of them +> +> Package Manager -> Install Package By Name -> `com.unity.vectorgraphics` +> +> However, this is completely optional, but highly recommended when working with images with the UI Toolkit. + ## [Release Notes](#release-notes) Coming soon. @@ -50,4 +122,5 @@ Coming soon. |Date|Details| |-|-| -|December 25th, 2025|V1.0.0 created, project creation| +|June 16th, 2026|V1.0.0-preview.1 created, project creation| +|July 20th, 2026|V1.0.0-preview.2 created, controls overhaul| diff --git a/Editor/ControlTemplates.meta b/Editor/ControlTemplates.meta new file mode 100644 index 0000000..69be819 --- /dev/null +++ b/Editor/ControlTemplates.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: d2af0b38f3e541e6a4d002fef9372a8b +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: \ No newline at end of file diff --git a/Editor/ControlTemplates/CircularImageButtonStarter.uxml b/Editor/ControlTemplates/CircularImageButtonStarter.uxml new file mode 100644 index 0000000..05da6cd --- /dev/null +++ b/Editor/ControlTemplates/CircularImageButtonStarter.uxml @@ -0,0 +1,7 @@ + +