Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions Core/GDCore/Project/ResourcesContainer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,20 @@ ResourcesContainer::GetResourcePosition(const gd::String &name) const {
return gd::String::npos;
}

Resource &ResourcesContainer::GetResourceAt(std::size_t index) {
if (index < resources.size())
return *resources[index];

return badResource;
}

const Resource &ResourcesContainer::GetResourceAt(std::size_t index) const {
if (index < resources.size())
return *resources[index];

return badResource;
}

void ResourcesContainer::MoveResource(std::size_t oldIndex,
std::size_t newIndex) {
if (oldIndex >= resources.size() || newIndex >= resources.size())
Expand Down
10 changes: 10 additions & 0 deletions Core/GDCore/Project/ResourcesContainer.h
Original file line number Diff line number Diff line change
Expand Up @@ -737,6 +737,16 @@ class GD_CORE_API ResourcesContainer {
*/
std::size_t GetResourcePosition(const gd::String &name) const;

/**
* \brief Return a reference to the resource at the specified position.
*/
Resource &GetResourceAt(std::size_t index);

/**
* \brief Return a reference to the resource at the specified position.
*/
const Resource &GetResourceAt(std::size_t index) const;

/**
* \brief Move a resource up in the list
*/
Expand Down
2 changes: 2 additions & 0 deletions GDevelop.js/Bindings/Bindings.idl
Original file line number Diff line number Diff line change
Expand Up @@ -1329,12 +1329,14 @@ interface ResourcesContainer {
[Value] VectorString FindFilesNotInResources([Const, Ref] VectorString filesToCheck);
boolean HasResource([Const] DOMString name);
[Const, Ref] Resource GetResource([Const] DOMString name);
[Const, Ref] Resource GetResourceAt(unsigned long index);
[Const, Ref] DOMString GetResourceNameWithOrigin([Const] DOMString originName, [Const] DOMString originIdentifier);
[Const, Ref] DOMString GetResourceNameWithFile([Const] DOMString file);
boolean AddResource([Const, Ref] Resource res);
void RemoveResource([Const] DOMString name);
void RenameResource([Const] DOMString oldName, [Const] DOMString name);
unsigned long GetResourcePosition([Const] DOMString name);
unsigned long Count();
boolean MoveResourceUpInList([Const] DOMString oldName);
boolean MoveResourceDownInList([Const] DOMString oldName);
void MoveResource(unsigned long oldIndex, unsigned long newIndex);
Expand Down
2 changes: 2 additions & 0 deletions GDevelop.js/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1100,12 +1100,14 @@ export class ResourcesContainer extends EmscriptenObject {
findFilesNotInResources(filesToCheck: VectorString): VectorString;
hasResource(name: string): boolean;
getResource(name: string): Resource;
getResourceAt(index: number): Resource;
getResourceNameWithOrigin(originName: string, originIdentifier: string): string;
getResourceNameWithFile(file: string): string;
addResource(res: Resource): boolean;
removeResource(name: string): void;
renameResource(oldName: string, name: string): void;
getResourcePosition(name: string): number;
count(): number;
moveResourceUpInList(oldName: string): boolean;
moveResourceDownInList(oldName: string): boolean;
moveResource(oldIndex: number, newIndex: number): void;
Expand Down
2 changes: 2 additions & 0 deletions GDevelop.js/types/gdresourcescontainer.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,14 @@ declare class gdResourcesContainer {
findFilesNotInResources(filesToCheck: gdVectorString): gdVectorString;
hasResource(name: string): boolean;
getResource(name: string): gdResource;
getResourceAt(index: number): gdResource;
getResourceNameWithOrigin(originName: string, originIdentifier: string): string;
getResourceNameWithFile(file: string): string;
addResource(res: gdResource): boolean;
removeResource(name: string): void;
renameResource(oldName: string, name: string): void;
getResourcePosition(name: string): number;
count(): number;
moveResourceUpInList(oldName: string): boolean;
moveResourceDownInList(oldName: string): boolean;
moveResource(oldIndex: number, newIndex: number): void;
Expand Down
36 changes: 31 additions & 5 deletions newIDE/app/src/ResourcesEditor/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ export default class ResourcesEditor extends React.Component<Props, State> {
this.onResourceExternallyChanged.bind(this)
);
}

componentWillUnmount() {
unregisterOnResourceExternallyChangedCallback(
this.resourceExternallyChangedCallbackId
Expand Down Expand Up @@ -126,18 +127,38 @@ export default class ResourcesEditor extends React.Component<Props, State> {
);
if (!answer) return;

const resourcesManager = project.getResourcesManager();
const allNames = resourcesManager.getAllResourceNames().toJSArray();
const currentIndex = allNames.indexOf(resource.getName());

let nextResourceName = null;
if (allNames.length > 1) {
const nextIndex =
currentIndex < allNames.length - 1
? currentIndex + 1
: currentIndex - 1;
nextResourceName = allNames[nextIndex];
}

onDeleteResource(resource, doRemove => {
if (!doRemove || !resource) return;

project.getResourcesManager().removeResource(resource.getName());
resourcesManager.removeResource(resource.getName());

const nextResourceToSelect = nextResourceName
? resourcesManager.getResource(nextResourceName)
: null;

this.setState(
{
selectedResource: null,
selectedResource: nextResourceToSelect,
},
() => {
// Force update of the resources list as otherwise it could render
// resource that was just deleted.
if (this._resourcesList) this._resourcesList.forceUpdateList();
if (this._resourcesList) {
this._resourcesList.forceUpdateList();
if (this._resourcesList.focus) this._resourcesList.focus();
}
if (this._propertiesEditor) this._propertiesEditor.forceUpdate();
this.updateToolbar();
}
);
Expand Down Expand Up @@ -288,6 +309,11 @@ export default class ResourcesEditor extends React.Component<Props, State> {
getResourceActionsSpecificToStorageProvider={
resourcesActionsMenuBuilder
}
onResourceRenamed={() => {
if (this._propertiesEditor) {
this._propertiesEditor.forceUpdate();
}
}}
/>
),
},
Expand Down
81 changes: 79 additions & 2 deletions newIDE/app/src/ResourcesList/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import * as React from 'react';
import { AutoSizer } from 'react-virtualized';
import Background from '../UI/Background';
import SearchBar from '../UI/SearchBar';
import KeyboardShortcuts from '../UI/KeyboardShortcuts';
import { showWarningBox } from '../UI/Messages/MessageBox';
import { filterResourcesList } from './EnumerateResources';
import { getResourceFilePathStatus } from './ResourceUtils';
Expand All @@ -26,6 +27,7 @@ import SortableVirtualizedItemList from '../UI/SortableVirtualizedItemList';
const styles = {
listContainer: {
flex: 1,
outline: 'none',
},
};

Expand Down Expand Up @@ -57,6 +59,7 @@ export const getDefaultResourceThumbnail = (resource: gdResource) => {
export type ResourcesListInterface = {|
forceUpdateList: () => void,
checkMissingPaths: () => void,
focus: () => void,
|};

type Props = {|
Expand All @@ -69,6 +72,7 @@ type Props = {|
newName: string,
cb: (boolean) => void
) => void,
onResourceRenamed?: () => void,
fileMetadata: ?FileMetadata,
onRemoveUnusedResources: ResourceKind => void,
onRemoveAllResourcesWithInvalidPath: () => void,
Expand All @@ -84,6 +88,7 @@ const ResourcesList = React.memo<Props, ResourcesListInterface>(
onSelectResource,
onDeleteResource,
onRenameResource,
onResourceRenamed,
fileMetadata,
onRemoveUnusedResources,
getResourceActionsSpecificToStorageProvider,
Expand All @@ -96,6 +101,7 @@ const ResourcesList = React.memo<Props, ResourcesListInterface>(
const [resourcesWithErrors, setResourcesWithErrors] = React.useState({});
const [infoBarContent, setInfoBarContent] = React.useState(null);
const sortableListRef = React.useRef(null);
const listContainerRef = React.useRef(null);

const deleteResource = React.useCallback(
(resource: gdResource) => {
Expand Down Expand Up @@ -153,9 +159,49 @@ const ResourcesList = React.memo<Props, ResourcesListInterface>(
resource.setName(newName);
// Force re-render
forceUpdateList();
if (onResourceRenamed) onResourceRenamed();
});

// Refocus the list container to allow keyboard shortcuts
if (listContainerRef.current) listContainerRef.current.focus();
},
[project, onRenameResource, forceUpdateList, onResourceRenamed]
);

const moveSelection = React.useCallback(
(delta: number) => {
const resourcesManager = project.getResourcesManager();
const resourceCount = resourcesManager.count();

if (resourceCount === 0) return;

let nextIndex = 0;
if (selectedResource) {
const currentIndex = resourcesManager.getResourcePosition(
selectedResource.getName()
);
nextIndex = Math.max(
0,
Math.min(resourceCount - 1, currentIndex + delta)
);
}

const nextResource = resourcesManager.getResourceAt(nextIndex);
onSelectResource(nextResource);
},
[project, selectedResource, onSelectResource]
);

const handleKeyDown = React.useCallback(
(event: KeyboardEvent) => {
if (event.key === 'ArrowDown' || event.key === 'ArrowUp') {
moveSelection(event.key === 'ArrowDown' ? 1 : -1);
event.preventDefault();
} else {
keyboardShortcutsRef.current.onKeyDown(event);
}
},
[project, onRenameResource, forceUpdateList]
[moveSelection]
);

const moveSelectionTo = React.useCallback(
Expand Down Expand Up @@ -257,9 +303,35 @@ const ResourcesList = React.memo<Props, ResourcesListInterface>(
[project, forceUpdateList]
);

// KeyboardShortcuts callbacks are set dynamically in useEffect below
// instead of here, because they depend on selectedResource which can change.
// This ensures the callbacks always use the current selectedResource.
const keyboardShortcutsRef = React.useRef<KeyboardShortcuts>(
new KeyboardShortcuts({
shortcutCallbacks: {},
})
);

React.useEffect(
() => {
if (!keyboardShortcutsRef.current) return;
if (!selectedResource) return;
keyboardShortcutsRef.current.setShortcutCallback('onDelete', () => {
deleteResource(selectedResource);
});
keyboardShortcutsRef.current.setShortcutCallback('onRename', () => {
editName(selectedResource);
});
},
[selectedResource, deleteResource, editName]
);

React.useImperativeHandle(ref, () => ({
forceUpdateList,
checkMissingPaths,
focus: () => {
if (listContainerRef.current) listContainerRef.current.focus();
},
}));

// Check missing paths on mount and when project changes.
Expand Down Expand Up @@ -294,7 +366,12 @@ const ResourcesList = React.memo<Props, ResourcesListInterface>(
/>
</Column>
</Line>
<div style={styles.listContainer}>
<div
ref={listContainerRef}
style={styles.listContainer}
tabIndex={0}
onKeyDown={handleKeyDown}
>
<AutoSizer>
{({ height, width }) => (
<I18n>
Expand Down