mirror of
https://github.com/wailsapp/wails.git
synced 2025-05-10 22:19:46 +08:00
[windows] Split out wndProc. Generate windows events, support per-window themes
This commit is contained in:
parent
7c63cee9e8
commit
178ea9c8c5
@ -136,6 +136,7 @@ var (
|
||||
procSetWindowsHookEx = moduser32.NewProc("SetWindowsHookExW")
|
||||
procUnhookWindowsHookEx = moduser32.NewProc("UnhookWindowsHookEx")
|
||||
procCallNextHookEx = moduser32.NewProc("CallNextHookEx")
|
||||
procGetForegroundWindow = moduser32.NewProc("GetForegroundWindow")
|
||||
|
||||
procSystemParametersInfo = moduser32.NewProc("SystemParametersInfoW")
|
||||
procSetClassLong = moduser32.NewProc("SetClassLongW")
|
||||
@ -301,6 +302,11 @@ func GetDpiForWindow(hwnd HWND) UINT {
|
||||
return uint(dpi)
|
||||
}
|
||||
|
||||
func GetForegroundWindow() HWND {
|
||||
ret, _, _ := procGetForegroundWindow.Call()
|
||||
return HWND(ret)
|
||||
}
|
||||
|
||||
func SetWindowCompositionAttribute(hwnd HWND, data *WINDOWCOMPOSITIONATTRIBDATA) bool {
|
||||
if procSetWindowCompositionAttribute != nil {
|
||||
ret, _, _ := procSetWindowCompositionAttribute.Call(
|
||||
|
@ -3,6 +3,7 @@
|
||||
package application
|
||||
|
||||
import (
|
||||
"github.com/wailsapp/wails/v3/pkg/events"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
@ -17,8 +18,13 @@ type windowsApp struct {
|
||||
|
||||
instance w32.HINSTANCE
|
||||
|
||||
windowMap map[w32.HWND]*windowsWebviewWindow
|
||||
|
||||
mainThreadID w32.HANDLE
|
||||
mainThreadWindowHWND w32.HWND
|
||||
|
||||
// system theme
|
||||
isDarkMode bool
|
||||
}
|
||||
|
||||
func (m *windowsApp) getPrimaryScreen() (*Screen, error) {
|
||||
@ -111,28 +117,57 @@ func (m *windowsApp) init() {
|
||||
if ret := w32.RegisterClassEx(&wc); ret == 0 {
|
||||
panic(syscall.GetLastError())
|
||||
}
|
||||
|
||||
m.isDarkMode = w32.IsCurrentlyDarkMode()
|
||||
}
|
||||
|
||||
func (m *windowsApp) wndProc(hwnd w32.HWND, msg uint32, wParam, lParam uintptr) uintptr {
|
||||
switch msg {
|
||||
case w32.WM_SIZE:
|
||||
|
||||
// Handle the invoke callback
|
||||
if msg == wmInvokeCallback {
|
||||
m.invokeCallback(wParam, lParam)
|
||||
return 0
|
||||
case w32.WM_CLOSE:
|
||||
w32.PostQuitMessage(0)
|
||||
return 0
|
||||
case wmInvokeCallback:
|
||||
if hwnd == m.mainThreadWindowHWND {
|
||||
m.invokeCallback(wParam, lParam)
|
||||
return 0
|
||||
}
|
||||
}
|
||||
switch msg {
|
||||
case w32.WM_SETTINGCHANGE:
|
||||
settingChanged := w32.UTF16PtrToString((*uint16)(unsafe.Pointer(lParam)))
|
||||
if settingChanged == "ImmersiveColorSet" {
|
||||
isDarkMode := w32.IsCurrentlyDarkMode()
|
||||
if isDarkMode != m.isDarkMode {
|
||||
applicationEvents <- uint(events.Windows.SystemThemeChanged)
|
||||
m.isDarkMode = isDarkMode
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
if window, ok := m.windowMap[hwnd]; ok {
|
||||
return window.WndProc(msg, wParam, lParam)
|
||||
}
|
||||
|
||||
// Dispatch the message to the appropriate window
|
||||
|
||||
return w32.DefWindowProc(hwnd, msg, wParam, lParam)
|
||||
}
|
||||
|
||||
func (m *windowsApp) registerWindow(result *windowsWebviewWindow) {
|
||||
m.windowMap[result.hwnd] = result
|
||||
}
|
||||
|
||||
func (m *windowsApp) unregisterWindow(w *windowsWebviewWindow) {
|
||||
delete(m.windowMap, w.hwnd)
|
||||
|
||||
// If this was the last window...
|
||||
if len(m.windowMap) == 0 {
|
||||
w32.PostQuitMessage(0)
|
||||
}
|
||||
}
|
||||
|
||||
func newPlatformApp(app *App) *windowsApp {
|
||||
result := &windowsApp{
|
||||
parent: app,
|
||||
instance: w32.GetModuleHandle(""),
|
||||
parent: app,
|
||||
instance: w32.GetModuleHandle(""),
|
||||
windowMap: make(map[w32.HWND]*windowsWebviewWindow),
|
||||
}
|
||||
|
||||
result.init()
|
||||
|
@ -15,4 +15,36 @@ type WindowsWindow struct {
|
||||
BackdropType BackdropType
|
||||
// Disable the icon in the titlebar
|
||||
DisableIcon bool
|
||||
// Theme. Defaults to SystemDefault which will use whatever the system theme is. The application will follow system theme changes.
|
||||
Theme Theme
|
||||
// Custom colours for dark/light mode
|
||||
CustomTheme *ThemeSettings
|
||||
}
|
||||
|
||||
type Theme int
|
||||
|
||||
const (
|
||||
// SystemDefault will use whatever the system theme is. The application will follow system theme changes.
|
||||
SystemDefault Theme = 0
|
||||
// Dark Mode
|
||||
Dark Theme = 1
|
||||
// Light Mode
|
||||
Light Theme = 2
|
||||
)
|
||||
|
||||
// ThemeSettings defines custom colours to use in dark or light mode.
|
||||
// They may be set using the hex values: 0x00BBGGRR
|
||||
type ThemeSettings struct {
|
||||
DarkModeTitleBar int32
|
||||
DarkModeTitleBarInactive int32
|
||||
DarkModeTitleText int32
|
||||
DarkModeTitleTextInactive int32
|
||||
DarkModeBorder int32
|
||||
DarkModeBorderInactive int32
|
||||
LightModeTitleBar int32
|
||||
LightModeTitleBarInactive int32
|
||||
LightModeTitleText int32
|
||||
LightModeTitleTextInactive int32
|
||||
LightModeBorder int32
|
||||
LightModeBorderInactive int32
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/wailsapp/wails/v3/internal/w32"
|
||||
"github.com/wailsapp/wails/v3/pkg/events"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
@ -107,6 +108,10 @@ func (w *windowsWebviewWindow) _run() {
|
||||
panic("Unable to create window")
|
||||
}
|
||||
|
||||
// Register the window with the application
|
||||
windowsApp := globalApplication.impl.(*windowsApp)
|
||||
windowsApp.registerWindow(w)
|
||||
|
||||
if options.DisableResize {
|
||||
w.setResizable(false)
|
||||
}
|
||||
@ -129,12 +134,25 @@ func (w *windowsWebviewWindow) _run() {
|
||||
case BackgroundTypeTranslucent:
|
||||
w.setBackdropType(options.Windows.BackdropType)
|
||||
}
|
||||
w.setForeground()
|
||||
|
||||
// Process the theme
|
||||
switch options.Windows.Theme {
|
||||
case SystemDefault:
|
||||
w.updateTheme(w32.IsCurrentlyDarkMode())
|
||||
globalApplication.On(events.Windows.SystemThemeChanged, func() {
|
||||
w.updateTheme(w32.IsCurrentlyDarkMode())
|
||||
})
|
||||
case Light:
|
||||
w.updateTheme(false)
|
||||
case Dark:
|
||||
w.updateTheme(true)
|
||||
}
|
||||
|
||||
if !options.Hidden {
|
||||
w.show()
|
||||
w.update()
|
||||
}
|
||||
w.setForeground()
|
||||
}
|
||||
|
||||
func (w *windowsWebviewWindow) center() {
|
||||
@ -365,7 +383,7 @@ func (w *windowsWebviewWindow) setBackdropType(backdropType BackdropType) {
|
||||
w32.SetWindowCompositionAttribute(w.hwnd, &data)
|
||||
} else {
|
||||
backdropValue := backdropType
|
||||
// We default to None, but in win32 None = 1 and Auto = 0
|
||||
// We default to None, but in w32 None = 1 and Auto = 0
|
||||
// So we check if the value given was Auto and set it to 0
|
||||
if backdropType == Auto {
|
||||
backdropValue = None
|
||||
@ -393,6 +411,65 @@ func (w *windowsWebviewWindow) disableIcon() {
|
||||
)
|
||||
}
|
||||
|
||||
func (w *windowsWebviewWindow) updateTheme(isDarkMode bool) {
|
||||
|
||||
if w32.IsCurrentlyHighContrastMode() {
|
||||
return
|
||||
}
|
||||
|
||||
if !w32.SupportsThemes() {
|
||||
return
|
||||
}
|
||||
|
||||
w32.SetTheme(w.hwnd, isDarkMode)
|
||||
|
||||
// Custom theme processing
|
||||
customTheme := w.parent.options.Windows.CustomTheme
|
||||
// Custom theme
|
||||
if w32.SupportsCustomThemes() && customTheme != nil {
|
||||
if w.isActive() {
|
||||
if isDarkMode {
|
||||
w32.SetTitleBarColour(w.hwnd, customTheme.DarkModeTitleBar)
|
||||
w32.SetTitleTextColour(w.hwnd, customTheme.DarkModeTitleText)
|
||||
w32.SetBorderColour(w.hwnd, customTheme.DarkModeBorder)
|
||||
} else {
|
||||
w32.SetTitleBarColour(w.hwnd, customTheme.LightModeTitleBar)
|
||||
w32.SetTitleTextColour(w.hwnd, customTheme.LightModeTitleText)
|
||||
w32.SetBorderColour(w.hwnd, customTheme.LightModeBorder)
|
||||
}
|
||||
} else {
|
||||
if isDarkMode {
|
||||
w32.SetTitleBarColour(w.hwnd, customTheme.DarkModeTitleBarInactive)
|
||||
w32.SetTitleTextColour(w.hwnd, customTheme.DarkModeTitleTextInactive)
|
||||
w32.SetBorderColour(w.hwnd, customTheme.DarkModeBorderInactive)
|
||||
} else {
|
||||
w32.SetTitleBarColour(w.hwnd, customTheme.LightModeTitleBarInactive)
|
||||
w32.SetTitleTextColour(w.hwnd, customTheme.LightModeTitleTextInactive)
|
||||
w32.SetBorderColour(w.hwnd, customTheme.LightModeBorderInactive)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (w *windowsWebviewWindow) isActive() bool {
|
||||
return w32.GetForegroundWindow() == w.hwnd
|
||||
}
|
||||
|
||||
func (w *windowsWebviewWindow) WndProc(msg uint32, wparam, lparam uintptr) uintptr {
|
||||
switch msg {
|
||||
case w32.WM_SIZE:
|
||||
return 0
|
||||
case w32.WM_CLOSE:
|
||||
w32.PostMessage(w.hwnd, w32.WM_QUIT, 0, 0)
|
||||
// Unregister the window with the application
|
||||
windowsApp := globalApplication.impl.(*windowsApp)
|
||||
windowsApp.unregisterWindow(w)
|
||||
return 0
|
||||
default:
|
||||
return w32.DefWindowProc(w.hwnd, msg, wparam, lparam)
|
||||
}
|
||||
}
|
||||
|
||||
func NewIconFromResource(instance w32.HINSTANCE, resId uint16) (w32.HICON, error) {
|
||||
var err error
|
||||
var result w32.HICON
|
||||
|
@ -260,3 +260,15 @@ func newMacEvents() macEvents {
|
||||
WindowFileDraggingExited: 1145,
|
||||
}
|
||||
}
|
||||
|
||||
var Windows = newWindowsEvents()
|
||||
|
||||
type windowsEvents struct {
|
||||
SystemThemeChanged ApplicationEventType
|
||||
}
|
||||
|
||||
func newWindowsEvents() windowsEvents {
|
||||
return windowsEvents{
|
||||
SystemThemeChanged: 1146,
|
||||
}
|
||||
}
|
||||
|
@ -120,4 +120,4 @@ mac:WebViewDidCommitNavigation
|
||||
mac:WindowFileDraggingEntered
|
||||
mac:WindowFileDraggingPerformed
|
||||
mac:WindowFileDraggingExited
|
||||
|
||||
windows:SystemThemeChanged
|
@ -25,6 +25,18 @@ func newMacEvents() macEvents {
|
||||
return macEvents{
|
||||
$$MACEVENTSVALUES }
|
||||
}
|
||||
|
||||
var Windows = newWindowsEvents()
|
||||
|
||||
type windowsEvents struct {
|
||||
$$WINDOWSEVENTSDECL}
|
||||
|
||||
func newWindowsEvents() windowsEvents {
|
||||
return windowsEvents{
|
||||
$$WINDOWSEVENTSVALUES }
|
||||
}
|
||||
|
||||
|
||||
`
|
||||
|
||||
var eventsH = `//go:build darwin
|
||||
@ -53,7 +65,11 @@ func main() {
|
||||
applicationDelegateEvents := bytes.NewBufferString("")
|
||||
webviewDelegateEvents := bytes.NewBufferString("")
|
||||
|
||||
windowsEventsDecl := bytes.NewBufferString("")
|
||||
windowsEventsValues := bytes.NewBufferString("")
|
||||
|
||||
var id int
|
||||
var maxMacEvents int
|
||||
var line []byte
|
||||
// Loop over each line in the file
|
||||
for id, line = range bytes.Split(eventNames, []byte{'\n'}) {
|
||||
@ -94,6 +110,7 @@ func main() {
|
||||
macEventsDecl.WriteString("\t" + eventTitle + " " + eventType + "\n")
|
||||
macEventsValues.WriteString("\t\t" + event + ": " + strconv.Itoa(id) + ",\n")
|
||||
cHeaderEvents.WriteString("#define Event" + eventTitle + " " + strconv.Itoa(id) + "\n")
|
||||
maxMacEvents = id
|
||||
if ignoreEvent {
|
||||
continue
|
||||
}
|
||||
@ -128,15 +145,61 @@ func main() {
|
||||
|
||||
`)
|
||||
}
|
||||
|
||||
case "windows":
|
||||
eventType := "ApplicationEventType"
|
||||
if strings.HasPrefix(event, "Window") {
|
||||
eventType = "WindowEventType"
|
||||
}
|
||||
if strings.HasPrefix(event, "WebView") {
|
||||
eventType = "WindowEventType"
|
||||
}
|
||||
windowsEventsDecl.WriteString("\t" + eventTitle + " " + eventType + "\n")
|
||||
windowsEventsValues.WriteString("\t\t" + event + ": " + strconv.Itoa(id) + ",\n")
|
||||
// cHeaderEvents.WriteString("#define Event" + eventTitle + " " + strconv.Itoa(id) + "\n")
|
||||
// if ignoreEvent {
|
||||
// continue
|
||||
// }
|
||||
// // Check if this is a window event
|
||||
// if strings.HasPrefix(event, "Window") {
|
||||
// windowDelegateEvents.WriteString(`- (void)` + delegateEventFunction + `:(NSNotification *)notification {
|
||||
// if( hasListeners(Event` + eventTitle + `) ) {
|
||||
// processWindowEvent(self.windowId, Event` + eventTitle + `);
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//`)
|
||||
// }
|
||||
// // Check if this is a webview event
|
||||
// if strings.HasPrefix(event, "WebView") {
|
||||
// webViewFunction := strings.TrimPrefix(event, "WebView")
|
||||
// webViewFunction = string(bytes.ToLower([]byte{webViewFunction[0]})) + webViewFunction[1:]
|
||||
// webviewDelegateEvents.WriteString(`- (void)webView:(WKWebView *)webview ` + webViewFunction + `:(WKNavigation *)navigation {
|
||||
// if( hasListeners(Event` + eventTitle + `) ) {
|
||||
// processWindowEvent(self.windowId, Event` + eventTitle + `);
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//`)
|
||||
// }
|
||||
// if strings.HasPrefix(event, "Application") {
|
||||
// applicationDelegateEvents.WriteString(`- (void)` + delegateEventFunction + `:(NSNotification *)notification {
|
||||
// if( hasListeners(Event` + eventTitle + `) ) {
|
||||
// processApplicationEvent(Event` + eventTitle + `);
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//`)
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
cHeaderEvents.WriteString("\n#define MAX_EVENTS " + strconv.Itoa(id-1) + "\n")
|
||||
cHeaderEvents.WriteString("\n#define MAX_EVENTS " + strconv.Itoa(maxMacEvents+1) + "\n")
|
||||
|
||||
// Save the eventsGo template substituting the values and decls
|
||||
templateToWrite := strings.ReplaceAll(eventsGo, "$$MACEVENTSDECL", macEventsDecl.String())
|
||||
templateToWrite = strings.ReplaceAll(templateToWrite, "$$MACEVENTSVALUES", macEventsValues.String())
|
||||
templateToWrite = strings.ReplaceAll(templateToWrite, "$$WINDOWSEVENTSDECL", windowsEventsDecl.String())
|
||||
templateToWrite = strings.ReplaceAll(templateToWrite, "$$WINDOWSEVENTSVALUES", windowsEventsValues.String())
|
||||
err = os.WriteFile("../../pkg/events/events.go", []byte(templateToWrite), 0644)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
|
Loading…
Reference in New Issue
Block a user