Add macOS support and CI

This commit is contained in:
Lab
2026-03-02 11:34:22 -08:00
parent efae193520
commit e0d5a799b4
12 changed files with 509 additions and 119 deletions

View File

@@ -3,7 +3,7 @@ name: Build
on:
push:
branches:
- '**'
- "**"
pull_request:
branches: [main]
@@ -22,9 +22,9 @@ jobs:
- name: Install Qt6 and MinGW
uses: jurplel/install-qt-action@v4
with:
version: '6.8.1'
arch: 'win64_mingw'
tools: 'tools_mingw1310,qt.tools.win64_mingw1310'
version: "6.8.1"
arch: "win64_mingw"
tools: "tools_mingw1310,qt.tools.win64_mingw1310"
cache: true
- name: Configure
@@ -84,7 +84,7 @@ jobs:
- name: Install Qt6
uses: jurplel/install-qt-action@v4
with:
version: '6.8.1'
version: "6.8.1"
cache: true
- name: Install dependencies
@@ -141,9 +141,66 @@ jobs:
name: Reclass-linux64-qt6
path: Reclass-linux64-qt6.AppImage
macos:
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
include:
- os: macos-15
qt_arch: clang_arm64
artifact_name: Reclass-macos-arm64-qt6
zip_name: Reclass-macos-arm64-qt6.zip
- os: macos-15-intel
qt_arch: clang_64
artifact_name: Reclass-macos-x86_64-qt6
zip_name: Reclass-macos-x86_64-qt6.zip
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
- name: Install dependencies
run: |
brew update
brew install cmake ninja qt
- name: Configure Qt paths
run: |
QT_PREFIX="$(brew --prefix qt)"
echo "QT_PREFIX=$QT_PREFIX" >> "$GITHUB_ENV"
echo "PATH=$QT_PREFIX/bin:$PATH" >> "$GITHUB_ENV"
- name: Configure
run: cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=Release -DBUILD_UI_TESTS=OFF -DCMAKE_PREFIX_PATH="$QT_PREFIX"
- name: Build
run: cmake --build build
- name: Test
run: ctest --test-dir build --output-on-failure
- name: Package app zip
run: |
MACDEPLOYQT_BIN="$QT_PREFIX/bin/macdeployqt"
if [ ! -x "$MACDEPLOYQT_BIN" ]; then
MACDEPLOYQT_BIN=$(which macdeployqt 2>/dev/null || find "$RUNNER_WORKSPACE" -name macdeployqt -path "*/bin/*" | head -1)
fi
echo "Found macdeployqt at: $MACDEPLOYQT_BIN"
"$MACDEPLOYQT_BIN" build/Reclass.app -always-overwrite
codesign --force --deep --sign - build/Reclass.app
ditto -c -k --sequesterRsrc --keepParent build/Reclass.app "${{ matrix.zip_name }}"
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: ${{ matrix.artifact_name }}
path: ${{ matrix.zip_name }}
release:
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
needs: [windows, linux]
needs: [windows, linux, macos]
runs-on: ubuntu-latest
steps:
@@ -168,5 +225,7 @@ jobs:
files: |
artifacts/Reclass-win64-qt6/Reclass-win64-qt6.zip
artifacts/Reclass-linux64-qt6/Reclass-linux64-qt6.AppImage
artifacts/Reclass-macos-arm64-qt6/Reclass-macos-arm64-qt6.zip
artifacts/Reclass-macos-x86_64-qt6/Reclass-macos-x86_64-qt6.zip
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

1
.gitignore vendored
View File

@@ -14,3 +14,4 @@ CMakeUserPresets.json
plugins/RcNetPluginCompatLayer/bridge/obj
plugins/RcNetPluginCompatLayer/bridge/bin
.cache
*.DS_Store

View File

