Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion code/framework/src/gui/cef/renderer_app.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,12 @@ namespace Framework::GUI::CEF {
CefRefPtr<CefV8Handler> handler = new CallEventHandler(browser);
CefRefPtr<CefV8Value> func = CefV8Value::CreateFunction("callEvent", handler);
global->SetValue("callEvent", func, V8_PROPERTY_ATTRIBUTE_NONE);

// Each document starts with nothing focused; without this the dedupe in
// OnFocusedNodeChanged would swallow the first focus after a same-process navigation.
if (frame && frame->IsMain()) {
_inputFocus[browser->GetIdentifier()] = false;
}
}

// The DOM is only reachable here, so the browser process cannot decide this for itself.
Expand All @@ -62,7 +68,9 @@ namespace Framework::GUI::CEF {
return;
}

const bool typing = node && node->GetType() == CefDOMNode::Type::DOM_NODE_TYPE_ELEMENT && node->GetFormControlElementType() != CefDOMNode::FormControlType::DOM_FORM_CONTROL_TYPE_UNSUPPORTED;
// IsEditable: text inputs, textareas and contenteditable. A form-control test would count
// buttons, checkboxes and selects as typing, and miss rich-text editors.
const bool typing = node && node->IsEditable();

bool &previous = _inputFocus[browser->GetIdentifier()];
if (previous == typing) {
Expand Down
2 changes: 1 addition & 1 deletion code/framework/src/gui/cef/renderer_app.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ namespace Framework::GUI::CEF {

class RendererApp final: public CefApp, public CefRenderProcessHandler {
private:
// browser id -> form control focused; a renderer process can host several browsers
// browser id -> editable node focused; a renderer process can host several browsers
std::unordered_map<int, bool> _inputFocus;

public:
Expand Down
10 changes: 10 additions & 0 deletions code/framework/src/gui/manager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -578,6 +578,16 @@ namespace Framework::GUI {
}
}

bool Manager::IsAnyTextInputFocused() const {
std::scoped_lock lock(_renderMutex);
for (const auto &view : _views) {
if (view->HasFocus() && view->ShouldDisplay() && view->IsTextInputFocused()) {
return true;
}
}
return false;
}
Comment thread
Kheartz marked this conversation as resolved.

bool Manager::IsAnyViewFocused() const {
std::scoped_lock lock(_renderMutex);
for (const auto &view : _views) {
Expand Down
5 changes: 5 additions & 0 deletions code/framework/src/gui/manager.h
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,11 @@ namespace Framework::GUI {

void CleanupViews();
bool IsAnyViewFocused() const;

// Whether a shown, focused view has the caret in an editable node, as opposed to merely
// holding focus. The predicate input gating wants: a focused HUD must not eat keybinds or
// mute voice.
bool IsAnyTextInputFocused() const;
bool IsAnyGCViewFocused() const;

std::vector<GUI::View *> GetAllViews() const;
Expand Down
10 changes: 10 additions & 0 deletions code/framework/src/gui/view.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,16 @@ namespace Framework::GUI {
_created = true;
}

if (data.event == ViewEvent::InputFocusChange) {
_textInputFocused = data.focused;
}
Comment thread
Kheartz marked this conversation as resolved.

// A new document starts with nothing focused; a cross-process navigation never reports
// the old caret going away.
if (data.event == ViewEvent::LoadingStart && data.isMainFrame) {
_textInputFocused = false;
}

// window object exists from main-frame load start; bind before anything talks to the page
if (data.event == ViewEvent::LoadingStart && data.isMainFrame && _sdk && _browser) {
(void)_sdk->Init(_browser);
Expand Down
12 changes: 10 additions & 2 deletions code/framework/src/gui/view.h
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,9 @@ namespace Framework::GUI {
// CPU renderer fallback
std::vector<uint8_t> _pixelData;

bool _gpuAccelerated = false;
bool _hasFocus = false;
bool _gpuAccelerated = false;
bool _hasFocus = false;
bool _textInputFocused = false;
Comment thread
Kheartz marked this conversation as resolved.
int _x;
int _y;
int _z;
Expand Down Expand Up @@ -109,6 +110,13 @@ namespace Framework::GUI {
}
}

// The DOM half of focus: an editable node holds the caret. Keys only reach a page while
// HasFocus() && ShouldDisplay(), so gate on all three. Not cleared by Focus(false): the DOM
// keeps its caret across it, and reports no transition when the view is refocused.
bool IsTextInputFocused() const {
return _textInputFocused;
}

bool HasFocus() const {
return _hasFocus;
}
Expand Down
10 changes: 6 additions & 4 deletions code/framework/src/integrations/client/instance.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -385,7 +385,8 @@ namespace Framework::Integrations::Client {
if (!_chatBox.IsSessionActive() || _chatBox.IsInputActive()) {
return false;
}
if (_webManager && _webManager->IsAnyViewFocused()) {
// Text entry, not view focus: a focused HUD or menu leaves keybinds and push-to-talk alone.
if (_webManager && _webManager->IsAnyTextInputFocused()) {
return false;
}
#ifdef _WIN32
Expand Down Expand Up @@ -565,9 +566,10 @@ namespace Framework::Integrations::Client {
{
FW_PROFILE_SCOPE_N("Client::Voice");

// The chat box and web views belong to the framework, so it enforces this itself
// rather than trusting every mod to remember.
_voiceClient.SetInputSuppressed(_chatBox.IsInputActive() || (_webManager && _webManager->IsAnyViewFocused()));
// The chat box and web views belong to the framework, so it enforces this itself rather
// than trusting every mod to remember. Text entry only: a focused HUD or menu must not
// mute the player.
_voiceClient.SetInputSuppressed(_chatBox.IsInputActive() || (_webManager && _webManager->IsAnyTextInputFocused()));

// Speaker positions come from the replicated entity set, as the server's voice
// router gets them: an owner GUID means a player-controlled entity. Done here so
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -369,7 +369,7 @@ namespace Framework::Integrations::Client::Scripting::Builtins {
{{"cursor", "string", "CSS-style cursor name, or \"custom\" for shapes without one."}, {"cursorType", "number", "Raw CEF cursor type."}}, "Dispatched when an owned view's requested cursor shape changes.");
describe(Framework::GUI::ViewEvent::Tooltip, "BrowserTooltipEvent", "A page wants to display a tooltip.", {{"text", "string", "Tooltip text; empty when the tooltip is dismissed."}},
"Dispatched when an owned view requests a tooltip; windowless rendering draws none, so the script must.");
describe(Framework::GUI::ViewEvent::InputFocusChange, "BrowserInputFocusChangeEvent", "A form control inside a page gained or lost focus.", {{"focused", "boolean", "True while the page holds keyboard input."}},
describe(Framework::GUI::ViewEvent::InputFocusChange, "BrowserInputFocusChangeEvent", "An editable element inside a page gained or lost focus.", {{"focused", "boolean", "True while the page holds keyboard input."}},
"Dispatched when focus enters or leaves an editable element of an owned view; use it to stop routing keys to the game.");
describe(Framework::GUI::ViewEvent::ResourceBlocked, "BrowserResourceBlockedEvent", "A web view refused a request.",
{{"url", "string", "URL that was refused."}, {"domain", "string", "Host component of that URL, empty when unparsable."}, {"reason", "\"cross-origin\" | \"invalid-url\" | \"host-filter\" | \"foreign-event\"", "Why the request was refused."}},
Expand Down
2 changes: 1 addition & 1 deletion code/framework/src/voice/client/voice_client.h
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,7 @@ namespace Framework::Voice {
void RemoveSpeaker(uint64_t speaker);

// Blocks transmission regardless of push-to-talk, cutting the release delay short. Set by
// the client Instance while its chat box has the caret or a web view holds focus.
// the client Instance while its chat box or a web view's editable node has the caret.
void SetInputSuppressed(bool suppressed);

// The mod-owned half of the same block: window focus, a game menu, locked controls. Ored
Expand Down
Loading