5
0
mirror of https://github.com/wailsapp/wails.git synced 2025-05-19 02:19:31 +08:00

[v3] Initial hooks implementation

This commit is contained in:
Lea Anthony 2023-07-12 20:31:13 +10:00
parent ba7ab2e607
commit dc865404a9
No known key found for this signature in database
GPG Key ID: 33DAF7BB90A58405
9 changed files with 179 additions and 20 deletions

View File

@ -32,7 +32,7 @@ func main() {
})
// OS specific application events
app.On(events.Mac.ApplicationDidFinishLaunching, func() {
app.On(events.Mac.ApplicationDidFinishLaunching, func(event *application.Event) {
for {
log.Println("Sending event")
app.Events.Emit(&application.WailsEvent{
@ -44,7 +44,7 @@ func main() {
})
// Platform agnostic events
app.On(events.Common.ApplicationStarted, func() {
app.On(events.Common.ApplicationStarted, func(event *application.Event) {
println("events.Common.ApplicationStarted fired!")
})
@ -56,7 +56,7 @@ func main() {
InvisibleTitleBarHeight: 50,
},
})
app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{
win2 := app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{
Title: "Events Demo",
Mac: application.MacWindow{
Backdrop: application.MacBackdropTranslucent,
@ -65,6 +65,20 @@ func main() {
},
})
var cancel bool
win2.RegisterHook(events.Common.WindowFocus, func(e *application.WindowEvent) {
println("---------------\n[Hook] Window focus!")
cancel = !cancel
if cancel {
e.Cancel()
}
})
win2.On(events.Common.WindowFocus, func(e *application.WindowEventContext) {
println("[Event] Window focus!")
})
err := app.Run()
if err != nil {

View File

@ -22,7 +22,7 @@ func main() {
ApplicationShouldTerminateAfterLastWindowClosed: true,
},
})
app.On(events.Mac.ApplicationDidFinishLaunching, func() {
app.On(events.Mac.ApplicationDidFinishLaunching, func(event *application.Event) {
log.Println("ApplicationDidFinishLaunching")
})

View File

@ -35,7 +35,7 @@ func init() {
}
type EventListener struct {
callback func()
callback func(*Event)
}
func Get() *App {
@ -219,10 +219,16 @@ func (r *webViewAssetRequest) Header() (http.Header, error) {
var webviewRequests = make(chan *webViewAssetRequest)
type eventHook struct {
callback func(*Event)
}
type App struct {
options Options
applicationEventListeners map[uint][]*EventListener
applicationEventListenersLock sync.RWMutex
applicationEventHooks map[uint][]*eventHook
applicationEventHooksLock sync.RWMutex
// Windows
windows map[uint]*WebviewWindow
@ -293,7 +299,7 @@ func (a *App) Capabilities() capabilities.Capabilities {
return a.capabilities
}
func (a *App) On(eventType events.ApplicationEventType, callback func()) func() {
func (a *App) On(eventType events.ApplicationEventType, callback func(event *Event)) func() {
eventID := uint(eventType)
a.applicationEventListenersLock.Lock()
defer a.applicationEventListenersLock.Unlock()
@ -313,6 +319,25 @@ func (a *App) On(eventType events.ApplicationEventType, callback func()) func()
a.applicationEventListeners[eventID] = lo.Without(a.applicationEventListeners[eventID], listener)
}
}
// RegisterHook registers a hook for the given event type. Hooks are called before the event listeners and can cancel the event.
// The returned function can be called to remove the hook.
func (a *App) RegisterHook(eventType events.ApplicationEventType, callback func(event *Event)) func() {
eventID := uint(eventType)
a.applicationEventHooksLock.Lock()
defer a.applicationEventHooksLock.Unlock()
thisHook := &eventHook{
callback: callback,
}
a.applicationEventHooks[eventID] = append(a.applicationEventHooks[eventID], thisHook)
return func() {
a.applicationEventHooksLock.Lock()
a.applicationEventHooks[eventID] = lo.Without(a.applicationEventHooks[eventID], thisHook)
a.applicationEventHooksLock.Unlock()
}
}
func (a *App) NewWebviewWindow() *WebviewWindow {
return a.NewWebviewWindowWithOptions(WebviewWindowOptions{})
}
@ -468,8 +493,24 @@ func (a *App) handleApplicationEvent(event uint) {
if !ok {
return
}
thisEvent := &Event{}
// Process Hooks
a.applicationEventHooksLock.RLock()
hooks, ok := a.applicationEventHooks[event]
a.applicationEventHooksLock.RUnlock()
if ok {
for _, thisHook := range hooks {
thisHook.callback(thisEvent)
if thisEvent.Cancelled {
return
}
}
}
for _, listener := range listeners {
go listener.callback()
go listener.callback(thisEvent)
}
}
@ -506,7 +547,7 @@ func (a *App) handleWebViewRequest(request *webViewAssetRequest) {
a.assets.ServeWebViewRequest(request)
}
func (a *App) handleWindowEvent(event *WindowEvent) {
func (a *App) handleWindowEvent(event *windowEvent) {
// Get window from window map
a.windowsLock.Lock()
window, ok := a.windows[event.WindowID]

View File

@ -220,7 +220,7 @@ func processApplicationEvent(eventID C.uint) {
//export processWindowEvent
func processWindowEvent(windowID C.uint, eventID C.uint) {
windowEvents <- &WindowEvent{
windowEvents <- &windowEvent{
WindowID: uint(windowID),
EventID: uint(eventID),
}

View File

@ -130,7 +130,7 @@ func processApplicationEvent(eventID C.uint) {
//export processWindowEvent
func processWindowEvent(windowID C.uint, eventID C.uint) {
windowEvents <- &WindowEvent{
windowEvents <- &windowEvent{
WindowID: uint(windowID),
EventID: uint(eventID),
}

View File

@ -9,19 +9,32 @@ import (
var applicationEvents = make(chan uint)
type WindowEvent struct {
type windowEvent struct {
WindowID uint
EventID uint
}
var windowEvents = make(chan *WindowEvent)
type Event struct {
Cancelled bool
}
func (e *Event) Cancel() {
e.Cancelled = true
}
var windowEvents = make(chan *windowEvent)
var menuItemClicked = make(chan uint)
type WailsEvent struct {
Name string `json:"name"`
Data any `json:"data"`
Sender string `json:"sender"`
Name string `json:"name"`
Data any `json:"data"`
Sender string `json:"sender"`
Cancelled bool
}
func (e *WailsEvent) Cancel() {
e.Cancelled = true
}
var commonEvents = make(chan uint)
@ -35,6 +48,10 @@ func (e WailsEvent) ToJSON() string {
return string(marshal)
}
type hook struct {
callback func(*WailsEvent)
}
// eventListener holds a callback function which is invoked when
// the event listened for is emitted. It has a counter which indicates
// how the total number of events it is interested in. A value of zero
@ -51,12 +68,15 @@ type EventProcessor struct {
listeners map[string][]*eventListener
notifyLock sync.RWMutex
dispatchEventToWindows func(*WailsEvent)
hooks map[string][]*hook
hookLock sync.RWMutex
}
func NewWailsEventProcessor(dispatchEventToWindows func(*WailsEvent)) *EventProcessor {
return &EventProcessor{
listeners: make(map[string][]*eventListener),
dispatchEventToWindows: dispatchEventToWindows,
hooks: make(map[string][]*hook),
}
}
@ -80,6 +100,19 @@ func (e *EventProcessor) Emit(thisEvent *WailsEvent) {
if thisEvent == nil {
return
}
// If we have any hooks, run them first and check if the event was cancelled
if e.hooks != nil {
if hooks, ok := e.hooks[thisEvent.Name]; ok {
for _, thisHook := range hooks {
thisHook.callback(thisEvent)
if thisEvent.Cancelled {
return
}
}
}
}
go e.dispatchEventToListeners(thisEvent)
go e.dispatchEventToWindows(thisEvent)
}
@ -119,6 +152,29 @@ func (e *EventProcessor) registerListener(eventName string, callback func(*Wails
}
}
// RegisterHook provides a means of registering methods to be called before emitting the event
func (e *EventProcessor) RegisterHook(eventName string, callback func(*WailsEvent)) func() {
// Create new hook
thisHook := &hook{
callback: callback,
}
e.hookLock.Lock()
// Append the new listener to the listeners slice
e.hooks[eventName] = append(e.hooks[eventName], thisHook)
e.hookLock.Unlock()
return func() {
e.hookLock.Lock()
defer e.hookLock.Unlock()
if _, ok := e.hooks[eventName]; !ok {
return
}
e.hooks[eventName] = lo.Filter(e.hooks[eventName], func(l *hook, i int) bool {
return l != thisHook
})
}
}
// unRegisterListener provides a means of unsubscribing to events of type "eventName"
func (e *EventProcessor) unRegisterListener(eventName string) {
e.notifyLock.Lock()

View File

@ -182,7 +182,7 @@ func (s *windowsSystemTray) run() {
s.updateIcon()
// Listen for dark mode changes
globalApplication.On(events.Windows.SystemThemeChanged, func() {
globalApplication.On(events.Windows.SystemThemeChanged, func(event *Event) {
s.updateIcon()
})

View File

@ -74,10 +74,23 @@ type (
}
)
type WindowEvent struct {
WindowEventContext
Cancelled bool
}
func (w *WindowEvent) Cancel() {
w.Cancelled = true
}
type WindowEventListener struct {
callback func(ctx *WindowEventContext)
}
type windowHook struct {
callback func(ctx *WindowEvent)
}
type WebviewWindow struct {
options WebviewWindowOptions
impl webviewWindowImpl
@ -85,6 +98,8 @@ type WebviewWindow struct {
eventListeners map[uint][]*WindowEventListener
eventListenersLock sync.RWMutex
eventHooks map[uint][]*windowHook
eventHooksLock sync.RWMutex
contextMenus map[string]*Menu
contextMenusLock sync.RWMutex
@ -106,7 +121,7 @@ func getWindowID() uint {
// Use onApplicationEvent to register a callback for an application event from a window.
// This will handle tidying up the callback when the window is destroyed
func (w *WebviewWindow) onApplicationEvent(eventType events.ApplicationEventType, callback func()) {
func (w *WebviewWindow) onApplicationEvent(eventType events.ApplicationEventType, callback func(*Event)) {
cancelFn := globalApplication.On(eventType, callback)
w.addCancellationFunction(cancelFn)
}
@ -152,6 +167,7 @@ func NewWindow(options WebviewWindowOptions) *WebviewWindow {
options: options,
eventListeners: make(map[uint][]*WindowEventListener),
contextMenus: make(map[string]*Menu),
eventHooks: make(map[uint][]*windowHook),
}
result.setupEventMapping()
@ -553,12 +569,44 @@ func (w *WebviewWindow) On(eventType events.WindowEventType, callback func(ctx *
defer w.eventListenersLock.Unlock()
w.eventListeners[eventID] = lo.Without(w.eventListeners[eventID], windowEventListener)
}
}
// RegisterHook registers a hook for the given window event
func (w *WebviewWindow) RegisterHook(eventType events.WindowEventType, callback func(ctx *WindowEvent)) func() {
eventID := uint(eventType)
w.eventHooksLock.Lock()
defer w.eventHooksLock.Unlock()
windowEventHook := &windowHook{
callback: callback,
}
w.eventHooks[eventID] = append(w.eventHooks[eventID], windowEventHook)
return func() {
w.eventHooksLock.Lock()
defer w.eventHooksLock.Unlock()
w.eventHooks[eventID] = lo.Without(w.eventHooks[eventID], windowEventHook)
}
}
func (w *WebviewWindow) handleWindowEvent(id uint) {
w.eventListenersLock.RLock()
defer w.eventListenersLock.RUnlock()
// Get hooks
w.eventHooksLock.RLock()
hooks := w.eventHooks[id]
w.eventHooksLock.RUnlock()
// Create new WindowEvent
thisEvent := &WindowEvent{}
for _, thisHook := range hooks {
thisHook.callback(thisEvent)
if thisEvent.Cancelled {
return
}
}
for _, listener := range w.eventListeners[id] {
go listener.callback(blankWindowEventContext)
}
@ -932,7 +980,7 @@ func (w *WebviewWindow) Focus() {
}
func (w *WebviewWindow) emit(eventType events.WindowEventType) {
windowEvents <- &WindowEvent{
windowEvents <- &windowEvent{
WindowID: w.id,
EventID: uint(eventType),
}

View File

@ -262,7 +262,7 @@ func (w *windowsWebviewWindow) run() {
switch options.Windows.Theme {
case SystemDefault:
w.updateTheme(w32.IsCurrentlyDarkMode())
w.parent.onApplicationEvent(events.Windows.SystemThemeChanged, func() {
w.parent.onApplicationEvent(events.Windows.SystemThemeChanged, func(*Event) {
w.updateTheme(w32.IsCurrentlyDarkMode())
})
case Light:
@ -1475,7 +1475,7 @@ func (w *windowsWebviewWindow) flash(enabled bool) {
func (w *windowsWebviewWindow) navigationCompleted(sender *edge.ICoreWebView2, args *edge.ICoreWebView2NavigationCompletedEventArgs) {
// Emit DomReady Event
windowEvents <- &WindowEvent{EventID: uint(events.Windows.WebViewNavigationCompleted), WindowID: w.parent.id}
windowEvents <- &windowEvent{EventID: uint(events.Windows.WebViewNavigationCompleted), WindowID: w.parent.id}
if w.hasStarted {
// NavigationCompleted is triggered for every Load. If an application uses reloads the Hide/Show will trigger