@@ -113,6 +113,8 @@ add_executable(Reclass
src/optionsdialog.cpp
src/titlebar.h
src/titlebar.cpp
src/macos_titlebar.h
$<$<PLATFORM_ID:Darwin>:src/macos_titlebar.mm>
src/mcp/mcp_bridge.h
src/mcp/mcp_bridge.cpp
src/addressparser.h
@@ -124,6 +126,16 @@ add_executable(Reclass
$<$<PLATFORM_ID:Windows>:src/app.rc>
)
if(APPLE)
set_target_properties(Reclass PROPERTIES
MACOSX_BUNDLE TRUE
MACOSX_BUNDLE_ICON_FILE "class.icns"
)
target_sources(Reclass PRIVATE src/icons/class.icns)
set_source_files_properties(src/icons/class.icns
PROPERTIES MACOSX_PACKAGE_LOCATION "Resources")
endif()
target_include_directories(Reclass PRIVATE src third_party/fadec)
target_link_libraries(Reclass PRIVATE
@@ -150,6 +162,12 @@ foreach(_tf ${_theme_files})
configure_file(${_tf} "${CMAKE_BINARY_DIR}/themes/${_name}" COPYONLY)
endforeach()
if(APPLE)
target_sources(Reclass PRIVATE ${_theme_files})
set_source_files_properties(${_theme_files}
PROPERTIES MACOSX_PACKAGE_LOCATION "Resources/themes")
endif()
# Copy example .rcx files to build directory
file(GLOB _example_files "${CMAKE_SOURCE_DIR}/src/examples/*.rcx")
file(MAKE_DIRECTORY "${CMAKE_BINARY_DIR}/examples")
@@ -158,6 +176,12 @@ foreach(_ef ${_example_files})
configure_file(${_ef} "${CMAKE_BINARY_DIR}/examples/${_name}" COPYONLY)
endforeach()
if(APPLE)
target_sources(Reclass PRIVATE ${_example_files})
set_source_files_properties(${_example_files}
PROPERTIES MACOSX_PACKAGE_LOCATION "Resources/examples")
endif()
include(deploy)
@@ -481,8 +505,10 @@ if(BUILD_TESTING)
endif() # BUILD_UI_TESTS
endif()
add_subdirectory(plugins/ProcessMemory)
add_subdirectory(plugins/RemoteProcessMemory)
if(NOT APPLE)
add_subdirectory(plugins/ProcessMemory)
add_subdirectory(plugins/RemoteProcessMemory)
endif()
if(WIN32)
add_subdirectory(plugins/WinDbgMemory)
add_subdirectory(plugins/RcNetPluginCompatLayer)

View File

@@ -49,7 +49,7 @@ Built with C++17, Qt 6 (Qt 5 also supported), and QScintilla. The entire editor
- [ ] Safe mode
- [ ] File import for other Reclass instances
- [ ] Expose UI functionality to plugins
- [ ] iOS/macOS support
- [ ] iOS support
- [ ] Display RTTI information
## Data Sources
@@ -92,7 +92,7 @@ Built-in [Model Context Protocol](https://modelcontextprotocol.io/) bridge via `
### Quick Build
```bash
```/dev/null/commands.sh#L1-4
git clone --recurse-submodules https://github.com/IChooseYou/Reclass.git
cd Reclass
.\scripts\build_qscintilla.ps1
@@ -101,6 +101,16 @@ cd Reclass
The build script auto-detects your Qt install location.
### macOS Build
```/dev/null/commands.sh#L1-2
./scripts/build_macos.sh --qt-dir /opt/homebrew/opt/qt --build-type Release --package
```
If you installed Qt via Homebrew, `--qt-dir /opt/homebrew/opt/qt` is typical on Apple Silicon. You can also set `QTDIR` or `Qt6_DIR` instead of passing `--qt-dir`.
Note: macOS Gatekeeper may block unsigned apps. If the app wont open, go to **System Settings → Privacy & Security** and click **Open Anyway**.
### Manual Build (MinGW)
1. Clone with `--recurse-submodules` (or run `git submodule update --init --recursive` after cloning)

168
scripts/build_macos.sh Executable file
View File

@@ -0,0 +1,168 @@
#!/usr/bin/env bash
set -euo pipefail
print_help() {
cat <<'EOF'
Reclass macOS Build Script
Usage:
./scripts/build_macos.sh [options]
Options:
--qt-dir <path> Qt installation prefix (e.g. /opt/homebrew/opt/qt)
--build-type <type> Release | Debug | RelWithDebInfo | MinSizeRel (default: Release)
--build-dir <path> Build directory (default: <repo>/build)
--generator <name> CMake generator (default: Ninja if available)
--clean Remove build directory before configuring
--rebuild Clean then build
--package Run macdeployqt and create a zip
--tests Run ctest after build
-h, --help Show this help
Notes:
- You can set QTDIR or Qt6_DIR in your environment instead of --qt-dir.
- If Qt is installed via Homebrew, the script will try to detect it.
EOF
}
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
project_root="$(cd "${script_dir}/.." && pwd)"
qt_dir=""
build_type="Release"
build_dir="${project_root}/build"
generator=""
do_clean="false"
do_package="false"
do_tests="false"
while [[ $# -gt 0 ]]; do
case "$1" in
--qt-dir)
qt_dir="${2:-}"
shift 2
;;
--build-type)
build_type="${2:-}"
shift 2
;;
--build-dir)
build_dir="${2:-}"
shift 2
;;
--generator)
generator="${2:-}"
shift 2
;;
--clean)
do_clean="true"
shift
;;
--rebuild)
do_clean="true"
shift
;;
--package)
do_package="true"
shift
;;
--tests)
do_tests="true"
shift
;;
-h|--help)
print_help
exit 0
;;
*)
echo "Unknown argument: $1" >&2
print_help
exit 1
;;
esac
done
if [[ -z "${qt_dir}" ]]; then
if [[ -n "${QTDIR:-}" ]]; then
qt_dir="${QTDIR}"
elif [[ -n "${Qt6_DIR:-}" ]]; then
qt_dir="${Qt6_DIR}"
elif command -v brew >/dev/null 2>&1; then
if brew --prefix qt >/dev/null 2>&1; then
qt_dir="$(brew --prefix qt)"
fi
fi
fi
if ! command -v cmake >/dev/null 2>&1; then
echo "ERROR: cmake not found. Install CMake and try again." >&2
exit 1
fi
if [[ -z "${generator}" ]]; then
if command -v ninja >/dev/null 2>&1; then
generator="Ninja"
fi
fi
if [[ "${do_clean}" == "true" && -d "${build_dir}" ]]; then
echo "Cleaning build directory: ${build_dir}"
rm -rf "${build_dir}"
fi
mkdir -p "${build_dir}"
cmake_args=(
-S "${project_root}"
-B "${build_dir}"
-DCMAKE_BUILD_TYPE="${build_type}"
)
if [[ -n "${generator}" ]]; then
cmake_args+=(-G "${generator}")
fi
if [[ -n "${qt_dir}" ]]; then
export PATH="${qt_dir}/bin:${PATH}"
cmake_args+=(-DCMAKE_PREFIX_PATH="${qt_dir}")
fi
echo "Configuring..."
cmake "${cmake_args[@]}"
echo "Building..."
cmake --build "${build_dir}" --config "${build_type}"
if [[ "${do_tests}" == "true" ]]; then
echo "Running tests..."
ctest --test-dir "${build_dir}" --output-on-failure -C "${build_type}"
fi
if [[ "${do_package}" == "true" ]]; then
app_path="${build_dir}/Reclass.app"
if [[ ! -d "${app_path}" ]]; then
echo "ERROR: ${app_path} not found. Build may have failed." >&2
exit 1
fi
macdeployqt_bin=""
if [[ -n "${qt_dir}" && -x "${qt_dir}/bin/macdeployqt" ]]; then
macdeployqt_bin="${qt_dir}/bin/macdeployqt"
elif command -v macdeployqt >/dev/null 2>&1; then
macdeployqt_bin="$(command -v macdeployqt)"
fi
if [[ -z "${macdeployqt_bin}" ]]; then
echo "ERROR: macdeployqt not found. Ensure Qt is installed and in PATH." >&2
exit 1
fi
echo "Running macdeployqt..."
"${macdeployqt_bin}" "${app_path}" -always-overwrite
arch="$(uname -m)"
zip_name="Reclass-macos-${arch}-qt6.zip"
echo "Creating zip: ${zip_name}"
ditto -c -k --sequesterRsrc --keepParent "${app_path}" "${build_dir}/${zip_name}"
echo "Packaged: ${build_dir}/${zip_name}"
fi

