From 0afa76a165a68f46c84581c47ed7ed7542a0fdd0 Mon Sep 17 00:00:00 2001 From: Mikael Finstad Date: Mon, 3 Apr 2023 09:27:30 +0900 Subject: [PATCH] add preference for color intensity https://github.com/mifi/lossless-cut/discussions/1507 --- public/configStore.js | 1 + src/App.jsx | 502 ++++++++++++----------- src/BottomBar.jsx | 15 +- src/SegmentList.jsx | 8 +- src/Timeline.jsx | 9 +- src/TimelineSeg.jsx | 17 +- src/components/SegmentCutpointButton.jsx | 11 +- src/components/Settings.jsx | 11 +- src/contexts.js | 6 + src/contexts/UserSettingsContext.js | 3 - src/hooks/useUserSettings.js | 2 +- src/hooks/useUserSettingsRoot.js | 4 + 12 files changed, 311 insertions(+), 278 deletions(-) create mode 100644 src/contexts.js delete mode 100644 src/contexts/UserSettingsContext.js diff --git a/public/configStore.js b/public/configStore.js index 1f905701..0c179519 100644 --- a/public/configStore.js +++ b/public/configStore.js @@ -123,6 +123,7 @@ const defaults = { }, allowMultipleInstances: false, darkMode: true, + preferStrongColors: false, }; // For portable app: https://github.com/mifi/lossless-cut/issues/645 diff --git a/src/App.jsx b/src/App.jsx index 5a20c271..a2870bdf 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -26,7 +26,7 @@ import useFrameCapture from './hooks/useFrameCapture'; import useSegments from './hooks/useSegments'; import useDirectoryAccess, { DirectoryAccessDeclinedError } from './hooks/useDirectoryAccess'; -import UserSettingsContext from './contexts/UserSettingsContext'; +import { UserSettingsContext, SegColorsContext } from './contexts'; import NoFileLoaded from './NoFileLoaded'; import Canvas from './Canvas'; @@ -50,6 +50,7 @@ import OutputFormatSelect from './components/OutputFormatSelect'; import { loadMifiLink, runStartupCheck } from './mifi'; import { controlsBackground, darkModeTransition } from './colors'; +import { getSegColor } from './util/colors'; import { getStreamFps, isCuttingStart, isCuttingEnd, readFileMeta, getSmarterOutFormat, renderThumbnails as ffmpegRenderThumbnails, @@ -175,7 +176,7 @@ const App = memo(() => { const allUserSettings = useUserSettingsRoot(); const { - captureFormat, setCaptureFormat, customOutDir, setCustomOutDir, keyframeCut, setKeyframeCut, preserveMovData, setPreserveMovData, movFastStart, setMovFastStart, avoidNegativeTs, autoMerge, timecodeFormat, invertCutSegments, setInvertCutSegments, autoExportExtraStreams, askBeforeClose, enableAskForImportChapters, enableAskForFileOpenAction, playbackVolume, setPlaybackVolume, autoSaveProjectFile, wheelSensitivity, invertTimelineScroll, language, ffmpegExperimental, hideNotifications, autoLoadTimecode, autoDeleteMergedSegments, exportConfirmEnabled, setExportConfirmEnabled, segmentsToChapters, setSegmentsToChapters, preserveMetadataOnMerge, setPreserveMetadataOnMerge, setSimpleMode, outSegTemplate, setOutSegTemplate, keyboardSeekAccFactor, keyboardNormalSeekSpeed, enableTransferTimestamps, outFormatLocked, setOutFormatLocked, safeOutputFileName, setSafeOutputFileName, enableAutoHtml5ify, segmentsToChaptersOnly, keyBindings, setKeyBindings, resetKeyBindings, enableSmartCut, customFfPath, storeProjectInWorkingDir, setStoreProjectInWorkingDir, enableOverwriteOutput, mouseWheelZoomModifierKey, captureFrameMethod, captureFrameQuality, captureFrameFileNameFormat, enableNativeHevc, cleanupChoices, setCleanupChoices, darkMode, setDarkMode, + captureFormat, setCaptureFormat, customOutDir, setCustomOutDir, keyframeCut, setKeyframeCut, preserveMovData, setPreserveMovData, movFastStart, setMovFastStart, avoidNegativeTs, autoMerge, timecodeFormat, invertCutSegments, setInvertCutSegments, autoExportExtraStreams, askBeforeClose, enableAskForImportChapters, enableAskForFileOpenAction, playbackVolume, setPlaybackVolume, autoSaveProjectFile, wheelSensitivity, invertTimelineScroll, language, ffmpegExperimental, hideNotifications, autoLoadTimecode, autoDeleteMergedSegments, exportConfirmEnabled, setExportConfirmEnabled, segmentsToChapters, setSegmentsToChapters, preserveMetadataOnMerge, setPreserveMetadataOnMerge, setSimpleMode, outSegTemplate, setOutSegTemplate, keyboardSeekAccFactor, keyboardNormalSeekSpeed, enableTransferTimestamps, outFormatLocked, setOutFormatLocked, safeOutputFileName, setSafeOutputFileName, enableAutoHtml5ify, segmentsToChaptersOnly, keyBindings, setKeyBindings, resetKeyBindings, enableSmartCut, customFfPath, storeProjectInWorkingDir, setStoreProjectInWorkingDir, enableOverwriteOutput, mouseWheelZoomModifierKey, captureFrameMethod, captureFrameQuality, captureFrameFileNameFormat, enableNativeHevc, cleanupChoices, setCleanupChoices, darkMode, setDarkMode, preferStrongColors, } = allUserSettings; useEffect(() => { @@ -510,6 +511,13 @@ const App = memo(() => { ...allUserSettings, toggleCaptureFormat, changeOutDir, toggleKeyframeCut, togglePreserveMovData, toggleMovFastStart, toggleExportConfirmEnabled, toggleSegmentsToChapters, togglePreserveMetadataOnMerge, toggleSimpleMode, toggleSafeOutputFileName, effectiveExportMode, }), [allUserSettings, changeOutDir, effectiveExportMode, toggleCaptureFormat, toggleExportConfirmEnabled, toggleKeyframeCut, toggleMovFastStart, togglePreserveMetadataOnMerge, togglePreserveMovData, toggleSafeOutputFileName, toggleSegmentsToChapters, toggleSimpleMode]); + const segColorsContext = useMemo(() => ({ + getSegColor: (seg) => { + const color = getSegColor(seg); + return preferStrongColors ? color.desaturate(0.2) : color.desaturate(0.6); + }, + }), [preferStrongColors]); + const isCopyingStreamId = useCallback((path, streamId) => ( !!(copyStreamIdsByFile[path] || {})[streamId] ), [copyStreamIdsByFile]); @@ -2153,269 +2161,271 @@ const App = memo(() => { // throw new Error('Test error boundary'); return ( - - -
- - -
- - {showLeftBar && ( - - )} - - - {/* Middle part: */} -
- {!isFileOpened && } - -
- {/* eslint-disable-next-line jsx-a11y/media-has-caption */} - - - {canvasPlayerEnabled && } -
+ + + +
+ - {bigWaveformEnabled && } +
+ + {showLeftBar && ( + + )} + - {isRotationSet && !hideCanvasPreview && ( -
- - {t('Rotation preview')} - {!canvasPlayerRequired && setHideCanvasPreview(true)} />} + {/* Middle part: */} +
+ {!isFileOpened && } + +
+ {/* eslint-disable-next-line jsx-a11y/media-has-caption */} + + + {canvasPlayerEnabled && }
- )} - {isFileOpened && ( -
- - - {subtitleStreams.length > 0 && } - - {!showRightBar && ( - - )} -
- )} + {bigWaveformEnabled && } + + {isRotationSet && !hideCanvasPreview && ( +
+ + {t('Rotation preview')} + {!canvasPlayerRequired && setHideCanvasPreview(true)} />} +
+ )} + + {isFileOpened && ( +
+ + + {subtitleStreams.length > 0 && } + + {!showRightBar && ( + + )} +
+ )} + + + {working && } + + + {tunerVisible && setTunerVisible()} />} +
- {working && } + {showRightBar && isFileOpened && ( + + )} +
+ +
+ - {tunerVisible && setTunerVisible()} />} +
- - {showRightBar && isFileOpened && ( - setStreamsSelectorShown(false)} style={{ padding: '1em 0' }}> + {mainStreams && ( + )} - -
+ -
- + - -
- setStreamsSelectorShown(false)} style={{ padding: '1em 0' }}> - {mainStreams && ( - + - )} - - - - - - - - - + - 0 && concatDialogVisible} onHide={() => setConcatDialogVisible(false)} paths={batchFilePaths} onConcat={userConcatFiles} setAlwaysConcatMultipleFiles={setAlwaysConcatMultipleFiles} alwaysConcatMultipleFiles={alwaysConcatMultipleFiles} /> + 0 && concatDialogVisible} onHide={() => setConcatDialogVisible(false)} paths={batchFilePaths} onConcat={userConcatFiles} setAlwaysConcatMultipleFiles={setAlwaysConcatMultipleFiles} alwaysConcatMultipleFiles={alwaysConcatMultipleFiles} /> - setKeyboardShortcutsVisible(false)} keyBindings={keyBindings} setKeyBindings={setKeyBindings} currentCutSeg={currentCutSeg} resetKeyBindings={resetKeyBindings} /> -
-
-
+ setKeyboardShortcutsVisible(false)} keyBindings={keyBindings} setKeyBindings={setKeyBindings} currentCutSeg={currentCutSeg} resetKeyBindings={resetKeyBindings} /> +
+ + + ); }); diff --git a/src/BottomBar.jsx b/src/BottomBar.jsx index 894c81a5..44af7d9a 100644 --- a/src/BottomBar.jsx +++ b/src/BottomBar.jsx @@ -18,7 +18,8 @@ import Select from './components/Select'; import SimpleModeButton from './components/SimpleModeButton'; import { withBlur, mirrorTransform, checkAppPath } from './util'; import { toast } from './swal'; -import { getSegColor } from './util/colors'; +import { getSegColor as getSegColorRaw } from './util/colors'; +import { useSegColors } from './contexts'; import { formatDuration, parseDuration, isExactDurationMatch } from './util/duration'; import useUserSettings from './hooks/useUserSettings'; @@ -31,6 +32,7 @@ const leftRightWidth = 100; const CutTimeInput = memo(({ darkMode, cutTime, setCutTime, startTimeOffset, seekAbs, currentCutSeg, currentApparentCutSeg, isStart }) => { const { t } = useTranslation(); + const { getSegColor } = useSegColors(); const [cutTimeManual, setCutTimeManual] = useState(); @@ -41,8 +43,10 @@ const CutTimeInput = memo(({ darkMode, cutTime, setCutTime, startTimeOffset, see const isCutTimeManualSet = () => cutTimeManual !== undefined; - const segColor = getSegColor(currentCutSeg); - const border = `.1em solid ${darkMode ? segColor.desaturate(0.9).lightness(50).string() : segColor.desaturate(0.7).lightness(60).string()}`; + const border = useMemo(() => { + const segColor = getSegColor(currentCutSeg); + return `.1em solid ${darkMode ? segColor.desaturate(0.4).lightness(50).string() : segColor.desaturate(0.2).lightness(60).string()}`; + }, [currentCutSeg, darkMode, getSegColor]); const cutTimeInputStyle = { border, borderRadius: 5, backgroundColor: 'var(--gray5)', transition: darkModeTransition, fontSize: 13, textAlign: 'center', padding: '1px 5px', marginTop: 0, marginBottom: 0, marginLeft: isStart ? 0 : 5, marginRight: isStart ? 5 : 0, boxSizing: 'border-box', fontFamily: 'inherit', width: 90, outline: 'none', @@ -142,6 +146,7 @@ const BottomBar = memo(({ toggleEnableThumbnails, toggleWaveformMode, waveformMode, showThumbnails, }) => { const { t } = useTranslation(); + const { getSegColor } = useSegColors(); // ok this is a bit over-engineered but what the hell! const loopSelectedSegmentsButtonStyle = useMemo(() => { @@ -149,7 +154,7 @@ const BottomBar = memo(({ const selectedSegmentsSafe = (selectedSegments.length > 1 ? selectedSegments : [selectedSegments[0], selectedSegments[0]]).slice(0, 10); const gradientColors = selectedSegmentsSafe.map((seg, i) => { - const segColor = getSegColor(seg); + const segColor = getSegColorRaw(seg); // make colors stronger, the more segments return `${segColor.alpha(Math.max(0.4, Math.min(0.8, selectedSegmentsSafe.length / 3))).string()} ${((i / (selectedSegmentsSafe.length - 1)) * 100).toFixed(1)}%`; }).join(', '); @@ -191,7 +196,7 @@ const BottomBar = memo(({ const newIndex = currentSegIndexSafe + direction; const seg = cutSegments[newIndex]; - const backgroundColor = seg && getSegColor(seg).desaturate(0.9).lightness(darkMode ? 35 : 55).string(); + const backgroundColor = seg && getSegColor(seg).desaturate(0.6).lightness(darkMode ? 35 : 55).string(); const opacity = seg ? undefined : 0.5; const text = seg ? `${newIndex + 1}` : '-'; const wide = text.length > 1; diff --git a/src/SegmentList.jsx b/src/SegmentList.jsx index d365ad72..e74e139b 100644 --- a/src/SegmentList.jsx +++ b/src/SegmentList.jsx @@ -12,7 +12,7 @@ import Swal from './swal'; import useContextMenu from './hooks/useContextMenu'; import useUserSettings from './hooks/useUserSettings'; import { saveColor, controlsBackground, primaryTextColor, darkModeTransition } from './colors'; -import { getSegColor } from './util/colors'; +import { useSegColors } from './contexts'; import { mySpring } from './animations'; const buttonBaseStyle = { @@ -24,6 +24,7 @@ const neutralButtonColor = 'var(--gray8)'; const Segment = memo(({ darkMode, seg, index, currentSegIndex, formatTimecode, getFrameCount, updateOrder, invertCutSegments, onClick, onRemovePress, onRemoveSelected, onLabelSelectedSegments, onReorderPress, onLabelPress, selected, onSelectSingleSegment, onToggleSegmentSelected, onDeselectAllSegments, onSelectSegmentsByLabel, onSelectAllSegments, jumpSegStart, jumpSegEnd, addSegment, onViewSegmentTags, onExtractSegmentFramesAsImages, onInvertSelectedSegments }) => { const { t } = useTranslation(); + const { getSegColor } = useSegColors(); const ref = useRef(); @@ -81,7 +82,7 @@ const Segment = memo(({ darkMode, seg, index, currentSegIndex, formatTimecode, g const segColor = getSegColor(seg); - const color = segColor.desaturate(0.75).lightness(darkMode ? 35 : 55); + const color = segColor.desaturate(0.25).lightness(darkMode ? 35 : 55); const borderColor = darkMode ? color.lighten(0.5) : color.darken(0.3); return {index + 1}; @@ -154,6 +155,7 @@ const SegmentList = memo(({ jumpSegStart, jumpSegEnd, onViewSegmentTags, }) => { const { t } = useTranslation(); + const { getSegColor } = useSegColors(); const { invertCutSegments, simpleMode, darkMode } = useUserSettings(); @@ -198,7 +200,7 @@ const SegmentList = memo(({ } function renderFooter() { - const getButtonColor = (seg) => getSegColor(seg).desaturate(0.8).lightness(darkMode ? 45 : 55).string(); + const getButtonColor = (seg) => getSegColor(seg).desaturate(0.3).lightness(darkMode ? 45 : 55).string(); const currentSegColor = getButtonColor(currentCutSeg); const segAtCursorColor = getButtonColor(segmentAtCursor); diff --git a/src/Timeline.jsx b/src/Timeline.jsx index b1938a20..9660127c 100644 --- a/src/Timeline.jsx +++ b/src/Timeline.jsx @@ -12,8 +12,6 @@ import useUserSettings from './hooks/useUserSettings'; import { timelineBackground, darkModeTransition } from './colors'; -import { getSegColor } from './util/colors'; - const currentTimeWidth = 1; const Waveform = memo(({ waveform, calculateTimelinePercent, durationSafe }) => { @@ -295,8 +293,6 @@ const Timeline = memo(({ )} {apparentCutSegments.map((seg, i) => { - const segColor = getSegColor(seg); - if (seg.start === 0 && seg.end === 0) return null; // No video loaded const selected = invertCutSegments || isSegmentSelected({ segId: seg.segId }); @@ -304,14 +300,11 @@ const Timeline = memo(({ return ( { const { darkMode } = useUserSettings(); + const { getSegColor } = useSegColors(); + + const segColor = useMemo(() => getSegColor(seg), [getSegColor, seg]); + + const { name, start: cutStart, end: cutEnd } = seg; const cutSectionWidth = `${((cutEnd - cutStart) / duration) * 100}%`; @@ -18,13 +23,13 @@ const TimelineSeg = memo(({ const markerBorder = useMemo(() => { if (!isActive) return '2px solid transparent'; - return `2px solid ${(darkMode ? segColor.desaturate(0.6).lightness(70) : segColor.desaturate(0.9).lightness(20)).string()}`; + return `2px solid ${darkMode ? segColor.desaturate(0.1).lightness(70).string() : segColor.desaturate(0.2).lightness(40).string()}`; }, [darkMode, isActive, segColor]); const backgroundColor = useMemo(() => { - if (invertCutSegments || !selected) return darkMode ? segColor.desaturate(0.9).lightness(25).string() : segColor.desaturate(0.9).lightness(80).string(); - if (isActive) return darkMode ? segColor.desaturate(0.7).lightness(50).string() : segColor.desaturate(0.7).lightness(40).string(); - return darkMode ? segColor.desaturate(0.7).lightness(40).string() : segColor.desaturate(0.9).lightness(55).string(); + if (invertCutSegments || !selected) return darkMode ? segColor.desaturate(0.4).lightness(25).string() : segColor.desaturate(0.4).lightness(83).string(); + if (isActive) return darkMode ? segColor.desaturate(0.2).lightness(50).string() : segColor.desaturate(0.2).lightness(60).string(); + return darkMode ? segColor.desaturate(0.2).lightness(40).string() : segColor.desaturate(0.5).lightness(65).string(); }, [darkMode, invertCutSegments, isActive, segColor, selected]); const markerBorderRadius = 5; diff --git a/src/components/SegmentCutpointButton.jsx b/src/components/SegmentCutpointButton.jsx index 361389e8..c705fd06 100644 --- a/src/components/SegmentCutpointButton.jsx +++ b/src/components/SegmentCutpointButton.jsx @@ -1,15 +1,16 @@ -import React from 'react'; +import React, { useMemo } from 'react'; -import { getSegColor } from '../util/colors'; +import { useSegColors } from '../contexts'; import useUserSettings from '../hooks/useUserSettings'; const SegmentCutpointButton = ({ currentCutSeg, side, Icon, onClick, title, style }) => { const { darkMode } = useUserSettings(); - const segColor = getSegColor(currentCutSeg); + const { getSegColor } = useSegColors(); + const segColor = useMemo(() => getSegColor(currentCutSeg), [currentCutSeg, getSegColor]); const start = side === 'start'; - const border = `3px solid ${segColor.desaturate(0.9).lightness(darkMode ? 45 : 35).string()}`; - const backgroundColor = segColor.desaturate(0.9).lightness(darkMode ? 35 : 55).string(); + const border = `3px solid ${segColor.desaturate(0.6).lightness(darkMode ? 45 : 35).string()}`; + const backgroundColor = segColor.desaturate(0.6).lightness(darkMode ? 35 : 55).string(); return ( { const { t } = useTranslation(); - const { customOutDir, changeOutDir, keyframeCut, toggleKeyframeCut, timecodeFormat, setTimecodeFormat, invertCutSegments, setInvertCutSegments, askBeforeClose, setAskBeforeClose, enableAskForImportChapters, setEnableAskForImportChapters, enableAskForFileOpenAction, setEnableAskForFileOpenAction, autoSaveProjectFile, setAutoSaveProjectFile, invertTimelineScroll, setInvertTimelineScroll, language, setLanguage, ffmpegExperimental, setFfmpegExperimental, hideNotifications, setHideNotifications, autoLoadTimecode, setAutoLoadTimecode, enableTransferTimestamps, setEnableTransferTimestamps, enableAutoHtml5ify, setEnableAutoHtml5ify, customFfPath, setCustomFfPath, storeProjectInWorkingDir, enableOverwriteOutput, setEnableOverwriteOutput, mouseWheelZoomModifierKey, setMouseWheelZoomModifierKey, captureFrameMethod, setCaptureFrameMethod, captureFrameQuality, setCaptureFrameQuality, captureFrameFileNameFormat, setCaptureFrameFileNameFormat, enableNativeHevc, setEnableNativeHevc, enableUpdateCheck, setEnableUpdateCheck, allowMultipleInstances, setAllowMultipleInstances } = useUserSettings(); + const { customOutDir, changeOutDir, keyframeCut, toggleKeyframeCut, timecodeFormat, setTimecodeFormat, invertCutSegments, setInvertCutSegments, askBeforeClose, setAskBeforeClose, enableAskForImportChapters, setEnableAskForImportChapters, enableAskForFileOpenAction, setEnableAskForFileOpenAction, autoSaveProjectFile, setAutoSaveProjectFile, invertTimelineScroll, setInvertTimelineScroll, language, setLanguage, ffmpegExperimental, setFfmpegExperimental, hideNotifications, setHideNotifications, autoLoadTimecode, setAutoLoadTimecode, enableTransferTimestamps, setEnableTransferTimestamps, enableAutoHtml5ify, setEnableAutoHtml5ify, customFfPath, setCustomFfPath, storeProjectInWorkingDir, enableOverwriteOutput, setEnableOverwriteOutput, mouseWheelZoomModifierKey, setMouseWheelZoomModifierKey, captureFrameMethod, setCaptureFrameMethod, captureFrameQuality, setCaptureFrameQuality, captureFrameFileNameFormat, setCaptureFrameFileNameFormat, enableNativeHevc, setEnableNativeHevc, enableUpdateCheck, setEnableUpdateCheck, allowMultipleInstances, setAllowMultipleInstances, preferStrongColors, setPreferStrongColors } = useUserSettings(); const onLangChange = useCallback((e) => { const { value } = e.target; @@ -311,6 +311,15 @@ const Settings = memo(({ +
+ + + {t('Prefer strong colors')} + + + + +
{!isMasBuild && ( diff --git a/src/contexts.js b/src/contexts.js new file mode 100644 index 00000000..7324dda1 --- /dev/null +++ b/src/contexts.js @@ -0,0 +1,6 @@ +import React, { useContext } from 'react'; + +export const UserSettingsContext = React.createContext(); +export const SegColorsContext = React.createContext(); + +export const useSegColors = () => useContext(SegColorsContext); diff --git a/src/contexts/UserSettingsContext.js b/src/contexts/UserSettingsContext.js deleted file mode 100644 index 6353dc06..00000000 --- a/src/contexts/UserSettingsContext.js +++ /dev/null @@ -1,3 +0,0 @@ -import React from 'react'; - -export default React.createContext(); diff --git a/src/hooks/useUserSettings.js b/src/hooks/useUserSettings.js index 78f0cffc..a1e737ca 100644 --- a/src/hooks/useUserSettings.js +++ b/src/hooks/useUserSettings.js @@ -1,5 +1,5 @@ import { useContext } from 'react'; -import UserSettingsContext from '../contexts/UserSettingsContext'; +import { UserSettingsContext } from '../contexts'; export default () => useContext(UserSettingsContext); diff --git a/src/hooks/useUserSettingsRoot.js b/src/hooks/useUserSettingsRoot.js index e5281f40..cccc239e 100644 --- a/src/hooks/useUserSettingsRoot.js +++ b/src/hooks/useUserSettingsRoot.js @@ -135,6 +135,8 @@ export default () => { useEffect(() => safeSetConfig({ allowMultipleInstances }), [allowMultipleInstances]); const [darkMode, setDarkMode] = useState(safeGetConfigInitial('darkMode')); useEffect(() => safeSetConfig({ darkMode }), [darkMode]); + const [preferStrongColors, setPreferStrongColors] = useState(safeGetConfigInitial('preferStrongColors')); + useEffect(() => safeSetConfig({ preferStrongColors }), [preferStrongColors]); const resetKeyBindings = useCallback(() => { @@ -248,5 +250,7 @@ export default () => { setAllowMultipleInstances, darkMode, setDarkMode, + preferStrongColors, + setPreferStrongColors, }; };