Skip to content
Merged
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
12 changes: 12 additions & 0 deletions core/include/tc/app/tcWindow.h
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,18 @@ class TC_PLATFORMS("macos,windows,linux") Window {
void dispatchMouseReleaseToTree(const MouseEventArgs& e) {
if (ctx_.rootNode) ctx_.rootNode->dispatchMouseRelease(e);
}
void dispatchMouseMoveToTree(const internal::MouseEventRaw& e) {
if (ctx_.rootNode) ctx_.rootNode->dispatchMouseMove(e);
}
void dispatchMouseScrollToTree(const ScrollEventArgs& e) {
if (ctx_.rootNode) ctx_.rootNode->dispatchMouseScroll(e);
}
void dispatchKeyPressToTree(const KeyEventArgs& e) {
if (ctx_.rootNode) ctx_.rootNode->dispatchKeyPress(e);
}
void dispatchKeyReleaseToTree(const KeyEventArgs& e) {
if (ctx_.rootNode) ctx_.rootNode->dispatchKeyRelease(e);
}
void tickTree() {
if (!ctx_.rootNode) return;
ctx_.rootNode->updateTree();
Expand Down
8 changes: 7 additions & 1 deletion core/include/tc/types/tcRectNode.h
Original file line number Diff line number Diff line change
Expand Up @@ -213,11 +213,17 @@ class RectNode : public Node {
// These override the rich form and fire the corresponding Event. They also
// forward to the simple (Vec2,int) virtual so that a subclass which overrode
// the legacy simple form (pre-rich, oF-style) still gets called.
//
// Propagation: press/release/move/scroll all BUBBLE (v0.7) — returning
// false hands the event to the parent chain. RectNode consumes
// press/release/drag by default (return true); override and return false
// to let an ancestor (e.g. a draggable panel behind this label) take the
// gesture. The press consumer becomes the grab target for drag/release.
bool onMousePress(const MouseEventArgs& e) override {
MouseEventArgs args = e; // already localized to this node
mousePressed.notify(args);
onMousePress(e.pos, e.button); // legacy subclass hook
return true; // Consume event
return true; // Consume event (return false to bubble to parent)
}

bool onMouseRelease(const MouseEventArgs& e) override {
Expand Down
55 changes: 41 additions & 14 deletions core/include/tcNode.h
Original file line number Diff line number Diff line change
Expand Up @@ -938,19 +938,37 @@ class Node : public std::enable_shared_from_this<Node> {
// Dispatch mouse event to tree (for 2D mode). `e` is in screen space
// (pos == globalPos); each node receives a copy localized to its space.
// return: node that handled event (nullptr if not handled)
//
// Propagation (v0.7): press/release/move BUBBLE like scroll always has —
// starting at the front-most hit node, the event walks the parent chain
// (localized per level, firing node + mod hooks) until a handler consumes
// it by returning true. The first consumer of a press becomes the
// grabbedNode: it owns the drag (fireMouseDrag) and the matching release.
// RectNode's default onMousePress still returns true (consume), so
// bubbling activates only where a handler explicitly returns false — a
// press that previously died silently now reaches the ancestors instead.
// Hover (enter/leave) is unaffected: it stays front-most-only, recomputed
// per frame by updateHoverState().
Ptr dispatchMousePress(const MouseEventArgs& e) {
HitResult result = findHitNodeFromScreen(e.globalPos.x, e.globalPos.y);

// Selection: clicking a node selects it; clicking empty space clears it.
// (Selection follows the front-most hit, not the consumer — it is a
// debugger/inspector concept, independent of event consumption.)
internal::currentWindowContext().selectedNode = result.hit() ? result.node.get() : nullptr;

if (result.hit()) {
MouseEventArgs local = result.node->localizeMouse(e);
if (result.node->fireMousePress(local)) {
// Set grabbed node for drag tracking
internal::currentWindowContext().grabbedNode = result.node.get();
internal::currentWindowContext().grabbedButton = e.button;
return result.node;
// Bubble up from the hit node until consumed
Node* current = result.node.get();
while (current) {
MouseEventArgs local = current->localizeMouse(e);
if (current->fireMousePress(local)) {
// The consumer grabs the pointer for drag tracking
internal::currentWindowContext().grabbedNode = current;
internal::currentWindowContext().grabbedButton = e.button;
return std::dynamic_pointer_cast<Node>(current->shared_from_this());
}
current = current->getParent().get();
}
}

Expand All @@ -973,13 +991,17 @@ class Node : public std::enable_shared_from_this<Node> {
return result;
}

// Fallback: send to hit node
// Fallback (no grab): bubble from the hit node like press
HitResult result = findHitNodeFromScreen(e.globalPos.x, e.globalPos.y);

if (result.hit()) {
MouseEventArgs local = result.node->localizeMouse(e);
if (result.node->fireMouseRelease(local)) {
return result.node;
Node* current = result.node.get();
while (current) {
MouseEventArgs local = current->localizeMouse(e);
if (current->fireMouseRelease(local)) {
return std::dynamic_pointer_cast<Node>(current->shared_from_this());
}
current = current->getParent().get();
}
}

Expand All @@ -994,13 +1016,18 @@ class Node : public std::enable_shared_from_this<Node> {
internal::currentWindowContext().grabbedNode->fireMouseDrag(internal::toDragArgs(local));
}

// Also send move event to hit node (for hover, etc.)
// Also send move event, bubbling from the hit node (hover itself is
// handled separately by updateHoverState and stays front-most-only)
HitResult result = findHitNodeFromScreen(e.globalPos.x, e.globalPos.y);

if (result.hit()) {
internal::MouseEventRaw local = result.node->localizeMouse(e);
if (result.node->fireMouseMove(internal::toMoveArgs(local))) {
return result.node;
Node* current = result.node.get();
while (current) {
internal::MouseEventRaw local = current->localizeMouse(e);
if (current->fireMouseMove(internal::toMoveArgs(local))) {
return std::dynamic_pointer_cast<Node>(current->shared_from_this());
}
current = current->getParent().get();
}
}

Expand Down
12 changes: 10 additions & 2 deletions core/platform/linux/tcWindowLinux.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ void windowEvent(const sapp_event* ev, sapp_window swin, void* user) {
e.syncLegacy();
win->events().mousePressed.notify(e);
if (win->getApp()) win->getApp()->mousePressed(e);
win->dispatchMousePressToTree(e);
if (!e.consumed) win->dispatchMousePressToTree(e);
break;
}
case SAPP_EVENTTYPE_MOUSE_UP: {
Expand All @@ -159,7 +159,7 @@ void windowEvent(const sapp_event* ev, sapp_window swin, void* user) {
e.syncLegacy();
win->events().mouseReleased.notify(e);
if (win->getApp()) win->getApp()->mouseReleased(e);
win->dispatchMouseReleaseToTree(e);
if (!e.consumed) win->dispatchMouseReleaseToTree(e);
break;
}
case SAPP_EVENTTYPE_MOUSE_ENTER:
Expand All @@ -176,11 +176,16 @@ void windowEvent(const sapp_event* ev, sapp_window swin, void* user) {
MouseDragEventArgs e = internal::toDragArgs(raw);
win->events().mouseDragged.notify(e);
if (win->getApp()) win->getApp()->mouseDragged(e);
raw.consumed = e.consumed;
} else {
MouseMoveEventArgs e = internal::toMoveArgs(raw);
win->events().mouseMoved.notify(e);
if (win->getApp()) win->getApp()->mouseMoved(e);
raw.consumed = e.consumed;
}
// Node tree: drag goes to the grabbed node, move bubbles from the
// hit node (same dispatchMouseMove path as the main window).
if (!raw.consumed) win->dispatchMouseMoveToTree(raw);
break;
}
case SAPP_EVENTTYPE_MOUSE_LEAVE: {
Expand All @@ -198,6 +203,7 @@ void windowEvent(const sapp_event* ev, sapp_window swin, void* user) {
e.syncLegacy();
win->events().mouseScrolled.notify(e);
if (win->getApp()) win->getApp()->mouseScrolled(e);
if (!e.consumed) win->dispatchMouseScrollToTree(e);
break;
}
case SAPP_EVENTTYPE_KEY_DOWN: {
Expand All @@ -208,6 +214,7 @@ void windowEvent(const sapp_event* ev, sapp_window swin, void* user) {
if (!ev->key_repeat) ctx.keysPressed.insert(e.key);
win->events().keyPressed.notify(e);
if (win->getApp()) win->getApp()->keyPressed(e);
if (!e.consumed) win->dispatchKeyPressToTree(e);
break;
}
case SAPP_EVENTTYPE_KEY_UP: {
Expand All @@ -218,6 +225,7 @@ void windowEvent(const sapp_event* ev, sapp_window swin, void* user) {
ctx.keysPressed.erase(e.key);
win->events().keyReleased.notify(e);
if (win->getApp()) win->getApp()->keyReleased(e);
if (!e.consumed) win->dispatchKeyReleaseToTree(e);
break;
}
case SAPP_EVENTTYPE_SUSPENDED:
Expand Down
12 changes: 10 additions & 2 deletions core/platform/mac/tcWindowMac.mm
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ void windowEvent(const sapp_event* ev, sapp_window swin, void* user) {
e.syncLegacy();
win->events().mousePressed.notify(e);
if (win->getApp()) win->getApp()->mousePressed(e);
win->dispatchMousePressToTree(e);
if (!e.consumed) win->dispatchMousePressToTree(e);
break;
}
case SAPP_EVENTTYPE_MOUSE_UP: {
Expand All @@ -161,7 +161,7 @@ void windowEvent(const sapp_event* ev, sapp_window swin, void* user) {
e.syncLegacy();
win->events().mouseReleased.notify(e);
if (win->getApp()) win->getApp()->mouseReleased(e);
win->dispatchMouseReleaseToTree(e);
if (!e.consumed) win->dispatchMouseReleaseToTree(e);
break;
}
case SAPP_EVENTTYPE_MOUSE_ENTER:
Expand All @@ -178,11 +178,16 @@ void windowEvent(const sapp_event* ev, sapp_window swin, void* user) {
MouseDragEventArgs e = internal::toDragArgs(raw);
win->events().mouseDragged.notify(e);
if (win->getApp()) win->getApp()->mouseDragged(e);
raw.consumed = e.consumed;
} else {
MouseMoveEventArgs e = internal::toMoveArgs(raw);
win->events().mouseMoved.notify(e);
if (win->getApp()) win->getApp()->mouseMoved(e);
raw.consumed = e.consumed;
}
// Node tree: drag goes to the grabbed node, move bubbles from the
// hit node (same dispatchMouseMove path as the main window).
if (!raw.consumed) win->dispatchMouseMoveToTree(raw);
break;
}
case SAPP_EVENTTYPE_MOUSE_LEAVE: {
Expand All @@ -200,6 +205,7 @@ void windowEvent(const sapp_event* ev, sapp_window swin, void* user) {
e.syncLegacy();
win->events().mouseScrolled.notify(e);
if (win->getApp()) win->getApp()->mouseScrolled(e);
if (!e.consumed) win->dispatchMouseScrollToTree(e);
break;
}
case SAPP_EVENTTYPE_KEY_DOWN: {
Expand All @@ -210,6 +216,7 @@ void windowEvent(const sapp_event* ev, sapp_window swin, void* user) {
if (!ev->key_repeat) ctx.keysPressed.insert(e.key);
win->events().keyPressed.notify(e);
if (win->getApp()) win->getApp()->keyPressed(e);
if (!e.consumed) win->dispatchKeyPressToTree(e);
break;
}
case SAPP_EVENTTYPE_KEY_UP: {
Expand All @@ -220,6 +227,7 @@ void windowEvent(const sapp_event* ev, sapp_window swin, void* user) {
ctx.keysPressed.erase(e.key);
win->events().keyReleased.notify(e);
if (win->getApp()) win->getApp()->keyReleased(e);
if (!e.consumed) win->dispatchKeyReleaseToTree(e);
break;
}
case SAPP_EVENTTYPE_SUSPENDED:
Expand Down
12 changes: 10 additions & 2 deletions core/platform/win/tcWindowWin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ void windowEvent(const sapp_event* ev, sapp_window swin, void* user) {
e.syncLegacy();
win->events().mousePressed.notify(e);
if (win->getApp()) win->getApp()->mousePressed(e);
win->dispatchMousePressToTree(e);
if (!e.consumed) win->dispatchMousePressToTree(e);
break;
}
case SAPP_EVENTTYPE_MOUSE_UP: {
Expand All @@ -159,7 +159,7 @@ void windowEvent(const sapp_event* ev, sapp_window swin, void* user) {
e.syncLegacy();
win->events().mouseReleased.notify(e);
if (win->getApp()) win->getApp()->mouseReleased(e);
win->dispatchMouseReleaseToTree(e);
if (!e.consumed) win->dispatchMouseReleaseToTree(e);
break;
}
case SAPP_EVENTTYPE_MOUSE_ENTER:
Expand All @@ -176,11 +176,16 @@ void windowEvent(const sapp_event* ev, sapp_window swin, void* user) {
MouseDragEventArgs e = internal::toDragArgs(raw);
win->events().mouseDragged.notify(e);
if (win->getApp()) win->getApp()->mouseDragged(e);
raw.consumed = e.consumed;
} else {
MouseMoveEventArgs e = internal::toMoveArgs(raw);
win->events().mouseMoved.notify(e);
if (win->getApp()) win->getApp()->mouseMoved(e);
raw.consumed = e.consumed;
}
// Node tree: drag goes to the grabbed node, move bubbles from the
// hit node (same dispatchMouseMove path as the main window).
if (!raw.consumed) win->dispatchMouseMoveToTree(raw);
break;
}
case SAPP_EVENTTYPE_MOUSE_LEAVE: {
Expand All @@ -198,6 +203,7 @@ void windowEvent(const sapp_event* ev, sapp_window swin, void* user) {
e.syncLegacy();
win->events().mouseScrolled.notify(e);
if (win->getApp()) win->getApp()->mouseScrolled(e);
if (!e.consumed) win->dispatchMouseScrollToTree(e);
break;
}
case SAPP_EVENTTYPE_KEY_DOWN: {
Expand All @@ -208,6 +214,7 @@ void windowEvent(const sapp_event* ev, sapp_window swin, void* user) {
if (!ev->key_repeat) ctx.keysPressed.insert(e.key);
win->events().keyPressed.notify(e);
if (win->getApp()) win->getApp()->keyPressed(e);
if (!e.consumed) win->dispatchKeyPressToTree(e);
break;
}
case SAPP_EVENTTYPE_KEY_UP: {
Expand All @@ -218,6 +225,7 @@ void windowEvent(const sapp_event* ev, sapp_window swin, void* user) {
ctx.keysPressed.erase(e.key);
win->events().keyReleased.notify(e);
if (win->getApp()) win->getApp()->keyReleased(e);
if (!e.consumed) win->dispatchKeyReleaseToTree(e);
break;
}
case SAPP_EVENTTYPE_SUSPENDED:
Expand Down
40 changes: 40 additions & 0 deletions core/tests/mouseBubbling/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# =============================================================================
# TrussC Project .gitignore
# =============================================================================

# Generated by projectGenerator (regenerate with projectGenerator update)
CMakeLists.txt
CMakePresets.json

# TrussC local config (path override, generated by projectGenerator)
.trussc

# Build directories
build/
build-*/
emscripten/
xcode*/
vs/

# Build scripts (generated, OS dependent)
build-web.*

# Binary output (keep data folder)
bin/*
!bin/data/

# IDE specific
.vscode/
.vs/
.cache/

# Generated shader headers (rebuilt by CMake)
*.glsl.h

# OS specific
.DS_Store
Thumbs.db

# Secrets (don't commit these!)
.env
secrets.*
1 change: 1 addition & 0 deletions core/tests/mouseBubbling/addons.make
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# TrussC addons - one addon per line
Loading
Loading