From cdaabb971751f8b174915cbd14e9c751313be306 Mon Sep 17 00:00:00 2001 From: "Simon (Darkside) Jackson" Date: Tue, 23 Jun 2026 11:05:52 +0100 Subject: [PATCH 1/8] Cleanup task --- CHANGELOG.md | 76 +- .../Controls/CircularImageButton.md | 100 +++ Documentation~/Controls/CollapsibleSection.md | 97 +++ Documentation~/Controls/ColorToggleButton.md | 105 +++ Documentation~/Controls/ColorToggleGroup.md | 92 +++ Documentation~/Controls/ComingSoonMessage.md | 91 +++ Documentation~/Controls/DropDownControl.md | 77 ++ Documentation~/Controls/GrayscaleImage.md | 92 +++ Documentation~/Controls/IconLabelButton.md | 99 +++ .../Controls/ImageCropOverlayControl.md | 85 +++ Documentation~/Controls/LoadingIcon.md | 98 +++ Documentation~/Controls/PageDotIndicator.md | 89 +++ Documentation~/Controls/PillButton.md | 101 +++ Documentation~/Controls/PillInputField.md | 113 +++ Documentation~/Controls/PillSelector.md | 101 +++ Documentation~/Controls/QuadrantStepper.md | 118 ++++ Documentation~/Controls/RoundedInputField.md | 99 +++ Documentation~/Controls/StepProgressBar.md | 90 +++ .../Controls/ToastSwipeDismissManipulator.md | 111 +++ Documentation~/Controls/ToggleButton.md | 99 +++ Documentation~/Examples/ContentExplorer.md | 71 ++ Documentation~/Examples/DropDownPhoneEntry.md | 47 ++ .../Examples/ImageCropOverlayDemo.md | 50 ++ Documentation~/Examples/ProfileEditor.md | 59 ++ Documentation~/Examples/RegistrationForm.md | 73 ++ Documentation~/Examples/ScrollSnapAndDots.md | 50 ++ Documentation~/Examples/StepWizard.md | 67 ++ Documentation~/Examples/ToastNotifications.md | 77 ++ .../Utilities/ProceduralTextureUtility.md | 137 ++++ .../Utilities/UIToolkitExtensions.md | 153 ++++ .../com.unity.uitoolkiyextensions.md | 59 +- Examples~ | 2 +- README.md | 11 +- Runtime/Assets.meta | 8 + Runtime/Assets/Icons.meta | 8 + Runtime/Assets/Icons/Camera.svg | 13 + Runtime/Assets/Icons/Camera.svg.meta | 53 ++ Runtime/Assets/Icons/ChevronDown.svg | 8 + Runtime/Assets/Icons/ChevronDown.svg.meta | 53 ++ Runtime/Assets/Icons/ChevronRight.svg | 8 + Runtime/Assets/Icons/ChevronRight.svg.meta | 53 ++ Runtime/Assets/Icons/ListItem.svg | 9 + Runtime/Assets/Icons/ListItem.svg.meta | 53 ++ Runtime/Assets/Icons/LoadingSpinner.svg | 10 + Runtime/Assets/Icons/LoadingSpinner.svg.meta | 53 ++ Runtime/Controls/CircularImageButton.meta | 8 + .../CircularImageButton.cs | 127 ++++ .../CircularImageButton.cs.meta | 2 + Runtime/Controls/CollapsibleSection.meta | 8 + .../CollapsibleSection/CollapsibleSection.cs | 114 +++ .../CollapsibleSection.cs.meta | 2 + Runtime/Controls/ColorToggleButton.meta | 8 + .../ColorToggleButton/ColorToggleButton.cs | 147 ++++ .../ColorToggleButton.cs.meta | 2 + Runtime/Controls/ColorToggleGroup.meta | 8 + .../ColorToggleGroup/ColorToggleGroup.cs | 258 +++++++ .../ColorToggleGroup/ColorToggleGroup.cs.meta | 2 + Runtime/Controls/ComingSoonMessage.meta | 8 + .../ComingSoonMessage/ComingSoonMessage.cs | 52 ++ .../ComingSoonMessage.cs.meta | 2 + Runtime/Controls/DropDownControl.meta | 15 + .../DropDownControl/DropDownControl.cs | 490 +++++++++++++ .../DropDownControl/DropDownControl.cs.meta | 2 + Runtime/Controls/GrayscaleImage.meta | 8 + .../Controls/GrayscaleImage/GrayscaleImage.cs | 260 +++++++ .../GrayscaleImage/GrayscaleImage.cs.meta | 2 + Runtime/Controls/IconLabelButton.meta | 8 + .../IconLabelButton/IconLabelButton.cs | 84 +++ .../IconLabelButton/IconLabelButton.cs.meta | 2 + Runtime/Controls/ImageCropOverlayControl.meta | 15 + .../ImageCropOverlayControl.cs | 659 ++++++++++++++++++ .../ImageCropOverlayControl.cs.meta | 2 + Runtime/Controls/LoadingIcon.meta | 8 + Runtime/Controls/LoadingIcon/LoadingIcon.cs | 108 +++ .../Controls/LoadingIcon/LoadingIcon.cs.meta | 2 + Runtime/Controls/PageDotIndicator.meta | 8 + .../PageDotIndicator/PageDotIndicator.cs | 139 ++++ .../PageDotIndicator/PageDotIndicator.cs.meta | 2 + Runtime/Controls/PillButton.meta | 8 + Runtime/Controls/PillButton/PillButton.cs | 179 +++++ .../Controls/PillButton/PillButton.cs.meta | 2 + Runtime/Controls/PillInputField.meta | 8 + .../Controls/PillInputField/PillInputField.cs | 254 +++++++ .../PillInputField/PillInputField.cs.meta | 2 + Runtime/Controls/PillSelector.meta | 8 + Runtime/Controls/PillSelector/PillSelector.cs | 70 ++ .../PillSelector/PillSelector.cs.meta | 2 + Runtime/Controls/RoundedInputField.meta | 8 + .../RoundedInputField/RoundedInputField.cs | 154 ++++ .../RoundedInputField.cs.meta | 2 + Runtime/Controls/ScrollSnap/ScrollSnap.cs | 244 ++----- Runtime/Controls/StepProgressBar.meta | 8 + .../StepProgressBar/StepProgressBar.cs | 132 ++++ .../StepProgressBar/StepProgressBar.cs.meta | 2 + Runtime/Controls/Stepper/QuadrantStepper.cs | 34 +- .../ToastSwipeDismissManipulator.meta | 8 + .../ToastSwipeDismissManipulator.cs | 506 ++++++++++++++ .../ToastSwipeDismissManipulator.cs.meta | 2 + Runtime/Controls/ToggleButton.meta | 8 + Runtime/Controls/ToggleButton/ToggleButton.cs | 100 +++ .../ToggleButton/ToggleButton.cs.meta | 2 + .../UIToolkitExtensionsControlStyles.uss | 575 +++++++++++++++ Runtime/Utility/Ease.cs | 175 +++++ Runtime/Utility/Ease.cs.meta | 2 + Runtime/Utility/ProceduralTextureUtility.cs | 108 +++ .../Utility/ProceduralTextureUtility.cs.meta | 2 + 106 files changed, 8060 insertions(+), 225 deletions(-) create mode 100644 Documentation~/Controls/CircularImageButton.md create mode 100644 Documentation~/Controls/CollapsibleSection.md create mode 100644 Documentation~/Controls/ColorToggleButton.md create mode 100644 Documentation~/Controls/ColorToggleGroup.md create mode 100644 Documentation~/Controls/ComingSoonMessage.md create mode 100644 Documentation~/Controls/DropDownControl.md create mode 100644 Documentation~/Controls/GrayscaleImage.md create mode 100644 Documentation~/Controls/IconLabelButton.md create mode 100644 Documentation~/Controls/ImageCropOverlayControl.md create mode 100644 Documentation~/Controls/LoadingIcon.md create mode 100644 Documentation~/Controls/PageDotIndicator.md create mode 100644 Documentation~/Controls/PillButton.md create mode 100644 Documentation~/Controls/PillInputField.md create mode 100644 Documentation~/Controls/PillSelector.md create mode 100644 Documentation~/Controls/QuadrantStepper.md create mode 100644 Documentation~/Controls/RoundedInputField.md create mode 100644 Documentation~/Controls/StepProgressBar.md create mode 100644 Documentation~/Controls/ToastSwipeDismissManipulator.md create mode 100644 Documentation~/Controls/ToggleButton.md create mode 100644 Documentation~/Examples/ContentExplorer.md create mode 100644 Documentation~/Examples/DropDownPhoneEntry.md create mode 100644 Documentation~/Examples/ImageCropOverlayDemo.md create mode 100644 Documentation~/Examples/ProfileEditor.md create mode 100644 Documentation~/Examples/RegistrationForm.md create mode 100644 Documentation~/Examples/ScrollSnapAndDots.md create mode 100644 Documentation~/Examples/StepWizard.md create mode 100644 Documentation~/Examples/ToastNotifications.md create mode 100644 Documentation~/Utilities/ProceduralTextureUtility.md create mode 100644 Documentation~/Utilities/UIToolkitExtensions.md create mode 100644 Runtime/Assets.meta create mode 100644 Runtime/Assets/Icons.meta create mode 100644 Runtime/Assets/Icons/Camera.svg create mode 100644 Runtime/Assets/Icons/Camera.svg.meta create mode 100644 Runtime/Assets/Icons/ChevronDown.svg create mode 100644 Runtime/Assets/Icons/ChevronDown.svg.meta create mode 100644 Runtime/Assets/Icons/ChevronRight.svg create mode 100644 Runtime/Assets/Icons/ChevronRight.svg.meta create mode 100644 Runtime/Assets/Icons/ListItem.svg create mode 100644 Runtime/Assets/Icons/ListItem.svg.meta create mode 100644 Runtime/Assets/Icons/LoadingSpinner.svg create mode 100644 Runtime/Assets/Icons/LoadingSpinner.svg.meta create mode 100644 Runtime/Controls/CircularImageButton.meta create mode 100644 Runtime/Controls/CircularImageButton/CircularImageButton.cs create mode 100644 Runtime/Controls/CircularImageButton/CircularImageButton.cs.meta create mode 100644 Runtime/Controls/CollapsibleSection.meta create mode 100644 Runtime/Controls/CollapsibleSection/CollapsibleSection.cs create mode 100644 Runtime/Controls/CollapsibleSection/CollapsibleSection.cs.meta create mode 100644 Runtime/Controls/ColorToggleButton.meta create mode 100644 Runtime/Controls/ColorToggleButton/ColorToggleButton.cs create mode 100644 Runtime/Controls/ColorToggleButton/ColorToggleButton.cs.meta create mode 100644 Runtime/Controls/ColorToggleGroup.meta create mode 100644 Runtime/Controls/ColorToggleGroup/ColorToggleGroup.cs create mode 100644 Runtime/Controls/ColorToggleGroup/ColorToggleGroup.cs.meta create mode 100644 Runtime/Controls/ComingSoonMessage.meta create mode 100644 Runtime/Controls/ComingSoonMessage/ComingSoonMessage.cs create mode 100644 Runtime/Controls/ComingSoonMessage/ComingSoonMessage.cs.meta create mode 100644 Runtime/Controls/DropDownControl.meta create mode 100644 Runtime/Controls/DropDownControl/DropDownControl.cs create mode 100644 Runtime/Controls/DropDownControl/DropDownControl.cs.meta create mode 100644 Runtime/Controls/GrayscaleImage.meta create mode 100644 Runtime/Controls/GrayscaleImage/GrayscaleImage.cs create mode 100644 Runtime/Controls/GrayscaleImage/GrayscaleImage.cs.meta create mode 100644 Runtime/Controls/IconLabelButton.meta create mode 100644 Runtime/Controls/IconLabelButton/IconLabelButton.cs create mode 100644 Runtime/Controls/IconLabelButton/IconLabelButton.cs.meta create mode 100644 Runtime/Controls/ImageCropOverlayControl.meta create mode 100644 Runtime/Controls/ImageCropOverlayControl/ImageCropOverlayControl.cs create mode 100644 Runtime/Controls/ImageCropOverlayControl/ImageCropOverlayControl.cs.meta create mode 100644 Runtime/Controls/LoadingIcon.meta create mode 100644 Runtime/Controls/LoadingIcon/LoadingIcon.cs create mode 100644 Runtime/Controls/LoadingIcon/LoadingIcon.cs.meta create mode 100644 Runtime/Controls/PageDotIndicator.meta create mode 100644 Runtime/Controls/PageDotIndicator/PageDotIndicator.cs create mode 100644 Runtime/Controls/PageDotIndicator/PageDotIndicator.cs.meta create mode 100644 Runtime/Controls/PillButton.meta create mode 100644 Runtime/Controls/PillButton/PillButton.cs create mode 100644 Runtime/Controls/PillButton/PillButton.cs.meta create mode 100644 Runtime/Controls/PillInputField.meta create mode 100644 Runtime/Controls/PillInputField/PillInputField.cs create mode 100644 Runtime/Controls/PillInputField/PillInputField.cs.meta create mode 100644 Runtime/Controls/PillSelector.meta create mode 100644 Runtime/Controls/PillSelector/PillSelector.cs create mode 100644 Runtime/Controls/PillSelector/PillSelector.cs.meta create mode 100644 Runtime/Controls/RoundedInputField.meta create mode 100644 Runtime/Controls/RoundedInputField/RoundedInputField.cs create mode 100644 Runtime/Controls/RoundedInputField/RoundedInputField.cs.meta create mode 100644 Runtime/Controls/StepProgressBar.meta create mode 100644 Runtime/Controls/StepProgressBar/StepProgressBar.cs create mode 100644 Runtime/Controls/StepProgressBar/StepProgressBar.cs.meta create mode 100644 Runtime/Controls/ToastSwipeDismissManipulator.meta create mode 100644 Runtime/Controls/ToastSwipeDismissManipulator/ToastSwipeDismissManipulator.cs create mode 100644 Runtime/Controls/ToastSwipeDismissManipulator/ToastSwipeDismissManipulator.cs.meta create mode 100644 Runtime/Controls/ToggleButton.meta create mode 100644 Runtime/Controls/ToggleButton/ToggleButton.cs create mode 100644 Runtime/Controls/ToggleButton/ToggleButton.cs.meta create mode 100644 Runtime/Utility/Ease.cs create mode 100644 Runtime/Utility/Ease.cs.meta create mode 100644 Runtime/Utility/ProceduralTextureUtility.cs create mode 100644 Runtime/Utility/ProceduralTextureUtility.cs.meta diff --git a/CHANGELOG.md b/CHANGELOG.md index 14b74ab..5ec6c2e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,78 @@ 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. +- **20 controls out of the box** — covering navigation, 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. +- **6 ready-to-run sample scenes** — included as a UPM sample, demonstrating the controls in realistic combinations. + +### Added + +The initial release contains 20 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. + +##### 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. + +##### 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. + +#### 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. + +### 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~/Controls/CircularImageButton.md b/Documentation~/Controls/CircularImageButton.md new file mode 100644 index 0000000..2b76b42 --- /dev/null +++ b/Documentation~/Controls/CircularImageButton.md @@ -0,0 +1,100 @@ +# CircularImageButton + +## Summary + +`CircularImageButton` is a circular button that displays an image or sprite at full bleed with a 50% border-radius. When no image is set it shows a centered overlay (typically an upload icon and label) to prompt the user to select a photo. + +Typical use cases: + +- Avatar display and photo selection in profile screens +- Circular media thumbnails in lists or grids +- Any tap target that must hold a user-supplied image + +## Properties + +This control has no data properties. Configure it through method calls and respond to the `Clicked` event. + +## USS Classes + +| Class | Description | +| --- | --- | +| `circularImageButton` | Root element. Applies circular clip via `border-radius: 50%`. | +| `circularImageButton__image` | The image layer. Absolutely positioned, fills the button, `border-radius: 50%`. | +| `circularImageButton__noImageOverlay` | Overlay shown when no image is set. Absolutely positioned and centered. | +| `circularImageButton__icon` | Icon inside the no-image overlay (typically an upload/camera glyph). | +| `circularImageButton__uploadLabel` | Text label inside the no-image overlay. | +| `circularImageButton--hasImage` | Modifier applied to the root when an image is present. Hides the no-image overlay. | + +## Events + +| Name | Description | Arguments | +| --- | --- | --- | +| `Clicked` | Fired when the button receives a pointer-up event inside its bounds. | none | + +## Public Methods + +| Signature | Description | +| --- | --- | +| `SetImage(Texture2D texture, bool isDefault = false)` | Sets the displayed image from a `Texture2D`. Pass `isDefault = true` to treat the image as a placeholder (does not apply the `--hasImage` modifier). | +| `SetImage(Sprite sprite, bool isDefault = false)` | Sets the displayed image from a `Sprite`. Same `isDefault` semantics. | +| `SetUploadLabel(string text)` | Updates the text shown inside the no-image overlay. | +| `ClearImage()` | Removes the current image and restores the no-image overlay. | +| `SetImageTint(Color color)` | Applies a tint color to the image element. | + +## Using the Control + +### Basic Setup + +```csharp +using UnityEngine; +using UnityEngine.UIElements; +using UnityUIToolkit.Extensions; + +public class AvatarController : MonoBehaviour +{ + [SerializeField] private UIDocument _document; + [SerializeField] private Texture2D _defaultAvatar; + + private CircularImageButton _avatarButton; + + private void OnEnable() + { + var root = _document.rootVisualElement; + _avatarButton = new CircularImageButton(); + + // Show a default placeholder image without hiding the overlay + _avatarButton.SetImage(_defaultAvatar, isDefault: true); + _avatarButton.SetUploadLabel("Tap to change"); + + _avatarButton.Clicked += OnAvatarTapped; + root.Add(_avatarButton); + } + + private void OnAvatarTapped() + { + // Open native photo picker, then call SetImage with the result + Debug.Log("Avatar tapped — open photo picker"); + } + + private void ApplyPickedPhoto(Texture2D picked) + { + // isDefault: false — hides the overlay and marks the button as having an image + _avatarButton.SetImage(picked, isDefault: false); + } + + private void ResetAvatar() + { + _avatarButton.ClearImage(); + } +} +``` + +### Tint and Dynamic Color + +```csharp +// Grey-out the avatar when the profile is locked +_avatarButton.SetImageTint(new Color(1f, 1f, 1f, 0.4f)); + +// Restore full color +_avatarButton.SetImageTint(Color.white); +``` diff --git a/Documentation~/Controls/CollapsibleSection.md b/Documentation~/Controls/CollapsibleSection.md new file mode 100644 index 0000000..79ee6be --- /dev/null +++ b/Documentation~/Controls/CollapsibleSection.md @@ -0,0 +1,97 @@ +# CollapsibleSection + +## Summary + +`CollapsibleSection` is a container with a tappable header that expands or collapses its body content. The body transition is driven by a `max-height` animation (0 → 2000 px, 250 ms ease-out) triggered by the `collapsibleSection--expanded` modifier class, so no code-side animation is required for the open/close motion. + +Typical use cases: + +- FAQ accordion panels +- Collapsible settings groups +- Nested content trees inside scroll containers +- Any section where body content should be hidden by default + +## Properties + +| Name | Description | Options | +| --- | --- | --- | +| `IsExpanded` | Gets or sets the current expanded state. Setting this value animates the body and fires `OnExpandedChanged`. | `bool` | +| `TitleText` | Gets or sets the header label text. | `string` | + +## USS Classes + +| Class | Description | +| --- | --- | +| `collapsibleSection` | Root element. | +| `collapsibleSection__header` | Tappable header row. Contains the title and chevron. | +| `collapsibleSection__title` | Label element inside the header. | +| `collapsibleSection__chevron` | Chevron/arrow icon that rotates to indicate state. | +| `collapsibleSection__body` | Outer body wrapper. Has `max-height` transition for the open/close animation. | +| `collapsibleSection__bodyContent` | Inner content container. Receives children added via `AddBodyContent`. | +| `collapsibleSection--expanded` | Modifier applied to the root when expanded. Drives the `max-height` transition and chevron rotation. | + +## Events + +| Name | Description | Arguments | +| --- | --- | --- | +| `OnExpandedChanged` | Fired after the expanded state changes. | `bool isExpanded` | + +## Public Methods + +| Signature | Description | +| --- | --- | +| `AddBodyContent(VisualElement element)` | Appends a child element to the inner body content container. | +| `SetBodyText(string text) : Label` | Convenience method that creates and appends a `Label` with the given text. Returns the created label. | +| `Toggle()` | Toggles the expanded state. Equivalent to `IsExpanded = !IsExpanded`. | + +## Using the Control + +### Basic Setup + +```csharp +using UnityEngine; +using UnityEngine.UIElements; +using UnityUIToolkit.Extensions; + +public class FaqController : MonoBehaviour +{ + [SerializeField] private UIDocument _document; + + private void OnEnable() + { + var root = _document.rootVisualElement; + + var section = new CollapsibleSection(); + section.TitleText = "What is this app?"; + + // Add plain text body + section.SetBodyText( + "This app helps you track your daily habits and review progress over time."); + + // Add a richer body element + var linkLabel = new Label("Learn more at example.com"); + linkLabel.style.color = new StyleColor(new Color(0.35f, 0.65f, 1f)); + section.AddBodyContent(linkLabel); + + section.OnExpandedChanged += isExpanded => + { + Debug.Log($"Section is now {(isExpanded ? "open" : "closed")}"); + }; + + root.Add(section); + } +} +``` + +### Programmatic Expand / Collapse + +```csharp +// Open all sections on first visit +foreach (var section in _faqSections) +{ + section.IsExpanded = true; +} + +// Toggle a section from an external button +_toggleButton.clicked += () => _detailsSection.Toggle(); +``` diff --git a/Documentation~/Controls/ColorToggleButton.md b/Documentation~/Controls/ColorToggleButton.md new file mode 100644 index 0000000..0170144 --- /dev/null +++ b/Documentation~/Controls/ColorToggleButton.md @@ -0,0 +1,105 @@ +# ColorToggleButton + +## Summary + +`ColorToggleButton` extends `ToggleButton` with per-instance tint colors, a ripple press animation, and a selection overlay. The background color is driven entirely by the tint values, making it straightforward to build color-coded toggle grids without USS variants. + +Typical use cases: + +- Color picker palette items +- Labeled color-coded category toggles +- Any toggle grid where each item has a distinct brand or theme color + +## Properties + +| Name | Description | Options | +| --- | --- | --- | +| `IsSelected` | Inherited from `ToggleButton`. Gets or sets the selected state. | `bool` | +| `TintColor` | The background tint used when the button is in its default (unselected) state. | `Color` (read-only; use `SetTintColor` to change) | +| `SelectedTintColor` | The background tint used when the button is selected. | `Color` (read-only; use `SetSelectedTintColor` to change) | + +## USS Classes + +| Class | Description | +| --- | --- | +| `toggleButton` | Root element (inherited from `ToggleButton`). | +| `toggleButton__image` | Image layer, 100% size, scale-to-fit (inherited). | +| `toggleButton--selected` | Modifier applied when selected (inherited). | +| `toggleButton__icon` | Optional icon element overlaid on the colored background. | +| `toggleButton__ripple` | Primary ripple circle that expands on press. | +| `toggleButton__rippleSecondary` | Secondary ripple circle for the layered ripple effect. | +| `toggleButton__selectedOverlay` | Overlay element shown when selected. | +| `toggleButton__selectedOverlay--visible` | Modifier that makes the selected overlay visible. | + +## Events + +| Name | Description | Arguments | +| --- | --- | --- | +| `OnClicked` | Inherited from `ToggleButton`. Fired on every pointer-down regardless of current state. | none | + +## Constructors + +| Signature | Description | +| --- | --- | +| `ColorToggleButton(Color tintColor)` | Creates a button with the same tint color for both selected and unselected states. | +| `ColorToggleButton(Color tintColor, Color selectedTintColor)` | Creates a button with distinct tints for each state. | + +## Public Methods + +| Signature | Description | +| --- | --- | +| `SetTintColor(Color color)` | Updates the unselected background tint at runtime. | +| `SetSelectedTintColor(Color color)` | Updates the selected background tint at runtime. | +| `SetImage(Texture2D texture)` | Inherited. Sets the icon/image on the button. | +| `ForceSelect()` | Inherited. Sets `IsSelected = true` without firing `OnClicked`. | +| `ForceDeselect()` | Inherited. Sets `IsSelected = false` without firing `OnClicked`. | + +## Using the Control + +### Color Palette Grid + +```csharp +using UnityEngine; +using UnityEngine.UIElements; +using UnityUIToolkit.Extensions; + +public class ColorPaletteController : MonoBehaviour +{ + [SerializeField] private UIDocument _document; + + private static readonly Color[] PaletteColors = new[] + { + new Color(0.91f, 0.27f, 0.38f), // red + new Color(0.25f, 0.56f, 0.96f), // blue + new Color(0.18f, 0.80f, 0.44f), // green + new Color(0.98f, 0.75f, 0.18f), // yellow + }; + + private ColorToggleButton _activeButton; + + private void OnEnable() + { + var root = _document.rootVisualElement; + var row = root.Q("paletteRow"); + + foreach (var color in PaletteColors) + { + // Slightly lighter shade for selected state + var selectedColor = Color.Lerp(color, Color.white, 0.25f); + var btn = new ColorToggleButton(color, selectedColor); + + btn.OnClicked += () => + { + // Deselect previous + if (_activeButton != null && _activeButton != btn) + _activeButton.ForceDeselect(); + + _activeButton = btn; + Debug.Log($"Selected color: {color}"); + }; + + row.Add(btn); + } + } +} +``` diff --git a/Documentation~/Controls/ColorToggleGroup.md b/Documentation~/Controls/ColorToggleGroup.md new file mode 100644 index 0000000..e2d1de4 --- /dev/null +++ b/Documentation~/Controls/ColorToggleGroup.md @@ -0,0 +1,92 @@ +# ColorToggleGroup + +## Summary + +`ColorToggleGroup` manages a set of `ColorToggleButton` items as a single-selection group. It supports both tap-to-select and drag-to-select gestures, ensuring that only one color is selected at a time. Selection state is coordinated internally; consumers only need to respond to `OnColorSelected`. + +Typical use cases: + +- Full-screen or inline color pickers +- Theme or accent color selectors +- Tag or category color selectors + +## Properties + +| Name | Description | Options | +| --- | --- | --- | +| `Colors` | Gets or sets the array of colors represented by the group. Changing this value rebuilds all child buttons. | `Color[]` | +| `SelectedColor` | Gets the currently selected color. `null` if nothing is selected. | `Color?` (nullable) | +| `Alignment` | Controls the flex direction of the buttons container. | `FlexDirection` | + +## USS Classes + +| Class | Description | +| --- | --- | +| `colorToggleGroup` | Root element. | +| `colorToggleGroup__container` | Flex container that holds all `ColorToggleButton` children. | + +## Events + +| Name | Description | Arguments | +| --- | --- | --- | +| `OnColorSelected` | Fired when the user selects a color by tap or drag. Not fired when selection changes programmatically via `SelectColor(color, propagateEvent: false)`. | `Color selectedColor` | + +## Public Methods + +| Signature | Description | +| --- | --- | +| `DeselectAll()` | Clears the current selection without firing `OnColorSelected`. | +| `SelectColor(Color color, bool propagateEvent = true)` | Programmatically selects the button whose color matches. Pass `propagateEvent: false` to suppress `OnColorSelected`. | + +## Using the Control + +### Inline Color Picker + +```csharp +using UnityEngine; +using UnityEngine.UIElements; +using UnityUIToolkit.Extensions; + +public class ThemeSelectorController : MonoBehaviour +{ + [SerializeField] private UIDocument _document; + + private ColorToggleGroup _colorGroup; + private Color _currentThemeColor = Color.white; + + private void OnEnable() + { + var root = _document.rootVisualElement; + + _colorGroup = new ColorToggleGroup(); + _colorGroup.Colors = new[] + { + new Color(0.91f, 0.27f, 0.38f), + new Color(0.25f, 0.56f, 0.96f), + new Color(0.18f, 0.80f, 0.44f), + new Color(0.98f, 0.75f, 0.18f), + new Color(0.60f, 0.20f, 0.80f), + }; + _colorGroup.Alignment = FlexDirection.Row; + + _colorGroup.OnColorSelected += OnThemeColorPicked; + + root.Q("colorPickerContainer").Add(_colorGroup); + + // Pre-select the saved theme color without firing the event + _colorGroup.SelectColor(_currentThemeColor, propagateEvent: false); + } + + private void OnThemeColorPicked(Color color) + { + _currentThemeColor = color; + Debug.Log($"Theme color changed to {color}"); + // Apply color to your UI here + } + + private void ResetSelection() + { + _colorGroup.DeselectAll(); + } +} +``` diff --git a/Documentation~/Controls/ComingSoonMessage.md b/Documentation~/Controls/ComingSoonMessage.md new file mode 100644 index 0000000..1e267d3 --- /dev/null +++ b/Documentation~/Controls/ComingSoonMessage.md @@ -0,0 +1,91 @@ +# ComingSoonMessage + +## Summary + +`ComingSoonMessage` is a simple full-area placeholder element that fills its container and communicates that a feature or screen is not yet available. It renders a background layer, a prominent title, and a descriptive message label. + +Typical use cases: + +- Placeholder screens during development or staged rollouts +- Stub pages inside a `ScrollSnap` onboarding flow +- In-progress feature sections within a settings or navigation layout + +## Properties + +| Name | Description | Options | +| --- | --- | --- | +| `Title` | Gets or sets the large heading text. | `string` | + +## USS Classes + +| Class | Description | +| --- | --- | +| `comingSoonMessage` | Root element. Fills its parent and centers its contents. | +| `comingSoonMessage__background` | Decorative background layer (may carry color or texture). | +| `comingSoonMessage__title` | Large heading label. | +| `comingSoonMessage__label` | Secondary descriptive message label below the title. | + +## Events + +This control does not emit events. + +## Public Methods + +| Signature | Description | +| --- | --- | +| `SetMessage(string text)` | Sets the secondary descriptive message shown below the title. | + +## Using the Control + +### Stub Page in a ScrollSnap + +```csharp +using UnityEngine; +using UnityEngine.UIElements; +using UnityUIToolkit.Extensions; + +public class OnboardingController : MonoBehaviour +{ + [SerializeField] private UIDocument _document; + + private void OnEnable() + { + var root = _document.rootVisualElement; + var scrollSnap = root.Q("onboardingSnap"); + + // Page 1 — real content + var welcomePage = new VisualElement(); + welcomePage.AddToClassList("sample-page"); + welcomePage.AddToClassList("sample-page--blue"); + scrollSnap.Add(welcomePage); + + // Page 2 — placeholder + var placeholder = new ComingSoonMessage(); + placeholder.Title = "Social Features"; + placeholder.SetMessage("Connect with friends and share your progress. Launching soon."); + scrollSnap.Add(placeholder); + + // Page 3 — another placeholder + var placeholder2 = new ComingSoonMessage(); + placeholder2.Title = "Challenges"; + placeholder2.SetMessage("Weekly challenges and leaderboards are on their way."); + scrollSnap.Add(placeholder2); + } +} +``` + +### Dynamic Title Update + +```csharp +// Swap placeholder text based on user role +if (user.IsPremium) +{ + _comingSoon.Title = "Advanced Analytics"; + _comingSoon.SetMessage("Your detailed stats dashboard is being prepared."); +} +else +{ + _comingSoon.Title = "Premium Feature"; + _comingSoon.SetMessage("Upgrade to unlock this section."); +} +``` diff --git a/Documentation~/Controls/DropDownControl.md b/Documentation~/Controls/DropDownControl.md new file mode 100644 index 0000000..96d3cb3 --- /dev/null +++ b/Documentation~/Controls/DropDownControl.md @@ -0,0 +1,77 @@ +# DropDownControl + +## Summary + +`DropDownControl` is a wheel-style dropdown picker that opens from an inline trigger and lets the user confirm a single value from a vertically scrollable modal list. + +Typical use cases: + +- Dial-code or country-code selection before a phone field +- Compact selectors that need a touch-friendly modal confirmation step +- Any workflow where a finite string list should feel like a native mobile picker rather than a standard menu + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| `Items` | `IReadOnlyList` | Ordered values shown in the picker. Updating the list refreshes the trigger label and open list content. | +| `Value` | `string` | The currently selected value, or an empty string when `Items` is empty. | + +## USS Classes + +| Class | Description | +| --- | --- | +| `dropDownControl` | Root element for the control. | +| `dropDownControl__trigger` | Closed-state trigger element. | +| `dropDownControl__triggerLabel` | Label showing the current selection. | +| `dropDownControl__triggerIcon` | Chevron icon inside the trigger. | +| `dropDownControl__backdrop` | Full-screen backdrop injected while the picker is open. | +| `dropDownControl__panel` | Floating modal panel that contains the picker list. | +| `dropDownControl__viewport` | Clipped viewport used to display the visible rows. | +| `dropDownControl__list` | Scrolling row container translated by the control logic. | +| `dropDownControl__row` | A single rendered picker row. | +| `dropDownControl__selectionLane` | Highlight lane showing the centred selection. | +| `dropDownControl__fadeTop` | Top fade overlay used to de-emphasize off-centre rows. | +| `dropDownControl__fadeBottom` | Bottom fade overlay used to de-emphasize off-centre rows. | +| `dropDownControl--open` | Root modifier applied while the modal picker is visible. | + +## Events + +| Name | Description | Arguments | +| --- | --- | --- | +| `ValueChanged` | Fired after the user confirms a new selection. | `string selectedValue` | +| `OpenStateChanged` | Fired when the modal picker is opened or closed. | `bool isOpen` | + +## Public Methods + +| Signature | Description | +| --- | --- | +| `SetDefault(string value)` | Sets the selected item when the supplied value exists in `Items`. | + +## Using the Control + +```csharp +using UnityEngine; +using UnityEngine.UIElements; +using UnityUIToolkit.Extensions; + +public class DialCodePickerExample : MonoBehaviour +{ + [SerializeField] private UIDocument document; + + private void OnEnable() + { + var root = document.rootVisualElement; + + var picker = new DropDownControl + { + Items = new[] { "+1", "+33", "+44", "+49" }, + }; + + picker.SetDefault("+44"); + picker.ValueChanged += selected => Debug.Log($"Selected {selected}"); + + root.Add(picker); + } +} +``` \ No newline at end of file diff --git a/Documentation~/Controls/GrayscaleImage.md b/Documentation~/Controls/GrayscaleImage.md new file mode 100644 index 0000000..4df031e --- /dev/null +++ b/Documentation~/Controls/GrayscaleImage.md @@ -0,0 +1,92 @@ +# GrayscaleImage + +## Summary + +`GrayscaleImage` is an `ImmediateModeElement` that renders a sprite or texture using `Graphics.DrawTexture` and supports toggling a greyscale effect via a material property. The greyscale effect requires a custom `Material` that exposes `_MainTex` and `_GreyscaleEnabled` shader properties; without a compatible material the image renders in full color only. + +Typical use cases: + +- Profile or media images that switch to greyscale when disabled or locked +- Toggling greyscale on achievement/badge imagery for locked states +- Any image that needs a shader-driven color/greyscale toggle without a separate texture asset + +## Properties + +| Name | Description | Options | +| --- | --- | --- | +| `SpriteProperty` | The `Sprite` to render. Mutually exclusive with `TextureProperty`. | `Sprite` | +| `TextureProperty` | The `Texture` to render. Mutually exclusive with `SpriteProperty`. | `Texture` | +| `scaleMode` | How the image is scaled within its bounds. | `ScaleMode` | +| `Material` | The material used for rendering. Must expose `_MainTex` and `_GreyscaleEnabled` for the greyscale feature. | `Material` | +| `GreyscaleEnabled` | Gets or sets whether the greyscale shader effect is active. Only functional when a compatible material is assigned. | `bool` | +| `MainTextureProperty` | The shader property name for the main texture. | `string` (default `"_MainTex"`) | +| `GreyscaleToggleProperty` | The shader property name for the greyscale toggle. | `string` (default `"_GreyscaleEnabled"`) | + +### Constants + +| Name | Value | Description | +| --- | --- | --- | +| `DefaultMainTextureProperty` | `"_MainTex"` | Default shader property name for the main texture. | +| `DefaultGreyscaleToggleProperty` | `"_GreyscaleEnabled"` | Default shader property name for the greyscale toggle. | + +## USS Classes + +| Class | Description | +| --- | --- | +| `grayscaleImage` | Root element. Dimensions should be set via USS or inline style; the element uses its resolved layout rect for `Graphics.DrawTexture`. | + +## Events + +This control does not emit events. Repaint is triggered automatically by the immediate-mode element lifecycle. + +## Public Methods + +This control exposes its API entirely through properties. No additional public methods are defined beyond those inherited from `ImmediateModeElement`. + +## Using the Control + +### Toggle Greyscale on a Locked Achievement + +```csharp +using UnityEngine; +using UnityEngine.UIElements; +using UnityUIToolkit.Extensions; + +public class AchievementBadgeController : MonoBehaviour +{ + [SerializeField] private UIDocument _document; + [SerializeField] private Sprite _badgeSprite; + [SerializeField] private Material _greyscaleMaterial; // shader with _MainTex + _GreyscaleEnabled + + private GrayscaleImage _badgeImage; + + private void OnEnable() + { + var root = _document.rootVisualElement; + + _badgeImage = new GrayscaleImage(); + _badgeImage.style.width = 80; + _badgeImage.style.height = 80; + _badgeImage.SpriteProperty = _badgeSprite; + _badgeImage.Material = _greyscaleMaterial; + _badgeImage.scaleMode = ScaleMode.ScaleToFit; + + root.Q("badgeContainer").Add(_badgeImage); + } + + public void SetLocked(bool locked) + { + _badgeImage.GreyscaleEnabled = locked; + } +} +``` + +### Texture-Based Rendering with Custom Property Names + +```csharp +// If your shader uses different property names: +_badgeImage.MainTextureProperty = "_BaseMap"; +_badgeImage.GreyscaleToggleProperty = "_UseGreyscale"; +_badgeImage.TextureProperty = _photoTexture; +_badgeImage.GreyscaleEnabled = true; +``` diff --git a/Documentation~/Controls/IconLabelButton.md b/Documentation~/Controls/IconLabelButton.md new file mode 100644 index 0000000..221d312 --- /dev/null +++ b/Documentation~/Controls/IconLabelButton.md @@ -0,0 +1,99 @@ +# IconLabelButton + +## Summary + +`IconLabelButton` is a full-width row button that pairs a 24 × 24 px icon on the left with a text label. It provides hover and pressed state modifier classes for visual feedback without custom USS. + +Typical use cases: + +- Menu and navigation row items +- Action list rows (share, delete, report, etc.) +- Settings list entries with a leading icon + +## Properties + +| Name | Description | Options | +| --- | --- | --- | +| `Text` | Gets or sets the button label text. | `string` | + +## USS Classes + +| Class | Description | +| --- | --- | +| `iconLabelButton` | Root element. Full-width flex row. | +| `iconLabelButton__button` | Inner button element that wraps icon and label. | +| `iconLabelButton__icon` | Icon element. Fixed at 24 × 24 px. | +| `iconLabelButton__label` | Text label element next to the icon. | +| `iconLabelButton--hover` | Modifier applied on pointer-enter. | +| `iconLabelButton--pressed` | Modifier applied while pointer is held down. | + +## Events + +| Name | Description | Arguments | +| --- | --- | --- | +| `Clicked` | Fired when the button is tapped or clicked. | none | + +## Public Methods + +No public methods beyond property access. Use the `Text` property and USS to configure the control. + +## Using the Control + +### Navigation Menu + +```csharp +using UnityEngine; +using UnityEngine.UIElements; +using UnityUIToolkit.Extensions; + +public class SideMenuController : MonoBehaviour +{ + [SerializeField] private UIDocument _document; + [SerializeField] private Texture2D _homeIcon; + [SerializeField] private Texture2D _profileIcon; + [SerializeField] private Texture2D _settingsIcon; + + private void OnEnable() + { + var root = _document.rootVisualElement; + var menu = root.Q("sideMenu"); + + menu.Add(CreateMenuRow("Home", _homeIcon, () => NavigateTo("home"))); + menu.Add(CreateMenuRow("Profile", _profileIcon, () => NavigateTo("profile"))); + menu.Add(CreateMenuRow("Settings", _settingsIcon, () => NavigateTo("settings"))); + } + + private IconLabelButton CreateMenuRow(string label, Texture2D icon, System.Action onClicked) + { + var btn = new IconLabelButton(); + btn.Text = label; + + // Query by class name (first arg is element name — pass null; second is class name) + var iconEl = btn.Q(null, IconLabelButton.IconClass); + if (iconEl != null) + iconEl.style.backgroundImage = new StyleBackground(icon); + + btn.Clicked += onClicked; + return btn; + } + + private void NavigateTo(string screen) + { + Debug.Log($"Navigating to: {screen}"); + } +} +``` + +### USS Customization + +Override hover and pressed states in your project USS: + +```uss +.iconLabelButton--hover { + background-color: rgba(255, 255, 255, 0.06); +} + +.iconLabelButton--pressed { + background-color: rgba(255, 255, 255, 0.12); +} +``` diff --git a/Documentation~/Controls/ImageCropOverlayControl.md b/Documentation~/Controls/ImageCropOverlayControl.md new file mode 100644 index 0000000..aa1d958 --- /dev/null +++ b/Documentation~/Controls/ImageCropOverlayControl.md @@ -0,0 +1,85 @@ +# ImageCropOverlayControl + +## Summary + +`ImageCropOverlayControl` is a full-screen modal cropper for square image exports. It displays the source image inside a movable and zoomable viewport, then exports the visible area to a new `Texture2D` when the user confirms. + +Typical use cases: + +- Profile picture editing before upload +- Cover-image cropping with a fixed export size +- Any UI Toolkit workflow that needs a self-contained move-and-scale image confirmation step + +## Nested Types + +### `Configuration` + +`Configuration` customizes the overlay title, button labels, export size, zoom limits, screen margins, viewport width ratio, and normalized corner radius used by the crop viewport mask. + +## USS Classes + +| Class | Description | +| --- | --- | +| `imageCropOverlay` | Full-screen modal root. | +| `imageCropOverlay__panel` | Centred container for the header, viewport, and footer. | +| `imageCropOverlay__header` | Header row that hosts the title. | +| `imageCropOverlay__title` | Title label. | +| `imageCropOverlay__viewportHost` | Layout host used to centre the crop viewport. | +| `imageCropOverlay__viewport` | Clipped crop viewport that receives drag, pinch, and wheel input. | +| `imageCropOverlay__image` | Absolute-positioned image surface inside the viewport. | +| `imageCropOverlay__footer` | Footer row for the action buttons. | +| `imageCropOverlay__buttonSlot` | Layout slot for each footer button. | +| `imageCropOverlay__button` | Shared button class applied to the cancel/save buttons. | +| `imageCropOverlay__button--cancel` | Modifier for the cancel button. | +| `imageCropOverlay__button--save` | Modifier for the save button. | + +## Public API + +| Signature | Description | +| --- | --- | +| `Show(VisualElement anchor, Texture2D sourceTexture, Configuration configuration, Action onConfirmed, Action onCancelled = null)` | Creates and displays the overlay on the anchor's panel root. Returns the created overlay or `null` when it cannot be shown. | +| `CreateUniformCornerRadiusPercent(float percent)` | Creates a clamped, uniform normalized corner-radius vector for the crop mask. | +| `ResolveNormalizedCornerRadiusPercent(VisualElement sourceElement, Vector4 fallback)` | Converts the source element's resolved pixel radii into normalized percentages suitable for the crop mask. | + +## Using the Control + +```csharp +using UnityEngine; +using UnityEngine.UIElements; +using UnityUIToolkit.Extensions; + +public class AvatarCropExample : MonoBehaviour +{ + [SerializeField] private UIDocument document; + [SerializeField] private Texture2D avatarSource; + + private CircularImageButton avatarButton; + + private void OnEnable() + { + var root = document.rootVisualElement; + + avatarButton = new CircularImageButton(); + avatarButton.SetImage(avatarSource); + avatarButton.Clicked += OpenCropper; + + root.Add(avatarButton); + } + + private void OpenCropper() + { + var configuration = new ImageCropOverlayControl.Configuration + { + Title = "Move and Scale", + ExportSize = 512, + CornerRadiusPercent = ImageCropOverlayControl.CircleCornerRadiusPercent, + }; + + ImageCropOverlayControl.Show( + avatarButton, + avatarSource, + configuration, + cropped => avatarButton.SetImage(cropped)); + } +} +``` \ No newline at end of file diff --git a/Documentation~/Controls/LoadingIcon.md b/Documentation~/Controls/LoadingIcon.md new file mode 100644 index 0000000..532af68 --- /dev/null +++ b/Documentation~/Controls/LoadingIcon.md @@ -0,0 +1,98 @@ +# LoadingIcon + +## Summary + +`LoadingIcon` is a continuously rotating image element used to indicate background work. Rotation is driven by a scheduled callback that fires every 16 ms. An optional `blockInteraction` flag captures pointer events so the user cannot interact with elements beneath the spinner while it is active. + +Typical use cases: + +- Async operation feedback (API calls, data loading) +- Image upload or file transfer progress indicator +- Form submission spinner overlay + +## Properties + +This control is configured through method calls. The visibility and animation state are reflected in modifier classes. + +## USS Classes + +| Class | Description | +| --- | --- | +| `loadingIcon` | Root element. | +| `loadingIcon__image` | The rotating image. Fixed at 40 × 40 px. | +| `loadingIcon--animating` | Modifier applied while rotation is active. | +| `loadingIcon--visible` | Modifier applied while the icon is shown. Pair with USS to control opacity or display. | + +## Events + +This control does not emit events. + +## Public Methods + +| Signature | Description | +| --- | --- | +| `SetIcon(Texture2D texture)` | Sets the texture used for the spinning image. | +| `PlayLoading(float customSpeed = 1f, bool blockInteraction = false)` | Starts the rotation animation. `customSpeed` is the duration of one full 360° rotation in seconds; lower values spin faster. When `blockInteraction` is `true` the control captures all pointer events. | +| `StopLoading()` | Stops the rotation, releases pointer capture, and removes the `--animating` and `--visible` modifiers. | + +## Using the Control + +### Simple Loading Overlay + +```csharp +using System.Threading.Tasks; +using UnityEngine; +using UnityEngine.UIElements; +using UnityUIToolkit.Extensions; + +public class UploadController : MonoBehaviour +{ + [SerializeField] private UIDocument _document; + [SerializeField] private Texture2D _spinnerTexture; + + private LoadingIcon _spinner; + + private void OnEnable() + { + var root = _document.rootVisualElement; + + _spinner = new LoadingIcon(); + _spinner.SetIcon(_spinnerTexture); + + root.Q("overlayContainer").Add(_spinner); + + root.Q