diff --git a/FyneApp.toml b/FyneApp.toml
index 9fa0938..bd9ab98 100644
--- a/FyneApp.toml
+++ b/FyneApp.toml
@@ -4,5 +4,5 @@ Website = "https://github.com/adamk33n3r/GoBorderless"
Icon = "res/icon.ico"
Name = "GoBorderless"
ID = "com.adamk33n3r.goborderless"
- Version = "1.3.1"
- Build = 79
+ Version = "1.3.2"
+ Build = 81
diff --git a/assets/fullscreen.svg b/assets/fullscreen.svg
new file mode 100644
index 0000000..ab31caf
--- /dev/null
+++ b/assets/fullscreen.svg
@@ -0,0 +1,3 @@
+
\ No newline at end of file
diff --git a/assets/halfleft.svg b/assets/halfleft.svg
new file mode 100644
index 0000000..d36a30e
--- /dev/null
+++ b/assets/halfleft.svg
@@ -0,0 +1,5 @@
+
\ No newline at end of file
diff --git a/assets/halfright.svg b/assets/halfright.svg
new file mode 100644
index 0000000..d36a30e
--- /dev/null
+++ b/assets/halfright.svg
@@ -0,0 +1,5 @@
+
\ No newline at end of file
diff --git a/go.sum b/go.sum
index 4288ad5..4a397d6 100644
--- a/go.sum
+++ b/go.sum
@@ -1,7 +1,5 @@
fyne.io/systray v1.11.0 h1:D9HISlxSkx+jHSniMBR6fCFOUjk1x/OOOJLa9lJYAKg=
fyne.io/systray v1.11.0/go.mod h1:RVwqP9nYMo7h5zViCBHri2FgjXF7H2cub7MAq4NSoLs=
-github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0=
-github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg=
github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
github.com/adamk33n3r/fyne/v2 v2.7.0 h1:S5OCzr/l4GNY7SEdJBP1s89NPT9qoJLqMLm5jttcnyQ=
@@ -14,8 +12,6 @@ github.com/felixge/fgprof v0.9.3 h1:VvyZxILNuCiUCSXtPtYmmtGvb65nqXh2QFWc0Wpf2/g=
github.com/felixge/fgprof v0.9.3/go.mod h1:RdbpDgzqYVh/T9fPELJyV7EYJuHB55UTEULNun8eiPw=
github.com/fredbi/uri v1.1.0 h1:OqLpTXtyRg9ABReqvDGdJPqZUxs8cyBDOMXBbskCaB8=
github.com/fredbi/uri v1.1.0/go.mod h1:aYTUoAXBOq7BLfVJ8GnKmfcuURosB1xyHDIfWeC/iW4=
-github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
-github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
github.com/fyne-io/gl-js v0.1.0 h1:8luJzNs0ntEAJo+8x8kfUOXujUlP8gB3QMOxO2mUdpM=
@@ -28,14 +24,10 @@ github.com/fyne-io/oksvg v0.1.0 h1:7EUKk3HV3Y2E+qypp3nWqMXD7mum0hCw2KEGhI1fnBw=
github.com/fyne-io/oksvg v0.1.0/go.mod h1:dJ9oEkPiWhnTFNCmRgEze+YNprJF7YRbpjgpWS4kzoI=
github.com/go-gl/gl v0.0.0-20231021071112-07e5d0ea2e71 h1:5BVwOaUSBTlVZowGO6VZGw2H/zl9nrd3eCZfYV+NfQA=
github.com/go-gl/gl v0.0.0-20231021071112-07e5d0ea2e71/go.mod h1:9YTyiznxEY1fVinfM7RvRcjRHbw2xLBJ3AAGIT0I4Nw=
-github.com/go-gl/glfw/v3.3/glfw v0.0.0-20240506104042-037f3cc74f2a h1:vxnBhFDDT+xzxf1jTJKMKZw3H0swfWk9RpWbBbDK5+0=
-github.com/go-gl/glfw/v3.3/glfw v0.0.0-20240506104042-037f3cc74f2a/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20250301202403-da16c1255728 h1:RkGhqHxEVAvPM0/R+8g7XRwQnHatO0KAuVcwHo8q9W8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20250301202403-da16c1255728/go.mod h1:SyRD8YfuKk+ZXlDqYiqe1qMSqjNgtHzBTG810KUagMc=
github.com/go-text/render v0.2.0 h1:LBYoTmp5jYiJ4NPqDc2pz17MLmA3wHw1dZSVGcOdeAc=
github.com/go-text/render v0.2.0/go.mod h1:CkiqfukRGKJA5vZZISkjSYrcdtgKQWRa2HIzvwNN5SU=
-github.com/go-text/typesetting v0.2.1 h1:x0jMOGyO3d1qFAPI0j4GSsh7M0Q3Ypjzr4+CEVg82V8=
-github.com/go-text/typesetting v0.2.1/go.mod h1:mTOxEwasOFpAMBjEQDhdWRckoLLeI/+qrQeBCTGEt6M=
github.com/go-text/typesetting v0.3.0 h1:OWCgYpp8njoxSRpwrdd1bQOxdjOXDj9Rqart9ML4iF4=
github.com/go-text/typesetting v0.3.0/go.mod h1:qjZLkhRgOEYMhU9eHBr3AR4sfnGJvOXNLt8yRAySFuY=
github.com/go-text/typesetting-utils v0.0.0-20241103174707-87a29e9e6066 h1:qCuYC+94v2xrb1PoS4NIDe7DGYtLnU2wWiQe9a1B1c0=
@@ -46,14 +38,12 @@ github.com/google/pprof v0.0.0-20211214055906-6f57359322fd h1:1FjCyPC+syAzJ5/2S8
github.com/google/pprof v0.0.0-20211214055906-6f57359322fd/go.mod h1:KgnwoLYCZ8IQu3XUZ8Nc/bM9CCZFOyjUNOSygVozoDg=
github.com/hack-pad/go-indexeddb v0.3.2 h1:DTqeJJYc1usa45Q5r52t01KhvlSN02+Oq+tQbSBI91A=
github.com/hack-pad/go-indexeddb v0.3.2/go.mod h1:QvfTevpDVlkfomY498LhstjwbPW6QC4VC/lxYb0Kom0=
-github.com/hack-pad/safejs v0.1.0 h1:qPS6vjreAqh2amUqj4WNG1zIw7qlRQJ9K10eDKMCnE8=
-github.com/hack-pad/safejs v0.1.0/go.mod h1:HdS+bKF1NrE72VoXZeWzxFOVQVUSqZJAG0xNCnb+Tio=
github.com/hack-pad/safejs v0.1.1 h1:d5qPO0iQ7h2oVtpzGnLExE+Wn9AtytxIfltcS2b9KD8=
github.com/hack-pad/safejs v0.1.1/go.mod h1:HdS+bKF1NrE72VoXZeWzxFOVQVUSqZJAG0xNCnb+Tio=
-github.com/jeandeaual/go-locale v0.0.0-20241217141322-fcc2cadd6f08 h1:wMeVzrPO3mfHIWLZtDcSaGAe2I4PW9B/P5nMkRSwCAc=
-github.com/jeandeaual/go-locale v0.0.0-20241217141322-fcc2cadd6f08/go.mod h1:ZDXo8KHryOWSIqnsb/CiDq7hQUYryCgdVnxbj8tDG7o=
github.com/jeandeaual/go-locale v0.0.0-20250421151639-a9d6ed1b3d45 h1:vFdvrlsVU+p/KFBWTq0lTG4fvWvG88sawGlCzM+RUEU=
github.com/jeandeaual/go-locale v0.0.0-20250421151639-a9d6ed1b3d45/go.mod h1:ZDXo8KHryOWSIqnsb/CiDq7hQUYryCgdVnxbj8tDG7o=
+github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
+github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/jsummers/gobmp v0.0.0-20230614200233-a9de23ed2e25 h1:YLvr1eE6cdCqjOe972w/cYF+FjW34v27+9Vo5106B4M=
github.com/jsummers/gobmp v0.0.0-20230614200233-a9de23ed2e25/go.mod h1:kLgvv7o6UM+0QSf0QjAse3wReFDsb9qbZJdfexWlrQw=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
@@ -62,8 +52,6 @@ github.com/lxn/win v0.0.0-20210218163916-a377121e959e h1:H+t6A/QJMbhCSEH5rAuRxh+
github.com/lxn/win v0.0.0-20210218163916-a377121e959e/go.mod h1:KxxjdtRkfNoYDCUP5ryK7XJJNTnpC8atvtmTheChOtk=
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ=
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
-github.com/nicksnyder/go-i18n/v2 v2.5.1 h1:IxtPxYsR9Gp60cGXjfuR/llTqV8aYMsC472zD0D1vHk=
-github.com/nicksnyder/go-i18n/v2 v2.5.1/go.mod h1:DrhgsSDZxoAfvVrBVLXoxZn/pN5TXqaDbq7ju94viiQ=
github.com/nicksnyder/go-i18n/v2 v2.6.0 h1:C/m2NNWNiTB6SK4Ao8df5EWm3JETSTIGNXBpMJTxzxQ=
github.com/nicksnyder/go-i18n/v2 v2.6.0/go.mod h1:88sRqr0C6OPyJn0/KRNaEz1uWorjxIKP7rUUcvycecE=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
@@ -80,23 +68,15 @@ github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef h1:Ch6Q+AZUxDBCVqd
github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef/go.mod h1:nXTWP6+gD5+LUJ8krVhhoeHjvHTutPxMYl5SvkcnJNE=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
-github.com/yuin/goldmark v1.7.8 h1:iERMLn0/QJeHFhxSt3p6PeN9mGnvIKSpG9YYorDMnic=
-github.com/yuin/goldmark v1.7.8/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E=
github.com/yuin/goldmark v1.7.12 h1:YwGP/rrea2/CnCtUHgjuolG/PnMxdQtPMO5PvaE2/nY=
github.com/yuin/goldmark v1.7.12/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg=
-golang.org/x/image v0.24.0 h1:AN7zRgVsbvmTfNyqIbbOraYL8mSwcKncEj8ofjgzcMQ=
-golang.org/x/image v0.24.0/go.mod h1:4b/ITuLfqYq1hqZcjofwctIhi7sZh2WaCjvsBNjjya8=
golang.org/x/image v0.28.0 h1:gdem5JW1OLS4FbkWgLO+7ZeFzYtL3xClb97GaUzYMFE=
golang.org/x/image v0.28.0/go.mod h1:GUJYXtnGKEUgggyzh+Vxt+AviiCcyiwpsl8iQ8MvwGY=
-golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8=
-golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw=
golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA=
golang.org/x/sys v0.0.0-20201018230417-eeed37f84f13/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
-golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
-golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M=
golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
diff --git a/gui_app.go b/gui_app.go
index 9d0e67e..69d9bc8 100644
--- a/gui_app.go
+++ b/gui_app.go
@@ -42,6 +42,16 @@ func intValidator(s string) error {
return nil
}
+func offsetIntValidator(s string) error {
+ if s == "" {
+ return nil
+ }
+ if _, err := strconv.Atoi(s); err != nil {
+ return fmt.Errorf("invalid number")
+ }
+ return nil
+}
+
var settingsList *widget.List
var currentWindows = make([]Window, 0) // Temporary list to store window titles
diff --git a/gui_appsetting.go b/gui_appsetting.go
index 287199e..c43d028 100644
--- a/gui_appsetting.go
+++ b/gui_appsetting.go
@@ -1,6 +1,7 @@
package main
import (
+ _ "embed"
"fmt"
"reflect"
"slices"
@@ -29,6 +30,10 @@ var (
widthText *widget.Entry
heightText *widget.Entry
confirmButton *widget.Button
+
+ halfLeftBtn *widget.Button
+ halfRightBtn *widget.Button
+ fullBtn *widget.Button
)
func isValid(isNew bool) bool {
@@ -71,46 +76,23 @@ func getWindowsForSelect(allWindows []Window) []Window {
// This will also filter out windows that we've already removed borders from
// Perhaps we should also check the list of existing configs?
for _, window := range allWindows {
- style := getWindowStyle(window.hwnd)
- if style&win.WS_CAPTION > 0 &&
- ((style&win.WS_BORDER) > 0 || (style&win.WS_THICKFRAME) > 0) {
+ if isValidWindowForSelection(window) {
copyOfWindows = append(copyOfWindows, window)
}
}
- slices.SortFunc(copyOfWindows, func(a Window, b Window) int {
+ slices.SortFunc(copyOfWindows, func(a, b Window) int {
return strings.Compare(strings.ToLower(a.String()), strings.ToLower(b.String()))
})
return copyOfWindows
}
-func makeAppSettingWindow(settings *Settings, appSetting AppSetting, isNew bool, parent fyne.Window, onClose func(newSetting *AppSetting)) *dialog.CustomDialog {
- currentWindowsMutex.Lock()
- windowsForSelect := getWindowsForSelect(currentWindows)
- currentWindowsMutex.Unlock()
-
- var appSettingDialog *dialog.CustomDialog
- var windowSub rx.Subscription
-
- confirmButton = widget.NewButtonWithIcon("Create", theme.ConfirmIcon(), func() {
- if isNew {
- windowSub.Unsubscribe()
- }
- appSettingDialog.Hide()
- onClose(&appSetting)
- })
- confirmButton.Importance = widget.HighImportance
- confirmButton.Disable()
- cancelButton := widget.NewButtonWithIcon("Cancel", theme.CancelIcon(), func() {
- if isNew {
- windowSub.Unsubscribe()
- }
- appSettingDialog.Hide()
- onClose(nil)
- })
- if !isNew {
- confirmButton.SetText("Save")
- }
+func isValidWindowForSelection(window Window) bool {
+ style := getWindowStyle(window.hwnd)
+ return style&win.WS_CAPTION > 0 &&
+ ((style&win.WS_BORDER) > 0 || (style&win.WS_THICKFRAME) > 0)
+}
+func createApplicationSelect(windowsForSelect []Window, appSetting *AppSetting, isNew bool) *ui.Select[Window] {
applicationSelect = ui.NewSelect(windowsForSelect, func(selected Window) {
if slices.Index(windowsForSelect, selected) == -1 {
fmt.Println("Selected application no longer exists in the updated window list, resetting selection.")
@@ -125,26 +107,40 @@ func makeAppSettingWindow(settings *Settings, appSetting AppSetting, isNew bool,
})
applicationSelect.PlaceHolder = "Select Application"
- monitorIdx := appSetting.Monitor - 1
- if isNew {
- monitorIdx = settings.Defaults.Monitor - 1
- if monitorIdx < 0 {
- monitorIdx = slices.IndexFunc(monitors, func(m Monitor) bool {
- return m.isPrimary
- })
- }
+ return applicationSelect
+}
+
+func getDefaultMonitorIndex(settings *Settings, appSetting AppSetting, isNew bool) int {
+ if !isNew {
+ return appSetting.Monitor - 1
}
+
+ monitorIdx := settings.Defaults.Monitor - 1
+ if monitorIdx < 0 {
+ monitorIdx = slices.IndexFunc(monitors, func(m Monitor) bool {
+ return m.isPrimary
+ })
+ }
+
+ return monitorIdx
+}
+
+func createDisplaySelect(settings *Settings, appSetting *AppSetting, isNew bool) *ui.Select[Monitor] {
+ monitorIdx := getDefaultMonitorIndex(settings, *appSetting, isNew)
+
displaySelect = ui.NewSelect(monitors, func(selected Monitor) {
appSetting.Monitor = selected.number
-
setConfirmButtonState(isNew)
})
displaySelect.PlaceHolder = "Select Display"
displaySelect.SetSelectedIndex(monitorIdx)
+ return displaySelect
+}
+
+func createMatchTypeRadio(settings *Settings, appSetting *AppSetting, isNew bool) *widget.RadioGroup {
matchType = widget.NewRadioGroup(matchTypes, func(selected string) {
appSetting.MatchType = GetMatchTypeFromString(selected)
-
setConfirmButtonState(isNew)
})
if isNew {
@@ -155,89 +151,101 @@ func makeAppSettingWindow(settings *Settings, appSetting AppSetting, isNew bool,
matchType.Horizontal = true
matchType.Required = true
- // Textboxes with labels
- xOffsetLabel := widget.NewLabel("X Offset:")
- xOffsetText = widget.NewEntry()
- xOffsetText.Validator = intValidator
- xOffsetText.OnChanged = func(s string) {
- appSetting.OffsetX = entryTextToInt(s)
+ return matchType
+}
- setConfirmButtonState(isNew)
+func createSizeButton(monitor Monitor, xDivider int32, xOffset int32, label string, icon fyne.Resource) *widget.Button {
+ monitorWidth := monitor.width / xDivider
+ offsetWidth := int32(0)
+ if xOffset == 1 {
+ offsetWidth = monitorWidth
}
- setOnFocusChanged(xOffsetText, func(focused bool) {
- if focused {
- xOffsetText.DoubleTapped(&fyne.PointEvent{})
- }
+
+ return widget.NewButtonWithIcon(label, icon, func() {
+ widthText.SetText(strconv.Itoa(int(monitorWidth)))
+ heightText.SetText(strconv.Itoa(int(monitor.height)))
+ xOffsetText.SetText(strconv.Itoa(int(offsetWidth)))
+ yOffsetText.SetText("0")
})
- xOffsetText.SetPlaceHolder("0")
- if isNew {
- xOffsetText.SetText(strconv.Itoa(int(settings.Defaults.OffsetX)))
- } else {
- xOffsetText.SetText(strconv.Itoa(int(appSetting.OffsetX)))
- }
+}
- yOffsetLabel := widget.NewLabel("Y Offset:")
- yOffsetText = widget.NewEntry()
- yOffsetText.Validator = intValidator
- yOffsetText.OnChanged = func(s string) {
- appSetting.OffsetY = entryTextToInt(s)
+func createOffsetEntry(label string, defaultValue int32, settings *Settings, appSetting *AppSetting, isNew bool, updateField func(int32)) (*widget.Label, *widget.Entry) {
+ labelWidget := widget.NewLabel(label)
+ entry := widget.NewEntry()
+ entry.Validator = offsetIntValidator
+ entry.OnChanged = func(s string) {
+ if s == "" {
+ updateField(0)
+ } else {
+ updateField(entryTextToInt(s))
+ }
setConfirmButtonState(isNew)
}
- setOnFocusChanged(yOffsetText, func(focused bool) {
+ setOnFocusChanged(entry, func(focused bool) {
if focused {
- yOffsetText.DoubleTapped(&fyne.PointEvent{})
+ entry.DoubleTapped(&fyne.PointEvent{})
}
})
- yOffsetText.SetPlaceHolder("0")
+
+ entry.SetPlaceHolder("0")
if isNew {
- yOffsetText.SetText(strconv.Itoa(int(settings.Defaults.OffsetY)))
+ entry.SetText(strconv.Itoa(int(defaultValue)))
} else {
- yOffsetText.SetText(strconv.Itoa(int(appSetting.OffsetY)))
+ entry.SetText(strconv.Itoa(int(defaultValue)))
}
- widthLabel := widget.NewLabel("Width:")
- widthText = widget.NewEntry()
- widthText.Validator = intValidator
- widthText.OnChanged = func(s string) {
- appSetting.Width = entryTextToInt(s)
+ return labelWidget, entry
+}
+
+func createSizeEntry(label string, placeholder string, defaultValue int32, settings *Settings, appSetting *AppSetting, isNew bool, updateField func(int32)) (*widget.Label, *widget.Entry) {
+ labelWidget := widget.NewLabel(label)
+ entry := widget.NewEntry()
+ entry.Validator = intValidator
+ entry.OnChanged = func(s string) {
+ updateField(entryTextToInt(s))
setConfirmButtonState(isNew)
}
- setOnFocusChanged(widthText, func(focused bool) {
+
+ setOnFocusChanged(entry, func(focused bool) {
if focused {
- widthText.DoubleTapped(&fyne.PointEvent{})
+ entry.DoubleTapped(&fyne.PointEvent{})
}
})
- widthText.SetPlaceHolder("1920")
+
+ entry.SetPlaceHolder(placeholder)
if isNew {
- widthText.SetText(strconv.Itoa(int(settings.Defaults.Width)))
+ entry.SetText(strconv.Itoa(int(defaultValue)))
} else {
- widthText.SetText(strconv.Itoa(int(appSetting.Width)))
+ entry.SetText(strconv.Itoa(int(defaultValue)))
}
- heightLabel := widget.NewLabel("Height:")
- heightText = widget.NewEntry()
- heightText.Validator = intValidator
- heightText.OnChanged = func(s string) {
- appSetting.Height = entryTextToInt(s)
+ return labelWidget, entry
+}
- setConfirmButtonState(isNew)
- }
- setOnFocusChanged(heightText, func(focused bool) {
- if focused {
- heightText.DoubleTapped(&fyne.PointEvent{})
- }
+func createTextGrid(settings *Settings, appSetting *AppSetting, isNew bool) *fyne.Container {
+ xOffsetLabel, xOffsetEntry := createOffsetEntry("X Offset:", settings.Defaults.OffsetX, settings, appSetting, isNew, func(val int32) {
+ appSetting.OffsetX = val
})
- heightText.SetPlaceHolder("1080")
- if isNew {
- heightText.SetText(strconv.Itoa(int(settings.Defaults.Height)))
- } else {
- heightText.SetText(strconv.Itoa(int(appSetting.Height)))
- }
+ xOffsetText = xOffsetEntry
- // 2x2 grid for labeled textboxes
- textGrid := container.NewGridWithRows(2,
+ yOffsetLabel, yOffsetEntry := createOffsetEntry("Y Offset:", settings.Defaults.OffsetY, settings, appSetting, isNew, func(val int32) {
+ appSetting.OffsetY = val
+ })
+ yOffsetText = yOffsetEntry
+
+ widthLabel, widthEntry := createSizeEntry("Width:", "1920", settings.Defaults.Width, settings, appSetting, isNew, func(val int32) {
+ appSetting.Width = val
+ })
+ widthText = widthEntry
+
+ heightLabel, heightEntry := createSizeEntry("Height:", "1080", settings.Defaults.Height, settings, appSetting, isNew, func(val int32) {
+ appSetting.Height = val
+ })
+ heightText = heightEntry
+
+ return container.NewGridWithRows(2,
container.NewGridWithColumns(2,
container.NewVBox(xOffsetLabel, xOffsetText),
container.NewVBox(yOffsetLabel, yOffsetText),
@@ -247,40 +255,121 @@ func makeAppSettingWindow(settings *Settings, appSetting AppSetting, isNew bool,
container.NewVBox(heightLabel, heightText),
),
)
+}
- if isNew {
- fmt.Println("subscribing to windows observable")
- // TODO: make it work like subject where it outputs last received data on subscription
+func subscribeToWindowUpdates(windowsForSelect []Window, isNew bool) rx.Subscription {
+ if !isNew {
+ return rx.Subscription{}
+ }
+
+ fmt.Println("subscribing to windows observable")
+ // TODO: make it work like subject where it outputs last received data on subscription
+
+ return windowObs.Subscribe(func(windows []Window) {
+ if len(windows) == 0 {
+ // This is probably a fluke, so let's skip it
+ return
+ }
+
+ fyne.Do(func() {
+ windowsForSelect = getWindowsForSelect(windows)
+ applicationSelect.SetOptions(windowsForSelect)
- windowSub = windowObs.Subscribe(func(windows []Window) {
- if len(windows) == 0 {
- // This is probably a fluke, so let's skip it
- return
+ if applicationSelect.Selected != nil && slices.Index(windowsForSelect, *applicationSelect.Selected) == -1 {
+ fmt.Println("Selected application no longer exists in the updated window list, resetting selection.")
+ applicationSelect.ClearSelected()
}
- fyne.Do(func() {
- windowsForSelect = getWindowsForSelect(windows)
- applicationSelect.SetOptions(windowsForSelect)
-
- if applicationSelect.Selected != nil && slices.Index(windowsForSelect, *applicationSelect.Selected) == -1 {
- fmt.Println("Selected application no longer exists in the updated window list, resetting selection.")
- applicationSelect.ClearSelected()
- }
- })
})
- }
+ })
+}
+
+func createPresetsContent(settings *Settings, appSetting *AppSetting, isNew bool, cancelButton, confirmBtn *widget.Button) *fyne.Container {
+ presetsRow := container.NewCenter(
+ container.NewHBox(
+ halfLeftBtn,
+ widget.NewLabel(" "),
+ halfRightBtn,
+ widget.NewLabel(" "),
+ fullBtn,
+ ),
+ )
content := container.NewVBox(
displaySelect,
widget.NewLabel("Match Type"),
matchType,
- textGrid,
+ widget.NewLabel("Presets:"),
+ presetsRow,
+ createTextGrid(settings, appSetting, isNew),
widget.NewLabel(""), // spacer
- container.NewHBox(cancelButton, layout.NewSpacer(), confirmButton),
+ container.NewHBox(cancelButton, layout.NewSpacer(), confirmBtn),
)
+
if isNew {
content.Objects = append([]fyne.CanvasObject{applicationSelect}, content.Objects...)
}
+ return content
+}
+
+//go:embed assets/fullscreen.svg
+var fullscreenSVGBytes []byte
+
+//go:embed assets/halfleft.svg
+var halfLeftSVGBytes []byte
+
+//go:embed assets/halfright.svg
+var halfRightSVGBytes []byte
+
+func makeAppSettingWindow(settings *Settings, appSetting AppSetting, isNew bool, parent fyne.Window, onClose func(newSetting *AppSetting)) *dialog.CustomDialog {
+ currentWindowsMutex.Lock()
+ windowsForSelect := getWindowsForSelect(currentWindows)
+ currentWindowsMutex.Unlock()
+
+ var appSettingDialog *dialog.CustomDialog
+ var windowSub rx.Subscription
+
+ leftIcon := fyne.NewStaticResource("right.svg", halfLeftSVGBytes)
+ rightIcon := fyne.NewStaticResource("right.svg", halfRightSVGBytes)
+ fullIcon := fyne.NewStaticResource("full.svg", fullscreenSVGBytes)
+
+ monitorIdx := getDefaultMonitorIndex(settings, appSetting, isNew)
+ selectedMonitor := monitors[monitorIdx]
+
+ halfLeftBtn = createSizeButton(selectedMonitor, 2, 0, "Half Left", leftIcon)
+ halfRightBtn = createSizeButton(selectedMonitor, 2, 1, "Half Right", rightIcon)
+ fullBtn = createSizeButton(selectedMonitor, 1, 0, "Full", fullIcon)
+
+ confirmButton = widget.NewButtonWithIcon("Create", theme.ConfirmIcon(), func() {
+ if isNew {
+ windowSub.Unsubscribe()
+ }
+ appSettingDialog.Hide()
+ onClose(&appSetting)
+ })
+ confirmButton.Importance = widget.HighImportance
+ confirmButton.Disable()
+
+ if !isNew {
+ confirmButton.SetText("Save")
+ }
+
+ cancelButton := widget.NewButtonWithIcon("Cancel", theme.CancelIcon(), func() {
+ if isNew {
+ windowSub.Unsubscribe()
+ }
+ appSettingDialog.Hide()
+ onClose(nil)
+ })
+
+ applicationSelect = createApplicationSelect(windowsForSelect, &appSetting, isNew)
+ displaySelect = createDisplaySelect(settings, &appSetting, isNew)
+ matchType = createMatchTypeRadio(settings, &appSetting, isNew)
+
+ windowSub = subscribeToWindowUpdates(windowsForSelect, isNew)
+
+ content := createPresetsContent(settings, &appSetting, isNew, cancelButton, confirmButton)
+
dialogName := "New App Config"
if !isNew {
dialogName = appSetting.Display()
diff --git a/winapi.go b/winapi.go
index 2659456..11dd143 100644
--- a/winapi.go
+++ b/winapi.go
@@ -107,24 +107,73 @@ func (m Monitor) String() string {
if m.isPrimary {
str += " (Primary)"
}
+ str += fmt.Sprintf(" | %dx%d", m.width, m.height)
return str
}
+var (
+ procEnumDisplaySettingsW = user32.NewProc("EnumDisplaySettingsW")
+)
+
+type DEVMODE struct {
+ DmDeviceName [32]uint16
+ DmSpecVersion uint16
+ DmDriverVersion uint16
+ DmSize uint16
+ DmDriverExtra uint16
+ DmFields uint32
+ DmPosition struct{ X, Y int32 }
+ DmDisplayOrientation uint32
+ DmDisplayFixedOutput uint32
+ DmColor int16
+ DmDuplex int16
+ DmYResolution int16
+ DmTTOption int16
+ DmCollate int16
+ DmFormName [32]uint16
+ DmLogPixels uint16
+ DmBitsPerPel uint32
+ DmPelsWidth uint32
+ DmPelsHeight uint32
+}
+
func getMonitors() []Monitor {
var monitors []Monitor
index := 0
+
cb := syscall.NewCallback(func(hMonitor win.HMONITOR, hdcMonitor win.HDC, lprcMonitor *win.RECT, dwData uintptr) uintptr {
- var info win.MONITORINFO
- info.CbSize = uint32(unsafe.Sizeof(info))
- if win.GetMonitorInfo(hMonitor, &info) {
+ var infoEx struct {
+ win.MONITORINFO
+ SzDevice [win.CCHDEVICENAME]uint16
+ }
+ infoEx.CbSize = uint32(unsafe.Sizeof(infoEx))
+
+ if win.GetMonitorInfo(hMonitor, (*win.MONITORINFO)(unsafe.Pointer(&infoEx))) {
+ var devMode DEVMODE
+ devMode.DmSize = uint16(unsafe.Sizeof(devMode))
+
+ ret, _, _ := procEnumDisplaySettingsW.Call(
+ uintptr(unsafe.Pointer(&infoEx.SzDevice[0])),
+ uintptr(0xFFFFFFFF),
+ uintptr(unsafe.Pointer(&devMode)),
+ )
+
+ width := int32(devMode.DmPelsWidth)
+ height := int32(devMode.DmPelsHeight)
+
+ if ret == 0 || width == 0 || height == 0 {
+ width = infoEx.RcMonitor.Right - infoEx.RcMonitor.Left
+ height = infoEx.RcMonitor.Bottom - infoEx.RcMonitor.Top
+ }
+
index++
monitors = append(monitors, Monitor{
number: index,
- isPrimary: info.DwFlags&win.MONITORINFOF_PRIMARY != 0,
- width: info.RcMonitor.Right - info.RcMonitor.Left,
- height: info.RcMonitor.Bottom - info.RcMonitor.Top,
- left: info.RcMonitor.Left,
- top: info.RcMonitor.Top,
+ isPrimary: infoEx.DwFlags&win.MONITORINFOF_PRIMARY != 0,
+ width: width,
+ height: height,
+ left: infoEx.RcMonitor.Left,
+ top: infoEx.RcMonitor.Top,
})
}
return 1