Files
Marlon Kranz 4693178345 feat(theme): add a selectable source color mode for wallpaper palettes (#3020)
* feat(theme): add a selectable source color mode for wallpaper palettes

DMS always seeds the palette from matugen's most dominant candidate
(--source-color-index 0, hardcoded at both call sites). When a large
muted region outvotes a smaller vivid one, a visibly warm wallpaper
produces a cold palette: one image here goes to #8ad0ee where the warm
subject is at hue 46.

Adds a Source Color setting with seven values. Dominant is the default
and is byte-identical to today. Colorful is computed in Go before
matugen runs: quantize the image (Wu for the starting clusters, then a
weighted k-means in Lab), score the result the way caelestia-cli does,
and hand matugen the resulting hex. Its scoring walks a chroma/tone bar
down from 20 until something clears it, which biases the pick toward a
colorful, well-lit color rather than whichever muted color covers the
most pixels. The remaining five map to matugen's --prefer.

- core/internal/matugen/sourcecolor.go: extraction, scoring, and the
  one helper both matugen call sites now share
- buildOnce resolves the seed once, before the StockColors branch, so
  the dry run and the real run cannot disagree about it
- extraction failure logs a warning and falls through to matugen's own
  extraction; it never fails a theme build
- --prefer arrived in matugen 4.1, so it is gated separately from the
  existing isV4 check; on 4.0.x those five modes degrade to the
  dominant color instead of aborting on an unknown argument
- --source-mode on generate and queue, plus sourceMode on the socket
  handler
- Source Color dropdown in Theme & Colors, defaulting to Dominant

Adds github.com/Nadim147c/material/v3 (Apache-2.0, no dependencies) for
HCT conversions, Wu quantization and the dislike analyzer. Its
QuantizeCelebi is not used: it returns after one unconverged iteration
and its Lab.DistanceSquared is a dot product rather than a distance, so
it collapses most of an image into a single cluster.

* fix(theme): address review feedback on source color mode

Two points from review on #3020.

The quantize comment pointed at a FOLLOWUPS file that does not exist in
this repository. Replaced the reference with the measurement it was
standing in for, so the claim is self-contained.

setMatugenSourceMode called Theme.generateSystemThemesFromCurrentTheme()
directly on top of the regenSystemThemes onChange hook that Spec.set
already dispatches. Both feed a 100ms debounce so it was one generation
either way, but the direct call was redundant and is gone.

* fix(theme): keep {{image}} resolvable in colorful mode

Colorful resolves the seed itself and calls matugen with a hex source, which
leaves matugen's {{image}} unset: the builtin pywalfox template and any user
template using the keyword render the literal string "Null".

Pass the wallpaper path alongside dank16 in --import-json-string, as suggested
in review, so those templates keep working unedited. The path is made absolute
to match what matugen renders for {{image}} in the other modes.

* fix(theme): cut translator terms on the source color dropdown

Per-mode descriptions were seven of the fifteen new terms this branch adds,
and five of them differed by a single adjective. Drop them: the dropdown row
already describes what the setting does, and the docs cover each mode.
2026-09-03 09:07:21 -04:00

81 lines
2.4 KiB
Go

package server
import (
"context"
"time"
"github.com/AvengeMedia/DankMaterialShell/core/internal/matugen"
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/models"
)
type MatugenQueueResult struct {
Success bool `json:"success"`
Message string `json:"message,omitempty"`
}
func handleMatugenQueue(conn *models.Conn, req models.Request) {
opts := matugen.Options{
StateDir: models.GetOr(req, "stateDir", ""),
ShellDir: models.GetOr(req, "shellDir", ""),
ConfigDir: models.GetOr(req, "configDir", ""),
Kind: models.GetOr(req, "kind", ""),
Value: models.GetOr(req, "value", ""),
Mode: matugen.ColorMode(models.GetOr(req, "mode", "")),
IconTheme: models.GetOr(req, "iconTheme", ""),
MatugenType: models.GetOr(req, "matugenType", ""),
RunUserTemplates: models.GetOr(req, "runUserTemplates", true),
StockColors: models.GetOr(req, "stockColors", ""),
SyncModeWithPortal: models.GetOr(req, "syncModeWithPortal", false),
TerminalsAlwaysDark: models.GetOr(req, "terminalsAlwaysDark", false),
SkipTemplates: models.GetOr(req, "skipTemplates", ""),
Contrast: models.GetOr(req, "contrast", 0.0),
SourceMode: models.GetOr(req, "sourceMode", ""),
}
wait := models.GetOr(req, "wait", true)
queue := matugen.GetQueue()
resultCh := queue.Submit(opts)
if !wait {
models.Respond(conn, req.ID, MatugenQueueResult{
Success: true,
Message: "queued",
})
return
}
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
defer cancel()
select {
case result := <-resultCh:
if result.Error != nil {
if result.Error == context.Canceled {
models.Respond(conn, req.ID, MatugenQueueResult{
Success: false,
Message: "cancelled",
})
return
}
models.RespondError(conn, req.ID, result.Error.Error())
return
}
models.Respond(conn, req.ID, MatugenQueueResult{
Success: true,
Message: "completed",
})
case <-ctx.Done():
models.RespondError(conn, req.ID, "timeout waiting for theme generation")
}
}
func handleMatugenStatus(conn *models.Conn, req models.Request) {
queue := matugen.GetQueue()
models.Respond(conn, req.ID, map[string]bool{
"running": queue.IsRunning(),
"pending": queue.HasPending(),
"smartSupported": matugen.SupportsSmart(),
})
}