5
0
mirror of https://github.com/wailsapp/wails.git synced 2025-05-05 00:12:45 +08:00
wails/v3/pkg/application/webview_window.go

696 lines
13 KiB
Go

package application
import (
"fmt"
"github.com/samber/lo"
"sync"
"time"
"github.com/wailsapp/wails/v3/pkg/logger"
"github.com/wailsapp/wails/v3/pkg/events"
)
type (
webviewWindowImpl interface {
setTitle(title string)
setSize(width, height int)
setAlwaysOnTop(alwaysOnTop bool)
setURL(url string)
setResizable(resizable bool)
setMinSize(width, height int)
setMaxSize(width, height int)
execJS(js string)
restore()
setBackgroundColour(color RGBA)
run()
center()
size() (int, int)
width() int
height() int
position() (int, int)
destroy()
reload()
forceReload()
toggleDevTools()
zoomReset()
zoomIn()
zoomOut()
getZoom() float64
setZoom(zoom float64)
close()
zoom()
setHTML(html string)
setPosition(x int, y int)
on(eventID uint)
minimise()
unminimise()
maximise()
unmaximise()
fullscreen()
unfullscreen()
isMinimised() bool
isMaximised() bool
isFullscreen() bool
disableSizeConstraints()
setFullscreenButtonEnabled(enabled bool)
show()
hide()
getScreen() (*Screen, error)
setFrameless(bool)
openContextMenu(menu *Menu, data *ContextMenuData)
}
)
type WindowEventListener struct {
callback func(ctx *WindowEventContext)
}
type WebviewWindow struct {
options *WebviewWindowOptions
impl webviewWindowImpl
implLock sync.RWMutex
id uint
eventListeners map[uint][]*WindowEventListener
eventListenersLock sync.RWMutex
contextMenus map[string]*Menu
contextMenusLock sync.RWMutex
// A map of listener cancellation functions
cancellersLock sync.RWMutex
cancellers []func()
}
var windowID uint
var windowIDLock sync.RWMutex
func getWindowID() uint {
windowIDLock.Lock()
defer windowIDLock.Unlock()
windowID++
return windowID
}
// 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()) {
cancelFn := globalApplication.On(eventType, callback)
w.addCancellationFunction(cancelFn)
}
func NewWindow(options *WebviewWindowOptions) *WebviewWindow {
if options.Width == 0 {
options.Width = 800
}
if options.Height == 0 {
options.Height = 600
}
if options.URL == "" {
options.URL = "/"
}
result := &WebviewWindow{
id: getWindowID(),
options: options,
eventListeners: make(map[uint][]*WindowEventListener),
contextMenus: make(map[string]*Menu),
}
return result
}
func (w *WebviewWindow) addCancellationFunction(canceller func()) {
w.cancellersLock.Lock()
defer w.cancellersLock.Unlock()
w.cancellers = append(w.cancellers, canceller)
}
func (w *WebviewWindow) SetTitle(title string) *WebviewWindow {
w.implLock.RLock()
defer w.implLock.RUnlock()
w.options.Title = title
if w.impl != nil {
w.impl.setTitle(title)
}
return w
}
func (w *WebviewWindow) Name() string {
return w.options.Name
}
func (w *WebviewWindow) SetSize(width, height int) *WebviewWindow {
// Don't set size if fullscreen
if w.IsFullscreen() {
return w
}
w.options.Width = width
w.options.Height = height
var newMaxWidth = w.options.MaxWidth
var newMaxHeight = w.options.MaxHeight
if width > w.options.MaxWidth && w.options.MaxWidth != 0 {
newMaxWidth = width
}
if height > w.options.MaxHeight && w.options.MaxHeight != 0 {
newMaxHeight = height
}
if newMaxWidth != 0 || newMaxHeight != 0 {
w.SetMaxSize(newMaxWidth, newMaxHeight)
}
var newMinWidth = w.options.MinWidth
var newMinHeight = w.options.MinHeight
if width < w.options.MinWidth && w.options.MinWidth != 0 {
newMinWidth = width
}
if height < w.options.MinHeight && w.options.MinHeight != 0 {
newMinHeight = height
}
if newMinWidth != 0 || newMinHeight != 0 {
w.SetMinSize(newMinWidth, newMinHeight)
}
if w.impl != nil {
w.impl.setSize(width, height)
}
return w
}
func (w *WebviewWindow) run() {
if w.impl != nil {
return
}
w.implLock.Lock()
w.impl = newWindowImpl(w)
w.implLock.Unlock()
w.impl.run()
}
func (w *WebviewWindow) SetAlwaysOnTop(b bool) *WebviewWindow {
w.options.AlwaysOnTop = b
if w.impl != nil {
w.impl.setAlwaysOnTop(b)
}
return w
}
func (w *WebviewWindow) Show() *WebviewWindow {
if globalApplication.impl == nil {
return w
}
if w.impl == nil {
w.run()
return w
}
w.impl.show()
return w
}
func (w *WebviewWindow) Hide() *WebviewWindow {
w.options.Hidden = true
if w.impl != nil {
w.impl.hide()
}
return w
}
func (w *WebviewWindow) SetURL(s string) *WebviewWindow {
w.options.URL = s
if w.impl != nil {
w.impl.setURL(s)
}
return w
}
func (w *WebviewWindow) SetZoom(magnification float64) *WebviewWindow {
w.options.Zoom = magnification
if w.impl != nil {
w.impl.setZoom(magnification)
}
return w
}
func (w *WebviewWindow) GetZoom() float64 {
if w.impl != nil {
return w.impl.getZoom()
}
return 1
}
func (w *WebviewWindow) SetResizable(b bool) *WebviewWindow {
w.options.DisableResize = !b
if w.impl != nil {
w.impl.setResizable(b)
}
return w
}
func (w *WebviewWindow) Resizable() bool {
return !w.options.DisableResize
}
func (w *WebviewWindow) SetMinSize(minWidth, minHeight int) *WebviewWindow {
w.options.MinWidth = minWidth
w.options.MinHeight = minHeight
currentWidth, currentHeight := w.Size()
newWidth, newHeight := currentWidth, currentHeight
var newSize bool
if minHeight != 0 && currentHeight < minHeight {
newHeight = minHeight
w.options.Height = newHeight
newSize = true
}
if minWidth != 0 && currentWidth < minWidth {
newWidth = minWidth
w.options.Width = newWidth
newSize = true
}
if w.impl != nil {
if newSize {
w.impl.setSize(newWidth, newHeight)
}
w.impl.setMinSize(minWidth, minHeight)
}
return w
}
func (w *WebviewWindow) SetMaxSize(maxWidth, maxHeight int) *WebviewWindow {
w.options.MaxWidth = maxWidth
w.options.MaxHeight = maxHeight
currentWidth, currentHeight := w.Size()
newWidth, newHeight := currentWidth, currentHeight
var newSize bool
if maxHeight != 0 && currentHeight > maxHeight {
newHeight = maxHeight
w.options.Height = maxHeight
newSize = true
}
if maxWidth != 0 && currentWidth > maxWidth {
newWidth = maxWidth
w.options.Width = maxWidth
newSize = true
}
if w.impl != nil {
if newSize {
w.impl.setSize(newWidth, newHeight)
}
w.impl.setMaxSize(maxWidth, maxHeight)
}
return w
}
func (w *WebviewWindow) ExecJS(js string) {
if w.impl == nil {
return
}
w.impl.execJS(js)
}
func (w *WebviewWindow) Fullscreen() *WebviewWindow {
if w.impl == nil {
w.options.StartState = WindowStateFullscreen
return w
}
if !w.IsFullscreen() {
w.disableSizeConstraints()
w.impl.fullscreen()
}
return w
}
func (w *WebviewWindow) SetFullscreenButtonEnabled(enabled bool) *WebviewWindow {
w.options.FullscreenButtonEnabled = enabled
if w.impl != nil {
w.impl.setFullscreenButtonEnabled(enabled)
}
return w
}
// IsMinimised returns true if the window is minimised
func (w *WebviewWindow) IsMinimised() bool {
if w.impl == nil {
return false
}
return w.impl.isMinimised()
}
// IsMaximised returns true if the window is maximised
func (w *WebviewWindow) IsMaximised() bool {
if w.impl == nil {
return false
}
return w.impl.isMaximised()
}
// Size returns the size of the window
func (w *WebviewWindow) Size() (width int, height int) {
if w.impl == nil {
return 0, 0
}
return w.impl.size()
}
// IsFullscreen returns true if the window is fullscreen
func (w *WebviewWindow) IsFullscreen() bool {
w.implLock.RLock()
defer w.implLock.RUnlock()
if w.impl == nil {
return false
}
return w.impl.isFullscreen()
}
func (w *WebviewWindow) SetBackgroundColour(colour RGBA) *WebviewWindow {
w.options.BackgroundColour = colour
if w.impl != nil {
w.impl.setBackgroundColour(colour)
}
return w
}
func (w *WebviewWindow) handleMessage(message string) {
w.info(message)
// Check for special messages
if message == "test" {
w.SetTitle("Hello World")
}
w.info("ProcessMessage from front end:", message)
}
func (w *WebviewWindow) Center() {
if w.impl == nil {
return
}
w.impl.center()
}
func (w *WebviewWindow) On(eventType events.WindowEventType, callback func(ctx *WindowEventContext)) func() {
eventID := uint(eventType)
w.eventListenersLock.Lock()
defer w.eventListenersLock.Unlock()
windowEventListener := &WindowEventListener{
callback: callback,
}
w.eventListeners[eventID] = append(w.eventListeners[eventID], windowEventListener)
if w.impl != nil {
w.impl.on(eventID)
}
return func() {
w.eventListenersLock.Lock()
defer w.eventListenersLock.Unlock()
w.eventListeners[eventID] = lo.Without(w.eventListeners[eventID], windowEventListener)
}
}
func (w *WebviewWindow) handleWindowEvent(id uint) {
w.eventListenersLock.RLock()
for _, listener := range w.eventListeners[id] {
go listener.callback(blankWindowEventContext)
}
w.eventListenersLock.RUnlock()
}
func (w *WebviewWindow) Width() int {
if w.impl == nil {
return 0
}
return w.impl.width()
}
func (w *WebviewWindow) Height() int {
if w.impl == nil {
return 0
}
return w.impl.height()
}
func (w *WebviewWindow) Position() (int, int) {
w.implLock.RLock()
defer w.implLock.RUnlock()
if w.impl == nil {
return 0, 0
}
return w.impl.position()
}
func (w *WebviewWindow) Destroy() {
if w.impl == nil {
return
}
// Cancel the callbacks
for _, cancelFunc := range w.cancellers {
cancelFunc()
}
w.impl.destroy()
}
func (w *WebviewWindow) Reload() {
if w.impl == nil {
return
}
w.impl.reload()
}
func (w *WebviewWindow) ForceReload() {
if w.impl == nil {
return
}
w.impl.forceReload()
}
func (w *WebviewWindow) ToggleFullscreen() {
if w.impl == nil {
return
}
if w.IsFullscreen() {
w.UnFullscreen()
} else {
w.Fullscreen()
}
}
func (w *WebviewWindow) ToggleDevTools() {
if w.impl == nil {
return
}
w.impl.toggleDevTools()
}
func (w *WebviewWindow) ZoomReset() *WebviewWindow {
if w.impl != nil {
w.impl.zoomReset()
}
return w
}
func (w *WebviewWindow) ZoomIn() {
if w.impl == nil {
return
}
w.impl.zoomIn()
}
func (w *WebviewWindow) ZoomOut() {
if w.impl == nil {
return
}
w.impl.zoomOut()
}
func (w *WebviewWindow) Close() {
if w.impl == nil {
return
}
w.impl.close()
}
func (w *WebviewWindow) Minimize() {
if w.impl == nil {
return
}
w.impl.minimise()
}
func (w *WebviewWindow) Zoom() {
if w.impl == nil {
return
}
w.impl.zoom()
}
func (w *WebviewWindow) SetHTML(html string) *WebviewWindow {
w.options.HTML = html
if w.impl != nil {
w.impl.setHTML(html)
}
return w
}
func (w *WebviewWindow) SetPosition(x, y int) *WebviewWindow {
w.options.X = x
w.options.Y = y
if w.impl != nil {
w.impl.setPosition(x, y)
}
return w
}
func (w *WebviewWindow) Minimise() *WebviewWindow {
if w.impl == nil {
w.options.StartState = WindowStateMinimised
return w
}
if !w.IsMinimised() {
w.impl.minimise()
}
return w
}
func (w *WebviewWindow) Maximise() *WebviewWindow {
if w.impl == nil {
w.options.StartState = WindowStateMaximised
return w
}
if !w.IsMaximised() {
w.disableSizeConstraints()
w.impl.maximise()
}
return w
}
func (w *WebviewWindow) UnMinimise() {
if w.impl == nil {
return
}
w.impl.unminimise()
}
func (w *WebviewWindow) UnMaximise() {
if w.impl == nil {
return
}
w.enableSizeConstraints()
w.impl.unmaximise()
}
func (w *WebviewWindow) UnFullscreen() {
if w.impl == nil {
return
}
w.enableSizeConstraints()
w.impl.unfullscreen()
}
func (w *WebviewWindow) Restore() {
if w.impl == nil {
return
}
if w.IsMinimised() {
w.UnMinimise()
} else if w.IsMaximised() {
w.UnMaximise()
} else if w.IsFullscreen() {
w.UnFullscreen()
}
}
func (w *WebviewWindow) disableSizeConstraints() {
if w.impl == nil {
return
}
w.impl.setMinSize(0, 0)
w.impl.setMaxSize(0, 0)
}
func (w *WebviewWindow) enableSizeConstraints() {
if w.impl == nil {
return
}
w.SetMinSize(w.options.MinWidth, w.options.MinHeight)
w.SetMaxSize(w.options.MaxWidth, w.options.MaxHeight)
}
func (w *WebviewWindow) GetScreen() (*Screen, error) {
if w.impl == nil {
return nil, nil
}
return w.impl.getScreen()
}
func (w *WebviewWindow) SetFrameless(frameless bool) *WebviewWindow {
w.options.Frameless = frameless
if w.impl != nil {
w.impl.setFrameless(frameless)
}
return w
}
func (w *WebviewWindow) dispatchWailsEvent(event *WailsEvent) {
msg := fmt.Sprintf("_wails.dispatchWailsEvent(%s);", event.ToJSON())
w.ExecJS(msg)
}
func (w *WebviewWindow) info(message string, args ...any) {
globalApplication.Log(&logger.Message{
Level: "INFO",
Message: message,
Data: args,
Sender: w.Name(),
Time: time.Now(),
})
}
func (w *WebviewWindow) error(message string, args ...any) {
globalApplication.Log(&logger.Message{
Level: "ERROR",
Message: message,
Data: args,
Sender: w.Name(),
Time: time.Now(),
})
}
func (w *WebviewWindow) handleDragAndDropMessage(event *dragAndDropMessage) {
ctx := newWindowEventContext()
ctx.setDroppedFiles(event.filenames)
for _, listener := range w.eventListeners[uint(events.FilesDropped)] {
listener.callback(ctx)
}
}
func (w *WebviewWindow) openContextMenu(data *ContextMenuData) {
menu, ok := w.contextMenus[data.Id]
if !ok {
// try application level context menu
menu, ok = globalApplication.getContextMenu(data.Id)
if !ok {
w.error("No context menu found for id: %s", data.Id)
return
}
}
menu.setContextData(data)
if w.impl == nil {
return
}
w.impl.openContextMenu(menu, data)
}
func (w *WebviewWindow) RegisterContextMenu(name string, menu *Menu) {
w.contextMenusLock.Lock()
defer w.contextMenusLock.Unlock()
w.contextMenus[name] = menu
}