Mark overrides in gold instead of bold, and up the hierarchy
An inspector label whose value deviates from its base was drawn bold, which is easy to miss next to the regular labels and says nothing in the hierarchy, where a folded subtree could hide a change entirely. The colour now lives as a theme token (`override`, #f2c84b) used by both the inspector labels and the hierarchy rows. Whether a row deviates is decided by `src/core/properties/deviation.ts`: `objectDeviates` compares an object against what it follows -- the container template for content, the asset otherwise -- and `deviationMarks` carries the mark up to every ancestor, so a folded subtree still shows that something inside was changed. `deviationMarks` takes the stored holder, not the resolved one: in the resolved view container content looks like an ordinary object and `instanceTargetOf` would compare against the asset, marking every instance. Covered by 3 unit tests and 5 e2e checks, including one that strips an object's own overrides to prove the row stays gold for a deviating child and restores them byte for byte. Full suite remote on the RTX 4070: 275 checks, all passed.
This commit is contained in:
1 parent
37e1450fca
commit
b87fcdfbad
13 files changed
+197
-14
No files matched your search
+17
-1
@@ -1,4 +1,4 @@
|
||||
import { type Application, type Container, Rectangle, Text } from 'pixi.js';
|
||||
import { type Application, Color, type ColorSource, type Container, Rectangle, Text } from 'pixi.js';
|
||||
import { resolveObjects } from '../core/model/instance';
|
||||
import { findObjectHolder } from '../core/model/Project';
|
||||
import { Button, ui, Widget } from '../ui';
|
||||
@@ -34,6 +34,20 @@ export function installDebugHook(app: Application, state: EditorState, editor: E
|
||||
const p = node.getGlobalPosition();
|
||||
return { x: p.x + node.width / 2, y: p.y + node.height / 2 };
|
||||
};
|
||||
/** Fill colour of a visible text, as `#rrggbb` – what tells an override label from a plain one. */
|
||||
const textColor = (text: string): string | null => {
|
||||
let found: string | null = null;
|
||||
const walk = (node: Container): void => {
|
||||
if (found !== null || !node.visible) return;
|
||||
if (node instanceof Text && node.text === text) {
|
||||
found = new Color(node.style.fill as ColorSource).toHex();
|
||||
return;
|
||||
}
|
||||
for (const child of node.children) walk(child);
|
||||
};
|
||||
walk(app.stage);
|
||||
return found;
|
||||
};
|
||||
/**
|
||||
* The visible text of the overlay layer, in drawing order – everything an open popup shows.
|
||||
* Menus hide filtered-out entries, so this is also what a search box currently leaves.
|
||||
@@ -118,6 +132,7 @@ export function installDebugHook(app: Application, state: EditorState, editor: E
|
||||
objectsOf,
|
||||
sceneObjects: () => (state.activeScene ? objectsOf(state.activeScene.id) : []),
|
||||
locate,
|
||||
textColor,
|
||||
overlayTexts,
|
||||
overlayRect,
|
||||
textOverflows,
|
||||
@@ -159,6 +174,7 @@ export function installDebugHook(app: Application, state: EditorState, editor: E
|
||||
/** Rows the hierarchy shows – collapsed subtrees are not in here. */
|
||||
hierarchyOrder: () => hierarchy.rowOrder(),
|
||||
hierarchyChevron: (id: string) => hierarchy.chevronPosition(id),
|
||||
hierarchyRowColor: (id: string) => hierarchy.rowColor(id),
|
||||
hierarchyExpandAll: () => hierarchy.expandAll(),
|
||||
hierarchyCollapseAll: () => hierarchy.collapseAll(),
|
||||
/** The colour actually on screen at a point, as `#rrggbb` – what was drawn, not what the model says. */
|
||||
|
||||
@@ -46,7 +46,7 @@ export interface FieldRow {
|
||||
/** Push a model value into the widget (no events fired). */
|
||||
setValue(value: PropertyValue): void;
|
||||
/**
|
||||
* Highlight the row as deviating from its base value (an override on scene objects, a
|
||||
* Colour the label as deviating from its base value (an override on scene objects, a
|
||||
* non-default value on assets) and re-evaluate which actions currently apply.
|
||||
*/
|
||||
setModified(modified: boolean): void;
|
||||
@@ -154,7 +154,7 @@ export function createFieldRow(
|
||||
row,
|
||||
setValue: field.setValue,
|
||||
setModified(modified) {
|
||||
label.setStyle(modified ? 'bodyBold' : 'muted');
|
||||
label.setStyle(modified ? 'override' : 'muted');
|
||||
refreshActions();
|
||||
},
|
||||
};
|
||||
|
||||
@@ -474,7 +474,7 @@ function parentRow(host: InspectorHost, holder: ObjectHolder, object: SceneObjec
|
||||
}
|
||||
|
||||
/**
|
||||
* Bold marks exactly what the ↺/↑ buttons offer: for content of a placed container a deviation
|
||||
* Gold marks exactly what the ↺/↑ buttons offer: for content of a placed container a deviation
|
||||
* from the template (position per axis, overrides), for any other object one from the asset. The
|
||||
* editor offset has one base only – no offset at all.
|
||||
*/
|
||||
|
||||
@@ -6,6 +6,7 @@ import { hasColorSwatch } from '../../core/model/AssetTypes';
|
||||
import { ancestorIdsOf, childIdsOf, findObject, visibleTreeOrder } from '../../core/model/hierarchy';
|
||||
import { isLiveInstance, isTemplateContent, resolvedHolder } from '../../core/model/instance';
|
||||
import { findAsset, type ObjectHolder } from '../../core/model/Project';
|
||||
import { deviationMarks } from '../../core/properties/deviation';
|
||||
import { resolveSceneObjectProps } from '../../core/properties/resolve';
|
||||
import { type DropSpot, ListView, Menu, Panel, ScrollContainer } from '../../ui';
|
||||
import { addCollider, createContainer, removeObjects } from '../actions';
|
||||
@@ -18,6 +19,9 @@ import { type EditorState, objectSelection } from '../state';
|
||||
* siblings, or onto the empty space below the rows to move it to the top level. The object keeps
|
||||
* its world position every time.
|
||||
*
|
||||
* A row's name turns gold when the object deviates from what it follows – its container template
|
||||
* or its asset – or when anything below it does, so a folded subtree still shows it.
|
||||
*
|
||||
* Rows with children fold: the chevron (or ←/→, or a double-click on the row) toggles them.
|
||||
* Everything starts collapsed – which ids are open is view state kept here, never part of the
|
||||
* project, and selecting an object always unfolds the way down to it.
|
||||
@@ -128,6 +132,11 @@ export class HierarchyPanel extends Panel {
|
||||
return holder ? visibleTreeOrder(holder, this.expanded).map((e) => e.object.id) : [];
|
||||
}
|
||||
|
||||
/** Label colour of a row as `#rrggbb` – gold means it or something below it deviates (test hook). */
|
||||
rowColor(id: string): string | null {
|
||||
return this.list.rowColor(id);
|
||||
}
|
||||
|
||||
/** Global centre of a row's chevron, so tests can click the real hit area (test hook). */
|
||||
chevronPosition(id: string): { x: number; y: number } | null {
|
||||
return this.list.chevronGlobal(id);
|
||||
@@ -226,6 +235,10 @@ export class HierarchyPanel extends Panel {
|
||||
this.shownHolderId = holder?.id ?? '';
|
||||
const kind = holder?.type === 'container' ? 'Container' : 'Scene';
|
||||
this.setTitle(holder ? `${kind}: ${holder.name}` : 'Scene objects');
|
||||
// From the stored holder, not the resolved one: what an object follows depends on where it
|
||||
// lives, and in a resolved view container content looks stored.
|
||||
const source = this.source();
|
||||
const marks = source ? deviationMarks(this.state.project, source) : new Set<string>();
|
||||
this.list.setItems(
|
||||
(holder ? visibleTreeOrder(holder, this.expanded) : []).map(({ object, depth, hasChildren }) => {
|
||||
const asset = findAsset(this.state.project, object.assetId);
|
||||
@@ -237,6 +250,7 @@ export class HierarchyPanel extends Panel {
|
||||
depth,
|
||||
swatch: asset && hasColorSwatch(asset.type) ? Number(props.color ?? 0) : undefined,
|
||||
expanded: hasChildren ? this.expanded.has(object.id) : undefined,
|
||||
overridden: marks.has(object.id),
|
||||
};
|
||||
}),
|
||||
);
|
||||
|
||||
Reference in new issue
Block a user