Files
Loki/internal/loki/sys_brand_icon.go
T
Claude 276a8287cb Refonte : modèle chargé, attribution, ombres, flèche, favicon
Retours sur la refonte, cinq corrections.

Modèle affiché
  Le sélecteur de l'en-tête ne montrait que le PRESET actif — or un preset n'est
  « actif » que si son empreinte correspond exactement à la configuration. Une
  configuration écrite à la main, héritée de l'image ou modifiée d'un cran depuis
  le dernier preset n'en désigne aucun, et l'en-tête restait muet alors qu'un
  modèle tournait. Il retombe désormais sur le nom du fichier de la
  configuration active (loadCfg le lui donne), et l'infobulle porte le chemin
  complet.

Attribution
  « fork de AJEAN » revient sous le nom de l'app, dans l'en-tête de la barre
  latérale, au lieu du pied de la zone défilante.

Ombres
  Les bulles du fil et la carte de saisie portent une vraie ombre douce
  (--shadow-card), comme la maquette — le filet de 1 px ne les décollait pas du
  fond.

Bouton d'envoi
  Chevron sauge sur fond transparent, à la place de la pastille verte pleine :
  c'est le dessin de la maquette, et il suit alors la même charte que ses
  voisins (dépôt, fichiers).

Favicon
  Carré sauge (#5E7F5A) au lieu du carré noir, aux quatre endroits qui le
  dessinent : favicon SVG, icône d'accueil iOS, icône du .exe (icon.ico) et PNG
  macOS — tous rendus depuis sys_brand_icon.go, seule source du dessin. La
  variante « template » de macOS reste noire : le système ne lit que son alpha.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LPyFxVHNAN9u5pVzSYMwjd
2026-08-16 18:15:24 +00:00

141 lines
5.0 KiB
Go

package loki
// sys_brand_icon.go — l'icône de la marque Loki, rendue à la volée.
//
// RÉPLIQUE EXACTE du favicon de l'UI web : carré à coins arrondis SAUGE + « j »
// blanc (rects (6,3) (6,5) (4,7) sur une grille 12x12). Une seule source pour
// tous les usages — zone de notification Windows, barre de menus macOS, icône du
// .exe — pour qu'ils ne puissent plus diverger comme quand le favicon est passé
// au noir en laissant les icônes système en bleu.
//
// La sauge est --accent de l'interface (#5E7F5A) : l'icône de l'onglet et celle
// du bureau portent la couleur de l'app, pas un carré noir anonyme.
//
// Aucun asset binaire à committer : tout est dessiné ici, et l'icône du .exe est
// produite par `go generate ./cmd/loki` (voir tools/gen-icon).
import (
"bytes"
"encoding/binary"
"image"
"image/color"
"image/png"
)
const trayIconSize = 32
var (
// brandSage = --accent du thème clair. Le blanc du « j » s'y lit à 4,9:1.
brandSage = color.RGBA{0x5e, 0x7f, 0x5a, 0xff}
brandBlack = color.RGBA{0x00, 0x00, 0x00, 0xff}
brandWhite = color.RGBA{0xff, 0xff, 0xff, 0xff}
brandClear = color.RGBA{0, 0, 0, 0}
)
// glyphRects : les trois rectangles blancs du « j », en unités de la grille 12.
var glyphRects = [][4]float64{{6, 3, 2, 2}, {6, 5, 2, 2}, {4, 7, 2, 2}}
// brandIconImage dessine l'icône à la taille n. bg peint le carré arrondi, fg le
// « j ». Les deux peuvent être transparents : c'est ce qui produit l'icône
// « template » de macOS.
func brandIconImage(n int, bg, fg color.RGBA) *image.RGBA {
const r = 2.0 // rayon des coins, en unités de la grille 12
outside := func(gx, gy float64) bool {
corner := func(cx, cy float64) bool {
dx, dy := gx-cx, gy-cy
return dx*dx+dy*dy > r*r
}
switch {
case gx < r && gy < r:
return corner(r, r)
case gx > 12-r && gy < r:
return corner(12-r, r)
case gx < r && gy > 12-r:
return corner(r, 12-r)
case gx > 12-r && gy > 12-r:
return corner(12-r, 12-r)
}
return false
}
img := image.NewRGBA(image.Rect(0, 0, n, n))
scale := float64(n) / 12
for y := 0; y < n; y++ {
for x := 0; x < n; x++ {
gx := (float64(x) + 0.5) / scale
gy := (float64(y) + 0.5) / scale
if outside(gx, gy) {
img.Set(x, y, brandClear)
continue
}
c := bg
for _, rc := range glyphRects {
if gx >= rc[0] && gx < rc[0]+rc[2] && gy >= rc[1] && gy < rc[1]+rc[3] {
c = fg
}
}
img.Set(x, y, c)
}
}
return img
}
func encodePNG(img *image.RGBA) []byte {
var buf bytes.Buffer
_ = png.Encode(&buf, img)
return buf.Bytes()
}
// BrandIconPNG rend l'icône de marque (sauge + « j » blanc) en PNG de n pixels.
// Exporté pour le générateur d'icône du .exe (tools/gen-icon).
func BrandIconPNG(n int) []byte { return encodePNG(brandIconImage(n, brandSage, brandWhite)) }
// brandTemplatePNG rend la variante « template » attendue par macOS : seule la
// couche alpha compte, le système colore la forme selon le thème de la barre de
// menus. Le « j » est donc DÉCOUPÉ (transparent) dans un carré opaque, sans quoi
// une icône entièrement noire disparaît sur une barre de menus sombre.
//
// Utilisée par sys_tray_darwin.go — un fichier que seule une compilation avec
// CGO voit. Les analyseurs lancés sans CGO la croient morte : elle avait été
// supprimée à ce titre, ce qui a cassé la compilation macOS en CI.
//
//lint:ignore U1000 utilisée par sys_tray_darwin.go, invisible sans CGO/macOS
func brandTemplatePNG(n int) []byte {
return encodePNG(brandIconImage(n, brandBlack, brandClear))
}
// BrandICO emballe une ou plusieurs tailles PNG dans un conteneur .ico. Windows
// accepte le PNG comme image d'une entrée .ico (alpha conservé pour les coins
// arrondis). Plusieurs tailles = un rendu net partout, de la barre des tâches
// (16 px) à la grande tuile de l'explorateur (256 px).
func BrandICO(sizes ...int) []byte {
if len(sizes) == 0 {
sizes = []int{trayIconSize}
}
imgs := make([][]byte, 0, len(sizes))
for _, n := range sizes {
imgs = append(imgs, BrandIconPNG(n))
}
var ico bytes.Buffer
binary.Write(&ico, binary.LittleEndian, uint16(0)) // réservé
binary.Write(&ico, binary.LittleEndian, uint16(1)) // type = icône
binary.Write(&ico, binary.LittleEndian, uint16(len(imgs))) // nombre d'images
offset := uint32(6 + 16*len(imgs)) // fin du répertoire
for i, p := range imgs {
n := sizes[i]
// 256 px se code 0 dans un .ico (le champ ne fait qu'un octet).
ico.WriteByte(byte(n % 256))
ico.WriteByte(byte(n % 256))
ico.WriteByte(0) // couleurs de la palette (0 = truecolor)
ico.WriteByte(0) // réservé
binary.Write(&ico, binary.LittleEndian, uint16(1)) // plans
binary.Write(&ico, binary.LittleEndian, uint16(32)) // bits/pixel
binary.Write(&ico, binary.LittleEndian, uint32(len(p))) // taille données
binary.Write(&ico, binary.LittleEndian, offset) // offset données
offset += uint32(len(p))
}
for _, p := range imgs {
ico.Write(p)
}
return ico.Bytes()
}