GridViewBase<TItem> is the abstract base for paginated, filterable grid views. It owns the grid layout, slot lifecycle, filter application, and selection state. Subclass it to bind your item data to slot UXML; or pick one of the two ready-made subclasses below: GridViewPagination<TItem> for paginated grids with a button strip, GridViewList<TItem> for virtualized scrolling backed by a Unity ListView.

Examples

InventoryView

Inventory Ingredients

Heroes

GridView Hero

GridView is a large class with lots of properties so it can be complex, but each property is simple on its own.

API

Constructor

csharp
public GridViewBase( List<TItem> itemList, VisualTreeAsset itemSlotTemplate, GameObject parent, VisualElement parentElement, VisualElement slotContainer, TooltipController tooltipController = null, bool startVisible = true, VisualElement focusElement = null, float fadeDuration = 0.5f, EasingMode easingMode = EasingMode.EaseOutCirc, bool disableOtherViewsOnFadeIn = false, bool debug = false )
ParameterTypeDefaultDescription
itemListList<TItem>Data source, list of items to display
itemSlotTemplateVisualTreeAssetTemplate for each grid item slot
parentGameObjectHost GameObject — the view registers itself on its UIViewReference
parentElementVisualElementRoot VisualElement the view shows/fades
slotContainerVisualElementContainer the grid lines/slots are built into
tooltipControllerTooltipControllernullOptional tooltip controller, closed on page/filter changes
startVisiblebooltrueInitial visibility applied in the constructor
focusElementVisualElementnullElement given keyboard/gamepad focus on fade-in
fadeDurationfloat0.5fFade in/out duration in seconds
easingModeEasingModeEaseOutCircEasing applied to the fade
disableOtherViewsOnFadeInboolfalseBlocks input on all other registered pages while this view is visible
debugboolfalseVerbose lifecycle logging

