mirror of
https://github.com/invoke-ai/InvokeAI.git
synced 2026-04-23 03:00:31 -04:00
feat(ui): simpler strategy to conditionally render slider brush width
This commit is contained in:
@@ -8,7 +8,6 @@ import {
|
||||
PopoverTrigger,
|
||||
Tooltip,
|
||||
} from '@invoke-ai/ui-library';
|
||||
import useResizeObserver from '@react-hook/resize-observer';
|
||||
import { createSelector } from '@reduxjs/toolkit';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import RgbaColorPicker from 'common/components/ColorPicker/RgbaColorPicker';
|
||||
@@ -22,20 +21,15 @@ import {
|
||||
} from 'features/controlLayers/store/canvasSettingsSlice';
|
||||
import type { RgbaColor } from 'features/controlLayers/store/types';
|
||||
import { useRegisteredHotkeys } from 'features/system/components/HotkeysModal/useHotkeyData';
|
||||
import { memo, useCallback, useMemo, useRef } from 'react';
|
||||
import { memo, useCallback, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const selectActiveColor = createSelector(selectCanvasSettingsSlice, (settings) => settings.activeColor);
|
||||
const selectBgColor = createSelector(selectCanvasSettingsSlice, (settings) => settings.bgColor);
|
||||
const selectFgColor = createSelector(selectCanvasSettingsSlice, (settings) => settings.fgColor);
|
||||
|
||||
interface ToolFillColorPickerProps {
|
||||
onComponentWidthChange: (value: number) => void;
|
||||
}
|
||||
|
||||
export const ToolFillColorPicker = memo(({ onComponentWidthChange }: ToolFillColorPickerProps) => {
|
||||
export const ToolFillColorPicker = memo(() => {
|
||||
const { t } = useTranslation();
|
||||
const ref = useRef(null);
|
||||
const activeColorType = useAppSelector(selectActiveColor);
|
||||
const bgColor = useAppSelector(selectBgColor);
|
||||
const fgColor = useAppSelector(selectFgColor);
|
||||
@@ -58,8 +52,6 @@ export const ToolFillColorPicker = memo(({ onComponentWidthChange }: ToolFillCol
|
||||
[activeColorType, dispatch]
|
||||
);
|
||||
|
||||
useResizeObserver(ref, (entry) => onComponentWidthChange(entry.contentRect.width));
|
||||
|
||||
useRegisteredHotkeys({
|
||||
id: 'setFillColorsToDefault',
|
||||
category: 'canvas',
|
||||
@@ -79,7 +71,7 @@ export const ToolFillColorPicker = memo(({ onComponentWidthChange }: ToolFillCol
|
||||
return (
|
||||
<Popover isLazy>
|
||||
<PopoverTrigger>
|
||||
<Flex ref={ref} role="button" aria-label={t('controlLayers.fill.fillColor')} tabIndex={-1} minW={8} w={8} h={8}>
|
||||
<Flex role="button" aria-label={t('controlLayers.fill.fillColor')} tabIndex={-1} minW={8} w={8} h={8}>
|
||||
<Tooltip label={tooltip}>
|
||||
<Flex alignItems="center" justifyContent="center" position="relative" w="full" h="full">
|
||||
<Box
|
||||
|
||||
@@ -70,10 +70,8 @@ const marks = [
|
||||
|
||||
const sliderDefaultValue = mapRawValueToSliderValue(50);
|
||||
|
||||
type ToolWidthPickerComponent = 'dropDown' | 'slider';
|
||||
const SLIDER_PICKER_WIDTH = 280;
|
||||
const DROPDOWN_PICKER_WIDTH = 76;
|
||||
const MIN_TOOLBAR_SPACE = 50;
|
||||
|
||||
interface ToolWidthPickerComponentProps {
|
||||
localValue: number;
|
||||
@@ -81,22 +79,10 @@ interface ToolWidthPickerComponentProps {
|
||||
onChangeInput: (value: number) => void;
|
||||
onBlur: () => void;
|
||||
onKeyDown: (value: KeyboardEvent<HTMLInputElement>) => void;
|
||||
onComponentWidthChange: (value: number) => void;
|
||||
}
|
||||
|
||||
const DropDownToolWidthPickerComponent = memo(
|
||||
({
|
||||
localValue,
|
||||
onChangeSlider,
|
||||
onChangeInput,
|
||||
onKeyDown,
|
||||
onBlur,
|
||||
onComponentWidthChange,
|
||||
}: ToolWidthPickerComponentProps) => {
|
||||
const ref = useRef(null);
|
||||
|
||||
useResizeObserver(ref, (entry) => onComponentWidthChange(entry.contentRect.width));
|
||||
|
||||
({ localValue, onChangeSlider, onChangeInput, onKeyDown, onBlur }: ToolWidthPickerComponentProps) => {
|
||||
const onChangeNumberInput = useCallback(
|
||||
(valueAsString: string, valueAsNumber: number) => {
|
||||
onChangeInput(valueAsNumber);
|
||||
@@ -106,7 +92,7 @@ const DropDownToolWidthPickerComponent = memo(
|
||||
|
||||
return (
|
||||
<Popover>
|
||||
<FormControl ref={ref} w="min-content" gap={2}>
|
||||
<FormControl w="min-content" gap={2} overflow="hidden">
|
||||
<PopoverAnchor>
|
||||
<NumberInput
|
||||
variant="outline"
|
||||
@@ -160,22 +146,12 @@ const DropDownToolWidthPickerComponent = memo(
|
||||
DropDownToolWidthPickerComponent.displayName = 'DropDownToolWidthPickerComponent';
|
||||
|
||||
const SliderToolWidthPickerComponent = memo(
|
||||
({
|
||||
localValue,
|
||||
onChangeSlider,
|
||||
onChangeInput,
|
||||
onKeyDown,
|
||||
onBlur,
|
||||
onComponentWidthChange,
|
||||
}: ToolWidthPickerComponentProps) => {
|
||||
const ref = useRef(null);
|
||||
|
||||
useResizeObserver(ref, (entry) => onComponentWidthChange(entry.contentRect.width));
|
||||
|
||||
({ localValue, onChangeSlider, onChangeInput, onKeyDown, onBlur }: ToolWidthPickerComponentProps) => {
|
||||
return (
|
||||
<Flex ref={ref} w={SLIDER_PICKER_WIDTH} h={8} gap={4}>
|
||||
<Flex w={SLIDER_PICKER_WIDTH} gap={4}>
|
||||
<CompositeSlider
|
||||
w={200}
|
||||
h="unset"
|
||||
min={0}
|
||||
max={100}
|
||||
value={mapRawValueToSliderValue(localValue)}
|
||||
@@ -203,52 +179,11 @@ const SliderToolWidthPickerComponent = memo(
|
||||
);
|
||||
SliderToolWidthPickerComponent.displayName = 'SliderToolWidthPickerComponent';
|
||||
|
||||
const components = {
|
||||
dropDown: DropDownToolWidthPickerComponent,
|
||||
slider: SliderToolWidthPickerComponent,
|
||||
} as const;
|
||||
|
||||
const calculateToolWidthPicker = (current: ToolWidthPickerComponent | undefined, toolBarAvailableSpace: number) => {
|
||||
switch (current) {
|
||||
case 'dropDown':
|
||||
if (toolBarAvailableSpace + DROPDOWN_PICKER_WIDTH > SLIDER_PICKER_WIDTH + MIN_TOOLBAR_SPACE) {
|
||||
return 'slider';
|
||||
}
|
||||
break;
|
||||
case 'slider':
|
||||
if (toolBarAvailableSpace < MIN_TOOLBAR_SPACE) {
|
||||
return 'dropDown';
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return toolBarAvailableSpace > SLIDER_PICKER_WIDTH + MIN_TOOLBAR_SPACE ? 'slider' : 'dropDown';
|
||||
}
|
||||
|
||||
return current;
|
||||
};
|
||||
|
||||
const useToolWidthPicker = (toolBarAvailableSpace: number) => {
|
||||
const prevToolWidthPicker = useRef<ToolWidthPickerComponent>();
|
||||
const toolWidthPicker = useMemo<ToolWidthPickerComponent>(() => {
|
||||
const next = calculateToolWidthPicker(prevToolWidthPicker.current, toolBarAvailableSpace);
|
||||
|
||||
prevToolWidthPicker.current = next;
|
||||
|
||||
return next;
|
||||
}, [toolBarAvailableSpace]);
|
||||
|
||||
return toolWidthPicker;
|
||||
};
|
||||
|
||||
const selectBrushWidth = createSelector(selectCanvasSettingsSlice, (settings) => settings.brushWidth);
|
||||
const selectEraserWidth = createSelector(selectCanvasSettingsSlice, (settings) => settings.eraserWidth);
|
||||
|
||||
interface ToolWidthPickerProps {
|
||||
toolBarAvailableSpace: number;
|
||||
onComponentWidthChange: (value: number) => void;
|
||||
}
|
||||
|
||||
export const ToolWidthPicker = memo(({ toolBarAvailableSpace, onComponentWidthChange }: ToolWidthPickerProps) => {
|
||||
export const ToolWidthPicker = memo(() => {
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
const dispatch = useAppDispatch();
|
||||
const isBrushSelected = useToolIsSelected('brush');
|
||||
const isEraserSelected = useToolIsSelected('eraser');
|
||||
@@ -267,7 +202,14 @@ export const ToolWidthPicker = memo(({ toolBarAvailableSpace, onComponentWidthCh
|
||||
return 0;
|
||||
}, [isBrushSelected, isEraserSelected, brushWidth, eraserWidth]);
|
||||
const [localValue, setLocalValue] = useState(width);
|
||||
const toolWidthPicker = useToolWidthPicker(toolBarAvailableSpace);
|
||||
const [componentType, setComponentType] = useState<'slider' | 'dropdown' | null>(null);
|
||||
useResizeObserver(ref, (entry) => {
|
||||
if (entry.contentRect.width > SLIDER_PICKER_WIDTH) {
|
||||
setComponentType('slider');
|
||||
} else {
|
||||
setComponentType('dropdown');
|
||||
}
|
||||
});
|
||||
|
||||
const onValueChange = useCallback(
|
||||
(value: number) => {
|
||||
@@ -351,18 +293,26 @@ export const ToolWidthPicker = memo(({ toolBarAvailableSpace, onComponentWidthCh
|
||||
dependencies: [increment, isToolSelected],
|
||||
});
|
||||
|
||||
const Component = components[toolWidthPicker];
|
||||
|
||||
return (
|
||||
<Flex px={4}>
|
||||
<Component
|
||||
localValue={localValue}
|
||||
onChangeSlider={onChangeSlider}
|
||||
onChangeInput={onChangeInput}
|
||||
onBlur={onBlur}
|
||||
onKeyDown={onKeyDown}
|
||||
onComponentWidthChange={onComponentWidthChange}
|
||||
/>
|
||||
<Flex ref={ref} alignItems="center" h="full" flexGrow={1} flexShrink={1} justifyContent="flex-start" px={4}>
|
||||
{componentType === 'slider' && (
|
||||
<SliderToolWidthPickerComponent
|
||||
localValue={localValue}
|
||||
onChangeSlider={onChangeSlider}
|
||||
onChangeInput={onChangeInput}
|
||||
onBlur={onBlur}
|
||||
onKeyDown={onKeyDown}
|
||||
/>
|
||||
)}
|
||||
{componentType === 'dropdown' && (
|
||||
<DropDownToolWidthPickerComponent
|
||||
localValue={localValue}
|
||||
onChangeSlider={onChangeSlider}
|
||||
onChangeInput={onChangeInput}
|
||||
onBlur={onBlur}
|
||||
onKeyDown={onKeyDown}
|
||||
/>
|
||||
)}
|
||||
</Flex>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { Divider, Flex } from '@invoke-ai/ui-library';
|
||||
import useResizeObserver from '@react-hook/resize-observer';
|
||||
import { CanvasSettingsPopover } from 'features/controlLayers/components/Settings/CanvasSettingsPopover';
|
||||
import { useToolIsSelected } from 'features/controlLayers/components/Tool/hooks';
|
||||
import { ToolFillColorPicker } from 'features/controlLayers/components/Tool/ToolFillColorPicker';
|
||||
@@ -22,46 +21,15 @@ import { useCanvasToggleNonRasterLayersHotkey } from 'features/controlLayers/hoo
|
||||
import { useCanvasTransformHotkey } from 'features/controlLayers/hooks/useCanvasTransformHotkey';
|
||||
import { useCanvasUndoRedoHotkeys } from 'features/controlLayers/hooks/useCanvasUndoRedoHotkeys';
|
||||
import { useNextPrevEntityHotkeys } from 'features/controlLayers/hooks/useNextPrevEntity';
|
||||
import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { memo, useMemo } from 'react';
|
||||
|
||||
export const CanvasToolbar = memo(() => {
|
||||
const toolbarRef = useRef(null);
|
||||
const [toolBarWidth, setToolBarWidth] = useState(0);
|
||||
const [fillColorPickerWidth, setFillColorPickerWidth] = useState(0);
|
||||
const [toolWidthPickerWidth, setToolWidthPickerWidth] = useState(0);
|
||||
|
||||
const isBrushSelected = useToolIsSelected('brush');
|
||||
const isEraserSelected = useToolIsSelected('eraser');
|
||||
const showToolWithPicker = useMemo(() => {
|
||||
return isBrushSelected || isEraserSelected;
|
||||
}, [isBrushSelected, isEraserSelected]);
|
||||
|
||||
const toolBarAvailableSpace = useMemo(() => {
|
||||
return toolBarWidth - fillColorPickerWidth - toolWidthPickerWidth;
|
||||
}, [toolBarWidth, fillColorPickerWidth, toolWidthPickerWidth]);
|
||||
|
||||
useResizeObserver(toolbarRef, (entry) => setToolBarWidth(entry.contentRect.width));
|
||||
|
||||
const onFillColorPickerWidthChange = useCallback(
|
||||
(width: number) => {
|
||||
setFillColorPickerWidth(width);
|
||||
},
|
||||
[setFillColorPickerWidth]
|
||||
);
|
||||
|
||||
const onToolWidthPickerWidthChange = useCallback(
|
||||
(width: number) => {
|
||||
setToolWidthPickerWidth(width);
|
||||
},
|
||||
[setToolWidthPickerWidth]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (!showToolWithPicker) {
|
||||
setToolWidthPickerWidth(0);
|
||||
}
|
||||
}, [showToolWithPicker]);
|
||||
|
||||
useCanvasResetLayerHotkey();
|
||||
useCanvasDeleteLayerHotkey();
|
||||
useCanvasUndoRedoHotkeys();
|
||||
@@ -75,16 +43,11 @@ export const CanvasToolbar = memo(() => {
|
||||
|
||||
return (
|
||||
<Flex w="full" gap={2} alignItems="center" px={2}>
|
||||
<Flex ref={toolbarRef} w="full">
|
||||
<ToolFillColorPicker onComponentWidthChange={onFillColorPickerWidthChange} />
|
||||
{showToolWithPicker && (
|
||||
<ToolWidthPicker
|
||||
toolBarAvailableSpace={toolBarAvailableSpace}
|
||||
onComponentWidthChange={onToolWidthPickerWidthChange}
|
||||
/>
|
||||
)}
|
||||
<Flex alignItems="center" h="full" flexGrow={1}>
|
||||
<ToolFillColorPicker />
|
||||
{showToolWithPicker && <ToolWidthPicker />}
|
||||
</Flex>
|
||||
<Flex alignItems="center" h="full" flexGrow={1} justifyContent="flex-end">
|
||||
<Flex alignItems="center" h="full">
|
||||
<CanvasToolbarScale />
|
||||
<CanvasToolbarResetViewButton />
|
||||
<CanvasToolbarFitBboxToLayersButton />
|
||||
|
||||
Reference in New Issue
Block a user