Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,9 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/).
## Release 1.0.0 - Launch - tbc

Coming Soon.

### Added

- Added `DropDownControl`, a wheel-style modal picker suitable for dial-code and similar compact selection flows.
- Added `ImageCropOverlayControl`, a move-and-scale square crop overlay for `Texture2D` exports.
- Added package sample scenes for a phone-entry dial-code flow and a profile-image crop flow under `Examples~`.
100 changes: 100 additions & 0 deletions Documentation~/Controls/CircularImageButton.md
Original file line number Diff line number Diff line change
@@ -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);
```
97 changes: 97 additions & 0 deletions Documentation~/Controls/CollapsibleSection.md
Original file line number Diff line number Diff line change
@@ -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();
```
105 changes: 105 additions & 0 deletions Documentation~/Controls/ColorToggleButton.md
Original file line number Diff line number Diff line change
@@ -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<VisualElement>("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);
}
}
}
```
Loading
Loading