feat(ui): simpler strategy to conditionally render slider brush width

This commit is contained in:
psychedelicious
2025-09-08 13:42:28 +10:00
parent f284d282c1
commit 9bbb8e8a5e
3 changed files with 42 additions and 137 deletions

View File

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

View File

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

View File

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