diff --git a/.github/workflows/development-buildandtestupmrelease.yml b/.github/workflows/development-buildandtestupmrelease.yml index 5d726a9..6eafe78 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 @@ -32,13 +25,18 @@ jobs: matrix: os: [ubuntu-latest, 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: windows-latest - build-targets: StandaloneWindows64 + build-targets: StandaloneWindows64, Android - os: macos-latest build-targets: StandaloneOSX, iOS steps: diff --git a/CHANGELOG.md b/CHANGELOG.md index 14b74ab..6ae6374 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,89 @@ 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/06 -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. +- **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. + +#### 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. +- **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. + +### 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/Examples~ b/Examples~ index b04f5fb..e6ab227 160000 --- a/Examples~ +++ b/Examples~ @@ -1 +1 @@ -Subproject commit b04f5fbaaa0bc663fc338be8b358c4988123c6a6 +Subproject commit e6ab227c534dc041a786ac01ce1e902baec49197 diff --git a/README.md b/README.md index 7b57bba..5473742 100644 --- a/README.md +++ b/README.md @@ -46,9 +46,16 @@ Coming Soon. ----- -## [Controls and extensions listed in this project](#controls-and-extensions-listed-in-this-project) +## [Controls and extensions listed in this project](https://unity-ui-extensions.github.io/uitoolkit/#controls) -Check back often as we begin adding controls for launch. +The package ships **25 controls** across navigation, layout, forms, media, toggles and feedback, plus reusable utilities, and **12 ready-to-run sample scenes** — all using the `Unity.UI.Extensions` namespace. + +Browse the full, searchable list with API references and code examples: + +- **[Controls →](https://unity-ui-extensions.github.io/uitoolkit/#controls)** +- **[Example scenes →](https://unity-ui-extensions.github.io/uitoolkit/examples/)** + +See the [CHANGELOG](./CHANGELOG.md) for the complete control and example breakdown. ----- @@ -66,10 +73,15 @@ The primary method for install is to use OpenUPM to add the package, it can be i > For more details on using [OpenUPM CLI, check the docs here](https://github.com/openupm/openupm-cli#installation). -- Unity Package Manager - manual - Alternatively, you can also add the package manually through the Unity package manager using the scope ```com.unity.uitoolkitextensions```, see the [Unity Package Manager docs](https://learn.unity.com/tutorial/the-package-manager) for more information. +> [!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. + ## [Contribution guidelines](https://unity-ui-extensions.github.io/ContributionGuidelines) Got a script you want added? Then just fork the [GitHub repository](https://github.com/unity-UI-Extensions/com.unity.uitoolkitextensions) and submit a PR. All contributions accepted (including fixes) @@ -82,7 +94,7 @@ Just ensure: ## [License](https://raw.githubusercontent.com/Unity-UI-Extensions/com.unity.uitoolkitextensions/release/LICENSE.md) -All scripts conform to the BSD3 license and are free to use / distribute. See the [LICENSE](https://raw.githubusercontent.com/Unity-UI-Extensions/com.unity.uitoolkitextensions/release/LICENSE.md) file for more information = +All scripts conform to the MIT license and are free to use / distribute. See the [LICENSE](https://raw.githubusercontent.com/Unity-UI-Extensions/com.unity.uitoolkitextensions/release/LICENSE.md) file for more information = ## [Like what you see?](https://unity-ui-extensions.github.io/FurtherInfo) diff --git a/Runtime/Assets.meta b/Runtime/Assets.meta new file mode 100644 index 0000000..2ba2161 --- /dev/null +++ b/Runtime/Assets.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: c79aa916fd5ac9245b17a43b7845edd5 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Assets/Icons.meta b/Runtime/Assets/Icons.meta new file mode 100644 index 0000000..dea290d --- /dev/null +++ b/Runtime/Assets/Icons.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: d3dd50e33bb44274e965ca73061934ab +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Assets/Icons/Camera.svg b/Runtime/Assets/Icons/Camera.svg new file mode 100644 index 0000000..18b71e1 --- /dev/null +++ b/Runtime/Assets/Icons/Camera.svg @@ -0,0 +1,13 @@ + + + + + + diff --git a/Runtime/Assets/Icons/Camera.svg.meta b/Runtime/Assets/Icons/Camera.svg.meta new file mode 100644 index 0000000..ebe5dfd --- /dev/null +++ b/Runtime/Assets/Icons/Camera.svg.meta @@ -0,0 +1,53 @@ +fileFormatVersion: 2 +guid: 8c0cf227d9fcf3e44aa519418d91faa6 +ScriptedImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 2 + userData: + assetBundleName: + assetBundleVariant: + script: {fileID: 12408, guid: 0000000000000000e000000000000000, type: 0} + svgType: 3 + texturedSpriteMeshType: 0 + svgPixelsPerUnit: 100 + gradientResolution: 64 + alignment: 0 + customPivot: {x: 0, y: 0} + generatePhysicsShape: 0 + viewportOptions: 0 + preserveViewport: 0 + advancedMode: 0 + tessellationMode: 0 + predefinedResolutionIndex: 1 + targetResolution: 1080 + resolutionMultiplier: 1 + stepDistance: 10 + samplingStepDistance: 100 + maxCordDeviationEnabled: 0 + maxCordDeviation: 1 + maxTangentAngleEnabled: 0 + maxTangentAngle: 5 + keepTextureAspectRatio: 1 + textureSize: 256 + textureWidth: 256 + textureHeight: 256 + wrapMode: 0 + filterMode: 1 + sampleCount: 4 + preserveSVGImageAspect: 0 + useSVGPixelsPerUnit: 0 + spriteData: + TessellationDetail: 0 + SpriteName: CameraSprite + SpritePivot: {x: 0.5, y: 0.5} + SpriteAlignment: 0 + SpriteBorder: {x: 0, y: 0, z: 0, w: 0} + SpriteRect: + serializedVersion: 2 + x: 0 + y: 0 + width: 23.5 + height: 19.5 + SpriteID: 118ca26f680d45540a798dbc5d389fc7 + PhysicsOutlines: [] diff --git a/Runtime/Assets/Icons/ChevronDown.svg b/Runtime/Assets/Icons/ChevronDown.svg new file mode 100644 index 0000000..c8f6c08 --- /dev/null +++ b/Runtime/Assets/Icons/ChevronDown.svg @@ -0,0 +1,8 @@ + + + diff --git a/Runtime/Assets/Icons/ChevronDown.svg.meta b/Runtime/Assets/Icons/ChevronDown.svg.meta new file mode 100644 index 0000000..df07bb2 --- /dev/null +++ b/Runtime/Assets/Icons/ChevronDown.svg.meta @@ -0,0 +1,53 @@ +fileFormatVersion: 2 +guid: f638b3b44e869b84daa631ec14674d06 +ScriptedImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 2 + userData: + assetBundleName: + assetBundleVariant: + script: {fileID: 12408, guid: 0000000000000000e000000000000000, type: 0} + svgType: 3 + texturedSpriteMeshType: 0 + svgPixelsPerUnit: 100 + gradientResolution: 64 + alignment: 0 + customPivot: {x: 0, y: 0} + generatePhysicsShape: 0 + viewportOptions: 0 + preserveViewport: 0 + advancedMode: 0 + tessellationMode: 0 + predefinedResolutionIndex: 1 + targetResolution: 1080 + resolutionMultiplier: 1 + stepDistance: 10 + samplingStepDistance: 100 + maxCordDeviationEnabled: 0 + maxCordDeviation: 1 + maxTangentAngleEnabled: 0 + maxTangentAngle: 5 + keepTextureAspectRatio: 1 + textureSize: 256 + textureWidth: 256 + textureHeight: 256 + wrapMode: 0 + filterMode: 1 + sampleCount: 4 + preserveSVGImageAspect: 0 + useSVGPixelsPerUnit: 0 + spriteData: + TessellationDetail: 0 + SpriteName: ChevronDownSprite + SpritePivot: {x: 0.5, y: 0.5} + SpriteAlignment: 0 + SpriteBorder: {x: 0, y: 0, z: 0, w: 0} + SpriteRect: + serializedVersion: 2 + x: 0 + y: 0 + width: 16.498768 + height: 9.499383 + SpriteID: e374633a2ff29cf4fa6acdaadaeb3e68 + PhysicsOutlines: [] diff --git a/Runtime/Assets/Icons/ChevronLeft.svg b/Runtime/Assets/Icons/ChevronLeft.svg new file mode 100644 index 0000000..76478be --- /dev/null +++ b/Runtime/Assets/Icons/ChevronLeft.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/Runtime/Assets/Icons/ChevronLeft.svg.meta b/Runtime/Assets/Icons/ChevronLeft.svg.meta new file mode 100644 index 0000000..6a5f614 --- /dev/null +++ b/Runtime/Assets/Icons/ChevronLeft.svg.meta @@ -0,0 +1,53 @@ +fileFormatVersion: 2 +guid: ca2f0d0a5eafc98459fa26d0a7720f25 +ScriptedImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 2 + userData: + assetBundleName: + assetBundleVariant: + script: {fileID: 12408, guid: 0000000000000000e000000000000000, type: 0} + svgType: 3 + texturedSpriteMeshType: 0 + svgPixelsPerUnit: 100 + gradientResolution: 64 + alignment: 0 + customPivot: {x: 0, y: 0} + generatePhysicsShape: 0 + viewportOptions: 0 + preserveViewport: 0 + advancedMode: 0 + tessellationMode: 0 + predefinedResolutionIndex: 1 + targetResolution: 1080 + resolutionMultiplier: 1 + stepDistance: 10 + samplingStepDistance: 100 + maxCordDeviationEnabled: 0 + maxCordDeviation: 1 + maxTangentAngleEnabled: 0 + maxTangentAngle: 5 + keepTextureAspectRatio: 1 + textureSize: 256 + textureWidth: 256 + textureHeight: 256 + wrapMode: 0 + filterMode: 1 + sampleCount: 4 + preserveSVGImageAspect: 0 + useSVGPixelsPerUnit: 0 + spriteData: + TessellationDetail: 0 + SpriteName: ChevronLeftSprite + SpritePivot: {x: 0.5, y: 0.5} + SpriteAlignment: 0 + SpriteBorder: {x: 0, y: 0, z: 0, w: 0} + SpriteRect: + serializedVersion: 2 + x: 0 + y: 0 + width: 11.99532 + height: 21.998322 + SpriteID: 6331f4884f9bd7d46a81b5a304be5d93 + PhysicsOutlines: [] diff --git a/Runtime/Assets/Icons/ChevronRight.svg b/Runtime/Assets/Icons/ChevronRight.svg new file mode 100644 index 0000000..dae37cd --- /dev/null +++ b/Runtime/Assets/Icons/ChevronRight.svg @@ -0,0 +1,8 @@ + + + diff --git a/Runtime/Assets/Icons/ChevronRight.svg.meta b/Runtime/Assets/Icons/ChevronRight.svg.meta new file mode 100644 index 0000000..4c1730b --- /dev/null +++ b/Runtime/Assets/Icons/ChevronRight.svg.meta @@ -0,0 +1,53 @@ +fileFormatVersion: 2 +guid: 1865e2568fd8dd14ba6a274e193765f0 +ScriptedImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 2 + userData: + assetBundleName: + assetBundleVariant: + script: {fileID: 12408, guid: 0000000000000000e000000000000000, type: 0} + svgType: 3 + texturedSpriteMeshType: 0 + svgPixelsPerUnit: 100 + gradientResolution: 64 + alignment: 0 + customPivot: {x: 0, y: 0} + generatePhysicsShape: 0 + viewportOptions: 0 + preserveViewport: 0 + advancedMode: 0 + tessellationMode: 0 + predefinedResolutionIndex: 1 + targetResolution: 1080 + resolutionMultiplier: 1 + stepDistance: 10 + samplingStepDistance: 100 + maxCordDeviationEnabled: 0 + maxCordDeviation: 1 + maxTangentAngleEnabled: 0 + maxTangentAngle: 5 + keepTextureAspectRatio: 1 + textureSize: 256 + textureWidth: 256 + textureHeight: 256 + wrapMode: 0 + filterMode: 1 + sampleCount: 4 + preserveSVGImageAspect: 0 + useSVGPixelsPerUnit: 0 + spriteData: + TessellationDetail: 0 + SpriteName: ChevronRightSprite + SpritePivot: {x: 0.5, y: 0.5} + SpriteAlignment: 0 + SpriteBorder: {x: 0, y: 0, z: 0, w: 0} + SpriteRect: + serializedVersion: 2 + x: 0 + y: 0 + width: 9.499383 + height: 16.498768 + SpriteID: 2c6135774ce725f48a021087f7283057 + PhysicsOutlines: [] diff --git a/Runtime/Assets/Icons/Information-icon.svg b/Runtime/Assets/Icons/Information-icon.svg new file mode 100644 index 0000000..3ab3d0d --- /dev/null +++ b/Runtime/Assets/Icons/Information-icon.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/Runtime/Assets/Icons/Information-icon.svg.meta b/Runtime/Assets/Icons/Information-icon.svg.meta new file mode 100644 index 0000000..3d5f74d --- /dev/null +++ b/Runtime/Assets/Icons/Information-icon.svg.meta @@ -0,0 +1,53 @@ +fileFormatVersion: 2 +guid: f84f562732350a244a6dbc0b5b5b09d4 +ScriptedImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 2 + userData: + assetBundleName: + assetBundleVariant: + script: {fileID: 12408, guid: 0000000000000000e000000000000000, type: 0} + svgType: 3 + texturedSpriteMeshType: 0 + svgPixelsPerUnit: 100 + gradientResolution: 64 + alignment: 0 + customPivot: {x: 0, y: 0} + generatePhysicsShape: 0 + viewportOptions: 0 + preserveViewport: 0 + advancedMode: 0 + tessellationMode: 0 + predefinedResolutionIndex: 1 + targetResolution: 1080 + resolutionMultiplier: 1 + stepDistance: 10 + samplingStepDistance: 100 + maxCordDeviationEnabled: 0 + maxCordDeviation: 1 + maxTangentAngleEnabled: 0 + maxTangentAngle: 5 + keepTextureAspectRatio: 1 + textureSize: 256 + textureWidth: 256 + textureHeight: 256 + wrapMode: 0 + filterMode: 1 + sampleCount: 4 + preserveSVGImageAspect: 0 + useSVGPixelsPerUnit: 0 + spriteData: + TessellationDetail: 0 + SpriteName: Information-iconSprite + SpritePivot: {x: 0.5, y: 0.5} + SpriteAlignment: 0 + SpriteBorder: {x: 0, y: 0, z: 0, w: 0} + SpriteRect: + serializedVersion: 2 + x: 0 + y: 0 + width: 28.666681 + height: 28.66665 + SpriteID: 8b83deaa07380994880a96393e642d00 + PhysicsOutlines: [] diff --git a/Runtime/Assets/Icons/ListItem.svg b/Runtime/Assets/Icons/ListItem.svg new file mode 100644 index 0000000..aace926 --- /dev/null +++ b/Runtime/Assets/Icons/ListItem.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/Runtime/Assets/Icons/ListItem.svg.meta b/Runtime/Assets/Icons/ListItem.svg.meta new file mode 100644 index 0000000..72c85e2 --- /dev/null +++ b/Runtime/Assets/Icons/ListItem.svg.meta @@ -0,0 +1,53 @@ +fileFormatVersion: 2 +guid: 3728164e1ded6a5419a81c6c61840330 +ScriptedImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 2 + userData: + assetBundleName: + assetBundleVariant: + script: {fileID: 12408, guid: 0000000000000000e000000000000000, type: 0} + svgType: 3 + texturedSpriteMeshType: 0 + svgPixelsPerUnit: 100 + gradientResolution: 64 + alignment: 0 + customPivot: {x: 0, y: 0} + generatePhysicsShape: 0 + viewportOptions: 0 + preserveViewport: 0 + advancedMode: 0 + tessellationMode: 0 + predefinedResolutionIndex: 1 + targetResolution: 1080 + resolutionMultiplier: 1 + stepDistance: 10 + samplingStepDistance: 100 + maxCordDeviationEnabled: 0 + maxCordDeviation: 1 + maxTangentAngleEnabled: 0 + maxTangentAngle: 5 + keepTextureAspectRatio: 1 + textureSize: 256 + textureWidth: 256 + textureHeight: 256 + wrapMode: 0 + filterMode: 1 + sampleCount: 4 + preserveSVGImageAspect: 0 + useSVGPixelsPerUnit: 0 + spriteData: + TessellationDetail: 0 + SpriteName: ListItemSprite + SpritePivot: {x: 0.5, y: 0.5} + SpriteAlignment: 0 + SpriteBorder: {x: 0, y: 0, z: 0, w: 0} + SpriteRect: + serializedVersion: 2 + x: 0 + y: 0 + width: 18.5 + height: 13 + SpriteID: dda8d3e7abc40d344a2467123685ada7 + PhysicsOutlines: [] diff --git a/Runtime/Assets/Icons/LoadingSpinner.svg b/Runtime/Assets/Icons/LoadingSpinner.svg new file mode 100644 index 0000000..46864b7 --- /dev/null +++ b/Runtime/Assets/Icons/LoadingSpinner.svg @@ -0,0 +1,10 @@ + + + + diff --git a/Runtime/Assets/Icons/LoadingSpinner.svg.meta b/Runtime/Assets/Icons/LoadingSpinner.svg.meta new file mode 100644 index 0000000..0d8b927 --- /dev/null +++ b/Runtime/Assets/Icons/LoadingSpinner.svg.meta @@ -0,0 +1,53 @@ +fileFormatVersion: 2 +guid: 3fadac0dd7f6b7c4bb619eec41c37b8d +ScriptedImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 2 + userData: + assetBundleName: + assetBundleVariant: + script: {fileID: 12408, guid: 0000000000000000e000000000000000, type: 0} + svgType: 3 + texturedSpriteMeshType: 0 + svgPixelsPerUnit: 100 + gradientResolution: 64 + alignment: 0 + customPivot: {x: 0, y: 0} + generatePhysicsShape: 0 + viewportOptions: 0 + preserveViewport: 0 + advancedMode: 0 + tessellationMode: 0 + predefinedResolutionIndex: 1 + targetResolution: 1080 + resolutionMultiplier: 1 + stepDistance: 10 + samplingStepDistance: 100 + maxCordDeviationEnabled: 0 + maxCordDeviation: 1 + maxTangentAngleEnabled: 0 + maxTangentAngle: 5 + keepTextureAspectRatio: 1 + textureSize: 256 + textureWidth: 256 + textureHeight: 256 + wrapMode: 0 + filterMode: 1 + sampleCount: 4 + preserveSVGImageAspect: 0 + useSVGPixelsPerUnit: 0 + spriteData: + TessellationDetail: 0 + SpriteName: LoadingSpinnerSprite + SpritePivot: {x: 0.5, y: 0.5} + SpriteAlignment: 0 + SpriteBorder: {x: 0, y: 0, z: 0, w: 0} + SpriteRect: + serializedVersion: 2 + x: 0 + y: 0 + width: 21 + height: 21 + SpriteID: ca5941a35859e07478be778477070dff + PhysicsOutlines: [] diff --git a/Runtime/Assets/Icons/Volume-off.svg b/Runtime/Assets/Icons/Volume-off.svg new file mode 100644 index 0000000..95c4ed0 --- /dev/null +++ b/Runtime/Assets/Icons/Volume-off.svg @@ -0,0 +1,3 @@ + + + diff --git a/Runtime/Assets/Icons/Volume-off.svg.meta b/Runtime/Assets/Icons/Volume-off.svg.meta new file mode 100644 index 0000000..e2f6f1d --- /dev/null +++ b/Runtime/Assets/Icons/Volume-off.svg.meta @@ -0,0 +1,53 @@ +fileFormatVersion: 2 +guid: ad8a9f82228238e44aa5eb4429c9e6e5 +ScriptedImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 2 + userData: + assetBundleName: + assetBundleVariant: + script: {fileID: 12408, guid: 0000000000000000e000000000000000, type: 0} + svgType: 3 + texturedSpriteMeshType: 0 + svgPixelsPerUnit: 100 + gradientResolution: 64 + alignment: 0 + customPivot: {x: 0, y: 0} + generatePhysicsShape: 0 + viewportOptions: 0 + preserveViewport: 0 + advancedMode: 0 + tessellationMode: 0 + predefinedResolutionIndex: 1 + targetResolution: 1080 + resolutionMultiplier: 1 + stepDistance: 10 + samplingStepDistance: 100 + maxCordDeviationEnabled: 0 + maxCordDeviation: 1 + maxTangentAngleEnabled: 0 + maxTangentAngle: 5 + keepTextureAspectRatio: 1 + textureSize: 256 + textureWidth: 256 + textureHeight: 256 + wrapMode: 0 + filterMode: 1 + sampleCount: 4 + preserveSVGImageAspect: 0 + useSVGPixelsPerUnit: 0 + spriteData: + TessellationDetail: 0 + SpriteName: Volume-offSprite + SpritePivot: {x: 0.5, y: 0.5} + SpriteAlignment: 0 + SpriteBorder: {x: 0, y: 0, z: 0, w: 0} + SpriteRect: + serializedVersion: 2 + x: 0 + y: 0 + width: 23.788185 + height: 18.333067 + SpriteID: b02ea7df7636c894381e744c70700220 + PhysicsOutlines: [] diff --git a/Runtime/Assets/Icons/Volume-on.svg b/Runtime/Assets/Icons/Volume-on.svg new file mode 100644 index 0000000..22755cd --- /dev/null +++ b/Runtime/Assets/Icons/Volume-on.svg @@ -0,0 +1,3 @@ + + + diff --git a/Runtime/Assets/Icons/Volume-on.svg.meta b/Runtime/Assets/Icons/Volume-on.svg.meta new file mode 100644 index 0000000..88dde02 --- /dev/null +++ b/Runtime/Assets/Icons/Volume-on.svg.meta @@ -0,0 +1,53 @@ +fileFormatVersion: 2 +guid: 048ce00f66a1ff940b96cf6275c92a78 +ScriptedImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 2 + userData: + assetBundleName: + assetBundleVariant: + script: {fileID: 12408, guid: 0000000000000000e000000000000000, type: 0} + svgType: 3 + texturedSpriteMeshType: 0 + svgPixelsPerUnit: 100 + gradientResolution: 64 + alignment: 0 + customPivot: {x: 0, y: 0} + generatePhysicsShape: 0 + viewportOptions: 0 + preserveViewport: 0 + advancedMode: 0 + tessellationMode: 0 + predefinedResolutionIndex: 1 + targetResolution: 1080 + resolutionMultiplier: 1 + stepDistance: 10 + samplingStepDistance: 100 + maxCordDeviationEnabled: 0 + maxCordDeviation: 1 + maxTangentAngleEnabled: 0 + maxTangentAngle: 5 + keepTextureAspectRatio: 1 + textureSize: 256 + textureWidth: 256 + textureHeight: 256 + wrapMode: 0 + filterMode: 1 + sampleCount: 4 + preserveSVGImageAspect: 0 + useSVGPixelsPerUnit: 0 + spriteData: + TessellationDetail: 0 + SpriteName: Volume-onSprite + SpritePivot: {x: 0.5, y: 0.5} + SpriteAlignment: 0 + SpriteBorder: {x: 0, y: 0, z: 0, w: 0} + SpriteRect: + serializedVersion: 2 + x: 0 + y: 0 + width: 25.33079 + height: 18.494492 + SpriteID: 743c6e8efec15d7418129d3db0df8a7c + PhysicsOutlines: [] diff --git a/Runtime/Controls/CircularImageButton.meta b/Runtime/Controls/CircularImageButton.meta new file mode 100644 index 0000000..771e56e --- /dev/null +++ b/Runtime/Controls/CircularImageButton.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: f5b0dd94ca232d343bf2fa05af9c4dbb +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Controls/CircularImageButton/CircularImageButton.cs b/Runtime/Controls/CircularImageButton/CircularImageButton.cs new file mode 100644 index 0000000..2bff08c --- /dev/null +++ b/Runtime/Controls/CircularImageButton/CircularImageButton.cs @@ -0,0 +1,127 @@ +/// Credit SimonDarksideJ + +using System; +using UnityEngine; +using UnityEngine.UIElements; + +namespace UnityUIToolkit.Extensions +{ + /// + /// A circular clickable image element with a no-image overlay (icon + optional upload label) + /// that hides when an image is set. State is driven by the modifier. + /// + [UxmlElement] + public partial class CircularImageButton : VisualElement + { + public const string RootClass = "circularImageButton"; + public const string ImageClass = "circularImageButton__image"; + public const string NoImageOverlayClass = "circularImageButton__noImageOverlay"; + public const string IconClass = "circularImageButton__icon"; + public const string UploadLabelClass = "circularImageButton__uploadLabel"; + public const string HasImageClass = "circularImageButton--hasImage"; + + private readonly VisualElement imageContainer; + private readonly VisualElement noImageOverlay; + private readonly VisualElement iconElement; + private readonly Label uploadNewImageLabel; + + public event Action Clicked; + + public CircularImageButton() + { + AddToClassList(RootClass); + + focusable = true; + this.AddManipulator(new Clickable(OnClickedHandler)); + + imageContainer = UIToolkitExtensions.CreateVisualElement(this, ImageClass); + imageContainer.pickingMode = PickingMode.Ignore; + + noImageOverlay = UIToolkitExtensions.CreateVisualElement(this, NoImageOverlayClass); + noImageOverlay.pickingMode = PickingMode.Ignore; + + iconElement = UIToolkitExtensions.CreateVisualElement(noImageOverlay, IconClass); + iconElement.pickingMode = PickingMode.Ignore; + + uploadNewImageLabel = UIToolkitExtensions.CreateVisualElement