BIN
src/icons/class.icns Normal file

Binary file not shown.

13
src/macos_titlebar.h Normal file
View File

@@ -0,0 +1,13 @@
#pragma once
#include <QWidget>
namespace rcx {
struct Theme;
// Apply macOS native title bar color to match the theme.
// No-op on non-macOS platforms (implementation is platform-specific).
void applyMacTitleBarTheme(QWidget* window, const Theme& theme);
} // namespace rcx

43
src/macos_titlebar.mm Normal file
View File

@@ -0,0 +1,43 @@
#include "macos_titlebar.h"
#include "themes/theme.h"
#import <Cocoa/Cocoa.h>
#include <QColor>
#include <QWidget>
namespace rcx {
static NSColor* toNSColor(const QColor& color) {
return [NSColor colorWithCalibratedRed:color.redF()
green:color.greenF()
blue:color.blueF()
alpha:color.alphaF()];
}
void applyMacTitleBarTheme(QWidget* window, const Theme& theme) {
if (!window) return;
// Ensure native window is created.
window->winId();
auto* nsView = reinterpret_cast<NSView*>(window->winId());
if (!nsView) return;
NSWindow* nsWindow = [nsView window];
if (!nsWindow) return;
// Keep native traffic lights while tinting the title bar to the theme.
// Match the title text contrast by selecting the appropriate system appearance.
const qreal luminance =
0.2126 * theme.background.redF() +
0.7152 * theme.background.greenF() +
0.0722 * theme.background.blueF();
const bool isLight = luminance >= 0.5;
[nsWindow setAppearance:[NSAppearance appearanceNamed:
(isLight ? NSAppearanceNameAqua : NSAppearanceNameDarkAqua)]];
[nsWindow setTitlebarAppearsTransparent:YES];
[nsWindow setTitleVisibility:NSWindowTitleVisible];
[nsWindow setBackgroundColor:toNSColor(theme.background)];
}
} // namespace rcx

