feat: support named switchable layouts
This commit is contained in:
+48
-13
@@ -12,13 +12,14 @@ import (
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
Version int `yaml:"version"`
|
||||
Gap int `yaml:"gap"`
|
||||
ShiftDrag bool `yaml:"shift_drag"`
|
||||
Overlay Overlay `yaml:"overlay"`
|
||||
Layouts []Layout `yaml:"layouts"`
|
||||
Excluded []string `yaml:"excluded_apps"`
|
||||
Hotkeys []Hotkey `yaml:"hotkeys"`
|
||||
Version int `yaml:"version"`
|
||||
Gap int `yaml:"gap"`
|
||||
ShiftDrag bool `yaml:"shift_drag"`
|
||||
ActiveLayout string `yaml:"active_layout"`
|
||||
Overlay Overlay `yaml:"overlay"`
|
||||
Layouts []Layout `yaml:"layouts"`
|
||||
Excluded []string `yaml:"excluded_apps"`
|
||||
Hotkeys []Hotkey `yaml:"hotkeys"`
|
||||
}
|
||||
|
||||
type Overlay struct {
|
||||
@@ -29,6 +30,7 @@ type Overlay struct {
|
||||
}
|
||||
|
||||
type Layout struct {
|
||||
Name string `yaml:"name"`
|
||||
Monitor string `yaml:"monitor"`
|
||||
Zones []Zone `yaml:"zones"`
|
||||
}
|
||||
@@ -49,8 +51,9 @@ type Hotkey struct {
|
||||
func Default() Config {
|
||||
return Config{
|
||||
Version: 1, Gap: 8, ShiftDrag: true,
|
||||
Overlay: Overlay{Enabled: true, Color: "#00AEEF", Opacity: 90, BorderWidth: 3},
|
||||
Layouts: []Layout{{Monitor: "*", Zones: []Zone{
|
||||
ActiveLayout: "columns",
|
||||
Overlay: Overlay{Enabled: true, Color: "#00AEEF", Opacity: 90, BorderWidth: 10},
|
||||
Layouts: []Layout{{Name: "columns", Monitor: "*", Zones: []Zone{
|
||||
{Name: "left", X: 0, Y: 0, Width: 50, Height: 100},
|
||||
{Name: "right", X: 50, Y: 0, Width: 50, Height: 100},
|
||||
}}},
|
||||
@@ -73,6 +76,7 @@ func Load(path string) (Config, error) {
|
||||
if err := d.Decode(&c); err != nil {
|
||||
return Config{}, fmt.Errorf("parse YAML: %w", err)
|
||||
}
|
||||
c.upgradeLegacyLayouts()
|
||||
if err := c.Validate(); err != nil {
|
||||
return Config{}, err
|
||||
}
|
||||
@@ -100,15 +104,27 @@ func (c Config) Validate() error {
|
||||
if len(c.Layouts) == 0 {
|
||||
return errors.New("at least one layout is required")
|
||||
}
|
||||
active := strings.TrimSpace(c.ActiveLayout)
|
||||
if active == "" {
|
||||
return errors.New("active_layout is required")
|
||||
}
|
||||
seenMonitors := map[string]bool{}
|
||||
activeExists := false
|
||||
for li, l := range c.Layouts {
|
||||
layoutName := strings.TrimSpace(l.Name)
|
||||
if layoutName == "" {
|
||||
return fmt.Errorf("layouts[%d].name is required", li)
|
||||
}
|
||||
if strings.EqualFold(layoutName, active) {
|
||||
activeExists = true
|
||||
}
|
||||
name := strings.TrimSpace(l.Monitor)
|
||||
if name == "" {
|
||||
return fmt.Errorf("layouts[%d].monitor is required", li)
|
||||
}
|
||||
key := strings.ToLower(name)
|
||||
key := strings.ToLower(layoutName) + "\x00" + strings.ToLower(name)
|
||||
if seenMonitors[key] {
|
||||
return fmt.Errorf("duplicate layout for monitor %q", name)
|
||||
return fmt.Errorf("duplicate layout named %q for monitor %q", layoutName, name)
|
||||
}
|
||||
seenMonitors[key] = true
|
||||
if len(l.Zones) == 0 {
|
||||
@@ -120,6 +136,9 @@ func (c Config) Validate() error {
|
||||
}
|
||||
}
|
||||
}
|
||||
if !activeExists {
|
||||
return fmt.Errorf("active_layout %q does not match any layout name", c.ActiveLayout)
|
||||
}
|
||||
for i, h := range c.Hotkeys {
|
||||
a := strings.ToLower(strings.TrimSpace(h.Action))
|
||||
if a != "next_zone" && a != "previous_zone" && !strings.HasPrefix(a, "zone_") {
|
||||
@@ -138,6 +157,21 @@ func (c Config) Validate() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Config) upgradeLegacyLayouts() {
|
||||
if strings.TrimSpace(c.ActiveLayout) != "" || len(c.Layouts) == 0 {
|
||||
return
|
||||
}
|
||||
for _, l := range c.Layouts {
|
||||
if strings.TrimSpace(l.Name) != "" {
|
||||
return
|
||||
}
|
||||
}
|
||||
c.ActiveLayout = "default"
|
||||
for i := range c.Layouts {
|
||||
c.Layouts[i].Name = "default"
|
||||
}
|
||||
}
|
||||
|
||||
func validHexColor(s string) bool {
|
||||
if len(s) != 7 || s[0] != '#' {
|
||||
return false
|
||||
@@ -151,14 +185,15 @@ func DefaultPath(exePath string) string {
|
||||
}
|
||||
|
||||
func (c Config) LayoutFor(device string) (Layout, bool) {
|
||||
active := strings.TrimSpace(c.ActiveLayout)
|
||||
for _, l := range c.Layouts {
|
||||
monitor := strings.TrimSpace(l.Monitor)
|
||||
if monitor != "*" && strings.EqualFold(monitor, strings.TrimSpace(device)) {
|
||||
if strings.EqualFold(strings.TrimSpace(l.Name), active) && monitor != "*" && strings.EqualFold(monitor, strings.TrimSpace(device)) {
|
||||
return l, true
|
||||
}
|
||||
}
|
||||
for _, l := range c.Layouts {
|
||||
if strings.TrimSpace(l.Monitor) == "*" {
|
||||
if strings.EqualFold(strings.TrimSpace(l.Name), active) && strings.TrimSpace(l.Monitor) == "*" {
|
||||
return l, true
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user