diff --git a/src/cpp/include/nodegui/core/Events/eventwidget.h b/src/cpp/include/nodegui/core/Events/eventwidget.h index 0fc90c87f..b9b27f93d 100644 --- a/src/cpp/include/nodegui/core/Events/eventwidget.h +++ b/src/cpp/include/nodegui/core/Events/eventwidget.h @@ -16,8 +16,13 @@ class DLL_EXPORT EventWidget { void unSubscribeToQtEvent(std::string evtString); bool event(QEvent* event); + bool eventAfterDefault(QEvent* event, bool baseWidgetResult); virtual void connectSignalsToEventEmitter(); ~EventWidget(); -}; \ No newline at end of file + + private: + bool sendEventToNode(QEvent* event, bool afterBaseWidget, + bool baseWidgetResult); +}; diff --git a/src/cpp/include/nodegui/core/Events/eventwidget_macro.h b/src/cpp/include/nodegui/core/Events/eventwidget_macro.h index 3afaeb39b..b1e8f654b 100644 --- a/src/cpp/include/nodegui/core/Events/eventwidget_macro.h +++ b/src/cpp/include/nodegui/core/Events/eventwidget_macro.h @@ -99,12 +99,13 @@ struct InitHelper { #endif // EVENTWIDGET_WRAPPED_METHODS_EXPORT_DEFINE #ifndef EVENTWIDGET_IMPLEMENTATIONS -#define EVENTWIDGET_IMPLEMENTATIONS(BaseWidgetName) \ - bool event(QEvent* event) override { \ - if (EventWidget::event(event)) { \ - return true; \ - } \ - return BaseWidgetName::event(event); \ +#define EVENTWIDGET_IMPLEMENTATIONS(BaseWidgetName) \ + bool event(QEvent* event) override { \ + if (EventWidget::event(event)) { \ + return true; \ + } \ + bool baseWidgetResult = BaseWidgetName::event(event); \ + return EventWidget::eventAfterDefault(event, baseWidgetResult); \ } #endif // EVENTWIDGET_IMPLEMENTATIONS \ No newline at end of file diff --git a/src/cpp/lib/core/Events/eventwidget.cpp b/src/cpp/lib/core/Events/eventwidget.cpp index ca753af25..25a5e0711 100644 --- a/src/cpp/lib/core/Events/eventwidget.cpp +++ b/src/cpp/lib/core/Events/eventwidget.cpp @@ -29,6 +29,15 @@ void EventWidget::unSubscribeToQtEvent(std::string evtString) { } bool EventWidget::event(QEvent* event) { + return sendEventToNode(event, false, false); +} + +bool EventWidget::eventAfterDefault(QEvent* event, bool baseWidgetResult) { + return sendEventToNode(event, true, baseWidgetResult); +} + +bool EventWidget::sendEventToNode(QEvent* event, bool afterBaseWidget, + bool baseWidgetResult) { if (this->emitOnNode) { try { QEvent::Type evtType = event->type(); @@ -37,8 +46,10 @@ bool EventWidget::event(QEvent* event) { Napi::HandleScope scope(env); Napi::Value nativeEvent = Napi::External::New(env, event); - std::vector args = {Napi::String::New(env, eventTypeString), - nativeEvent}; + std::vector args = { + Napi::String::New(env, eventTypeString), nativeEvent, + Napi::Boolean::New(env, afterBaseWidget), + Napi::Boolean::New(env, baseWidgetResult)}; Napi::Value returnCode = this->emitOnNode.Call(args); return returnCode.As().Value(); @@ -46,7 +57,7 @@ bool EventWidget::event(QEvent* event) { // Do nothing } } - return false; + return baseWidgetResult; } void EventWidget::connectSignalsToEventEmitter() { diff --git a/src/lib/core/EventWidget.ts b/src/lib/core/EventWidget.ts index 583a5e5aa..34a7f3d95 100644 --- a/src/lib/core/EventWidget.ts +++ b/src/lib/core/EventWidget.ts @@ -7,6 +7,15 @@ function addDefaultErrorHandler(native: NativeElement, emitter: EventEmitter): v emitter.addListener('error', () => null); } +export interface EventListenerOptions { + /** + * This applies only when listening to QEvents. If set to true, then the callback will + * be called after the default processing by the base widget has occurred. By default + * callbacks for QEvents are called before the base widget `::event()` is called. + */ + afterDefault?: boolean; +} + /** > Abstract class that adds event handling support to all widgets. @@ -33,6 +42,7 @@ view.addEventListener(WidgetEventTypes.MouseMove, () => { }); ``` */ + export abstract class EventWidget extends Component { private emitter: EventEmitter; private _isEventProcessed = false; @@ -51,18 +61,43 @@ export abstract class EventWidget extends Component { this.emitter = new EventEmitter(); this.emitter.emit = wrapWithActivateUvLoop(this.emitter.emit.bind(this.emitter)); - const logExceptions = (event: string | symbol, ...args: any[]): boolean => { + const logExceptions = (eventName: string, ...args: any[]): boolean => { // Preserve the value of `_isQObjectEventProcessed` as we dispatch this event // to JS land, and restore it afterwards. This lets us support recursive event // dispatches on the same object. - const wrappedArgs = args.map(wrapNative); const previousEventProcessed = this._isEventProcessed; this._isEventProcessed = false; - try { - this.emitter.emit(event, ...wrappedArgs); - } catch (e) { - console.log(`An exception was thrown while dispatching an event of type '${event.toString()}':`); - console.log(e); + + // Events start with a capital letter, signals are lower case by convention. + const firstChar = eventName.charAt(0); + const isQEvent = firstChar.toUpperCase() === firstChar; + if (isQEvent) { + try { + const event = wrapNative(args[0]); + const afterBaseWidget = args[1]; + const baseWidgetResult = args[2]; + if (!afterBaseWidget) { + this.emitter.emit(eventName, event); + } else { + this._isEventProcessed = baseWidgetResult; + this.emitter.emit(`${eventName}_after`); + } + } catch (e) { + console.log( + `An exception was thrown while dispatching an event of type '${eventName.toString()}':`, + ); + console.log(e); + } + } else { + try { + const wrappedArgs = args.map(wrapNative); + this.emitter.emit(eventName, ...wrappedArgs); + } catch (e) { + console.log( + `An exception was thrown while dispatching a signal of type '${eventName.toString()}':`, + ); + console.log(e); + } } const returnCode = this._isEventProcessed; @@ -107,6 +142,7 @@ export abstract class EventWidget extends Component { * @param signalType SignalType is a signal from the widgets signals interface. @param callback Corresponding callback for the signal as mentioned in the widget's signal interface + @param options Extra optional options controlling how this event listener is added. @returns void For example in the case of QPushButton: @@ -116,23 +152,37 @@ export abstract class EventWidget extends Component { // here clicked is a value from QPushButtonSignals interface ``` */ - addEventListener(signalType: SignalType, callback: Signals[SignalType]): void; + addEventListener( + signalType: SignalType, + callback: Signals[SignalType], + options?: EventListenerOptions, + ): void; /** - @param eventType - @param callback + @param eventType + @param callback + @param options Extra optional options controlling how this event listener is added. - For example in the case of QPushButton: - ```js - const button = new QPushButton(); - button.addEventListener(WidgetEventTypes.HoverEnter,()=>console.log("hovered")); - ``` - */ - addEventListener(eventType: WidgetEventTypes, callback: (event?: NativeRawPointer<'QEvent'>) => void): void; - addEventListener(eventOrSignalType: string, callback: (...payloads: any[]) => void): void { + For example in the case of QPushButton: + ```js + const button = new QPushButton(); + button.addEventListener(WidgetEventTypes.HoverEnter,()=>console.log("hovered")); + ``` + */ + addEventListener( + eventType: WidgetEventTypes, + callback: (event?: NativeRawPointer<'QEvent'>) => void, + options?: EventListenerOptions, + ): void; + addEventListener( + eventOrSignalType: string, + callback: (...payloads: any[]) => void, + options?: EventListenerOptions, + ): void { + const eventOrSignalName = options?.afterDefault ? `${eventOrSignalType}_after` : eventOrSignalType; if (this.native.subscribeToQtEvent(eventOrSignalType)) { - this.emitter.addListener(eventOrSignalType, callback); + this.emitter.addListener(eventOrSignalName, callback); } else { try { throw new Error(); @@ -145,15 +195,29 @@ export abstract class EventWidget extends Component { } } - removeEventListener(signalType: SignalType, callback: Signals[SignalType]): void; - removeEventListener(eventType: WidgetEventTypes, callback: (event?: NativeRawPointer<'QEvent'>) => void): void; - removeEventListener(eventOrSignalType: string, callback?: (...payloads: any[]) => void): void { + removeEventListener( + signalType: SignalType, + callback: Signals[SignalType], + options?: EventListenerOptions, + ): void; + removeEventListener( + eventType: WidgetEventTypes, + callback: (event?: NativeRawPointer<'QEvent'>) => void, + options?: EventListenerOptions, + ): void; + removeEventListener( + eventOrSignalType: string, + callback?: (...payloads: any[]) => void, + options?: EventListenerOptions, + ): void { + const eventOrSignalTypeAfter = `${eventOrSignalType}_after`; + const registeredEventName = options?.afterDefault ? eventOrSignalTypeAfter : eventOrSignalType; if (callback) { - this.emitter.removeListener(eventOrSignalType, callback); + this.emitter.removeListener(registeredEventName, callback); } else { - this.emitter.removeAllListeners(eventOrSignalType); + this.emitter.removeAllListeners(registeredEventName); } - if (this.emitter.listenerCount(eventOrSignalType) < 1) { + if (this.emitter.listenerCount(eventOrSignalType) + this.emitter.listenerCount(eventOrSignalTypeAfter) === 0) { this.native.unSubscribeToQtEvent(eventOrSignalType); } }