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 layer = useLayer(id);
|
||||
const selectedLayer = useAppSelector((s) => s.regionalPrompts.selectedLayer);
|
||||
const promptLayerOpacity = useAppSelector((s) => s.regionalPrompts.promptLayerOpacity);
|
||||
const tool = useAppSelector((s) => s.regionalPrompts.tool);
|
||||
const layerRef = useRef<KonvaLayerType>(null);
|
||||
const groupRef = useRef<KonvaGroupType>(null);
|
||||
@ -43,15 +44,10 @@ export const LayerComponent: React.FC<Props> = ({ 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(
|
||||
(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, id]
|
||||
@ -66,6 +62,7 @@ export const LayerComponent: React.FC<Props> = ({ id }) => {
|
||||
if (!cursorPos) {
|
||||
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()) {
|
||||
return this.getAbsolutePosition();
|
||||
}
|
||||
@ -75,15 +72,34 @@ export const LayerComponent: React.FC<Props> = ({ id }) => {
|
||||
|
||||
useEffect(() => {
|
||||
if (!layerRef.current || tool !== 'move') {
|
||||
// This effectively makes the bbox only calculate as the tool is changed to 'move'.
|
||||
return;
|
||||
}
|
||||
if (layer.objects.length === 0) {
|
||||
// Reset the bbox when we have no objects.
|
||||
onChangeBbox(null);
|
||||
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));
|
||||
}, [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) {
|
||||
return null;
|
||||
}
|
||||
@ -94,7 +110,6 @@ export const LayerComponent: React.FC<Props> = ({ id }) => {
|
||||
ref={layerRef}
|
||||
id={layer.id}
|
||||
name={REGIONAL_PROMPT_LAYER_NAME}
|
||||
onDragEnd={onDragEnd}
|
||||
onDragMove={onDragMove}
|
||||
dragBoundFunc={dragBoundFunc}
|
||||
draggable
|
||||
@ -104,6 +119,7 @@ export const LayerComponent: React.FC<Props> = ({ id }) => {
|
||||
name={REGIONAL_PROMPT_LAYER_OBJECT_GROUP_NAME}
|
||||
ref={groupRef}
|
||||
listening={false}
|
||||
opacity={promptLayerOpacity}
|
||||
>
|
||||
{layer.objects.map((obj) => {
|
||||
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 { BrushSize } from 'features/regionalPrompts/components/BrushSize';
|
||||
import { LayerListItem } from 'features/regionalPrompts/components/LayerListItem';
|
||||
import { PromptLayerOpacity } from 'features/regionalPrompts/components/PromptLayerOpacity';
|
||||
import { RegionalPromptsStage } from 'features/regionalPrompts/components/RegionalPromptsStage';
|
||||
import { ToolChooser } from 'features/regionalPrompts/components/ToolChooser';
|
||||
import { selectRegionalPromptsSlice } from 'features/regionalPrompts/store/regionalPromptsSlice';
|
||||
@ -27,6 +28,7 @@ export const RegionalPromptsEditor = () => {
|
||||
<Button onClick={debugBlobs}>DEBUG LAYERS</Button>
|
||||
<AddLayerButton />
|
||||
<BrushSize />
|
||||
<PromptLayerOpacity />
|
||||
<ImageSizeLinear />
|
||||
<ToolChooser />
|
||||
{layerIdsReversed.map((id) => (
|
||||
|
@ -64,6 +64,7 @@ type RegionalPromptsState = {
|
||||
selectedLayer: string | null;
|
||||
layers: PromptRegionLayer[];
|
||||
brushSize: number;
|
||||
promptLayerOpacity: number;
|
||||
};
|
||||
|
||||
const initialRegionalPromptsState: RegionalPromptsState = {
|
||||
@ -72,6 +73,7 @@ const initialRegionalPromptsState: RegionalPromptsState = {
|
||||
selectedLayer: null,
|
||||
brushSize: 40,
|
||||
layers: [],
|
||||
promptLayerOpacity: 0.5,
|
||||
};
|
||||
|
||||
const isLine = (obj: LayerObject): obj is LineObject => obj.kind === 'line';
|
||||
@ -196,6 +198,9 @@ export const regionalPromptsSlice = createSlice({
|
||||
toolChanged: (state, action: PayloadAction<Tool>) => {
|
||||
state.tool = action.payload;
|
||||
},
|
||||
promptLayerOpacityChanged: (state, action: PayloadAction<number>) => {
|
||||
state.promptLayerOpacity = action.payload;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@ -245,6 +250,7 @@ export const {
|
||||
toolChanged,
|
||||
layerTranslated,
|
||||
layerBboxChanged,
|
||||
promptLayerOpacityChanged,
|
||||
} = regionalPromptsSlice.actions;
|
||||
|
||||
export const selectRegionalPromptsSlice = (state: RootState) => state.regionalPrompts;
|
||||
|
Loading…
Reference in New Issue
Block a user