Files
workinf_Blender_Wasm/blender-5.2.0/intern/ghost/intern/GHOST_SystemWeb.cc
mes123456 f3e21c6b98
Some checks failed
M6 deployable RC / quick (push) Has been cancelled
M6 deployable RC / chromium (push) Has been cancelled
M6 deployable RC / release (push) Has been cancelled
chore: checkpoint web Blender migration source, tools and docs
Accumulated uncommitted work: GHOST Web backend, WebGL/GPU web layer,
web workflow engine and protocol, tooling, docs and task records.
Large generated evidence, user-testing archives, build snapshots and
imported test assets remain untracked.

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-11-10 11:18:01 +08:00

240 lines
8.9 KiB
C++

/* SPDX-FileCopyrightText: 2026 Blender Authors
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "GHOST_SystemWeb.hh"
#include <algorithm>
#include "GHOST_TimerManager.hh"
#include "GHOST_WindowManager.hh"
#include "GHOST_WindowWeb.hh"
#include "GHOST_EventCursor.hh"
#include <cmath>
#include <emscripten.h>
bool GHOST_SystemWeb::bindCanvas(const char *selector, float pixel_ratio)
{
if (canvas_window_ || !selector || selector[0] != '#' || selector[1] == '\0' ||
!std::isfinite(pixel_ratio) || pixel_ratio <= 0.0f)
{
return false;
}
canvas_selector_ = selector;
pixel_ratio_ = pixel_ratio;
return true;
}
GHOST_IWindow *GHOST_SystemWeb::createWindow(const char *title,
int32_t /*left*/,
int32_t /*top*/,
uint32_t width,
uint32_t height,
GHOST_TWindowState state,
GHOST_GPUSettings gpu_settings,
bool exclusive,
bool is_dialog,
const GHOST_IWindow *parent_window)
{
/* One transferred canvas per module. Reject desktop window requests until
* the host supplies another canvas, rather than aliasing the live context. */
if (canvas_window_ || canvas_selector_.empty() || exclusive || is_dialog || parent_window ||
state != GHOST_kWindowStateNormal ||
gpu_settings.context_type != GHOST_kDrawingContextTypeWebGL)
{
return nullptr;
}
auto *window = new GHOST_WindowWeb(canvas_selector_.c_str(), width, height, pixel_ratio_);
if (!window->getValid() || !window_manager_->addWindow(window)) {
delete window;
return nullptr;
}
window->setTitle(title);
canvas_window_ = window;
return window;
}
GHOST_TSuccess GHOST_SystemWeb::disposeWindow(GHOST_IWindow *window)
{
const bool is_canvas = window == canvas_window_;
const GHOST_TSuccess result = GHOST_System::disposeWindow(window);
if (result && is_canvas) {
canvas_window_ = nullptr;
modifiers_.clear();
buttons_.clear();
cursor_valid_ = false;
}
return result;
}
bool GHOST_SystemWeb::processEvents(bool /*wait_for_event*/)
{
/* The browser owns the event loop; blocking here would prevent new input. */
const bool timers_fired = timer_manager_->fireTimers(getMilliSeconds());
return timers_fired || event_manager_->getNumEvents() != 0;
}
uint64_t GHOST_SystemWeb::getMilliSeconds() const
{
return uint64_t(emscripten_get_now());
}
GHOST_TSuccess GHOST_SystemWeb::getModifierKeys(GHOST_ModifierKeys &keys) const
{
keys = modifiers_;
return GHOST_kSuccess;
}
GHOST_TSuccess GHOST_SystemWeb::getButtons(GHOST_Buttons &buttons) const
{
buttons = buttons_;
return GHOST_kSuccess;
}
GHOST_TSuccess GHOST_SystemWeb::getCursorPosition(int32_t &x, int32_t &y) const
{
if (!cursor_valid_) return GHOST_kFailure;
x = cursor_x_;
y = cursor_y_;
/* Match Wayland's relative pointer: WM receives continuous coordinates,
* while cursor queries return the location within the native wrap region. */
if (canvas_window_ && canvas_window_->getCursorGrabModeIsWarp()) {
GHOST_Rect bounds;
if (!canvas_window_->getCursorGrabBounds(bounds)) canvas_window_->getClientBounds(bounds);
bounds.wrapPoint(x, y, 0, canvas_window_->getCursorGrabAxis());
}
return GHOST_kSuccess;
}
GHOST_TSuccess GHOST_SystemWeb::setCursorPosition(int32_t x, int32_t y)
{
/* Browsers cannot warp the OS pointer. During native grab the pointer is
* relative and WM owns its software cursor, so reposition that native cursor. */
if (!canvas_window_ || !canvas_window_->getCursorGrabModeIsWarp()) return GHOST_kFailure;
cursor_remainder_[0] = cursor_remainder_[1] = 0;
return pushBrowserEvent(std::make_unique<GHOST_EventCursor>(
getMilliSeconds(), GHOST_kEventCursorMove, canvas_window_, x, y, GHOST_TABLET_DATA_NONE));
}
GHOST_TSuccess GHOST_SystemWeb::moveBrowserCursor(float x, float y, bool relative)
{
if (!canvas_window_ || !std::isfinite(x) || !std::isfinite(y)) return GHOST_kFailure;
if (relative) {
/* A final browser packet can arrive after WM confirmed/cancelled the tool. */
if (canvas_window_->getCursorGrabMode() == GHOST_kGrabDisable) return GHOST_kSuccess;
if (!cursor_valid_) return GHOST_kFailure;
cursor_remainder_[0] += x;
cursor_remainder_[1] += y;
const int dx = int(cursor_remainder_[0]), dy = int(cursor_remainder_[1]);
cursor_remainder_[0] -= dx;
cursor_remainder_[1] -= dy;
/* Keep native integer conversion defined even for a very long grab. */
if (std::abs(double(cursor_x_) + dx) > 1000000000.0 ||
std::abs(double(cursor_y_) + dy) > 1000000000.0) return GHOST_kFailure;
int32_t next_x = cursor_x_ + dx, next_y = cursor_y_ + dy;
if (canvas_window_->getCursorGrabMode() == GHOST_kGrabNormal) {
/* Normal grab confines, but does not wrap, the visible software pointer. */
GHOST_Rect bounds;
canvas_window_->getClientBounds(bounds);
next_x = std::clamp(next_x, bounds.l_, bounds.r_ - 1);
next_y = std::clamp(next_y, bounds.t_, bounds.b_ - 1);
}
return pushBrowserEvent(std::make_unique<GHOST_EventCursor>(
getMilliSeconds(), GHOST_kEventCursorMove, canvas_window_,
next_x, next_y, GHOST_TABLET_DATA_NONE));
}
cursor_remainder_[0] = cursor_remainder_[1] = 0;
return pushBrowserEvent(std::make_unique<GHOST_EventCursor>(
getMilliSeconds(), GHOST_kEventCursorMove, canvas_window_, int(x), int(y),
GHOST_TABLET_DATA_NONE));
}
void GHOST_SystemWeb::getMainDisplayDimensions(uint32_t &width, uint32_t &height) const
{
GHOST_Rect bounds;
if (canvas_window_) canvas_window_->getClientBounds(bounds);
width = canvas_window_ ? bounds.getWidth() : 0;
height = canvas_window_ ? bounds.getHeight() : 0;
}
void GHOST_SystemWeb::getAllDisplayDimensions(uint32_t &width, uint32_t &height) const
{
getMainDisplayDimensions(width, height);
}
GHOST_IWindow *GHOST_SystemWeb::getWindowUnderCursor(int32_t x, int32_t y)
{
if (!canvas_window_ || !cursor_valid_) return nullptr;
GHOST_Rect bounds;
canvas_window_->getClientBounds(bounds);
return bounds.isInside(x, y) ? canvas_window_ : nullptr;
}
void GHOST_SystemWeb::setBrowserModifiers(int modifiers, GHOST_TKey changed_key, bool down)
{
const auto update_pair = [&](int bit, GHOST_TModifierKey left, GHOST_TModifierKey right) {
if (!(modifiers & bit)) {
modifiers_.set(left, false);
modifiers_.set(right, false);
return;
}
const bool is_left = changed_key == GHOST_ModifierKeys::getModifierKeyCode(left);
const bool is_right = changed_key == GHOST_ModifierKeys::getModifierKeyCode(right);
if (is_left) modifiers_.set(left, down);
if (is_right) modifiers_.set(right, down);
// Browser pointer events carry aggregate state; retain known physical sides.
if (!modifiers_.get(left) && !modifiers_.get(right)) {
modifiers_.set(is_left && !down ? right : left, true);
}
};
update_pair(1, GHOST_kModifierKeyLeftShift, GHOST_kModifierKeyRightShift);
update_pair(2, GHOST_kModifierKeyLeftControl, GHOST_kModifierKeyRightControl);
update_pair(4, GHOST_kModifierKeyLeftAlt, GHOST_kModifierKeyRightAlt);
update_pair(8, GHOST_kModifierKeyLeftOS, GHOST_kModifierKeyRightOS);
}
GHOST_TSuccess GHOST_SystemWeb::pushBrowserEvent(std::unique_ptr<const GHOST_IEvent> event)
{
if (!event || event->getWindow() != canvas_window_ || !canvas_window_) return GHOST_kFailure;
switch (event->getType()) {
case GHOST_kEventCursorMove: {
const auto *data = static_cast<const GHOST_TEventCursorData *>(event->getData());
cursor_x_ = data->x;
cursor_y_ = data->y;
cursor_valid_ = true;
break;
}
case GHOST_kEventTrackpad: {
const auto *data = static_cast<const GHOST_TEventTrackpadData *>(event->getData());
cursor_x_ = data->x;
cursor_y_ = data->y;
cursor_valid_ = true;
break;
}
case GHOST_kEventDraggingDropDone: {
const auto *data = static_cast<const GHOST_TEventDragnDropData *>(event->getData());
cursor_x_ = data->x;
cursor_y_ = data->y;
cursor_valid_ = true;
buttons_.clear();
window_manager_->setActiveWindow(canvas_window_);
break;
}
case GHOST_kEventButtonDown:
case GHOST_kEventButtonUp:
buttons_.set(static_cast<const GHOST_TEventButtonData *>(event->getData())->button,
event->getType() == GHOST_kEventButtonDown);
break;
case GHOST_kEventWindowActivate:
window_manager_->setActiveWindow(canvas_window_);
break;
case GHOST_kEventWindowDeactivate:
window_manager_->setWindowInactive(canvas_window_);
modifiers_.clear();
buttons_.clear();
cursor_valid_ = false;
break;
default:
break;
}
return pushEvent(std::move(event));
}