131 lines
3.3 KiB
Go
131 lines
3.3 KiB
Go
package messagewall
|
|
|
|
import (
|
|
"fmt"
|
|
"sort"
|
|
)
|
|
|
|
const (
|
|
versionV1 = 1
|
|
unitGrid = "grid"
|
|
fitModeCover = "cover"
|
|
fitModeContain = "contain"
|
|
defaultDuration = 20
|
|
defaultLoadTime = 15
|
|
defaultOnError = "skip"
|
|
)
|
|
|
|
func Resolve(request ResolveRequest) (ResolveResult, error) {
|
|
if err := Validate(request.Layout); err != nil {
|
|
return ResolveResult{}, err
|
|
}
|
|
|
|
result := ResolveResult{
|
|
Version: request.Layout.Version,
|
|
CoordinateSpace: request.Layout.CoordinateSpace,
|
|
FitMode: request.Layout.FitMode,
|
|
Scenes: make([]ResolvedScene, 0, len(request.Layout.Slots)),
|
|
}
|
|
|
|
duration := request.DurationSeconds
|
|
if duration <= 0 {
|
|
duration = defaultDuration
|
|
}
|
|
|
|
loadTimeout := request.LoadTimeoutSeconds
|
|
if loadTimeout <= 0 {
|
|
loadTimeout = defaultLoadTime
|
|
}
|
|
|
|
onError := request.OnError
|
|
if onError == "" {
|
|
onError = defaultOnError
|
|
}
|
|
|
|
for _, slot := range request.Layout.Slots {
|
|
result.Scenes = append(result.Scenes, ResolvedScene{
|
|
SlotID: slot.SlotID,
|
|
Source: request.Source,
|
|
DurationSeconds: duration,
|
|
LoadTimeoutSeconds: loadTimeout,
|
|
OnError: onError,
|
|
Crop: Crop{
|
|
X: slot.X,
|
|
Y: slot.Y,
|
|
Width: slot.Width,
|
|
Height: slot.Height,
|
|
Unit: request.Layout.CoordinateSpace.Unit,
|
|
},
|
|
})
|
|
}
|
|
|
|
sort.Slice(result.Scenes, func(i, j int) bool {
|
|
return result.Scenes[i].SlotID < result.Scenes[j].SlotID
|
|
})
|
|
|
|
return result, nil
|
|
}
|
|
|
|
func Validate(layout Layout) error {
|
|
if layout.Version != versionV1 {
|
|
return fmt.Errorf("unsupported layout version: %d", layout.Version)
|
|
}
|
|
|
|
if layout.CoordinateSpace.Width <= 0 || layout.CoordinateSpace.Height <= 0 {
|
|
return fmt.Errorf("coordinate_space width and height must be positive")
|
|
}
|
|
|
|
if layout.CoordinateSpace.Unit != unitGrid {
|
|
return fmt.Errorf("unsupported coordinate_space unit: %s", layout.CoordinateSpace.Unit)
|
|
}
|
|
|
|
if layout.FitMode != fitModeCover && layout.FitMode != fitModeContain {
|
|
return fmt.Errorf("unsupported fit_mode: %s", layout.FitMode)
|
|
}
|
|
|
|
if len(layout.Slots) == 0 {
|
|
return fmt.Errorf("layout must contain at least one slot")
|
|
}
|
|
|
|
seen := make(map[string]struct{}, len(layout.Slots))
|
|
for i, slot := range layout.Slots {
|
|
if slot.SlotID == "" {
|
|
return fmt.Errorf("slot %d has empty slot_id", i)
|
|
}
|
|
|
|
if _, ok := seen[slot.SlotID]; ok {
|
|
return fmt.Errorf("duplicate slot_id: %s", slot.SlotID)
|
|
}
|
|
seen[slot.SlotID] = struct{}{}
|
|
|
|
if slot.Width <= 0 || slot.Height <= 0 {
|
|
return fmt.Errorf("slot %s must have positive width and height", slot.SlotID)
|
|
}
|
|
|
|
if slot.X < 0 || slot.Y < 0 {
|
|
return fmt.Errorf("slot %s must not start outside coordinate space", slot.SlotID)
|
|
}
|
|
|
|
if slot.X+slot.Width > layout.CoordinateSpace.Width {
|
|
return fmt.Errorf("slot %s exceeds coordinate_space width", slot.SlotID)
|
|
}
|
|
|
|
if slot.Y+slot.Height > layout.CoordinateSpace.Height {
|
|
return fmt.Errorf("slot %s exceeds coordinate_space height", slot.SlotID)
|
|
}
|
|
}
|
|
|
|
for i := range layout.Slots {
|
|
for j := i + 1; j < len(layout.Slots); j++ {
|
|
if overlaps(layout.Slots[i], layout.Slots[j]) {
|
|
return fmt.Errorf("slots %s and %s overlap", layout.Slots[i].SlotID, layout.Slots[j].SlotID)
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func overlaps(a, b Slot) bool {
|
|
return a.X < b.X+b.Width && a.X+a.Width > b.X && a.Y < b.Y+b.Height && a.Y+a.Height > b.Y
|
|
}
|