feat(ui): wip layer transparency

This commit is contained in:
psychedelicious 2024-04-12 17:52:15 +10:00 committed by Kent Keirsey
parent 0a42d7d510
commit b9d0da44eb
4 changed files with 55 additions and 8 deletions

View File

@ -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') {

View File

@ -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>
);
};

View File

@ -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) => (

View File

@ -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;