Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ Each layer row has a dot on the left that switches the layer on or off for gener

### Settings live in the Properties pane

Each layer's settings, such as a regional prompt or a control layer's model, are in the **Properties** tab at the bottom of the Layers panel. Select the layer and click **Properties**: the heading at the top of the tab confirms which layer you are editing, for example *Layer: Regional Guidance 1*. The settings are in its **Layer** section. The Layer section comes after the settings of the active tool, so **you may need to scroll the Properties pane down to see it**. Dragging the divider above the Properties pane upward, or collapsing the color picker, gives it more room.
Each layer's settings, such as a regional prompt or a control layer's model, are in the **Properties** tab at the bottom of the Layers panel. Select the layer and click **Properties**: the heading at the top of the tab confirms which layer you are editing, for example *Layer: Regional Guidance 1*. The settings are in its **Layer** section. The Layer section comes after the settings of the active tool, so **you may need to scroll the Properties pane down to see it**. Right-clicking the layer and choosing **Layer properties** opens the tab and scrolls to that section for you. Dragging the divider above the Properties pane upward, or collapsing the color picker, gives it more room.

### The generation frame

Expand Down Expand Up @@ -120,7 +120,7 @@ Regional guidance gives one area of the image its own prompt. For example, keep

2. **Paint the region** with the **Brush**, or draw it with the **Shape** tool. It shows as a tinted overlay.

3. **Enter the region's prompt.** With the layer selected, open **Properties** and scroll down to its **Layer** section. Type in **Positive prompt**, and optionally **Negative prompt**. The prompt is saved when you click away from the field.
3. **Enter the region's prompt.** Right-click the layer and choose **Layer properties**: the **Properties** tab opens at its **Layer** section. Type in **Positive prompt**, and optionally **Negative prompt**. The prompt is saved when you click away from the field.

4. **Press Invoke.** The main prompt still describes the whole image; the regional prompt applies inside the region.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ Right-click a layer, or select it and press **Shift+F10**, for its menu. Right-c
The icons at the top **Arrange** the layer (move it to the front, forward, backward or to the back), **Duplicate layer** and **Group layers**. Below them:

* **For every layer:**
* **Layer properties** opens the **Properties** tab and scrolls it to the layer's **Layer** section.
* **Rename layer** and **Fit to frame** (scale the layer to fit inside the generation frame).
* **Merge down** — combines the layer with the one below. Each layer's adjustments are baked into the merged pixels; undo restores both layers with their adjustments.
* **Copy to** — including **Copy layer to clipboard**, and copies to other layer types.
Expand Down Expand Up @@ -117,7 +118,7 @@ Below the heading, the tab shows up to three sections, in this order:
3. **Layer** — the settings of the selected layer.

