mirror of
https://github.com/NohamR/Reclass.git
synced 2026-05-10 19:59:21 +00:00
Compare commits
7 Commits
snapshot-0
...
msvc-fix-2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
955db3813a | ||
|
|
f4f203e0f0 | ||
|
|
1d3f1a672a | ||
|
|
da29206bdb | ||
|
|
4986893fca | ||
|
|
17a1fb032e | ||
|
|
8d92957837 |
@@ -109,6 +109,8 @@ add_executable(Reclass
|
|||||||
src/scannerpanel.h
|
src/scannerpanel.h
|
||||||
src/scannerpanel.cpp
|
src/scannerpanel.cpp
|
||||||
src/mainwindow.h
|
src/mainwindow.h
|
||||||
|
src/startpage.h
|
||||||
|
src/dock_tab_buttons.h
|
||||||
src/optionsdialog.h
|
src/optionsdialog.h
|
||||||
src/optionsdialog.cpp
|
src/optionsdialog.cpp
|
||||||
src/titlebar.h
|
src/titlebar.h
|
||||||
@@ -344,6 +346,11 @@ if(BUILD_TESTING)
|
|||||||
endif()
|
endif()
|
||||||
add_test(NAME test_controller COMMAND test_controller)
|
add_test(NAME test_controller COMMAND test_controller)
|
||||||
|
|
||||||
|
add_executable(grab_tabs tests/grab_tabs.cpp
|
||||||
|
src/themes/theme.cpp src/themes/thememanager.cpp src/resources.qrc)
|
||||||
|
target_include_directories(grab_tabs PRIVATE src)
|
||||||
|
target_link_libraries(grab_tabs PRIVATE ${QT}::Widgets ${QT}::Svg ${QT}::Test)
|
||||||
|
|
||||||
add_executable(test_validation tests/test_validation.cpp
|
add_executable(test_validation tests/test_validation.cpp
|
||||||
src/editor.cpp src/compose.cpp src/format.cpp src/addressparser.cpp src/controller.cpp
|
src/editor.cpp src/compose.cpp src/format.cpp src/addressparser.cpp src/controller.cpp
|
||||||
src/processpicker.cpp src/processpicker.ui src/providerregistry.cpp
|
src/processpicker.cpp src/processpicker.ui src/providerregistry.cpp
|
||||||
|
|||||||
@@ -140,10 +140,12 @@
|
|||||||
<ClInclude Include="..\src\addressparser.h" />
|
<ClInclude Include="..\src\addressparser.h" />
|
||||||
<ClInclude Include="..\src\core.h" />
|
<ClInclude Include="..\src\core.h" />
|
||||||
<ClInclude Include="..\src\disasm.h" />
|
<ClInclude Include="..\src\disasm.h" />
|
||||||
|
<QtMoc Include="..\src\dock_tab_buttons.h" />
|
||||||
<ClInclude Include="..\src\generator.h" />
|
<ClInclude Include="..\src\generator.h" />
|
||||||
<ClInclude Include="..\src\iplugin.h" />
|
<ClInclude Include="..\src\iplugin.h" />
|
||||||
<ClInclude Include="..\src\pluginmanager.h" />
|
<ClInclude Include="..\src\pluginmanager.h" />
|
||||||
<ClInclude Include="..\src\providerregistry.h" />
|
<ClInclude Include="..\src\providerregistry.h" />
|
||||||
|
<QtMoc Include="..\src\startpage.h" />
|
||||||
<ClInclude Include="..\src\workspace_model.h" />
|
<ClInclude Include="..\src\workspace_model.h" />
|
||||||
<ClInclude Include="..\src\imports\export_reclass_xml.h" />
|
<ClInclude Include="..\src\imports\export_reclass_xml.h" />
|
||||||
<ClInclude Include="..\src\imports\import_pdb.h" />
|
<ClInclude Include="..\src\imports\import_pdb.h" />
|
||||||
@@ -163,7 +165,12 @@
|
|||||||
<ClCompile Include="..\src\editor.cpp" />
|
<ClCompile Include="..\src\editor.cpp" />
|
||||||
<ClCompile Include="..\src\format.cpp" />
|
<ClCompile Include="..\src\format.cpp" />
|
||||||
<ClCompile Include="..\src\generator.cpp" />
|
<ClCompile Include="..\src\generator.cpp" />
|
||||||
<ClCompile Include="..\src\main.cpp" />
|
<ClCompile Include="..\src\main.cpp">
|
||||||
|
<DynamicSource Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">input</DynamicSource>
|
||||||
|
<QtMocFileName Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">%(Filename).moc</QtMocFileName>
|
||||||
|
<DynamicSource Condition="'$(Configuration)|$(Platform)'=='Release|x64'">input</DynamicSource>
|
||||||
|
<QtMocFileName Condition="'$(Configuration)|$(Platform)'=='Release|x64'">%(Filename).moc</QtMocFileName>
|
||||||
|
</ClCompile>
|
||||||
<ClCompile Include="..\src\optionsdialog.cpp" />
|
<ClCompile Include="..\src\optionsdialog.cpp" />
|
||||||
<ClCompile Include="..\src\pluginmanager.cpp" />
|
<ClCompile Include="..\src\pluginmanager.cpp" />
|
||||||
<ClCompile Include="..\src\processpicker.cpp" />
|
<ClCompile Include="..\src\processpicker.cpp" />
|
||||||
|
|||||||
@@ -89,6 +89,12 @@
|
|||||||
<QtMoc Include="..\src\themes\thememanager.h">
|
<QtMoc Include="..\src\themes\thememanager.h">
|
||||||
<Filter>Header Files\themes</Filter>
|
<Filter>Header Files\themes</Filter>
|
||||||
</QtMoc>
|
</QtMoc>
|
||||||
|
<QtMoc Include="..\src\dock_tab_buttons.h">
|
||||||
|
<Filter>Header Files</Filter>
|
||||||
|
</QtMoc>
|
||||||
|
<QtMoc Include="..\src\startpage.h">
|
||||||
|
<Filter>Header Files</Filter>
|
||||||
|
</QtMoc>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ClInclude Include="..\src\addressparser.h">
|
<ClInclude Include="..\src\addressparser.h">
|
||||||
@@ -165,9 +171,6 @@
|
|||||||
<ClCompile Include="..\src\generator.cpp">
|
<ClCompile Include="..\src\generator.cpp">
|
||||||
<Filter>Source Files</Filter>
|
<Filter>Source Files</Filter>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
<ClCompile Include="..\src\main.cpp">
|
|
||||||
<Filter>Source Files</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="..\src\optionsdialog.cpp">
|
<ClCompile Include="..\src\optionsdialog.cpp">
|
||||||
<Filter>Source Files</Filter>
|
<Filter>Source Files</Filter>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
@@ -219,5 +222,8 @@
|
|||||||
<ClCompile Include="..\src\themes\thememanager.cpp">
|
<ClCompile Include="..\src\themes\thememanager.cpp">
|
||||||
<Filter>Source Files\themes</Filter>
|
<Filter>Source Files\themes</Filter>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
|
<ClCompile Include="..\src\main.cpp">
|
||||||
|
<Filter>Source Files</Filter>
|
||||||
|
</ClCompile>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
65
src/dock_tab_buttons.h
Normal file
65
src/dock_tab_buttons.h
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QWidget>
|
||||||
|
#include <QToolButton>
|
||||||
|
#include <QHBoxLayout>
|
||||||
|
#include <QIcon>
|
||||||
|
|
||||||
|
// Dock tab button widget (pin + close)
|
||||||
|
// Placed on the right side of each dock tab via QTabBar::setTabButton.
|
||||||
|
class DockTabButtons : public QWidget {
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
QToolButton* pinBtn;
|
||||||
|
QToolButton* closeBtn;
|
||||||
|
bool pinned = false;
|
||||||
|
|
||||||
|
explicit DockTabButtons(QWidget* parent = nullptr) : QWidget(parent) {
|
||||||
|
auto* hl = new QHBoxLayout(this);
|
||||||
|
hl->setContentsMargins(0, 0, 0, 0);
|
||||||
|
hl->setSpacing(0);
|
||||||
|
|
||||||
|
pinBtn = new QToolButton(this);
|
||||||
|
pinBtn->setAutoRaise(true);
|
||||||
|
pinBtn->setCursor(Qt::PointingHandCursor);
|
||||||
|
pinBtn->setFixedSize(16, 16);
|
||||||
|
pinBtn->setToolTip("Pin tab");
|
||||||
|
updatePinIcon();
|
||||||
|
hl->addWidget(pinBtn);
|
||||||
|
|
||||||
|
closeBtn = new QToolButton(this);
|
||||||
|
closeBtn->setAutoRaise(true);
|
||||||
|
closeBtn->setCursor(Qt::PointingHandCursor);
|
||||||
|
closeBtn->setFixedSize(16, 16);
|
||||||
|
closeBtn->setToolTip("Close tab");
|
||||||
|
closeBtn->setIcon(QIcon(":/vsicons/close.svg"));
|
||||||
|
closeBtn->setIconSize(QSize(12, 12));
|
||||||
|
hl->addWidget(closeBtn);
|
||||||
|
|
||||||
|
connect(pinBtn, &QToolButton::clicked, this, [this]() {
|
||||||
|
pinned = !pinned;
|
||||||
|
updatePinIcon();
|
||||||
|
emit pinToggled(pinned);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void applyTheme(const QColor& hover) {
|
||||||
|
QString style = QStringLiteral(
|
||||||
|
"QToolButton { border: none; padding: 1px; border-radius: 0px; }"
|
||||||
|
"QToolButton:hover { background: %1; }").arg(hover.name());
|
||||||
|
pinBtn->setStyleSheet(style);
|
||||||
|
closeBtn->setStyleSheet(style);
|
||||||
|
}
|
||||||
|
|
||||||
|
void setPinned(bool p) { pinned = p; updatePinIcon(); emit pinToggled(pinned); }
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void pinToggled(bool pinned);
|
||||||
|
|
||||||
|
private:
|
||||||
|
void updatePinIcon() {
|
||||||
|
pinBtn->setIcon(QIcon(pinned ? ":/vsicons/pinned.svg" : ":/vsicons/pin.svg"));
|
||||||
|
pinBtn->setIconSize(QSize(12, 12));
|
||||||
|
pinBtn->setToolTip(pinned ? "Unpin tab" : "Pin tab");
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -1,143 +0,0 @@
|
|||||||
{
|
|
||||||
"baseAddress": "0",
|
|
||||||
"nextId": "20",
|
|
||||||
"nodes": [
|
|
||||||
{
|
|
||||||
"id": "1",
|
|
||||||
"kind": "Struct",
|
|
||||||
"name": "player",
|
|
||||||
"structTypeName": "PlayerEntity",
|
|
||||||
"classKeyword": "class",
|
|
||||||
"parentId": "0",
|
|
||||||
"offset": 0,
|
|
||||||
"collapsed": false,
|
|
||||||
"refId": "0",
|
|
||||||
"elementKind": "UInt8",
|
|
||||||
"arrayLen": 1,
|
|
||||||
"strLen": 64
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "2",
|
|
||||||
"kind": "Pointer64",
|
|
||||||
"name": "__vptr",
|
|
||||||
"parentId": "1",
|
|
||||||
"offset": 0,
|
|
||||||
"collapsed": false,
|
|
||||||
"refId": "0",
|
|
||||||
"elementKind": "UInt8",
|
|
||||||
"arrayLen": 1,
|
|
||||||
"strLen": 64
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "3",
|
|
||||||
"kind": "Int32",
|
|
||||||
"name": "health",
|
|
||||||
"parentId": "1",
|
|
||||||
"offset": 8,
|
|
||||||
"collapsed": false,
|
|
||||||
"refId": "0",
|
|
||||||
"elementKind": "UInt8",
|
|
||||||
"arrayLen": 1,
|
|
||||||
"strLen": 64
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "4",
|
|
||||||
"kind": "Int32",
|
|
||||||
"name": "armor",
|
|
||||||
"parentId": "1",
|
|
||||||
"offset": 12,
|
|
||||||
"collapsed": false,
|
|
||||||
"refId": "0",
|
|
||||||
"elementKind": "UInt8",
|
|
||||||
"arrayLen": 1,
|
|
||||||
"strLen": 64
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "5",
|
|
||||||
"kind": "Float",
|
|
||||||
"name": "pos_x",
|
|
||||||
"parentId": "1",
|
|
||||||
"offset": 16,
|
|
||||||
"collapsed": false,
|
|
||||||
"refId": "0",
|
|
||||||
"elementKind": "UInt8",
|
|
||||||
"arrayLen": 1,
|
|
||||||
"strLen": 64
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "6",
|
|
||||||
"kind": "Float",
|
|
||||||
"name": "pos_y",
|
|
||||||
"parentId": "1",
|
|
||||||
"offset": 20,
|
|
||||||
"collapsed": false,
|
|
||||||
"refId": "0",
|
|
||||||
"elementKind": "UInt8",
|
|
||||||
"arrayLen": 1,
|
|
||||||
"strLen": 64
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "7",
|
|
||||||
"kind": "Float",
|
|
||||||
"name": "pos_z",
|
|
||||||
"parentId": "1",
|
|
||||||
"offset": 24,
|
|
||||||
"collapsed": false,
|
|
||||||
"refId": "0",
|
|
||||||
"elementKind": "UInt8",
|
|
||||||
"arrayLen": 1,
|
|
||||||
"strLen": 64
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "8",
|
|
||||||
"kind": "Hex32",
|
|
||||||
"name": "pad_1C",
|
|
||||||
"parentId": "1",
|
|
||||||
"offset": 28,
|
|
||||||
"collapsed": false,
|
|
||||||
"refId": "0",
|
|
||||||
"elementKind": "UInt8",
|
|
||||||
"arrayLen": 1,
|
|
||||||
"strLen": 64
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "9",
|
|
||||||
"kind": "Pointer64",
|
|
||||||
"name": "name",
|
|
||||||
"parentId": "1",
|
|
||||||
"offset": 32,
|
|
||||||
"collapsed": false,
|
|
||||||
"refId": "0",
|
|
||||||
"elementKind": "UInt8",
|
|
||||||
"arrayLen": 1,
|
|
||||||
"strLen": 64,
|
|
||||||
"ptrDepth": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "10",
|
|
||||||
"kind": "UInt64",
|
|
||||||
"name": "flags",
|
|
||||||
"parentId": "1",
|
|
||||||
"offset": 40,
|
|
||||||
"collapsed": false,
|
|
||||||
"refId": "0",
|
|
||||||
"elementKind": "UInt8",
|
|
||||||
"arrayLen": 1,
|
|
||||||
"strLen": 64
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "11",
|
|
||||||
"kind": "Hex64",
|
|
||||||
"name": "static_field",
|
|
||||||
"parentId": "1",
|
|
||||||
"offset": 0,
|
|
||||||
"isStatic": true,
|
|
||||||
"offsetExpr": "base + pos_x",
|
|
||||||
"collapsed": false,
|
|
||||||
"refId": "0",
|
|
||||||
"elementKind": "UInt8",
|
|
||||||
"arrayLen": 1,
|
|
||||||
"strLen": 64
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
245257
src/examples/WinSDK.rcx
Normal file
245257
src/examples/WinSDK.rcx
Normal file
File diff suppressed because it is too large
Load Diff
42817
src/examples/windows-x86_64.h
Normal file
42817
src/examples/windows-x86_64.h
Normal file
File diff suppressed because it is too large
Load Diff
148
src/main.cpp
148
src/main.cpp
@@ -480,64 +480,7 @@ public:
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// ── Dock tab button widget (pin + close) ──
|
#include "dock_tab_buttons.h"
|
||||||
// Placed on the right side of each dock tab via QTabBar::setTabButton.
|
|
||||||
class DockTabButtons : public QWidget {
|
|
||||||
Q_OBJECT
|
|
||||||
public:
|
|
||||||
QToolButton* pinBtn;
|
|
||||||
QToolButton* closeBtn;
|
|
||||||
bool pinned = false;
|
|
||||||
|
|
||||||
explicit DockTabButtons(QWidget* parent = nullptr) : QWidget(parent) {
|
|
||||||
auto* hl = new QHBoxLayout(this);
|
|
||||||
hl->setContentsMargins(0, 0, 0, 0);
|
|
||||||
hl->setSpacing(0);
|
|
||||||
|
|
||||||
pinBtn = new QToolButton(this);
|
|
||||||
pinBtn->setAutoRaise(true);
|
|
||||||
pinBtn->setCursor(Qt::PointingHandCursor);
|
|
||||||
pinBtn->setFixedSize(16, 16);
|
|
||||||
pinBtn->setToolTip("Pin tab");
|
|
||||||
updatePinIcon();
|
|
||||||
hl->addWidget(pinBtn);
|
|
||||||
|
|
||||||
closeBtn = new QToolButton(this);
|
|
||||||
closeBtn->setAutoRaise(true);
|
|
||||||
closeBtn->setCursor(Qt::PointingHandCursor);
|
|
||||||
closeBtn->setFixedSize(16, 16);
|
|
||||||
closeBtn->setToolTip("Close tab");
|
|
||||||
closeBtn->setIcon(QIcon(":/vsicons/close.svg"));
|
|
||||||
closeBtn->setIconSize(QSize(12, 12));
|
|
||||||
hl->addWidget(closeBtn);
|
|
||||||
|
|
||||||
connect(pinBtn, &QToolButton::clicked, this, [this]() {
|
|
||||||
pinned = !pinned;
|
|
||||||
updatePinIcon();
|
|
||||||
emit pinToggled(pinned);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void applyTheme(const QColor& hover) {
|
|
||||||
QString style = QStringLiteral(
|
|
||||||
"QToolButton { border: none; padding: 1px; border-radius: 0px; }"
|
|
||||||
"QToolButton:hover { background: %1; }").arg(hover.name());
|
|
||||||
pinBtn->setStyleSheet(style);
|
|
||||||
closeBtn->setStyleSheet(style);
|
|
||||||
}
|
|
||||||
|
|
||||||
void setPinned(bool p) { pinned = p; updatePinIcon(); emit pinToggled(pinned); }
|
|
||||||
|
|
||||||
signals:
|
|
||||||
void pinToggled(bool pinned);
|
|
||||||
|
|
||||||
private:
|
|
||||||
void updatePinIcon() {
|
|
||||||
pinBtn->setIcon(QIcon(pinned ? ":/vsicons/pinned.svg" : ":/vsicons/pin.svg"));
|
|
||||||
pinBtn->setIconSize(QSize(12, 12));
|
|
||||||
pinBtn->setToolTip(pinned ? "Unpin tab" : "Pin tab");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
static void applyGlobalTheme(const rcx::Theme& theme) {
|
static void applyGlobalTheme(const rcx::Theme& theme) {
|
||||||
QPalette pal;
|
QPalette pal;
|
||||||
@@ -631,6 +574,7 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent) {
|
|||||||
overlay->raise();
|
overlay->raise();
|
||||||
overlay->show();
|
overlay->show();
|
||||||
|
|
||||||
|
// Central placeholder — will be replaced by start page after construction
|
||||||
m_centralPlaceholder = new QWidget(this);
|
m_centralPlaceholder = new QWidget(this);
|
||||||
m_centralPlaceholder->setFixedSize(0, 0);
|
m_centralPlaceholder->setFixedSize(0, 0);
|
||||||
m_centralPlaceholder->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Ignored);
|
m_centralPlaceholder->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Ignored);
|
||||||
@@ -669,6 +613,9 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent) {
|
|||||||
connect(&ThemeManager::instance(), &ThemeManager::themeChanged,
|
connect(&ThemeManager::instance(), &ThemeManager::themeChanged,
|
||||||
this, &MainWindow::applyTheme);
|
this, &MainWindow::applyTheme);
|
||||||
|
|
||||||
|
// Apply theme once at startup (the signal only fires on change, not initial load)
|
||||||
|
applyTheme(ThemeManager::instance().current());
|
||||||
|
|
||||||
// Load plugins
|
// Load plugins
|
||||||
m_pluginManager.LoadPlugins();
|
m_pluginManager.LoadPlugins();
|
||||||
|
|
||||||
@@ -2349,15 +2296,20 @@ void MainWindow::applyTheme(const Theme& theme) {
|
|||||||
if (m_titleBar)
|
if (m_titleBar)
|
||||||
m_titleBar->applyTheme(theme);
|
m_titleBar->applyTheme(theme);
|
||||||
|
|
||||||
|
// Start page
|
||||||
|
if (m_startPage)
|
||||||
|
m_startPage->applyTheme(theme);
|
||||||
|
|
||||||
// Update border overlay color
|
// Update border overlay color
|
||||||
updateBorderColor(isActiveWindow() ? theme.borderFocused : theme.border);
|
updateBorderColor(isActiveWindow() ? theme.borderFocused : theme.border);
|
||||||
|
|
||||||
// Style doc dock tab bars and remove dock borders
|
// Style doc dock tab bars and remove dock borders.
|
||||||
|
// QWidget default colors are required because having ANY stylesheet on QMainWindow
|
||||||
|
// switches children from palette-based to CSS-based rendering.
|
||||||
setStyleSheet(QStringLiteral(
|
setStyleSheet(QStringLiteral(
|
||||||
"QMainWindow::separator { width: 1px; height: 1px; background: transparent; }"
|
"QMainWindow::separator { width: 1px; height: 1px; background: transparent; }"
|
||||||
"QDockWidget { border: none; }"
|
"QDockWidget { border: none; }"
|
||||||
"QDockWidget > QWidget { border: none; }")
|
"QDockWidget > QWidget { border: none; }"));
|
||||||
.arg(theme.border.name()));
|
|
||||||
|
|
||||||
for (auto* tabBar : findChildren<QTabBar*>()) {
|
for (auto* tabBar : findChildren<QTabBar*>()) {
|
||||||
// Only style tab bars owned directly by this QMainWindow (dock tabs),
|
// Only style tab bars owned directly by this QMainWindow (dock tabs),
|
||||||
@@ -3222,10 +3174,7 @@ QDockWidget* MainWindow::project_open(const QString& path) {
|
|||||||
if (filePath.isEmpty()) {
|
if (filePath.isEmpty()) {
|
||||||
filePath = QFileDialog::getOpenFileName(this,
|
filePath = QFileDialog::getOpenFileName(this,
|
||||||
"Open Definition", {},
|
"Open Definition", {},
|
||||||
"All Supported (*.rcx *.json *.reclass *.MemeCls *.xml)"
|
"Reclass (*.rcx)"
|
||||||
";;Reclass (*.rcx)"
|
|
||||||
";;JSON (*.json)"
|
|
||||||
";;ReClass XML (*.reclass *.MemeCls *.xml)"
|
|
||||||
";;All (*)");
|
";;All (*)");
|
||||||
if (filePath.isEmpty()) return nullptr;
|
if (filePath.isEmpty()) return nullptr;
|
||||||
}
|
}
|
||||||
@@ -3236,8 +3185,7 @@ QDockWidget* MainWindow::project_open(const QString& path) {
|
|||||||
QFile probe(filePath);
|
QFile probe(filePath);
|
||||||
if (probe.open(QIODevice::ReadOnly)) {
|
if (probe.open(QIODevice::ReadOnly)) {
|
||||||
QByteArray head = probe.read(64);
|
QByteArray head = probe.read(64);
|
||||||
isXml = head.trimmed().startsWith("<?xml") || head.trimmed().startsWith("<ReClass")
|
isXml = head.trimmed().startsWith("<?xml") || head.trimmed().startsWith("<ReClass");
|
||||||
|| head.trimmed().startsWith("<MemeCls");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4007,6 +3955,66 @@ void MainWindow::updateBorderColor(const QColor& color) {
|
|||||||
m_borderOverlay->update();
|
m_borderOverlay->update();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MainWindow::showStartPage() {
|
||||||
|
if (m_startPage) return;
|
||||||
|
|
||||||
|
m_startPage = new StartPageWidget(this);
|
||||||
|
m_startPage->applyTheme(ThemeManager::instance().current());
|
||||||
|
|
||||||
|
// Size the popup to ~90% of the main window
|
||||||
|
QSize sz(qBound(900, int(width() * 0.9), width() - 20),
|
||||||
|
qBound(560, int(height() * 0.85), height() - 20));
|
||||||
|
m_startPage->setFixedSize(sz);
|
||||||
|
|
||||||
|
// Wire start page signals — each closes the dialog then performs action
|
||||||
|
connect(m_startPage, &StartPageWidget::openProject, this, [this]() {
|
||||||
|
dismissStartPage();
|
||||||
|
openFile();
|
||||||
|
if (m_tabs.isEmpty()) showStartPage();
|
||||||
|
});
|
||||||
|
connect(m_startPage, &StartPageWidget::newClass, this, [this]() {
|
||||||
|
dismissStartPage();
|
||||||
|
newClass();
|
||||||
|
});
|
||||||
|
connect(m_startPage, &StartPageWidget::importSource, this, [this]() {
|
||||||
|
dismissStartPage();
|
||||||
|
importFromSource();
|
||||||
|
if (m_tabs.isEmpty()) showStartPage();
|
||||||
|
});
|
||||||
|
connect(m_startPage, &StartPageWidget::importXml, this, [this]() {
|
||||||
|
dismissStartPage();
|
||||||
|
importReclassXml();
|
||||||
|
if (m_tabs.isEmpty()) showStartPage();
|
||||||
|
});
|
||||||
|
connect(m_startPage, &StartPageWidget::importPdb, this, [this]() {
|
||||||
|
dismissStartPage();
|
||||||
|
importPdb();
|
||||||
|
if (m_tabs.isEmpty()) showStartPage();
|
||||||
|
});
|
||||||
|
connect(m_startPage, &StartPageWidget::continueClicked, this, [this]() {
|
||||||
|
dismissStartPage();
|
||||||
|
selfTest();
|
||||||
|
});
|
||||||
|
connect(m_startPage, &StartPageWidget::fileSelected, this, [this](const QString& path) {
|
||||||
|
dismissStartPage();
|
||||||
|
project_open(path);
|
||||||
|
});
|
||||||
|
connect(m_startPage, &QDialog::rejected, this, [this]() {
|
||||||
|
dismissStartPage();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Center over main window and show as application-modal
|
||||||
|
m_startPage->move(geometry().center() - m_startPage->rect().center());
|
||||||
|
m_startPage->open();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MainWindow::dismissStartPage() {
|
||||||
|
if (!m_startPage) return;
|
||||||
|
m_startPage->close();
|
||||||
|
m_startPage->deleteLater();
|
||||||
|
m_startPage = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace rcx
|
} // namespace rcx
|
||||||
|
|
||||||
// ── Entry point ──
|
// ── Entry point ──
|
||||||
@@ -4043,11 +4051,9 @@ int main(int argc, char* argv[]) {
|
|||||||
|
|
||||||
window.show();
|
window.show();
|
||||||
|
|
||||||
// Auto-open demo project from saved .rcx file
|
// Show VS2022-style start page instead of jumping straight to demo
|
||||||
QMetaObject::invokeMethod(&window, "selfTest");
|
QMetaObject::invokeMethod(&window, "showStartPage", Qt::QueuedConnection);
|
||||||
|
|
||||||
return app.exec();
|
return app.exec();
|
||||||
}
|
}
|
||||||
|
|
||||||
// DockTabButtons has Q_OBJECT in main.cpp — need the moc include
|
|
||||||
#include "main.moc"
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
#include "titlebar.h"
|
#include "titlebar.h"
|
||||||
#include "pluginmanager.h"
|
#include "pluginmanager.h"
|
||||||
#include "scannerpanel.h"
|
#include "scannerpanel.h"
|
||||||
|
#include "startpage.h"
|
||||||
#include <QMainWindow>
|
#include <QMainWindow>
|
||||||
#include <QLabel>
|
#include <QLabel>
|
||||||
#include <QSplitter>
|
#include <QSplitter>
|
||||||
@@ -169,6 +170,11 @@ private:
|
|||||||
DockGripWidget* m_scanDockGrip = nullptr;
|
DockGripWidget* m_scanDockGrip = nullptr;
|
||||||
void createScannerDock();
|
void createScannerDock();
|
||||||
|
|
||||||
|
// Start page
|
||||||
|
StartPageWidget* m_startPage = nullptr;
|
||||||
|
Q_INVOKABLE void showStartPage();
|
||||||
|
void dismissStartPage();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void changeEvent(QEvent* event) override;
|
void changeEvent(QEvent* event) override;
|
||||||
void resizeEvent(QResizeEvent* event) override;
|
void resizeEvent(QResizeEvent* event) override;
|
||||||
|
|||||||
@@ -64,5 +64,6 @@
|
|||||||
<file alias="pinned.svg">vsicons/pinned.svg</file>
|
<file alias="pinned.svg">vsicons/pinned.svg</file>
|
||||||
<file alias="close-all.svg">vsicons/close-all.svg</file>
|
<file alias="close-all.svg">vsicons/close-all.svg</file>
|
||||||
<file alias="split-vertical.svg">vsicons/split-vertical.svg</file>
|
<file alias="split-vertical.svg">vsicons/split-vertical.svg</file>
|
||||||
|
<file alias="book.svg">vsicons/book.svg</file>
|
||||||
</qresource>
|
</qresource>
|
||||||
</RCC>
|
</RCC>
|
||||||
|
|||||||
360
src/startpage.h
Normal file
360
src/startpage.h
Normal file
@@ -0,0 +1,360 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "themes/thememanager.h"
|
||||||
|
#include <QDialog>
|
||||||
|
#include <QLineEdit>
|
||||||
|
#include <QPainter>
|
||||||
|
#include <QMouseEvent>
|
||||||
|
#include <QWheelEvent>
|
||||||
|
#include <QFileInfo>
|
||||||
|
#include <QDir>
|
||||||
|
#include <QSettings>
|
||||||
|
#include <QCoreApplication>
|
||||||
|
#include <QPainterPath>
|
||||||
|
|
||||||
|
namespace rcx {
|
||||||
|
|
||||||
|
// Single-widget start page: everything painted in paintEvent.
|
||||||
|
// Zero CSS, zero Fusion conflicts, zero child-widget styling issues.
|
||||||
|
|
||||||
|
class StartPageWidget : public QDialog {
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
explicit StartPageWidget(QWidget* parent = nullptr) : QDialog(parent) {
|
||||||
|
setWindowFlags(Qt::FramelessWindowHint | Qt::Dialog);
|
||||||
|
setMouseTracking(true);
|
||||||
|
setAttribute(Qt::WA_OpaquePaintEvent);
|
||||||
|
|
||||||
|
m_search = new QLineEdit(this);
|
||||||
|
m_search->setPlaceholderText("Search recent...");
|
||||||
|
m_search->setFixedHeight(30);
|
||||||
|
m_search->setMaximumWidth(330);
|
||||||
|
m_search->addAction(QIcon(":/vsicons/search.svg"), QLineEdit::TrailingPosition);
|
||||||
|
connect(m_search, &QLineEdit::textChanged, this, [this]{ buildGroups(); update(); });
|
||||||
|
|
||||||
|
loadEntries();
|
||||||
|
buildGroups();
|
||||||
|
applyTheme(ThemeManager::instance().current());
|
||||||
|
}
|
||||||
|
|
||||||
|
void applyTheme(const Theme& t) {
|
||||||
|
m_t = t;
|
||||||
|
m_search->setStyleSheet(
|
||||||
|
"QLineEdit { background: " + t.background.name() + "; color: " + t.text.name()
|
||||||
|
+ "; border: 1px solid " + t.border.name()
|
||||||
|
+ "; padding: 2px 8px; font-size: 13px; }"
|
||||||
|
"QLineEdit:focus { border: 1px solid " + t.borderFocused.name() + "; }");
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void openProject();
|
||||||
|
void newClass();
|
||||||
|
void importSource();
|
||||||
|
void importXml();
|
||||||
|
void importPdb();
|
||||||
|
void continueClicked();
|
||||||
|
void fileSelected(const QString& path);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void paintEvent(QPaintEvent*) override {
|
||||||
|
QPainter p(this);
|
||||||
|
p.setRenderHint(QPainter::Antialiasing);
|
||||||
|
|
||||||
|
const int LX = 48, TM = 36, RM = 32, GAP = 40, RW = 340;
|
||||||
|
const int rpX = width() - RW - RM;
|
||||||
|
const int lW = qMax(100, rpX - GAP - LX);
|
||||||
|
|
||||||
|
p.fillRect(rect(), m_t.background);
|
||||||
|
|
||||||
|
// ── Title ──
|
||||||
|
int y = TM;
|
||||||
|
QFont titleF = font(); titleF.setPixelSize(30); titleF.setWeight(QFont::Light);
|
||||||
|
p.setFont(titleF); p.setPen(m_t.text);
|
||||||
|
QFontMetrics titleFm(titleF);
|
||||||
|
p.drawText(LX, y + titleFm.ascent(), "Reclass");
|
||||||
|
y += titleFm.height() + 24;
|
||||||
|
|
||||||
|
// ── Headings (left + right at same y) ──
|
||||||
|
QFont headF = font(); headF.setPixelSize(20); headF.setWeight(QFont::DemiBold);
|
||||||
|
p.setFont(headF); QFontMetrics headFm(headF);
|
||||||
|
p.drawText(LX, y + headFm.ascent(), "Open recent");
|
||||||
|
int ry = y;
|
||||||
|
p.drawText(rpX, ry + headFm.ascent(), "Get started");
|
||||||
|
ry += headFm.height() + 14;
|
||||||
|
y += headFm.height() + 14;
|
||||||
|
|
||||||
|
// ── Search bar (only child widget) ──
|
||||||
|
m_search->setGeometry(LX, y, qMin(330, lW), 30);
|
||||||
|
y += 46;
|
||||||
|
m_listTop = y;
|
||||||
|
|
||||||
|
// ── Right panel ──
|
||||||
|
drawCards(p, rpX, ry, RW);
|
||||||
|
|
||||||
|
// ── File list ──
|
||||||
|
drawFileList(p, LX, lW);
|
||||||
|
|
||||||
|
// ── Border ──
|
||||||
|
p.setPen(QPen(m_t.border, 1));
|
||||||
|
p.setBrush(Qt::NoBrush);
|
||||||
|
p.drawRect(rect().adjusted(0, 0, -1, -1));
|
||||||
|
}
|
||||||
|
|
||||||
|
void mouseMoveEvent(QMouseEvent* e) override {
|
||||||
|
auto [z, i] = hitTest(e->pos());
|
||||||
|
if (z != m_hz || i != m_hi) {
|
||||||
|
m_hz = z; m_hi = i;
|
||||||
|
setCursor(z != HZ_None ? Qt::PointingHandCursor : Qt::ArrowCursor);
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void mousePressEvent(QMouseEvent* e) override {
|
||||||
|
if (e->button() != Qt::LeftButton) return;
|
||||||
|
auto [z, i] = hitTest(e->pos());
|
||||||
|
if (z == HZ_Entry) emit fileSelected(m_filtered[i].path);
|
||||||
|
if (z == HZ_Group) { m_groups[i].expanded = !m_groups[i].expanded; update(); }
|
||||||
|
if (z == HZ_Card && i == 0) emit newClass();
|
||||||
|
if (z == HZ_Card && i == 1) emit openProject();
|
||||||
|
if (z == HZ_Card && i == 2) emit importSource();
|
||||||
|
if (z == HZ_Card && i == 3) emit importXml();
|
||||||
|
if (z == HZ_Card && i == 4) emit importPdb();
|
||||||
|
if (z == HZ_Continue) emit continueClicked();
|
||||||
|
}
|
||||||
|
|
||||||
|
void wheelEvent(QWheelEvent* e) override {
|
||||||
|
m_scrollY = qBound(0, m_scrollY - e->angleDelta().y() / 2, m_maxScroll);
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
|
||||||
|
void resizeEvent(QResizeEvent* e) override { QWidget::resizeEvent(e); update(); }
|
||||||
|
void leaveEvent(QEvent*) override { m_hz = HZ_None; m_hi = -1; setCursor(Qt::ArrowCursor); update(); }
|
||||||
|
void keyPressEvent(QKeyEvent* e) override { if (e->key() == Qt::Key_Escape) reject(); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
enum HZ { HZ_None, HZ_Entry, HZ_Group, HZ_Card, HZ_Continue };
|
||||||
|
struct Hit { HZ zone; int idx; };
|
||||||
|
|
||||||
|
struct Entry {
|
||||||
|
QString path, fileName, dirPath;
|
||||||
|
QDateTime lastModified;
|
||||||
|
bool isExample;
|
||||||
|
};
|
||||||
|
struct Group {
|
||||||
|
QString name;
|
||||||
|
bool expanded = true;
|
||||||
|
QVector<int> entries;
|
||||||
|
};
|
||||||
|
|
||||||
|
Theme m_t;
|
||||||
|
QLineEdit* m_search;
|
||||||
|
QVector<Entry> m_all, m_filtered;
|
||||||
|
QVector<Group> m_groups;
|
||||||
|
int m_scrollY = 0, m_maxScroll = 0, m_listTop = 0, m_contentH = 0;
|
||||||
|
|
||||||
|
HZ m_hz = HZ_None;
|
||||||
|
int m_hi = -1;
|
||||||
|
|
||||||
|
// Hit rects populated during paint
|
||||||
|
QVector<QPair<int, QRectF>> m_grpRects, m_entRects;
|
||||||
|
QRectF m_cardR[5], m_contR;
|
||||||
|
|
||||||
|
void drawIcon(QPainter& p, const QString& path, int x, int y, int sz) {
|
||||||
|
QIcon(path).paint(&p, x, y, sz, sz);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Data loading ──
|
||||||
|
|
||||||
|
void loadEntries() {
|
||||||
|
m_all.clear();
|
||||||
|
QSettings s("Reclass", "Reclass");
|
||||||
|
for (const auto& path : s.value("recentFiles").toStringList()) {
|
||||||
|
QFileInfo fi(path);
|
||||||
|
if (!fi.exists()) continue;
|
||||||
|
m_all.append({fi.absoluteFilePath(), fi.fileName(), fi.absolutePath(),
|
||||||
|
fi.lastModified(), false});
|
||||||
|
}
|
||||||
|
#ifdef __APPLE__
|
||||||
|
QDir exDir(QDir::cleanPath(QCoreApplication::applicationDirPath() + "/../Resources/examples"));
|
||||||
|
#else
|
||||||
|
QDir exDir(QCoreApplication::applicationDirPath() + "/examples");
|
||||||
|
#endif
|
||||||
|
for (const auto& fn : exDir.entryList({"*.rcx"}, QDir::Files, QDir::Name))
|
||||||
|
m_all.append({exDir.absoluteFilePath(fn), fn, exDir.absolutePath(),
|
||||||
|
QFileInfo(exDir.filePath(fn)).lastModified(), true});
|
||||||
|
}
|
||||||
|
|
||||||
|
void buildGroups() {
|
||||||
|
QString f = m_search->text().trimmed().toLower();
|
||||||
|
m_filtered.clear();
|
||||||
|
for (const auto& e : m_all)
|
||||||
|
if (f.isEmpty() || e.fileName.toLower().contains(f) || e.dirPath.toLower().contains(f))
|
||||||
|
m_filtered.append(e);
|
||||||
|
|
||||||
|
QDate today = QDate::currentDate();
|
||||||
|
QVector<int> bk[6];
|
||||||
|
for (int i = 0; i < m_filtered.size(); i++) {
|
||||||
|
auto& e = m_filtered[i];
|
||||||
|
if (e.isExample) { bk[5].append(i); continue; }
|
||||||
|
int d = e.lastModified.date().daysTo(today);
|
||||||
|
if (d == 0) bk[0].append(i);
|
||||||
|
else if (d == 1) bk[1].append(i);
|
||||||
|
else if (d < 7) bk[2].append(i);
|
||||||
|
else if (e.lastModified.date().month() == today.month()
|
||||||
|
&& e.lastModified.date().year() == today.year()) bk[3].append(i);
|
||||||
|
else bk[4].append(i);
|
||||||
|
}
|
||||||
|
static const char* names[] = {"Today","Yesterday","This week","This month","Older","Examples"};
|
||||||
|
m_groups.clear();
|
||||||
|
for (int i = 0; i < 6; i++)
|
||||||
|
if (!bk[i].isEmpty()) m_groups.append({names[i], true, bk[i]});
|
||||||
|
m_scrollY = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Drawing ──
|
||||||
|
|
||||||
|
void drawCards(QPainter& p, int x, int y, int w) {
|
||||||
|
struct C { const char* icon; const char* title; const char* desc; };
|
||||||
|
static const C cards[] = {
|
||||||
|
{":/vsicons/symbol-structure.svg", "New Class", "Start a new binary class definition"},
|
||||||
|
{":/vsicons/folder-opened.svg", "Open project", "Open an existing .rcx project"},
|
||||||
|
{":/vsicons/file-binary.svg", "Import from Source", "Import C/C++ header or source file"},
|
||||||
|
{":/vsicons/code.svg", "Import ReClass XML", "Import from ReClass .xml format"},
|
||||||
|
{":/vsicons/debug.svg", "Import PDB", "Import types from a .pdb symbol file"}
|
||||||
|
};
|
||||||
|
|
||||||
|
const int N = 5, CH = 84, R = 6, panelH = N * CH;
|
||||||
|
|
||||||
|
// Rounded panel background
|
||||||
|
QPainterPath clip;
|
||||||
|
clip.addRoundedRect(QRectF(x, y, w, panelH), R, R);
|
||||||
|
p.save();
|
||||||
|
p.setClipPath(clip);
|
||||||
|
p.fillRect(x, y, w, panelH, m_t.background);
|
||||||
|
|
||||||
|
for (int i = 0; i < N; i++) {
|
||||||
|
int cy = y + i * CH;
|
||||||
|
QRectF cr(x, cy, w, CH);
|
||||||
|
m_cardR[i] = cr;
|
||||||
|
bool hov = (m_hz == HZ_Card && m_hi == i);
|
||||||
|
|
||||||
|
if (hov) {
|
||||||
|
p.fillRect(cr, m_t.hover);
|
||||||
|
p.fillRect(QRectF(x, cy, 3, CH), m_t.indHoverSpan);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Icon (32px, centered vertically)
|
||||||
|
int iconSz = 32;
|
||||||
|
drawIcon(p, cards[i].icon, x + 24, cy + (CH - iconSz) / 2, iconSz);
|
||||||
|
|
||||||
|
// Title + description block, centered vertically
|
||||||
|
int tx = x + 24 + iconSz + 16;
|
||||||
|
QFont tf = font(); tf.setPixelSize(15);
|
||||||
|
QFont df = font(); df.setPixelSize(12);
|
||||||
|
QFontMetrics tfm(tf), dfm(df);
|
||||||
|
int blockH = tfm.height() + 5 + dfm.height();
|
||||||
|
int by = cy + (CH - blockH) / 2;
|
||||||
|
|
||||||
|
p.setFont(tf); p.setPen(m_t.text);
|
||||||
|
p.drawText(tx, by + tfm.ascent(), cards[i].title);
|
||||||
|
p.setFont(df); p.setPen(m_t.textDim);
|
||||||
|
p.drawText(tx, by + tfm.height() + 5 + dfm.ascent(), cards[i].desc);
|
||||||
|
}
|
||||||
|
|
||||||
|
p.restore();
|
||||||
|
|
||||||
|
// "Continue →" centered under the panel
|
||||||
|
int cy = y + panelH + 8;
|
||||||
|
QFont lf = font(); lf.setPixelSize(13);
|
||||||
|
if (m_hz == HZ_Continue) lf.setUnderline(true);
|
||||||
|
p.setFont(lf); p.setPen(m_t.indHoverSpan);
|
||||||
|
QFontMetrics lfm(lf);
|
||||||
|
QString ct = QStringLiteral("Continue \u2192");
|
||||||
|
int cw = lfm.horizontalAdvance(ct);
|
||||||
|
m_contR = QRectF(x + (w - cw) / 2, cy, cw, lfm.height());
|
||||||
|
p.drawText(int(m_contR.x()), cy + lfm.ascent(), ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
void drawFileList(QPainter& p, int x, int w) {
|
||||||
|
int listH = height() - 24 - m_listTop;
|
||||||
|
p.save();
|
||||||
|
p.setClipRect(x, m_listTop, w, listH);
|
||||||
|
|
||||||
|
int fy = m_listTop - m_scrollY;
|
||||||
|
m_grpRects.clear();
|
||||||
|
m_entRects.clear();
|
||||||
|
|
||||||
|
for (int gi = 0; gi < m_groups.size(); gi++) {
|
||||||
|
auto& g = m_groups[gi];
|
||||||
|
if (gi > 0) fy += 15;
|
||||||
|
|
||||||
|
// Group header
|
||||||
|
m_grpRects.append({gi, QRectF(x, fy, w, 28)});
|
||||||
|
p.setPen(Qt::NoPen); p.setBrush(m_t.text);
|
||||||
|
int triX = x + 8, triY = fy + 11;
|
||||||
|
QPolygonF tri;
|
||||||
|
if (g.expanded) tri << QPointF(triX,triY) << QPointF(triX+6,triY) << QPointF(triX+3,triY+6);
|
||||||
|
else tri << QPointF(triX,triY) << QPointF(triX+6,triY+3) << QPointF(triX,triY+6);
|
||||||
|
p.drawPolygon(tri);
|
||||||
|
|
||||||
|
QFont gf = font(); gf.setPixelSize(13);
|
||||||
|
p.setFont(gf); p.setPen(m_t.text);
|
||||||
|
p.drawText(triX + 14, fy + 14 + QFontMetrics(gf).ascent() / 2 - 1, g.name);
|
||||||
|
fy += 28;
|
||||||
|
|
||||||
|
if (!g.expanded) continue;
|
||||||
|
|
||||||
|
for (int ei : g.entries) {
|
||||||
|
auto& e = m_filtered[ei];
|
||||||
|
QRectF er(x, fy, w, 52);
|
||||||
|
m_entRects.append({ei, er});
|
||||||
|
if (m_hz == HZ_Entry && m_hi == ei) p.fillRect(er, m_t.hover);
|
||||||
|
|
||||||
|
drawIcon(p, e.isExample ? ":/vsicons/book.svg" : ":/vsicons/symbol-structure.svg",
|
||||||
|
x + 24, fy + 17, 18);
|
||||||
|
|
||||||
|
int tx = x + 52, avail = w - 64;
|
||||||
|
QFont nf = font(); nf.setPixelSize(14);
|
||||||
|
p.setFont(nf); p.setPen(m_t.text);
|
||||||
|
QFontMetrics nm(nf);
|
||||||
|
int ny = fy + 8;
|
||||||
|
p.drawText(tx, ny + nm.ascent(),
|
||||||
|
nm.elidedText(e.fileName, Qt::ElideMiddle, avail * 0.65));
|
||||||
|
|
||||||
|
if (!e.isExample) {
|
||||||
|
p.setPen(m_t.textDim);
|
||||||
|
QString dt = e.lastModified.toString("M/d/yyyy h:mm AP");
|
||||||
|
p.drawText(x + w - 12 - nm.horizontalAdvance(dt), ny + nm.ascent(), dt);
|
||||||
|
}
|
||||||
|
|
||||||
|
QFont pf = font(); pf.setPixelSize(12);
|
||||||
|
p.setFont(pf); p.setPen(m_t.textDim);
|
||||||
|
QFontMetrics pm(pf);
|
||||||
|
p.drawText(tx, ny + nm.height() + 4 + pm.ascent(),
|
||||||
|
pm.elidedText(e.dirPath, Qt::ElideMiddle, avail));
|
||||||
|
fy += 52;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
m_contentH = fy + m_scrollY - m_listTop;
|
||||||
|
m_maxScroll = qMax(0, m_contentH - listH);
|
||||||
|
p.restore();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Hit testing ──
|
||||||
|
|
||||||
|
Hit hitTest(QPoint pos) const {
|
||||||
|
for (int i = 0; i < 5; i++)
|
||||||
|
if (m_cardR[i].contains(pos)) return {HZ_Card, i};
|
||||||
|
if (m_contR.contains(pos)) return {HZ_Continue, 0};
|
||||||
|
if (pos.y() >= m_listTop && pos.y() < height() - 24) {
|
||||||
|
for (const auto& [gi, r] : m_grpRects)
|
||||||
|
if (r.contains(pos)) return {HZ_Group, gi};
|
||||||
|
for (const auto& [ei, r] : m_entRects)
|
||||||
|
if (r.contains(pos)) return {HZ_Entry, ei};
|
||||||
|
}
|
||||||
|
return {HZ_None, -1};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace rcx
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
#include "titlebar.h"
|
#include "titlebar.h"
|
||||||
#include "themes/thememanager.h"
|
#include "themes/thememanager.h"
|
||||||
|
#include <QMenu>
|
||||||
#include <QMouseEvent>
|
#include <QMouseEvent>
|
||||||
#include <QPainter>
|
#include <QPainter>
|
||||||
#include <QStyle>
|
#include <QStyle>
|
||||||
@@ -76,15 +77,35 @@ void TitleBarWidget::applyTheme(const Theme& theme) {
|
|||||||
QStringLiteral("QLabel { color: %1; font-size: 12px; font-weight: bold; }")
|
QStringLiteral("QLabel { color: %1; font-size: 12px; font-weight: bold; }")
|
||||||
.arg(theme.text.name()));
|
.arg(theme.text.name()));
|
||||||
|
|
||||||
// Menu bar palette — hover/bg handled by MenuBarStyle QProxyStyle.
|
// Menu bar palette — all roles used by MenuBarStyle, so live theme
|
||||||
// Set Window + Button to background so Fusion never paints a foreign color.
|
// switches don't rely on app-palette inheritance (which can stall
|
||||||
|
// once setPalette has been called on a widget).
|
||||||
{
|
{
|
||||||
QPalette mbPal = m_menuBar->palette();
|
QPalette mbPal = m_menuBar->palette();
|
||||||
mbPal.setColor(QPalette::Window, theme.background);
|
mbPal.setColor(QPalette::Window, theme.background);
|
||||||
mbPal.setColor(QPalette::Button, theme.background);
|
mbPal.setColor(QPalette::Button, theme.background);
|
||||||
mbPal.setColor(QPalette::ButtonText, theme.text);
|
mbPal.setColor(QPalette::ButtonText, theme.text);
|
||||||
|
mbPal.setColor(QPalette::Text, theme.text);
|
||||||
|
mbPal.setColor(QPalette::Highlight, theme.selected);
|
||||||
|
mbPal.setColor(QPalette::Link, theme.indHoverSpan);
|
||||||
|
mbPal.setColor(QPalette::AlternateBase, theme.surface);
|
||||||
|
mbPal.setColor(QPalette::Dark, theme.border);
|
||||||
|
mbPal.setColor(QPalette::Mid, theme.hover);
|
||||||
m_menuBar->setPalette(mbPal);
|
m_menuBar->setPalette(mbPal);
|
||||||
m_menuBar->setAutoFillBackground(false);
|
m_menuBar->setAutoFillBackground(false);
|
||||||
|
|
||||||
|
// Propagate to existing QMenu children so dropdown popups update too
|
||||||
|
for (auto* menu : m_menuBar->findChildren<QMenu*>()) {
|
||||||
|
QPalette mp = menu->palette();
|
||||||
|
mp.setColor(QPalette::Window, theme.background);
|
||||||
|
mp.setColor(QPalette::WindowText, theme.text);
|
||||||
|
mp.setColor(QPalette::Text, theme.text);
|
||||||
|
mp.setColor(QPalette::Highlight, theme.selected);
|
||||||
|
mp.setColor(QPalette::Link, theme.indHoverSpan);
|
||||||
|
mp.setColor(QPalette::AlternateBase, theme.surface);
|
||||||
|
mp.setColor(QPalette::Dark, theme.border);
|
||||||
|
menu->setPalette(mp);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Chrome buttons
|
// Chrome buttons
|
||||||
|
|||||||
@@ -63,24 +63,10 @@ inline void buildProjectExplorer(QStandardItemModel* model,
|
|||||||
return QString::fromLatin1(kindToString(m.kind));
|
return QString::fromLatin1(kindToString(m.kind));
|
||||||
};
|
};
|
||||||
|
|
||||||
// Sort structs by visible children count descending (most fields first)
|
// TODO: re-enable sorting once startup perf is acceptable
|
||||||
auto countVisible = [&](const Entry& e) {
|
// auto countVisible = [&](const Entry& e) { ... };
|
||||||
int n = 0;
|
// std::sort(types.begin(), types.end(), cmpChildren);
|
||||||
for (int idx : e.tree->childrenOf(e.node->id))
|
// std::sort(enums.begin(), enums.end(), cmpName);
|
||||||
if (!isHexPad(e.tree->nodes[idx].kind)) ++n;
|
|
||||||
return n;
|
|
||||||
};
|
|
||||||
auto cmpChildren = [&](const Entry& a, const Entry& b) {
|
|
||||||
int ca = countVisible(a);
|
|
||||||
int cb = countVisible(b);
|
|
||||||
if (ca != cb) return ca > cb;
|
|
||||||
return nameOf(a.node).compare(nameOf(b.node), Qt::CaseInsensitive) < 0;
|
|
||||||
};
|
|
||||||
std::sort(types.begin(), types.end(), cmpChildren);
|
|
||||||
auto cmpName = [&](const Entry& a, const Entry& b) {
|
|
||||||
return nameOf(a.node).compare(nameOf(b.node), Qt::CaseInsensitive) < 0;
|
|
||||||
};
|
|
||||||
std::sort(enums.begin(), enums.end(), cmpName);
|
|
||||||
|
|
||||||
for (const auto& e : types) {
|
for (const auto& e : types) {
|
||||||
QVector<int> members = e.tree->childrenOf(e.node->id);
|
QVector<int> members = e.tree->childrenOf(e.node->id);
|
||||||
|
|||||||
Reference in New Issue
Block a user