wails/v3/pkg/application/event_manager.go
Andrey Pshenkin 561473d992
[V3] Refactor binding transport layer (#4702)
* custom transport initial

* transport codecs

* runtime set transport

* events transport

* clauded example

* bundled runtime

* wip: transport

* rework transports

* rework dialog responses

* cleanup

* cleanup

* improve error handling in HTTPTransport

* cleanup

* cleanup

* cleanup

* cleanup

* review changes

* review changes

* review changes

* review changes

* review changes

* review changes

* review changes

* move documentation to website docs

* update doc

* update changelog

* introduce JSClient method for transport for embedding JS part in transport

---------

Co-authored-by: Atterpac <Capretta.Michael@gmail.com>
Co-authored-by: Lea Anthony <lea.anthony@gmail.com>
2025-12-07 22:19:12 +11:00

171 lines
5.2 KiB
Go

package application
import (
"slices"
"github.com/samber/lo"
"github.com/wailsapp/wails/v3/pkg/events"
)
// EventManager manages event-related operations
type EventManager struct {
app *App
}
// newEventManager creates a new EventManager instance
func newEventManager(app *App) *EventManager {
return &EventManager{
app: app,
}
}
// Emit emits a custom event with the specified name and associated data.
// It returns a boolean indicating whether the event was cancelled by a hook.
//
// If no data argument is provided, Emit emits an event with nil data.
// When there is exactly one data argument, it will be used as the custom event's data field.
// When more than one argument is provided, the event's data field will be set to the argument slice.
//
// If the given event name is registered, Emit validates the data parameter
// against the expected data type. In case of a mismatch, Emit reports an error
// to the registered error handler for the application and cancels the event.
func (em *EventManager) Emit(name string, data ...any) bool {
event := &CustomEvent{Name: name}
if len(data) == 1 {
event.Data = data[0]
} else if len(data) > 1 {
event.Data = data
}
if err := em.app.customEventProcessor.Emit(event); err != nil {
globalApplication.handleError(err)
}
return event.IsCancelled()
}
// EmitEvent emits a custom event object (internal use)
// It returns a boolean indicating whether the event was cancelled by a hook.
//
// If the given event name is registered, emitEvent validates the data parameter
// against the expected data type. In case of a mismatch, emitEvent reports an error
// to the registered error handler for the application and cancels the event.
func (em *EventManager) EmitEvent(event *CustomEvent) bool {
if err := em.app.customEventProcessor.Emit(event); err != nil {
globalApplication.handleError(err)
}
return event.IsCancelled()
}
// On registers a listener for custom events
func (em *EventManager) On(name string, callback func(event *CustomEvent)) func() {
return em.app.customEventProcessor.On(name, callback)
}
// Off removes all listeners for a custom event
func (em *EventManager) Off(name string) {
em.app.customEventProcessor.Off(name)
}
// OnMultiple registers a listener for custom events that will be called N times
func (em *EventManager) OnMultiple(name string, callback func(event *CustomEvent), counter int) {
em.app.customEventProcessor.OnMultiple(name, callback, counter)
}
// Reset removes all custom event listeners
func (em *EventManager) Reset() {
em.app.customEventProcessor.OffAll()
}
// OnApplicationEvent registers a listener for application events
func (em *EventManager) OnApplicationEvent(eventType events.ApplicationEventType, callback func(event *ApplicationEvent)) func() {
eventID := uint(eventType)
em.app.applicationEventListenersLock.Lock()
defer em.app.applicationEventListenersLock.Unlock()
listener := &EventListener{
callback: callback,
}
em.app.applicationEventListeners[eventID] = append(em.app.applicationEventListeners[eventID], listener)
if em.app.impl != nil {
go func() {
defer handlePanic()
em.app.impl.on(eventID)
}()
}
return func() {
// lock the map
em.app.applicationEventListenersLock.Lock()
defer em.app.applicationEventListenersLock.Unlock()
// Remove listener
em.app.applicationEventListeners[eventID] = lo.Without(em.app.applicationEventListeners[eventID], listener)
}
}
// RegisterApplicationEventHook registers an application event hook
func (em *EventManager) RegisterApplicationEventHook(eventType events.ApplicationEventType, callback func(event *ApplicationEvent)) func() {
eventID := uint(eventType)
em.app.applicationEventHooksLock.Lock()
defer em.app.applicationEventHooksLock.Unlock()
thisHook := &eventHook{
callback: callback,
}
em.app.applicationEventHooks[eventID] = append(em.app.applicationEventHooks[eventID], thisHook)
return func() {
em.app.applicationEventHooksLock.Lock()
em.app.applicationEventHooks[eventID] = lo.Without(em.app.applicationEventHooks[eventID], thisHook)
em.app.applicationEventHooksLock.Unlock()
}
}
// Dispatch dispatches an event to listeners (internal use)
func (em *EventManager) dispatch(event *CustomEvent) {
// Snapshot listeners under Lock
em.app.wailsEventListenerLock.Lock()
listeners := slices.Clone(em.app.wailsEventListeners)
em.app.wailsEventListenerLock.Unlock()
for _, listener := range listeners {
if event.IsCancelled() {
return
}
listener.DispatchWailsEvent(event)
}
}
// HandleApplicationEvent handles application events (internal use)
func (em *EventManager) handleApplicationEvent(event *ApplicationEvent) {
defer handlePanic()
em.app.applicationEventListenersLock.RLock()
listeners, ok := em.app.applicationEventListeners[event.Id]
em.app.applicationEventListenersLock.RUnlock()
if !ok {
return
}
// Process Hooks
em.app.applicationEventHooksLock.RLock()
hooks, ok := em.app.applicationEventHooks[event.Id]
em.app.applicationEventHooksLock.RUnlock()
if ok {
for _, thisHook := range hooks {
thisHook.callback(event)
if event.IsCancelled() {
return
}
}
}
for _, listener := range listeners {
go func() {
if event.IsCancelled() {
return
}
defer handlePanic()
listener.callback(event)
}()
}
}