Skip to content

Commit

Permalink
Adding gdscript => JS functions methods bindings #40
Browse files Browse the repository at this point in the history
  • Loading branch information
Lecrapouille committed Dec 10, 2024
1 parent 62e7653 commit 5d73626
Show file tree
Hide file tree
Showing 8 changed files with 392 additions and 43 deletions.
18 changes: 14 additions & 4 deletions addons/gdcef/demos/JS/Control.gd
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,8 @@ func _update_character_stats():
"level": level
}
print("Character update: ", character_info)
# Refresh the GUI
$CEF.get_node(BROWSER_NAME).send_to_js("character_update", get_character_state())
pass

# ==============================================================================
Expand All @@ -88,13 +90,17 @@ func get_character_state() -> Dictionary:

# ==============================================================================
# CEF Callback when a page has ended to load with success.
# TODO on page_unload ?
# ==============================================================================
func _on_page_loaded(browser):
print("The browser " + browser.name + " has loaded " + browser.get_url())

# Register methods for JS->Godot communication
browser.register_method(Callable(self, "change_weapon"))
browser.register_method(Callable(self, "set_character_name"))
browser.register_method(Callable(self, "modify_xp"))

# Send initial character state to JS
browser.send_to_js("character_update", get_character_state())
pass

# ==============================================================================
Expand All @@ -116,8 +122,11 @@ func initialize_cef():

### CEF

if !$CEF.initialize({"incognito": true, "locale": "en-US",
"remote_debugging_port": 7777, "remote_allow_origin": "*"}):
if !$CEF.initialize({
"incognito": true,
"remote_debugging_port": 7777,
"remote_allow_origin": "*"
}):
push_error("Failed initializing CEF")
get_tree().quit()
else:
Expand All @@ -126,7 +135,8 @@ func initialize_cef():

### Browser

var browser = $CEF.create_browser("res://character-management-ui.html", $TextureRect, {"javascript": true})
var browser = $CEF.create_browser("res://character-management-ui.html",
$TextureRect, {"javascript": true})
browser.name = BROWSER_NAME
browser.connect("on_page_loaded", _on_page_loaded)
browser.connect("on_page_failed_loading", _on_page_failed_loading)
Expand Down
87 changes: 76 additions & 11 deletions addons/gdcef/demos/JS/character-management-ui.html
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

body {
font-family: 'Arial', sans-serif;
font-size: 14px;
background-color: var(--background-color);
display: flex;
justify-content: center;
Expand All @@ -27,25 +28,31 @@
background-color: white;
border-radius: 12px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
padding: 30px;
padding: 20px;
width: 100%;
max-width: 500px;
}

.character-header {
text-align: center;
margin-bottom: 20px;
margin-bottom: 15px;
}

.character-header h1 {
font-size: 1.8em;
margin-bottom: 15px;
}

.character-section {
margin-bottom: 20px;
margin-bottom: 15px;
border-bottom: 1px solid #e0e0e0;
padding-bottom: 20px;
padding-bottom: 15px;
}

.section-title {
font-size: 1.3em;
margin-bottom: 12px;
color: var(--primary-color);
margin-bottom: 15px;
}

.weapon-grid {
Expand All @@ -58,10 +65,11 @@
background-color: var(--secondary-color);
color: white;
border: none;
padding: 10px;
padding: 8px;
border-radius: 6px;
cursor: pointer;
transition: background-color 0.3s ease;
font-size: 0.9em;
}

.weapon-button:hover {
Expand All @@ -80,20 +88,27 @@

.xp-input {
flex-grow: 1;
padding: 8px;
padding: 6px;
border: 1px solid #bdc3c7;
border-radius: 4px;
font-size: 0.9em;
}

.character-name-form {
display: flex;
gap: 10px;
}

.stat-row {
margin-bottom: 6px;
font-size: 0.95em;
}

#characterStatus {
margin-top: 20px;
margin-top: 15px;
text-align: center;
color: #2ecc71;
font-size: 0.9em;
}
</style>
</head>
Expand Down Expand Up @@ -133,6 +148,28 @@ <h2 class="section-title">XP Management</h2>
</div>
</div>

