From 1b0c523352678e30b34071db4b6b3bbadf3eb101 Mon Sep 17 00:00:00 2001 From: wallstop Date: Fri, 2 Oct 2026 19:10:08 +0000 Subject: [PATCH 1/2] Document search, filtering, asset operations, and extension points The live documentation site published only an index and a getting-started page, so the long-form guide stayed in README.md, where it drifted from the code it describes. Add four guide pages, each traced to source: - search-and-filter.md: the header-row search box covers every tracked type and matches names, type names, exact GUIDs, and string fields on nested plain objects, capped at 25 results. Also the type filter and the label filter, including that an empty clause never matches in OR mode and that the Advanced row refuses to collapse while OR labels exist. - managing-assets.md: where each asset operation lives, the per-type folder Create writes into, the clone naming rule, dialog validation messages, and processor scope with its load-in-progress refusal. - organizing.md: ordering, pane widths, both persistence targets and the migration between them, themes and their tokens, and the Play Mode pause. - extending.md: the display attribute, BaseDataObject, the lifecycle hooks with their defaults, IGUIProvider, IDisplayable, and IDataProcessor. Correct the README claims the sweep found false: the search box is in the window header row and is not scoped to the selected type, arrow buttons move a row to the top or bottom rather than stepping it, Create writes to a per-type folder under the Data Folder, clones strip any existing "(Clone n)" before reapplying one, label-filter OR semantics were omitted, the persistence setting names both targets, and label editing was missing. --- README.md | 32 +++-- docs/extending.md | 226 +++++++++++++++++++++++++++++++++ docs/extending.md.meta | 7 + docs/getting-started.md | 47 ++++--- docs/index.md | 6 +- docs/managing-assets.md | 115 +++++++++++++++++ docs/managing-assets.md.meta | 7 + docs/organizing.md | 171 +++++++++++++++++++++++++ docs/organizing.md.meta | 7 + docs/search-and-filter.md | 132 +++++++++++++++++++ docs/search-and-filter.md.meta | 7 + mkdocs.yml | 4 + 12 files changed, 732 insertions(+), 29 deletions(-) create mode 100644 docs/extending.md create mode 100644 docs/extending.md.meta create mode 100644 docs/managing-assets.md create mode 100644 docs/managing-assets.md.meta create mode 100644 docs/organizing.md create mode 100644 docs/organizing.md.meta create mode 100644 docs/search-and-filter.md create mode 100644 docs/search-and-filter.md.meta diff --git a/README.md b/README.md index faf9892..e2e40ad 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ Data Visualizer streamlines working with ScriptableObject-heavy systems by centr Data Visualizer is free forever: no subscriptions, no paid upgrades, no feature-gated tiers. The full source is MIT-licensed, and every capability documented here ships in the free package. -This guide captures the key points from the companion [video walkthrough](https://youtu.be/3oUxUSKNyhw) while keeping the instructions project-agnostic. +This guide captures the key points from the companion [video walkthrough](https://youtu.be/3oUxUSKNyhw) while keeping the instructions project-agnostic. A browsable version of this documentation, with per-topic pages, is published at . ## Getting Started @@ -21,9 +21,9 @@ Open **Tools → Wallstop Studios → Data Visualizer** and dock it alongside th The window uses a three-panel layout: -**Namespace & Type Panel (left)** organizes ScriptableObject types by C# namespace. Click a namespace to expose its types, then select a type to load all instances. Reorder namespaces and types with the up and down arrow buttons—your ordering persists across sessions. +**Namespace & Type Panel (left)** organizes ScriptableObject types by C# namespace. Click a namespace to expose its types, then select a type to load all instances. Reorder namespaces and types by dragging them, or with the up and down arrow buttons, which move a row to the top or bottom of its list. Your ordering persists across sessions. -**Objects Panel (center)** lists every instance of the selected type and keeps one selection at a time. Arrow buttons let you reorder instances without dragging through long lists, and batch edits run through the per-type processors area (scoped to all instances or the filtered set) and per-row actions. +**Objects Panel (center)** lists every instance of the selected type and keeps one selection at a time. Drag a row to place an instance precisely, or use its arrow buttons to move it to the top or bottom. Batch edits run through the per-type processors area, scoped to all instances or to the filtered set, and through the per-row actions. **Inspector Panel (right)** displays the full inspector for the selected asset, including Odin Inspector integrations and custom editors. Changes save immediately, just like Unity's default Inspector. @@ -34,7 +34,7 @@ The window uses a three-panel layout: Asset management controls live above the Objects panel: -**Clone** duplicates the selected asset with a "Clone" suffix in the same folder as the original. Useful for creating variants without leaving the window. +**Clone** duplicates the selected asset in the same folder as the original, directly after it in the list. Any existing `(Clone)` or `(Clone n)` suffix is stripped from the source name first, then reapplied, so repeated clones read `(Clone)`, `(Clone 1)`, `(Clone 2)`, and so on. Useful for creating variants without leaving the window. **Rename** opens a draggable prompt that renames the asset on disk. No need to coordinate between multiple panels or windows. @@ -49,7 +49,7 @@ Inspector edits save immediately. Your selection persists when switching between ![Create button and data folder selector above object list](https://raw.githubusercontent.com/wallstop/DataVisualizer/main/docs/images/data-visualizer-create.jpg) *New asset workflow at 06:45.* -The **Create** button spawns a new instance of the active type in your configured **Data Folder** (see Settings below). Clones stay beside their originals regardless of the Data Folder setting. Chain create with rename or move to place new assets exactly where you need them. +The **Create** button asks for a name and spawns a new instance of the active type under your configured **Data Folder** (see Settings below), in a per-type folder named after the type's full namespace, such as `Assets/Data/MyGame/Items/WeaponData/`. Those folders are created for you. Clones stay beside their originals regardless of the Data Folder setting. Chain create with rename or move to place new assets exactly where you need them. ## Building Your Type Catalog @@ -70,18 +70,26 @@ Organize the catalog to match your team's mental model. The structure persists a ## Search & Filtering -The filter field above the Namespace list narrows type rows by display name with case-insensitive matching; namespace headers are not filtered. The global search box above the Objects panel finds text across all loaded instances, letting you jump directly to specific assets without manual scanning. +Three finders narrow different things. + +The **Search** box in the window header row, next to the Settings button, searches every tracked type rather than only the selected one. A space-separated query matches when every term matches an asset name, a type name, an exact asset GUID, or a string field on the asset or a nested plain object; field matching is case-insensitive and skips primitives, vectors, colors, and references to other Unity objects. Results are ordered by asset name then full type name, list up to two matched fields for context, highlight the matched terms, and cap at 25 results. Use Up and Down to move, Enter to open the highlighted asset, Escape to dismiss. + +The filter field above the Namespace list narrows type rows by display name with case-insensitive matching; namespace headers are not filtered. + +Dragging labels from **Available** into the **AND:** or **OR:** rows filters the selected type's instances by Unity asset labels. The **AND &&** and **OR ||** switch chooses between requiring every dragged label and requiring any one of them; in OR mode an empty clause never counts as a match. The Advanced row refuses to collapse while OR mode or any OR label is active, so a filter that hides rows is never left concealed. A line under the filter reports how many objects are hidden. The selected asset's inspector panel adds and removes its labels, and the filter re-applies immediately. ## Settings ![Settings dropdown showing persistence options and data folder field](https://raw.githubusercontent.com/wallstop/DataVisualizer/main/docs/images/data-visualizer-settings.jpg) *State management settings at 18:20.* -**Persist state in user settings** stores layout, ordering, and tracked types in your local user cache instead of a shared project asset. Enable this to avoid merge conflicts when multiple developers customize their own workspace. +**Persist State in UserState** (the default) stores the selected namespace and type, the selected object per type, namespace, type, and object ordering, collapse state, tracked types, per-type label filters, and the per-type processor scope in a per-user JSON file instead of a shared project asset. Each developer keeps a private arrangement and version control stays free of layout churn. -**Select active object** syncs selection between Data Visualizer and Unity's Inspector window. Useful for cross-referencing assets in other editor windows. +Turning the setting off stores the same state inside a `DataVisualizerSettings` asset in the project, which suits a team that wants one shared arrangement. Data Visualizer creates one at `Assets/Editor/DataVisualizerSettings.asset` on first use if no such asset exists. Switching between the two copies the current state across. -**Data Folder** defines where new assets land. Click to ping the current folder or browse to set a new default. +**Select Active Object** syncs selection between Data Visualizer and Unity's Inspector window. Useful for cross-referencing assets in other editor windows. + +**Data Folder** defines where new assets land, each in a per-type folder named after its full namespace. Click the path to ping the current folder, or browse to set a new default. ### Themes @@ -112,7 +120,7 @@ The override stylesheet is applied after the package stylesheet. Standard contro Data Visualizer exposes several extension points for custom workflows: -**Attributes** let you override display namespace or friendly names on ScriptableObject classes. Useful when code organization doesn't match your content taxonomy. +**Attributes** let you override display namespace or friendly names on ScriptableObject classes. `[CustomDataVisualization(Namespace = "...", TypeName = "...")]` replaces the namespace group and the display name the window shows. Without it the window files a type under the last segment of its C# namespace. Useful when code organization doesn't match your content taxonomy. **BaseDataObject** provides a ready-made base class for ScriptableObjects with built-in lifecycle support: - Stores an asset GUID, title, and description for display and stable identity @@ -123,7 +131,9 @@ Derived classes override only what they need—GUID generation, cache resets, co **Lifecycle Interfaces** hook into asset events before and after clone, create, and rename operations. Enforce invariants like regenerating IDs or pushing audit logs to telemetry without writing per-asset editor scripts. -**UI Toolkit Extensions** render custom UI alongside the default inspector. Return a `VisualElement` tree—graphs, thumbnails, validation badges, or any UI Toolkit component—and Data Visualizer slots it in automatically. Because the entire window runs on UI Toolkit, this approach scales to complex dashboards without leaving the unified workflow. +**UI Toolkit Extensions** render custom UI alongside the default inspector. Return a `VisualElement` tree—graphs, thumbnails, validation badges, or any UI Toolkit component—from `IGUIProvider.BuildGUI` (or `BaseDataObject.BuildGUI`) and Data Visualizer slots it in below the inspector. The `DataVisualizerGUIContext` argument carries the selected asset's `SerializedObject` for writing changes back. Because the entire window runs on UI Toolkit, this approach scales to complex dashboards without leaving the unified workflow. + +**Processors** are plain `IDataProcessor` classes that the window discovers with no registration. `Name` labels the button, `Description` is its tooltip, `Accepts` limits the types it applies to, and `Process(Type, IEnumerable)` receives the **ALL** or **FILTERED** object set you chose. The window instantiates processors through their public parameterless constructor, skips any that lack one, and surfaces a thrown exception in a dialog without stopping other processors. ## Workflow Tips diff --git a/docs/extending.md b/docs/extending.md new file mode 100644 index 0000000..73a4645 --- /dev/null +++ b/docs/extending.md @@ -0,0 +1,226 @@ +# Extending the window + +The runtime assembly is part of the package and carries the extension surface, so +your types compile against it in both the editor and a player build. Everything +on this page is public API. + +## Display attributes + +`[CustomDataVisualization]` overrides how a type appears in the catalog. + +```csharp +using WallstopStudios.DataVisualizer; + +[CustomDataVisualization(Namespace = "Weapons", TypeName = "Weapon")] +public sealed class WeaponData : ScriptableObject +{ +} +``` + +`Namespace` replaces the namespace group the type is filed under. Without it the +window uses the last segment of your C# namespace, so +`MyGame.Items.Weapons` appears under `Weapons`, and a type in no namespace appears +under `No Namespace`. + +`TypeName` replaces the type's display name in the list, in the type filter, and +in the dialogs that name the type. Without it the C# type name is used. + +With the Odin Inspector available, the attribute also carries `UseOdinInspector`, +which defaults to `true`. See [Odin Inspector](#odin-inspector). + +## BaseDataObject + +`BaseDataObject` is an abstract `ScriptableObject` that carries identity and the +lifecycle hooks. Derive from it instead of `ScriptableObject` to get them. + +```csharp +using UnityEngine; +using WallstopStudios.DataVisualizer; + +public sealed class ItemData : BaseDataObject +{ + [SerializeField] + private int value; +} +``` + +It provides: + +- `Id`, the asset's GUID, kept in sync with the AssetDatabase. +- `Title`, the display name. A blank title falls back to the asset name, so you + only set it when the asset name is not what you want to show. +- `Description`. +- Ordering by title, then asset name, then Id, then description. + +The serialized fields `_assetGuid`, `_title`, and `_description` are shown in the +inspector under a **Base Data** header, read-only. + +## Lifecycle hooks + +Each hook is a `virtual` method on `BaseDataObject` and also a standalone +interface, so you can implement one without deriving. + +| Interface | Methods | When | +| --- | --- | --- | +| `IDuplicable` | `BeforeClone(ScriptableObject previous)`, `AfterClone(ScriptableObject previous)` | Around [Clone](managing-assets.md#clone) | +| `ICreatable` | `BeforeCreate()`, `AfterCreate()` | Around [Create](managing-assets.md#create) | +| `IRenamable` | `BeforeRename(string newName)`, `AfterRename(string newName)` | Around [Rename](managing-assets.md#rename) | + +Override only what you need. The default implementations already do the work you +would otherwise repeat: `BeforeClone` clears the inherited asset GUID so the copy +does not share the original's identity, and `AfterClone` refreshes the GUID from +the new asset path and applies the `(Clone)`, `(Clone 1)`, `(Clone 2)`, … suffix +to the title. + +```csharp +public sealed class ItemData : BaseDataObject +{ + public override void BeforeRename(string newName) + { + // Audit before the file on disk changes. + } + + public override void AfterRename(string newName) + { + EditorUtility.SetDirty(this); + } +} +``` + +## Custom inspector content + +Implement `IGUIProvider` and return a `VisualElement` from `BuildGUI`. The window +adds it below the standard inspector. + +```csharp +using UnityEngine.UIElements; +using WallstopStudios.DataVisualizer; + +public sealed class ItemData : BaseDataObject +{ + public override VisualElement BuildGUI(DataVisualizerGUIContext context) + { + return new Label("Weight: ") { name = "weight-label" }; + } +} +``` + +Return `null` to add nothing. `BaseDataObject` implements `IGUIProvider` and +returns `null` by default, so a derived type only overrides the method when it +has something to show. + +The `DataVisualizerGUIContext` carries the `SerializedObject` for the selected +asset in the editor, which is what you need to write back through. The context +type has no editor-only members in a player build, so a `BuildGUI` override +compiles in both. + +## Display name + +Implement `IDisplayable` to control the name shown for an object. `BaseDataObject` +implements it through `Title`. For a plain `ScriptableObject`, implement it +yourself: + +```csharp +public sealed class ItemData : ScriptableObject, IDisplayable +{ + [SerializeField] + private string displayName; + + public string Title => string.IsNullOrWhiteSpace(displayName) ? name : displayName; +} +``` + +The row label, the search result name, and the drag ghost all read `Title` when +the type provides it. + +## Processors + +A processor is a plain class implementing `IDataProcessor`. The window discovers +every implementation in your project, so no registration is needed. + +```csharp +using System; +using System.Collections.Generic; +using UnityEngine; +using WallstopStudios.DataVisualizer; + +public sealed class RecalculateRarity : IDataProcessor +{ + public string Name => "Recalculate rarity"; + + public string Description => "Recomputes each item's rarity from its level."; + + public IEnumerable Accepts => new[] { typeof(ItemData) }; + + public void Process(Type type, IEnumerable objects) + { + foreach (ScriptableObject obj in objects) + { + if (obj is ItemData item) + { + item.Rarity = Rarity.FromLevel(item.Level); + EditorUtility.SetDirty(item); + } + } + } +} +``` + +- `Name` is the button label and sorts the list. +- `Description` is the button tooltip. +- `Accepts` limits the processor to the types you list. Return `null` to accept + every type. +- `Process` receives the type and the objects, which is the **ALL** or + **FILTERED** set chosen in the window. See + [Processors](managing-assets.md#processors) for scope, the confirmation, and + the load-in-progress refusal. +- `WillEffect(Type, IEnumerable)` is a default interface method + that returns the number of objects. Override it when a processor will skip some. + +The window instantiates each processor with its public parameterless constructor. +A processor that is abstract, generic, or lacks that constructor is skipped and +logged. Exceptions from `Process` are caught, logged, and surfaced in a dialog; +one failing processor does not stop the others. + +The window saves assets and refreshes after a processor runs, so call +`EditorUtility.SetDirty` on anything you change. + +## Read-only fields + +`[ReadOnly]` on a serialized field draws it in the inspector as read-only. It +applies to fields and properties. + +```csharp +public sealed class ItemData : BaseDataObject +{ + [ReadOnly] + [SerializeField] + private string contentHash; +} +``` + +## Odin Inspector + +When the Odin Inspector is installed, the window renders assets through Odin +instead of the standard inspector in two cases: the type carries +`[CustomDataVisualization]` with `UseOdinInspector` set, and the type derives +from `SerializedScriptableObject` (which `BaseDataObject` does when Odin is +present). A type that opts out shows the standard inspector. + +Odin integration is optional. The package has no dependency on Odin and compiles +and runs without it. + +## Removing a type from the catalog + +Types deriving from `BaseDataObject` and types carrying +`[CustomDataVisualization]` are managed for you and have no remove button. Other +tracked types can be removed with the **X** on their row or their namespace +header. Removing a type stops Data Visualizer from tracking it; the assets stay on +disk. + +## Next steps + +- [Managing assets](managing-assets.md) — the operations your hooks run inside. +- [Search and filtering](search-and-filter.md) — the label filter your processors + can be scoped to. +- [Organizing your data](organizing.md) — themes, layout, and persistence. \ No newline at end of file diff --git a/docs/extending.md.meta b/docs/extending.md.meta new file mode 100644 index 0000000..22926bd --- /dev/null +++ b/docs/extending.md.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 562ae70135f8423f972833a02be45152 +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: \ No newline at end of file diff --git a/docs/getting-started.md b/docs/getting-started.md index 1212beb..418af67 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -67,27 +67,37 @@ beside their original, whatever the Data Folder is. ## Filter and search +Three finders narrow different things, and they do not affect each other. The +[Search and filtering](search-and-filter.md) page covers each one in full. + +- The **Search** box in the window header row searches every tracked type, not + only the type you selected. It matches asset names, type names, asset GUIDs, + and string fields on the assets themselves, so you can find an asset by what is + inside it. - The filter field above the namespace list narrows the type rows by display name, without case sensitivity. Namespace headers stay visible. -- The search box above the object list searches the text of all loaded - instances, so you can jump to an asset by its contents. - Drag labels from **Available** into the **AND:** or **OR:** rows to show only - assets that carry them. The **AND &&** and **OR ||** toggle switches between - matching every dragged label and matching any of them. + assets that carry them. The **AND &&** and **OR ||** switch chooses between + matching every label you dragged and matching any of them. ## Keep your arrangement -Pane widths, ordering, tracked types, and your selection persist between -sessions. Under **Settings**, choose where that state lives: +Ordering, collapse state, tracked types, and your selection persist between +sessions, and you can reorder namespaces, types, and instances directly in the +window. The [Organizing your data](organizing.md) page covers ordering, +persistence, themes, and the Play Mode pause. + +Under **Settings**, choose where that state lives: -- **Persist state in user settings** (default) stores it in your local user - cache, so each developer keeps a private arrangement and version control - stays free of layout churn. +- **Persist State in UserState** (default) stores it in your local user cache, so + each developer keeps a private arrangement and version control stays free of + layout churn. - The project settings asset stores the state in the repository, which suits a team that wants one shared arrangement. -**Select active object** mirrors your Data Visualizer selection in Unity's -Inspector, and **Data Folder** sets where new assets are created. +**Select Active Object** mirrors your Data Visualizer selection in Unity's +Inspector, and **Data Folder** sets where new assets are created. New assets land +in a per-type folder under it; clones stay beside their original. ## While the editor is playing @@ -104,9 +114,12 @@ Everything resumes when you leave Play Mode. ## Next steps -- Themes: pick Classic, Nord, Dracula, Compact, or Minimal under **Settings → - Theme**, or author your own. -- Extensibility: derive from `BaseDataObject`, implement the lifecycle - interfaces, and return your own UI Toolkit content from `BuildGUI`. -- The [README](https://github.com/wallstop/DataVisualizer#readme) documents - every control, the theme tokens, and the runtime extension points. +- [Search and filtering](search-and-filter.md) — what global search matches, its + 25-result limit, and the exact AND/OR label rules. +- [Managing assets](managing-assets.md) — where each asset operation lives, where + created files go, how clones are named, and how processors are scoped. +- [Organizing your data](organizing.md) — ordering, themes and their tokens, + persistence, and what pauses during Play Mode. +- [Extending the window](extending.md) — attributes, `BaseDataObject`, lifecycle + hooks, processors, and custom inspector content. +- The [video walkthrough](https://youtu.be/3oUxUSKNyhw) gives a visual tour. diff --git a/docs/index.md b/docs/index.md index f5955bb..8946800 100644 --- a/docs/index.md +++ b/docs/index.md @@ -43,6 +43,10 @@ Open the window with **Tools → Wallstop Studios → Data Visualizer**. ## Where to go next - [Getting started](getting-started.md) — install the package, open the window, and edit your first asset. -- [README](https://github.com/wallstop/DataVisualizer#readme) — the full feature guide, including themes, search, filtering, persistence, and extension points. +- [Search and filtering](search-and-filter.md) — global search, the type filter, and label filtering. +- [Managing assets](managing-assets.md) — create, clone, rename, move, delete, and run processors. +- [Organizing your data](organizing.md) — ordering, layout, themes, persistence, and Play Mode. +- [Extending the window](extending.md) — attributes, `BaseDataObject`, processors, and custom inspector content. +- [README](https://github.com/wallstop/DataVisualizer#readme) — the repository README, which links the video walkthrough. - [Video walkthrough](https://youtu.be/3oUxUSKNyhw) — a visual tour of the window. - [Issues](https://github.com/wallstop/DataVisualizer/issues) — known problems and feature requests. diff --git a/docs/managing-assets.md b/docs/managing-assets.md new file mode 100644 index 0000000..eff27ca --- /dev/null +++ b/docs/managing-assets.md @@ -0,0 +1,115 @@ +# Managing assets + +This page covers the operations that change assets on disk, and the processors +that change many at once. Everything here is refused while the window is paused +for Play Mode; see [While the editor is playing](organizing.md#while-the-editor-is-playing). + +## Where an action lives + +Two places run asset operations: + +- **Buttons above the object list** act on the selected object: **Create** on the + left of that row, then **Clone**, **Rename**, **Move**, and **Delete** on the + right. +- **Per-row buttons** on each object row do the same for that row's object + without selecting it first. + +Both paths call the same code, so they behave identically. + +## Create + +**Create** asks for a name, prefilled with the type's display name, and refuses a +blank name or one containing a character that is invalid in a file name. + +The asset is written to a per-type folder under your **Data Folder**, using the +type's full name with each `.` replaced by a directory separator. For +`MyGame.Items.WeaponData` with a Data Folder of `Assets/Data`, the asset lands in +`Assets/Data/MyGame/Items/WeaponData/`. Those folders are created for you. + +If the name is already taken, nothing is written. When the existing asset is the +same type it is selected and shown, and the dialog says so. + +## Clone + +**Clone** copies the selected asset into the same folder as the original, and +inserts the copy directly after the original in the list, then selects it. + +Naming strips any existing `(Clone)` or `(Clone n)` from the source file name +first, so repeated clones do not accumulate suffixes. The first clone is +`(Clone)`, then `(Clone 1)`, `(Clone 2)`, and so on. Unity's own unique-path +check breaks any remaining collision. + +Clones always stay beside their original. Your **Data Folder** does not affect +them. + +## Rename + +**Rename** asks for a new name without the extension and validates it before +touching the asset. The dialog reports `Invalid name.` for a blank name or an +invalid character, `Name is unchanged.` when nothing differs, and Unity's own +`Invalid: …` message when the move would be rejected, for example onto an +existing file. + +The asset keeps its folder; only the file name changes. + +## Move + +**Move** opens a folder picker starting in the asset's current folder. The target +must be inside the project's `Assets` directory; anything else is refused with an +**Invalid Folder** dialog. Moving an asset to the folder it is already in does +nothing. + +The asset keeps its file name. + +## Delete + +**Delete** asks for confirmation, naming the asset. Confirming removes the `.asset` +file. Cancel does nothing. + +## Processors + +The **Processors** area sits below the namespace panel. It lists every +`IDataProcessor` implementation in your project whose `Accepts` list covers the +selected type, sorted by name. A processor with no `Accepts` list applies to every +type. The collapsed header shows how many processors are listed; the area is +hidden entirely when none apply. + +Clicking a processor opens a confirmation that names the processor and the number +of objects it will receive. Confirming runs `Process`, saves assets, refreshes the +AssetDatabase, and schedules a window refresh. A processor that throws is caught: +the exception is logged and an **Error running processor '…'** dialog reports the +message. Other processors are unaffected. + +### ALL and FILTERED + +The **ALL** / **FILTERED** switch above the processor list sets the scope. + +- **ALL** passes every loaded instance of the selected type. +- **FILTERED** passes only the instances the [label filter](search-and-filter.md#label-filter) + currently shows. With no label filter, that is the same set as **ALL**. + +**FILTERED** is the default. + +### Refusal while loading + +Instances stream in asynchronously. Running a processor while the selected type +is still loading would touch only the subset that has arrived while the dialog +implied the whole type, so the run is refused with a **Loading In Progress** +dialog. Wait for the load to finish, then run it. + +Processors are also refused during the Play Mode pause, and the switch is +disabled while the window is paused. + +## Undo + +These operations are not on Unity's undo stack. Clone, rename, move, and delete +change files directly. Inspector edits go through Unity's own serialized-object +path and do support undo. + +## Next steps + +- [Search and filtering](search-and-filter.md) — find objects and narrow them by + label. +- [Organizing your data](organizing.md) — ordering, layout, persistence, and the + Play Mode pause. +- [Extending the window](extending.md) — write your own processors. \ No newline at end of file diff --git a/docs/managing-assets.md.meta b/docs/managing-assets.md.meta new file mode 100644 index 0000000..56d81fc --- /dev/null +++ b/docs/managing-assets.md.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: c04ec56b778f49d8bcf85871f05126f3 +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: \ No newline at end of file diff --git a/docs/organizing.md b/docs/organizing.md new file mode 100644 index 0000000..7dee061 --- /dev/null +++ b/docs/organizing.md @@ -0,0 +1,171 @@ +# Organizing your data + +This page covers the arrangement of the window: what you can reorder, what +persists, where it is stored, and what the window does while you are in Play +Mode. + +## Reordering + +Three lists can be reordered, and all three persist. + +### Namespaces + +Each namespace header has **↑** and **↓**. **↑** moves the namespace to the top +of the list, **↓** to the bottom. The buttons disable at the ends, so the control +tells you when there is nowhere to go. + +You can also drag a namespace header to any position. The dragged row follows the +pointer and shows a ghost of its label. + +### Types + +Each type row has **↑** and **↓** with the same top-and-bottom behavior, scoped +to its own namespace. Types can also be dragged, within and across namespaces. + +### Objects + +Each object row has **↑** and **↓**, which move that object to the top or bottom +of the list for its type. **↓** is disabled on the last row. + +Objects can also be dragged directly. The list reorders as you drag, and the new +order is saved. + +The **X** on a namespace header or a type row removes it from Data Visualizer. +This is not destructive: your assets stay on disk. Removal is offered only for +types that are neither `BaseDataObject` subclasses nor types carrying +`[CustomDataVisualization]`, because the window manages those types for you. + +## Collapsing namespaces + +The arrow on a namespace header collapses and expands its types. Collapse state +persists per namespace. + +## Pane widths + +The two splitters between the three panels save their widths, so the +arrangement survives a reopen. Widths are saved per user through Unity's editor +preferences, independently of the persistence setting below. A width is +normalized so it cannot go below the pane's minimum, and the window remembers +your preferred size separately from a size the window had to clamp to, so +shrinking the editor and growing it back does not overwrite your choice. + +## Persistence + +One setting decides where the window's state lives: which namespace and type you +had selected, which object was selected in each type, the namespace, type, and +object ordering, collapse state, the types you track, the per-type label filters, +and the per-type processor scope and collapse state. + +### Persist in user state + +**Persist State in UserState** is the default. State is written as JSON to +`DataVisualizerUserState.json` in Unity's per-user persistent data path. Each +developer keeps a private arrangement and version control stays free of layout +churn. + +### Persist in a project settings asset + +Turning the setting off stores the same state inside a `DataVisualizerSettings` +asset in the project. Data Visualizer creates one at +`Assets/Editor/DataVisualizerSettings.asset` on first use if no +`DataVisualizerSettings` asset exists, and uses the first one it finds if you +have several. This suits a team that wants one shared arrangement, at the cost of +layout churn in version control. + +Switching between the two copies the current state across, so you do not lose +your arrangement when you toggle. + +### What is never shared + +The Data Folder, the `selectActiveObject` preference, and the pane widths stay +with the `DataVisualizerSettings` asset regardless of where the window state +lives. + +## Other settings + +The **Settings** popover opens from the **…** button in the header row. + +- **Select Active Object** mirrors your Data Visualizer selection in Unity's own + Inspector, for cross-referencing assets in other editor windows. +- **Theme** picks the appearance. See [Themes](#themes). +- **Data Folder** is where new assets are created. Click the path to ping the + folder, or **Select** to browse. The target must be inside `Assets`. Clones are + unaffected: they stay beside their original. + +## Themes + +Five themes ship in the package: **Classic**, **Nord**, **Dracula**, **Compact**, +and **Minimal**. Compact and Minimal keep the Classic palette and shrink the +density: Compact uses 13px type, 20px action buttons, and 4px control corners; +Minimal uses 12px type, 16px action buttons, and square corners. + +Click the theme field to open a searchable dropdown. Search by name or asset +path, use Up and Down to move, Enter to select, Escape to cancel. Themes from +both `Assets` and `Packages` are listed, and a duplicate name shows its path so +you can tell them apart. + +**Reset Theme** restores the Classic appearance and clears the saved theme +selection, keeping the compact Data Folder button sizing. Selecting the Classic +asset gives the same appearance but keeps an explicit selection. + +### Your own theme + +Create one with **Assets → Create → Wallstop Studios → DataVisualizer → Data +Visualizer Theme**, assign a `.uss` asset to its **Style Sheet** field, then pick +it in **Settings → Theme**. Keep the theme and its stylesheet under an `Editor` +folder; they are editor-only assets. + +The stylesheet you assign is applied after the package stylesheet, and standard +control rules are scoped to `.dataviz-root` inside the Data Visualizer window. +Ordinary USS precedence still applies, and inline styles win. Data color swatches +and label colors stay data-driven, and IMGUI or third-party inspector styling is +not replaced. + +Override the tokens you need rather than restyling controls: + +```css +:root { + --dataviz-accent: #88c0d0; + --dataviz-background: #2e3440; + --dataviz-control-hover: #88c0d0; + --dataviz-control-pressed: #81a1c1; + --dataviz-on-accent: #2e3440; + --dataviz-font-size: 15px; + --dataviz-circle-size: 32px; +} +``` + +Use `Nord.uss` or `Dracula.uss` as palette references. Copy them under your +project's `Editor` folder before customizing rather than editing installed +package files. + +Theme changes apply to the open window without reselecting or reopening it. +Editing the applied theme's stylesheet, changing its Style Sheet reference, and +renaming or moving either asset all update immediately. Deleting the applied +theme falls back to the package style without discarding the saved GUID; reset +clears that reference too. + +The selected theme follows the persistence setting above, so a theme chosen in +user state is yours alone and one chosen in the settings asset is shared. + +## While the editor is playing + +Entering Play Mode suspends package-driven work. The window keeps the last +loaded view and shows **Package editing is paused during Play Mode**. While it is +paused: + +- **Create**, the add-type controls, and the processor switch are disabled. +- The inspector is disabled and shows read-only content. +- Asset operations are refused: clone, rename, move, delete, and label changes. +- Selecting another type does not load its instances, and the catalog does not + change. + +Everything resumes when you leave Play Mode. + +## Next steps + +- [Search and filtering](search-and-filter.md) — find and narrow your data. +- [Managing assets](managing-assets.md) — create, rename, move, clone, delete, + and batch-process. +- [Extending the window](extending.md) — attributes, lifecycle hooks, custom + inspector content. \ No newline at end of file diff --git a/docs/organizing.md.meta b/docs/organizing.md.meta new file mode 100644 index 0000000..401e66c --- /dev/null +++ b/docs/organizing.md.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 0dc364a4658d4eecb33e260c16436fb3 +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: \ No newline at end of file diff --git a/docs/search-and-filter.md b/docs/search-and-filter.md new file mode 100644 index 0000000..4ac3c2c --- /dev/null +++ b/docs/search-and-filter.md @@ -0,0 +1,132 @@ +# Search and filtering + +Data Visualizer has three separate finders. Each one narrows a different thing, +and they do not affect each other. + +| Finder | Where it is | What it narrows | +| --- | --- | --- | +| Global search | Header row, next to the **Settings** button | Objects of every tracked type | +| Type filter | Above the namespace list | Type rows in the left panel | +| Label filter | Below the object list header | Objects of the selected type | + +## Global search + +The search box sits in the window header row, above all three panels. It is not +part of the Objects panel, and it does not search only the type you have +selected. + +Type a term and a popover opens with the matches. Each result shows the object's +name and its type name. When the term matched something other than the name, +type name, or GUID, the result also shows up to two `field: value` lines naming +the fields that matched, so you can tell why an asset is in the list. + +### What it matches + +Each space-separated term is matched on its own, and an object matches when +every term matches somewhere. A term is checked against, in order: + +1. the asset name, +2. the type name, +3. the asset GUID, as an exact match, +4. string fields on the asset and on nested plain objects. + +Field matching is case-insensitive. It skips primitives, `Vector2`, `Vector3`, +`Vector4`, `Quaternion`, `Color`, `Rect`, and `Bounds`, and it does not follow +references to other Unity objects or assets. Cycles in nested plain objects are +tracked, so a self-referencing structure terminates. + +Matching terms are highlighted in the results. The highlight deepens while the +pointer is over the row. + +### Limits and behavior + +- The popover lists at most 25 results. The list stops there; there is no + "show more". +- Results are ordered by asset name, then by full type name, both ordinal. +- Searching covers every tracked type, so a hit can be in a type you have not + selected yet. Selecting a result switches to that object's type when needed and + selects it there. +- The first search after the window opens can show **Building search index…**. + The index loads in the background; when it finishes, the current query is + re-run and the results appear. The popover stays open instead of dismissing + your query. +- A query with no matches shows **No matching objects found.** +- Up and Down move the highlight and wrap around. Enter or Return opens the + highlighted result. Escape closes the popover and keeps your text. + +## Type filter + +The field above the namespace list narrows the type rows by display name, +without case sensitivity. Namespace headers are not filtered, so you keep the +structure while the types inside it narrow. + +A display name comes from `[CustomDataVisualization]` when your type sets +`TypeName`; otherwise it is the C# type name. Every space-separated term must +match the display name. + +## Label filter + +Labels are Unity asset labels, the same ones you see on an asset in the Project +panel. The filter has three rows: + +- **Available** lists every label used by an instance of the selected type. +- **AND:** and **OR:** are drop targets. + +Drag a label pill from **Available** into **AND:** or **OR:** to filter, and drag +it back out to remove it. Drag labels between the two rows to change which clause +they belong to. + +### AND and OR + +The **AND &&** / **OR ||** switch above the OR row picks how the two clauses +combine. + +In **AND** mode an object must satisfy every clause that has labels. An empty +clause adds no constraint. + +In **OR** mode an object must satisfy at least one clause **that has labels**. An +empty clause never counts as a match. This matters: if you only fill the AND row +and switch to OR, nothing matches, because the empty OR clause is not a pass. + +Labels compare exactly, so `Urgent` and `urgent` are two different labels. + +### The Advanced row + +**Advanced** reveals the AND/OR switch and the OR row. The row refuses to +collapse while you are in OR mode or while any OR label is present, so a filter +you cannot see is never left behind hiding objects. The **Labels** header above +the whole section refuses to collapse while any label is configured. + +### What filtering changes + +The filter narrows the list you see. It does not change what is on disk, and it +does not change the order of the objects it keeps. + +When a filter hides rows, a line below the filter reports how many, in red when +fewer than 20 are hidden and in yellow otherwise. When nothing is hidden, the +line disappears. + +The filter is stored per type and follows the persistence setting described in +[Organizing your data](organizing.md). + +## Label editing + +The inspector panel for a selected asset has an **Asset Labels** section. Type a +label and press **Add**, or press Enter, to attach it. The field suggests labels +already used in the project, and you can move through the suggestions with the +arrow keys. Each attached label has a remove button. + +Adding a label the asset already has is a no-op. Adding or removing a label +saves the asset, refreshes the label cache, and re-applies the current filter, so +you see the effect immediately. + +Label editing is package-driven asset editing, so it is refused during the Play +Mode pause described in [Managing assets](managing-assets.md). + +## Next steps + +- [Managing assets](managing-assets.md) — create, rename, move, clone, delete, + and run processors. +- [Organizing your data](organizing.md) — ordering, layout, and persistence. +- [Extending the window](extending.md) — attributes, `BaseDataObject`, + processors, and custom inspector content. \ No newline at end of file diff --git a/docs/search-and-filter.md.meta b/docs/search-and-filter.md.meta new file mode 100644 index 0000000..bf0282b --- /dev/null +++ b/docs/search-and-filter.md.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: c7d8388914f14c96b69259f2d23dbb17 +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: \ No newline at end of file diff --git a/mkdocs.yml b/mkdocs.yml index 621756f..964af8f 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -45,6 +45,10 @@ theme: nav: - Home: index.md - Getting started: getting-started.md + - Search and filtering: search-and-filter.md + - Managing assets: managing-assets.md + - Organizing your data: organizing.md + - Extending the window: extending.md validation: nav: From fca86ae56899a42d2d58364bfcba6a82b3e49993 Mon Sep 17 00:00:00 2001 From: wallstop Date: Fri, 2 Oct 2026 19:17:51 +0000 Subject: [PATCH 2/2] Correct five claims in the new documentation against the source An adversarial pass over every statement in the four new pages found five that the code contradicts: - Global search matches an asset when ANY space-separated term matches, not only when every term does, and a term is matched against string fields only when the name, type name, and GUID have not already matched it. - The "objects hidden by label filter" line highlights fewer than 20 hidden in yellow and 20 or more in red. It was stated the other way round. - A processor whose Accepts is null or empty is never offered; it does not apply to every type. - ReadOnly is internal to the package, so it is not part of the documented extension surface and the section claiming otherwise is gone. - Clone, Rename, Move, and Delete live on each object row, not above the object list, and act on that row's object without selecting it first. --- README.md | 10 +++++----- docs/extending.md | 19 +++---------------- docs/managing-assets.md | 12 ++++-------- docs/search-and-filter.md | 12 ++++++++---- 4 files changed, 20 insertions(+), 33 deletions(-) diff --git a/README.md b/README.md index e2e40ad..c608b95 100644 --- a/README.md +++ b/README.md @@ -29,12 +29,12 @@ The window uses a three-panel layout: ## Instance Management -![Clone, rename, move, and delete controls highlighted above the object list](https://raw.githubusercontent.com/wallstop/DataVisualizer/main/docs/images/data-visualizer-instance-actions.jpg) +![Clone, rename, move, and delete controls on an object row](https://raw.githubusercontent.com/wallstop/DataVisualizer/main/docs/images/data-visualizer-instance-actions.jpg) *Instance actions demo at 03:35.* -Asset management controls live above the Objects panel: +Asset management controls sit on the right of each object row: -**Clone** duplicates the selected asset in the same folder as the original, directly after it in the list. Any existing `(Clone)` or `(Clone n)` suffix is stripped from the source name first, then reapplied, so repeated clones read `(Clone)`, `(Clone 1)`, `(Clone 2)`, and so on. Useful for creating variants without leaving the window. +**Clone** (`++`) duplicates that row's asset in the same folder as the original, directly after it in the list. Any existing `(Clone)` or `(Clone n)` suffix is stripped from the source name first, then reapplied, so repeated clones read `(Clone)`, `(Clone 1)`, `(Clone 2)`, and so on. Useful for creating variants without leaving the window. **Rename** opens a draggable prompt that renames the asset on disk. No need to coordinate between multiple panels or windows. @@ -72,7 +72,7 @@ Organize the catalog to match your team's mental model. The structure persists a Three finders narrow different things. -The **Search** box in the window header row, next to the Settings button, searches every tracked type rather than only the selected one. A space-separated query matches when every term matches an asset name, a type name, an exact asset GUID, or a string field on the asset or a nested plain object; field matching is case-insensitive and skips primitives, vectors, colors, and references to other Unity objects. Results are ordered by asset name then full type name, list up to two matched fields for context, highlight the matched terms, and cap at 25 results. Use Up and Down to move, Enter to open the highlighted asset, Escape to dismiss. +The **Search** box in the window header row, next to the Settings button, searches every tracked type rather than only the selected one. A space-separated query matches an asset when any one of its terms matches an asset name, a type name, an exact asset GUID, or a string field on the asset or a nested plain object; field matching is case-insensitive and skips primitives, vectors, colors, and references to other Unity objects. Results are ordered by asset name then full type name, list up to two matched fields for context, highlight the matched terms, and cap at 25 results. Use Up and Down to move, Enter to open the highlighted asset, Escape to dismiss. The filter field above the Namespace list narrows type rows by display name with case-insensitive matching; namespace headers are not filtered. @@ -133,7 +133,7 @@ Derived classes override only what they need—GUID generation, cache resets, co **UI Toolkit Extensions** render custom UI alongside the default inspector. Return a `VisualElement` tree—graphs, thumbnails, validation badges, or any UI Toolkit component—from `IGUIProvider.BuildGUI` (or `BaseDataObject.BuildGUI`) and Data Visualizer slots it in below the inspector. The `DataVisualizerGUIContext` argument carries the selected asset's `SerializedObject` for writing changes back. Because the entire window runs on UI Toolkit, this approach scales to complex dashboards without leaving the unified workflow. -**Processors** are plain `IDataProcessor` classes that the window discovers with no registration. `Name` labels the button, `Description` is its tooltip, `Accepts` limits the types it applies to, and `Process(Type, IEnumerable)` receives the **ALL** or **FILTERED** object set you chose. The window instantiates processors through their public parameterless constructor, skips any that lack one, and surfaces a thrown exception in a dialog without stopping other processors. +**Processors** are plain `IDataProcessor` classes that the window discovers with no registration. `Name` labels the button, `Description` is its tooltip, `Accepts` lists the types it applies to, and `Process(Type, IEnumerable)` receives the **ALL** or **FILTERED** object set you chose. The window instantiates processors through their public parameterless constructor, skips any that lack one, and surfaces a thrown exception in a dialog without stopping other processors. ## Workflow Tips diff --git a/docs/extending.md b/docs/extending.md index 73a4645..4780f25 100644 --- a/docs/extending.md +++ b/docs/extending.md @@ -168,8 +168,9 @@ public sealed class RecalculateRarity : IDataProcessor - `Name` is the button label and sorts the list. - `Description` is the button tooltip. -- `Accepts` limits the processor to the types you list. Return `null` to accept - every type. +- `Accepts` limits the processor to the types you list, and the processor is + only offered for a selected type that appears in that list. Return `null` or an + empty list and the processor is never offered. - `Process` receives the type and the objects, which is the **ALL** or **FILTERED** set chosen in the window. See [Processors](managing-assets.md#processors) for scope, the confirmation, and @@ -185,20 +186,6 @@ one failing processor does not stop the others. The window saves assets and refreshes after a processor runs, so call `EditorUtility.SetDirty` on anything you change. -## Read-only fields - -`[ReadOnly]` on a serialized field draws it in the inspector as read-only. It -applies to fields and properties. - -```csharp -public sealed class ItemData : BaseDataObject -{ - [ReadOnly] - [SerializeField] - private string contentHash; -} -``` - ## Odin Inspector When the Odin Inspector is installed, the window renders assets through Odin diff --git a/docs/managing-assets.md b/docs/managing-assets.md index eff27ca..7ce3e0b 100644 --- a/docs/managing-assets.md +++ b/docs/managing-assets.md @@ -6,15 +6,11 @@ for Play Mode; see [While the editor is playing](organizing.md#while-the-editor- ## Where an action lives -Two places run asset operations: +**Create** is the **+** button in the object list header; it always acts on the +selected type. -- **Buttons above the object list** act on the selected object: **Create** on the - left of that row, then **Clone**, **Rename**, **Move**, and **Delete** on the - right. -- **Per-row buttons** on each object row do the same for that row's object - without selecting it first. - -Both paths call the same code, so they behave identically. +**Clone**, **Rename**, **Move**, and **Delete** are the four buttons on the right +of each object row, and they act on that row's object without selecting it first. ## Create diff --git a/docs/search-and-filter.md b/docs/search-and-filter.md index 4ac3c2c..486a0fc 100644 --- a/docs/search-and-filter.md +++ b/docs/search-and-filter.md @@ -22,14 +22,18 @@ the fields that matched, so you can tell why an asset is in the list. ### What it matches -Each space-separated term is matched on its own, and an object matches when -every term matches somewhere. A term is checked against, in order: +Each space-separated term is matched on its own, and an object matches when any +one of its terms matches somewhere. A term is checked against, in order: 1. the asset name, 2. the type name, 3. the asset GUID, as an exact match, 4. string fields on the asset and on nested plain objects. +Field matching is checked only when a term has not already matched the name, type +name, or GUID, and it stops at the first field that matches, so a result names +one matching field per term rather than every match. + Field matching is case-insensitive. It skips primitives, `Vector2`, `Vector3`, `Vector4`, `Quaternion`, `Color`, `Rect`, and `Bounds`, and it does not follow references to other Unity objects or assets. Cycles in nested plain objects are @@ -102,8 +106,8 @@ the whole section refuses to collapse while any label is configured. The filter narrows the list you see. It does not change what is on disk, and it does not change the order of the objects it keeps. -When a filter hides rows, a line below the filter reports how many, in red when -fewer than 20 are hidden and in yellow otherwise. When nothing is hidden, the +When a filter hides rows, a line below the filter reports how many: fewer than 20 +hidden is highlighted in yellow, 20 or more in red. When nothing is hidden, the line disappears. The filter is stored per type and follows the persistence setting described in