diff --git a/public/locales/en/panel-navigation.json b/public/locales/en/panel-navigation.json index c007c4ba..06f8a6b3 100644 --- a/public/locales/en/panel-navigation.json +++ b/public/locales/en/panel-navigation.json @@ -46,7 +46,8 @@ "title": "Navigation Settings", "include-non-focusable-label": "Include Non-focusable Nodes in Search", "include-non-focusable-description": "Per default, nodes that are marked as non-focusable from the search in the navigation menu. Checking this option will include them and allow setting these nodes as focus.", - "camera-path-settings": "Camera Path Settings" + "camera-path-settings": "Camera Path Settings", + "override-fly-to-duration-label": "Override Fly-to Duration" }, "remaining-flight-time-indicator": { "flying-to": "Flying to {{target}}", diff --git a/src/components/NodeNavigationButton/NodeNavigationButton.tsx b/src/components/NodeNavigationButton/NodeNavigationButton.tsx index 9c83d028..32ac0da7 100644 --- a/src/components/NodeNavigationButton/NodeNavigationButton.tsx +++ b/src/components/NodeNavigationButton/NodeNavigationButton.tsx @@ -18,6 +18,7 @@ import { FrameFocusIcon, LightningFlashIcon } from '@/icons/icons'; +import { useAppSelector } from '@/redux/hooks'; import { NavigationType } from '@/types/enums'; import { Identifier } from '@/types/types'; @@ -81,6 +82,9 @@ export function NodeNavigationButton({ const engineMode = useSubscribeToEngineMode(); const luaApi = useOpenSpaceApi(); + const flyToOverrideDuration = useAppSelector( + (state) => state.local.menus.navigation.flyToOverrideDuration + ); const isInPlayback = engineMode === EngineMode.SessionRecordingPlayback; @@ -95,6 +99,8 @@ export function NodeNavigationButton({ function flyTo(event: React.MouseEvent) { if (event.shiftKey) { luaApi?.navigation.flyTo(identifier, 0.0); + } else if (flyToOverrideDuration.enabled) { + luaApi?.navigation.flyTo(identifier, flyToOverrideDuration.duration); } else { luaApi?.navigation.flyTo(identifier); } diff --git a/src/panels/NavigationPanel/NavigationSettings.tsx b/src/panels/NavigationPanel/NavigationSettings.tsx index 41a6caa3..5d96e1fc 100644 --- a/src/panels/NavigationPanel/NavigationSettings.tsx +++ b/src/panels/NavigationPanel/NavigationSettings.tsx @@ -1,11 +1,25 @@ import { useTranslation } from 'react-i18next'; -import { Container, Divider, Menu } from '@mantine/core'; +import { + Box, + Checkbox, + Container, + Divider, + Group, + Menu, + NumberInput, + Stack +} from '@mantine/core'; import { BoolInput } from '@/components/Input/BoolInput'; +import { NumericSlider } from '@/components/Input/NumericInput/NumericSlider/NumericSlider'; import { Property } from '@/components/Property/Property'; import { SettingsPopout } from '@/components/SettingsPopout/SettingsPopout'; import { useAppDispatch, useAppSelector } from '@/redux/hooks'; -import { setOnlyFocusableInNavMenu } from '@/redux/local/localSlice'; +import { + setFlyToOverrideDuration, + setFlyToOverrideDurationEnabled, + setOnlyFocusableInNavMenu +} from '@/redux/local/localSlice'; import { ApplyIdleMotionOnPathFinishKey, CameraPathArrivalDistanceFactorKey, @@ -20,6 +34,10 @@ export function NavigationSettings() { (state) => state.local.menus.navigation.onlyFocusable ); + const flyToOverrideDuration = useAppSelector( + (state) => state.local.menus.navigation.flyToOverrideDuration + ); + const dispatch = useAppDispatch(); return ( @@ -40,6 +58,44 @@ export function NavigationSettings() { + + + dispatch(setFlyToOverrideDurationEnabled(event.currentTarget.checked)) + } + /> + + + dispatch(setFlyToOverrideDuration(value))} + /> + + { + if (typeof value !== 'number' || Number.isNaN(value)) { + return; + } + dispatch(setFlyToOverrideDuration(value)); + }} + /> + + ); diff --git a/src/redux/local/localSlice.ts b/src/redux/local/localSlice.ts index 833f7f26..de14fe97 100644 --- a/src/redux/local/localSlice.ts +++ b/src/redux/local/localSlice.ts @@ -8,6 +8,10 @@ export interface LocalState { navigation: { // Whether to show non-focusable nodes in the navigation menu search results onlyFocusable: boolean; + flyToOverrideDuration: { + enabled: boolean; + duration: number; + }; }; }; sceneTree: { @@ -24,7 +28,11 @@ export interface LocalState { const initialState: LocalState = { menus: { navigation: { - onlyFocusable: true + onlyFocusable: true, + flyToOverrideDuration: { + enabled: false, + duration: 5 + } } }, // @TODO: (emmbr 2025-04-09): Consider moving this to the menus object above. did not @@ -74,6 +82,14 @@ export const localSlice = createSlice({ state.menus.navigation.onlyFocusable = action.payload; return state; }, + setFlyToOverrideDurationEnabled: (state, action: PayloadAction) => { + state.menus.navigation.flyToOverrideDuration.enabled = action.payload; + return state; + }, + setFlyToOverrideDuration: (state, action: PayloadAction) => { + state.menus.navigation.flyToOverrideDuration.duration = action.payload; + return state; + }, setMenuItemVisible: ( state, action: PayloadAction<{ id: string; visible: boolean }> @@ -108,6 +124,8 @@ export const { setSceneTreeNodeExpanded, setSceneTreeSelectedNode, setOnlyFocusableInNavMenu, + setFlyToOverrideDurationEnabled, + setFlyToOverrideDuration, setMenuItemVisible, setMenuItemOpen, setMenuItemsConfig,