<div class="character-section">
<h2 class="section-title">Character Stats</h2>
<div class="stats-container">
<div class="stat-row">
<span class="stat-label">Name:</span>
<span id="statName" class="stat-value">-</span>
</div>
<div class="stat-row">
<span class="stat-label">Weapon:</span>
<span id="statWeapon" class="stat-value">-</span>
</div>
<div class="stat-row">
<span class="stat-label">XP:</span>
<span id="statXP" class="stat-value">-</span>
</div>
<div class="stat-row">
<span class="stat-label">Level:</span>
<span id="statLevel" class="stat-value">-</span>
</div>
</div>
</div>

<div id="characterStatus"></div>
</div>

Expand All @@ -141,7 +178,7 @@ <h2 class="section-title">XP Management</h2>
let characterName = '';
let characterXP = 0;

function selectWeapon(weapon) {
function selectWeapon(weapon, fromGodot = false) {
// Disable all buttons
document.querySelectorAll('.weapon-button').forEach(btn => {
btn.classList.remove('active');
Expand All @@ -152,8 +189,8 @@ <h2 class="section-title">XP Management</h2>
weaponBtn.classList.add('active');
currentWeapon = weapon;

// Call the Godot method to change the weapon
if (window.godot && window.godot.change_weapon) {
// Call the Godot method only if not triggered by Godot
if (!fromGodot && window.godot && window.godot.change_weapon) {
window.godot.change_weapon(weapon);
updateStatus(`Weapon changed to : ${weapon}`);
}
Expand Down Expand Up @@ -214,6 +251,34 @@ <h2 class="section-title">XP Management</h2>
statusElement.textContent = '';
}, 3000);
}

// Listen for updates coming from Godot
window.godotEventSystem.on('character_update', function (data) {
try {
// Update character name
if (data.name) {
characterName = data.name;
document.getElementById('statName').textContent = data.name;
}

// Update weapon selection
if (data.weapon) {
selectWeapon(data.weapon, true);
document.getElementById('statWeapon').textContent = data.weapon;
}

// Update XP and Level
if (data.xp !== undefined) {
characterXP = data.xp;
document.getElementById('statXP').textContent = data.xp;
const level = Math.floor(data.xp / 100) + 1;
document.getElementById('statLevel').textContent = level;
updateStatus(`XP updated: ${characterXP}`);
}
} catch (e) {
console.error('[GodotEventSystem] Error processing update:', e);
}
});
</script>
</body>

Expand Down
31 changes: 17 additions & 14 deletions addons/gdcef/gdcef/src/gdbrowser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,6 @@
# include <omp.h>
#endif

#ifndef CALL_GODOT_METHOD
# define CALL_GODOT_METHOD "callGodotMethod"
#endif

//------------------------------------------------------------------------------
// Visit the html content of the current page.
class Visitor: public CefStringVisitor
Expand Down Expand Up @@ -155,6 +151,8 @@ void GDBrowserView::_bind_methods()
&GDBrowserView::getPixelColor);
ClassDB::bind_method(D_METHOD("register_method"),
&GDBrowserView::registerGodotMethod);
ClassDB::bind_method(D_METHOD("send_to_js", "event_name", "data"),
&GDBrowserView::sendToJS);

// Signals
ADD_SIGNAL(MethodInfo("on_download_updated",
Expand Down Expand Up @@ -902,20 +900,22 @@ void GDBrowserView::onDownloadUpdated(
}

//------------------------------------------------------------------------------
void GDBrowserView::registerGodotMethod(const godot::Callable& callable)
bool GDBrowserView::registerGodotMethod(const godot::Callable& callable)
{
godot::String method_name = callable.get_method();

BROWSER_DEBUG("Registering gdscript method "
<< method_name.utf8().get_data());

if (!callable.is_valid())
{
BROWSER_ERROR("Invalid callable provided");
return;
BROWSER_ERROR("Invalid callable gdscript method "
<< method_name.utf8().get_data());
return false;
}

std::string key = method_name.utf8().get_data();
m_js_bindings[key] = callable;
return true;
}

//------------------------------------------------------------------------------
Expand All @@ -926,30 +926,31 @@ bool GDBrowserView::onProcessMessageReceived(
CefRefPtr<CefProcessMessage> message)
{
BROWSER_DEBUG("Received message " << message->GetName().ToString());
if (message->GetName() != CALL_GODOT_METHOD)
if (message->GetName() != "callGodotMethod")
{
BROWSER_DEBUG("Not method " << CALL_GODOT_METHOD);
BROWSER_DEBUG("Expecting IPC command 'callGodotMethod'");
return false;
}

if (message->GetArgumentList()->GetSize() < 1)
{
BROWSER_ERROR("Expected method name as first argument");
BROWSER_ERROR("Expected method name as first argument for "
"'callGodotMethod' IPC command");
return false;
}

// Create the callable key
// Create the Godot Callable key
std::string key = message->GetArgumentList()->GetString(0).ToString();

// Does not exist ?
// Does the Godot Callable exist ?
auto callable = m_js_bindings[key];
if (!callable.is_valid())
{
BROWSER_ERROR("Callable not found for method " << key);
return false;
}

// Convert the message arguments to a Godot Array
// Convert the IPC message arguments to a Godot Array
godot::Array args;
auto message_args = message->GetArgumentList();
for (size_t i = 1; i < message_args->GetSize(); ++i)
Expand All @@ -973,6 +974,7 @@ bool GDBrowserView::onProcessMessageReceived(
// For unsupported types, pass as string
args.push_back(godot::String(
message_args->GetString(i).ToString().c_str()));
break;
}
}

Expand Down Expand Up @@ -1014,6 +1016,7 @@ bool GDBrowserView::sendToJS(godot::String eventName,
args->SetValue(1, cef_data);

// Send to render process
BROWSER_DEBUG("Sending message to render process");
m_browser->GetMainFrame()->SendProcessMessage(PID_RENDERER, message);
return true;
}
45 changes: 43 additions & 2 deletions addons/gdcef/gdcef/src/gdbrowser.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -682,9 +682,50 @@ class GDBrowserView: public godot::Node
godot::Color getPixelColor(int x, int y) const;

// -------------------------------------------------------------------------
//! \brief Register a Godot method in the JavaScript context
//! \brief Register a GDScript method in the JavaScript context.
//! The registered GDScript method can be called from JavaScript using:
//! window.godot.methodName(args)
//!
//! \param[in] callable the GDScript method to register. The callable must
//! be a valid GDScript method that can receive string parameters.
//!
//! \return true if the method has been successfully registered, false
//! otherwise.
//!
//! \note The registered method will be available in JavaScript under the
//! 'window.godot' namespace. All parameters passed from JavaScript will
//! be converted to a Godot::Variant before been executed.
//!
//! Example in GDScript:
//! func my_method(data: String) -> void:
//! print("Received from JS: ", data)
//!
//! browser.register_method(Callable(self, "my_method"))
//!
//! Example in JavaScript:
//! window.godot.my_method("Hello from JS!");
//!
// -------------------------------------------------------------------------
bool registerGodotMethod(const godot::Callable& callable);

// -------------------------------------------------------------------------
//! \brief Send a message to the JavaScript side.
//! The message will be received in JavaScript as a JSON object.
//!
//! \param[in] eventName Name of the event to trigger in JavaScript.
//! \param[in] data Godot::Variant to send.
//! \return true if the message has been sent, false otherwise.
//!
//! Example in GDScript:
//! browser.sendToJS("myEvent", {"key": "value"})
//!
//! Example in JavaScript:
//! window.addEventListener("myEvent", function(event) {
//! const data = JSON.parse(event.data);
//! console.log(data.key); // outputs: "value"
//! });
// -------------------------------------------------------------------------
void registerGodotMethod(const godot::Callable& callable);
bool sendToJS(godot::String eventName, const godot::Variant& data);

private:

Expand Down
Loading

0 comments on commit 5d73626

Please sign in to comment.