diff --git a/CHANGELOG.md b/CHANGELOG.md index 14b74ab..61a3a7e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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~`. 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