Add macOS Cocoa port and CEF helper support
Introduce a macOS Cocoa-based UI and CEF helper subprocess support. CMake: enable OBJCXX on Apple, treat mac sources as .mm, set -fobjc-arc, link Cocoa frameworks, and generate helper app targets using a mac Info.plist template; keep libcef logical target off macOS. Implementation: add Objective-C++ implementations for browser_host and nebula_window, convert startup to ObjC++ (prepare NSApplication), add process_helper_mac (CEF helper entry) and load CEF library from main/main_bigpicture on mac. Tooling/docs: add .clangd fallback flags, compile_commands symlink helper, update cross-platform docs for mac status. Misc: add menu-popup UI page, define UNREFERENCED_PARAMETER in platform/types.h, small code cleanups (use (void)layout, include platform/types.h) and update .gitignore.
This commit is contained in:
@@ -0,0 +1,17 @@
|
|||||||
|
# clangd configuration for Nebula Browser
|
||||||
|
#
|
||||||
|
# Full diagnostics require:
|
||||||
|
# 1. CEF binary distribution unpacked to thirdparty/cef/
|
||||||
|
# 2. Running: cmake -B build -DCMAKE_EXPORT_COMPILE_COMMANDS=ON
|
||||||
|
# 3. Symlink or copy: ln -s build/compile_commands.json .
|
||||||
|
#
|
||||||
|
# Without compile_commands.json, clangd uses fallback flags below.
|
||||||
|
|
||||||
|
CompileFlags:
|
||||||
|
Add:
|
||||||
|
- -std=c++20
|
||||||
|
- -xobjective-c++
|
||||||
|
- -Isrc
|
||||||
|
- -Ithirdparty/cef
|
||||||
|
- -Ithirdparty/cef/include
|
||||||
|
- -Wno-c++17-extensions
|
||||||
@@ -18,3 +18,4 @@ CMakeFiles/
|
|||||||
|
|
||||||
# Logs
|
# Logs
|
||||||
*.log
|
*.log
|
||||||
|
/.cache
|
||||||
|
|||||||
+84
-7
@@ -1,6 +1,11 @@
|
|||||||
cmake_minimum_required(VERSION 3.21)
|
cmake_minimum_required(VERSION 3.21)
|
||||||
|
|
||||||
project(NebulaBrowser LANGUAGES CXX)
|
# Enable OBJCXX early on macOS for Cocoa integration
|
||||||
|
if(APPLE)
|
||||||
|
project(NebulaBrowser LANGUAGES CXX OBJCXX)
|
||||||
|
else()
|
||||||
|
project(NebulaBrowser LANGUAGES CXX)
|
||||||
|
endif()
|
||||||
|
|
||||||
set(CMAKE_CXX_STANDARD 20)
|
set(CMAKE_CXX_STANDARD 20)
|
||||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||||
@@ -56,9 +61,16 @@ if(OS_WINDOWS)
|
|||||||
elseif(OS_MACOSX)
|
elseif(OS_MACOSX)
|
||||||
set(NEBULA_PLATFORM_SOURCES
|
set(NEBULA_PLATFORM_SOURCES
|
||||||
src/platform/mac/paths_mac.cpp
|
src/platform/mac/paths_mac.cpp
|
||||||
src/platform/mac/startup_mac.cpp
|
src/platform/mac/startup_mac.mm
|
||||||
src/platform/mac/browser_host_mac.cpp
|
src/platform/mac/browser_host_mac.mm
|
||||||
src/platform/mac/nebula_window_mac.cpp
|
src/platform/mac/nebula_window_mac.mm
|
||||||
|
)
|
||||||
|
set_source_files_properties(
|
||||||
|
src/platform/mac/startup_mac.mm
|
||||||
|
src/platform/mac/browser_host_mac.mm
|
||||||
|
src/platform/mac/nebula_window_mac.mm
|
||||||
|
PROPERTIES
|
||||||
|
COMPILE_FLAGS "-fobjc-arc"
|
||||||
)
|
)
|
||||||
elseif(OS_LINUX)
|
elseif(OS_LINUX)
|
||||||
set(NEBULA_PLATFORM_SOURCES
|
set(NEBULA_PLATFORM_SOURCES
|
||||||
@@ -71,7 +83,11 @@ else()
|
|||||||
message(FATAL_ERROR "Unsupported platform.")
|
message(FATAL_ERROR "Unsupported platform.")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
ADD_LOGICAL_TARGET("libcef_lib" "${CEF_LIB_RELEASE}" "${CEF_LIB_DEBUG}")
|
# On macOS, CEF is a framework linked via CEF_STANDARD_LIBS.
|
||||||
|
# On Windows/Linux, we create a logical target for libcef.
|
||||||
|
if(NOT OS_MACOSX)
|
||||||
|
ADD_LOGICAL_TARGET("libcef_lib" "${CEF_LIB_RELEASE}" "${CEF_LIB_DEBUG}")
|
||||||
|
endif()
|
||||||
|
|
||||||
if(OS_LINUX)
|
if(OS_LINUX)
|
||||||
FIND_LINUX_LIBRARIES("X11")
|
FIND_LINUX_LIBRARIES("X11")
|
||||||
@@ -107,11 +123,19 @@ function(add_nebula_app_target nebula_target entry_source)
|
|||||||
"${CEF_ROOT}/include"
|
"${CEF_ROOT}/include"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if(OS_MACOSX)
|
||||||
|
# On macOS, CEF is a framework; don't link libcef_lib
|
||||||
|
target_link_libraries(${nebula_target} PRIVATE
|
||||||
|
libcef_dll_wrapper
|
||||||
|
${CEF_STANDARD_LIBS}
|
||||||
|
)
|
||||||
|
else()
|
||||||
target_link_libraries(${nebula_target} PRIVATE
|
target_link_libraries(${nebula_target} PRIVATE
|
||||||
libcef_lib
|
libcef_lib
|
||||||
libcef_dll_wrapper
|
libcef_dll_wrapper
|
||||||
${CEF_STANDARD_LIBS}
|
${CEF_STANDARD_LIBS}
|
||||||
)
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
if(MSVC)
|
if(MSVC)
|
||||||
set_property(TARGET ${nebula_target} PROPERTY
|
set_property(TARGET ${nebula_target} PROPERTY
|
||||||
@@ -135,12 +159,19 @@ function(add_nebula_app_target nebula_target entry_source)
|
|||||||
COPY_FILES("${nebula_target}" "${CEF_BINARY_FILES}" "${CEF_BINARY_DIR_RELEASE}" "${CEF_TARGET_OUT_DIR}")
|
COPY_FILES("${nebula_target}" "${CEF_BINARY_FILES}" "${CEF_BINARY_DIR_RELEASE}" "${CEF_TARGET_OUT_DIR}")
|
||||||
COPY_FILES("${nebula_target}" "${CEF_RESOURCE_FILES}" "${CEF_RESOURCE_DIR}" "${CEF_TARGET_OUT_DIR}")
|
COPY_FILES("${nebula_target}" "${CEF_RESOURCE_FILES}" "${CEF_RESOURCE_DIR}" "${CEF_TARGET_OUT_DIR}")
|
||||||
elseif(OS_MACOSX)
|
elseif(OS_MACOSX)
|
||||||
|
target_link_libraries(${nebula_target} PRIVATE
|
||||||
|
"-framework Cocoa"
|
||||||
|
"-framework ApplicationServices"
|
||||||
|
)
|
||||||
set(NEBULA_APP "${CEF_TARGET_OUT_DIR}/${nebula_target}.app")
|
set(NEBULA_APP "${CEF_TARGET_OUT_DIR}/${nebula_target}.app")
|
||||||
|
set(NEBULA_HELPER_TARGET "${nebula_target}_Helper")
|
||||||
|
set(NEBULA_HELPER_OUTPUT_NAME "${nebula_target} Helper")
|
||||||
|
string(TOLOWER "${nebula_target}" NEBULA_HELPER_BUNDLE_NAME)
|
||||||
|
|
||||||
COPY_MAC_FRAMEWORK(
|
COPY_MAC_FRAMEWORK(
|
||||||
"${nebula_target}"
|
"${nebula_target}"
|
||||||
"Chromium Embedded Framework"
|
|
||||||
"${CEF_BINARY_DIR_RELEASE}"
|
"${CEF_BINARY_DIR_RELEASE}"
|
||||||
"${NEBULA_APP}/Contents/Frameworks"
|
"${NEBULA_APP}"
|
||||||
)
|
)
|
||||||
COPY_FILES(
|
COPY_FILES(
|
||||||
"${nebula_target}"
|
"${nebula_target}"
|
||||||
@@ -154,6 +185,52 @@ function(add_nebula_app_target nebula_target entry_source)
|
|||||||
"${CEF_RESOURCE_DIR}"
|
"${CEF_RESOURCE_DIR}"
|
||||||
"${NEBULA_APP}/Contents/Resources"
|
"${NEBULA_APP}/Contents/Resources"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
foreach(_suffix_list ${CEF_HELPER_APP_SUFFIXES})
|
||||||
|
string(REPLACE ":" ";" _suffix_list ${_suffix_list})
|
||||||
|
list(GET _suffix_list 0 _name_suffix)
|
||||||
|
list(GET _suffix_list 1 _target_suffix)
|
||||||
|
list(GET _suffix_list 2 _plist_suffix)
|
||||||
|
|
||||||
|
set(_helper_target "${NEBULA_HELPER_TARGET}${_target_suffix}")
|
||||||
|
set(_helper_output_name "${NEBULA_HELPER_OUTPUT_NAME}${_name_suffix}")
|
||||||
|
set(_helper_info_plist "${CMAKE_CURRENT_BINARY_DIR}/${_helper_target}-Info.plist")
|
||||||
|
|
||||||
|
file(READ "${CMAKE_SOURCE_DIR}/cmake/mac-helper-Info.plist.in" _plist_contents)
|
||||||
|
string(REPLACE "\${EXECUTABLE_NAME}" "${_helper_output_name}" _plist_contents "${_plist_contents}")
|
||||||
|
string(REPLACE "\${PRODUCT_NAME}" "${_helper_output_name}" _plist_contents "${_plist_contents}")
|
||||||
|
string(REPLACE "\${HELPER_BUNDLE_NAME}" "${NEBULA_HELPER_BUNDLE_NAME}" _plist_contents "${_plist_contents}")
|
||||||
|
string(REPLACE "\${BUNDLE_ID_SUFFIX}" "${_plist_suffix}" _plist_contents "${_plist_contents}")
|
||||||
|
file(WRITE "${_helper_info_plist}" "${_plist_contents}")
|
||||||
|
|
||||||
|
add_executable(${_helper_target} MACOSX_BUNDLE
|
||||||
|
app/process_helper_mac.cc
|
||||||
|
src/cef/nebula_app.cpp
|
||||||
|
)
|
||||||
|
SET_EXECUTABLE_TARGET_PROPERTIES(${_helper_target})
|
||||||
|
add_dependencies(${_helper_target} libcef_dll_wrapper)
|
||||||
|
target_include_directories(${_helper_target} PRIVATE
|
||||||
|
"${CMAKE_SOURCE_DIR}/src"
|
||||||
|
"${CEF_ROOT}"
|
||||||
|
"${CEF_ROOT}/include"
|
||||||
|
)
|
||||||
|
target_link_libraries(${_helper_target} PRIVATE
|
||||||
|
libcef_dll_wrapper
|
||||||
|
${CEF_STANDARD_LIBS}
|
||||||
|
)
|
||||||
|
set_target_properties(${_helper_target} PROPERTIES
|
||||||
|
MACOSX_BUNDLE_INFO_PLIST "${_helper_info_plist}"
|
||||||
|
OUTPUT_NAME "${_helper_output_name}"
|
||||||
|
)
|
||||||
|
|
||||||
|
add_dependencies(${nebula_target} "${_helper_target}")
|
||||||
|
add_custom_command(TARGET ${nebula_target} POST_BUILD
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E copy_directory
|
||||||
|
"${CEF_TARGET_OUT_DIR}/${_helper_output_name}.app"
|
||||||
|
"${NEBULA_APP}/Contents/Frameworks/${_helper_output_name}.app"
|
||||||
|
VERBATIM
|
||||||
|
)
|
||||||
|
endforeach()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# ------------------------------------------------------------
|
# ------------------------------------------------------------
|
||||||
|
|||||||
@@ -3,7 +3,11 @@
|
|||||||
|
|
||||||
#if defined(_WIN32)
|
#if defined(_WIN32)
|
||||||
#include <windows.h>
|
#include <windows.h>
|
||||||
|
#elif defined(__APPLE__)
|
||||||
|
#include "include/wrapper/cef_library_loader.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(_WIN32)
|
||||||
int APIENTRY wWinMain(HINSTANCE instance,
|
int APIENTRY wWinMain(HINSTANCE instance,
|
||||||
HINSTANCE previous_instance,
|
HINSTANCE previous_instance,
|
||||||
LPWSTR command_line,
|
LPWSTR command_line,
|
||||||
@@ -16,6 +20,13 @@ int APIENTRY wWinMain(HINSTANCE instance,
|
|||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
int main(int argc, char* argv[]) {
|
int main(int argc, char* argv[]) {
|
||||||
|
#if defined(__APPLE__)
|
||||||
|
CefScopedLibraryLoader library_loader;
|
||||||
|
if (!library_loader.LoadInMain()) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
const nebula::platform::AppStartup startup{argc, argv};
|
const nebula::platform::AppStartup startup{argc, argv};
|
||||||
return nebula::app::RunNebula(startup, {nebula::app::AppMode::Desktop});
|
return nebula::app::RunNebula(startup, {nebula::app::AppMode::Desktop});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,11 @@
|
|||||||
|
|
||||||
#if defined(_WIN32)
|
#if defined(_WIN32)
|
||||||
#include <windows.h>
|
#include <windows.h>
|
||||||
|
#elif defined(__APPLE__)
|
||||||
|
#include "include/wrapper/cef_library_loader.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(_WIN32)
|
||||||
int APIENTRY wWinMain(HINSTANCE instance,
|
int APIENTRY wWinMain(HINSTANCE instance,
|
||||||
HINSTANCE previous_instance,
|
HINSTANCE previous_instance,
|
||||||
LPWSTR command_line,
|
LPWSTR command_line,
|
||||||
@@ -16,6 +20,13 @@ int APIENTRY wWinMain(HINSTANCE instance,
|
|||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
int main(int argc, char* argv[]) {
|
int main(int argc, char* argv[]) {
|
||||||
|
#if defined(__APPLE__)
|
||||||
|
CefScopedLibraryLoader library_loader;
|
||||||
|
if (!library_loader.LoadInMain()) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
const nebula::platform::AppStartup startup{argc, argv};
|
const nebula::platform::AppStartup startup{argc, argv};
|
||||||
return nebula::app::RunNebula(startup, {nebula::app::AppMode::BigPicture});
|
return nebula::app::RunNebula(startup, {nebula::app::AppMode::BigPicture});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
#include "cef/nebula_app.h"
|
||||||
|
#include "include/cef_app.h"
|
||||||
|
#include "include/wrapper/cef_library_loader.h"
|
||||||
|
|
||||||
|
#if defined(CEF_USE_SANDBOX)
|
||||||
|
#include "include/cef_sandbox_mac.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
int main(int argc, char* argv[]) {
|
||||||
|
#if defined(CEF_USE_SANDBOX)
|
||||||
|
CefScopedSandboxContext sandbox_context;
|
||||||
|
if (!sandbox_context.Initialize(argc, argv)) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
CefScopedLibraryLoader library_loader;
|
||||||
|
if (!library_loader.LoadInHelper()) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
const CefMainArgs main_args(argc, argv);
|
||||||
|
CefRefPtr<nebula::cef::NebulaApp> app(new nebula::cef::NebulaApp);
|
||||||
|
return CefExecuteProcess(main_args, app, nullptr);
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>CFBundleDevelopmentRegion</key>
|
||||||
|
<string>en</string>
|
||||||
|
<key>CFBundleDisplayName</key>
|
||||||
|
<string>${EXECUTABLE_NAME}</string>
|
||||||
|
<key>CFBundleExecutable</key>
|
||||||
|
<string>${EXECUTABLE_NAME}</string>
|
||||||
|
<key>CFBundleIdentifier</key>
|
||||||
|
<string>com.nebula.browser.${HELPER_BUNDLE_NAME}.helper${BUNDLE_ID_SUFFIX}</string>
|
||||||
|
<key>CFBundleInfoDictionaryVersion</key>
|
||||||
|
<string>6.0</string>
|
||||||
|
<key>CFBundleName</key>
|
||||||
|
<string>${PRODUCT_NAME}</string>
|
||||||
|
<key>CFBundlePackageType</key>
|
||||||
|
<string>APPL</string>
|
||||||
|
<key>CFBundleSignature</key>
|
||||||
|
<string>????</string>
|
||||||
|
<key>CFBundleVersion</key>
|
||||||
|
<string>1.0</string>
|
||||||
|
<key>CFBundleShortVersionString</key>
|
||||||
|
<string>1.0</string>
|
||||||
|
<key>LSEnvironment</key>
|
||||||
|
<dict>
|
||||||
|
<key>MallocNanoZone</key>
|
||||||
|
<string>0</string>
|
||||||
|
</dict>
|
||||||
|
<key>LSFileQuarantineEnabled</key>
|
||||||
|
<true/>
|
||||||
|
<key>LSMinimumSystemVersion</key>
|
||||||
|
<string>12.0</string>
|
||||||
|
<key>LSUIElement</key>
|
||||||
|
<string>1</string>
|
||||||
|
<key>NSSupportsAutomaticGraphicsSwitching</key>
|
||||||
|
<true/>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
Symlink
+1
@@ -0,0 +1 @@
|
|||||||
|
build/compile_commands.json
|
||||||
+28
-11
@@ -15,7 +15,9 @@ Nebula Browser uses a **single codebase** and **one git branch** for Windows, ma
|
|||||||
|
|
||||||
**Windows** has a full native shell (custom frame, DWM, embedded CEF views).
|
**Windows** has a full native shell (custom frame, DWM, embedded CEF views).
|
||||||
|
|
||||||
**macOS and Linux** compile and link today, but `NebulaWindow::Create()` is still a stub that returns `false` until a real Cocoa / X11 (or GTK) host is implemented.
|
**macOS** has a Cocoa native shell (`NSWindow` / `NSView`) with CEF child-view embedding.
|
||||||
|
|
||||||
|
**Linux** compiles and links today, but `NebulaWindow::Create()` is still a stub that returns `false` until a real X11 (or GTK) host is implemented.
|
||||||
|
|
||||||
## Directory layout
|
## Directory layout
|
||||||
|
|
||||||
@@ -29,7 +31,7 @@ src/platform/
|
|||||||
paths_platform.h # ExecutableDirectory, DefaultUserDataRoot, PathToUtf8
|
paths_platform.h # ExecutableDirectory, DefaultUserDataRoot, PathToUtf8
|
||||||
|
|
||||||
win/ # Windows implementation
|
win/ # Windows implementation
|
||||||
mac/ # macOS stubs + partial CEF glue
|
mac/ # macOS Cocoa implementation
|
||||||
linux/ # Linux stubs + partial CEF glue
|
linux/ # Linux stubs + partial CEF glue
|
||||||
|
|
||||||
src/window/
|
src/window/
|
||||||
@@ -64,6 +66,22 @@ ui/ # HTML/JS UI (copied next to the binary at build time)
|
|||||||
|
|
||||||
`thirdparty/cef/` is listed in `.gitignore` so binaries are never committed. Each developer and CI machine supplies its own copy.
|
`thirdparty/cef/` is listed in `.gitignore` so binaries are never committed. Each developer and CI machine supplies its own copy.
|
||||||
|
|
||||||
|
## IDE / clangd setup
|
||||||
|
|
||||||
|
Without CEF headers, clangd will show many errors (unknown types like `CefWindowInfo`, `CefMainArgs`, etc.). To get full IDE support:
|
||||||
|
|
||||||
|
1. Unpack CEF into `thirdparty/cef/` as described above
|
||||||
|
2. Generate `compile_commands.json`:
|
||||||
|
```bash
|
||||||
|
cmake -B build -DCMAKE_EXPORT_COMPILE_COMMANDS=ON
|
||||||
|
```
|
||||||
|
3. Symlink it to the project root:
|
||||||
|
```bash
|
||||||
|
ln -s build/compile_commands.json .
|
||||||
|
```
|
||||||
|
|
||||||
|
The included `.clangd` file provides fallback flags, but full diagnostics require the compile database.
|
||||||
|
|
||||||
Optional: override the location at configure time:
|
Optional: override the location at configure time:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@@ -88,12 +106,9 @@ cmake -B build -DPROJECT_ARCH=x86_64 # or arm64 on Apple Silicon
|
|||||||
cmake --build build --config Release
|
cmake --build build --config Release
|
||||||
```
|
```
|
||||||
|
|
||||||
CMake builds a `NebulaBrowser.app` bundle and copies the Chromium Embedded Framework into `Contents/Frameworks`. A full macOS port still requires:
|
CMake builds a `NebulaBrowser.app` bundle and copies the Chromium Embedded Framework into `Contents/Frameworks`. The macOS port includes a Cocoa `NSWindow` / `NSView` host and child-view embedding for CEF browsers.
|
||||||
|
|
||||||
- A real `NebulaWindow` implementation (Cocoa `NSWindow` / `NSView`)
|
CEF **helper app** subprocess targets may still need to be added if the selected CEF distribution does not provide a compatible default subprocess layout. Use CEF’s `cefsimple` sample as the reference for the complete Mac bundle layout.
|
||||||
- CEF **helper app** subprocess targets (see CEF’s `cefsimple` sample for the complete Mac bundle layout)
|
|
||||||
|
|
||||||
Until that work lands, the macOS target may build but will exit early because `Create()` fails.
|
|
||||||
|
|
||||||
### Linux
|
### Linux
|
||||||
|
|
||||||
@@ -176,8 +191,10 @@ Platform-specific discovery lives in `src/platform/*/paths_*.cpp`.
|
|||||||
|
|
||||||
## Porting checklist (macOS / Linux)
|
## Porting checklist (macOS / Linux)
|
||||||
|
|
||||||
- [ ] Implement `NebulaWindow` in `nebula_window_mac.cpp` / `nebula_window_linux.cpp`
|
- [x] macOS: implement `NebulaWindow` in `nebula_window_mac.mm`
|
||||||
- [ ] Wire `browser_host_*.cpp` resize/show/raise to the real toolkit
|
- [x] macOS: wire `browser_host_mac.mm` resize/show/raise to Cocoa views
|
||||||
|
- [ ] Linux: implement `NebulaWindow` in `nebula_window_linux.cpp`
|
||||||
|
- [ ] Linux: wire `browser_host_linux.cpp` resize/show/raise to the real toolkit
|
||||||
- [ ] macOS: add CEF helper app targets and bundle layout (copy from `cefsimple`)
|
- [ ] macOS: add CEF helper app targets and bundle layout (copy from `cefsimple`)
|
||||||
- [ ] Linux: confirm X11 (or chosen toolkit) parent handle for `CefWindowInfo::SetAsChild`
|
- [ ] Linux: confirm X11 (or chosen toolkit) parent handle for `CefWindowInfo::SetAsChild`
|
||||||
- [ ] Test GPU diagnostics page and hardware acceleration on target hardware
|
- [ ] Test GPU diagnostics page and hardware acceleration on target hardware
|
||||||
@@ -191,8 +208,8 @@ No. Use one branch; swap `thirdparty/cef` and rebuild on each machine.
|
|||||||
**Can I commit CEF into the repo?**
|
**Can I commit CEF into the repo?**
|
||||||
Not recommended (size, licensing, per-arch binaries). Keeping `thirdparty/cef/` gitignored is intentional.
|
Not recommended (size, licensing, per-arch binaries). Keeping `thirdparty/cef/` gitignored is intentional.
|
||||||
|
|
||||||
**Why does macOS/Linux build but not run?**
|
**Why does Linux build but not run?**
|
||||||
The window stub intentionally returns `false` from `Create()` until a native shell exists. Shared CEF and browser code are ready; the missing piece is the host window.
|
The Linux window stub intentionally returns `false` from `Create()` until a native shell exists. Shared CEF and browser code are ready; the missing piece is the host window.
|
||||||
|
|
||||||
**Where is the Windows UI code?**
|
**Where is the Windows UI code?**
|
||||||
`src/platform/win/nebula_window_win.cpp` — custom chrome, hit-testing, fullscreen, and child browser placement.
|
`src/platform/win/nebula_window_win.cpp` — custom chrome, hit-testing, fullscreen, and child browser placement.
|
||||||
|
|||||||
@@ -211,7 +211,7 @@ void NebulaController::OnWindowCreated() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void NebulaController::OnWindowResized(const nebula::window::BrowserLayout& layout) {
|
void NebulaController::OnWindowResized(const nebula::window::BrowserLayout& layout) {
|
||||||
UNREFERENCED_PARAMETER(layout);
|
(void)layout;
|
||||||
ResizeBrowsers();
|
ResizeBrowsers();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#include "cef/browser_client.h"
|
#include "cef/browser_client.h"
|
||||||
|
|
||||||
#include "include/cef_request.h"
|
#include "include/cef_request.h"
|
||||||
|
#include "platform/types.h"
|
||||||
#include "include/wrapper/cef_helpers.h"
|
#include "include/wrapper/cef_helpers.h"
|
||||||
#include "ui/paths.h"
|
#include "ui/paths.h"
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#include "cef/nebula_app.h"
|
#include "cef/nebula_app.h"
|
||||||
|
|
||||||
#include "include/cef_process_message.h"
|
#include "include/cef_process_message.h"
|
||||||
|
#include "platform/types.h"
|
||||||
#include "include/wrapper/cef_helpers.h"
|
#include "include/wrapper/cef_helpers.h"
|
||||||
|
|
||||||
namespace nebula::cef {
|
namespace nebula::cef {
|
||||||
|
|||||||
@@ -1,75 +0,0 @@
|
|||||||
#include "platform/browser_host.h"
|
|
||||||
|
|
||||||
#include <algorithm>
|
|
||||||
|
|
||||||
namespace nebula::platform {
|
|
||||||
|
|
||||||
CefWindowInfo MakeChildWindowInfo(NativeWindow parent, const Rect& rect) {
|
|
||||||
CefWindowInfo info;
|
|
||||||
info.SetAsChild(parent, CefRect(rect.x, rect.y, rect.width, rect.height));
|
|
||||||
info.runtime_style = CEF_RUNTIME_STYLE_ALLOY;
|
|
||||||
return info;
|
|
||||||
}
|
|
||||||
|
|
||||||
CefWindowInfo MakeDevToolsPopup(NativeWindow parent, const char* title) {
|
|
||||||
CefWindowInfo info;
|
|
||||||
info.SetAsPopup(parent, title);
|
|
||||||
info.runtime_style = CEF_RUNTIME_STYLE_ALLOY;
|
|
||||||
return info;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ResizeBrowserWindow(NativeWindow browser_window, const Rect& rect) {
|
|
||||||
UNREFERENCED_PARAMETER(browser_window);
|
|
||||||
UNREFERENCED_PARAMETER(rect);
|
|
||||||
}
|
|
||||||
|
|
||||||
void SetBrowserVisible(NativeWindow browser_window, bool visible) {
|
|
||||||
UNREFERENCED_PARAMETER(browser_window);
|
|
||||||
UNREFERENCED_PARAMETER(visible);
|
|
||||||
}
|
|
||||||
|
|
||||||
void RaiseBrowserWindow(NativeWindow browser_window) {
|
|
||||||
UNREFERENCED_PARAMETER(browser_window);
|
|
||||||
}
|
|
||||||
|
|
||||||
void MoveCursorToBrowserPoint(NativeWindow browser_window, int x, int y) {
|
|
||||||
UNREFERENCED_PARAMETER(browser_window);
|
|
||||||
UNREFERENCED_PARAMETER(x);
|
|
||||||
UNREFERENCED_PARAMETER(y);
|
|
||||||
}
|
|
||||||
|
|
||||||
int ScaleForParentWindow(NativeWindow parent, int value) {
|
|
||||||
UNREFERENCED_PARAMETER(parent);
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::pair<int, int> ParentClientSize(NativeWindow parent) {
|
|
||||||
UNREFERENCED_PARAMETER(parent);
|
|
||||||
return {1280, 720};
|
|
||||||
}
|
|
||||||
|
|
||||||
Rect MenuPopupRect(NativeWindow parent, const BrowserLayout& layout) {
|
|
||||||
const auto client_size = ParentClientSize(parent);
|
|
||||||
const int width = 260;
|
|
||||||
const int height = 258;
|
|
||||||
const int margin = 12;
|
|
||||||
const int overlap = 2;
|
|
||||||
const int x = std::max(0, client_size.first - width - margin);
|
|
||||||
const int y = std::max(0, layout.chrome.y + layout.chrome.height - overlap);
|
|
||||||
return {
|
|
||||||
x,
|
|
||||||
y,
|
|
||||||
std::min(client_size.first, x + width) - x,
|
|
||||||
std::min(client_size.second, y + height) - y,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string CacheBusterToken() {
|
|
||||||
return "0";
|
|
||||||
}
|
|
||||||
|
|
||||||
void DestroyTopLevelWindow(NativeWindow window) {
|
|
||||||
UNREFERENCED_PARAMETER(window);
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace nebula::platform
|
|
||||||
@@ -0,0 +1,134 @@
|
|||||||
|
#include "platform/browser_host.h"
|
||||||
|
|
||||||
|
#import <ApplicationServices/ApplicationServices.h>
|
||||||
|
#import <Cocoa/Cocoa.h>
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <chrono>
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
|
namespace nebula::platform {
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
NSView* AsView(NativeWindow window) {
|
||||||
|
return (__bridge NSView*)window;
|
||||||
|
}
|
||||||
|
|
||||||
|
NSRect ToNativeRect(const Rect& rect) {
|
||||||
|
return NSMakeRect(rect.x, rect.y, std::max(0, rect.width), std::max(0, rect.height));
|
||||||
|
}
|
||||||
|
|
||||||
|
Rect ToPlatformRect(NSRect rect) {
|
||||||
|
return {
|
||||||
|
static_cast<int>(std::round(NSMinX(rect))),
|
||||||
|
static_cast<int>(std::round(NSMinY(rect))),
|
||||||
|
std::max(0, static_cast<int>(std::round(NSWidth(rect)))),
|
||||||
|
std::max(0, static_cast<int>(std::round(NSHeight(rect)))),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
CefWindowInfo MakeChildWindowInfo(NativeWindow parent, const Rect& rect) {
|
||||||
|
CefWindowInfo info;
|
||||||
|
info.SetAsChild((__bridge CefWindowHandle)AsView(parent),
|
||||||
|
CefRect(rect.x, rect.y, rect.width, rect.height));
|
||||||
|
info.runtime_style = CEF_RUNTIME_STYLE_ALLOY;
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
|
||||||
|
CefWindowInfo MakeDevToolsPopup(NativeWindow parent, const char* title) {
|
||||||
|
(void)title;
|
||||||
|
CefWindowInfo info;
|
||||||
|
info.SetAsChild((__bridge CefWindowHandle)AsView(parent), CefRect(0, 0, 800, 600));
|
||||||
|
info.runtime_style = CEF_RUNTIME_STYLE_ALLOY;
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ResizeBrowserWindow(NativeWindow browser_window, const Rect& rect) {
|
||||||
|
NSView* view = AsView(browser_window);
|
||||||
|
if (!view) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
[view setFrame:ToNativeRect(rect)];
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetBrowserVisible(NativeWindow browser_window, bool visible) {
|
||||||
|
NSView* view = AsView(browser_window);
|
||||||
|
if (!view) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
[view setHidden:!visible];
|
||||||
|
if (visible && [view superview]) {
|
||||||
|
[[view superview] addSubview:view positioned:NSWindowAbove relativeTo:nil];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void RaiseBrowserWindow(NativeWindow browser_window) {
|
||||||
|
NSView* view = AsView(browser_window);
|
||||||
|
if (view && [view superview]) {
|
||||||
|
[[view superview] addSubview:view positioned:NSWindowAbove relativeTo:nil];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MoveCursorToBrowserPoint(NativeWindow browser_window, int x, int y) {
|
||||||
|
NSView* view = AsView(browser_window);
|
||||||
|
if (!view || ![view window]) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const NSPoint window_point = [view convertPoint:NSMakePoint(x, y) toView:nil];
|
||||||
|
const NSPoint screen_point = [[view window] convertPointToScreen:window_point];
|
||||||
|
const CGFloat screen_height = NSMaxY([[NSScreen mainScreen] frame]);
|
||||||
|
CGWarpMouseCursorPosition(CGPointMake(screen_point.x, screen_height - screen_point.y));
|
||||||
|
}
|
||||||
|
|
||||||
|
int ScaleForParentWindow(NativeWindow parent, int value) {
|
||||||
|
(void)parent;
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::pair<int, int> ParentClientSize(NativeWindow parent) {
|
||||||
|
NSView* view = AsView(parent);
|
||||||
|
if (!view) {
|
||||||
|
return {0, 0};
|
||||||
|
}
|
||||||
|
|
||||||
|
const Rect rect = ToPlatformRect([view bounds]);
|
||||||
|
return {rect.width, rect.height};
|
||||||
|
}
|
||||||
|
|
||||||
|
Rect MenuPopupRect(NativeWindow parent, const BrowserLayout& layout) {
|
||||||
|
const auto client_size = ParentClientSize(parent);
|
||||||
|
const int width = 260;
|
||||||
|
const int height = 258;
|
||||||
|
const int margin = 12;
|
||||||
|
const int overlap = 2;
|
||||||
|
const int x = std::max(0, client_size.first - width - margin);
|
||||||
|
const int y = std::max(0, layout.chrome.y + layout.chrome.height - overlap);
|
||||||
|
return {
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
std::min(client_size.first, x + width) - x,
|
||||||
|
std::min(client_size.second, y + height) - y,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string CacheBusterToken() {
|
||||||
|
const auto now = std::chrono::system_clock::now().time_since_epoch();
|
||||||
|
const auto millis = std::chrono::duration_cast<std::chrono::milliseconds>(now).count();
|
||||||
|
return std::to_string(millis);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DestroyTopLevelWindow(NativeWindow window) {
|
||||||
|
NSView* view = AsView(window);
|
||||||
|
NSWindow* native_window = [view window];
|
||||||
|
if (native_window) {
|
||||||
|
[native_window setDelegate:nil];
|
||||||
|
[native_window close];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace nebula::platform
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
#include "window/nebula_window.h"
|
|
||||||
|
|
||||||
#include <memory>
|
|
||||||
|
|
||||||
namespace nebula::window {
|
|
||||||
|
|
||||||
struct nebula::window::NebulaWindowImpl {
|
|
||||||
WindowDelegate* delegate = nullptr;
|
|
||||||
};
|
|
||||||
|
|
||||||
NebulaWindow::NebulaWindow(WindowDelegate* delegate)
|
|
||||||
: impl_(std::make_unique<NebulaWindowImpl>()) {
|
|
||||||
impl_->delegate = delegate;
|
|
||||||
}
|
|
||||||
|
|
||||||
NebulaWindow::~NebulaWindow() = default;
|
|
||||||
|
|
||||||
bool NebulaWindow::Create(const platform::AppStartup& startup) {
|
|
||||||
UNREFERENCED_PARAMETER(startup);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
platform::NativeWindow NebulaWindow::native_handle() const {
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
BrowserLayout NebulaWindow::CurrentLayout(bool show_chrome) const {
|
|
||||||
UNREFERENCED_PARAMETER(show_chrome);
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
void NebulaWindow::ResizeChild(platform::NativeWindow child, const platform::Rect& rect) const {
|
|
||||||
UNREFERENCED_PARAMETER(child);
|
|
||||||
UNREFERENCED_PARAMETER(rect);
|
|
||||||
}
|
|
||||||
|
|
||||||
void NebulaWindow::Minimize() {}
|
|
||||||
void NebulaWindow::ToggleMaximize() {}
|
|
||||||
void NebulaWindow::SetFullscreen(bool fullscreen) { UNREFERENCED_PARAMETER(fullscreen); }
|
|
||||||
void NebulaWindow::Close() {}
|
|
||||||
void NebulaWindow::BeginDrag() {}
|
|
||||||
void NebulaWindow::SetTitle(const std::string& title) { UNREFERENCED_PARAMETER(title); }
|
|
||||||
void NebulaWindow::EnableFrameHitTest(platform::NativeWindow child) const { UNREFERENCED_PARAMETER(child); }
|
|
||||||
|
|
||||||
} // namespace nebula::window
|
|
||||||
@@ -0,0 +1,222 @@
|
|||||||
|
#include "window/nebula_window.h"
|
||||||
|
|
||||||
|
#import <Cocoa/Cocoa.h>
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cmath>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
namespace nebula::window {
|
||||||
|
struct NebulaWindowImpl;
|
||||||
|
} // namespace nebula::window
|
||||||
|
|
||||||
|
@interface NebulaContentView : NSView
|
||||||
|
@end
|
||||||
|
|
||||||
|
@implementation NebulaContentView
|
||||||
|
- (BOOL)isFlipped {
|
||||||
|
return YES;
|
||||||
|
}
|
||||||
|
@end
|
||||||
|
|
||||||
|
@interface NebulaWindowDelegate : NSObject <NSWindowDelegate> {
|
||||||
|
@private
|
||||||
|
nebula::window::NebulaWindowImpl* owner_;
|
||||||
|
}
|
||||||
|
- (instancetype)initWithOwner:(nebula::window::NebulaWindowImpl*)owner;
|
||||||
|
@end
|
||||||
|
|
||||||
|
namespace nebula::window {
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
constexpr CGFloat kChromeHeight = 104.0;
|
||||||
|
|
||||||
|
NSRect ToNativeRect(const platform::Rect& rect) {
|
||||||
|
return NSMakeRect(rect.x, rect.y, std::max(0, rect.width), std::max(0, rect.height));
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
struct NebulaWindowImpl {
|
||||||
|
WindowDelegate* delegate = nullptr;
|
||||||
|
NSWindow* window = nil;
|
||||||
|
NebulaContentView* content_view = nil;
|
||||||
|
NebulaWindowDelegate* window_delegate = nil;
|
||||||
|
bool fullscreen = false;
|
||||||
|
|
||||||
|
BrowserLayout CurrentLayout(bool show_chrome) const {
|
||||||
|
const NSRect bounds = content_view ? [content_view bounds] : NSZeroRect;
|
||||||
|
const int width = std::max(0, static_cast<int>(std::round(NSWidth(bounds))));
|
||||||
|
const int height = std::max(0, static_cast<int>(std::round(NSHeight(bounds))));
|
||||||
|
const int chrome_height = show_chrome ? std::min(height, static_cast<int>(kChromeHeight)) : 0;
|
||||||
|
|
||||||
|
BrowserLayout layout;
|
||||||
|
layout.chrome = show_chrome ? platform::Rect{0, 0, width, chrome_height} : platform::Rect{};
|
||||||
|
layout.content = {0, chrome_height, width, std::max(0, height - chrome_height)};
|
||||||
|
return layout;
|
||||||
|
}
|
||||||
|
|
||||||
|
void NotifyCreated() const {
|
||||||
|
if (delegate) {
|
||||||
|
delegate->OnWindowCreated();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void NotifyResized() const {
|
||||||
|
if (delegate) {
|
||||||
|
delegate->OnWindowResized(CurrentLayout(true));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void NotifyCloseRequested() const {
|
||||||
|
if (delegate) {
|
||||||
|
delegate->OnWindowCloseRequested();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace nebula::window
|
||||||
|
|
||||||
|
@implementation NebulaWindowDelegate
|
||||||
|
- (instancetype)initWithOwner:(nebula::window::NebulaWindowImpl*)owner {
|
||||||
|
self = [super init];
|
||||||
|
if (self) {
|
||||||
|
owner_ = owner;
|
||||||
|
}
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)windowDidResize:(NSNotification*)notification {
|
||||||
|
(void)notification;
|
||||||
|
if (owner_) {
|
||||||
|
owner_->NotifyResized();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (BOOL)windowShouldClose:(id)sender {
|
||||||
|
(void)sender;
|
||||||
|
if (owner_) {
|
||||||
|
owner_->NotifyCloseRequested();
|
||||||
|
}
|
||||||
|
return NO;
|
||||||
|
}
|
||||||
|
@end
|
||||||
|
|
||||||
|
namespace nebula::window {
|
||||||
|
|
||||||
|
NebulaWindow::NebulaWindow(WindowDelegate* delegate)
|
||||||
|
: impl_(std::make_unique<NebulaWindowImpl>()) {
|
||||||
|
impl_->delegate = delegate;
|
||||||
|
}
|
||||||
|
|
||||||
|
NebulaWindow::~NebulaWindow() = default;
|
||||||
|
|
||||||
|
bool NebulaWindow::Create(const platform::AppStartup& startup) {
|
||||||
|
(void)startup;
|
||||||
|
|
||||||
|
@autoreleasepool {
|
||||||
|
[NSApplication sharedApplication];
|
||||||
|
|
||||||
|
const NSRect visible_frame = [[NSScreen mainScreen] visibleFrame];
|
||||||
|
const CGFloat width = std::min<CGFloat>(1400.0, NSWidth(visible_frame));
|
||||||
|
const CGFloat height = std::min<CGFloat>(900.0, NSHeight(visible_frame));
|
||||||
|
const CGFloat x = NSMinX(visible_frame) + (NSWidth(visible_frame) - width) / 2.0;
|
||||||
|
const CGFloat y = NSMinY(visible_frame) + (NSHeight(visible_frame) - height) / 2.0;
|
||||||
|
|
||||||
|
impl_->content_view = [[NebulaContentView alloc] initWithFrame:NSMakeRect(0, 0, width, height)];
|
||||||
|
impl_->window_delegate = [[NebulaWindowDelegate alloc] initWithOwner:impl_.get()];
|
||||||
|
impl_->window = [[NSWindow alloc] initWithContentRect:NSMakeRect(x, y, width, height)
|
||||||
|
styleMask:NSWindowStyleMaskTitled |
|
||||||
|
NSWindowStyleMaskClosable |
|
||||||
|
NSWindowStyleMaskMiniaturizable |
|
||||||
|
NSWindowStyleMaskResizable |
|
||||||
|
NSWindowStyleMaskFullSizeContentView
|
||||||
|
backing:NSBackingStoreBuffered
|
||||||
|
defer:NO];
|
||||||
|
if (!impl_->window) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
[impl_->window setTitle:@"Nebula Browser"];
|
||||||
|
[impl_->window setTitleVisibility:NSWindowTitleHidden];
|
||||||
|
[impl_->window setTitlebarAppearsTransparent:YES];
|
||||||
|
[impl_->window setContentView:impl_->content_view];
|
||||||
|
[impl_->window setDelegate:impl_->window_delegate];
|
||||||
|
[impl_->window center];
|
||||||
|
[impl_->window makeKeyAndOrderFront:nil];
|
||||||
|
[NSApp activateIgnoringOtherApps:YES];
|
||||||
|
|
||||||
|
impl_->NotifyCreated();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
platform::NativeWindow NebulaWindow::native_handle() const {
|
||||||
|
return (__bridge void*)impl_->content_view;
|
||||||
|
}
|
||||||
|
|
||||||
|
BrowserLayout NebulaWindow::CurrentLayout(bool show_chrome) const {
|
||||||
|
return impl_->CurrentLayout(show_chrome);
|
||||||
|
}
|
||||||
|
|
||||||
|
void NebulaWindow::ResizeChild(platform::NativeWindow child, const platform::Rect& rect) const {
|
||||||
|
NSView* view = (__bridge NSView*)child;
|
||||||
|
if (!view) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
[view setFrame:ToNativeRect(rect)];
|
||||||
|
}
|
||||||
|
|
||||||
|
void NebulaWindow::Minimize() {
|
||||||
|
[impl_->window miniaturize:nil];
|
||||||
|
}
|
||||||
|
|
||||||
|
void NebulaWindow::ToggleMaximize() {
|
||||||
|
if (impl_->window && !impl_->fullscreen) {
|
||||||
|
[impl_->window zoom:nil];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void NebulaWindow::SetFullscreen(bool fullscreen) {
|
||||||
|
if (!impl_->window || impl_->fullscreen == fullscreen) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl_->fullscreen = fullscreen;
|
||||||
|
[impl_->window toggleFullScreen:nil];
|
||||||
|
}
|
||||||
|
|
||||||
|
void NebulaWindow::Close() {
|
||||||
|
if (impl_->delegate) {
|
||||||
|
impl_->delegate->OnWindowCloseRequested();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void NebulaWindow::BeginDrag() {
|
||||||
|
if (!impl_->window) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
NSEvent* event = [NSApp currentEvent];
|
||||||
|
if (event) {
|
||||||
|
[impl_->window performWindowDragWithEvent:event];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void NebulaWindow::SetTitle(const std::string& title) {
|
||||||
|
if (!impl_->window) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
NSString* native_title = title.empty()
|
||||||
|
? @"Nebula Browser"
|
||||||
|
: [[NSString alloc] initWithUTF8String:title.c_str()];
|
||||||
|
[impl_->window setTitle:native_title ?: @"Nebula Browser"];
|
||||||
|
}
|
||||||
|
|
||||||
|
void NebulaWindow::EnableFrameHitTest(platform::NativeWindow child) const {
|
||||||
|
(void)child;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace nebula::window
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
#include "platform/startup.h"
|
#include "platform/startup.h"
|
||||||
|
|
||||||
|
#import <Cocoa/Cocoa.h>
|
||||||
|
|
||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
#include <system_error>
|
#include <system_error>
|
||||||
@@ -16,7 +18,13 @@ int g_single_instance_lock = -1;
|
|||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
void PrepareApp() {}
|
void PrepareApp() {
|
||||||
|
@autoreleasepool {
|
||||||
|
[NSApplication sharedApplication];
|
||||||
|
[NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
|
||||||
|
[NSApp finishLaunching];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bool TryAcquireSingleInstance() {
|
bool TryAcquireSingleInstance() {
|
||||||
const auto lock_path = nebula::ui::GetUserDataDirectory() / ".nebula_instance.lock";
|
const auto lock_path = nebula::ui::GetUserDataDirectory() / ".nebula_instance.lock";
|
||||||
@@ -1,5 +1,9 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#ifndef UNREFERENCED_PARAMETER
|
||||||
|
#define UNREFERENCED_PARAMETER(P) (void)(P)
|
||||||
|
#endif
|
||||||
|
|
||||||
namespace nebula::platform {
|
namespace nebula::platform {
|
||||||
|
|
||||||
struct Rect {
|
struct Rect {
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Nebula Menu</title>
|
||||||
|
<link rel="stylesheet" href="../css/menu-popup.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<main id="menu-popup" role="menu" aria-label="Nebula browser menu">
|
||||||
|
<button type="button" role="menuitem" data-cmd="open-settings">Settings</button>
|
||||||
|
<button type="button" role="menuitem" data-cmd="gpu-diagnostics">GPU diagnostics</button>
|
||||||
|
<button type="button" role="menuitem" data-cmd="big-picture">Big Picture mode</button>
|
||||||
|
<button type="button" role="menuitem" data-cmd="toggle-devtools">Developer tools</button>
|
||||||
|
|
||||||
|
<div class="zoom-controls" aria-label="Page zoom">
|
||||||
|
<button type="button" data-cmd="zoom-out" aria-label="Zoom out">-</button>
|
||||||
|
<span id="zoom-percent">100%</span>
|
||||||
|
<button type="button" data-cmd="zoom-in" aria-label="Zoom in">+</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button type="button" role="menuitem" data-cmd="hard-reload">Hard reload</button>
|
||||||
|
<button type="button" role="menuitem" data-cmd="fresh-reload">Fresh reload</button>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<script src="../js/menu-popup.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Reference in New Issue
Block a user