Menu popup: visibility, zoom sync, and tab fixes
Track menu popup visibility and propagate zoom level to the popup. Add menu_popup_visible_ flag, SendMenuPopupZoom(), and call it when creating/showing the popup and when adjusting zoom. Make CreateNewTab accept an optional URL and route popup/new-tab flows to it. Prevent per-tab child closes from triggering app shutdown by tracking closing_tab_browsers_ and adding ForgetClosingTabBrowser(). Treat MenuPopup role specially when enabling frame hit-testing. Platform: remove usage of ApplyRoundedBrowserRegion and switch menu popup sizing to use resized client_size helpers (consolidate calculations across Win/Mac/Linux). UI: update menu-popup CSS/JS (new styling, font, zoom formatting API via NebulaMenuPopup.setZoomLevel), wire settings to use nebulaNative.postMessage for new-tab, and remove the static menu-popup.html. Misc: small chrome CSS/JS tweaks and ensure chrome state includes zoomLevel.
This commit is contained in:
@@ -155,6 +155,12 @@ void NebulaController::OnWindowResized(const nebula::window::BrowserLayout& layo
|
|||||||
}
|
}
|
||||||
|
|
||||||
void NebulaController::OnWindowCloseRequested() {
|
void NebulaController::OnWindowCloseRequested() {
|
||||||
|
if (!closing_ && !closing_tab_browsers_.empty()) {
|
||||||
|
// CEF Alloy can bubble a child browser close as WM_CLOSE on the host
|
||||||
|
// window. Per-tab closes should not turn into full app shutdown.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (closing_) {
|
if (closing_) {
|
||||||
// CEF re-sends WM_CLOSE to the top-level window after each Alloy
|
// CEF re-sends WM_CLOSE to the top-level window after each Alloy
|
||||||
// child browser finishes its JS unload + DoClose phase. Destroy the
|
// child browser finishes its JS unload + DoClose phase. Destroy the
|
||||||
@@ -193,7 +199,7 @@ void NebulaController::OnActiveTabChanged(const nebula::browser::NebulaTab& tab)
|
|||||||
}
|
}
|
||||||
|
|
||||||
void NebulaController::OnBrowserCreated(nebula::cef::BrowserRole role, CefRefPtr<CefBrowser> browser) {
|
void NebulaController::OnBrowserCreated(nebula::cef::BrowserRole role, CefRefPtr<CefBrowser> browser) {
|
||||||
if (window_ && browser) {
|
if (window_ && browser && role != nebula::cef::BrowserRole::MenuPopup) {
|
||||||
window_->EnableFrameHitTest(browser->GetHost()->GetWindowHandle());
|
window_->EnableFrameHitTest(browser->GetHost()->GetWindowHandle());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -205,7 +211,9 @@ void NebulaController::OnBrowserCreated(nebula::cef::BrowserRole role, CefRefPtr
|
|||||||
}
|
}
|
||||||
} else if (role == nebula::cef::BrowserRole::MenuPopup) {
|
} else if (role == nebula::cef::BrowserRole::MenuPopup) {
|
||||||
menu_popup_browser_ = browser;
|
menu_popup_browser_ = browser;
|
||||||
|
menu_popup_visible_ = true;
|
||||||
PositionMenuPopup();
|
PositionMenuPopup();
|
||||||
|
SendMenuPopupZoom();
|
||||||
} else {
|
} else {
|
||||||
tabs_.SetActiveBrowser(browser);
|
tabs_.SetActiveBrowser(browser);
|
||||||
}
|
}
|
||||||
@@ -220,7 +228,9 @@ void NebulaController::OnBrowserClosing(nebula::cef::BrowserRole role, CefRefPtr
|
|||||||
} else if (role == nebula::cef::BrowserRole::MenuPopup) {
|
} else if (role == nebula::cef::BrowserRole::MenuPopup) {
|
||||||
menu_popup_browser_ = nullptr;
|
menu_popup_browser_ = nullptr;
|
||||||
menu_popup_client_ = nullptr;
|
menu_popup_client_ = nullptr;
|
||||||
|
menu_popup_visible_ = false;
|
||||||
} else {
|
} else {
|
||||||
|
ForgetClosingTabBrowser(browser);
|
||||||
if (content_fullscreen_) {
|
if (content_fullscreen_) {
|
||||||
const auto* active_tab = tabs_.ActiveTab();
|
const auto* active_tab = tabs_.ActiveTab();
|
||||||
if (active_tab && active_tab->browser && active_tab->browser->IsSame(browser)) {
|
if (active_tab && active_tab->browser && active_tab->browser->IsSame(browser)) {
|
||||||
@@ -242,7 +252,7 @@ void NebulaController::OnChromeCommand(const std::string& command, const std::st
|
|||||||
tabs_.LoadURL(target);
|
tabs_.LoadURL(target);
|
||||||
}
|
}
|
||||||
} else if (command == "new-tab") {
|
} else if (command == "new-tab") {
|
||||||
CreateNewTab();
|
CreateNewTab(payload);
|
||||||
} else if (command == "activate-tab") {
|
} else if (command == "activate-tab") {
|
||||||
ActivateTab(ParseTabId(payload));
|
ActivateTab(ParseTabId(payload));
|
||||||
} else if (command == "close-tab") {
|
} else if (command == "close-tab") {
|
||||||
@@ -354,7 +364,7 @@ void NebulaController::OnPopupRequested(CefRefPtr<CefBrowser> browser, const std
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
tabs_.LoadURL(nebula::ui::IsEmptyOrChromiumNewTabUrl(target_url)
|
CreateNewTab(nebula::ui::IsEmptyOrChromiumNewTabUrl(target_url)
|
||||||
? nebula::ui::GetHomeUrl()
|
? nebula::ui::GetHomeUrl()
|
||||||
: target_url);
|
: target_url);
|
||||||
}
|
}
|
||||||
@@ -373,12 +383,14 @@ bool NebulaController::ShouldBypassInsecureWarning(CefRefPtr<CefBrowser> browser
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void NebulaController::CreateNewTab() {
|
void NebulaController::CreateNewTab(std::string url) {
|
||||||
if (auto* tab = tabs_.ActiveTab()) {
|
if (auto* tab = tabs_.ActiveTab()) {
|
||||||
SetBrowserVisible(tab->browser, false);
|
SetBrowserVisible(tab->browser, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
tabs_.CreateTab(nebula::ui::GetHomeUrl());
|
const std::string target =
|
||||||
|
url.empty() ? nebula::ui::GetHomeUrl() : nebula::browser::NormalizeNavigationInput(url);
|
||||||
|
tabs_.CreateTab(target.empty() ? nebula::ui::GetHomeUrl() : target);
|
||||||
PersistSession();
|
PersistSession();
|
||||||
CreateContentBrowser();
|
CreateContentBrowser();
|
||||||
}
|
}
|
||||||
@@ -415,6 +427,7 @@ void NebulaController::CloseTab(int tab_id) {
|
|||||||
CefRefPtr<CefBrowser> closing_browser = tabs_.CloseTab(tab_id);
|
CefRefPtr<CefBrowser> closing_browser = tabs_.CloseTab(tab_id);
|
||||||
PersistSession();
|
PersistSession();
|
||||||
if (closing_browser) {
|
if (closing_browser) {
|
||||||
|
closing_tab_browsers_.push_back(closing_browser);
|
||||||
closing_browser->GetHost()->CloseBrowser(false);
|
closing_browser->GetHost()->CloseBrowser(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -464,22 +477,31 @@ void NebulaController::CreateContentBrowser() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void NebulaController::ToggleMenuPopup() {
|
void NebulaController::ToggleMenuPopup() {
|
||||||
if (menu_popup_browser_) {
|
if (menu_popup_browser_ && menu_popup_visible_) {
|
||||||
CloseMenuPopup();
|
CloseMenuPopup();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (menu_popup_browser_) {
|
||||||
|
menu_popup_visible_ = true;
|
||||||
|
PositionMenuPopup();
|
||||||
|
SetBrowserVisible(menu_popup_browser_, true);
|
||||||
|
SendMenuPopupZoom();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
CreateMenuPopupBrowser();
|
CreateMenuPopupBrowser();
|
||||||
}
|
}
|
||||||
|
|
||||||
void NebulaController::CloseMenuPopup() {
|
void NebulaController::CloseMenuPopup() {
|
||||||
if (menu_popup_browser_) {
|
if (menu_popup_browser_) {
|
||||||
menu_popup_browser_->GetHost()->CloseBrowser(false);
|
menu_popup_visible_ = false;
|
||||||
|
SetBrowserVisible(menu_popup_browser_, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void NebulaController::CreateMenuPopupBrowser() {
|
void NebulaController::CreateMenuPopupBrowser() {
|
||||||
if (!window_ || !window_->native_handle()) {
|
if (!window_ || !window_->native_handle() || content_fullscreen_) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -494,20 +516,34 @@ void NebulaController::CreateMenuPopupBrowser() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void NebulaController::PositionMenuPopup() {
|
void NebulaController::PositionMenuPopup() {
|
||||||
if (content_fullscreen_ || !window_ || !window_->native_handle() || !menu_popup_browser_) {
|
if (content_fullscreen_ || !window_ || !window_->native_handle() || !menu_popup_browser_ ||
|
||||||
|
!menu_popup_visible_) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto rect =
|
const auto rect =
|
||||||
nebula::platform::MenuPopupRect(window_->native_handle(), window_->CurrentLayout());
|
nebula::platform::MenuPopupRect(window_->native_handle(), window_->CurrentLayout());
|
||||||
const auto browser_window = menu_popup_browser_->GetHost()->GetWindowHandle();
|
const auto browser_window = menu_popup_browser_->GetHost()->GetWindowHandle();
|
||||||
window_->ResizeChild(browser_window, rect);
|
nebula::platform::ResizeBrowserWindow(browser_window, rect);
|
||||||
nebula::platform::ApplyRoundedBrowserRegion(
|
|
||||||
browser_window,
|
|
||||||
nebula::platform::ScaleForParentWindow(window_->native_handle(), 28));
|
|
||||||
nebula::platform::RaiseBrowserWindow(browser_window);
|
nebula::platform::RaiseBrowserWindow(browser_window);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void NebulaController::SendMenuPopupZoom() {
|
||||||
|
if (!menu_popup_browser_) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
double zoom_level = 0.0;
|
||||||
|
if (const auto* tab = tabs_.ActiveTab(); tab && tab->browser) {
|
||||||
|
zoom_level = tab->browser->GetHost()->GetZoomLevel();
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string script =
|
||||||
|
"window.NebulaMenuPopup && window.NebulaMenuPopup.setZoomLevel(" +
|
||||||
|
std::to_string(zoom_level) + ");";
|
||||||
|
menu_popup_browser_->GetMainFrame()->ExecuteJavaScript(script, nebula::ui::GetMenuPopupUrl(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
void NebulaController::ToggleDevTools() {
|
void NebulaController::ToggleDevTools() {
|
||||||
auto* tab = tabs_.ActiveTab();
|
auto* tab = tabs_.ActiveTab();
|
||||||
if (!tab || !tab->browser || !window_ || !window_->native_handle()) {
|
if (!tab || !tab->browser || !window_ || !window_->native_handle()) {
|
||||||
@@ -534,6 +570,10 @@ void NebulaController::AdjustZoom(double delta) {
|
|||||||
|
|
||||||
CefRefPtr<CefBrowserHost> host = tab->browser->GetHost();
|
CefRefPtr<CefBrowserHost> host = tab->browser->GetHost();
|
||||||
host->SetZoomLevel(host->GetZoomLevel() + delta);
|
host->SetZoomLevel(host->GetZoomLevel() + delta);
|
||||||
|
SendMenuPopupZoom();
|
||||||
|
if (chrome_ready_) {
|
||||||
|
SendChromeState(*tab);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void NebulaController::FreshReload() {
|
void NebulaController::FreshReload() {
|
||||||
@@ -589,6 +629,10 @@ void NebulaController::SendChromeState(const nebula::browser::NebulaTab& tab) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const std::string display_url = GetChromeDisplayUrl(tab.url);
|
const std::string display_url = GetChromeDisplayUrl(tab.url);
|
||||||
|
double zoom_level = 0.0;
|
||||||
|
if (tab.browser) {
|
||||||
|
zoom_level = tab.browser->GetHost()->GetZoomLevel();
|
||||||
|
}
|
||||||
std::string tabs_json = "[";
|
std::string tabs_json = "[";
|
||||||
const auto& tabs = tabs_.Tabs();
|
const auto& tabs = tabs_.Tabs();
|
||||||
for (size_t i = 0; i < tabs.size(); ++i) {
|
for (size_t i = 0; i < tabs.size(); ++i) {
|
||||||
@@ -615,6 +659,7 @@ void NebulaController::SendChromeState(const nebula::browser::NebulaTab& tab) {
|
|||||||
",\"canGoBack\":" + std::string(tab.CanGoBack() ? "true" : "false") +
|
",\"canGoBack\":" + std::string(tab.CanGoBack() ? "true" : "false") +
|
||||||
",\"canGoForward\":" + std::string(tab.CanGoForward() ? "true" : "false") +
|
",\"canGoForward\":" + std::string(tab.CanGoForward() ? "true" : "false") +
|
||||||
",\"favicon\":\"" + nebula::browser::JsonEscape(tab.favicon_url) + "\"" +
|
",\"favicon\":\"" + nebula::browser::JsonEscape(tab.favicon_url) + "\"" +
|
||||||
|
",\"zoomLevel\":" + std::to_string(zoom_level) +
|
||||||
",\"tabs\":" + tabs_json +
|
",\"tabs\":" + tabs_json +
|
||||||
"});";
|
"});";
|
||||||
|
|
||||||
@@ -667,4 +712,23 @@ void NebulaController::MaybeFinishShutdown() {
|
|||||||
CefQuitMessageLoop();
|
CefQuitMessageLoop();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool NebulaController::ForgetClosingTabBrowser(CefRefPtr<CefBrowser> browser) {
|
||||||
|
if (!browser) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto it = std::find_if(
|
||||||
|
closing_tab_browsers_.begin(),
|
||||||
|
closing_tab_browsers_.end(),
|
||||||
|
[browser](const CefRefPtr<CefBrowser>& closing_browser) {
|
||||||
|
return closing_browser && closing_browser->IsSame(browser);
|
||||||
|
});
|
||||||
|
if (it == closing_tab_browsers_.end()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
closing_tab_browsers_.erase(it);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace nebula::app
|
} // namespace nebula::app
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ public:
|
|||||||
bool ShouldBypassInsecureWarning(CefRefPtr<CefBrowser> browser, const std::string& target_url) override;
|
bool ShouldBypassInsecureWarning(CefRefPtr<CefBrowser> browser, const std::string& target_url) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void CreateNewTab();
|
void CreateNewTab(std::string url = {});
|
||||||
void ActivateTab(int tab_id);
|
void ActivateTab(int tab_id);
|
||||||
void CloseTab(int tab_id);
|
void CloseTab(int tab_id);
|
||||||
void CreateChromeBrowser();
|
void CreateChromeBrowser();
|
||||||
@@ -50,6 +50,7 @@ private:
|
|||||||
void CloseMenuPopup();
|
void CloseMenuPopup();
|
||||||
void CreateMenuPopupBrowser();
|
void CreateMenuPopupBrowser();
|
||||||
void PositionMenuPopup();
|
void PositionMenuPopup();
|
||||||
|
void SendMenuPopupZoom();
|
||||||
void ToggleDevTools();
|
void ToggleDevTools();
|
||||||
void AdjustZoom(double delta);
|
void AdjustZoom(double delta);
|
||||||
void FreshReload();
|
void FreshReload();
|
||||||
@@ -60,12 +61,14 @@ private:
|
|||||||
void InjectSettingsHistory(CefRefPtr<CefBrowser> browser);
|
void InjectSettingsHistory(CefRefPtr<CefBrowser> browser);
|
||||||
void PersistSession() const;
|
void PersistSession() const;
|
||||||
void MaybeFinishShutdown();
|
void MaybeFinishShutdown();
|
||||||
|
bool ForgetClosingTabBrowser(CefRefPtr<CefBrowser> browser);
|
||||||
|
|
||||||
nebula::platform::AppStartup startup_;
|
nebula::platform::AppStartup startup_;
|
||||||
std::string initial_url_;
|
std::string initial_url_;
|
||||||
bool closing_ = false;
|
bool closing_ = false;
|
||||||
bool chrome_ready_ = false;
|
bool chrome_ready_ = false;
|
||||||
bool content_fullscreen_ = false;
|
bool content_fullscreen_ = false;
|
||||||
|
bool menu_popup_visible_ = false;
|
||||||
|
|
||||||
std::unique_ptr<nebula::window::NebulaWindow> window_;
|
std::unique_ptr<nebula::window::NebulaWindow> window_;
|
||||||
nebula::browser::TabManager tabs_;
|
nebula::browser::TabManager tabs_;
|
||||||
@@ -74,6 +77,7 @@ private:
|
|||||||
CefRefPtr<nebula::cef::NebulaBrowserClient> chrome_client_;
|
CefRefPtr<nebula::cef::NebulaBrowserClient> chrome_client_;
|
||||||
CefRefPtr<nebula::cef::NebulaBrowserClient> content_client_;
|
CefRefPtr<nebula::cef::NebulaBrowserClient> content_client_;
|
||||||
CefRefPtr<nebula::cef::NebulaBrowserClient> menu_popup_client_;
|
CefRefPtr<nebula::cef::NebulaBrowserClient> menu_popup_client_;
|
||||||
|
std::vector<CefRefPtr<CefBrowser>> closing_tab_browsers_;
|
||||||
std::unordered_set<std::string> insecure_warning_bypasses_;
|
std::unordered_set<std::string> insecure_warning_bypasses_;
|
||||||
std::vector<std::string> site_history_;
|
std::vector<std::string> site_history_;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -51,8 +51,8 @@ bool NebulaBrowserClient::OnProcessMessageReceived(CefRefPtr<CefBrowser> browser
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (role_ != BrowserRole::Chrome && role_ != BrowserRole::MenuPopup &&
|
if (role_ != BrowserRole::Chrome && role_ != BrowserRole::Content &&
|
||||||
role_ != BrowserRole::Content) {
|
role_ != BrowserRole::MenuPopup) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -64,6 +64,7 @@ bool NebulaBrowserClient::OnProcessMessageReceived(CefRefPtr<CefBrowser> browser
|
|||||||
command == "navigate-insecure" && IsInsecureInterstitialFrame(frame);
|
command == "navigate-insecure" && IsInsecureInterstitialFrame(frame);
|
||||||
const bool allowed_settings_command =
|
const bool allowed_settings_command =
|
||||||
IsSettingsFrame(frame) && (command == "navigate" ||
|
IsSettingsFrame(frame) && (command == "navigate" ||
|
||||||
|
command == "new-tab" ||
|
||||||
command == "clear-site-history" ||
|
command == "clear-site-history" ||
|
||||||
command == "clear-search-history");
|
command == "clear-search-history");
|
||||||
if (!allowed_insecure_command && !allowed_settings_command) {
|
if (!allowed_insecure_command && !allowed_settings_command) {
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ void ResizeBrowserWindow(NativeWindow browser_window, const Rect& rect);
|
|||||||
void SetBrowserVisible(NativeWindow browser_window, bool visible);
|
void SetBrowserVisible(NativeWindow browser_window, bool visible);
|
||||||
void RaiseBrowserWindow(NativeWindow browser_window);
|
void RaiseBrowserWindow(NativeWindow browser_window);
|
||||||
Rect MenuPopupRect(NativeWindow parent, const BrowserLayout& layout);
|
Rect MenuPopupRect(NativeWindow parent, const BrowserLayout& layout);
|
||||||
void ApplyRoundedBrowserRegion(NativeWindow browser_window, int corner_radius);
|
|
||||||
std::string CacheBusterToken();
|
std::string CacheBusterToken();
|
||||||
void DestroyTopLevelWindow(NativeWindow window);
|
void DestroyTopLevelWindow(NativeWindow window);
|
||||||
int ScaleForParentWindow(NativeWindow parent, int value);
|
int ScaleForParentWindow(NativeWindow parent, int value);
|
||||||
|
|||||||
@@ -45,26 +45,21 @@ std::pair<int, int> ParentClientSize(NativeWindow parent) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Rect MenuPopupRect(NativeWindow parent, const BrowserLayout& layout) {
|
Rect MenuPopupRect(NativeWindow parent, const BrowserLayout& layout) {
|
||||||
const auto [client_right, client_bottom] = ParentClientSize(parent);
|
const auto client_size = ParentClientSize(parent);
|
||||||
const int width = 260;
|
const int width = 260;
|
||||||
const int height = 258;
|
const int height = 258;
|
||||||
const int margin = 12;
|
const int margin = 12;
|
||||||
const int overlap = 2;
|
const int overlap = 2;
|
||||||
const int x = std::max(0, client_right - width - margin);
|
const int x = std::max(0, client_size.first - width - margin);
|
||||||
const int y = std::max(0, layout.chrome.y + layout.chrome.height - overlap);
|
const int y = std::max(0, layout.chrome.y + layout.chrome.height - overlap);
|
||||||
return {
|
return {
|
||||||
x,
|
x,
|
||||||
y,
|
y,
|
||||||
std::min(client_right, x + width) - x,
|
std::min(client_size.first, x + width) - x,
|
||||||
std::min(client_bottom, y + height) - y,
|
std::min(client_size.second, y + height) - y,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
void ApplyRoundedBrowserRegion(NativeWindow browser_window, int corner_radius) {
|
|
||||||
UNREFERENCED_PARAMETER(browser_window);
|
|
||||||
UNREFERENCED_PARAMETER(corner_radius);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string CacheBusterToken() {
|
std::string CacheBusterToken() {
|
||||||
return "0";
|
return "0";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,26 +43,21 @@ std::pair<int, int> ParentClientSize(NativeWindow parent) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Rect MenuPopupRect(NativeWindow parent, const BrowserLayout& layout) {
|
Rect MenuPopupRect(NativeWindow parent, const BrowserLayout& layout) {
|
||||||
const auto [client_right, client_bottom] = ParentClientSize(parent);
|
const auto client_size = ParentClientSize(parent);
|
||||||
const int width = 260;
|
const int width = 260;
|
||||||
const int height = 258;
|
const int height = 258;
|
||||||
const int margin = 12;
|
const int margin = 12;
|
||||||
const int overlap = 2;
|
const int overlap = 2;
|
||||||
const int x = std::max(0, client_right - width - margin);
|
const int x = std::max(0, client_size.first - width - margin);
|
||||||
const int y = std::max(0, layout.chrome.y + layout.chrome.height - overlap);
|
const int y = std::max(0, layout.chrome.y + layout.chrome.height - overlap);
|
||||||
return {
|
return {
|
||||||
x,
|
x,
|
||||||
y,
|
y,
|
||||||
std::min(client_right, x + width) - x,
|
std::min(client_size.first, x + width) - x,
|
||||||
std::min(client_bottom, y + height) - y,
|
std::min(client_size.second, y + height) - y,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
void ApplyRoundedBrowserRegion(NativeWindow browser_window, int corner_radius) {
|
|
||||||
UNREFERENCED_PARAMETER(browser_window);
|
|
||||||
UNREFERENCED_PARAMETER(corner_radius);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string CacheBusterToken() {
|
std::string CacheBusterToken() {
|
||||||
return "0";
|
return "0";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -95,42 +95,22 @@ std::pair<int, int> ParentClientSize(NativeWindow parent) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Rect MenuPopupRect(NativeWindow parent, const BrowserLayout& layout) {
|
Rect MenuPopupRect(NativeWindow parent, const BrowserLayout& layout) {
|
||||||
const auto [client_right, client_bottom] = ParentClientSize(parent);
|
const auto client_size = ParentClientSize(parent);
|
||||||
|
|
||||||
const int width = ScaleForParentWindow(parent, 260);
|
const int width = ScaleForParentWindow(parent, 260);
|
||||||
const int height = ScaleForParentWindow(parent, 258);
|
const int height = ScaleForParentWindow(parent, 258);
|
||||||
const int margin = ScaleForParentWindow(parent, 12);
|
const int margin = ScaleForParentWindow(parent, 12);
|
||||||
const int overlap = ScaleForParentWindow(parent, 2);
|
const int overlap = ScaleForParentWindow(parent, 2);
|
||||||
|
|
||||||
const int x = std::max(0, client_right - width - margin);
|
const int x = std::max(0, client_size.first - width - margin);
|
||||||
const int y = std::max(0, layout.chrome.y + layout.chrome.height - overlap);
|
const int y = std::max(0, layout.chrome.y + layout.chrome.height - overlap);
|
||||||
return {
|
return {
|
||||||
x,
|
x,
|
||||||
y,
|
y,
|
||||||
std::min(client_right, x + width) - x,
|
std::min(client_size.first, x + width) - x,
|
||||||
std::min(client_bottom, y + height) - y,
|
std::min(client_size.second, y + height) - y,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
void ApplyRoundedBrowserRegion(NativeWindow browser_window, int corner_radius) {
|
|
||||||
const HWND hwnd = AsHwnd(browser_window);
|
|
||||||
if (!hwnd) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
RECT rect = {};
|
|
||||||
if (!GetClientRect(hwnd, &rect)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const int width = std::max<LONG>(1, rect.right - rect.left);
|
|
||||||
const int height = std::max<LONG>(1, rect.bottom - rect.top);
|
|
||||||
HRGN region = CreateRoundRectRgn(0, 0, width + 1, height + 1, corner_radius, corner_radius);
|
|
||||||
if (region && !SetWindowRgn(hwnd, region, TRUE)) {
|
|
||||||
DeleteObject(region);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string CacheBusterToken() {
|
std::string CacheBusterToken() {
|
||||||
return std::to_string(GetTickCount64());
|
return std::to_string(GetTickCount64());
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-1
@@ -59,9 +59,10 @@ button:disabled {
|
|||||||
|
|
||||||
.nebula-chrome {
|
.nebula-chrome {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-rows: 42px 52px;
|
grid-template-rows: 42px 52px 1fr;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
border-bottom: 1px solid var(--outline);
|
border-bottom: 1px solid var(--outline);
|
||||||
|
overflow: visible;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ── Title row ──────────────────────────────────────────────── */
|
/* ── Title row ──────────────────────────────────────────────── */
|
||||||
@@ -253,6 +254,7 @@ button:disabled {
|
|||||||
padding: 0 12px;
|
padding: 0 12px;
|
||||||
background: var(--surface-raised);
|
background: var(--surface-raised);
|
||||||
border-top: 1px solid var(--outline);
|
border-top: 1px solid var(--outline);
|
||||||
|
overflow: visible;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ── Lucide icon sizing ─────────────────────────────────────── */
|
/* ── Lucide icon sizing ─────────────────────────────────────── */
|
||||||
@@ -361,3 +363,4 @@ button:disabled {
|
|||||||
transform: rotate(360deg);
|
transform: rotate(360deg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+31
-21
@@ -1,34 +1,40 @@
|
|||||||
:root {
|
:root {
|
||||||
--bg: #0b0d10;
|
--surface-raised: #141824;
|
||||||
--primary: #7b2eff;
|
--surface-hover: rgba(255, 255, 255, 0.06);
|
||||||
--accent: #00c6ff;
|
--text: #e8e8f0;
|
||||||
--text: #e0e0e0;
|
--muted: #7a7e90;
|
||||||
--url-bar-bg: #1c2030;
|
--outline: #1f2533;
|
||||||
--url-bar-border: #3e4652;
|
color-scheme: dark;
|
||||||
--shadow-1: 0 12px 30px rgba(0, 0, 0, 0.35);
|
}
|
||||||
--blur: 12px;
|
|
||||||
|
@font-face {
|
||||||
|
font-family: "InterVariable";
|
||||||
|
src: url("../assets/fonts/InterVariable.ttf") format("truetype");
|
||||||
|
font-weight: 100 900;
|
||||||
|
font-display: swap;
|
||||||
}
|
}
|
||||||
|
|
||||||
* { box-sizing: border-box; }
|
* { box-sizing: border-box; }
|
||||||
|
|
||||||
body {
|
body {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
background: transparent;
|
background: var(--surface-raised);
|
||||||
color: var(--text);
|
color: var(--text);
|
||||||
font-family: system-ui, -apple-system, Segoe UI, Roboto, sans-serif;
|
font-family: "InterVariable", "Segoe UI", system-ui, sans-serif;
|
||||||
|
overflow: hidden;
|
||||||
|
user-select: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
#menu-popup {
|
#menu-popup {
|
||||||
background: color-mix(in srgb, var(--url-bar-bg) 92%, var(--text) 8%);
|
width: 100%;
|
||||||
border: 1px solid color-mix(in srgb, var(--primary) 25%, color-mix(in srgb, var(--accent) 18%, transparent));
|
height: 100%;
|
||||||
border-radius: 14px;
|
background: var(--surface-raised);
|
||||||
padding: 8px;
|
border: 1px solid var(--outline);
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 6px;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
min-width: 220px;
|
box-shadow: 0 12px 32px rgba(0, 0, 0, 0.45);
|
||||||
box-shadow: var(--shadow-1);
|
|
||||||
-webkit-backdrop-filter: blur(var(--blur));
|
|
||||||
backdrop-filter: blur(var(--blur));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#menu-popup button {
|
#menu-popup button {
|
||||||
@@ -37,13 +43,15 @@ body {
|
|||||||
color: var(--text);
|
color: var(--text);
|
||||||
text-align: left;
|
text-align: left;
|
||||||
padding: 8px 10px;
|
padding: 8px 10px;
|
||||||
border-radius: 10px;
|
border-radius: 8px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: background 120ms ease, filter 120ms ease;
|
font: inherit;
|
||||||
|
font-size: 0.84rem;
|
||||||
|
transition: background 120ms ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
#menu-popup button:hover {
|
#menu-popup button:hover {
|
||||||
background: color-mix(in srgb, var(--text) 8%, transparent);
|
background: var(--surface-hover);
|
||||||
}
|
}
|
||||||
|
|
||||||
.zoom-controls {
|
.zoom-controls {
|
||||||
@@ -63,4 +71,6 @@ body {
|
|||||||
#zoom-percent {
|
#zoom-percent {
|
||||||
min-width: 54px;
|
min-width: 54px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
color: var(--muted);
|
||||||
|
font-size: 0.82rem;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -123,6 +123,7 @@ function applyState(nextState) {
|
|||||||
|
|
||||||
progressBar.style.width = `${Math.max(0, Math.min(1, state.progress || 0)) * 100}%`;
|
progressBar.style.width = `${Math.max(0, Math.min(1, state.progress || 0)) * 100}%`;
|
||||||
progressBar.style.opacity = state.isLoading ? '1' : '0';
|
progressBar.style.opacity = state.isLoading ? '1' : '0';
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function wireCommands() {
|
function wireCommands() {
|
||||||
|
|||||||
+11
-19
@@ -18,37 +18,29 @@ function applyTheme(theme) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function sendMenuCommand(cmd) {
|
function sendMenuCommand(cmd) {
|
||||||
if (window.electronAPI?.send) {
|
|
||||||
window.electronAPI.send('menu-popup-command', { cmd });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (window.nebulaNative?.postMessage) {
|
if (window.nebulaNative?.postMessage) {
|
||||||
window.nebulaNative.postMessage(cmd);
|
window.nebulaNative.postMessage(cmd);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function refreshZoom() {
|
function formatZoomPercent(zoomLevel) {
|
||||||
if (!window.electronAPI?.invoke || !zoomPercentEl) return;
|
const level = Number.isFinite(zoomLevel) ? zoomLevel : 0;
|
||||||
try {
|
return `${Math.round(Math.pow(1.2, level) * 100)}%`;
|
||||||
const z = await window.electronAPI.invoke('get-zoom-factor');
|
|
||||||
zoomPercentEl.textContent = `${Math.round(z * 100)}%`;
|
|
||||||
} catch {}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
window.electronAPI?.on?.('menu-popup-init', (payload) => {
|
function setZoomLevel(zoomLevel) {
|
||||||
applyTheme(payload?.theme);
|
if (zoomPercentEl) {
|
||||||
refreshZoom();
|
zoomPercentEl.textContent = formatZoomPercent(zoomLevel);
|
||||||
});
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
window.NebulaMenuPopup = { applyTheme, setZoomLevel };
|
||||||
|
|
||||||
window.addEventListener('click', (e) => {
|
window.addEventListener('click', (e) => {
|
||||||
const btn = e.target.closest('button[data-cmd]');
|
const btn = e.target.closest('button[data-cmd]');
|
||||||
if (!btn) return;
|
if (!btn) return;
|
||||||
const cmd = btn.getAttribute('data-cmd');
|
const cmd = btn.getAttribute('data-cmd');
|
||||||
sendMenuCommand(cmd);
|
sendMenuCommand(cmd);
|
||||||
if (cmd === 'zoom-in' || cmd === 'zoom-out') {
|
|
||||||
setTimeout(refreshZoom, 50);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
window.addEventListener('keydown', (e) => {
|
window.addEventListener('keydown', (e) => {
|
||||||
@@ -57,4 +49,4 @@ window.addEventListener('keydown', (e) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
refreshZoom();
|
setZoomLevel(0);
|
||||||
|
|||||||
+6
-2
@@ -773,7 +773,9 @@ window.addEventListener('DOMContentLoaded', () => {
|
|||||||
try {
|
try {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const url = gh.getAttribute('href');
|
const url = gh.getAttribute('href');
|
||||||
if (window.electronAPI && typeof window.electronAPI.sendToHost === 'function') {
|
if (window.nebulaNative && typeof window.nebulaNative.postMessage === 'function') {
|
||||||
|
window.nebulaNative.postMessage('new-tab', url);
|
||||||
|
} else if (window.electronAPI && typeof window.electronAPI.sendToHost === 'function') {
|
||||||
window.electronAPI.sendToHost('navigate', url, { newTab: true });
|
window.electronAPI.sendToHost('navigate', url, { newTab: true });
|
||||||
} else if (window.parent && window.parent !== window) {
|
} else if (window.parent && window.parent !== window) {
|
||||||
window.parent.postMessage({ type: 'navigate', url, newTab: true }, '*');
|
window.parent.postMessage({ type: 'navigate', url, newTab: true }, '*');
|
||||||
@@ -792,7 +794,9 @@ window.addEventListener('DOMContentLoaded', () => {
|
|||||||
try {
|
try {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const url = help.getAttribute('href');
|
const url = help.getAttribute('href');
|
||||||
if (window.electronAPI && typeof window.electronAPI.sendToHost === 'function') {
|
if (window.nebulaNative && typeof window.nebulaNative.postMessage === 'function') {
|
||||||
|
window.nebulaNative.postMessage('new-tab', url);
|
||||||
|
} else if (window.electronAPI && typeof window.electronAPI.sendToHost === 'function') {
|
||||||
window.electronAPI.sendToHost('navigate', url, { newTab: true });
|
window.electronAPI.sendToHost('navigate', url, { newTab: true });
|
||||||
} else if (window.parent && window.parent !== window) {
|
} else if (window.parent && window.parent !== window) {
|
||||||
window.parent.postMessage({ type: 'navigate', url, newTab: true }, '*');
|
window.parent.postMessage({ type: 'navigate', url, newTab: true }, '*');
|
||||||
|
|||||||
@@ -1,24 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8" />
|
|
||||||
<title>Menu</title>
|
|
||||||
<link rel="stylesheet" href="../css/menu-popup.css" />
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div id="menu-popup" role="menu">
|
|
||||||
<button data-cmd="open-settings" role="menuitem">Settings</button>
|
|
||||||
<button data-cmd="big-picture" role="menuitem">🎮 Big Picture Mode</button>
|
|
||||||
<button data-cmd="gpu-diagnostics" role="menuitem">GPU Diagnostics</button>
|
|
||||||
<button data-cmd="toggle-devtools" role="menuitem">Toggle Developer Tools</button>
|
|
||||||
<div class="zoom-controls" role="group" aria-label="Zoom controls">
|
|
||||||
<button data-cmd="zoom-out" aria-label="Zoom out">-</button>
|
|
||||||
<span id="zoom-percent">100%</span>
|
|
||||||
<button data-cmd="zoom-in" aria-label="Zoom in">+</button>
|
|
||||||
</div>
|
|
||||||
<button data-cmd="hard-reload" role="menuitem">Hard Reload (Ignore Cache)</button>
|
|
||||||
<button data-cmd="fresh-reload" role="menuitem">Reload Fresh (Add Cache-Buster)</button>
|
|
||||||
</div>
|
|
||||||
<script src="../js/menu-popup.js"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -489,8 +489,10 @@
|
|||||||
a.addEventListener('click', (e) => {
|
a.addEventListener('click', (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
try {
|
try {
|
||||||
if (window.electronAPI && typeof window.electronAPI.sendToHost === 'function') {
|
if (window.nebulaNative && typeof window.nebulaNative.postMessage === 'function') {
|
||||||
// Ask the host to open this URL in a new tab to keep Settings open
|
// Ask the CEF host to open this URL in a new tab to keep Settings open.
|
||||||
|
window.nebulaNative.postMessage('new-tab', item);
|
||||||
|
} else if (window.electronAPI && typeof window.electronAPI.sendToHost === 'function') {
|
||||||
window.electronAPI.sendToHost('navigate', item, { newTab: true });
|
window.electronAPI.sendToHost('navigate', item, { newTab: true });
|
||||||
} else if (window.parent && window.parent !== window) {
|
} else if (window.parent && window.parent !== window) {
|
||||||
// Fallback: postMessage to parent if available
|
// Fallback: postMessage to parent if available
|
||||||
|
|||||||
Reference in New Issue
Block a user