diff --git a/editor/views/editor/editor.cpp b/editor/views/editor/editor.cpp index 838363de..4796661a 100644 --- a/editor/views/editor/editor.cpp +++ b/editor/views/editor/editor.cpp @@ -853,6 +853,7 @@ void EditorWindow::activateWorkspace(int index) { if (auto *button = workspaceModeGroup->button(index)) button->setChecked(true); } + scheduleLayoutSave(); } void EditorWindow::createScene() { @@ -1718,6 +1719,8 @@ void EditorWindow::saveLayout() { settings.setValue("window/geometry", saveGeometry()); settings.setValue(DockStateKey, coreManager->saveState(DockStateVersion)); + if (workspaceStack != nullptr) + settings.setValue("workspace/mode", workspaceStack->currentIndex()); settings.sync(); } @@ -1735,6 +1738,10 @@ void EditorWindow::restoreLayout() { coreManager->restoreState(dockState, DockStateVersion); if (!restored && !defaultDockState.isEmpty()) coreManager->restoreState(defaultDockState, DockStateVersion); + if (workspaceStack != nullptr) + activateWorkspace( + std::clamp(settings.value("workspace/mode", 0).toInt(), 0, + workspaceStack->count() - 1)); restoringLayout = false; configureDockSplitters(); } diff --git a/editor/views/editor/inspector.cpp b/editor/views/editor/inspector.cpp index 11d07987..617c70f9 100644 --- a/editor/views/editor/inspector.cpp +++ b/editor/views/editor/inspector.cpp @@ -27,6 +27,7 @@ #include #include #include +#include #include #include #include @@ -192,6 +193,12 @@ QIcon inspectorIcon(QWidget *, const QString &type) { return styling::icon(styling::Icon::MusicNote, "#849589"); if (normalized.contains("material")) return styling::icon(styling::Icon::Material, "#9E897D"); + if (normalized == "png" || normalized == "jpg" || + normalized == "jpeg" || normalized == "bmp" || + normalized == "gif" || normalized == "webp" || + normalized == "tif" || normalized == "tiff" || + normalized == "tga" || normalized == "hdr" || normalized == "exr") + return styling::icon(styling::Icon::Image, "#A1957D"); if (normalized.contains("script") || normalized == "ts" || normalized == "js") return styling::icon(styling::Icon::FileCode, "#7E929C"); @@ -1883,6 +1890,44 @@ void InspectorPanel::showFile() { connect(nameField, &QLineEdit::editingFinished, this, &InspectorPanel::commitHeaderName); + if (info.isFile()) { + QImageReader reader(info.absoluteFilePath()); + reader.setAutoTransform(true); + if (reader.canRead()) { + const QSize sourceSize = reader.size(); + if (sourceSize.isValid() && + (sourceSize.width() > 720 || sourceSize.height() > 480)) { + reader.setScaledSize( + sourceSize.scaled(720, 480, Qt::KeepAspectRatio)); + } + const QImage image = reader.read(); + if (!image.isNull()) { + auto *previewCard = new QFrame(content); + previewCard->setObjectName("inspectorComponent"); + auto *previewLayout = new QVBoxLayout(previewCard); + previewLayout->setContentsMargins(0, 0, 0, 7); + previewLayout->setSpacing(2); + auto *previewTitle = new QLabel("Preview", previewCard); + previewTitle->setObjectName("inspectorComponentHeader"); + auto *previewBody = new QWidget(previewCard); + previewBody->setObjectName("inspectorComponentBody"); + auto *previewBodyLayout = new QVBoxLayout(previewBody); + previewBodyLayout->setContentsMargins(5, 5, 5, 5); + auto *imageLabel = new QLabel(previewBody); + imageLabel->setAlignment(Qt::AlignCenter); + imageLabel->setSizePolicy(QSizePolicy::Expanding, + QSizePolicy::Preferred); + imageLabel->setPixmap(QPixmap::fromImage(image).scaled( + 360, 240, Qt::KeepAspectRatio, + Qt::SmoothTransformation)); + previewBodyLayout->addWidget(imageLabel); + previewLayout->addWidget(previewTitle); + previewLayout->addWidget(previewBody); + contentLayout->addWidget(previewCard); + } + } + } + QJsonObject metadata{ {"path", info.absoluteFilePath()}, {"size", static_cast(info.size())}, diff --git a/editor/views/editor/materialEditor.cpp b/editor/views/editor/materialEditor.cpp index ca2072db..a2948e5c 100644 --- a/editor/views/editor/materialEditor.cpp +++ b/editor/views/editor/materialEditor.cpp @@ -28,6 +28,7 @@ #include #include #include +#include #include #include #include @@ -482,6 +483,7 @@ void MaterialEditorPanel::rebuildBody() { delete item; } preview = nullptr; + materialSplitter = nullptr; textureFields.clear(); texturePreviews.clear(); } @@ -504,10 +506,10 @@ void MaterialEditorPanel::showMaterial() { titleLabel->setText(QFileInfo(materialPath).completeBaseName()); statusLabel->setText("Ready"); - auto *splitter = new QSplitter(Qt::Horizontal, body); - splitter->setChildrenCollapsible(false); - auto *previewPane = new QWidget(splitter); - previewPane->setMinimumWidth(1); + materialSplitter = new QSplitter(Qt::Horizontal, body); + materialSplitter->setChildrenCollapsible(false); + auto *previewPane = new QWidget(materialSplitter); + previewPane->setMinimumWidth(180); previewPane->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); auto *previewLayout = new QVBoxLayout(previewPane); previewLayout->setContentsMargins(0, 0, 5, 0); @@ -528,22 +530,45 @@ void MaterialEditorPanel::showMaterial() { connect(environment, &QComboBox::currentIndexChanged, preview, &MaterialPreviewWidget::setEnvironmentMode); - auto *propertiesScroll = new QScrollArea(splitter); + auto *propertiesScroll = new QScrollArea(materialSplitter); propertiesScroll->setObjectName("materialPropertiesScroll"); propertiesScroll->setWidgetResizable(true); propertiesScroll->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); auto *properties = new QWidget(propertiesScroll); - properties->setMinimumWidth(1); + propertiesScroll->setMinimumWidth(280); + properties->setMinimumWidth(260); properties->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); auto *propertiesLayout = new QVBoxLayout(properties); propertiesLayout->setContentsMargins(5, 0, 0, 0); propertiesLayout->setSpacing(9); propertiesScroll->setWidget(properties); - splitter->addWidget(previewPane); - splitter->addWidget(propertiesScroll); - splitter->setStretchFactor(0, 3); - splitter->setStretchFactor(1, 2); - bodyLayout->addWidget(splitter, 1); + materialSplitter->addWidget(previewPane); + materialSplitter->addWidget(propertiesScroll); + materialSplitter->setStretchFactor(0, 2); + materialSplitter->setStretchFactor(1, 3); + bodyLayout->addWidget(materialSplitter, 1); + + QSettings settings("Neutral Software", "Atlas Engine"); + const QByteArray splitterState = + settings.value("materialEditor/splitterState").toByteArray(); + if (splitterState.isEmpty() || + !materialSplitter->restoreState(splitterState)) { + QTimer::singleShot(0, materialSplitter, [this] { + if (materialSplitter == nullptr) + return; + const int available = std::max(materialSplitter->width(), 500); + materialSplitter->setSizes( + {available * 2 / 5, available * 3 / 5}); + }); + } + connect(materialSplitter, &QSplitter::splitterMoved, this, + [this](int, int) { + if (materialSplitter == nullptr) + return; + QSettings settings("Neutral Software", "Atlas Engine"); + settings.setValue("materialEditor/splitterState", + materialSplitter->saveState()); + }); auto *surface = new QGroupBox("Surface", properties); auto *surfaceForm = new QFormLayout(surface); diff --git a/editor/views/general/contentBrowser.cpp b/editor/views/general/contentBrowser.cpp index aa2b3e18..b0dcee81 100644 --- a/editor/views/general/contentBrowser.cpp +++ b/editor/views/general/contentBrowser.cpp @@ -34,6 +34,7 @@ #include #include #include +#include #include #include #include @@ -139,7 +140,9 @@ class AtlasFileIconProvider : public QFileIconProvider { suffix == "h" || suffix == "json") return styling::icon(styling::Icon::FileCode, "#7E929C"); if (suffix == "png" || suffix == "jpg" || suffix == "jpeg" || - suffix == "hdr") + suffix == "bmp" || suffix == "gif" || suffix == "webp" || + suffix == "tif" || suffix == "tiff" || suffix == "tga" || + suffix == "hdr" || suffix == "exr") return styling::icon(styling::Icon::Image, "#A1957D"); if (suffix == "wav" || suffix == "mp3" || suffix == "ogg" || suffix == "flac") @@ -155,6 +158,29 @@ class AtlasFileIconProvider : public QFileIconProvider { }; AtlasFileIconProvider atlasFileIconProvider; + +class ContentBrowserFilterModel : public QSortFilterProxyModel { + protected: + bool filterAcceptsRow(int sourceRow, + const QModelIndex &sourceParent) const override { + const QModelIndex index = + sourceModel()->index(sourceRow, 0, sourceParent); + const QFileInfo info = + qobject_cast(sourceModel())->fileInfo(index); + const QString name = info.fileName().toLower(); + if (info.isDir()) { + if (name == "lib" || name == "dist" || name == "node_modules") + return false; + return true; + } + if (name == "package.json" || name == "package-lock.json" || + name == "tsconfig.json" || name == "yarn.lock" || + name == "bun.lock" || name == "bun.lockb") + return false; + return QSortFilterProxyModel::filterAcceptsRow(sourceRow, + sourceParent); + } +}; } // namespace ContentBrowserPanel::ContentBrowserPanel(const QString &projectFile, @@ -239,9 +265,16 @@ ContentBrowserPanel::ContentBrowserPanel(const QString &projectFile, model->setRootPath(projectRoot); model->sort(0, Qt::AscendingOrder); + filterModel = new ContentBrowserFilterModel(); + filterModel->setParent(this); + filterModel->setSourceModel(model); + filterModel->setFilterCaseSensitivity(Qt::CaseInsensitive); + filterModel->setSortCaseSensitivity(Qt::CaseInsensitive); + filterModel->sort(0, Qt::AscendingOrder); + gridView = new QListView(this); gridView->setObjectName("contentGrid"); - gridView->setModel(model); + gridView->setModel(filterModel); gridView->setViewMode(QListView::IconMode); gridView->setFlow(QListView::LeftToRight); gridView->setWrapping(true); @@ -319,10 +352,9 @@ ContentBrowserPanel::ContentBrowserPanel(const QString &projectFile, }); connect(searchField, &QLineEdit::textChanged, this, [this](const QString &search) { - model->setNameFilters(search.isEmpty() - ? QStringList() - : QStringList{"*" + search + "*"}); - model->setNameFilterDisables(false); + filterModel->setFilterWildcard(search.isEmpty() + ? QString() + : "*" + search + "*"); }); connect(gridView->selectionModel(), &QItemSelectionModel::selectionChanged, this, [this] { @@ -403,7 +435,8 @@ void ContentBrowserPanel::navigateTo(const QString &path, bool recordHistory) { } currentPath = target; - gridView->setRootIndex(model->index(currentPath)); + gridView->setRootIndex( + filterModel->mapFromSource(model->index(currentPath))); gridView->clearSelection(); searchField->clear(); @@ -421,7 +454,7 @@ void ContentBrowserPanel::navigateTo(const QString &path, bool recordHistory) { } void ContentBrowserPanel::openIndex(const QModelIndex &index) { - const QFileInfo info = model->fileInfo(index); + const QFileInfo info = model->fileInfo(filterModel->mapToSource(index)); if (info.isDir()) { navigateTo(info.absoluteFilePath()); return; @@ -507,7 +540,8 @@ void ContentBrowserPanel::createScene() { if (path.isEmpty()) return; if (writeNewFile(path, EmptyScene)) { - gridView->setCurrentIndex(model->index(path)); + gridView->setCurrentIndex( + filterModel->mapFromSource(model->index(path))); } } @@ -532,7 +566,7 @@ void ContentBrowserPanel::createScript() { error.isEmpty() ? "Atlas could not create the script." : error); return; } - gridView->setCurrentIndex(model->index(path)); + gridView->setCurrentIndex(filterModel->mapFromSource(model->index(path))); } void ContentBrowserPanel::createMaterial() { @@ -558,7 +592,8 @@ void ContentBrowserPanel::createMaterial() { " }\n" "}\n"; if (writeNewFile(path, material)) { - const QModelIndex index = model->index(path); + const QModelIndex index = + filterModel->mapFromSource(model->index(path)); gridView->setCurrentIndex(index); emit assetActivated(path); } @@ -586,7 +621,8 @@ void ContentBrowserPanel::renameSelection() { return; } gridView->setCurrentIndex( - model->index(QDir(info.absolutePath()).filePath(entryName))); + filterModel->mapFromSource( + model->index(QDir(info.absolutePath()).filePath(entryName)))); } void ContentBrowserPanel::deleteSelection() { @@ -596,7 +632,8 @@ void ContentBrowserPanel::deleteSelection() { return; } for (const QModelIndex &index : selected) { - const QFileInfo info = model->fileInfo(index); + const QFileInfo info = + model->fileInfo(filterModel->mapToSource(index)); if (info.isSymLink()) { QFile::remove(info.absoluteFilePath()); } else if (info.isDir()) { @@ -616,7 +653,8 @@ void ContentBrowserPanel::copySelection() { clipboardPaths.clear(); for (const QModelIndex &index : gridView->selectionModel()->selectedIndexes()) { - clipboardPaths.append(model->filePath(index)); + clipboardPaths.append( + model->filePath(filterModel->mapToSource(index))); } cutClipboard = false; } @@ -654,7 +692,8 @@ void ContentBrowserPanel::duplicateSelection() { void ContentBrowserPanel::refreshAssets() { model->setRootPath(QString()); model->setRootPath(projectRoot); - gridView->setRootIndex(model->index(currentPath)); + gridView->setRootIndex( + filterModel->mapFromSource(model->index(currentPath))); } void ContentBrowserPanel::selectAllAssets() { gridView->selectAll(); } @@ -683,7 +722,7 @@ void ContentBrowserPanel::copySelectionPath() const { QString ContentBrowserPanel::selectedPath() const { const QModelIndex index = gridView->currentIndex(); return index.isValid() && gridView->selectionModel()->isSelected(index) - ? model->filePath(index) + ? model->filePath(filterModel->mapToSource(index)) : QString(); } diff --git a/editor/views/general/projectBrowser.cpp b/editor/views/general/projectBrowser.cpp index 66286bfa..12835c26 100644 --- a/editor/views/general/projectBrowser.cpp +++ b/editor/views/general/projectBrowser.cpp @@ -23,6 +23,7 @@ #include #include #include +#include #include #include #include @@ -160,8 +161,13 @@ class CreateProjectDialog : public QDialog { fields->addWidget(locationLabel); auto *locationLayout = new QHBoxLayout(); locationField = new QLineEdit(this); + QSettings settings("Neutral Software", "Atlas Engine"); QString defaultLocation = - QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation); + settings.value("projects/lastCreatedLocation").toString(); + if (defaultLocation.isEmpty() || !QDir(defaultLocation).exists()) { + defaultLocation = QStandardPaths::writableLocation( + QStandardPaths::DocumentsLocation); + } if (defaultLocation.isEmpty()) { defaultLocation = QDir::homePath(); } @@ -222,6 +228,9 @@ class CreateProjectDialog : public QDialog { errorLabel->show(); return; } + QSettings settings("Neutral Software", "Atlas Engine"); + settings.setValue("projects/lastCreatedLocation", + QDir(locationField->text()).absolutePath()); accept(); }); nameField->setFocus(); diff --git a/include/editor/views/fileExplorer.h b/include/editor/views/fileExplorer.h index 4577d498..3d8e972e 100644 --- a/include/editor/views/fileExplorer.h +++ b/include/editor/views/fileExplorer.h @@ -14,6 +14,7 @@ #include class QFileSystemModel; +class QSortFilterProxyModel; class QLineEdit; class QListView; class QModelIndex; @@ -62,6 +63,7 @@ class ContentBrowserPanel : public QWidget { QListView *gridView = nullptr; QFileSystemModel *model = nullptr; + QSortFilterProxyModel *filterModel = nullptr; QToolButton *backButton = nullptr; QToolButton *forwardButton = nullptr; QToolButton *upButton = nullptr; diff --git a/include/editor/views/materialEditor.h b/include/editor/views/materialEditor.h index ef65e73f..1fb7a268 100644 --- a/include/editor/views/materialEditor.h +++ b/include/editor/views/materialEditor.h @@ -12,6 +12,7 @@ class QDoubleSpinBox; class QLabel; class QLineEdit; class QPushButton; +class QSplitter; class QTimer; class QVBoxLayout; class MaterialPreviewWidget; @@ -51,6 +52,7 @@ class MaterialEditorPanel : public QWidget { QWidget *body = nullptr; QVBoxLayout *bodyLayout = nullptr; MaterialPreviewWidget *preview = nullptr; + QSplitter *materialSplitter = nullptr; QLabel *titleLabel = nullptr; QLabel *statusLabel = nullptr; QPushButton *albedoButton = nullptr;