mirror of
https://github.com/4ian/GDevelop.git
synced 2025-10-15 10:19:04 +00:00
Display the scene editors more conveniently on mobile (#5441)
This commit is contained in:
@@ -48,6 +48,67 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"swipeable-drawer": {
|
||||
"top-bar": {
|
||||
"pill-color": {
|
||||
"value": "#1D1D26"
|
||||
}
|
||||
}
|
||||
},
|
||||
"icon-button": {
|
||||
"selected": {
|
||||
"color": {
|
||||
"value": "#1D1D26",
|
||||
"comment": "Palette/Grey/100"
|
||||
},
|
||||
"background-color": {
|
||||
"value": "#C9B6FC",
|
||||
"comment:": "Palette/Purple/20"
|
||||
}
|
||||
}
|
||||
},
|
||||
"link": {
|
||||
"color": {
|
||||
"value": "#DDD1FF",
|
||||
"comment": "Palette/Purple/10"
|
||||
},
|
||||
"hover-color": {
|
||||
"value": "#C9B6FC",
|
||||
"comment": "Palette/Purple/20"
|
||||
}
|
||||
},
|
||||
"switch": {
|
||||
"default": {
|
||||
"thumb-color": {
|
||||
"value": "#EBEBED",
|
||||
"comment": "Palette/Grey/10"
|
||||
},
|
||||
"track-color": {
|
||||
"value": "#606066",
|
||||
"comment": "Palette/Grey/60"
|
||||
}
|
||||
},
|
||||
"toggled": {
|
||||
"thumb-color": {
|
||||
"value": "#DDD1FF",
|
||||
"comment": "Palette/Purple/10"
|
||||
},
|
||||
"track-color": {
|
||||
"value": "#7046EC",
|
||||
"comment": "Palette/Purple/40"
|
||||
}
|
||||
},
|
||||
"disabled": {
|
||||
"thumb-color": {
|
||||
"value": "#494952",
|
||||
"comment": "Palette/Grey/70"
|
||||
},
|
||||
"track-color": {
|
||||
"value": "#32323B",
|
||||
"comment": "Palette/Grey/80"
|
||||
}
|
||||
}
|
||||
},
|
||||
"search-bar": {
|
||||
"default": {
|
||||
"background-color": {
|
||||
|
@@ -14,8 +14,17 @@ import SearchBar, { type SearchBarInterface } from '../../UI/SearchBar';
|
||||
import RemoveCircle from '../../UI/CustomSvgIcons/RemoveCircle';
|
||||
import Lock from '../../UI/CustomSvgIcons/Lock';
|
||||
import LockOpen from '../../UI/CustomSvgIcons/LockOpen';
|
||||
import { toFixedWithoutTrailingZeros } from '../../Utils/Mathematics';
|
||||
const gd = global.gd;
|
||||
|
||||
const minimumWidths = {
|
||||
table: 400,
|
||||
objectName: 80,
|
||||
icon: 40,
|
||||
numberProperty: 40,
|
||||
layerName: 50,
|
||||
};
|
||||
|
||||
type State = {|
|
||||
searchText: string,
|
||||
sortBy: string,
|
||||
@@ -46,6 +55,7 @@ const styles = {
|
||||
flexDirection: 'column',
|
||||
alignItems: 'stretch',
|
||||
},
|
||||
tableContainer: { flex: 1, overflowX: 'auto', overflowY: 'hidden' },
|
||||
};
|
||||
|
||||
const compareStrings = (x: string, y: string, direction: number): number => {
|
||||
@@ -89,9 +99,9 @@ export default class InstancesList extends Component<Props, State> {
|
||||
instance,
|
||||
name,
|
||||
locked: instance.isLocked(),
|
||||
x: instance.getX().toFixed(2),
|
||||
y: instance.getY().toFixed(2),
|
||||
angle: instance.getAngle().toFixed(2),
|
||||
x: toFixedWithoutTrailingZeros(instance.getX(), 2),
|
||||
y: toFixedWithoutTrailingZeros(instance.getY(), 2),
|
||||
angle: toFixedWithoutTrailingZeros(instance.getAngle(), 2),
|
||||
layer: instance.getLayer(),
|
||||
zOrder: instance.getZOrder(),
|
||||
});
|
||||
@@ -225,7 +235,7 @@ export default class InstancesList extends Component<Props, State> {
|
||||
{gdevelopTheme => (
|
||||
<div style={styles.container}>
|
||||
<div
|
||||
style={{ flex: 1 }}
|
||||
style={styles.tableContainer}
|
||||
onKeyDown={this._keyboardShortcuts.onKeyDown}
|
||||
onKeyUp={this._keyboardShortcuts.onKeyUp}
|
||||
>
|
||||
@@ -246,49 +256,64 @@ export default class InstancesList extends Component<Props, State> {
|
||||
sort={this._sort}
|
||||
sortBy={sortBy}
|
||||
sortDirection={sortDirection}
|
||||
width={width}
|
||||
width={Math.max(width, minimumWidths.table)}
|
||||
>
|
||||
<RVColumn
|
||||
label={<Trans>Object name</Trans>}
|
||||
dataKey="name"
|
||||
width={width * 0.35}
|
||||
width={Math.max(width * 0.35, minimumWidths.objectName)}
|
||||
className={'tableColumn'}
|
||||
/>
|
||||
<RVColumn
|
||||
label=""
|
||||
dataKey="locked"
|
||||
width={width * 0.05}
|
||||
width={Math.max(
|
||||
width * 0.05,
|
||||
minimumWidths.numberProperty
|
||||
)}
|
||||
className={'tableColumn'}
|
||||
cellRenderer={this._renderLockCell}
|
||||
/>
|
||||
<RVColumn
|
||||
label={<Trans>X</Trans>}
|
||||
dataKey="x"
|
||||
width={width * 0.1}
|
||||
width={Math.max(
|
||||
width * 0.1,
|
||||
minimumWidths.numberProperty
|
||||
)}
|
||||
className={'tableColumn'}
|
||||
/>
|
||||
<RVColumn
|
||||
label={<Trans>Y</Trans>}
|
||||
dataKey="y"
|
||||
width={width * 0.1}
|
||||
width={Math.max(
|
||||
width * 0.1,
|
||||
minimumWidths.numberProperty
|
||||
)}
|
||||
className={'tableColumn'}
|
||||
/>
|
||||
<RVColumn
|
||||
label={<Trans>Angle</Trans>}
|
||||
dataKey="angle"
|
||||
width={width * 0.1}
|
||||
width={Math.max(
|
||||
width * 0.1,
|
||||
minimumWidths.numberProperty
|
||||
)}
|
||||
className={'tableColumn'}
|
||||
/>
|
||||
<RVColumn
|
||||
label={<Trans>Layer</Trans>}
|
||||
dataKey="layer"
|
||||
width={width * 0.2}
|
||||
width={Math.max(width * 0.2, minimumWidths.layerName)}
|
||||
className={'tableColumn'}
|
||||
/>
|
||||
<RVColumn
|
||||
label={<Trans>Z Order</Trans>}
|
||||
dataKey="zOrder"
|
||||
width={width * 0.1}
|
||||
width={Math.max(
|
||||
width * 0.1,
|
||||
minimumWidths.numberProperty
|
||||
)}
|
||||
className={'tableColumn'}
|
||||
/>
|
||||
</RVTable>
|
||||
|
@@ -190,7 +190,7 @@ export default class SelectedInstances {
|
||||
);
|
||||
}
|
||||
|
||||
getSelectionAABB(): Rectangle {
|
||||
getSelectionAABB = (): Rectangle => {
|
||||
const selectionAABB = new Rectangle();
|
||||
const selection = this.instancesSelection.getSelectedInstances();
|
||||
let instanceRect = new Rectangle();
|
||||
@@ -208,7 +208,7 @@ export default class SelectedInstances {
|
||||
}
|
||||
}
|
||||
return selectionAABB;
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const {
|
||||
|
@@ -83,6 +83,14 @@ export default class ViewPosition {
|
||||
return [x + this.viewX, y + this.viewY];
|
||||
};
|
||||
|
||||
/**
|
||||
* Convert a length from canvas referential to scene referential.
|
||||
*/
|
||||
toSceneScale = (a: number): number =>
|
||||
this.instancesEditorSettings.zoomFactor === 0
|
||||
? a
|
||||
: a / Math.abs(this.instancesEditorSettings.zoomFactor);
|
||||
|
||||
/**
|
||||
* Convert a point from the "world" coordinates (for example, an object position) to the
|
||||
* canvas coordinates.
|
||||
|
@@ -96,9 +96,9 @@ type Props = {|
|
||||
...InstancesEditorPropsWithoutSizeAndScroll,
|
||||
width: number,
|
||||
height: number,
|
||||
onViewPositionChanged: ViewPosition => void,
|
||||
onMouseMove: MouseEvent => void,
|
||||
onMouseLeave: MouseEvent => void,
|
||||
onViewPositionChanged?: ViewPosition => void,
|
||||
onMouseMove?: MouseEvent => void,
|
||||
onMouseLeave?: MouseEvent => void,
|
||||
screenType: ScreenType,
|
||||
showObjectInstancesIn3D: boolean,
|
||||
|};
|
||||
@@ -161,7 +161,7 @@ export default class InstancesEditor extends Component<Props> {
|
||||
// project can be used here for initializing stuff, but don't keep references to it.
|
||||
// Instead, create editors in _mountEditorComponents (as they will be destroyed/recreated
|
||||
// if the project changes).
|
||||
const { project } = this.props;
|
||||
const { project, onMouseMove, onMouseLeave } = this.props;
|
||||
|
||||
this.keyboardShortcuts = new KeyboardShortcuts({
|
||||
shortcutCallbacks: {
|
||||
@@ -257,12 +257,14 @@ export default class InstancesEditor extends Component<Props> {
|
||||
'mouseup',
|
||||
this.keyboardShortcuts.onMouseUp
|
||||
);
|
||||
this.pixiRenderer.view.addEventListener('mousemove', event => {
|
||||
this.props.onMouseMove(event);
|
||||
});
|
||||
this.pixiRenderer.view.addEventListener('mouseout', event => {
|
||||
this.props.onMouseLeave(event);
|
||||
});
|
||||
if (onMouseMove)
|
||||
this.pixiRenderer.view.addEventListener('mousemove', event => {
|
||||
onMouseMove(event);
|
||||
});
|
||||
if (onMouseLeave)
|
||||
this.pixiRenderer.view.addEventListener('mouseout', event => {
|
||||
onMouseLeave(event);
|
||||
});
|
||||
this.pixiRenderer.view.addEventListener('focusout', event => {
|
||||
if (this.keyboardShortcuts) {
|
||||
this.keyboardShortcuts.resetModifiers();
|
||||
@@ -368,9 +370,9 @@ export default class InstancesEditor extends Component<Props> {
|
||||
* this when the initial instances were recreated to ensure that there
|
||||
* is not mismatch between renderers and the instances that were updated.
|
||||
*/
|
||||
forceRemount() {
|
||||
forceRemount = () => {
|
||||
this._mountEditorComponents(this.props);
|
||||
}
|
||||
};
|
||||
|
||||
_mountEditorComponents(props: Props) {
|
||||
//Remove and delete any existing editor component
|
||||
@@ -558,14 +560,14 @@ export default class InstancesEditor extends Component<Props> {
|
||||
* See also ResourcesLoader and PixiResourcesLoader.
|
||||
* @param {string} objectName The name of the object for which instance must be re-rendered.
|
||||
*/
|
||||
resetInstanceRenderersFor(objectName: string) {
|
||||
resetInstanceRenderersFor = (objectName: string) => {
|
||||
if (this.instancesRenderer)
|
||||
this.instancesRenderer.resetInstanceRenderersFor(objectName);
|
||||
}
|
||||
};
|
||||
|
||||
zoomBy(value: number) {
|
||||
zoomBy = (value: number) => {
|
||||
this.setZoomFactor(this.getZoomFactor() * value);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Zoom and scroll so that the cursor stays on the same position scene-wise.
|
||||
@@ -642,7 +644,8 @@ export default class InstancesEditor extends Component<Props> {
|
||||
|
||||
if (
|
||||
!this.keyboardShortcuts.shouldMultiSelect() &&
|
||||
!this.keyboardShortcuts.shouldMoveView()
|
||||
!this.keyboardShortcuts.shouldMoveView() &&
|
||||
this.props.instancesSelection.hasSelectedInstances()
|
||||
) {
|
||||
this.props.instancesSelection.clearSelection();
|
||||
this.props.onInstancesSelected([]);
|
||||
@@ -925,7 +928,7 @@ export default class InstancesEditor extends Component<Props> {
|
||||
return this.canvasArea.getBoundingClientRect();
|
||||
}
|
||||
|
||||
zoomToFitContent() {
|
||||
zoomToFitContent = () => {
|
||||
const { initialInstances } = this.props;
|
||||
if (initialInstances.getInstancesCount() === 0) return;
|
||||
|
||||
@@ -954,9 +957,9 @@ export default class InstancesEditor extends Component<Props> {
|
||||
initialInstances.iterateOverInstances(getInstanceRectangle);
|
||||
getInstanceRectangle.delete();
|
||||
if (contentAABB) this.fitViewToRectangle(contentAABB, { adaptZoom: true });
|
||||
}
|
||||
};
|
||||
|
||||
zoomToInitialPosition() {
|
||||
zoomToInitialPosition = () => {
|
||||
const width = this.props.project.getGameResolutionWidth();
|
||||
const height = this.props.project.getGameResolutionHeight();
|
||||
const x = width / 2;
|
||||
@@ -965,9 +968,9 @@ export default class InstancesEditor extends Component<Props> {
|
||||
getRecommendedInitialZoomFactor(Math.max(height, width))
|
||||
);
|
||||
this.scrollTo(x, y);
|
||||
}
|
||||
};
|
||||
|
||||
zoomToFitSelection(instances: Array<gdInitialInstance>) {
|
||||
zoomToFitSelection = (instances: Array<gdInitialInstance>) => {
|
||||
if (instances.length === 0) return;
|
||||
const [firstInstance, ...otherInstances] = instances;
|
||||
const instanceMeasurer = this.instancesRenderer.getInstanceMeasurer();
|
||||
@@ -981,9 +984,12 @@ export default class InstancesEditor extends Component<Props> {
|
||||
);
|
||||
});
|
||||
this.fitViewToRectangle(selectedInstancesRectangle, { adaptZoom: true });
|
||||
}
|
||||
};
|
||||
|
||||
centerViewOnLastInstance(instances: Array<gdInitialInstance>) {
|
||||
centerViewOnLastInstance = (
|
||||
instances: Array<gdInitialInstance>,
|
||||
offset?: ?[number, number]
|
||||
) => {
|
||||
if (instances.length === 0) return;
|
||||
|
||||
const instanceMeasurer = this.instancesRenderer.getInstanceMeasurer();
|
||||
@@ -992,7 +998,8 @@ export default class InstancesEditor extends Component<Props> {
|
||||
new Rectangle()
|
||||
);
|
||||
this.fitViewToRectangle(lastInstanceRectangle, { adaptZoom: false });
|
||||
}
|
||||
if (offset) this.scrollBy(offset[0], offset[1]);
|
||||
};
|
||||
|
||||
getLastContextMenuSceneCoordinates = () => {
|
||||
return this.viewPosition.toSceneCoordinates(
|
||||
@@ -1008,7 +1015,7 @@ export default class InstancesEditor extends Component<Props> {
|
||||
);
|
||||
};
|
||||
|
||||
getViewPosition = () /*: ?ViewPosition */ => {
|
||||
getViewPosition = (): ?ViewPosition => {
|
||||
return this.viewPosition;
|
||||
};
|
||||
|
||||
|
@@ -941,4 +941,4 @@ const areEqual = (prevProps: Props, nextProps: Props): boolean =>
|
||||
prevProps.project === nextProps.project &&
|
||||
prevProps.objectsContainer === nextProps.objectsContainer;
|
||||
|
||||
export default React.memo<Props>(ObjectsList, areEqual);
|
||||
export default React.memo<Props, ObjectsListInterface>(ObjectsList, areEqual);
|
||||
|
161
newIDE/app/src/SceneEditor/EditorsDisplay.flow.js
Normal file
161
newIDE/app/src/SceneEditor/EditorsDisplay.flow.js
Normal file
@@ -0,0 +1,161 @@
|
||||
// @flow
|
||||
import { type I18n as I18nType } from '@lingui/core';
|
||||
|
||||
import {
|
||||
type GroupWithContext,
|
||||
type ObjectWithContext,
|
||||
} from '../ObjectsList/EnumerateObjects';
|
||||
import { type UnsavedChanges } from '../MainFrame/UnsavedChangesContext';
|
||||
import { type HotReloadPreviewButtonProps } from '../HotReload/HotReloadPreviewButton';
|
||||
import { type ResourceManagementProps } from '../ResourcesList/ResourceSource';
|
||||
import { type InstancesEditorSettings } from '../InstancesEditor/InstancesEditorSettings';
|
||||
import InstancesSelection from '../InstancesEditor/InstancesSelection';
|
||||
import { type ObjectEditorTab } from '../ObjectEditor/ObjectEditorDialog';
|
||||
import { type HistoryHandler } from '../VariablesList/VariablesList';
|
||||
import { type InstancesEditorShortcutsCallbacks } from '../InstancesEditor';
|
||||
import { type EditorId } from '.';
|
||||
import Rectangle from '../Utils/Rectangle';
|
||||
import ViewPosition from '../InstancesEditor/ViewPosition';
|
||||
|
||||
export type SceneEditorsDisplayProps = {|
|
||||
project: gdProject,
|
||||
layout: gdLayout,
|
||||
initialInstances: gdInitialInstancesContainer,
|
||||
instancesSelection: InstancesSelection,
|
||||
selectedLayer: string,
|
||||
onSelectInstances: (
|
||||
instances: Array<gdInitialInstance>,
|
||||
multiSelect: boolean,
|
||||
targetPosition?: 'center' | 'upperCenter'
|
||||
) => void,
|
||||
editInstanceVariables: (instance: ?gdInitialInstance) => void,
|
||||
editObjectByName: (objectName: string, initialTab?: ObjectEditorTab) => void,
|
||||
onEditObject: gdObject => void,
|
||||
selectedObjectNames: string[],
|
||||
renamedObjectWithContext: ?ObjectWithContext,
|
||||
onSelectLayer: (layerName: string) => void,
|
||||
editLayerEffects: (layer: ?gdLayer) => void,
|
||||
editLayer: (layer: ?gdLayer) => void,
|
||||
onRemoveLayer: (layerName: string, done: (boolean) => void) => void,
|
||||
onRenameLayer: (
|
||||
oldName: string,
|
||||
newName: string,
|
||||
done: (boolean) => void
|
||||
) => void,
|
||||
onObjectCreated: gdObject => void,
|
||||
onObjectSelected: (?ObjectWithContext) => void,
|
||||
onExportObject: (object: ?gdObject) => void,
|
||||
onDeleteObject: (
|
||||
i18n: I18nType,
|
||||
objectWithContext: ObjectWithContext,
|
||||
cb: (boolean) => void
|
||||
) => void,
|
||||
onAddObjectInstance: (
|
||||
objectName: string,
|
||||
targetPosition?: 'center' | 'upperCenter'
|
||||
) => void,
|
||||
onRenameObjectStart: (?ObjectWithContext) => void,
|
||||
onRenameObjectFinish: (
|
||||
objectWithContext: ObjectWithContext,
|
||||
newName: string,
|
||||
done: (boolean) => void
|
||||
) => void,
|
||||
onEditObjectGroup: (?gdObjectGroup) => void,
|
||||
onDeleteObjectGroup: (
|
||||
groupWithContext: GroupWithContext,
|
||||
done: (boolean) => void
|
||||
) => void,
|
||||
onRenameObjectGroup: (
|
||||
groupWithContext: GroupWithContext,
|
||||
newName: string,
|
||||
done: (boolean) => void
|
||||
) => void,
|
||||
canRenameObjectGroup: (
|
||||
newName: string,
|
||||
global: boolean,
|
||||
i18n: I18nType
|
||||
) => boolean,
|
||||
canObjectOrGroupBeGlobal: (
|
||||
i18n: I18nType,
|
||||
objectOrGroupName: string
|
||||
) => boolean,
|
||||
canObjectOrGroupUseNewName: (
|
||||
newName: string,
|
||||
global: boolean,
|
||||
i18n: I18nType
|
||||
) => boolean,
|
||||
|
||||
updateBehaviorsSharedData: () => void,
|
||||
onInstancesAdded: (Array<gdInitialInstance>) => void,
|
||||
onInstancesSelected: (Array<gdInitialInstance>) => void,
|
||||
onInstanceDoubleClicked: gdInitialInstance => void,
|
||||
onInstancesMoved: (Array<gdInitialInstance>) => void,
|
||||
onInstancesResized: (Array<gdInitialInstance>) => void,
|
||||
onInstancesRotated: (Array<gdInitialInstance>) => void,
|
||||
isInstanceOf3DObject: gdInitialInstance => boolean,
|
||||
onSelectAllInstancesOfObjectInLayout: string => void,
|
||||
|
||||
canInstallPrivateAsset: () => boolean,
|
||||
|
||||
instancesEditorSettings: InstancesEditorSettings,
|
||||
onInstancesEditorSettingsMutated: InstancesEditorSettings => void,
|
||||
|
||||
historyHandler: HistoryHandler,
|
||||
unsavedChanges?: ?UnsavedChanges,
|
||||
hotReloadPreviewButtonProps: HotReloadPreviewButtonProps,
|
||||
onContextMenu: (
|
||||
x: number,
|
||||
y: number,
|
||||
ignoreSelectedObjectsForContextMenu?: boolean
|
||||
) => void,
|
||||
|
||||
resourceManagementProps: ResourceManagementProps,
|
||||
isActive: boolean,
|
||||
instancesEditorShortcutsCallbacks: InstancesEditorShortcutsCallbacks,
|
||||
|
||||
onOpenedEditorsChanged: () => void,
|
||||
|};
|
||||
|
||||
export type SceneEditorsDisplayInterface = {|
|
||||
getName: () => 'mosaic' | 'swipeableDrawer',
|
||||
forceUpdateInstancesList: () => void,
|
||||
forceUpdateInstancesPropertiesEditor: () => void,
|
||||
forceUpdateObjectsList: () => void,
|
||||
forceUpdateObjectGroupsList: () => void,
|
||||
forceUpdateLayersList: () => void,
|
||||
openNewObjectDialog: () => void,
|
||||
toggleEditorView: (editorId: EditorId) => void,
|
||||
isEditorVisible: (editorId: EditorId) => boolean,
|
||||
viewControls: {|
|
||||
zoomBy: (factor: number) => void,
|
||||
setZoomFactor: (factor: number) => void,
|
||||
zoomToInitialPosition: () => void,
|
||||
zoomToFitContent: () => void,
|
||||
zoomToFitSelection: (Array<gdInitialInstance>) => void,
|
||||
centerViewOnLastInstance: (
|
||||
Array<gdInitialInstance>,
|
||||
offset?: ?[number, number]
|
||||
) => void,
|
||||
getLastCursorSceneCoordinates: () => [number, number],
|
||||
getLastContextMenuSceneCoordinates: () => [number, number],
|
||||
getViewPosition: () => ?ViewPosition,
|
||||
|},
|
||||
instancesHandlers: {|
|
||||
getSelectionAABB: () => Rectangle,
|
||||
addInstances: (
|
||||
pos: [number, number],
|
||||
objectNames: Array<string>,
|
||||
layer: string
|
||||
) => Array<gdInitialInstance>,
|
||||
clearHighlightedInstance: () => void,
|
||||
resetInstanceRenderersFor: (objectName: string) => void,
|
||||
forceRemountInstancesRenderers: () => void,
|
||||
addSerializedInstances: ({|
|
||||
position: [number, number],
|
||||
copyReferential: [number, number],
|
||||
serializedInstances: Array<Object>,
|
||||
preventSnapToGrid?: boolean,
|
||||
addInstancesInTheForeground?: boolean,
|
||||
|}) => Array<gdInitialInstance>,
|
||||
|},
|
||||
|};
|
@@ -2,24 +2,24 @@
|
||||
import { t } from '@lingui/macro';
|
||||
import { type I18n as I18nType } from '@lingui/core';
|
||||
import * as React from 'react';
|
||||
import { ToolbarGroup } from '../UI/Toolbar';
|
||||
import ToolbarSeparator from '../UI/ToolbarSeparator';
|
||||
import IconButton from '../UI/IconButton';
|
||||
import ElementWithMenu from '../UI/Menu/ElementWithMenu';
|
||||
import ToolbarCommands from './ToolbarCommands';
|
||||
import InstancesSelection from '../InstancesEditor/InstancesSelection';
|
||||
import { type MenuItemTemplate } from '../UI/Menu/Menu.flow';
|
||||
import ObjectIcon from '../UI/CustomSvgIcons/Object';
|
||||
import ObjectGroupIcon from '../UI/CustomSvgIcons/ObjectGroup';
|
||||
import EditIcon from '../UI/CustomSvgIcons/Edit';
|
||||
import InstancesListIcon from '../UI/CustomSvgIcons/InstancesList';
|
||||
import LayersIcon from '../UI/CustomSvgIcons/Layers';
|
||||
import UndoIcon from '../UI/CustomSvgIcons/Undo';
|
||||
import RedoIcon from '../UI/CustomSvgIcons/Redo';
|
||||
import TrashIcon from '../UI/CustomSvgIcons/Trash';
|
||||
import GridIcon from '../UI/CustomSvgIcons/Grid';
|
||||
import ZoomInIcon from '../UI/CustomSvgIcons/ZoomIn';
|
||||
import EditSceneIcon from '../UI/CustomSvgIcons/EditScene';
|
||||
import { ToolbarGroup } from '../../UI/Toolbar';
|
||||
import ToolbarSeparator from '../../UI/ToolbarSeparator';
|
||||
import IconButton from '../../UI/IconButton';
|
||||
import ElementWithMenu from '../../UI/Menu/ElementWithMenu';
|
||||
import ToolbarCommands from '../ToolbarCommands';
|
||||
import InstancesSelection from '../../InstancesEditor/InstancesSelection';
|
||||
import { type MenuItemTemplate } from '../../UI/Menu/Menu.flow';
|
||||
import ObjectIcon from '../../UI/CustomSvgIcons/Object';
|
||||
import ObjectGroupIcon from '../../UI/CustomSvgIcons/ObjectGroup';
|
||||
import EditIcon from '../../UI/CustomSvgIcons/Edit';
|
||||
import InstancesListIcon from '../../UI/CustomSvgIcons/InstancesList';
|
||||
import LayersIcon from '../../UI/CustomSvgIcons/Layers';
|
||||
import UndoIcon from '../../UI/CustomSvgIcons/Undo';
|
||||
import RedoIcon from '../../UI/CustomSvgIcons/Redo';
|
||||
import TrashIcon from '../../UI/CustomSvgIcons/Trash';
|
||||
import GridIcon from '../../UI/CustomSvgIcons/Grid';
|
||||
import ZoomInIcon from '../../UI/CustomSvgIcons/ZoomIn';
|
||||
import EditSceneIcon from '../../UI/CustomSvgIcons/EditScene';
|
||||
|
||||
type Props = {|
|
||||
toggleObjectsList: () => void,
|
422
newIDE/app/src/SceneEditor/MosaicEditorsDisplay/index.js
Normal file
422
newIDE/app/src/SceneEditor/MosaicEditorsDisplay/index.js
Normal file
@@ -0,0 +1,422 @@
|
||||
// @flow
|
||||
|
||||
import * as React from 'react';
|
||||
import { t } from '@lingui/macro';
|
||||
import { I18n } from '@lingui/react';
|
||||
import { type I18n as I18nType } from '@lingui/core';
|
||||
|
||||
import { useResponsiveWindowWidth } from '../../UI/Reponsive/ResponsiveWindowMeasurer';
|
||||
import PreferencesContext from '../../MainFrame/Preferences/PreferencesContext';
|
||||
import EditorMosaic from '../../UI/EditorMosaic';
|
||||
import InstancesEditor from '../../InstancesEditor';
|
||||
import InstancePropertiesEditor, {
|
||||
type InstancePropertiesEditorInterface,
|
||||
} from '../../InstancesEditor/InstancePropertiesEditor';
|
||||
import LayersList, { type LayersListInterface } from '../../LayersList';
|
||||
import FullSizeInstancesEditorWithScrollbars from '../../InstancesEditor/FullSizeInstancesEditorWithScrollbars';
|
||||
import TagsButton from '../../UI/EditorMosaic/TagsButton';
|
||||
import CloseButton from '../../UI/EditorMosaic/CloseButton';
|
||||
import ObjectsList, { type ObjectsListInterface } from '../../ObjectsList';
|
||||
import ObjectGroupsList from '../../ObjectGroupsList';
|
||||
import InstancesList from '../../InstancesEditor/InstancesList';
|
||||
import ObjectsRenderingService from '../../ObjectsRendering/ObjectsRenderingService';
|
||||
|
||||
import {
|
||||
getTagsFromString,
|
||||
buildTagsMenuTemplate,
|
||||
type SelectedTags,
|
||||
} from '../../Utils/TagsHelper';
|
||||
import { enumerateObjects } from '../../ObjectsList/EnumerateObjects';
|
||||
import Rectangle from '../../Utils/Rectangle';
|
||||
import { type EditorId } from '..';
|
||||
import {
|
||||
type SceneEditorsDisplayProps,
|
||||
type SceneEditorsDisplayInterface,
|
||||
} from '../EditorsDisplay.flow';
|
||||
|
||||
const initialMosaicEditorNodes = {
|
||||
direction: 'row',
|
||||
first: 'properties',
|
||||
splitPercentage: 23,
|
||||
second: {
|
||||
direction: 'row',
|
||||
first: 'instances-editor',
|
||||
second: 'objects-list',
|
||||
splitPercentage: 77,
|
||||
},
|
||||
};
|
||||
|
||||
const noop = () => {};
|
||||
|
||||
const defaultPanelConfigByEditor = {
|
||||
'objects-list': {
|
||||
position: 'end',
|
||||
splitPercentage: 75,
|
||||
direction: 'column',
|
||||
},
|
||||
properties: {
|
||||
position: 'start',
|
||||
splitPercentage: 25,
|
||||
direction: 'column',
|
||||
},
|
||||
'object-groups-list': {
|
||||
position: 'end',
|
||||
splitPercentage: 75,
|
||||
direction: 'column',
|
||||
},
|
||||
'instances-list': {
|
||||
position: 'end',
|
||||
splitPercentage: 75,
|
||||
direction: 'row',
|
||||
},
|
||||
'layers-list': {
|
||||
position: 'end',
|
||||
splitPercentage: 75,
|
||||
direction: 'row',
|
||||
},
|
||||
};
|
||||
|
||||
// Forward ref to allow Scene editor to force update some editors
|
||||
const MosaicEditorsDisplay = React.forwardRef<
|
||||
SceneEditorsDisplayProps,
|
||||
SceneEditorsDisplayInterface
|
||||
>((props, ref) => {
|
||||
const {
|
||||
project,
|
||||
layout,
|
||||
initialInstances,
|
||||
selectedLayer,
|
||||
onSelectInstances,
|
||||
} = props;
|
||||
const windowWidth = useResponsiveWindowWidth();
|
||||
const {
|
||||
getDefaultEditorMosaicNode,
|
||||
setDefaultEditorMosaicNode,
|
||||
} = React.useContext(PreferencesContext);
|
||||
const selectedInstances = props.instancesSelection.getSelectedInstances();
|
||||
const [
|
||||
selectedObjectTags,
|
||||
setSelectedObjectTags,
|
||||
] = React.useState<SelectedTags>([]);
|
||||
|
||||
const instancesPropertiesEditorRef = React.useRef<?InstancePropertiesEditorInterface>(
|
||||
null
|
||||
);
|
||||
const layersListRef = React.useRef<?LayersListInterface>(null);
|
||||
const instancesListRef = React.useRef<?InstancesList>(null);
|
||||
const editorRef = React.useRef<?InstancesEditor>(null);
|
||||
const objectsListRef = React.useRef<?ObjectsListInterface>(null);
|
||||
const editorMosaicRef = React.useRef<?EditorMosaic>(null);
|
||||
const objectGroupsListRef = React.useRef<?ObjectGroupsList>(null);
|
||||
|
||||
const forceUpdateInstancesPropertiesEditor = React.useCallback(() => {
|
||||
if (instancesPropertiesEditorRef.current)
|
||||
instancesPropertiesEditorRef.current.forceUpdate();
|
||||
}, []);
|
||||
const forceUpdateInstancesList = React.useCallback(() => {
|
||||
if (instancesListRef.current) instancesListRef.current.forceUpdate();
|
||||
}, []);
|
||||
const forceUpdateObjectsList = React.useCallback(() => {
|
||||
if (objectsListRef.current) objectsListRef.current.forceUpdateList();
|
||||
}, []);
|
||||
const forceUpdateObjectGroupsList = React.useCallback(() => {
|
||||
if (objectGroupsListRef.current) objectGroupsListRef.current.forceUpdate();
|
||||
}, []);
|
||||
const forceUpdateLayersList = React.useCallback(() => {
|
||||
if (layersListRef.current) layersListRef.current.forceUpdate();
|
||||
}, []);
|
||||
const getInstanceSize = React.useCallback((instance: gdInitialInstance) => {
|
||||
if (!editorRef.current) return [0, 0, 0];
|
||||
|
||||
return editorRef.current.getInstanceSize(instance);
|
||||
}, []);
|
||||
const openNewObjectDialog = React.useCallback(() => {
|
||||
if (!objectsListRef.current) return;
|
||||
|
||||
objectsListRef.current.openNewObjectDialog();
|
||||
}, []);
|
||||
const toggleEditorView = React.useCallback((editorId: EditorId) => {
|
||||
if (!editorMosaicRef.current) return;
|
||||
const config = defaultPanelConfigByEditor[editorId];
|
||||
editorMosaicRef.current.toggleEditor(
|
||||
editorId,
|
||||
config.position,
|
||||
config.splitPercentage,
|
||||
config.direction
|
||||
);
|
||||
}, []);
|
||||
const isEditorVisible = React.useCallback((editorId: EditorId) => {
|
||||
if (!editorMosaicRef.current) return false;
|
||||
return editorMosaicRef.current.getOpenedEditorNames().includes(editorId);
|
||||
}, []);
|
||||
|
||||
React.useImperativeHandle(ref, () => {
|
||||
const { current: editor } = editorRef;
|
||||
return {
|
||||
getName: () => 'mosaic',
|
||||
forceUpdateInstancesList,
|
||||
forceUpdateInstancesPropertiesEditor,
|
||||
forceUpdateObjectsList,
|
||||
forceUpdateObjectGroupsList,
|
||||
forceUpdateLayersList,
|
||||
openNewObjectDialog,
|
||||
toggleEditorView,
|
||||
isEditorVisible,
|
||||
viewControls: {
|
||||
zoomBy: editor ? editor.zoomBy : noop,
|
||||
setZoomFactor: editor ? editor.setZoomFactor : noop,
|
||||
zoomToInitialPosition: editor ? editor.zoomToInitialPosition : noop,
|
||||
zoomToFitContent: editor ? editor.zoomToFitContent : noop,
|
||||
zoomToFitSelection: editor ? editor.zoomToFitSelection : noop,
|
||||
centerViewOnLastInstance: editor
|
||||
? editor.centerViewOnLastInstance
|
||||
: noop,
|
||||
getLastCursorSceneCoordinates: editor
|
||||
? editor.getLastCursorSceneCoordinates
|
||||
: () => [0, 0],
|
||||
getLastContextMenuSceneCoordinates: editor
|
||||
? editor.getLastContextMenuSceneCoordinates
|
||||
: () => [0, 0],
|
||||
getViewPosition: editor ? editor.getViewPosition : noop,
|
||||
},
|
||||
instancesHandlers: {
|
||||
getSelectionAABB: editor
|
||||
? editor.selectedInstances.getSelectionAABB
|
||||
: () => new Rectangle(),
|
||||
addInstances: editor ? editor.addInstances : () => [],
|
||||
clearHighlightedInstance: editor
|
||||
? editor.clearHighlightedInstance
|
||||
: noop,
|
||||
resetInstanceRenderersFor: editor
|
||||
? editor.resetInstanceRenderersFor
|
||||
: noop,
|
||||
forceRemountInstancesRenderers: editor ? editor.forceRemount : noop,
|
||||
addSerializedInstances: editor
|
||||
? editor.addSerializedInstances
|
||||
: () => [],
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
const selectInstances = React.useCallback(
|
||||
(instances: Array<gdInitialInstance>, multiSelect: boolean) => {
|
||||
onSelectInstances(instances, multiSelect);
|
||||
forceUpdateInstancesList();
|
||||
forceUpdateInstancesPropertiesEditor();
|
||||
},
|
||||
[
|
||||
forceUpdateInstancesList,
|
||||
forceUpdateInstancesPropertiesEditor,
|
||||
onSelectInstances,
|
||||
]
|
||||
);
|
||||
|
||||
const getAllObjectTags = React.useCallback(
|
||||
(): Array<string> => {
|
||||
const tagsSet: Set<string> = new Set();
|
||||
enumerateObjects(project, layout).allObjectsList.forEach(({ object }) => {
|
||||
getTagsFromString(object.getTags()).forEach(tag => tagsSet.add(tag));
|
||||
});
|
||||
|
||||
return Array.from(tagsSet);
|
||||
},
|
||||
[project, layout]
|
||||
);
|
||||
|
||||
const buildObjectTagsMenuTemplate = React.useCallback(
|
||||
(i18n: I18nType): Array<any> => {
|
||||
return buildTagsMenuTemplate({
|
||||
noTagLabel: i18n._(t`No tags - add a tag to an object first`),
|
||||
getAllTags: getAllObjectTags,
|
||||
selectedTags: selectedObjectTags,
|
||||
onChange: setSelectedObjectTags,
|
||||
});
|
||||
},
|
||||
[selectedObjectTags, getAllObjectTags]
|
||||
);
|
||||
|
||||
const editors = {
|
||||
properties: {
|
||||
type: 'secondary',
|
||||
title: t`Properties`,
|
||||
renderEditor: () => (
|
||||
<I18n>
|
||||
{({ i18n }) => (
|
||||
<InstancePropertiesEditor
|
||||
i18n={i18n}
|
||||
project={project}
|
||||
layout={layout}
|
||||
instances={selectedInstances}
|
||||
editInstanceVariables={props.editInstanceVariables}
|
||||
onEditObjectByName={props.editObjectByName}
|
||||
onInstancesModified={forceUpdateInstancesList}
|
||||
onGetInstanceSize={getInstanceSize}
|
||||
ref={instancesPropertiesEditorRef}
|
||||
unsavedChanges={props.unsavedChanges}
|
||||
historyHandler={props.historyHandler}
|
||||
/>
|
||||
)}
|
||||
</I18n>
|
||||
),
|
||||
},
|
||||
'layers-list': {
|
||||
type: 'secondary',
|
||||
title: t`Layers`,
|
||||
renderEditor: () => (
|
||||
<LayersList
|
||||
project={project}
|
||||
selectedLayer={selectedLayer}
|
||||
onSelectLayer={props.onSelectLayer}
|
||||
onEditLayerEffects={props.editLayerEffects}
|
||||
onEditLayer={props.editLayer}
|
||||
onRemoveLayer={props.onRemoveLayer}
|
||||
onRenameLayer={props.onRenameLayer}
|
||||
onCreateLayer={forceUpdateInstancesPropertiesEditor}
|
||||
layersContainer={layout}
|
||||
unsavedChanges={props.unsavedChanges}
|
||||
ref={layersListRef}
|
||||
hotReloadPreviewButtonProps={props.hotReloadPreviewButtonProps}
|
||||
/>
|
||||
),
|
||||
},
|
||||
'instances-list': {
|
||||
type: 'secondary',
|
||||
title: t`Instances List`,
|
||||
renderEditor: () => (
|
||||
<InstancesList
|
||||
instances={initialInstances}
|
||||
selectedInstances={selectedInstances}
|
||||
onSelectInstances={selectInstances}
|
||||
ref={instancesListRef}
|
||||
/>
|
||||
),
|
||||
},
|
||||
'instances-editor': {
|
||||
type: 'primary',
|
||||
noTitleBar: true,
|
||||
renderEditor: () => (
|
||||
<FullSizeInstancesEditorWithScrollbars
|
||||
project={project}
|
||||
layout={layout}
|
||||
selectedLayer={selectedLayer}
|
||||
initialInstances={initialInstances}
|
||||
instancesEditorSettings={props.instancesEditorSettings}
|
||||
onInstancesEditorSettingsMutated={
|
||||
props.onInstancesEditorSettingsMutated
|
||||
}
|
||||
instancesSelection={props.instancesSelection}
|
||||
onInstancesAdded={props.onInstancesAdded}
|
||||
onInstancesSelected={props.onInstancesSelected}
|
||||
onInstanceDoubleClicked={props.onInstanceDoubleClicked}
|
||||
onInstancesMoved={props.onInstancesMoved}
|
||||
onInstancesResized={props.onInstancesResized}
|
||||
onInstancesRotated={props.onInstancesRotated}
|
||||
selectedObjectNames={props.selectedObjectNames}
|
||||
onContextMenu={props.onContextMenu}
|
||||
isInstanceOf3DObject={props.isInstanceOf3DObject}
|
||||
instancesEditorShortcutsCallbacks={
|
||||
props.instancesEditorShortcutsCallbacks
|
||||
}
|
||||
wrappedEditorRef={editor => {
|
||||
editorRef.current = editor;
|
||||
}}
|
||||
pauseRendering={!props.isActive}
|
||||
/>
|
||||
),
|
||||
},
|
||||
'objects-list': {
|
||||
type: 'secondary',
|
||||
title: t`Objects`,
|
||||
toolbarControls: [
|
||||
<TagsButton
|
||||
key="tags"
|
||||
buildMenuTemplate={buildObjectTagsMenuTemplate}
|
||||
/>,
|
||||
<CloseButton key="close" />,
|
||||
],
|
||||
renderEditor: () => (
|
||||
<I18n>
|
||||
{({ i18n }) => (
|
||||
<ObjectsList
|
||||
getThumbnail={ObjectsRenderingService.getThumbnail.bind(
|
||||
ObjectsRenderingService
|
||||
)}
|
||||
project={project}
|
||||
objectsContainer={layout}
|
||||
layout={layout}
|
||||
onSelectAllInstancesOfObjectInLayout={
|
||||
props.onSelectAllInstancesOfObjectInLayout
|
||||
}
|
||||
resourceManagementProps={props.resourceManagementProps}
|
||||
selectedObjectNames={props.selectedObjectNames}
|
||||
canInstallPrivateAsset={props.canInstallPrivateAsset}
|
||||
onEditObject={props.onEditObject}
|
||||
onExportObject={props.onExportObject}
|
||||
onDeleteObject={(objectWithContext, cb) =>
|
||||
props.onDeleteObject(i18n, objectWithContext, cb)
|
||||
}
|
||||
canRenameObject={(newName, global) =>
|
||||
props.canObjectOrGroupUseNewName(newName, global, i18n)
|
||||
}
|
||||
onObjectCreated={props.onObjectCreated}
|
||||
onObjectSelected={props.onObjectSelected}
|
||||
renamedObjectWithContext={props.renamedObjectWithContext}
|
||||
onRenameObjectStart={props.onRenameObjectStart}
|
||||
onRenameObjectFinish={props.onRenameObjectFinish}
|
||||
onAddObjectInstance={props.onAddObjectInstance}
|
||||
onObjectPasted={props.updateBehaviorsSharedData}
|
||||
selectedObjectTags={selectedObjectTags}
|
||||
beforeSetAsGlobalObject={objectName =>
|
||||
props.canObjectOrGroupBeGlobal(i18n, objectName)
|
||||
}
|
||||
onChangeSelectedObjectTags={setSelectedObjectTags}
|
||||
getAllObjectTags={getAllObjectTags}
|
||||
ref={objectsListRef}
|
||||
unsavedChanges={props.unsavedChanges}
|
||||
hotReloadPreviewButtonProps={props.hotReloadPreviewButtonProps}
|
||||
/>
|
||||
)}
|
||||
</I18n>
|
||||
),
|
||||
},
|
||||
'object-groups-list': {
|
||||
type: 'secondary',
|
||||
title: t`Object Groups`,
|
||||
renderEditor: () => (
|
||||
<I18n>
|
||||
{({ i18n }) => (
|
||||
<ObjectGroupsList
|
||||
ref={objectGroupsListRef}
|
||||
globalObjectGroups={project.getObjectGroups()}
|
||||
objectGroups={layout.getObjectGroups()}
|
||||
onEditGroup={props.onEditObjectGroup}
|
||||
onDeleteGroup={props.onDeleteObjectGroup}
|
||||
onRenameGroup={props.onRenameObjectGroup}
|
||||
canRenameGroup={(newName, global) =>
|
||||
props.canRenameObjectGroup(newName, global, i18n)
|
||||
}
|
||||
beforeSetAsGlobalGroup={groupName =>
|
||||
props.canObjectOrGroupBeGlobal(i18n, groupName)
|
||||
}
|
||||
unsavedChanges={props.unsavedChanges}
|
||||
/>
|
||||
)}
|
||||
</I18n>
|
||||
),
|
||||
},
|
||||
};
|
||||
return (
|
||||
<EditorMosaic
|
||||
editors={editors}
|
||||
limitToOneSecondaryEditor={windowWidth === 'small'}
|
||||
initialNodes={
|
||||
getDefaultEditorMosaicNode('scene-editor') || initialMosaicEditorNodes
|
||||
}
|
||||
onOpenedEditorsChanged={props.onOpenedEditorsChanged}
|
||||
onPersistNodes={node => setDefaultEditorMosaicNode('scene-editor', node)}
|
||||
ref={editorMosaicRef}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
export default MosaicEditorsDisplay;
|
@@ -0,0 +1,74 @@
|
||||
// @flow
|
||||
|
||||
import * as React from 'react';
|
||||
import { Toolbar, ToolbarGroup } from '../../UI/Toolbar';
|
||||
import ObjectIcon from '../../UI/CustomSvgIcons/Object';
|
||||
import ObjectGroupIcon from '../../UI/CustomSvgIcons/ObjectGroup';
|
||||
import EditIcon from '../../UI/CustomSvgIcons/Edit';
|
||||
import InstancesListIcon from '../../UI/CustomSvgIcons/InstancesList';
|
||||
import LayersIcon from '../../UI/CustomSvgIcons/Layers';
|
||||
import IconButton from '../../UI/IconButton';
|
||||
import { type EditorId } from '..';
|
||||
import Paper from '../../UI/Paper';
|
||||
|
||||
const styles = { container: { padding: 4, paddingBottom: 8 } };
|
||||
|
||||
type Props = {|
|
||||
selectedEditorId: ?EditorId,
|
||||
onSelectEditor: EditorId => void,
|
||||
|};
|
||||
|
||||
const editors = {
|
||||
'objects-list': {
|
||||
buttonId: 'toolbar-open-objects-panel-button',
|
||||
icon: <ObjectIcon />,
|
||||
},
|
||||
'object-groups-list': {
|
||||
buttonId: 'toolbar-open-object-groups-panel-button',
|
||||
icon: <ObjectGroupIcon />,
|
||||
},
|
||||
properties: {
|
||||
buttonId: 'toolbar-open-properties-panel-button',
|
||||
icon: <EditIcon />,
|
||||
},
|
||||
'instances-list': {
|
||||
buttonId: 'toolbar-open-instances-list-panel-button',
|
||||
icon: <InstancesListIcon />,
|
||||
},
|
||||
'layers-list': {
|
||||
buttonId: 'toolbar-open-layers-panel-button',
|
||||
icon: <LayersIcon />,
|
||||
},
|
||||
};
|
||||
|
||||
const BottomToolbar = (props: Props) => {
|
||||
return (
|
||||
<Paper background="medium" square style={styles.container}>
|
||||
<Toolbar>
|
||||
<ToolbarGroup>
|
||||
{Object.keys(editors).map(editorId => {
|
||||
const { icon, buttonId } = editors[editorId];
|
||||
const isSelected = props.selectedEditorId === editorId;
|
||||
return (
|
||||
<IconButton
|
||||
color="default"
|
||||
key={editorId}
|
||||
disableRipple
|
||||
disableFocusRipple
|
||||
id={buttonId}
|
||||
onClick={() => {
|
||||
props.onSelectEditor(editorId);
|
||||
}}
|
||||
selected={isSelected}
|
||||
>
|
||||
{icon}
|
||||
</IconButton>
|
||||
);
|
||||
})}
|
||||
</ToolbarGroup>
|
||||
</Toolbar>
|
||||
</Paper>
|
||||
);
|
||||
};
|
||||
|
||||
export default BottomToolbar;
|
@@ -0,0 +1,131 @@
|
||||
// @flow
|
||||
|
||||
import * as React from 'react';
|
||||
import Text from '../../UI/Text';
|
||||
import { Column, Line } from '../../UI/Grid';
|
||||
import { getBackgroundColor } from '../../UI/Paper';
|
||||
import { ColumnStackLayout } from '../../UI/Layout';
|
||||
import GDevelopThemeContext from '../../UI/Theme/GDevelopThemeContext';
|
||||
import useSwipeGesture from './UseSwipeGesture';
|
||||
|
||||
const topMargin = 52; // This is equal to the height of the bottom bar.
|
||||
|
||||
const styles = {
|
||||
container: {
|
||||
flexDirection: 'column',
|
||||
overflow: 'hidden',
|
||||
},
|
||||
childrenContainer: {
|
||||
display: 'flex',
|
||||
flex: 1,
|
||||
overflowY: 'auto',
|
||||
overflowX: 'hidden',
|
||||
},
|
||||
topBarContainer: {
|
||||
borderRadius: '8px 8px 0 0',
|
||||
padding: 4,
|
||||
},
|
||||
topBarHandleContainer: {
|
||||
height: 4,
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
},
|
||||
topBarHandle: {
|
||||
height: 4,
|
||||
width: '40%',
|
||||
borderRadius: 2,
|
||||
},
|
||||
};
|
||||
|
||||
type SwipeableDrawerTopBarProps = {|
|
||||
title: React.Node,
|
||||
onClick: () => void,
|
||||
onSwipeUp: () => void,
|
||||
onSwipeDown: () => void,
|
||||
controls: ?React.Node,
|
||||
|};
|
||||
|
||||
const SwipeableDrawerTopBar = (props: SwipeableDrawerTopBarProps) => {
|
||||
const gdevelopTheme = React.useContext(GDevelopThemeContext);
|
||||
const { onTouchStart, onTouchEnd } = useSwipeGesture({
|
||||
onSwipeUp: props.onSwipeUp,
|
||||
onSwipeDown: props.onSwipeDown,
|
||||
});
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
...styles.topBarContainer,
|
||||
backgroundColor: getBackgroundColor(gdevelopTheme, 'light'),
|
||||
}}
|
||||
onClick={props.onClick}
|
||||
onTouchStart={onTouchStart}
|
||||
onTouchEnd={onTouchEnd}
|
||||
>
|
||||
<ColumnStackLayout noMargin>
|
||||
<div style={styles.topBarHandleContainer}>
|
||||
<span
|
||||
style={{
|
||||
...styles.topBarHandle,
|
||||
backgroundColor: gdevelopTheme.swipeableDrawer.topBar.pillColor,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<Column>
|
||||
<Line
|
||||
noMargin
|
||||
justifyContent={props.controls ? 'space-between' : 'flex-start'}
|
||||
alignItems="center"
|
||||
>
|
||||
<Text size="sub-title" noMargin>
|
||||
{props.title}
|
||||
</Text>
|
||||
{props.controls}
|
||||
</Line>
|
||||
</Column>
|
||||
</ColumnStackLayout>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
type DrawerOpeningState = 'closed' | 'halfOpen' | 'open';
|
||||
|
||||
type Props = {|
|
||||
maxHeight: number,
|
||||
children: React.Node,
|
||||
title: React.Node,
|
||||
openingState: DrawerOpeningState,
|
||||
setOpeningState: DrawerOpeningState => void,
|
||||
topBarControls: ?React.Node,
|
||||
|};
|
||||
|
||||
const SwipeableDrawer = (props: Props) => {
|
||||
const { openingState, setOpeningState } = props;
|
||||
const height =
|
||||
openingState === 'closed'
|
||||
? 0
|
||||
: openingState === 'halfOpen'
|
||||
? props.maxHeight * 0.42 // Empirical value that leaves space in both editor and canvas.
|
||||
: props.maxHeight - topMargin;
|
||||
const display = openingState === 'closed' ? 'none' : 'flex';
|
||||
return (
|
||||
<div style={{ ...styles.container, height, display }}>
|
||||
<SwipeableDrawerTopBar
|
||||
title={props.title}
|
||||
onClick={() => setOpeningState('closed')}
|
||||
onSwipeUp={() => {
|
||||
if (openingState === 'halfOpen') setOpeningState('open');
|
||||
}}
|
||||
onSwipeDown={() => {
|
||||
if (openingState === 'halfOpen') setOpeningState('closed');
|
||||
else if (openingState === 'open') setOpeningState('halfOpen');
|
||||
}}
|
||||
controls={props.topBarControls}
|
||||
/>
|
||||
<div style={styles.childrenContainer}>{props.children}</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default SwipeableDrawer;
|
@@ -0,0 +1,163 @@
|
||||
// @flow
|
||||
import { t } from '@lingui/macro';
|
||||
import { type I18n as I18nType } from '@lingui/core';
|
||||
import * as React from 'react';
|
||||
import { ToolbarGroup } from '../../UI/Toolbar';
|
||||
import ToolbarSeparator from '../../UI/ToolbarSeparator';
|
||||
import IconButton from '../../UI/IconButton';
|
||||
import ElementWithMenu from '../../UI/Menu/ElementWithMenu';
|
||||
import ToolbarCommands from '../ToolbarCommands';
|
||||
import InstancesSelection from '../../InstancesEditor/InstancesSelection';
|
||||
import { type MenuItemTemplate } from '../../UI/Menu/Menu.flow';
|
||||
import UndoIcon from '../../UI/CustomSvgIcons/Undo';
|
||||
import RedoIcon from '../../UI/CustomSvgIcons/Redo';
|
||||
import TrashIcon from '../../UI/CustomSvgIcons/Trash';
|
||||
import GridIcon from '../../UI/CustomSvgIcons/Grid';
|
||||
import ZoomInIcon from '../../UI/CustomSvgIcons/ZoomIn';
|
||||
import EditSceneIcon from '../../UI/CustomSvgIcons/EditScene';
|
||||
|
||||
type Props = {|
|
||||
toggleObjectsList: () => void,
|
||||
toggleObjectGroupsList: () => void,
|
||||
toggleProperties: () => void,
|
||||
toggleInstancesList: () => void,
|
||||
toggleLayersList: () => void,
|
||||
undo: () => void,
|
||||
canUndo: boolean,
|
||||
redo: () => void,
|
||||
canRedo: boolean,
|
||||
deleteSelection: () => void,
|
||||
instancesSelection: InstancesSelection,
|
||||
isWindowMaskShown: () => boolean,
|
||||
toggleWindowMask: () => void,
|
||||
isGridShown: () => boolean,
|
||||
toggleGrid: () => void,
|
||||
openSetupGrid: () => void,
|
||||
getContextMenuZoomItems: I18nType => Array<MenuItemTemplate>,
|
||||
setZoomFactor: number => void,
|
||||
onOpenSettings: () => void,
|
||||
settingsIcon: React.Node,
|
||||
canRenameObject: boolean,
|
||||
onRenameObject: () => void,
|
||||
|};
|
||||
|
||||
const Toolbar = (props: Props) => {
|
||||
return (
|
||||
<>
|
||||
<ToolbarCommands
|
||||
toggleObjectsList={props.toggleObjectsList}
|
||||
toggleObjectGroupsList={props.toggleObjectGroupsList}
|
||||
togglePropertiesPanel={props.toggleProperties}
|
||||
toggleInstancesList={props.toggleInstancesList}
|
||||
toggleLayersList={props.toggleLayersList}
|
||||
undo={props.undo}
|
||||
canUndo={props.canUndo}
|
||||
redo={props.redo}
|
||||
canRedo={props.canRedo}
|
||||
deleteSelection={props.deleteSelection}
|
||||
toggleWindowMask={props.toggleWindowMask}
|
||||
toggleGrid={props.toggleGrid}
|
||||
setupGrid={props.openSetupGrid}
|
||||
canDeleteSelection={
|
||||
props.instancesSelection.getSelectedInstances().length !== 0
|
||||
}
|
||||
canRenameObject={props.canRenameObject}
|
||||
onRenameObject={props.onRenameObject}
|
||||
/>
|
||||
<IconButton
|
||||
size="small"
|
||||
color="default"
|
||||
onClick={props.undo}
|
||||
disabled={!props.canUndo}
|
||||
tooltip={t`Undo the last changes`}
|
||||
>
|
||||
<UndoIcon />
|
||||
</IconButton>
|
||||
<IconButton
|
||||
size="small"
|
||||
color="default"
|
||||
onClick={props.redo}
|
||||
disabled={!props.canRedo}
|
||||
tooltip={t`Redo the last changes`}
|
||||
>
|
||||
<RedoIcon />
|
||||
</IconButton>
|
||||
<ElementWithMenu
|
||||
element={
|
||||
<IconButton
|
||||
size="small"
|
||||
color="default"
|
||||
tooltip={t`Change editor zoom`}
|
||||
>
|
||||
<ZoomInIcon />
|
||||
</IconButton>
|
||||
}
|
||||
buildMenuTemplate={(i18n: I18nType) => [
|
||||
...props.getContextMenuZoomItems(i18n),
|
||||
{ type: 'separator' },
|
||||
{ label: '5%', click: () => props.setZoomFactor(0.05) },
|
||||
{ label: '10%', click: () => props.setZoomFactor(0.1) },
|
||||
{ label: '25%', click: () => props.setZoomFactor(0.25) },
|
||||
{ label: '50%', click: () => props.setZoomFactor(0.5) },
|
||||
{ label: '100%', click: () => props.setZoomFactor(1.0) },
|
||||
{ label: '150%', click: () => props.setZoomFactor(1.5) },
|
||||
{ label: '200%', click: () => props.setZoomFactor(2.0) },
|
||||
{ label: '400%', click: () => props.setZoomFactor(4.0) },
|
||||
]}
|
||||
/>
|
||||
<IconButton
|
||||
size="small"
|
||||
color="default"
|
||||
onClick={props.deleteSelection}
|
||||
disabled={!props.instancesSelection.getSelectedInstances().length}
|
||||
tooltip={t`Delete the selected instances from the scene`}
|
||||
>
|
||||
<TrashIcon />
|
||||
</IconButton>
|
||||
<ToolbarSeparator />
|
||||
<ToolbarGroup lastChild>
|
||||
<ElementWithMenu
|
||||
element={
|
||||
<IconButton
|
||||
size="small"
|
||||
color="default"
|
||||
tooltip={t`Toggle/edit grid`}
|
||||
>
|
||||
<GridIcon />
|
||||
</IconButton>
|
||||
}
|
||||
buildMenuTemplate={(i18n: I18nType) => [
|
||||
{
|
||||
type: 'checkbox',
|
||||
label: i18n._(t`Show Mask`),
|
||||
checked: props.isWindowMaskShown(),
|
||||
click: () => props.toggleWindowMask(),
|
||||
},
|
||||
{
|
||||
type: 'checkbox',
|
||||
label: i18n._(t`Show grid`),
|
||||
checked: props.isGridShown(),
|
||||
click: () => props.toggleGrid(),
|
||||
},
|
||||
{ type: 'separator' },
|
||||
{
|
||||
label: i18n._(t`Setup grid`),
|
||||
click: () => props.openSetupGrid(),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
<ToolbarSeparator />
|
||||
<IconButton
|
||||
size="small"
|
||||
color="default"
|
||||
onClick={props.onOpenSettings}
|
||||
tooltip={t`Open settings`}
|
||||
>
|
||||
{props.settingsIcon || <EditSceneIcon />}
|
||||
</IconButton>
|
||||
</ToolbarGroup>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Toolbar;
|
@@ -0,0 +1,50 @@
|
||||
// @flow
|
||||
|
||||
import * as React from 'react';
|
||||
|
||||
type Props = {|
|
||||
onSwipeUp: () => void,
|
||||
onSwipeDown: () => void,
|
||||
|};
|
||||
|
||||
const minMovement = 30; // px
|
||||
const minSpeed = 200; // px/s
|
||||
|
||||
const useSwipeGesture = (props: Props) => {
|
||||
const startTimeRef = React.useRef<?number>(null);
|
||||
const startYRef = React.useRef<?number>(null);
|
||||
|
||||
const onTouchStart = React.useCallback((event: TouchEvent) => {
|
||||
startTimeRef.current = Date.now();
|
||||
startYRef.current = event.touches[0].clientY;
|
||||
}, []);
|
||||
|
||||
const onTouchEnd = React.useCallback(
|
||||
(event: TouchEvent) => {
|
||||
if (!startYRef.current || !startTimeRef.current) return;
|
||||
const { current: startY } = startYRef;
|
||||
const { current: startTime } = startTimeRef;
|
||||
|
||||
const deltaY = event.changedTouches[0].clientY - startY;
|
||||
const deltaTimeInSeconds = (Date.now() - startTime) / 1000;
|
||||
if (
|
||||
Math.abs(deltaY) > minMovement &&
|
||||
Math.abs(deltaY) / deltaTimeInSeconds > minSpeed
|
||||
) {
|
||||
if (deltaY < 0) props.onSwipeUp();
|
||||
else props.onSwipeDown();
|
||||
}
|
||||
|
||||
startTimeRef.current = null;
|
||||
startYRef.current = null;
|
||||
},
|
||||
[props]
|
||||
);
|
||||
|
||||
return {
|
||||
onTouchStart,
|
||||
onTouchEnd,
|
||||
};
|
||||
};
|
||||
|
||||
export default useSwipeGesture;
|
@@ -0,0 +1,413 @@
|
||||
// @flow
|
||||
|
||||
import * as React from 'react';
|
||||
import { Trans, t } from '@lingui/macro';
|
||||
import { I18n } from '@lingui/react';
|
||||
import { type I18n as I18nType } from '@lingui/core';
|
||||
|
||||
import InstancesEditor from '../../InstancesEditor';
|
||||
import InstancePropertiesEditor, {
|
||||
type InstancePropertiesEditorInterface,
|
||||
} from '../../InstancesEditor/InstancePropertiesEditor';
|
||||
import LayersList, { type LayersListInterface } from '../../LayersList';
|
||||
import TagsButton from '../../UI/EditorMosaic/TagsButton';
|
||||
import ObjectsList, { type ObjectsListInterface } from '../../ObjectsList';
|
||||
import ObjectGroupsList from '../../ObjectGroupsList';
|
||||
import InstancesList from '../../InstancesEditor/InstancesList';
|
||||
import ObjectsRenderingService from '../../ObjectsRendering/ObjectsRenderingService';
|
||||
|
||||
import {
|
||||
getTagsFromString,
|
||||
buildTagsMenuTemplate,
|
||||
type SelectedTags,
|
||||
} from '../../Utils/TagsHelper';
|
||||
import { enumerateObjects } from '../../ObjectsList/EnumerateObjects';
|
||||
import Rectangle from '../../Utils/Rectangle';
|
||||
import SwipeableDrawer from './SwipeableDrawer';
|
||||
import BottomToolbar from './BottomToolbar';
|
||||
import { FullSizeMeasurer } from '../../UI/FullSizeMeasurer';
|
||||
import PreferencesContext from '../../MainFrame/Preferences/PreferencesContext';
|
||||
import { useScreenType } from '../../UI/Reponsive/ScreenTypeMeasurer';
|
||||
import Paper from '../../UI/Paper';
|
||||
import { type EditorId } from '..';
|
||||
import {
|
||||
type SceneEditorsDisplayInterface,
|
||||
type SceneEditorsDisplayProps,
|
||||
} from '../EditorsDisplay.flow';
|
||||
|
||||
const editorTitleById = {
|
||||
'objects-list': <Trans>Objects</Trans>,
|
||||
properties: <Trans>Instance properties</Trans>,
|
||||
'object-groups-list': <Trans>Objects groups</Trans>,
|
||||
'instances-list': <Trans>Instances</Trans>,
|
||||
'layers-list': <Trans>Layers</Trans>,
|
||||
};
|
||||
|
||||
const noop = () => {};
|
||||
|
||||
const styles = {
|
||||
container: { width: '100%' },
|
||||
bottomContainer: { position: 'absolute', bottom: 0, width: '100%' },
|
||||
instancesListContainer: { display: 'flex', flex: 1 },
|
||||
};
|
||||
|
||||
// Forward ref to allow Scene editor to force update some editors
|
||||
const SwipeableDrawerEditorsDisplay = React.forwardRef<
|
||||
SceneEditorsDisplayProps,
|
||||
SceneEditorsDisplayInterface
|
||||
>((props, ref) => {
|
||||
const {
|
||||
project,
|
||||
layout,
|
||||
initialInstances,
|
||||
selectedLayer,
|
||||
onSelectInstances,
|
||||
} = props;
|
||||
const selectedInstances = props.instancesSelection.getSelectedInstances();
|
||||
const [
|
||||
selectedObjectTags,
|
||||
setSelectedObjectTags,
|
||||
] = React.useState<SelectedTags>([]);
|
||||
const { values } = React.useContext(PreferencesContext);
|
||||
const screenType = useScreenType();
|
||||
|
||||
const instancesPropertiesEditorRef = React.useRef<?InstancePropertiesEditorInterface>(
|
||||
null
|
||||
);
|
||||
const layersListRef = React.useRef<?LayersListInterface>(null);
|
||||
const instancesListRef = React.useRef<?InstancesList>(null);
|
||||
const editorRef = React.useRef<?InstancesEditor>(null);
|
||||
const objectsListRef = React.useRef<?ObjectsListInterface>(null);
|
||||
const objectGroupsListRef = React.useRef<?ObjectGroupsList>(null);
|
||||
|
||||
const [selectedEditorId, setSelectedEditorId] = React.useState<?EditorId>(
|
||||
null
|
||||
);
|
||||
|
||||
const [drawerOpeningState, setDrawerOpeningState] = React.useState<
|
||||
'closed' | 'halfOpen' | 'open'
|
||||
>('closed');
|
||||
|
||||
const halfOpenOrCloseDrawerOnEditor = React.useCallback(
|
||||
(editorId: ?EditorId) => {
|
||||
if (selectedEditorId === editorId) {
|
||||
if (drawerOpeningState === 'closed') {
|
||||
setDrawerOpeningState('halfOpen');
|
||||
} else {
|
||||
setDrawerOpeningState('closed');
|
||||
}
|
||||
} else {
|
||||
setSelectedEditorId(editorId || null);
|
||||
if (drawerOpeningState === 'closed') setDrawerOpeningState('halfOpen');
|
||||
}
|
||||
},
|
||||
[selectedEditorId, drawerOpeningState]
|
||||
);
|
||||
|
||||
const forceUpdateInstancesPropertiesEditor = React.useCallback(() => {
|
||||
if (instancesPropertiesEditorRef.current)
|
||||
instancesPropertiesEditorRef.current.forceUpdate();
|
||||
}, []);
|
||||
const forceUpdateInstancesList = React.useCallback(() => {
|
||||
if (instancesListRef.current) instancesListRef.current.forceUpdate();
|
||||
}, []);
|
||||
const forceUpdateObjectsList = React.useCallback(() => {
|
||||
if (objectsListRef.current) objectsListRef.current.forceUpdateList();
|
||||
}, []);
|
||||
const forceUpdateObjectGroupsList = React.useCallback(() => {
|
||||
if (objectGroupsListRef.current) objectGroupsListRef.current.forceUpdate();
|
||||
}, []);
|
||||
const forceUpdateLayersList = React.useCallback(() => {
|
||||
if (layersListRef.current) layersListRef.current.forceUpdate();
|
||||
}, []);
|
||||
const getInstanceSize = React.useCallback((instance: gdInitialInstance) => {
|
||||
if (!editorRef.current) return [0, 0, 0];
|
||||
|
||||
return editorRef.current.getInstanceSize(instance);
|
||||
}, []);
|
||||
const openNewObjectDialog = React.useCallback(() => {
|
||||
if (!objectsListRef.current) return;
|
||||
|
||||
objectsListRef.current.openNewObjectDialog();
|
||||
}, []);
|
||||
const isEditorVisible = React.useCallback(
|
||||
(editorId: EditorId) => {
|
||||
return editorId === selectedEditorId && drawerOpeningState !== 'closed';
|
||||
},
|
||||
[selectedEditorId, drawerOpeningState]
|
||||
);
|
||||
|
||||
React.useImperativeHandle(ref, () => {
|
||||
const { current: editor } = editorRef;
|
||||
|
||||
return {
|
||||
getName: () => 'swipeableDrawer',
|
||||
forceUpdateInstancesList,
|
||||
forceUpdateInstancesPropertiesEditor,
|
||||
forceUpdateObjectsList,
|
||||
forceUpdateObjectGroupsList,
|
||||
forceUpdateLayersList,
|
||||
openNewObjectDialog,
|
||||
toggleEditorView: halfOpenOrCloseDrawerOnEditor,
|
||||
isEditorVisible,
|
||||
viewControls: {
|
||||
zoomBy: editor ? editor.zoomBy : noop,
|
||||
setZoomFactor: editor ? editor.setZoomFactor : noop,
|
||||
zoomToInitialPosition: editor ? editor.zoomToInitialPosition : noop,
|
||||
zoomToFitContent: editor ? editor.zoomToFitContent : noop,
|
||||
zoomToFitSelection: editor ? editor.zoomToFitSelection : noop,
|
||||
centerViewOnLastInstance: editor
|
||||
? editor.centerViewOnLastInstance
|
||||
: noop,
|
||||
getLastCursorSceneCoordinates: editor
|
||||
? editor.getLastCursorSceneCoordinates
|
||||
: () => [0, 0],
|
||||
getLastContextMenuSceneCoordinates: editor
|
||||
? editor.getLastContextMenuSceneCoordinates
|
||||
: () => [0, 0],
|
||||
getViewPosition: editor ? editor.getViewPosition : noop,
|
||||
},
|
||||
instancesHandlers: {
|
||||
getSelectionAABB: editor
|
||||
? editor.selectedInstances.getSelectionAABB
|
||||
: () => new Rectangle(),
|
||||
addInstances: editor ? editor.addInstances : () => [],
|
||||
clearHighlightedInstance: editor
|
||||
? editor.clearHighlightedInstance
|
||||
: noop,
|
||||
resetInstanceRenderersFor: editor
|
||||
? editor.resetInstanceRenderersFor
|
||||
: noop,
|
||||
forceRemountInstancesRenderers: editor ? editor.forceRemount : noop,
|
||||
addSerializedInstances: editor
|
||||
? editor.addSerializedInstances
|
||||
: () => [],
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
const selectInstances = React.useCallback(
|
||||
(instances: Array<gdInitialInstance>, multiSelect: boolean) => {
|
||||
onSelectInstances(instances, multiSelect, 'upperCenter');
|
||||
forceUpdateInstancesList();
|
||||
forceUpdateInstancesPropertiesEditor();
|
||||
},
|
||||
[
|
||||
forceUpdateInstancesList,
|
||||
forceUpdateInstancesPropertiesEditor,
|
||||
onSelectInstances,
|
||||
]
|
||||
);
|
||||
|
||||
const getAllObjectTags = React.useCallback(
|
||||
(): Array<string> => {
|
||||
const tagsSet: Set<string> = new Set();
|
||||
enumerateObjects(project, layout).allObjectsList.forEach(({ object }) => {
|
||||
getTagsFromString(object.getTags()).forEach(tag => tagsSet.add(tag));
|
||||
});
|
||||
|
||||
return Array.from(tagsSet);
|
||||
},
|
||||
[project, layout]
|
||||
);
|
||||
|
||||
const buildObjectTagsMenuTemplate = React.useCallback(
|
||||
(i18n: I18nType): Array<any> => {
|
||||
return buildTagsMenuTemplate({
|
||||
noTagLabel: i18n._(t`No tags - add a tag to an object first`),
|
||||
getAllTags: getAllObjectTags,
|
||||
selectedTags: selectedObjectTags,
|
||||
onChange: setSelectedObjectTags,
|
||||
});
|
||||
},
|
||||
[selectedObjectTags, getAllObjectTags]
|
||||
);
|
||||
|
||||
return (
|
||||
<FullSizeMeasurer>
|
||||
{({ width, height }) => (
|
||||
<div style={styles.container}>
|
||||
<InstancesEditor
|
||||
ref={editorRef}
|
||||
height={height}
|
||||
width={width}
|
||||
project={project}
|
||||
layout={layout}
|
||||
selectedLayer={selectedLayer}
|
||||
screenType={screenType}
|
||||
initialInstances={initialInstances}
|
||||
instancesEditorSettings={props.instancesEditorSettings}
|
||||
onInstancesEditorSettingsMutated={
|
||||
props.onInstancesEditorSettingsMutated
|
||||
}
|
||||
instancesSelection={props.instancesSelection}
|
||||
onInstancesAdded={props.onInstancesAdded}
|
||||
onInstancesSelected={props.onInstancesSelected}
|
||||
onInstanceDoubleClicked={props.onInstanceDoubleClicked}
|
||||
onInstancesMoved={props.onInstancesMoved}
|
||||
onInstancesResized={props.onInstancesResized}
|
||||
onInstancesRotated={props.onInstancesRotated}
|
||||
selectedObjectNames={props.selectedObjectNames}
|
||||
onContextMenu={props.onContextMenu}
|
||||
isInstanceOf3DObject={props.isInstanceOf3DObject}
|
||||
instancesEditorShortcutsCallbacks={
|
||||
props.instancesEditorShortcutsCallbacks
|
||||
}
|
||||
pauseRendering={!props.isActive}
|
||||
showObjectInstancesIn3D={values.use3DEditor}
|
||||
/>
|
||||
<div style={styles.bottomContainer}>
|
||||
<SwipeableDrawer
|
||||
maxHeight={height}
|
||||
title={
|
||||
selectedEditorId ? editorTitleById[selectedEditorId] : null
|
||||
}
|
||||
openingState={drawerOpeningState}
|
||||
setOpeningState={setDrawerOpeningState}
|
||||
topBarControls={
|
||||
selectedEditorId === 'objects-list'
|
||||
? [
|
||||
<TagsButton
|
||||
key="tags"
|
||||
size="small"
|
||||
buildMenuTemplate={buildObjectTagsMenuTemplate}
|
||||
/>,
|
||||
]
|
||||
: null
|
||||
}
|
||||
>
|
||||
{selectedEditorId === 'objects-list' && (
|
||||
<I18n>
|
||||
{({ i18n }) => (
|
||||
<ObjectsList
|
||||
getThumbnail={ObjectsRenderingService.getThumbnail.bind(
|
||||
ObjectsRenderingService
|
||||
)}
|
||||
project={project}
|
||||
objectsContainer={layout}
|
||||
layout={layout}
|
||||
onSelectAllInstancesOfObjectInLayout={
|
||||
props.onSelectAllInstancesOfObjectInLayout
|
||||
}
|
||||
resourceManagementProps={props.resourceManagementProps}
|
||||
selectedObjectNames={props.selectedObjectNames}
|
||||
canInstallPrivateAsset={props.canInstallPrivateAsset}
|
||||
onEditObject={props.onEditObject}
|
||||
onExportObject={props.onExportObject}
|
||||
onDeleteObject={(objectWithContext, cb) =>
|
||||
props.onDeleteObject(i18n, objectWithContext, cb)
|
||||
}
|
||||
canRenameObject={(newName, global) =>
|
||||
props.canObjectOrGroupUseNewName(newName, global, i18n)
|
||||
}
|
||||
onObjectCreated={props.onObjectCreated}
|
||||
onObjectSelected={props.onObjectSelected}
|
||||
renamedObjectWithContext={props.renamedObjectWithContext}
|
||||
onRenameObjectStart={props.onRenameObjectStart}
|
||||
onRenameObjectFinish={props.onRenameObjectFinish}
|
||||
onAddObjectInstance={objectName =>
|
||||
props.onAddObjectInstance(objectName, 'upperCenter')
|
||||
}
|
||||
onObjectPasted={props.updateBehaviorsSharedData}
|
||||
selectedObjectTags={selectedObjectTags}
|
||||
beforeSetAsGlobalObject={objectName =>
|
||||
props.canObjectOrGroupBeGlobal(i18n, objectName)
|
||||
}
|
||||
onChangeSelectedObjectTags={setSelectedObjectTags}
|
||||
getAllObjectTags={getAllObjectTags}
|
||||
ref={objectsListRef}
|
||||
unsavedChanges={props.unsavedChanges}
|
||||
hotReloadPreviewButtonProps={
|
||||
props.hotReloadPreviewButtonProps
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</I18n>
|
||||
)}
|
||||
{selectedEditorId === 'properties' && (
|
||||
<I18n>
|
||||
{({ i18n }) => (
|
||||
<InstancePropertiesEditor
|
||||
i18n={i18n}
|
||||
project={project}
|
||||
layout={layout}
|
||||
instances={selectedInstances}
|
||||
editInstanceVariables={props.editInstanceVariables}
|
||||
onEditObjectByName={props.editObjectByName}
|
||||
onInstancesModified={forceUpdateInstancesList}
|
||||
onGetInstanceSize={getInstanceSize}
|
||||
ref={instancesPropertiesEditorRef}
|
||||
unsavedChanges={props.unsavedChanges}
|
||||
historyHandler={props.historyHandler}
|
||||
/>
|
||||
)}
|
||||
</I18n>
|
||||
)}
|
||||
{selectedEditorId === 'object-groups-list' && (
|
||||
<I18n>
|
||||
{({ i18n }) => (
|
||||
<ObjectGroupsList
|
||||
ref={objectGroupsListRef}
|
||||
globalObjectGroups={project.getObjectGroups()}
|
||||
objectGroups={layout.getObjectGroups()}
|
||||
onEditGroup={props.onEditObjectGroup}
|
||||
onDeleteGroup={props.onDeleteObjectGroup}
|
||||
onRenameGroup={props.onRenameObjectGroup}
|
||||
canRenameGroup={(newName, global) =>
|
||||
props.canRenameObjectGroup(newName, global, i18n)
|
||||
}
|
||||
beforeSetAsGlobalGroup={groupName =>
|
||||
props.canObjectOrGroupBeGlobal(i18n, groupName)
|
||||
}
|
||||
unsavedChanges={props.unsavedChanges}
|
||||
/>
|
||||
)}
|
||||
</I18n>
|
||||
)}
|
||||
{selectedEditorId === 'instances-list' && (
|
||||
<Paper
|
||||
background="medium"
|
||||
square
|
||||
style={styles.instancesListContainer}
|
||||
>
|
||||
<InstancesList
|
||||
instances={initialInstances}
|
||||
selectedInstances={selectedInstances}
|
||||
onSelectInstances={selectInstances}
|
||||
ref={instancesListRef}
|
||||
/>
|
||||
</Paper>
|
||||
)}
|
||||
{selectedEditorId === 'layers-list' && (
|
||||
<LayersList
|
||||
project={project}
|
||||
selectedLayer={selectedLayer}
|
||||
onSelectLayer={props.onSelectLayer}
|
||||
onEditLayerEffects={props.editLayerEffects}
|
||||
onEditLayer={props.editLayer}
|
||||
onRemoveLayer={props.onRemoveLayer}
|
||||
onRenameLayer={props.onRenameLayer}
|
||||
onCreateLayer={forceUpdateInstancesPropertiesEditor}
|
||||
layersContainer={layout}
|
||||
unsavedChanges={props.unsavedChanges}
|
||||
ref={layersListRef}
|
||||
hotReloadPreviewButtonProps={
|
||||
props.hotReloadPreviewButtonProps
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</SwipeableDrawer>
|
||||
<BottomToolbar
|
||||
selectedEditorId={
|
||||
drawerOpeningState === 'closed' ? null : selectedEditorId
|
||||
}
|
||||
onSelectEditor={halfOpenOrCloseDrawerOnEditor}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</FullSizeMeasurer>
|
||||
);
|
||||
});
|
||||
|
||||
export default SwipeableDrawerEditorsDisplay;
|
File diff suppressed because it is too large
Load Diff
@@ -7,11 +7,16 @@ import { type MenuItemTemplate } from '../Menu/Menu.flow';
|
||||
import Filter from '../CustomSvgIcons/Filter';
|
||||
|
||||
const styles = {
|
||||
container: {
|
||||
mediumContainer: {
|
||||
padding: 0,
|
||||
width: 32,
|
||||
height: 32,
|
||||
},
|
||||
smallContainer: {
|
||||
padding: 0,
|
||||
width: 16,
|
||||
height: 16,
|
||||
},
|
||||
icon: {
|
||||
width: 16,
|
||||
height: 16,
|
||||
@@ -20,13 +25,20 @@ const styles = {
|
||||
|
||||
type Props = {|
|
||||
buildMenuTemplate: (i18n: I18nType) => Array<MenuItemTemplate>,
|
||||
size?: 'small',
|
||||
|};
|
||||
|
||||
export default function TagsButton(props: Props) {
|
||||
return (
|
||||
<ElementWithMenu
|
||||
element={
|
||||
<IconButton style={styles.container}>
|
||||
<IconButton
|
||||
style={
|
||||
props.size === 'small'
|
||||
? styles.smallContainer
|
||||
: styles.mediumContainer
|
||||
}
|
||||
>
|
||||
<Filter htmlColor="inherit" style={styles.icon} />
|
||||
</IconButton>
|
||||
}
|
||||
|
@@ -57,6 +57,9 @@ type Props = {|
|
||||
acceleratorString?: string,
|
||||
'aria-label'?: string,
|
||||
|
||||
disableRipple?: boolean,
|
||||
disableFocusRipple?: boolean,
|
||||
|
||||
color?: 'default',
|
||||
|};
|
||||
|
||||
|
@@ -55,7 +55,17 @@ const MaterialUIContextMenu = React.forwardRef<
|
||||
top: anchorPosition[1],
|
||||
}}
|
||||
anchorReference={'anchorPosition'}
|
||||
onClose={() => setOpenMenu(false)}
|
||||
onClose={(event, reason) => {
|
||||
if (reason === 'backdropClick') {
|
||||
// Prevent any side effect of a backdrop click that should only
|
||||
// close the context menu.
|
||||
// When used in the ElementWithMenu component, there are cases where
|
||||
// the event propagates to the element on which the menu is set up and
|
||||
// then the event bubbles up, triggering click events on its way up.
|
||||
event.stopPropagation();
|
||||
}
|
||||
setOpenMenu(false);
|
||||
}}
|
||||
TransitionComponent={Fade}
|
||||
{...menuImplementation.getMenuProps()}
|
||||
>
|
||||
|
@@ -22,7 +22,8 @@ export default class ElementWithMenu extends React.Component<Props, State> {
|
||||
_contextMenu: ?ContextMenuInterface;
|
||||
_wrappedElement: ?any;
|
||||
|
||||
open = () => {
|
||||
open = (event?: Event) => {
|
||||
if (event && event.stopPropagation) event.stopPropagation();
|
||||
const { _contextMenu } = this;
|
||||
if (!_contextMenu) return;
|
||||
|
||||
|
@@ -224,7 +224,8 @@ export default class MaterialUIMenuImplementation
|
||||
// $FlowFixMe - existence should be inferred by Flow.
|
||||
item.enabled === false
|
||||
}
|
||||
onClick={async () => {
|
||||
onClick={async e => {
|
||||
e.stopPropagation();
|
||||
if (item.enabled === false) {
|
||||
return;
|
||||
}
|
||||
@@ -269,7 +270,8 @@ export default class MaterialUIMenuImplementation
|
||||
dense
|
||||
key={'item' + item.label}
|
||||
disabled={item.enabled === false}
|
||||
onClick={() => {
|
||||
onClick={e => {
|
||||
e.stopPropagation();
|
||||
if (item.enabled === false) {
|
||||
return;
|
||||
}
|
||||
|
@@ -2,6 +2,7 @@
|
||||
import * as React from 'react';
|
||||
import MuiPaper from '@material-ui/core/Paper';
|
||||
import GDevelopThemeContext from './Theme/GDevelopThemeContext';
|
||||
import { type GDevelopTheme } from './Theme';
|
||||
|
||||
type Props = {|
|
||||
id?: string,
|
||||
@@ -16,6 +17,16 @@ type Props = {|
|
||||
square?: boolean,
|
||||
|};
|
||||
|
||||
export const getBackgroundColor = (
|
||||
gdevelopTheme: GDevelopTheme,
|
||||
backgroundColor: 'light' | 'medium' | 'dark'
|
||||
) =>
|
||||
backgroundColor === 'dark'
|
||||
? gdevelopTheme.paper.backgroundColor.dark
|
||||
: backgroundColor === 'medium'
|
||||
? gdevelopTheme.paper.backgroundColor.medium
|
||||
: gdevelopTheme.paper.backgroundColor.light;
|
||||
|
||||
const Paper = ({
|
||||
id,
|
||||
children,
|
||||
@@ -26,12 +37,7 @@ const Paper = ({
|
||||
square,
|
||||
}: Props) => {
|
||||
const gdevelopTheme = React.useContext(GDevelopThemeContext);
|
||||
const backgroundColor =
|
||||
background === 'dark'
|
||||
? gdevelopTheme.paper.backgroundColor.dark
|
||||
: background === 'medium'
|
||||
? gdevelopTheme.paper.backgroundColor.medium
|
||||
: gdevelopTheme.paper.backgroundColor.light;
|
||||
const backgroundColor = getBackgroundColor(gdevelopTheme, background);
|
||||
return (
|
||||
<MuiPaper
|
||||
id={id}
|
||||
|
@@ -63,6 +63,13 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"swipeable-drawer": {
|
||||
"top-bar": {
|
||||
"pill-color": {
|
||||
"value": "#0f0f12"
|
||||
}
|
||||
}
|
||||
},
|
||||
"icon-button": {
|
||||
"selected": {
|
||||
"color": {
|
||||
|
@@ -469,6 +469,11 @@ export function createGdevelopTheme({
|
||||
backgroundColor: styles['ThemeSurfaceToolbarBackgroundColor'],
|
||||
separatorColor: styles['ThemeToolbarSeparatorColor'],
|
||||
},
|
||||
swipeableDrawer: {
|
||||
topBar: {
|
||||
pillColor: styles['ThemeSwipeableDrawerTopBarPillColor'],
|
||||
},
|
||||
},
|
||||
text: {
|
||||
color: {
|
||||
primary: styles['ThemeTextDefaultColor'],
|
||||
|
@@ -168,6 +168,13 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"swipeable-drawer": {
|
||||
"top-bar": {
|
||||
"pill-color": {
|
||||
"value": "#1D1D26"
|
||||
}
|
||||
}
|
||||
},
|
||||
"icon-button": {
|
||||
"selected": {
|
||||
"color": {
|
||||
|
@@ -153,6 +153,13 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"swipeable-drawer": {
|
||||
"top-bar": {
|
||||
"pill-color": {
|
||||
"value": "#7F7F85"
|
||||
}
|
||||
}
|
||||
},
|
||||
"icon-button": {
|
||||
"selected": {
|
||||
"color": {
|
||||
|
@@ -6,6 +6,13 @@
|
||||
color: var(--table-text-color);
|
||||
}
|
||||
|
||||
.gd-table .tableHeaderRow {
|
||||
/* react-virtualized adds a padding right directly on the header row element
|
||||
* so the !important is needed to override it. It is set to 0 to avoid having
|
||||
* a gap between the right part of the screen/container and the scroll bar. */
|
||||
padding-right: 0px !important;
|
||||
}
|
||||
|
||||
.gd-table .tableHeaderRow,
|
||||
.gd-table .tableEvenRow,
|
||||
.gd-table .tableSelectedRow,
|
||||
|
@@ -59,6 +59,13 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"swipeable-drawer": {
|
||||
"top-bar": {
|
||||
"pill-color": {
|
||||
"value": "#1c1f26"
|
||||
}
|
||||
}
|
||||
},
|
||||
"icon-button": {
|
||||
"selected": {
|
||||
"color": {
|
||||
|
@@ -71,6 +71,13 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"swipeable-drawer": {
|
||||
"top-bar": {
|
||||
"pill-color": {
|
||||
"value": "#14161a"
|
||||
}
|
||||
}
|
||||
},
|
||||
"icon-button": {
|
||||
"selected": {
|
||||
"color": {
|
||||
|
@@ -59,6 +59,13 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"swipeable-drawer": {
|
||||
"top-bar": {
|
||||
"pill-color": {
|
||||
"value": "#907aa9"
|
||||
}
|
||||
}
|
||||
},
|
||||
"icon-button": {
|
||||
"color": {
|
||||
"value": "#1f1d2e"
|
||||
|
@@ -60,6 +60,13 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"swipeable-drawer": {
|
||||
"top-bar": {
|
||||
"pill-color": {
|
||||
"value": "#001419"
|
||||
}
|
||||
}
|
||||
},
|
||||
"icon-button": {
|
||||
"color": {
|
||||
"value": "#002B36"
|
||||
|
@@ -1,6 +1,5 @@
|
||||
// @flow
|
||||
import * as React from 'react';
|
||||
import GDevelopThemeContext from '../Theme/GDevelopThemeContext';
|
||||
|
||||
const styles = {
|
||||
row: {
|
||||
@@ -22,15 +21,12 @@ type TreeTableRowProps = {|
|
||||
|};
|
||||
|
||||
export const TreeTableRow = (props: TreeTableRowProps) => {
|
||||
const gdevelopTheme = React.useContext(GDevelopThemeContext);
|
||||
|
||||
return (
|
||||
<div
|
||||
id={props.id}
|
||||
style={{
|
||||
...styles.row,
|
||||
alignItems: props.alignItems,
|
||||
backgroundColor: gdevelopTheme.list.itemsBackgroundColor,
|
||||
}}
|
||||
>
|
||||
{props.children}
|
||||
|
Reference in New Issue
Block a user