mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
feat(ui): wip layer transparency
This commit is contained in:
parent
0a42d7d510
commit
b9d0da44eb
@ -32,6 +32,7 @@ export const LayerComponent: React.FC<Props> = ({ id }) => {
|
|||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const layer = useLayer(id);
|
const layer = useLayer(id);
|
||||||
const selectedLayer = useAppSelector((s) => s.regionalPrompts.selectedLayer);
|
const selectedLayer = useAppSelector((s) => s.regionalPrompts.selectedLayer);
|
||||||
|
const promptLayerOpacity = useAppSelector((s) => s.regionalPrompts.promptLayerOpacity);
|
||||||
const tool = useAppSelector((s) => s.regionalPrompts.tool);
|
const tool = useAppSelector((s) => s.regionalPrompts.tool);
|
||||||
const layerRef = useRef<KonvaLayerType>(null);
|
const layerRef = useRef<KonvaLayerType>(null);
|
||||||
const groupRef = useRef<KonvaGroupType>(null);
|
const groupRef = useRef<KonvaGroupType>(null);
|
||||||
@ -43,15 +44,10 @@ export const LayerComponent: React.FC<Props> = ({ id }) => {
|
|||||||
[dispatch, layer.id]
|
[dispatch, layer.id]
|
||||||
);
|
);
|
||||||
|
|
||||||
const onDragEnd = useCallback(
|
|
||||||
(e: KonvaEventObject<DragEvent>) => {
|
|
||||||
dispatch(layerTranslated({ layerId: id, x: e.target.x(), y: e.target.y() }));
|
|
||||||
},
|
|
||||||
[dispatch, id]
|
|
||||||
);
|
|
||||||
|
|
||||||
const onDragMove = useCallback(
|
const onDragMove = useCallback(
|
||||||
(e: KonvaEventObject<DragEvent>) => {
|
(e: KonvaEventObject<DragEvent>) => {
|
||||||
|
// We must track the layer's coordinates to accurately position objects. For example, when drawing a line, the
|
||||||
|
// mouse events are handled by the stage, but the line is drawn on the layer.
|
||||||
dispatch(layerTranslated({ layerId: id, x: e.target.x(), y: e.target.y() }));
|
dispatch(layerTranslated({ layerId: id, x: e.target.x(), y: e.target.y() }));
|
||||||
},
|
},
|
||||||
[dispatch, id]
|
[dispatch, id]
|
||||||
@ -66,6 +62,7 @@ export const LayerComponent: React.FC<Props> = ({ id }) => {
|
|||||||
if (!cursorPos) {
|
if (!cursorPos) {
|
||||||
return this.getAbsolutePosition();
|
return this.getAbsolutePosition();
|
||||||
}
|
}
|
||||||
|
// This prevents the user from dragging the object out of the stage.
|
||||||
if (cursorPos.x < 0 || cursorPos.x > stage.width() || cursorPos.y < 0 || cursorPos.y > stage.height()) {
|
if (cursorPos.x < 0 || cursorPos.x > stage.width() || cursorPos.y < 0 || cursorPos.y > stage.height()) {
|
||||||
return this.getAbsolutePosition();
|
return this.getAbsolutePosition();
|
||||||
}
|
}
|
||||||
@ -75,15 +72,34 @@ export const LayerComponent: React.FC<Props> = ({ id }) => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!layerRef.current || tool !== 'move') {
|
if (!layerRef.current || tool !== 'move') {
|
||||||
|
// This effectively makes the bbox only calculate as the tool is changed to 'move'.
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (layer.objects.length === 0) {
|
if (layer.objects.length === 0) {
|
||||||
|
// Reset the bbox when we have no objects.
|
||||||
onChangeBbox(null);
|
onChangeBbox(null);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
// We could debounce this, remove the early return when the tool isn't move, and always show the bbox. For now,
|
||||||
|
// it is only shown when we are using the move tool.
|
||||||
onChangeBbox(getKonvaLayerBbox(layerRef.current, selectPromptLayerObjectGroup));
|
onChangeBbox(getKonvaLayerBbox(layerRef.current, selectPromptLayerObjectGroup));
|
||||||
}, [tool, layer.objects, onChangeBbox]);
|
}, [tool, layer.objects, onChangeBbox]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!groupRef.current) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Caching is not allowed for "empty" nodes. We must draw here to render the group instead. This is required
|
||||||
|
// to clear the layer when the layer is reset and on first render, when the layer has no objects.
|
||||||
|
if (layer.objects.length === 0) {
|
||||||
|
groupRef.current.draw();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Caching the group allows its opacity to apply to all shapes at once. We should cache only when the layer's
|
||||||
|
// objects or attributes with a visual effect (e.g. color) change.
|
||||||
|
groupRef.current.cache();
|
||||||
|
}, [layer.objects, layer.color, layer.isVisible]);
|
||||||
|
|
||||||
if (!layer.isVisible) {
|
if (!layer.isVisible) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -94,7 +110,6 @@ export const LayerComponent: React.FC<Props> = ({ id }) => {
|
|||||||
ref={layerRef}
|
ref={layerRef}
|
||||||
id={layer.id}
|
id={layer.id}
|
||||||
name={REGIONAL_PROMPT_LAYER_NAME}
|
name={REGIONAL_PROMPT_LAYER_NAME}
|
||||||
onDragEnd={onDragEnd}
|
|
||||||
onDragMove={onDragMove}
|
onDragMove={onDragMove}
|
||||||
dragBoundFunc={dragBoundFunc}
|
dragBoundFunc={dragBoundFunc}
|
||||||
draggable
|
draggable
|
||||||
@ -104,6 +119,7 @@ export const LayerComponent: React.FC<Props> = ({ id }) => {
|
|||||||
name={REGIONAL_PROMPT_LAYER_OBJECT_GROUP_NAME}
|
name={REGIONAL_PROMPT_LAYER_OBJECT_GROUP_NAME}
|
||||||
ref={groupRef}
|
ref={groupRef}
|
||||||
listening={false}
|
listening={false}
|
||||||
|
opacity={promptLayerOpacity}
|
||||||
>
|
>
|
||||||
{layer.objects.map((obj) => {
|
{layer.objects.map((obj) => {
|
||||||
if (obj.kind === 'line') {
|
if (obj.kind === 'line') {
|
||||||
|
@ -0,0 +1,23 @@
|
|||||||
|
import { CompositeSlider, FormControl, FormLabel } from '@invoke-ai/ui-library';
|
||||||
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
|
import { promptLayerOpacityChanged } from 'features/regionalPrompts/store/regionalPromptsSlice';
|
||||||
|
import { useCallback } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
export const PromptLayerOpacity = () => {
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const promptLayerOpacity = useAppSelector((s) => s.regionalPrompts.promptLayerOpacity);
|
||||||
|
const onChange = useCallback(
|
||||||
|
(v: number) => {
|
||||||
|
dispatch(promptLayerOpacityChanged(v));
|
||||||
|
},
|
||||||
|
[dispatch]
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<FormControl orientation="vertical">
|
||||||
|
<FormLabel>{t('regionalPrompts.brushSize')}</FormLabel>
|
||||||
|
<CompositeSlider min={0.25} max={1} step={0.01} value={promptLayerOpacity} onChange={onChange} />
|
||||||
|
</FormControl>
|
||||||
|
);
|
||||||
|
};
|
@ -5,6 +5,7 @@ import { useAppSelector } from 'app/store/storeHooks';
|
|||||||
import { AddLayerButton } from 'features/regionalPrompts/components/AddLayerButton';
|
import { AddLayerButton } from 'features/regionalPrompts/components/AddLayerButton';
|
||||||
import { BrushSize } from 'features/regionalPrompts/components/BrushSize';
|
import { BrushSize } from 'features/regionalPrompts/components/BrushSize';
|
||||||
import { LayerListItem } from 'features/regionalPrompts/components/LayerListItem';
|
import { LayerListItem } from 'features/regionalPrompts/components/LayerListItem';
|
||||||
|
import { PromptLayerOpacity } from 'features/regionalPrompts/components/PromptLayerOpacity';
|
||||||
import { RegionalPromptsStage } from 'features/regionalPrompts/components/RegionalPromptsStage';
|
import { RegionalPromptsStage } from 'features/regionalPrompts/components/RegionalPromptsStage';
|
||||||
import { ToolChooser } from 'features/regionalPrompts/components/ToolChooser';
|
import { ToolChooser } from 'features/regionalPrompts/components/ToolChooser';
|
||||||
import { selectRegionalPromptsSlice } from 'features/regionalPrompts/store/regionalPromptsSlice';
|
import { selectRegionalPromptsSlice } from 'features/regionalPrompts/store/regionalPromptsSlice';
|
||||||
@ -27,6 +28,7 @@ export const RegionalPromptsEditor = () => {
|
|||||||
<Button onClick={debugBlobs}>DEBUG LAYERS</Button>
|
<Button onClick={debugBlobs}>DEBUG LAYERS</Button>
|
||||||
<AddLayerButton />
|
<AddLayerButton />
|
||||||
<BrushSize />
|
<BrushSize />
|
||||||
|
<PromptLayerOpacity />
|
||||||
<ImageSizeLinear />
|
<ImageSizeLinear />
|
||||||
<ToolChooser />
|
<ToolChooser />
|
||||||
{layerIdsReversed.map((id) => (
|
{layerIdsReversed.map((id) => (
|
||||||
|
@ -64,6 +64,7 @@ type RegionalPromptsState = {
|
|||||||
selectedLayer: string | null;
|
selectedLayer: string | null;
|
||||||
layers: PromptRegionLayer[];
|
layers: PromptRegionLayer[];
|
||||||
brushSize: number;
|
brushSize: number;
|
||||||
|
promptLayerOpacity: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
const initialRegionalPromptsState: RegionalPromptsState = {
|
const initialRegionalPromptsState: RegionalPromptsState = {
|
||||||
@ -72,6 +73,7 @@ const initialRegionalPromptsState: RegionalPromptsState = {
|
|||||||
selectedLayer: null,
|
selectedLayer: null,
|
||||||
brushSize: 40,
|
brushSize: 40,
|
||||||
layers: [],
|
layers: [],
|
||||||
|
promptLayerOpacity: 0.5,
|
||||||
};
|
};
|
||||||
|
|
||||||
const isLine = (obj: LayerObject): obj is LineObject => obj.kind === 'line';
|
const isLine = (obj: LayerObject): obj is LineObject => obj.kind === 'line';
|
||||||
@ -196,6 +198,9 @@ export const regionalPromptsSlice = createSlice({
|
|||||||
toolChanged: (state, action: PayloadAction<Tool>) => {
|
toolChanged: (state, action: PayloadAction<Tool>) => {
|
||||||
state.tool = action.payload;
|
state.tool = action.payload;
|
||||||
},
|
},
|
||||||
|
promptLayerOpacityChanged: (state, action: PayloadAction<number>) => {
|
||||||
|
state.promptLayerOpacity = action.payload;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -245,6 +250,7 @@ export const {
|
|||||||
toolChanged,
|
toolChanged,
|
||||||
layerTranslated,
|
layerTranslated,
|
||||||
layerBboxChanged,
|
layerBboxChanged,
|
||||||
|
promptLayerOpacityChanged,
|
||||||
} = regionalPromptsSlice.actions;
|
} = regionalPromptsSlice.actions;
|
||||||
|
|
||||||
export const selectRegionalPromptsSlice = (state: RootState) => state.regionalPrompts;
|
export const selectRegionalPromptsSlice = (state: RootState) => state.regionalPrompts;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user