The last seven parameters are shared verbatim by GridViewPagination and GridViewList (see below), so one mental model covers all three. The final five (focusElement through debug) are the standard UIView constructor tail; tooltipController and startVisible are grid-level extras (startVisible is applied via ApplyStartVisibilityUIView's constructor no longer takes it).

Properties

PropertyTypeDescription
SlotContainerVisualElementThe container holding item slots
SlotPerLineintCurrent number of slots per grid line
ItemSlotsList<VisualElement>List of all created item slot elements
SelectedItemsHashSet<TItem>Currently selected items

Methods

CreateSlots

Creates the grid slot elements. Must be implemented by derived classes.

csharp
public abstract void CreateSlots();

Refresh

Refilters and refreshes all items in the grid.

csharp
public void Refresh()

SetItemList

Updates the data source and refreshes the grid.

csharp
public void SetItemList(List<TItem> itemList)

Selection Methods

csharp
public abstract void Select(int slotIndex); public abstract void Deselect(int slotIndex); public void DeselectAll();

Filter Methods

SetFilters

Sets the filter functions for the grid.

csharp
public void SetFilters(List<Func<List<TItem>, int, List<TItem>>> filters)

SetFilter

Applies a filter value at the specified filter index.

csharp
public void SetFilter(int filterIndex, int filter)

ClearFilter

Clears the filter at the specified index.

csharp
public void ClearFilter(int filterIndex)

GetFilter

Gets the current filter value at the specified index.

csharp
public int GetFilter(int index)

GetFilteredList

Returns the filtered list based on current filters.

csharp
public virtual List<TItem> GetFilteredList(List<int> filters)

Filter Button Methods

SetFilterButtons

Sets up filter toggle buttons.

csharp
public void SetFilterButtons(List<List<Button>> filterButtons)

SetFilterClearButtons

Sets up filter clear buttons.

csharp
public void SetFilterClearButtons(List<List<Button>> filterClearButtons)

SetEnabledFilterButtons

Enables or disables filter buttons at an index.

csharp
public void SetEnabledFilterButtons(int filterIndex, bool enabled)

UpdateFilterButtons

Updates filter button visual states.

csharp
public void UpdateFilterButtons(int filterIndex)

Configuration Methods

SetHideEmpty

When true, empty slots are hidden instead of shown.

csharp
public void SetHideEmpty(bool hideEmpty)

SetSelectedFilterColor

Sets the color class applied to selected filter buttons (a Luna color-class name like "sky").

csharp
public void SetSelectedFilterColor(string color)

SetSelectedFilterClass

Sets the USS class applied to selected filter buttons.

csharp
public void SetSelectedFilterClass(string selectedFilterClass)

SetFilterClass

Sets the USS class applied to all filter buttons.

csharp
public void SetFilterClass(string filterClass)

Input Methods

SetInputPrompt

Sets up input prompts for tab navigation (prev/next filters).

csharp
public void SetInputPrompt(InputPrompt filterItemPrevious, InputPrompt filterItemNext)

PreviousTab / NextTab

Navigate between filter tabs.

csharp
public void PreviousTab() public void PreviousTab(int filterIndex) public void NextTab() public void NextTab(int filterIndex)

RegisterInput / UnregisterInput

Manually register or unregister input handlers.

csharp
public void RegisterInput() public void UnregisterInput()

Dynamic Layout

RegisterDynamicItemPerLineUpdate

Registers a callback to update items per line when container size changes.

csharp
public void RegisterDynamicItemPerLineUpdate() public void UnregisterDynamicItemPerLineUpdate()

Abstract Methods to Implement

When extending GridViewBase, implement these methods:

csharp
// Create slot visual elements public abstract void CreateSlots(); // Refresh items in slots protected abstract void RefreshItems(); // Filter list based on current filters protected abstract void FilterList(); // Handle filter changes protected abstract void OnFilterChange(int filterIndex); // Handle dynamic layout changes protected abstract void DynamicItemPerLineUpdate(GeometryChangedEvent evt); // Called when creating each slot protected abstract void OnItemCreate(VisualElement slot); // Unbind item from slot protected abstract void UnbindItem(VisualElement slot); // Bind item to slot protected abstract void BindItem(VisualElement slot, TItem item, int slotIndex, bool selected); // Selection handling public abstract void Select(int slotIndex); public abstract void Deselect(int slotIndex);

Example Implementation

csharp
using System.Collections.Generic; using UnityEngine; using UnityEngine.UIElements; using CupkekGames.Luna; public class InventoryGridView : GridViewBase<InventoryItem> { private List<InventoryItem> _filteredList; public InventoryGridView( List<InventoryItem> items, VisualTreeAsset template, GameObject parent, VisualElement parentElement, VisualElement container ) : base(items, template, parent, parentElement, container) { _slotPerLine = 4; _itemWidth = 80; } public override void CreateSlots() { _slotContainer.Clear(); ItemSlots.Clear(); int lineCount = Mathf.CeilToInt((float)_itemList.Count / _slotPerLine); for (int i = 0; i < lineCount; i++) { VisualElement line = MakeLine(); _slotContainer.Add(line); } RefreshItems(); } protected override void FilterList() { _filteredList = GetFilteredList(_currentFilters); } protected override void RefreshItems() { for (int i = 0; i < ItemSlots.Count; i++) { VisualElement slot = ItemSlots[i]; if (i < _filteredList.Count) { InventoryItem item = _filteredList[i]; bool selected = _selectedItems.Contains(item); BindItem(slot, item, i, selected); slot.style.visibility = Visibility.Visible; } else { UnbindItem(slot); slot.style.visibility = _hideEmpty ? Visibility.Hidden : Visibility.Visible; } } } protected override void OnItemCreate(VisualElement slot) { // Initial slot setup slot.AddToClassList("inventory-slot"); } protected override void BindItem(VisualElement slot, InventoryItem item, int index, bool selected) { var icon = slot.Q<VisualElement>("Icon"); icon.style.backgroundImage = new StyleBackground(item.Icon); var label = slot.Q<Label>("Name"); label.text = item.Name; if (selected) { slot.AddToClassList("selected"); } else { slot.RemoveFromClassList("selected"); } } protected override void UnbindItem(VisualElement slot) { var icon = slot.Q<VisualElement>("Icon"); icon.style.backgroundImage = null; var label = slot.Q<Label>("Name"); label.text = ""; slot.RemoveFromClassList("selected"); } protected override void OnFilterChange(int filterIndex) { // Handle filter change - e.g., update category header } protected override void DynamicItemPerLineUpdate(GeometryChangedEvent evt) { float width = _slotContainer.resolvedStyle.width; int newSlotPerLine = Mathf.Max(1, Mathf.FloorToInt(width / _itemWidth)); if (newSlotPerLine != _slotPerLine) { _slotPerLine = newSlotPerLine; CreateSlots(); } } public override void Select(int slotIndex) { if (slotIndex < _filteredList.Count) { _selectedItems.Add(_filteredList[slotIndex]); RefreshItems(); } } public override void Deselect(int slotIndex) { if (slotIndex < _filteredList.Count) { _selectedItems.Remove(_filteredList[slotIndex]); RefreshItems(); } } }

GridViewPagination

GridViewPagination<TItem> extends GridViewBase<TItem> and adds pagination support. Items are split across pages and rendered into a fixed number of grid lines.

Constructor

csharp
public GridViewPagination( List<TItem> itemList, VisualTreeAsset itemSlotTemplate, GameObject parent, VisualElement parentElement, VisualElement slotContainer, TooltipController tooltipController = null, bool startVisible = true, VisualElement focusElement = null, float fadeDuration = 0.5f, EasingMode easingMode = EasingMode.EaseOutCirc, bool disableOtherViewsOnFadeIn = false, bool debug = false )

Properties

PropertyTypeDescription
PaginationPaginationController<TItem>The pagination controller managing page state and UI

Public Methods

SetPagination

Sets the number of lines and items per line, updating items per page.

csharp
public void SetPagination(int lineCount, int perLine)
ParameterTypeDescription
lineCountintNumber of grid lines (rows) to display
perLineintNumber of items per line

SetDynamicItemPerLine (GridViewPagination)

Calculates items per line based on container width and item width.

csharp
public void SetDynamicItemPerLine(int lineCount, int itemWidth)
ParameterTypeDescription
lineCountintNumber of grid lines (rows) to display
itemWidthintWidth of each item in pixels

SetPaginationUI

Configures the pagination button UI.

csharp
public void SetPaginationUI(VisualElement parent, string normal, string active, int maxButtonAmount)
ParameterTypeDescription
parentVisualElementContainer element for pagination buttons
normalstringColor-class name for non-active page buttons
activestringColor-class name for the active page button
maxButtonAmountintMaximum number of page buttons to display

GridViewList

GridViewList<TItem> extends GridViewBase<TItem> and uses Unity's ListView for virtualized scrolling. Only visible items are rendered, making it efficient for large data sets. Columns are dynamic-only (width-driven), and it can swap row height, scroller visibility, and scroll physics per breakpoint.

Constructor

csharp
public GridViewList( List<TItem> itemList, VisualTreeAsset itemSlotTemplate, GameObject parent, VisualElement parentElement, ListView slotContainer, TooltipController tooltipController = null, bool startVisible = true, VisualElement focusElement = null, float fadeDuration = 0.5f, EasingMode easingMode = EasingMode.EaseOutCirc, bool disableOtherViewsOnFadeIn = false, bool debug = false )

Note: slotContainer is a ListView instead of a plain VisualElement.

Delegate binders — skip the subclass

For trivial bind logic you don't need a GridViewList/GridViewPagination subclass at all. DelegateGridViewList<TItem> and DelegateGridViewPagination<TItem> take the three binder hooks as delegates — the same idiom as Unity's own ListView.makeItem/bindItem:

csharp
_grid = new DelegateGridViewList<int>( items, slotTemplate, gameObject, gridParent, listView, makeItem: slot => slot.userData = new MySlotController(slot), bindItem: (slot, item, index, selected) => ((MySlotController)slot.userData).Bind(item, index), unbindItem: slot => ((MySlotController)slot.userData).Unbind() );

The delegates slot in right after slotContainer; the optional tail (tooltipControllerdebug) is unchanged. Subclass only when you need to override more than the three binder hooks.

Properties

PropertyTypeDescription
ListViewListViewThe underlying ListView element

Columns

GridViewList columns are width-driven by default: the count is floor(containerWidth / minItemWidth), clamped to at least 1. For a pinned count (a single-column list, a fixed 3-up grid), use SetFixedItemPerLine.

SetDynamicItemPerLine (GridViewList)

Sets the minimum slot width and recomputes the column count from the container's current width — CSS repeat(auto-fit, minmax(minItemWidth, 1fr)). Give the slot template flex-grow: 1 so cards fill the row (the 1fr).

csharp
public void SetDynamicItemPerLine(int minItemWidth)
ParameterTypeDescription
minItemWidthintMinimum slot width in px (the minmax floor)

Pair with RegisterDynamicItemPerLineUpdate so the count recomputes as the container resizes.

This is a grid-specific container query. To restyle any element by its own width (not just a grid's column count), use LunaContainerQuery — the general per-element version.

SetFixedItemPerLine (GridViewList)

Pins the column count regardless of container width — the honest way to make a plain list (one column) or any fixed grid out of a GridViewList. Clears the width-derived baseline so responsive configs don't inherit a bogus MinItemWidth.

csharp
public void SetFixedItemPerLine(int itemsPerLine)
ParameterTypeDescription
itemsPerLineintColumns to always use (clamped to ≥ 1)
csharp
_list.SetFixedItemPerLine(1); // single-column leaderboard-style list

Removed: SetSlotPerLine(int) and the old maxColumns parameter on SetDynamicItemPerLine. For a pinned column count use SetFixedItemPerLine — don't pass an oversized minItemWidth to the dynamic overload; it poisons the responsive baseline.

Responsive configuration (per breakpoint)

GridViewList can swap the C#-only ListView / ScrollView settings that USS can't reach — scroller visibility, row height, scroll physics, and the column floor — when the active breakpoint changes. It listens to LunaResponsiveSettingsSO.BreakpointChanged (see Responsive) and applies the matching config. Exactly one breakpoint is active at a time — resolved from the viewport's logical width against the ladder (default "sm" / "md" / "lg"), so name your configs to match the tiers you author.

GridViewResponsiveConfig

The runtime config. Every field is nullable — null means inherit: fall through to the default config, then to the element's captured baseline (its authored value, snapshotted before any config is applied). Because of that baseline fallback, leaving a breakpoint that set a field reverts it automatically.

csharp
public struct GridViewResponsiveConfig { public ScrollerVisibility? VerticalScroller; public ScrollerVisibility? HorizontalScroller; public float? FixedItemHeight; // ListView row height public int? MinItemWidth; // dynamic column floor public ScrollViewMode? ScrollMode; // see orientation note public ScrollView.TouchScrollBehavior? TouchScroll; public float? ScrollDecelerationRate; public float? Elasticity; public float? MouseWheelScrollSize; }

SetResponsiveConfig / SetResponsiveConfigs

Register a config per breakpoint. Use "" (or null) for the default baseline merged under every breakpoint; a named breakpoint (e.g. "sm") overrides only the fields it sets.

csharp
public void SetResponsiveConfig(string breakpoint, GridViewResponsiveConfig config); public void SetResponsiveConfigs(IEnumerable<GridViewBreakpointSettings> settings);

SetResponsiveConfigs takes the inspector-authorable GridViewBreakpointSettings (below) and converts each entry.

RegisterResponsive / UnregisterResponsive

Subscribe / unsubscribe to BreakpointChanged and apply the current breakpoint immediately. Always pair them — the settings asset is long-lived, so an unregistered view would leak a dead subscriber. Dispose also calls UnregisterResponsive.

csharp
public void RegisterResponsive(); public void UnregisterResponsive();

This complements RegisterDynamicItemPerLineUpdate, it doesn't replace it: geometry handles continuous resize within a breakpoint; BreakpointChanged handles the discrete config swap across breakpoints.

GridViewBreakpointSettings (inspector)

GridViewResponsiveConfig's nullable fields can't be serialized by Unity, so author per-breakpoint configs in the inspector with GridViewBreakpointSettings — a [Serializable] mirror using Inherit-first enum dropdowns and non-positive sentinels (<= 0 = inherit). Expose a List<GridViewBreakpointSettings> and feed it to SetResponsiveConfigs.

csharp
[SerializeField] private List<GridViewBreakpointSettings> _responsiveConfigs = new() { // "" = the default baseline, reverted to on desktop new GridViewBreakpointSettings { Breakpoint = "", MinItemWidth = 150, FixedItemHeight = 260 }, // "sm" = mobile override: tighter columns, shorter rows, no scrollbar, elastic touch new GridViewBreakpointSettings { Breakpoint = "sm", MinItemWidth = 120, FixedItemHeight = 200, VerticalScroller = GridViewBreakpointSettings.ScrollerVisibilityOption.Hidden, TouchScroll = GridViewBreakpointSettings.TouchScrollOption.Elastic, }, }; // in your view setup: _grid.SetResponsiveConfigs(_responsiveConfigs); _grid.RegisterDynamicItemPerLineUpdate(); _grid.RegisterResponsive();

Reverting is automatic. An Inherit field falls back to the element's authored baseline (snapshotted on the first apply), so leaving a breakpoint that set a field restores the original — e.g. sm hides the scroller, and moving to a wider tier (md/lg) shows it again. You only need a "" default entry when you want a baseline that differs from the element's authored UXML/USS value.

Debugging. Call SetResponsiveDebug(true) on the grid to log the resolved config on every breakpoint apply — [GridViewList] breakpoint 'sm' applied — vScroller=Hidden, ….

Orientation caveat. A ListView virtualizes vertically. ScrollMode = Horizontal enables horizontal overflow scrolling but does not reflow rows into a horizontal carousel — that needs a non-ListView grid. The reliable responsive knobs are scroller visibility, row height, column floor, and scroll physics.


Settings

Theme

Light

Contrast

Material

Dark

Dim

Material Dark

System

Sidebar(Light & Contrast only)

Light
Dark

Font Family

DM Sans

Wix

Inclusive Sans

AR One Sans

Direction

LTR
RTL