:::tip[Scroll down for the layer's settings]
The **Layer** section comes after the tool options, so on most screens it starts below the visible part of the pane. Scroll the Properties pane down to reach it, or drag the divider above the pane upward to make it taller.
The **Layer** section comes after the tool options, so on most screens it starts below the visible part of the pane. Right-click the layer and choose **Layer properties** to jump to it, scroll the Properties pane down, or drag the divider above the pane upward to make it taller.
:::

The Layer section is dimmed while Filter or Select Object is running.
Expand Down
1 change: 1 addition & 0 deletions invokeai/frontend/webv2/public/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -4736,6 +4736,7 @@
"operationFailed": "The canvas could not apply this change, so nothing was changed.",
"overBudget": "There is not enough canvas memory to make this change undoable, so nothing was changed.",
"stale": "The layer changed before this action finished, so nothing was changed. Try again.",
"properties": "Layer properties",
"rasterize": "Rasterize layer",
"removeQuickSwitchBookmark": "Remove quick switch bookmark",
"rename": "Rename layer",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ import {
needsBakedControlPixels,
} from './layerOps';
import { LayerSaveToBoardSubmenu } from './LayerSaveToBoardSubmenu';
import { useRevealLayerProperties } from './revealLayerProperties';
import { revealLayerSection, useRevealLayerProperties } from './revealLayerProperties';
import { RunLayerWorkflowDialog, useLayerWorkflowAvailability } from './RunLayerWorkflowDialog';
import { useSelectedModelBase } from './useSelectedModelBase';

Expand Down Expand Up @@ -744,6 +744,7 @@ const LayerMenu = ({
const handleOpenProperties = useCallback(() => {
revealProperties(layer.id);
focusRegion('right', 'layers');
revealLayerSection();
Comment on lines 745 to +750

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] Layer properties can show the child’s settings instead.

Select a regional reference image, mask modifier, or raster adjustment; then right-click its parent and choose Layer properties. The parent is already selected, and this action never clears child selection. The pane therefore retains the child editor—for example, reference-image controls instead of regional prompts. Clear child selection in this action and test that transition.

}, [focusRegion, layer.id, revealProperties]);

const handleBooleanRaster = useCallback(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -487,6 +487,7 @@ describe('getLayerContextActions', () => {
'duplicate',
'group',
'remove-from-group',
'open-properties',
'rename',
'transform',
'fit-to-bbox',
Expand Down Expand Up @@ -941,6 +942,21 @@ describe('getLayerContextActions', () => {
});
});

describe('open-properties', () => {
it('offers Layer properties on every layer type and opens the Properties pane through its effect', () => {
for (const type of ['raster', 'control', 'inpaint_mask', 'regional_guidance'] as const) {
const action = byId(getLayerContextActions(makeState(makeLayer(type))), 'open-properties');
expect(action.isDisabled, type).toBe(false);
}
expect(getEnglishTranslation('widgets.layers.actions.properties')).toBe('Layer properties');

const effects = makeEffects();
getLayerContextActionDefinition('open-properties').handler(makeRuntimeContext(rasterLayer, { effects }));

expect(Object.values(effects).filter((effect) => effect.mock.calls.length > 0)).toEqual([effects.openProperties]);
});
});

describe('toggle-visibility', () => {
it('hides and shows the layer itself, the one visibility that generation also follows', () => {
const shown = getLayerContextActions(makeState(rasterLayer));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ import {
MergeIcon,
PencilIcon,
SaveIcon,
Settings2Icon,
ContrastIcon,
RainbowIcon,
ScanSearchIcon,
Expand Down Expand Up @@ -90,6 +91,7 @@ export type LayerContextActionId =
| 'duplicate'
| 'group'
| 'remove-from-group'
| 'open-properties'
| 'rename'
| 'transform'
| 'fit-to-bbox'
Expand Down Expand Up @@ -518,6 +520,18 @@ export const LAYER_CONTEXT_ACTION_DEFINITIONS: readonly LayerContextActionDefini
section: 'quick',
supportedLayerTypes: ALL_LAYER_TYPES,
},
{
defaultLabel: 'Layer properties',
handler: ({ effects }) => effects.openProperties(),
icon: Settings2Icon,
id: 'open-properties',
isEnabled: isOperationFree,
isVisible: alwaysVisible,
labelKey: 'widgets.layers.actions.properties',
order: 5,
section: 'primary',
supportedLayerTypes: ALL_LAYER_TYPES,
},
{
defaultLabel: 'Transform',
handler: ({ effects }) => effects.transform(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ describe('getLayerContextMenuLayout', () => {
{
id: 'primary',
items: [
'open-properties',
'transform',
'rename',
'fit-to-bbox',
Expand Down Expand Up @@ -126,12 +127,12 @@ describe('getLayerContextMenuLayout', () => {

expect(summarize(inpaint)[1]).toEqual({
id: 'primary',
items: ['rename', 'fit-to-bbox', 'add-noise', 'add-denoise-limit', 'extract-masked-area'],
items: ['open-properties', 'rename', 'fit-to-bbox', 'add-noise', 'add-denoise-limit', 'extract-masked-area'],
presentation: 'list',
});
expect(summarize(regional)[1]).toEqual({
id: 'primary',
items: ['rename', 'fit-to-bbox', 'regional-auto-negative', 'add-reference-image'],
items: ['open-properties', 'rename', 'fit-to-bbox', 'regional-auto-negative', 'add-reference-image'],
presentation: 'list',
});
});
Expand All @@ -144,7 +145,15 @@ describe('getLayerContextMenuLayout', () => {

expect(summarize(control)[1]).toEqual({
id: 'primary',
items: ['transform', 'rename', 'fit-to-bbox', 'select-object', 'run-workflow', 'control-transparency-effect'],
items: [
'open-properties',
'transform',
'rename',
'fit-to-bbox',
'select-object',
'run-workflow',
'control-transparency-effect',
],
presentation: 'list',
});
expect(summarize(control)[2]).toEqual({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ vi.mock('@workbench/canvas-operations/api', async (importOriginal) => ({

import { SegmentTabs, segmentTabsPanelId, segmentTabsTabId } from '@platform/ui/SegmentTabs';
import { clearMaskTintTarget } from '@workbench/widgets/canvas/color-system/maskTintTarget';
import { revealLayerSection } from '@workbench/widgets/layers/revealLayerProperties';

import type { LayerEditorPaneLayout } from './editorPaneLayout';

Expand Down Expand Up @@ -271,7 +272,11 @@ const GRADIENT_SOURCE = {
const mount = async (
View: typeof PropertiesPane,
selection: Selection = 'none',
{ extraNodes = [], width = 450 }: { extraNodes?: Parameters<typeof stacksFrom>[0]; width?: number } = {}
{
extraNodes = [],
height = 600,
width = 450,
}: { extraNodes?: Parameters<typeof stacksFrom>[0]; height?: number; width?: number } = {}
) => {
const base = { ...createInitialWorkbenchState().projects[0]!, id: 'p' };
const nodes =
Expand Down Expand Up @@ -304,7 +309,7 @@ const mount = async (
type: 'replaceCanvasDocument',
});
host = document.createElement('div');
host.style.cssText = `width:${width}px;height:600px;`;
host.style.cssText = `width:${width}px;height:${height}px;`;
document.body.append(host);
root = createRoot(host);
registry = createEngineRegistry({ gracePeriodMs: 0 });
Expand Down Expand Up @@ -656,6 +661,22 @@ describe('Control layer properties', () => {
});
});

describe('Layer section reveal', () => {
it('scrolls the Layer section below the tool settings into view', async () => {
await mount(PropertiesPane, 'control', { height: 240 });
await act(() => engine!.tools.setTool('brush'));
await settle();
const section = host!.querySelector<HTMLElement>('[data-properties-section="layer"]')!;
const inView = () => section.getBoundingClientRect().top < host!.getBoundingClientRect().bottom;
expect(inView()).toBe(false);

revealLayerSection();

await expect.poll(inView).toBe(true);
await expect.element(page.getByRole('combobox', { exact: true, name: 'Type' })).toBeInViewport();
});
});

describe('Tool property scrubbers', () => {
beforeEach(() => resetPropertyGroupCollapse());
afterEach(() => resetPropertyGroupCollapse());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ export const LayerSection = ({ disabled }: { disabled: boolean }) => {
return (
<PropertiesSection
disabled={disabled}
sectionId="layer"
subtitle={child ? child.subtitle : (node?.name ?? t('widgets.transform.noSelection'))}
title={t('widgets.properties.sections.layer')}
>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,16 @@ export const PropertiesSection = ({
children,
disabled = false,
ref,
sectionId,
subtitle,
title,
}: {
children: ReactNode;
/** The surface is busy elsewhere (staging, generation, an operation): the rows stay in place but cannot act. */
disabled?: boolean;
ref?: Ref<HTMLDivElement>;
/** Marks the section for reveals that scroll it into view. */
sectionId?: 'layer';
subtitle?: string;
title: string;
}) => {
Expand All @@ -24,6 +27,7 @@ export const PropertiesSection = ({
aria-label={title}
borderBottomWidth="1px"
borderColor="border.subtle"
data-properties-section={sectionId}
gap="2"
inert={disabled || undefined}
opacity={disabled ? 0.5 : 1}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,27 @@ export const useRevealLayerProperties = (): ((layerId: string) => void) => {
[registry, widgets]
);
};

/** How long a reveal waits for the Properties pane, just opened, to render the Layer section. */
const REVEAL_WAIT_MS = 1500;

/**
* Scrolls the Properties pane to its Layer section, which sits below the active tool's settings and often starts out of
* view. The caller opens the pane first; a reveal whose pane never renders gives up quietly.
*/
export const revealLayerSection = (): void => {
const deadline = performance.now() + REVEAL_WAIT_MS;

const attempt = () => {
const section = [...document.querySelectorAll<HTMLElement>('[data-properties-section="layer"]')].find(
(element) => element.getClientRects().length > 0
);
if (section) {
section.scrollIntoView({ block: 'nearest' });

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] The shortcut can leave the section’s initial controls offscreen.

After scrolling down within a tall Layer section, choosing Layer properties may do nothing: block: 'nearest' preserves the position when the section spans the viewport. Its heading and prompts remain above view. Align the section’s start or target its heading, and test repeated navigation. I confirmed this behavior in an isolated Chromium reproduction.

} else if (performance.now() < deadline) {
requestAnimationFrame(attempt);
}
};

requestAnimationFrame(attempt);
};