Add NebulaController, tab manager, and CEF clients

Introduce core application structure and browser management: add NebulaController and run entry (src/app/*) to centralize window, tab and CEF lifecycle logic; implement TabManager and NebulaTab (src/browser/*) for tab creation, navigation and state tracking; add URL utilities (NormalizeNavigationInput, JsonEscape) and CEF browser client glue (src/cef/browser_client.cpp/.h) to forward chrome commands and content events. Update app/main.cpp to delegate startup to nebula::app::RunNebula. Add UI assets (chrome.html, chrome.css, chrome.js, lucide, menu-popup updates) and remove obsolete nebot.html. Update CMakeLists to include new sources, add ${CMAKE_SOURCE_DIR}/src to includes and link dwmapi on Windows. Overall this refactors startup and splits responsibilities for cleaner tab and browser lifecycle handling.
This commit is contained in:
Andrew Zambazos
2026-05-14 10:18:51 +12:00
parent 207a849f06
commit a8786b4c1c
23 changed files with 2835 additions and 330 deletions
+121
View File
@@ -0,0 +1,121 @@
#include "ui/paths.h"
#include <windows.h>
#include <algorithm>
#include <cctype>
namespace nebula::ui {
namespace {
std::string WideToUtf8(const std::wstring& value) {
if (value.empty()) {
return {};
}
const int size = WideCharToMultiByte(
CP_UTF8, 0, value.data(), static_cast<int>(value.size()),
nullptr, 0, nullptr, nullptr);
std::string result(size, '\0');
WideCharToMultiByte(
CP_UTF8, 0, value.data(), static_cast<int>(value.size()),
result.data(), size, nullptr, nullptr);
return result;
}
std::string GetUrlWithoutDecoration(std::string url) {
const size_t split = url.find_first_of("?#");
if (split != std::string::npos) {
url.resize(split);
}
return url;
}
std::string ToLowerAscii(std::string value) {
std::transform(value.begin(), value.end(), value.begin(), [](unsigned char ch) {
return static_cast<char>(std::tolower(ch));
});
return value;
}
} // namespace
std::filesystem::path GetExecutableDirectory() {
wchar_t exe_path[MAX_PATH] = {};
const DWORD length = GetModuleFileNameW(nullptr, exe_path, MAX_PATH);
if (length == 0 || length == MAX_PATH) {
return {};
}
return std::filesystem::path(exe_path).parent_path();
}
std::filesystem::path GetUiPagePath(const std::wstring& page_name) {
const auto exe_dir = GetExecutableDirectory();
if (exe_dir.empty()) {
return {};
}
return exe_dir / L"ui" / L"pages" / page_name;
}
std::string FilePathToUrl(std::filesystem::path path) {
std::string value = WideToUtf8(path.wstring());
for (char& ch : value) {
if (ch == '\\') {
ch = '/';
}
}
std::string encoded;
encoded.reserve(value.size());
for (char ch : value) {
encoded += ch == ' ' ? "%20" : std::string(1, ch);
}
return "file:///" + encoded;
}
std::string GetChromeUrl() {
const auto path = GetUiPagePath(L"chrome.html");
return path.empty() ? GetHomeUrl() : FilePathToUrl(path);
}
std::string GetHomeUrl() {
const auto path = GetUiPagePath(L"home.html");
return path.empty() ? "https://www.google.com" : FilePathToUrl(path);
}
std::string GetSettingsUrl() {
const auto path = GetUiPagePath(L"settings.html");
return path.empty() ? GetHomeUrl() : FilePathToUrl(path);
}
std::string GetBigPictureUrl() {
const auto path = GetUiPagePath(L"bigpicture.html");
return path.empty() ? GetHomeUrl() : FilePathToUrl(path);
}
std::string GetMenuPopupUrl() {
const auto path = GetUiPagePath(L"menu-popup.html");
return path.empty() ? GetHomeUrl() : FilePathToUrl(path);
}
bool IsInternalHomeUrl(const std::string& url) {
return GetUrlWithoutDecoration(url) == GetHomeUrl();
}
bool IsChromiumNewTabUrl(const std::string& url) {
const std::string target = ToLowerAscii(GetUrlWithoutDecoration(url));
return target == "about:blank" ||
target == "chrome://newtab" ||
target == "chrome://newtab/" ||
target == "chrome://new-tab-page" ||
target == "chrome://new-tab-page/" ||
target == "chrome-search://local-ntp/local-ntp.html";
}
bool IsEmptyOrChromiumNewTabUrl(const std::string& url) {
return url.empty() || IsChromiumNewTabUrl(url);
}
} // namespace nebula::ui