Add Big Picture mode and multi-target build
Introduce a Big Picture mode and support building two app targets. CMakeLists was refactored to add a helper (add_nebula_app_target) and now registers NebulaBrowser and NebulaBigPicture executables, moving UI/assets post-build copying into the helper. A new app/main_bigpicture.cpp entry was added and RunNebula now accepts LaunchOptions (AppMode) with a new LaunchOptions struct. NebulaController was extended heavily to manage a BigPicture browser role: creation, enter/exit mode, layout logic, cursor injection/removal, remote input handlers (mouse move/click/wheel/text), and state syncing. Cef/browser_client updated for the new BigPicture role and message filtering. Platform API gained MoveCursorToBrowserPoint with platform stubs/Win implementation. Big-picture UI files (CSS/JS/HTML) were also updated to support the new mode.
This commit is contained in:
+96
-80
@@ -35,7 +35,6 @@ SET_CEF_TARGET_OUT_DIR()
|
|||||||
# ------------------------------------------------------------
|
# ------------------------------------------------------------
|
||||||
|
|
||||||
set(NEBULA_COMMON_SOURCES
|
set(NEBULA_COMMON_SOURCES
|
||||||
app/main.cpp
|
|
||||||
src/app/nebula_controller.cpp
|
src/app/nebula_controller.cpp
|
||||||
src/app/run.cpp
|
src/app/run.cpp
|
||||||
src/browser/session_state.cpp
|
src/browser/session_state.cpp
|
||||||
@@ -54,10 +53,6 @@ if(OS_WINDOWS)
|
|||||||
src/platform/win/browser_host_win.cpp
|
src/platform/win/browser_host_win.cpp
|
||||||
src/platform/win/nebula_window_win.cpp
|
src/platform/win/nebula_window_win.cpp
|
||||||
)
|
)
|
||||||
add_executable(NebulaBrowser WIN32
|
|
||||||
${NEBULA_COMMON_SOURCES}
|
|
||||||
${NEBULA_PLATFORM_SOURCES}
|
|
||||||
)
|
|
||||||
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
|
||||||
@@ -65,10 +60,6 @@ elseif(OS_MACOSX)
|
|||||||
src/platform/mac/browser_host_mac.cpp
|
src/platform/mac/browser_host_mac.cpp
|
||||||
src/platform/mac/nebula_window_mac.cpp
|
src/platform/mac/nebula_window_mac.cpp
|
||||||
)
|
)
|
||||||
add_executable(NebulaBrowser MACOSX_BUNDLE
|
|
||||||
${NEBULA_COMMON_SOURCES}
|
|
||||||
${NEBULA_PLATFORM_SOURCES}
|
|
||||||
)
|
|
||||||
elseif(OS_LINUX)
|
elseif(OS_LINUX)
|
||||||
set(NEBULA_PLATFORM_SOURCES
|
set(NEBULA_PLATFORM_SOURCES
|
||||||
src/platform/linux/paths_linux.cpp
|
src/platform/linux/paths_linux.cpp
|
||||||
@@ -76,86 +67,111 @@ elseif(OS_LINUX)
|
|||||||
src/platform/linux/browser_host_linux.cpp
|
src/platform/linux/browser_host_linux.cpp
|
||||||
src/platform/linux/nebula_window_linux.cpp
|
src/platform/linux/nebula_window_linux.cpp
|
||||||
)
|
)
|
||||||
add_executable(NebulaBrowser
|
|
||||||
${NEBULA_COMMON_SOURCES}
|
|
||||||
${NEBULA_PLATFORM_SOURCES}
|
|
||||||
)
|
|
||||||
else()
|
else()
|
||||||
message(FATAL_ERROR "Unsupported platform.")
|
message(FATAL_ERROR "Unsupported platform.")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
SET_EXECUTABLE_TARGET_PROPERTIES(NebulaBrowser)
|
|
||||||
add_dependencies(NebulaBrowser libcef_dll_wrapper)
|
|
||||||
|
|
||||||
target_include_directories(NebulaBrowser PRIVATE
|
|
||||||
"${CMAKE_SOURCE_DIR}/src"
|
|
||||||
"${CEF_ROOT}"
|
|
||||||
"${CEF_ROOT}/include"
|
|
||||||
)
|
|
||||||
|
|
||||||
ADD_LOGICAL_TARGET("libcef_lib" "${CEF_LIB_RELEASE}" "${CEF_LIB_DEBUG}")
|
ADD_LOGICAL_TARGET("libcef_lib" "${CEF_LIB_RELEASE}" "${CEF_LIB_DEBUG}")
|
||||||
target_link_libraries(NebulaBrowser PRIVATE
|
|
||||||
libcef_lib
|
|
||||||
libcef_dll_wrapper
|
|
||||||
${CEF_STANDARD_LIBS}
|
|
||||||
)
|
|
||||||
|
|
||||||
if(MSVC)
|
if(OS_LINUX)
|
||||||
set_property(TARGET NebulaBrowser PROPERTY
|
|
||||||
MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>"
|
|
||||||
)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
# ------------------------------------------------------------
|
|
||||||
# Platform-specific CEF runtime deployment
|
|
||||||
# ------------------------------------------------------------
|
|
||||||
|
|
||||||
if(OS_WINDOWS)
|
|
||||||
target_link_libraries(NebulaBrowser PRIVATE dwmapi)
|
|
||||||
target_compile_definitions(NebulaBrowser PRIVATE
|
|
||||||
NOMINMAX
|
|
||||||
WIN32_LEAN_AND_MEAN
|
|
||||||
)
|
|
||||||
COPY_FILES("NebulaBrowser" "${CEF_BINARY_FILES}" "${CEF_BINARY_DIR_RELEASE}" "${CEF_TARGET_OUT_DIR}")
|
|
||||||
COPY_FILES("NebulaBrowser" "${CEF_RESOURCE_FILES}" "${CEF_RESOURCE_DIR}" "${CEF_TARGET_OUT_DIR}")
|
|
||||||
elseif(OS_LINUX)
|
|
||||||
FIND_LINUX_LIBRARIES("X11")
|
FIND_LINUX_LIBRARIES("X11")
|
||||||
COPY_FILES("NebulaBrowser" "${CEF_BINARY_FILES}" "${CEF_BINARY_DIR_RELEASE}" "${CEF_TARGET_OUT_DIR}")
|
|
||||||
COPY_FILES("NebulaBrowser" "${CEF_RESOURCE_FILES}" "${CEF_RESOURCE_DIR}" "${CEF_TARGET_OUT_DIR}")
|
|
||||||
elseif(OS_MACOSX)
|
|
||||||
set(NEBULA_APP "${CEF_TARGET_OUT_DIR}/NebulaBrowser.app")
|
|
||||||
COPY_MAC_FRAMEWORK(
|
|
||||||
"NebulaBrowser"
|
|
||||||
"Chromium Embedded Framework"
|
|
||||||
"${CEF_BINARY_DIR_RELEASE}"
|
|
||||||
"${NEBULA_APP}/Contents/Frameworks"
|
|
||||||
)
|
|
||||||
COPY_FILES(
|
|
||||||
"NebulaBrowser"
|
|
||||||
"${CEF_BINARY_FILES}"
|
|
||||||
"${CEF_BINARY_DIR_RELEASE}"
|
|
||||||
"${NEBULA_APP}/Contents/Frameworks"
|
|
||||||
)
|
|
||||||
COPY_FILES(
|
|
||||||
"NebulaBrowser"
|
|
||||||
"${CEF_RESOURCE_FILES}"
|
|
||||||
"${CEF_RESOURCE_DIR}"
|
|
||||||
"${NEBULA_APP}/Contents/Resources"
|
|
||||||
)
|
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# ------------------------------------------------------------
|
function(add_nebula_app_target nebula_target entry_source)
|
||||||
# Copy Nebula UI files after build
|
if(OS_WINDOWS)
|
||||||
# ------------------------------------------------------------
|
add_executable(${nebula_target} WIN32
|
||||||
|
${entry_source}
|
||||||
|
${NEBULA_COMMON_SOURCES}
|
||||||
|
${NEBULA_PLATFORM_SOURCES}
|
||||||
|
)
|
||||||
|
elseif(OS_MACOSX)
|
||||||
|
add_executable(${nebula_target} MACOSX_BUNDLE
|
||||||
|
${entry_source}
|
||||||
|
${NEBULA_COMMON_SOURCES}
|
||||||
|
${NEBULA_PLATFORM_SOURCES}
|
||||||
|
)
|
||||||
|
elseif(OS_LINUX)
|
||||||
|
add_executable(${nebula_target}
|
||||||
|
${entry_source}
|
||||||
|
${NEBULA_COMMON_SOURCES}
|
||||||
|
${NEBULA_PLATFORM_SOURCES}
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
add_custom_command(TARGET NebulaBrowser POST_BUILD
|
SET_EXECUTABLE_TARGET_PROPERTIES(${nebula_target})
|
||||||
COMMAND ${CMAKE_COMMAND} -E copy_directory
|
add_dependencies(${nebula_target} libcef_dll_wrapper)
|
||||||
"${CMAKE_SOURCE_DIR}/ui"
|
|
||||||
"$<TARGET_FILE_DIR:NebulaBrowser>/ui"
|
|
||||||
|
|
||||||
COMMAND ${CMAKE_COMMAND} -E copy_directory
|
target_include_directories(${nebula_target} PRIVATE
|
||||||
"${CMAKE_SOURCE_DIR}/assets"
|
"${CMAKE_SOURCE_DIR}/src"
|
||||||
"$<TARGET_FILE_DIR:NebulaBrowser>/ui/assets"
|
"${CEF_ROOT}"
|
||||||
|
"${CEF_ROOT}/include"
|
||||||
|
)
|
||||||
|
|
||||||
COMMENT "Copying Nebula UI files and assets..."
|
target_link_libraries(${nebula_target} PRIVATE
|
||||||
)
|
libcef_lib
|
||||||
|
libcef_dll_wrapper
|
||||||
|
${CEF_STANDARD_LIBS}
|
||||||
|
)
|
||||||
|
|
||||||
|
if(MSVC)
|
||||||
|
set_property(TARGET ${nebula_target} PROPERTY
|
||||||
|
MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>"
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# ------------------------------------------------------------
|
||||||
|
# Platform-specific CEF runtime deployment
|
||||||
|
# ------------------------------------------------------------
|
||||||
|
|
||||||
|
if(OS_WINDOWS)
|
||||||
|
target_link_libraries(${nebula_target} PRIVATE dwmapi)
|
||||||
|
target_compile_definitions(${nebula_target} PRIVATE
|
||||||
|
NOMINMAX
|
||||||
|
WIN32_LEAN_AND_MEAN
|
||||||
|
)
|
||||||
|
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}")
|
||||||
|
elseif(OS_LINUX)
|
||||||
|
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}")
|
||||||
|
elseif(OS_MACOSX)
|
||||||
|
set(NEBULA_APP "${CEF_TARGET_OUT_DIR}/${nebula_target}.app")
|
||||||
|
COPY_MAC_FRAMEWORK(
|
||||||
|
"${nebula_target}"
|
||||||
|
"Chromium Embedded Framework"
|
||||||
|
"${CEF_BINARY_DIR_RELEASE}"
|
||||||
|
"${NEBULA_APP}/Contents/Frameworks"
|
||||||
|
)
|
||||||
|
COPY_FILES(
|
||||||
|
"${nebula_target}"
|
||||||
|
"${CEF_BINARY_FILES}"
|
||||||
|
"${CEF_BINARY_DIR_RELEASE}"
|
||||||
|
"${NEBULA_APP}/Contents/Frameworks"
|
||||||
|
)
|
||||||
|
COPY_FILES(
|
||||||
|
"${nebula_target}"
|
||||||
|
"${CEF_RESOURCE_FILES}"
|
||||||
|
"${CEF_RESOURCE_DIR}"
|
||||||
|
"${NEBULA_APP}/Contents/Resources"
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# ------------------------------------------------------------
|
||||||
|
# Copy Nebula UI files after build
|
||||||
|
# ------------------------------------------------------------
|
||||||
|
|
||||||
|
add_custom_command(TARGET ${nebula_target} POST_BUILD
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E copy_directory
|
||||||
|
"${CMAKE_SOURCE_DIR}/ui"
|
||||||
|
"$<TARGET_FILE_DIR:${nebula_target}>/ui"
|
||||||
|
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E copy_directory
|
||||||
|
"${CMAKE_SOURCE_DIR}/assets"
|
||||||
|
"$<TARGET_FILE_DIR:${nebula_target}>/ui/assets"
|
||||||
|
|
||||||
|
COMMENT "Copying Nebula UI files and assets for ${nebula_target}..."
|
||||||
|
)
|
||||||
|
endfunction()
|
||||||
|
|
||||||
|
add_nebula_app_target(NebulaBrowser app/main.cpp)
|
||||||
|
add_nebula_app_target(NebulaBigPicture app/main_bigpicture.cpp)
|
||||||
|
|||||||
+2
-2
@@ -12,11 +12,11 @@ int APIENTRY wWinMain(HINSTANCE instance,
|
|||||||
UNREFERENCED_PARAMETER(command_line);
|
UNREFERENCED_PARAMETER(command_line);
|
||||||
|
|
||||||
const nebula::platform::AppStartup startup{instance, show_command};
|
const nebula::platform::AppStartup startup{instance, show_command};
|
||||||
return nebula::app::RunNebula(startup);
|
return nebula::app::RunNebula(startup, {nebula::app::AppMode::Desktop});
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
int main(int argc, char* argv[]) {
|
int main(int argc, char* argv[]) {
|
||||||
const nebula::platform::AppStartup startup{argc, argv};
|
const nebula::platform::AppStartup startup{argc, argv};
|
||||||
return nebula::app::RunNebula(startup);
|
return nebula::app::RunNebula(startup, {nebula::app::AppMode::Desktop});
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
#include "app/run.h"
|
||||||
|
#include "platform/types.h"
|
||||||
|
|
||||||
|
#if defined(_WIN32)
|
||||||
|
#include <windows.h>
|
||||||
|
|
||||||
|
int APIENTRY wWinMain(HINSTANCE instance,
|
||||||
|
HINSTANCE previous_instance,
|
||||||
|
LPWSTR command_line,
|
||||||
|
int show_command) {
|
||||||
|
UNREFERENCED_PARAMETER(previous_instance);
|
||||||
|
UNREFERENCED_PARAMETER(command_line);
|
||||||
|
|
||||||
|
const nebula::platform::AppStartup startup{instance, show_command};
|
||||||
|
return nebula::app::RunNebula(startup, {nebula::app::AppMode::BigPicture});
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
int main(int argc, char* argv[]) {
|
||||||
|
const nebula::platform::AppStartup startup{argc, argv};
|
||||||
|
return nebula::app::RunNebula(startup, {nebula::app::AppMode::BigPicture});
|
||||||
|
}
|
||||||
|
#endif
|
||||||
+439
-11
@@ -94,6 +94,54 @@ int ParseTabId(const std::string& value) {
|
|||||||
return result.ec == std::errc{} && result.ptr == value.data() + value.size() ? tab_id : 0;
|
return result.ec == std::errc{} && result.ptr == value.data() + value.size() ? tab_id : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool ParseTwoInts(const std::string& value, int& first, int& second) {
|
||||||
|
const size_t separator = value.find(',');
|
||||||
|
if (separator == std::string::npos) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string first_value = value.substr(0, separator);
|
||||||
|
const std::string second_value = value.substr(separator + 1);
|
||||||
|
const auto first_result =
|
||||||
|
std::from_chars(first_value.data(), first_value.data() + first_value.size(), first);
|
||||||
|
const auto second_result =
|
||||||
|
std::from_chars(second_value.data(), second_value.data() + second_value.size(), second);
|
||||||
|
return first_result.ec == std::errc{} && first_result.ptr == first_value.data() + first_value.size() &&
|
||||||
|
second_result.ec == std::errc{} && second_result.ptr == second_value.data() + second_value.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ParseFourInts(const std::string& value, int& first, int& second, int& third, int& fourth) {
|
||||||
|
const size_t first_separator = value.find(',');
|
||||||
|
if (first_separator == std::string::npos) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const size_t second_separator = value.find(',', first_separator + 1);
|
||||||
|
if (second_separator == std::string::npos) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const size_t third_separator = value.find(',', second_separator + 1);
|
||||||
|
if (third_separator == std::string::npos) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string first_value = value.substr(0, first_separator);
|
||||||
|
const std::string second_value = value.substr(first_separator + 1, second_separator - first_separator - 1);
|
||||||
|
const std::string third_value = value.substr(second_separator + 1, third_separator - second_separator - 1);
|
||||||
|
const std::string fourth_value = value.substr(third_separator + 1);
|
||||||
|
const auto first_result =
|
||||||
|
std::from_chars(first_value.data(), first_value.data() + first_value.size(), first);
|
||||||
|
const auto second_result =
|
||||||
|
std::from_chars(second_value.data(), second_value.data() + second_value.size(), second);
|
||||||
|
const auto third_result =
|
||||||
|
std::from_chars(third_value.data(), third_value.data() + third_value.size(), third);
|
||||||
|
const auto fourth_result =
|
||||||
|
std::from_chars(fourth_value.data(), fourth_value.data() + fourth_value.size(), fourth);
|
||||||
|
return first_result.ec == std::errc{} && first_result.ptr == first_value.data() + first_value.size() &&
|
||||||
|
second_result.ec == std::errc{} && second_result.ptr == second_value.data() + second_value.size() &&
|
||||||
|
third_result.ec == std::errc{} && third_result.ptr == third_value.data() + third_value.size() &&
|
||||||
|
fourth_result.ec == std::errc{} && fourth_result.ptr == fourth_value.data() + fourth_value.size();
|
||||||
|
}
|
||||||
|
|
||||||
std::string WithCacheBuster(std::string url) {
|
std::string WithCacheBuster(std::string url) {
|
||||||
if (url.empty()) {
|
if (url.empty()) {
|
||||||
return url;
|
return url;
|
||||||
@@ -124,9 +172,12 @@ void SetBrowserVisible(CefRefPtr<CefBrowser> browser, bool visible) {
|
|||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
NebulaController::NebulaController(nebula::platform::AppStartup startup, std::string initial_url)
|
NebulaController::NebulaController(nebula::platform::AppStartup startup,
|
||||||
|
std::string initial_url,
|
||||||
|
LaunchOptions launch_options)
|
||||||
: startup_(startup),
|
: startup_(startup),
|
||||||
initial_url_(std::move(initial_url)),
|
initial_url_(std::move(initial_url)),
|
||||||
|
launch_options_(launch_options),
|
||||||
tabs_(this),
|
tabs_(this),
|
||||||
site_history_(LoadSiteHistory()) {}
|
site_history_(LoadSiteHistory()) {}
|
||||||
|
|
||||||
@@ -138,6 +189,11 @@ bool NebulaController::Create() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void NebulaController::OnWindowCreated() {
|
void NebulaController::OnWindowCreated() {
|
||||||
|
big_picture_mode_ = launch_options_.mode == AppMode::BigPicture;
|
||||||
|
if (big_picture_mode_ && window_) {
|
||||||
|
window_->SetFullscreen(true);
|
||||||
|
}
|
||||||
|
|
||||||
if (initial_url_.empty()) {
|
if (initial_url_.empty()) {
|
||||||
tabs_.CreateInitialTab(nebula::ui::GetHomeUrl());
|
tabs_.CreateInitialTab(nebula::ui::GetHomeUrl());
|
||||||
} else {
|
} else {
|
||||||
@@ -145,8 +201,13 @@ void NebulaController::OnWindowCreated() {
|
|||||||
}
|
}
|
||||||
PersistSession();
|
PersistSession();
|
||||||
|
|
||||||
CreateChromeBrowser();
|
if (!big_picture_mode_) {
|
||||||
|
CreateChromeBrowser();
|
||||||
|
}
|
||||||
CreateContentBrowser();
|
CreateContentBrowser();
|
||||||
|
if (big_picture_mode_) {
|
||||||
|
CreateBigPictureBrowser();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void NebulaController::OnWindowResized(const nebula::window::BrowserLayout& layout) {
|
void NebulaController::OnWindowResized(const nebula::window::BrowserLayout& layout) {
|
||||||
@@ -178,6 +239,9 @@ void NebulaController::OnWindowCloseRequested() {
|
|||||||
if (chrome_browser_) {
|
if (chrome_browser_) {
|
||||||
chrome_browser_->GetHost()->CloseBrowser(true);
|
chrome_browser_->GetHost()->CloseBrowser(true);
|
||||||
}
|
}
|
||||||
|
if (big_picture_browser_) {
|
||||||
|
big_picture_browser_->GetHost()->CloseBrowser(true);
|
||||||
|
}
|
||||||
if (menu_popup_browser_) {
|
if (menu_popup_browser_) {
|
||||||
menu_popup_browser_->GetHost()->CloseBrowser(true);
|
menu_popup_browser_->GetHost()->CloseBrowser(true);
|
||||||
}
|
}
|
||||||
@@ -200,6 +264,12 @@ void NebulaController::OnActiveTabChanged(const nebula::browser::NebulaTab& tab)
|
|||||||
if (chrome_ready_) {
|
if (chrome_ready_) {
|
||||||
SendChromeState(tab);
|
SendChromeState(tab);
|
||||||
}
|
}
|
||||||
|
if (big_picture_ready_) {
|
||||||
|
SendBigPictureState(tab);
|
||||||
|
}
|
||||||
|
if (big_picture_mode_) {
|
||||||
|
InjectBigPictureCursor(tab.browser);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void NebulaController::OnBrowserCreated(nebula::cef::BrowserRole role, CefRefPtr<CefBrowser> browser) {
|
void NebulaController::OnBrowserCreated(nebula::cef::BrowserRole role, CefRefPtr<CefBrowser> browser) {
|
||||||
@@ -213,6 +283,13 @@ void NebulaController::OnBrowserCreated(nebula::cef::BrowserRole role, CefRefPtr
|
|||||||
if (const auto* tab = tabs_.ActiveTab()) {
|
if (const auto* tab = tabs_.ActiveTab()) {
|
||||||
SendChromeState(*tab);
|
SendChromeState(*tab);
|
||||||
}
|
}
|
||||||
|
} else if (role == nebula::cef::BrowserRole::BigPicture) {
|
||||||
|
big_picture_browser_ = browser;
|
||||||
|
big_picture_ready_ = true;
|
||||||
|
SetBrowserVisible(big_picture_browser_, big_picture_mode_);
|
||||||
|
if (const auto* tab = tabs_.ActiveTab()) {
|
||||||
|
SendBigPictureState(*tab);
|
||||||
|
}
|
||||||
} 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;
|
menu_popup_visible_ = true;
|
||||||
@@ -229,6 +306,11 @@ void NebulaController::OnBrowserClosing(nebula::cef::BrowserRole role, CefRefPtr
|
|||||||
if (role == nebula::cef::BrowserRole::Chrome) {
|
if (role == nebula::cef::BrowserRole::Chrome) {
|
||||||
chrome_browser_ = nullptr;
|
chrome_browser_ = nullptr;
|
||||||
chrome_ready_ = false;
|
chrome_ready_ = false;
|
||||||
|
} else if (role == nebula::cef::BrowserRole::BigPicture) {
|
||||||
|
big_picture_browser_ = nullptr;
|
||||||
|
big_picture_client_ = nullptr;
|
||||||
|
big_picture_ready_ = false;
|
||||||
|
big_picture_mode_ = false;
|
||||||
} 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;
|
||||||
@@ -277,8 +359,7 @@ void NebulaController::OnChromeCommand(const std::string& command, const std::st
|
|||||||
CloseMenuPopup();
|
CloseMenuPopup();
|
||||||
tabs_.LoadURL(nebula::ui::GetSettingsUrl());
|
tabs_.LoadURL(nebula::ui::GetSettingsUrl());
|
||||||
} else if (command == "big-picture") {
|
} else if (command == "big-picture") {
|
||||||
CloseMenuPopup();
|
EnterBigPictureMode();
|
||||||
tabs_.LoadURL(nebula::ui::GetBigPictureUrl());
|
|
||||||
} else if (command == "gpu-diagnostics") {
|
} else if (command == "gpu-diagnostics") {
|
||||||
CloseMenuPopup();
|
CloseMenuPopup();
|
||||||
tabs_.LoadURL(nebula::ui::GetGpuDiagnosticsUrl());
|
tabs_.LoadURL(nebula::ui::GetGpuDiagnosticsUrl());
|
||||||
@@ -306,6 +387,25 @@ void NebulaController::OnChromeCommand(const std::string& command, const std::st
|
|||||||
if (auto* tab = tabs_.ActiveTab(); tab && tab->browser) {
|
if (auto* tab = tabs_.ActiveTab(); tab && tab->browser) {
|
||||||
InjectSettingsHistory(tab->browser);
|
InjectSettingsHistory(tab->browser);
|
||||||
}
|
}
|
||||||
|
if (auto* tab = tabs_.ActiveTab()) {
|
||||||
|
SendBigPictureState(*tab);
|
||||||
|
}
|
||||||
|
} else if (command == "clear-search-history") {
|
||||||
|
if (auto* tab = tabs_.ActiveTab()) {
|
||||||
|
SendBigPictureState(*tab);
|
||||||
|
}
|
||||||
|
} else if (command == "bigpicture-mouse-move") {
|
||||||
|
SendBigPictureMouseMove(payload);
|
||||||
|
} else if (command == "bigpicture-click") {
|
||||||
|
SendBigPictureMouseClick(payload, false);
|
||||||
|
} else if (command == "bigpicture-right-click") {
|
||||||
|
SendBigPictureMouseClick(payload, true);
|
||||||
|
} else if (command == "bigpicture-scroll") {
|
||||||
|
SendBigPictureMouseWheel(payload);
|
||||||
|
} else if (command == "bigpicture-text") {
|
||||||
|
SendBigPictureText(payload);
|
||||||
|
} else if (command == "bigpicture-browse-visible") {
|
||||||
|
SetBigPictureBrowseVisible(payload == "1" || payload == "true");
|
||||||
} else if (command == "minimize" && window_) {
|
} else if (command == "minimize" && window_) {
|
||||||
window_->Minimize();
|
window_->Minimize();
|
||||||
} else if (command == "maximize" && window_) {
|
} else if (command == "maximize" && window_) {
|
||||||
@@ -313,7 +413,11 @@ void NebulaController::OnChromeCommand(const std::string& command, const std::st
|
|||||||
} else if (command == "close" && window_) {
|
} else if (command == "close" && window_) {
|
||||||
OnWindowCloseRequested();
|
OnWindowCloseRequested();
|
||||||
} else if (command == "exit-bigpicture" && window_) {
|
} else if (command == "exit-bigpicture" && window_) {
|
||||||
OnWindowCloseRequested();
|
if (launch_options_.mode == AppMode::BigPicture) {
|
||||||
|
OnWindowCloseRequested();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ExitBigPictureMode();
|
||||||
} else if (command == "drag" && window_) {
|
} else if (command == "drag" && window_) {
|
||||||
window_->BeginDrag();
|
window_->BeginDrag();
|
||||||
}
|
}
|
||||||
@@ -326,6 +430,9 @@ void NebulaController::OnContentAddressChanged(CefRefPtr<CefBrowser> browser, co
|
|||||||
? nebula::ui::GetHomeUrl()
|
? nebula::ui::GetHomeUrl()
|
||||||
: internal_url);
|
: internal_url);
|
||||||
RecordSiteHistory(internal_url);
|
RecordSiteHistory(internal_url);
|
||||||
|
if (const auto* active_tab = tabs_.ActiveTab()) {
|
||||||
|
SendBigPictureState(*active_tab);
|
||||||
|
}
|
||||||
PersistSession();
|
PersistSession();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -350,6 +457,7 @@ void NebulaController::OnContentLoadFinished(CefRefPtr<CefBrowser> browser, cons
|
|||||||
if (nebula::ui::ToInternalUrl(url).starts_with(nebula::ui::GetSettingsUrl())) {
|
if (nebula::ui::ToInternalUrl(url).starts_with(nebula::ui::GetSettingsUrl())) {
|
||||||
InjectSettingsHistory(browser);
|
InjectSettingsHistory(browser);
|
||||||
}
|
}
|
||||||
|
InjectBigPictureCursor(browser);
|
||||||
}
|
}
|
||||||
|
|
||||||
void NebulaController::OnContentFaviconChanged(CefRefPtr<CefBrowser> browser, const std::vector<std::string>& urls) {
|
void NebulaController::OnContentFaviconChanged(CefRefPtr<CefBrowser> browser, const std::vector<std::string>& urls) {
|
||||||
@@ -457,7 +565,7 @@ void NebulaController::CreateChromeBrowser() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto layout = window_->CurrentLayout();
|
const auto layout = CurrentBrowserLayout();
|
||||||
CefBrowserSettings browser_settings = BrowserSettings();
|
CefBrowserSettings browser_settings = BrowserSettings();
|
||||||
chrome_client_ = new nebula::cef::NebulaBrowserClient(nebula::cef::BrowserRole::Chrome, this);
|
chrome_client_ = new nebula::cef::NebulaBrowserClient(nebula::cef::BrowserRole::Chrome, this);
|
||||||
CefWindowInfo window_info =
|
CefWindowInfo window_info =
|
||||||
@@ -466,6 +574,25 @@ void NebulaController::CreateChromeBrowser() {
|
|||||||
window_info, chrome_client_, nebula::ui::GetChromeUrl(), browser_settings, nullptr, nullptr);
|
window_info, chrome_client_, nebula::ui::GetChromeUrl(), browser_settings, nullptr, nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void NebulaController::CreateBigPictureBrowser() {
|
||||||
|
if (!window_ || !window_->native_handle()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto layout = CurrentBrowserLayout();
|
||||||
|
CefBrowserSettings browser_settings = BrowserSettings();
|
||||||
|
big_picture_client_ = new nebula::cef::NebulaBrowserClient(nebula::cef::BrowserRole::BigPicture, this);
|
||||||
|
CefWindowInfo window_info =
|
||||||
|
nebula::platform::MakeChildWindowInfo(window_->native_handle(), layout.chrome);
|
||||||
|
CefBrowserHost::CreateBrowser(
|
||||||
|
window_info,
|
||||||
|
big_picture_client_,
|
||||||
|
nebula::ui::ResolveInternalUrl(nebula::ui::GetBigPictureUrl()),
|
||||||
|
browser_settings,
|
||||||
|
nullptr,
|
||||||
|
nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
void NebulaController::CreateContentBrowser() {
|
void NebulaController::CreateContentBrowser() {
|
||||||
if (!window_ || !window_->native_handle()) {
|
if (!window_ || !window_->native_handle()) {
|
||||||
return;
|
return;
|
||||||
@@ -473,7 +600,7 @@ void NebulaController::CreateContentBrowser() {
|
|||||||
|
|
||||||
const auto* tab = tabs_.ActiveTab();
|
const auto* tab = tabs_.ActiveTab();
|
||||||
const std::string url = tab && !tab->url.empty() ? tab->url : nebula::ui::GetHomeUrl();
|
const std::string url = tab && !tab->url.empty() ? tab->url : nebula::ui::GetHomeUrl();
|
||||||
const auto layout = window_->CurrentLayout();
|
const auto layout = CurrentBrowserLayout();
|
||||||
CefBrowserSettings browser_settings = BrowserSettings();
|
CefBrowserSettings browser_settings = BrowserSettings();
|
||||||
content_client_ = new nebula::cef::NebulaBrowserClient(nebula::cef::BrowserRole::Content, this);
|
content_client_ = new nebula::cef::NebulaBrowserClient(nebula::cef::BrowserRole::Content, this);
|
||||||
CefWindowInfo window_info =
|
CefWindowInfo window_info =
|
||||||
@@ -482,7 +609,94 @@ void NebulaController::CreateContentBrowser() {
|
|||||||
window_info, content_client_, nebula::ui::ResolveInternalUrl(url), browser_settings, nullptr, nullptr);
|
window_info, content_client_, nebula::ui::ResolveInternalUrl(url), browser_settings, nullptr, nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void NebulaController::EnterBigPictureMode() {
|
||||||
|
if (big_picture_mode_) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
CloseMenuPopup();
|
||||||
|
if (content_fullscreen_) {
|
||||||
|
SetContentFullscreen(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
big_picture_mode_ = true;
|
||||||
|
big_picture_browse_visible_ = false;
|
||||||
|
if (auto* tab = tabs_.ActiveTab()) {
|
||||||
|
InjectBigPictureCursor(tab->browser);
|
||||||
|
}
|
||||||
|
SetBrowserVisible(chrome_browser_, false);
|
||||||
|
if (big_picture_browser_) {
|
||||||
|
SetBrowserVisible(big_picture_browser_, true);
|
||||||
|
if (const auto* tab = tabs_.ActiveTab()) {
|
||||||
|
SendBigPictureState(*tab);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
CreateBigPictureBrowser();
|
||||||
|
}
|
||||||
|
ResizeBrowsers();
|
||||||
|
}
|
||||||
|
|
||||||
|
void NebulaController::ExitBigPictureMode() {
|
||||||
|
if (!big_picture_mode_) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
big_picture_mode_ = false;
|
||||||
|
big_picture_browse_visible_ = false;
|
||||||
|
if (auto* tab = tabs_.ActiveTab()) {
|
||||||
|
RemoveBigPictureCursor(tab->browser);
|
||||||
|
}
|
||||||
|
SetBrowserVisible(big_picture_browser_, false);
|
||||||
|
SetBrowserVisible(chrome_browser_, true);
|
||||||
|
if (const auto* tab = tabs_.ActiveTab()) {
|
||||||
|
SendChromeState(*tab);
|
||||||
|
}
|
||||||
|
ResizeBrowsers();
|
||||||
|
}
|
||||||
|
|
||||||
|
nebula::window::BrowserLayout NebulaController::CurrentBrowserLayout() const {
|
||||||
|
if (!window_) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!big_picture_mode_) {
|
||||||
|
return window_->CurrentLayout(!content_fullscreen_);
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto client_size = nebula::platform::ParentClientSize(window_->native_handle());
|
||||||
|
if (!big_picture_browse_visible_) {
|
||||||
|
nebula::window::BrowserLayout layout;
|
||||||
|
layout.chrome = {0, 0, client_size.first, client_size.second};
|
||||||
|
layout.content = {};
|
||||||
|
return layout;
|
||||||
|
}
|
||||||
|
|
||||||
|
nebula::window::BrowserLayout layout;
|
||||||
|
layout.chrome = {0, 0, client_size.first, client_size.second};
|
||||||
|
|
||||||
|
// left_margin must clear the 220px sidebar; right_margin must clear the
|
||||||
|
// native-browser-panel (clamp(168,18vw,260) + 20px right inset ≈ 280px max).
|
||||||
|
// top/bottom margins must clear the header (~68px) and footer (~56px).
|
||||||
|
const int left_margin = nebula::platform::ScaleForParentWindow(window_->native_handle(), 224);
|
||||||
|
const int right_margin = nebula::platform::ScaleForParentWindow(window_->native_handle(), 284);
|
||||||
|
const int top_margin = nebula::platform::ScaleForParentWindow(window_->native_handle(), 68);
|
||||||
|
const int bottom_margin = nebula::platform::ScaleForParentWindow(window_->native_handle(), 56);
|
||||||
|
const int available_width = std::max(0, client_size.first - left_margin - right_margin);
|
||||||
|
const int available_height = std::max(0, client_size.second - top_margin - bottom_margin);
|
||||||
|
|
||||||
|
layout.content = {
|
||||||
|
left_margin,
|
||||||
|
top_margin,
|
||||||
|
available_width,
|
||||||
|
available_height};
|
||||||
|
return layout;
|
||||||
|
}
|
||||||
|
|
||||||
void NebulaController::ToggleMenuPopup() {
|
void NebulaController::ToggleMenuPopup() {
|
||||||
|
if (big_picture_mode_) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (menu_popup_browser_ && menu_popup_visible_) {
|
if (menu_popup_browser_ && menu_popup_visible_) {
|
||||||
CloseMenuPopup();
|
CloseMenuPopup();
|
||||||
return;
|
return;
|
||||||
@@ -507,7 +721,7 @@ void NebulaController::CloseMenuPopup() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void NebulaController::CreateMenuPopupBrowser() {
|
void NebulaController::CreateMenuPopupBrowser() {
|
||||||
if (!window_ || !window_->native_handle() || content_fullscreen_) {
|
if (!window_ || !window_->native_handle() || content_fullscreen_ || big_picture_mode_) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -591,6 +805,110 @@ void NebulaController::FreshReload() {
|
|||||||
tabs_.LoadURL(WithCacheBuster(tab->url));
|
tabs_.LoadURL(WithCacheBuster(tab->url));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void NebulaController::SendBigPictureMouseMove(const std::string& payload) {
|
||||||
|
auto* tab = tabs_.ActiveTab();
|
||||||
|
if (!tab || !tab->browser) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int x = 0;
|
||||||
|
int y = 0;
|
||||||
|
if (!ParseTwoInts(payload, x, y)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
CefMouseEvent event = {};
|
||||||
|
event.x = x;
|
||||||
|
event.y = y;
|
||||||
|
nebula::platform::MoveCursorToBrowserPoint(tab->browser->GetHost()->GetWindowHandle(), x, y);
|
||||||
|
tab->browser->GetHost()->SendMouseMoveEvent(event, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void NebulaController::SendBigPictureMouseClick(const std::string& payload, bool right_click) {
|
||||||
|
auto* tab = tabs_.ActiveTab();
|
||||||
|
if (!tab || !tab->browser) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int x = 0;
|
||||||
|
int y = 0;
|
||||||
|
if (!ParseTwoInts(payload, x, y)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
CefMouseEvent event = {};
|
||||||
|
event.x = x;
|
||||||
|
event.y = y;
|
||||||
|
const auto button = right_click ? MBT_RIGHT : MBT_LEFT;
|
||||||
|
nebula::platform::MoveCursorToBrowserPoint(tab->browser->GetHost()->GetWindowHandle(), x, y);
|
||||||
|
tab->browser->GetHost()->SendMouseMoveEvent(event, false);
|
||||||
|
tab->browser->GetHost()->SendMouseClickEvent(event, button, false, 1);
|
||||||
|
tab->browser->GetHost()->SendMouseClickEvent(event, button, true, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void NebulaController::SendBigPictureMouseWheel(const std::string& payload) {
|
||||||
|
auto* tab = tabs_.ActiveTab();
|
||||||
|
if (!tab || !tab->browser) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int delta_x = 0;
|
||||||
|
int delta_y = 0;
|
||||||
|
int x = 0;
|
||||||
|
int y = 0;
|
||||||
|
const bool has_pointer = ParseFourInts(payload, delta_x, delta_y, x, y);
|
||||||
|
if (!has_pointer && !ParseTwoInts(payload, delta_x, delta_y)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto layout = CurrentBrowserLayout();
|
||||||
|
CefMouseEvent event = {};
|
||||||
|
event.x = has_pointer ? std::clamp(x, 0, std::max(0, layout.content.width - 1))
|
||||||
|
: std::max(0, layout.content.width / 2);
|
||||||
|
event.y = has_pointer ? std::clamp(y, 0, std::max(0, layout.content.height - 1))
|
||||||
|
: std::max(0, layout.content.height / 2);
|
||||||
|
tab->browser->GetHost()->SendMouseWheelEvent(event, delta_x, delta_y);
|
||||||
|
}
|
||||||
|
|
||||||
|
void NebulaController::SendBigPictureText(const std::string& payload) {
|
||||||
|
auto* tab = tabs_.ActiveTab();
|
||||||
|
if (!tab || !tab->browser) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string script =
|
||||||
|
"(function(){"
|
||||||
|
"const el=document.activeElement;"
|
||||||
|
"if(!el)return;"
|
||||||
|
"const value=\"" + nebula::browser::JsonEscape(payload) + "\";"
|
||||||
|
"const editable=el.tagName==='INPUT'||el.tagName==='TEXTAREA'||el.isContentEditable;"
|
||||||
|
"if(!editable)return;"
|
||||||
|
"if(el.isContentEditable){el.textContent=value;}else{el.value=value;}"
|
||||||
|
"el.dispatchEvent(new Event('input',{bubbles:true}));"
|
||||||
|
"el.dispatchEvent(new Event('change',{bubbles:true}));"
|
||||||
|
"el.dispatchEvent(new KeyboardEvent('keydown',{key:'Enter',keyCode:13,bubbles:true}));"
|
||||||
|
"el.dispatchEvent(new KeyboardEvent('keyup',{key:'Enter',keyCode:13,bubbles:true}));"
|
||||||
|
"})();";
|
||||||
|
tab->browser->GetMainFrame()->ExecuteJavaScript(script, tab->url, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void NebulaController::SetBigPictureBrowseVisible(bool visible) {
|
||||||
|
if (big_picture_browse_visible_ == visible) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
big_picture_browse_visible_ = visible;
|
||||||
|
if (visible) {
|
||||||
|
if (auto* tab = tabs_.ActiveTab()) {
|
||||||
|
InjectBigPictureCursor(tab->browser);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ResizeBrowsers();
|
||||||
|
if (const auto* tab = tabs_.ActiveTab()) {
|
||||||
|
SendBigPictureState(*tab);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void NebulaController::SetContentFullscreen(bool fullscreen) {
|
void NebulaController::SetContentFullscreen(bool fullscreen) {
|
||||||
if (content_fullscreen_ == fullscreen) {
|
if (content_fullscreen_ == fullscreen) {
|
||||||
return;
|
return;
|
||||||
@@ -599,6 +917,7 @@ void NebulaController::SetContentFullscreen(bool fullscreen) {
|
|||||||
content_fullscreen_ = fullscreen;
|
content_fullscreen_ = fullscreen;
|
||||||
if (fullscreen) {
|
if (fullscreen) {
|
||||||
CloseMenuPopup();
|
CloseMenuPopup();
|
||||||
|
ExitBigPictureMode();
|
||||||
}
|
}
|
||||||
|
|
||||||
SetBrowserVisible(chrome_browser_, !fullscreen);
|
SetBrowserVisible(chrome_browser_, !fullscreen);
|
||||||
@@ -613,18 +932,24 @@ void NebulaController::ResizeBrowsers() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto layout = window_->CurrentLayout(!content_fullscreen_);
|
const auto layout = CurrentBrowserLayout();
|
||||||
if (chrome_browser_) {
|
if (chrome_browser_) {
|
||||||
window_->ResizeChild(
|
window_->ResizeChild(
|
||||||
chrome_browser_->GetHost()->GetWindowHandle(),
|
chrome_browser_->GetHost()->GetWindowHandle(),
|
||||||
layout.chrome);
|
layout.chrome);
|
||||||
}
|
}
|
||||||
|
if (big_picture_browser_) {
|
||||||
|
window_->ResizeChild(
|
||||||
|
big_picture_browser_->GetHost()->GetWindowHandle(),
|
||||||
|
layout.chrome);
|
||||||
|
}
|
||||||
if (const auto* tab = tabs_.ActiveTab(); tab && tab->browser) {
|
if (const auto* tab = tabs_.ActiveTab(); tab && tab->browser) {
|
||||||
|
SetBrowserVisible(tab->browser, !big_picture_mode_ || big_picture_browse_visible_);
|
||||||
window_->ResizeChild(
|
window_->ResizeChild(
|
||||||
tab->browser->GetHost()->GetWindowHandle(),
|
tab->browser->GetHost()->GetWindowHandle(),
|
||||||
layout.content);
|
layout.content);
|
||||||
}
|
}
|
||||||
if (!content_fullscreen_) {
|
if (!content_fullscreen_ && !big_picture_mode_) {
|
||||||
PositionMenuPopup();
|
PositionMenuPopup();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -672,6 +997,71 @@ void NebulaController::SendChromeState(const nebula::browser::NebulaTab& tab) {
|
|||||||
chrome_browser_->GetMainFrame()->ExecuteJavaScript(script, nebula::ui::GetChromeUrl(), 0);
|
chrome_browser_->GetMainFrame()->ExecuteJavaScript(script, nebula::ui::GetChromeUrl(), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void NebulaController::SendBigPictureState(const nebula::browser::NebulaTab& tab) {
|
||||||
|
if (!big_picture_browser_) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 = "[";
|
||||||
|
const auto& tabs = tabs_.Tabs();
|
||||||
|
for (size_t i = 0; i < tabs.size(); ++i) {
|
||||||
|
const auto& item = tabs[i];
|
||||||
|
if (i > 0) {
|
||||||
|
tabs_json += ",";
|
||||||
|
}
|
||||||
|
tabs_json +=
|
||||||
|
"{\"id\":" + std::to_string(item.id) +
|
||||||
|
",\"title\":\"" + nebula::browser::JsonEscape(item.title) + "\"" +
|
||||||
|
",\"url\":\"" + nebula::browser::JsonEscape(item.url) + "\"" +
|
||||||
|
",\"isLoading\":" + std::string(item.is_loading ? "true" : "false") +
|
||||||
|
",\"favicon\":\"" + nebula::browser::JsonEscape(item.favicon_url) + "\"" +
|
||||||
|
"}";
|
||||||
|
}
|
||||||
|
tabs_json += "]";
|
||||||
|
|
||||||
|
std::string history_json = "[";
|
||||||
|
for (size_t i = 0; i < site_history_.size(); ++i) {
|
||||||
|
if (i > 0) {
|
||||||
|
history_json += ",";
|
||||||
|
}
|
||||||
|
history_json += "\"" + nebula::browser::JsonEscape(site_history_[i]) + "\"";
|
||||||
|
}
|
||||||
|
history_json += "]";
|
||||||
|
|
||||||
|
const auto layout = CurrentBrowserLayout();
|
||||||
|
const std::string script =
|
||||||
|
"window.NebulaBigPicture && window.NebulaBigPicture.applyState({"
|
||||||
|
"\"id\":" + std::to_string(tab.id) +
|
||||||
|
",\"url\":\"" + nebula::browser::JsonEscape(display_url) + "\""
|
||||||
|
",\"title\":\"" + nebula::browser::JsonEscape(tab.title) + "\""
|
||||||
|
",\"isLoading\":" + std::string(tab.is_loading ? "true" : "false") +
|
||||||
|
",\"progress\":" + std::to_string(tab.load_progress) +
|
||||||
|
",\"canGoBack\":" + std::string(tab.CanGoBack() ? "true" : "false") +
|
||||||
|
",\"canGoForward\":" + std::string(tab.CanGoForward() ? "true" : "false") +
|
||||||
|
",\"favicon\":\"" + nebula::browser::JsonEscape(tab.favicon_url) + "\"" +
|
||||||
|
",\"zoomLevel\":" + std::to_string(zoom_level) +
|
||||||
|
",\"browserLayout\":{"
|
||||||
|
"\"x\":" + std::to_string(layout.content.x) +
|
||||||
|
",\"y\":" + std::to_string(layout.content.y) +
|
||||||
|
",\"width\":" + std::to_string(layout.content.width) +
|
||||||
|
",\"height\":" + std::to_string(layout.content.height) +
|
||||||
|
"}" +
|
||||||
|
",\"tabs\":" + tabs_json +
|
||||||
|
",\"history\":" + history_json +
|
||||||
|
"});";
|
||||||
|
|
||||||
|
big_picture_browser_->GetMainFrame()->ExecuteJavaScript(
|
||||||
|
script,
|
||||||
|
nebula::ui::GetBigPictureUrl(),
|
||||||
|
0);
|
||||||
|
}
|
||||||
|
|
||||||
void NebulaController::RecordSiteHistory(const std::string& url) {
|
void NebulaController::RecordSiteHistory(const std::string& url) {
|
||||||
if (!IsSiteHistoryUrl(url)) {
|
if (!IsSiteHistoryUrl(url)) {
|
||||||
return;
|
return;
|
||||||
@@ -699,6 +1089,44 @@ void NebulaController::InjectSettingsHistory(CefRefPtr<CefBrowser> browser) {
|
|||||||
browser->GetMainFrame()->ExecuteJavaScript(script, nebula::ui::GetSettingsUrl(), 0);
|
browser->GetMainFrame()->ExecuteJavaScript(script, nebula::ui::GetSettingsUrl(), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void NebulaController::InjectBigPictureCursor(CefRefPtr<CefBrowser> browser) {
|
||||||
|
if (!big_picture_mode_ || !browser) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
static constexpr char kScript[] = R"JS(
|
||||||
|
(function(){
|
||||||
|
const id = 'nebula-bigpicture-custom-cursor';
|
||||||
|
const cursor = 'url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2232%22%20height%3D%2232%22%20viewBox%3D%220%200%2032%2032%22%3E%3Cpath%20d%3D%22M5%203v24l6.6-6.4%204.1%208.5%204.3-2.1-4.1-8.3H25L5%203z%22%20fill%3D%22%2300C6FF%22%20stroke%3D%22%23FFFFFF%22%20stroke-width%3D%222.2%22%20stroke-linejoin%3D%22round%22%2F%3E%3Cpath%20d%3D%22M9%207.7v9.8l2.5-2.4%202%204.1%201.5-.7-2-4h4L9%207.7z%22%20fill%3D%22%237B2EFF%22%20opacity%3D%220.8%22%2F%3E%3C%2Fsvg%3E") 5 3, auto';
|
||||||
|
const css = 'html, body, body * { cursor: ' + cursor + ' !important; }';
|
||||||
|
let style = document.getElementById(id);
|
||||||
|
if (!style) {
|
||||||
|
style = document.createElement('style');
|
||||||
|
style.id = id;
|
||||||
|
(document.head || document.documentElement).appendChild(style);
|
||||||
|
}
|
||||||
|
style.textContent = css;
|
||||||
|
})();
|
||||||
|
)JS";
|
||||||
|
browser->GetMainFrame()->ExecuteJavaScript(kScript, browser->GetMainFrame()->GetURL(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void NebulaController::RemoveBigPictureCursor(CefRefPtr<CefBrowser> browser) {
|
||||||
|
if (!browser) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
static constexpr char kScript[] = R"JS(
|
||||||
|
(function(){
|
||||||
|
const style = document.getElementById('nebula-bigpicture-custom-cursor');
|
||||||
|
if (style) {
|
||||||
|
style.remove();
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
)JS";
|
||||||
|
browser->GetMainFrame()->ExecuteJavaScript(kScript, browser->GetMainFrame()->GetURL(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
void NebulaController::PersistSession() const {
|
void NebulaController::PersistSession() const {
|
||||||
nebula::browser::SaveSessionState(tabs_.Tabs(), tabs_.ActiveTabIndex());
|
nebula::browser::SaveSessionState(tabs_.Tabs(), tabs_.ActiveTabIndex());
|
||||||
}
|
}
|
||||||
@@ -708,7 +1136,7 @@ void NebulaController::MaybeFinishShutdown() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (chrome_browser_ || menu_popup_browser_ || tabs_.HasOpenBrowsers()) {
|
if (chrome_browser_ || big_picture_browser_ || menu_popup_browser_ || tabs_.HasOpenBrowsers()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
#include <unordered_set>
|
#include <unordered_set>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
#include "app/run.h"
|
||||||
#include "browser/tab_manager.h"
|
#include "browser/tab_manager.h"
|
||||||
#include "cef/browser_client.h"
|
#include "cef/browser_client.h"
|
||||||
#include "platform/types.h"
|
#include "platform/types.h"
|
||||||
@@ -16,7 +17,9 @@ class NebulaController final : public nebula::window::WindowDelegate,
|
|||||||
public nebula::browser::TabObserver,
|
public nebula::browser::TabObserver,
|
||||||
public nebula::cef::BrowserClientDelegate {
|
public nebula::cef::BrowserClientDelegate {
|
||||||
public:
|
public:
|
||||||
NebulaController(nebula::platform::AppStartup startup, std::string initial_url);
|
NebulaController(nebula::platform::AppStartup startup,
|
||||||
|
std::string initial_url,
|
||||||
|
LaunchOptions launch_options = {});
|
||||||
~NebulaController() override;
|
~NebulaController() override;
|
||||||
|
|
||||||
bool Create();
|
bool Create();
|
||||||
@@ -45,7 +48,11 @@ private:
|
|||||||
void ActivateTab(int tab_id);
|
void ActivateTab(int tab_id);
|
||||||
void CloseTab(int tab_id);
|
void CloseTab(int tab_id);
|
||||||
void CreateChromeBrowser();
|
void CreateChromeBrowser();
|
||||||
|
void CreateBigPictureBrowser();
|
||||||
void CreateContentBrowser();
|
void CreateContentBrowser();
|
||||||
|
void EnterBigPictureMode();
|
||||||
|
void ExitBigPictureMode();
|
||||||
|
nebula::window::BrowserLayout CurrentBrowserLayout() const;
|
||||||
void ToggleMenuPopup();
|
void ToggleMenuPopup();
|
||||||
void CloseMenuPopup();
|
void CloseMenuPopup();
|
||||||
void CreateMenuPopupBrowser();
|
void CreateMenuPopupBrowser();
|
||||||
@@ -54,27 +61,41 @@ private:
|
|||||||
void ToggleDevTools();
|
void ToggleDevTools();
|
||||||
void AdjustZoom(double delta);
|
void AdjustZoom(double delta);
|
||||||
void FreshReload();
|
void FreshReload();
|
||||||
|
void SendBigPictureMouseMove(const std::string& payload);
|
||||||
|
void SendBigPictureMouseClick(const std::string& payload, bool right_click);
|
||||||
|
void SendBigPictureMouseWheel(const std::string& payload);
|
||||||
|
void SendBigPictureText(const std::string& payload);
|
||||||
|
void SetBigPictureBrowseVisible(bool visible);
|
||||||
void SetContentFullscreen(bool fullscreen);
|
void SetContentFullscreen(bool fullscreen);
|
||||||
void ResizeBrowsers();
|
void ResizeBrowsers();
|
||||||
void SendChromeState(const nebula::browser::NebulaTab& tab);
|
void SendChromeState(const nebula::browser::NebulaTab& tab);
|
||||||
|
void SendBigPictureState(const nebula::browser::NebulaTab& tab);
|
||||||
void RecordSiteHistory(const std::string& url);
|
void RecordSiteHistory(const std::string& url);
|
||||||
void InjectSettingsHistory(CefRefPtr<CefBrowser> browser);
|
void InjectSettingsHistory(CefRefPtr<CefBrowser> browser);
|
||||||
|
void InjectBigPictureCursor(CefRefPtr<CefBrowser> browser);
|
||||||
|
void RemoveBigPictureCursor(CefRefPtr<CefBrowser> browser);
|
||||||
void PersistSession() const;
|
void PersistSession() const;
|
||||||
void MaybeFinishShutdown();
|
void MaybeFinishShutdown();
|
||||||
bool ForgetClosingTabBrowser(CefRefPtr<CefBrowser> browser);
|
bool ForgetClosingTabBrowser(CefRefPtr<CefBrowser> browser);
|
||||||
|
|
||||||
nebula::platform::AppStartup startup_;
|
nebula::platform::AppStartup startup_;
|
||||||
std::string initial_url_;
|
std::string initial_url_;
|
||||||
|
LaunchOptions launch_options_;
|
||||||
bool closing_ = false;
|
bool closing_ = false;
|
||||||
bool chrome_ready_ = false;
|
bool chrome_ready_ = false;
|
||||||
|
bool big_picture_ready_ = false;
|
||||||
|
bool big_picture_mode_ = false;
|
||||||
|
bool big_picture_browse_visible_ = false;
|
||||||
bool content_fullscreen_ = false;
|
bool content_fullscreen_ = false;
|
||||||
bool menu_popup_visible_ = 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_;
|
||||||
CefRefPtr<CefBrowser> chrome_browser_;
|
CefRefPtr<CefBrowser> chrome_browser_;
|
||||||
|
CefRefPtr<CefBrowser> big_picture_browser_;
|
||||||
CefRefPtr<CefBrowser> menu_popup_browser_;
|
CefRefPtr<CefBrowser> menu_popup_browser_;
|
||||||
CefRefPtr<nebula::cef::NebulaBrowserClient> chrome_client_;
|
CefRefPtr<nebula::cef::NebulaBrowserClient> chrome_client_;
|
||||||
|
CefRefPtr<nebula::cef::NebulaBrowserClient> big_picture_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::vector<CefRefPtr<CefBrowser>> closing_tab_browsers_;
|
||||||
|
|||||||
+2
-2
@@ -9,7 +9,7 @@
|
|||||||
|
|
||||||
namespace nebula::app {
|
namespace nebula::app {
|
||||||
|
|
||||||
int RunNebula(const nebula::platform::AppStartup& startup) {
|
int RunNebula(const nebula::platform::AppStartup& startup, LaunchOptions options) {
|
||||||
nebula::platform::PrepareApp();
|
nebula::platform::PrepareApp();
|
||||||
|
|
||||||
const CefMainArgs main_args = nebula::platform::MakeMainArgs(startup);
|
const CefMainArgs main_args = nebula::platform::MakeMainArgs(startup);
|
||||||
@@ -41,7 +41,7 @@ int RunNebula(const nebula::platform::AppStartup& startup) {
|
|||||||
initial_url = nebula::ui::GetHomeUrl();
|
initial_url = nebula::ui::GetHomeUrl();
|
||||||
}
|
}
|
||||||
|
|
||||||
NebulaController controller(startup, std::move(initial_url));
|
NebulaController controller(startup, std::move(initial_url), options);
|
||||||
const bool created = controller.Create();
|
const bool created = controller.Create();
|
||||||
if (created) {
|
if (created) {
|
||||||
CefRunMessageLoop();
|
CefRunMessageLoop();
|
||||||
|
|||||||
+10
-1
@@ -4,6 +4,15 @@
|
|||||||
|
|
||||||
namespace nebula::app {
|
namespace nebula::app {
|
||||||
|
|
||||||
int RunNebula(const nebula::platform::AppStartup& startup);
|
enum class AppMode {
|
||||||
|
Desktop,
|
||||||
|
BigPicture,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct LaunchOptions {
|
||||||
|
AppMode mode = AppMode::Desktop;
|
||||||
|
};
|
||||||
|
|
||||||
|
int RunNebula(const nebula::platform::AppStartup& startup, LaunchOptions options = {});
|
||||||
|
|
||||||
} // namespace nebula::app
|
} // namespace nebula::app
|
||||||
|
|||||||
@@ -35,6 +35,33 @@ bool IsBigPictureFrame(CefRefPtr<CefFrame> frame) {
|
|||||||
url.starts_with("nebula://big-picture");
|
url.starts_with("nebula://big-picture");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool IsBigPictureCommand(const std::string& command) {
|
||||||
|
return command == "navigate" ||
|
||||||
|
command == "new-tab" ||
|
||||||
|
command == "activate-tab" ||
|
||||||
|
command == "close-tab" ||
|
||||||
|
command == "back" ||
|
||||||
|
command == "forward" ||
|
||||||
|
command == "reload" ||
|
||||||
|
command == "stop" ||
|
||||||
|
command == "home" ||
|
||||||
|
command == "settings" ||
|
||||||
|
command == "open-settings" ||
|
||||||
|
command == "big-picture" ||
|
||||||
|
command == "exit-bigpicture" ||
|
||||||
|
command == "gpu-diagnostics" ||
|
||||||
|
command == "zoom-out" ||
|
||||||
|
command == "zoom-in" ||
|
||||||
|
command == "clear-site-history" ||
|
||||||
|
command == "clear-search-history" ||
|
||||||
|
command == "bigpicture-mouse-move" ||
|
||||||
|
command == "bigpicture-click" ||
|
||||||
|
command == "bigpicture-right-click" ||
|
||||||
|
command == "bigpicture-scroll" ||
|
||||||
|
command == "bigpicture-text" ||
|
||||||
|
command == "bigpicture-browse-visible";
|
||||||
|
}
|
||||||
|
|
||||||
std::vector<std::string> ToStringVector(const std::vector<CefString>& values) {
|
std::vector<std::string> ToStringVector(const std::vector<CefString>& values) {
|
||||||
std::vector<std::string> result;
|
std::vector<std::string> result;
|
||||||
result.reserve(values.size());
|
result.reserve(values.size());
|
||||||
@@ -62,7 +89,7 @@ bool NebulaBrowserClient::OnProcessMessageReceived(CefRefPtr<CefBrowser> browser
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (role_ != BrowserRole::Chrome && role_ != BrowserRole::Content &&
|
if (role_ != BrowserRole::Chrome && role_ != BrowserRole::Content &&
|
||||||
role_ != BrowserRole::MenuPopup) {
|
role_ != BrowserRole::BigPicture && role_ != BrowserRole::MenuPopup) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -83,6 +110,10 @@ bool NebulaBrowserClient::OnProcessMessageReceived(CefRefPtr<CefBrowser> browser
|
|||||||
!allowed_big_picture_command) {
|
!allowed_big_picture_command) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
} else if (role_ == BrowserRole::BigPicture) {
|
||||||
|
if (!IsBigPictureFrame(frame) || !IsBigPictureCommand(command)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (delegate_ && !command.empty()) {
|
if (delegate_ && !command.empty()) {
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ namespace nebula::cef {
|
|||||||
enum class BrowserRole {
|
enum class BrowserRole {
|
||||||
Chrome,
|
Chrome,
|
||||||
Content,
|
Content,
|
||||||
|
BigPicture,
|
||||||
MenuPopup,
|
MenuPopup,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ CefWindowInfo MakeDevToolsPopup(NativeWindow parent, const char* title);
|
|||||||
void ResizeBrowserWindow(NativeWindow browser_window, const Rect& rect);
|
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);
|
||||||
|
void MoveCursorToBrowserPoint(NativeWindow browser_window, int x, int y);
|
||||||
Rect MenuPopupRect(NativeWindow parent, const BrowserLayout& layout);
|
Rect MenuPopupRect(NativeWindow parent, const BrowserLayout& layout);
|
||||||
std::string CacheBusterToken();
|
std::string CacheBusterToken();
|
||||||
void DestroyTopLevelWindow(NativeWindow window);
|
void DestroyTopLevelWindow(NativeWindow window);
|
||||||
|
|||||||
@@ -34,6 +34,12 @@ void RaiseBrowserWindow(NativeWindow browser_window) {
|
|||||||
UNREFERENCED_PARAMETER(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) {
|
int ScaleForParentWindow(NativeWindow parent, int value) {
|
||||||
UNREFERENCED_PARAMETER(parent);
|
UNREFERENCED_PARAMETER(parent);
|
||||||
return value;
|
return value;
|
||||||
|
|||||||
@@ -32,6 +32,12 @@ void RaiseBrowserWindow(NativeWindow browser_window) {
|
|||||||
UNREFERENCED_PARAMETER(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) {
|
int ScaleForParentWindow(NativeWindow parent, int value) {
|
||||||
UNREFERENCED_PARAMETER(parent);
|
UNREFERENCED_PARAMETER(parent);
|
||||||
return value;
|
return value;
|
||||||
|
|||||||
@@ -75,6 +75,20 @@ void RaiseBrowserWindow(NativeWindow browser_window) {
|
|||||||
SetWindowPos(hwnd, HWND_TOP, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
|
SetWindowPos(hwnd, HWND_TOP, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MoveCursorToBrowserPoint(NativeWindow browser_window, int x, int y) {
|
||||||
|
const HWND hwnd = AsHwnd(browser_window);
|
||||||
|
if (!hwnd) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
POINT point = {x, y};
|
||||||
|
if (!ClientToScreen(hwnd, &point)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SetCursorPos(point.x, point.y);
|
||||||
|
}
|
||||||
|
|
||||||
int ScaleForParentWindow(NativeWindow parent, int value) {
|
int ScaleForParentWindow(NativeWindow parent, int value) {
|
||||||
const HWND hwnd = AsHwnd(parent);
|
const HWND hwnd = AsHwnd(parent);
|
||||||
if (!hwnd) {
|
if (!hwnd) {
|
||||||
|
|||||||
+237
-1
@@ -132,6 +132,83 @@ body.mouse-active {
|
|||||||
left: -100px;
|
left: -100px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.browser-stage-frame {
|
||||||
|
position: fixed;
|
||||||
|
left: var(--browser-stage-x, 20%);
|
||||||
|
top: var(--browser-stage-y, 10%);
|
||||||
|
width: var(--browser-stage-width, 60%);
|
||||||
|
height: var(--browser-stage-height, 80%);
|
||||||
|
z-index: 3;
|
||||||
|
pointer-events: none;
|
||||||
|
border-radius: var(--bp-radius-xl);
|
||||||
|
box-shadow:
|
||||||
|
0 0 0 2px rgba(255, 255, 255, 0.08),
|
||||||
|
0 0 0 6px rgba(123, 46, 255, 0.18),
|
||||||
|
0 24px 80px rgba(0, 0, 0, 0.55),
|
||||||
|
0 0 80px var(--bp-primary-glow);
|
||||||
|
opacity: 1;
|
||||||
|
transition: opacity var(--bp-transition-normal), box-shadow var(--bp-transition-normal);
|
||||||
|
}
|
||||||
|
|
||||||
|
.browser-stage-frame.hidden {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.virtual-cursor {
|
||||||
|
position: fixed;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
z-index: 250;
|
||||||
|
width: 34px;
|
||||||
|
height: 34px;
|
||||||
|
pointer-events: none;
|
||||||
|
transform: translate3d(var(--virtual-cursor-x, 50vw), var(--virtual-cursor-y, 50vh), 0);
|
||||||
|
transition: opacity var(--bp-transition-fast);
|
||||||
|
filter: drop-shadow(0 8px 18px rgba(0, 0, 0, 0.55));
|
||||||
|
}
|
||||||
|
|
||||||
|
.virtual-cursor.hidden {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.virtual-cursor::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
left: 4px;
|
||||||
|
top: 3px;
|
||||||
|
width: 18px;
|
||||||
|
height: 24px;
|
||||||
|
background: linear-gradient(135deg, var(--bp-text), var(--bp-accent));
|
||||||
|
clip-path: polygon(0 0, 0 100%, 38% 76%, 60% 100%, 84% 86%, 62% 62%, 100% 62%);
|
||||||
|
box-shadow: 0 0 0 2px rgba(0, 0, 0, 0.45), 0 0 20px var(--bp-accent-glow);
|
||||||
|
}
|
||||||
|
|
||||||
|
.virtual-cursor-dot {
|
||||||
|
position: absolute;
|
||||||
|
left: 10px;
|
||||||
|
top: 10px;
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
border-radius: 999px;
|
||||||
|
background: var(--bp-primary);
|
||||||
|
opacity: 0;
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.virtual-cursor.clicking .virtual-cursor-dot {
|
||||||
|
animation: virtual-cursor-click 180ms ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.virtual-cursor.right-clicking .virtual-cursor-dot {
|
||||||
|
background: var(--bp-warning);
|
||||||
|
animation: virtual-cursor-click 180ms ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes virtual-cursor-click {
|
||||||
|
0% { opacity: 0.95; transform: scale(0.8); }
|
||||||
|
100% { opacity: 0; transform: scale(4.4); }
|
||||||
|
}
|
||||||
|
|
||||||
@keyframes glow-pulse {
|
@keyframes glow-pulse {
|
||||||
0% { transform: scale(1); opacity: 0.3; }
|
0% { transform: scale(1); opacity: 0.3; }
|
||||||
100% { transform: scale(1.2); opacity: 0.5; }
|
100% { transform: scale(1.2); opacity: 0.5; }
|
||||||
@@ -220,6 +297,15 @@ body.mouse-active {
|
|||||||
color: var(--bp-text-muted);
|
color: var(--bp-text-muted);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.status-icon.controller-status.connected {
|
||||||
|
color: var(--bp-success);
|
||||||
|
box-shadow: 0 0 20px rgba(74, 222, 128, 0.25);
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-icon.controller-status.disconnected {
|
||||||
|
color: var(--bp-text-dim);
|
||||||
|
}
|
||||||
|
|
||||||
.status-icon .material-symbols-outlined {
|
.status-icon .material-symbols-outlined {
|
||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
}
|
}
|
||||||
@@ -338,8 +424,9 @@ body.mouse-active {
|
|||||||
inset: 0;
|
inset: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
background: var(--bp-bg);
|
background: transparent;
|
||||||
z-index: 2;
|
z-index: 2;
|
||||||
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.webview-container.hidden {
|
.webview-container.hidden {
|
||||||
@@ -353,6 +440,155 @@ body.mouse-active {
|
|||||||
border: none;
|
border: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.native-browser-panel {
|
||||||
|
position: absolute;
|
||||||
|
top: var(--bp-spacing-md);
|
||||||
|
right: var(--bp-spacing-md);
|
||||||
|
bottom: var(--bp-spacing-md);
|
||||||
|
width: clamp(168px, 18vw, 260px);
|
||||||
|
overflow-y: auto;
|
||||||
|
padding: var(--bp-spacing-sm);
|
||||||
|
background: linear-gradient(180deg, rgba(10,10,15,0.88) 0%, rgba(20,20,31,0.82) 100%);
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||||
|
border-radius: var(--bp-radius-lg);
|
||||||
|
box-shadow: 0 18px 60px rgba(0, 0, 0, 0.35);
|
||||||
|
backdrop-filter: blur(20px);
|
||||||
|
pointer-events: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.native-page-card {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: auto minmax(0, 1fr);
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--bp-spacing-sm);
|
||||||
|
padding: var(--bp-spacing-sm);
|
||||||
|
margin-bottom: var(--bp-spacing-md);
|
||||||
|
background: rgba(20, 20, 31, 0.78);
|
||||||
|
border: 2px solid var(--bp-border);
|
||||||
|
border-radius: var(--bp-radius-lg);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.native-page-card:focus,
|
||||||
|
.native-page-card.focused {
|
||||||
|
outline: none;
|
||||||
|
border-color: var(--bp-accent);
|
||||||
|
box-shadow: var(--bp-focus-ring-accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.native-page-card .material-symbols-outlined {
|
||||||
|
font-size: 28px;
|
||||||
|
color: var(--bp-accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.native-page-card h2 {
|
||||||
|
font-size: 0.95rem;
|
||||||
|
color: var(--bp-text);
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
.native-page-card p {
|
||||||
|
font-size: 0.72rem;
|
||||||
|
color: var(--bp-text-muted);
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
.native-page-card .controller-note {
|
||||||
|
grid-column: 1 / -1;
|
||||||
|
margin-top: 4px;
|
||||||
|
color: var(--bp-accent);
|
||||||
|
white-space: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.browser-actions {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
gap: var(--bp-spacing-xs);
|
||||||
|
margin-bottom: var(--bp-spacing-lg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-button:disabled {
|
||||||
|
opacity: 0.45;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--bp-spacing-xs);
|
||||||
|
}
|
||||||
|
|
||||||
|
.bp-tab {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: auto minmax(0, 1fr) auto;
|
||||||
|
grid-template-areas:
|
||||||
|
"dot title close"
|
||||||
|
"dot url close";
|
||||||
|
align-items: center;
|
||||||
|
column-gap: var(--bp-spacing-sm);
|
||||||
|
padding: var(--bp-spacing-sm);
|
||||||
|
background: var(--bp-surface);
|
||||||
|
border: 2px solid var(--bp-border);
|
||||||
|
border-radius: var(--bp-radius-md);
|
||||||
|
color: var(--bp-text);
|
||||||
|
cursor: pointer;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bp-tab.active {
|
||||||
|
border-color: var(--bp-primary);
|
||||||
|
background: var(--bp-surface-active);
|
||||||
|
}
|
||||||
|
|
||||||
|
.bp-tab:focus,
|
||||||
|
.bp-tab.focused {
|
||||||
|
outline: none;
|
||||||
|
border-color: var(--bp-accent);
|
||||||
|
box-shadow: var(--bp-focus-ring-accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-dot {
|
||||||
|
grid-area: dot;
|
||||||
|
color: var(--bp-accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-text {
|
||||||
|
grid-area: title;
|
||||||
|
font-weight: 600;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-url {
|
||||||
|
grid-area: url;
|
||||||
|
color: var(--bp-text-muted);
|
||||||
|
font-size: 0.8rem;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-close-inline {
|
||||||
|
grid-area: close;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 28px;
|
||||||
|
height: 28px;
|
||||||
|
border-radius: var(--bp-radius-sm);
|
||||||
|
color: var(--bp-text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-close-inline:hover {
|
||||||
|
background: var(--bp-danger);
|
||||||
|
color: var(--bp-text);
|
||||||
|
}
|
||||||
|
|
||||||
/* Content area */
|
/* Content area */
|
||||||
.bp-content {
|
.bp-content {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
|||||||
+817
-2797
File diff suppressed because it is too large
Load Diff
@@ -26,6 +26,10 @@
|
|||||||
<div class="bg-particles"></div>
|
<div class="bg-particles"></div>
|
||||||
<div class="bg-glow"></div>
|
<div class="bg-glow"></div>
|
||||||
</div>
|
</div>
|
||||||
|
<div id="browser-stage-frame" class="browser-stage-frame hidden" aria-hidden="true"></div>
|
||||||
|
<div id="virtual-cursor" class="virtual-cursor hidden" aria-hidden="true">
|
||||||
|
<div class="virtual-cursor-dot"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Top header bar -->
|
<!-- Top header bar -->
|
||||||
<header class="bp-header">
|
<header class="bp-header">
|
||||||
@@ -41,6 +45,9 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="header-right">
|
<div class="header-right">
|
||||||
<div class="status-icons">
|
<div class="status-icons">
|
||||||
|
<span id="bp-controller-status" class="status-icon controller-status disconnected" title="Controller disconnected">
|
||||||
|
<span class="material-symbols-outlined">sports_esports</span>
|
||||||
|
</span>
|
||||||
<span id="bp-wifi" class="status-icon" title="Connected">
|
<span id="bp-wifi" class="status-icon" title="Connected">
|
||||||
<span class="material-symbols-outlined">wifi</span>
|
<span class="material-symbols-outlined">wifi</span>
|
||||||
</span>
|
</span>
|
||||||
@@ -72,6 +79,10 @@
|
|||||||
<span class="material-symbols-outlined">bookmarks</span>
|
<span class="material-symbols-outlined">bookmarks</span>
|
||||||
<span class="nav-label">Bookmarks</span>
|
<span class="nav-label">Bookmarks</span>
|
||||||
</button>
|
</button>
|
||||||
|
<button class="nav-item" data-section="history" data-focusable tabindex="0">
|
||||||
|
<span class="material-symbols-outlined">history</span>
|
||||||
|
<span class="nav-label">History</span>
|
||||||
|
</button>
|
||||||
<button class="nav-item" data-section="downloads" data-focusable tabindex="0">
|
<button class="nav-item" data-section="downloads" data-focusable tabindex="0">
|
||||||
<span class="material-symbols-outlined">download</span>
|
<span class="material-symbols-outlined">download</span>
|
||||||
<span class="nav-label">Downloads</span>
|
<span class="nav-label">Downloads</span>
|
||||||
@@ -147,6 +158,26 @@
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<!-- History section -->
|
||||||
|
<section id="section-history" class="bp-section">
|
||||||
|
<div class="section-header">
|
||||||
|
<h1 class="section-title">History</h1>
|
||||||
|
<p class="section-subtitle">Recently visited sites from this profile</p>
|
||||||
|
</div>
|
||||||
|
<div class="section-actions">
|
||||||
|
<button class="action-btn danger" id="bp-clear-history" data-focusable tabindex="0">
|
||||||
|
<span class="material-symbols-outlined">delete</span>
|
||||||
|
<span>Clear History</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="list-container" id="historyList">
|
||||||
|
<div class="empty-state">
|
||||||
|
<span class="material-symbols-outlined">history</span>
|
||||||
|
<p>No browsing history</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
<!-- Downloads section -->
|
<!-- Downloads section -->
|
||||||
<section id="section-downloads" class="bp-section">
|
<section id="section-downloads" class="bp-section">
|
||||||
<div class="section-header">
|
<div class="section-header">
|
||||||
@@ -332,7 +363,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="option-control">
|
<div class="option-control">
|
||||||
<button class="action-button" id="bp-clear-history" data-focusable tabindex="0">
|
<button class="action-button" id="bp-clear-history-settings" data-focusable tabindex="0">
|
||||||
<span class="material-symbols-outlined">delete</span>
|
<span class="material-symbols-outlined">delete</span>
|
||||||
<span>Clear</span>
|
<span>Clear</span>
|
||||||
</button>
|
</button>
|
||||||
@@ -403,28 +434,28 @@
|
|||||||
|
|
||||||
<!-- Bottom controller hints -->
|
<!-- Bottom controller hints -->
|
||||||
<footer class="bp-footer">
|
<footer class="bp-footer">
|
||||||
<div class="controller-hints">
|
<div class="controller-hints" id="controller-hints">
|
||||||
<div class="hint">
|
<div class="hint">
|
||||||
<span class="controller-btn dpad">
|
<span class="controller-btn dpad">
|
||||||
<span class="material-symbols-outlined">gamepad</span>
|
<span class="material-symbols-outlined">gamepad</span>
|
||||||
</span>
|
</span>
|
||||||
<span>Navigate</span>
|
<span id="hint-navigate">Navigate</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="hint">
|
<div class="hint">
|
||||||
<span class="controller-btn a-btn">A</span>
|
<span class="controller-btn a-btn">A</span>
|
||||||
<span>Select</span>
|
<span id="hint-a">Select</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="hint">
|
<div class="hint">
|
||||||
<span class="controller-btn b-btn">B</span>
|
<span class="controller-btn b-btn">B</span>
|
||||||
<span>Back</span>
|
<span id="hint-b">Back</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="hint">
|
<div class="hint">
|
||||||
<span class="controller-btn y-btn">Y</span>
|
<span class="controller-btn y-btn">Y</span>
|
||||||
<span>Search</span>
|
<span id="hint-y">Search</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="hint">
|
<div class="hint">
|
||||||
<span class="controller-btn menu-btn">☰</span>
|
<span class="controller-btn menu-btn">☰</span>
|
||||||
<span>Menu</span>
|
<span id="hint-menu">Menu</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
|
|||||||
Reference in New Issue
Block a user