View File

@@ -53,7 +53,6 @@
#include "themes/thememanager.h"
#include "themes/themeeditor.h"
#include "optionsdialog.h"
#ifdef _WIN32
#include <windows.h>
#include <windowsx.h>
@@ -389,13 +388,18 @@ public:
namespace rcx {
#ifdef __APPLE__
void applyMacTitleBarTheme(QWidget* window, const Theme& theme);
#endif
// MainWindow class declaration is in mainwindow.h
MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent) {
setWindowTitle("Reclass");
resize(1200, 800);
// Frameless window with system menu (Alt+Space) and min/max/close support
#ifndef __APPLE__
// Frameless window with system menu (Alt+Space) and min/max/close support.
setWindowFlags(Qt::FramelessWindowHint | Qt::WindowSystemMenuHint
| Qt::WindowMinMaxButtonsHint);
@@ -403,6 +407,14 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent) {
m_titleBar = new TitleBarWidget(this);
m_titleBar->applyTheme(ThemeManager::instance().current());
setMenuWidget(m_titleBar);
m_menuBar = m_titleBar->menuBar();
#else
setWindowTitle(QStringLiteral("Reclass"));
setUnifiedTitleAndToolBarOnMac(true);
m_menuBar = menuBar();
m_menuBar->setNativeMenuBar(true);
applyMacTitleBarTheme(this, ThemeManager::instance().current());
#endif
#ifdef _WIN32
// 1px top margin preserves DWM drop shadow on the frameless window
@@ -454,8 +466,9 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent) {
// Restore menu bar title case setting (after menus are created)
{
QSettings s("Reclass", "Reclass");
m_titleBar->setMenuBarTitleCase(s.value("menuBarTitleCase", false).toBool());
if (s.value("showIcon", false).toBool())
m_menuBarTitleCase = s.value("menuBarTitleCase", false).toBool();
applyMenuBarTitleCase(m_menuBarTitleCase);
if (m_titleBar && s.value("showIcon", false).toBool())
m_titleBar->setShowIcon(true);
}
@@ -507,9 +520,42 @@ inline QAction* Qt5Qt6AddAction(QMenu* menu, const QString &text, const QKeySequ
return result;
}
void MainWindow::applyMenuBarTitleCase(bool titleCase) {
m_menuBarTitleCase = titleCase;
if (m_titleBar) {
m_titleBar->setMenuBarTitleCase(titleCase);
return;
}
if (!m_menuBar) return;
for (QAction* action : m_menuBar->actions()) {
QString text = action->text();
QString clean = text;
clean.remove('&');
if (titleCase) {
action->setText("&" + clean.toUpper());
} else {
QString result;
bool capitalizeNext = true;
for (int i = 0; i < clean.length(); ++i) {
QChar ch = clean[i];
if (ch.isLetter()) {
result += capitalizeNext ? ch.toUpper() : ch.toLower();
capitalizeNext = false;
} else {
result += ch;
if (ch.isSpace()) capitalizeNext = true;
}
}
action->setText("&" + result);
}
}
}
void MainWindow::createMenus() {
// File
auto* file = m_titleBar->menuBar()->addMenu("&File");
auto* file = m_menuBar->addMenu("&File");
Qt5Qt6AddAction(file, "New &Class", QKeySequence::New, QIcon(), this, &MainWindow::newClass);
Qt5Qt6AddAction(file, "New &Struct", QKeySequence(Qt::CTRL | Qt::Key_T), QIcon(), this, &MainWindow::newStruct);
Qt5Qt6AddAction(file, "New &Enum", QKeySequence(Qt::CTRL | Qt::Key_E), QIcon(), this, &MainWindow::newEnum);
@@ -529,7 +575,11 @@ void MainWindow::createMenus() {
Qt5Qt6AddAction(exportMenu, "ReClass &XML...", QKeySequence::UnknownKey, QIcon(), this, &MainWindow::exportReclassXmlAction);
// Examples submenu — scan once at init
{
#ifdef __APPLE__
QDir exDir(QDir::cleanPath(QCoreApplication::applicationDirPath() + "/../Resources/examples"));
#else
QDir exDir(QCoreApplication::applicationDirPath() + "/examples");
#endif
QStringList rcxFiles = exDir.entryList({"*.rcx"}, QDir::Files, QDir::Name);
if (!rcxFiles.isEmpty()) {
auto* examples = file->addMenu("E&xamples");
@@ -545,12 +595,12 @@ void MainWindow::createMenus() {
Qt5Qt6AddAction(file, "E&xit", QKeySequence(Qt::Key_Close), makeIcon(":/vsicons/close.svg"), this, &QMainWindow::close);
// Edit
auto* edit = m_titleBar->menuBar()->addMenu("&Edit");
auto* edit = m_menuBar->addMenu("&Edit");
Qt5Qt6AddAction(edit, "&Undo", QKeySequence::Undo, makeIcon(":/vsicons/arrow-left.svg"), this, &MainWindow::undo);
Qt5Qt6AddAction(edit, "&Redo", QKeySequence::Redo, makeIcon(":/vsicons/arrow-right.svg"), this, &MainWindow::redo);
// View
auto* view = m_titleBar->menuBar()->addMenu("&View");
auto* view = m_menuBar->addMenu("&View");
Qt5Qt6AddAction(view, "Split &Horizontal", QKeySequence::UnknownKey, makeIcon(":/vsicons/split-horizontal.svg"), this, &MainWindow::splitView);
m_removeSplitAction = Qt5Qt6AddAction(view, "&Remove Split", QKeySequence::UnknownKey, makeIcon(":/vsicons/chrome-close.svg"), this, &MainWindow::unsplitView);
m_removeSplitAction->setVisible(false);
@@ -626,7 +676,7 @@ void MainWindow::createMenus() {
}
// Tools
auto* tools = m_titleBar->menuBar()->addMenu("&Tools");
auto* tools = m_menuBar->addMenu("&Tools");
Qt5Qt6AddAction(tools, "&Type Aliases...", QKeySequence::UnknownKey, QIcon(), this, &MainWindow::showTypeAliasesDialog);
tools->addSeparator();
const auto mcpName = QSettings("Reclass", "Reclass").value("autoStartMcp", true).toBool() ? "Stop &MCP Server" : "Start &MCP Server";
@@ -635,11 +685,11 @@ void MainWindow::createMenus() {
Qt5Qt6AddAction(tools, "&Options...", QKeySequence::UnknownKey, makeIcon(":/vsicons/settings-gear.svg"), this, &MainWindow::showOptionsDialog);
// Plugins
auto* plugins = m_titleBar->menuBar()->addMenu("&Plugins");
auto* plugins = m_menuBar->addMenu("&Plugins");
Qt5Qt6AddAction(plugins, "&Manage Plugins...", QKeySequence::UnknownKey, QIcon(), this, &MainWindow::showPluginsDialog);
// Help
auto* help = m_titleBar->menuBar()->addMenu("&Help");
auto* help = m_menuBar->addMenu("&Help");
Qt5Qt6AddAction(help, "&About Reclass", QKeySequence::UnknownKey, makeIcon(":/vsicons/question.svg"), this, &MainWindow::about);
}
@@ -1671,9 +1721,14 @@ void MainWindow::toggleMcp() {
void MainWindow::applyTheme(const Theme& theme) {
applyGlobalTheme(theme);
#ifdef __APPLE__
applyMacTitleBarTheme(this, theme);
#endif
// Dock separator is 1px via PM_DockWidgetSeparatorExtent in MenuBarStyle
// Custom title bar
if (m_titleBar)
m_titleBar->applyTheme(theme);
// Update border overlay color
@@ -1831,8 +1886,10 @@ void MainWindow::showOptionsDialog() {
OptionsResult current;
current.themeIndex = tm.currentIndex();
current.fontName = QSettings("Reclass", "Reclass").value("font", "JetBrains Mono").toString();
current.menuBarTitleCase = m_titleBar->menuBarTitleCase();
current.showIcon = QSettings("Reclass", "Reclass").value("showIcon", false).toBool();
current.menuBarTitleCase = m_menuBarTitleCase;
current.showIcon = m_titleBar
? QSettings("Reclass", "Reclass").value("showIcon", false).toBool()
: false;
current.safeMode = QSettings("Reclass", "Reclass").value("safeMode", false).toBool();
current.autoStartMcp = QSettings("Reclass", "Reclass").value("autoStartMcp", true).toBool();
current.refreshMs = QSettings("Reclass", "Reclass").value("refreshMs", 660).toInt();
@@ -1850,11 +1907,12 @@ void MainWindow::showOptionsDialog() {
setEditorFont(r.fontName);
if (r.menuBarTitleCase != current.menuBarTitleCase) {
m_titleBar->setMenuBarTitleCase(r.menuBarTitleCase);
applyMenuBarTitleCase(r.menuBarTitleCase);
QSettings("Reclass", "Reclass").setValue("menuBarTitleCase", r.menuBarTitleCase);
}
if (r.showIcon != current.showIcon) {
if (m_titleBar)
m_titleBar->setShowIcon(r.showIcon);
QSettings("Reclass", "Reclass").setValue("showIcon", r.showIcon);
}
@@ -1932,6 +1990,9 @@ MainWindow::TabState* MainWindow::tabByIndex(int index) {
}
void MainWindow::updateWindowTitle() {
#ifdef __APPLE__
setWindowTitle(QStringLiteral("Reclass"));
#else
QString title;
auto* sub = m_mdiArea->activeSubWindow();
if (sub && m_tabs.contains(sub)) {
@@ -1945,6 +2006,7 @@ void MainWindow::updateWindowTitle() {
title = "Reclass";
}
setWindowTitle(title);
#endif
}
// ── Rendered view setup ──
@@ -3110,7 +3172,7 @@ void MainWindow::changeEvent(QEvent* event) {
const auto& t = ThemeManager::instance().current();
updateBorderColor(isActiveWindow() ? t.borderFocused : t.border);
}
if (event->type() == QEvent::WindowStateChange)
if (event->type() == QEvent::WindowStateChange && m_titleBar)
m_titleBar->updateMaximizeIcon();
}

View File

@@ -88,6 +88,8 @@ private:
QPushButton* m_btnReclass = nullptr;
QPushButton* m_btnRendered = nullptr;
TitleBarWidget* m_titleBar = nullptr;
QMenuBar* m_menuBar = nullptr;
bool m_menuBarTitleCase = false;
QWidget* m_borderOverlay = nullptr;
PluginManager m_pluginManager;
McpBridge* m_mcp = nullptr;
@@ -118,6 +120,7 @@ private:
void rebuildAllDocs();
void createMenus();
void applyMenuBarTitleCase(bool titleCase);
void createStatusBar();
void showPluginsDialog();
void populateSourceMenu();

View File

@@ -33,7 +33,12 @@ ThemeManager::ThemeManager() {
// ── Load built-in themes from JSON files next to the executable ──
QString ThemeManager::builtInDir() const {
#ifdef Q_OS_MACOS
// In a macOS .app bundle, resources live in Contents/Resources, not Contents/MacOS
return QCoreApplication::applicationDirPath() + "/../Resources/themes";
#else
return QCoreApplication::applicationDirPath() + "/themes";
#endif
}
void ThemeManager::loadBuiltInThemes() {

View File

@@ -74,7 +74,7 @@ void TitleBarWidget::applyTheme(const Theme& theme) {
// App label
m_appLabel->setStyleSheet(
QStringLiteral("QLabel { color: %1; font-size: 12px; font-weight: bold; }")
.arg(theme.textDim.name()));
.arg(theme.text.name()));
// Menu bar palette — hover/bg handled by MenuBarStyle QProxyStyle.
// Set Window + Button to background so Fusion never paints a foreign color.
@@ -82,7 +82,7 @@ void TitleBarWidget::applyTheme(const Theme& theme) {
QPalette mbPal = m_menuBar->palette();
mbPal.setColor(QPalette::Window, theme.background);
mbPal.setColor(QPalette::Button, theme.background);
mbPal.setColor(QPalette::ButtonText, theme.textDim);
mbPal.setColor(QPalette::ButtonText, theme.text);
m_menuBar->setPalette(mbPal);
m_menuBar->setAutoFillBackground(false);
}
@@ -112,7 +112,7 @@ void TitleBarWidget::setShowIcon(bool show) {
m_appLabel->setText(QStringLiteral("Reclass"));
m_appLabel->setStyleSheet(
QStringLiteral("QLabel { color: %1; font-size: 12px; font-weight: bold; }")
.arg(m_theme.textDim.name()));
.arg(m_theme.text.name()));
}
}