Initial Das OS 0.1.0: Void Linux live ISO with Xfce

- xbps-src templates: das-theme (adw-gtk3-based GTK themes, generated
  xfwm4 frames, wallpapers, logo), das-settings (Xfce, panel, LightDM
  defaults in /etc/xdg/das), adw-gtk3 and font-jetbrains-mono
- void-mklive profile in live/: packages, runit services, rootfs overlay,
  Ukrainian locale, live user with autologin
- Void builder container, build scripts, QEMU runner and Makefile
- Bilingual README with screenshots

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
das
2026-10-18 21:23:16 +03:00
co-authored by Claude Opus 5.5
commit b7399031c7
48 changed files with 1548 additions and 0 deletions
+1
View File
@@ -0,0 +1 @@
../../../LICENSE
@@ -0,0 +1,71 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="3840" height="2160" viewBox="0 0 1920 1080">
<defs>
<linearGradient id="bg" gradientUnits="userSpaceOnUse" x1="0" y1="0" x2="1920" y2="1080">
<stop offset="0" stop-color="#141820"/>
<stop offset="1" stop-color="#1f2736"/>
</linearGradient>
<!-- Rushnyk-style ribbon: a column of embroidered diamonds -->
<pattern id="stitch" patternUnits="userSpaceOnUse" x="1366" y="0" width="28" height="36">
<path d="M14 8 L24 18 L14 28 L4 18 Z" fill="#4f86ec"/>
<path d="M14 14 L18 18 L14 22 L10 18 Z" fill="#f7cc3a"/>
</pattern>
<path id="petal" d="M0 0 L38.27 -92.39 L0 -184.78 L-38.27 -92.39 Z"/>
<path id="core" d="M0 -50.81 L17.22 -92.39 L0 -133.97 L-17.22 -92.39 Z"/>
<path id="bead" d="M0 -205 L7 -214 L0 -223 L-7 -214 Z"/>
<g id="pair-blue">
<use xlink:href="#petal" fill="#3f78e4"/>
<use xlink:href="#core" fill="#ffd23f"/>
</g>
<g id="pair-yellow">
<use xlink:href="#petal" fill="#f2c230"/>
<use xlink:href="#core" fill="#1f4fa8"/>
</g>
<g id="star">
<use xlink:href="#petal"/>
<use xlink:href="#petal" transform="rotate(45)"/>
<use xlink:href="#petal" transform="rotate(90)"/>
<use xlink:href="#petal" transform="rotate(135)"/>
<use xlink:href="#petal" transform="rotate(180)"/>
<use xlink:href="#petal" transform="rotate(225)"/>
<use xlink:href="#petal" transform="rotate(270)"/>
<use xlink:href="#petal" transform="rotate(315)"/>
</g>
</defs>
<rect width="1920" height="1080" fill="url(#bg)"/>
<!-- Faint echo of the star behind everything -->
<use xlink:href="#star" transform="translate(1380 540) scale(2.6)" fill="#8fb3ff" opacity="0.04"/>
<!-- Ribbon with thin side threads, broken around the medallion -->
<g opacity="0.3">
<rect x="1366" y="0" width="28" height="288" fill="url(#stitch)"/>
<rect x="1366" y="792" width="28" height="288" fill="url(#stitch)"/>
<rect x="1357" y="0" width="1.5" height="288" fill="#4f86ec"/>
<rect x="1357" y="792" width="1.5" height="288" fill="#4f86ec"/>
<rect x="1401.5" y="0" width="1.5" height="288" fill="#4f86ec"/>
<rect x="1401.5" y="792" width="1.5" height="288" fill="#4f86ec"/>
</g>
<!-- Medallion -->
<g transform="translate(1380 540)">
<use xlink:href="#pair-blue"/>
<use xlink:href="#pair-yellow" transform="rotate(45)"/>
<use xlink:href="#pair-blue" transform="rotate(90)"/>
<use xlink:href="#pair-yellow" transform="rotate(135)"/>
<use xlink:href="#pair-blue" transform="rotate(180)"/>
<use xlink:href="#pair-yellow" transform="rotate(225)"/>
<use xlink:href="#pair-blue" transform="rotate(270)"/>
<use xlink:href="#pair-yellow" transform="rotate(315)"/>
<g fill="#4f86ec" opacity="0.6">
<use xlink:href="#bead" transform="rotate(22.5)"/>
<use xlink:href="#bead" transform="rotate(67.5)"/>
<use xlink:href="#bead" transform="rotate(112.5)"/>
<use xlink:href="#bead" transform="rotate(157.5)"/>
<use xlink:href="#bead" transform="rotate(202.5)"/>
<use xlink:href="#bead" transform="rotate(247.5)"/>
<use xlink:href="#bead" transform="rotate(292.5)"/>
<use xlink:href="#bead" transform="rotate(337.5)"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.3 KiB

@@ -0,0 +1,71 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="3840" height="2160" viewBox="0 0 1920 1080">
<defs>
<linearGradient id="bg" gradientUnits="userSpaceOnUse" x1="0" y1="0" x2="1920" y2="1080">
<stop offset="0" stop-color="#f7f9fc"/>
<stop offset="1" stop-color="#dfe7f3"/>
</linearGradient>
<!-- Rushnyk-style ribbon: a column of embroidered diamonds -->
<pattern id="stitch" patternUnits="userSpaceOnUse" x="1366" y="0" width="28" height="36">
<path d="M14 8 L24 18 L14 28 L4 18 Z" fill="#2f6fde"/>
<path d="M14 14 L18 18 L14 22 L10 18 Z" fill="#f5c400"/>
</pattern>
<path id="petal" d="M0 0 L38.27 -92.39 L0 -184.78 L-38.27 -92.39 Z"/>
<path id="core" d="M0 -50.81 L17.22 -92.39 L0 -133.97 L-17.22 -92.39 Z"/>
<path id="bead" d="M0 -205 L7 -214 L0 -223 L-7 -214 Z"/>
<g id="pair-blue">
<use xlink:href="#petal" fill="#2f6fde"/>
<use xlink:href="#core" fill="#ffd23f"/>
</g>
<g id="pair-yellow">
<use xlink:href="#petal" fill="#f5c400"/>
<use xlink:href="#core" fill="#1f4fa8"/>
</g>
<g id="star">
<use xlink:href="#petal"/>
<use xlink:href="#petal" transform="rotate(45)"/>
<use xlink:href="#petal" transform="rotate(90)"/>
<use xlink:href="#petal" transform="rotate(135)"/>
<use xlink:href="#petal" transform="rotate(180)"/>
<use xlink:href="#petal" transform="rotate(225)"/>
<use xlink:href="#petal" transform="rotate(270)"/>
<use xlink:href="#petal" transform="rotate(315)"/>
</g>
</defs>
<rect width="1920" height="1080" fill="url(#bg)"/>
<!-- Faint echo of the star behind everything -->
<use xlink:href="#star" transform="translate(1380 540) scale(2.6)" fill="#2f6fde" opacity="0.05"/>
<!-- Ribbon with thin side threads, broken around the medallion -->
<g opacity="0.35">
<rect x="1366" y="0" width="28" height="288" fill="url(#stitch)"/>
<rect x="1366" y="792" width="28" height="288" fill="url(#stitch)"/>
<rect x="1357" y="0" width="1.5" height="288" fill="#2f6fde"/>
<rect x="1357" y="792" width="1.5" height="288" fill="#2f6fde"/>
<rect x="1401.5" y="0" width="1.5" height="288" fill="#2f6fde"/>
<rect x="1401.5" y="792" width="1.5" height="288" fill="#2f6fde"/>
</g>
<!-- Medallion -->
<g transform="translate(1380 540)">
<use xlink:href="#pair-blue"/>
<use xlink:href="#pair-yellow" transform="rotate(45)"/>
<use xlink:href="#pair-blue" transform="rotate(90)"/>
<use xlink:href="#pair-yellow" transform="rotate(135)"/>
<use xlink:href="#pair-blue" transform="rotate(180)"/>
<use xlink:href="#pair-yellow" transform="rotate(225)"/>
<use xlink:href="#pair-blue" transform="rotate(270)"/>
<use xlink:href="#pair-yellow" transform="rotate(315)"/>
<g fill="#2f6fde" opacity="0.6">
<use xlink:href="#bead" transform="rotate(22.5)"/>
<use xlink:href="#bead" transform="rotate(67.5)"/>
<use xlink:href="#bead" transform="rotate(112.5)"/>
<use xlink:href="#bead" transform="rotate(157.5)"/>
<use xlink:href="#bead" transform="rotate(202.5)"/>
<use xlink:href="#bead" transform="rotate(247.5)"/>
<use xlink:href="#bead" transform="rotate(292.5)"/>
<use xlink:href="#bead" transform="rotate(337.5)"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.3 KiB

@@ -0,0 +1,23 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="128" height="128" viewBox="-100 -100 200 200">
<!-- Eight-petal star from Ukrainian embroidery: rhombi with a 45° apex share their edges. -->
<defs>
<path id="petal" d="M0 0 L19.13 -46.19 L0 -92.39 L-19.13 -46.19 Z"/>
<path id="core" d="M0 -25.4 L8.61 -46.19 L0 -66.98 L-8.61 -46.19 Z"/>
<g id="pair-blue">
<use xlink:href="#petal" fill="#2f6fde"/>
<use xlink:href="#core" fill="#ffd23f"/>
</g>
<g id="pair-yellow">
<use xlink:href="#petal" fill="#f5c400"/>
<use xlink:href="#core" fill="#1f4fa8"/>
</g>
</defs>
<use xlink:href="#pair-blue"/>
<use xlink:href="#pair-yellow" transform="rotate(45)"/>
<use xlink:href="#pair-blue" transform="rotate(90)"/>
<use xlink:href="#pair-yellow" transform="rotate(135)"/>
<use xlink:href="#pair-blue" transform="rotate(180)"/>
<use xlink:href="#pair-yellow" transform="rotate(225)"/>
<use xlink:href="#pair-blue" transform="rotate(270)"/>
<use xlink:href="#pair-yellow" transform="rotate(315)"/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

@@ -0,0 +1,14 @@
/* Das OS dark: adw-gtk3-dark with the Das OS accent colours. */
@import url("../../adw-gtk3-dark/gtk-3.0/gtk.css");
@define-color accent_bg_color #3f78e4;
@define-color accent_fg_color #ffffff;
@define-color accent_color #7aa7f5;
/* A dark panel that matches the window frames instead of pure black. */
@define-color panel_bg_color #1b1f27;
@define-color panel_fg_color #e3e6ec;
.xfce4-panel.background {
border-top: 1px solid alpha(@panel_fg_color, 0.08);
}
@@ -0,0 +1,12 @@
[Desktop Entry]
Type=X-GNOME-Metatheme
Name=DasOS-Dark
Comment=Das OS dark theme
Encoding=UTF-8
[X-GNOME-Metatheme]
GtkTheme=DasOS-Dark
MetacityTheme=DasOS-Dark
IconTheme=Papirus
CursorTheme=Adwaita
ButtonLayout=menu:minimize,maximize,close
@@ -0,0 +1,23 @@
/* Das OS light: adw-gtk3 with the Das OS accent colours. */
@import url("../../adw-gtk3/gtk-3.0/gtk.css");
@define-color accent_bg_color #2f6fde;
@define-color accent_fg_color #ffffff;
@define-color accent_color #2a63c8;
/* adw-gtk3 paints the Xfce panel black like the GNOME top bar; Das OS keeps it light. */
@define-color panel_bg_color #f6f7fa;
@define-color panel_fg_color #1f2430;
.xfce4-panel.background {
border-top: 1px solid alpha(@panel_fg_color, 0.12);
}
/* adw-gtk3 draws white desktop labels with a shadow, made for dark wallpapers. */
XfdesktopIconView.view {
color: #1f2430;
}
XfdesktopIconView.view .label {
text-shadow: none;
}
@@ -0,0 +1,12 @@
[Desktop Entry]
Type=X-GNOME-Metatheme
Name=DasOS
Comment=Das OS light theme
Encoding=UTF-8
[X-GNOME-Metatheme]
GtkTheme=DasOS
MetacityTheme=DasOS
IconTheme=Papirus
CursorTheme=Adwaita
ButtonLayout=menu:minimize,maximize,close
+3
View File
@@ -0,0 +1,3 @@
module dasos/xfwm4gen
go 1.22
+343
View File
@@ -0,0 +1,343 @@
// Command xfwm4gen renders the Das OS xfwm4 window decorations.
//
// xfwm4 themes are a directory of image slices plus a themerc file. Drawing
// ~90 slices by hand is error-prone, so they are generated from one palette:
//
// xfwm4gen -variant light -out build/DasOS/xfwm4
package main
import (
"flag"
"fmt"
"image"
"image/color"
"image/png"
"log"
"math"
"os"
"path/filepath"
"strings"
)
const (
titleHeight = 32
frameWidth = 3
cornerRadius = 8.0
cornerWidth = 12
buttonWidth = 30
circleRadius = 11.0
glyphHalf = 4.0
strokeWidth = 1.5
samples = 4 // supersampling per axis, gives 16 samples per pixel
)
type palette struct {
titleBg, border, separator, glyph, text color.NRGBA
}
type variant struct {
active, inactive palette
hover, pressed color.NRGBA
closeHover, closePressed, onClose color.NRGBA
}
var variants = map[string]variant{
"light": {
active: palette{
titleBg: rgb(0xe9ecf1), border: rgb(0xb4bcc9), separator: rgb(0xd3d8e1),
glyph: rgb(0x2e3440), text: rgb(0x1f2430),
},
inactive: palette{
titleBg: rgb(0xf4f5f8), border: rgb(0xccd2db), separator: rgb(0xe2e5eb),
glyph: rgb(0x9aa1ad), text: rgb(0x8a909c),
},
hover: rgb(0xd5dae3), pressed: rgb(0xc3cad6),
closeHover: rgb(0xe0464c), closePressed: rgb(0xc2353b), onClose: rgb(0xffffff),
},
"dark": {
active: palette{
titleBg: rgb(0x2b2f38), border: rgb(0x0e1014), separator: rgb(0x1d2026),
glyph: rgb(0xe3e6ec), text: rgb(0xeceff4),
},
inactive: palette{
titleBg: rgb(0x24272e), border: rgb(0x15171b), separator: rgb(0x1b1d22),
glyph: rgb(0x7c8391), text: rgb(0x8a909c),
},
hover: rgb(0x3b404b), pressed: rgb(0x4a505d),
closeHover: rgb(0xe0464c), closePressed: rgb(0xc2353b), onClose: rgb(0xffffff),
},
}
func rgb(v uint32) color.NRGBA {
return color.NRGBA{R: uint8(v >> 16), G: uint8(v >> 8), B: uint8(v), A: 0xff}
}
func hex(c color.NRGBA) string {
return fmt.Sprintf("#%02x%02x%02x", c.R, c.G, c.B)
}
// shader returns the colour of a point in pixel coordinates; transparent outside the shape.
type shader func(x, y float64) color.NRGBA
// render rasterises a shader with supersampling, averaging in premultiplied space.
func render(w, h int, fn shader) *image.NRGBA {
img := image.NewNRGBA(image.Rect(0, 0, w, h))
const n = samples * samples
for py := 0; py < h; py++ {
for px := 0; px < w; px++ {
var r, g, b, a float64
for sy := 0; sy < samples; sy++ {
for sx := 0; sx < samples; sx++ {
c := fn(float64(px)+(float64(sx)+0.5)/samples, float64(py)+(float64(sy)+0.5)/samples)
ca := float64(c.A) / 255
r += float64(c.R) * ca
g += float64(c.G) * ca
b += float64(c.B) * ca
a += ca
}
}
if a == 0 {
continue
}
img.SetNRGBA(px, py, color.NRGBA{
R: uint8(math.Round(r / a)), G: uint8(math.Round(g / a)), B: uint8(math.Round(b / a)),
A: uint8(math.Round(a / n * 255)),
})
}
}
return img
}
// titleRow is the colour of the title bar at height y: top border, fill, separator above the client.
func titleRow(p palette, y float64) color.NRGBA {
switch {
case y < 1:
return p.border
case y >= titleHeight-1:
return p.separator
default:
return p.titleBg
}
}
// insideRounded reports whether (x, y) lies in a shape whose top-left corner is
// rounded with radius r at origin (ox, oy) and which extends right and down.
func insideRounded(x, y, ox, oy, r float64) bool {
if x < ox || y < oy {
return false
}
if x < ox+r && y < oy+r {
return math.Hypot(x-(ox+r), y-(oy+r)) <= r
}
return true
}
func topLeft(p palette) shader {
return func(x, y float64) color.NRGBA {
switch {
case !insideRounded(x, y, 0, 0, cornerRadius):
return color.NRGBA{}
case !insideRounded(x, y, 1, 1, cornerRadius-1):
return p.border
case x < frameWidth:
return p.titleBg
default:
return titleRow(p, y)
}
}
}
func mirror(fn shader, w int) shader {
return func(x, y float64) color.NRGBA { return fn(float64(w)-x, y) }
}
func leftEdge(p palette) shader {
return func(x, _ float64) color.NRGBA {
if x < 1 {
return p.border
}
return p.titleBg
}
}
func bottomEdge(p palette) shader {
return func(_, y float64) color.NRGBA {
if y >= frameWidth-1 {
return p.border
}
return p.titleBg
}
}
func bottomLeft(p palette) shader {
return func(x, y float64) color.NRGBA {
if x < 1 || y >= frameWidth-1 {
return p.border
}
return p.titleBg
}
}
type segment struct{ x1, y1, x2, y2 float64 }
// glyph is a button symbol drawn around the button centre.
type glyph struct {
lines []segment
ring float64 // stroked circle radius, 0 for none
dot float64 // filled circle radius, 0 for none
}
func square(x1, y1, x2, y2 float64) []segment {
return []segment{{x1, y1, x2, y1}, {x2, y1, x2, y2}, {x2, y2, x1, y2}, {x1, y2, x1, y1}}
}
var glyphs = func() map[string]glyph {
const g = glyphHalf
return map[string]glyph{
"close": {lines: []segment{{-g, -g, g, g}, {-g, g, g, -g}}},
"hide": {lines: []segment{{-g, 3, g, 3}}},
"maximize": {lines: square(-g, -g, g, g)},
"maximize-toggled": {lines: append(square(-g, -g+2.5, g-2.5, g), segment{-g + 2.5, -g, g, -g}, segment{g, -g, g, g - 2.5})},
"menu": {lines: []segment{{-g, -3, g, -3}, {-g, 0, g, 0}, {-g, 3, g, 3}}},
"shade": {lines: []segment{{-g, 2, 0, -2}, {0, -2, g, 2}}},
"shade-toggled": {lines: []segment{{-g, -2, 0, 2}, {0, 2, g, -2}}},
"stick": {ring: g},
"stick-toggled": {dot: g},
}
}()
func distToSegment(px, py float64, s segment) float64 {
dx, dy := s.x2-s.x1, s.y2-s.y1
t := ((px-s.x1)*dx + (py-s.y1)*dy) / (dx*dx + dy*dy)
t = math.Max(0, math.Min(1, t))
return math.Hypot(px-(s.x1+t*dx), py-(s.y1+t*dy))
}
func (g glyph) covers(x, y float64) bool {
for _, s := range g.lines {
if distToSegment(x, y, s) <= strokeWidth/2 {
return true
}
}
r := math.Hypot(x, y)
if g.ring > 0 && math.Abs(r-g.ring) <= strokeWidth/2 {
return true
}
return g.dot > 0 && r <= g.dot
}
func button(p palette, g glyph, circle, ink color.NRGBA) shader {
cx, cy := buttonWidth/2.0, titleHeight/2.0
return func(x, y float64) color.NRGBA {
dx, dy := x-cx, y-cy
switch {
case g.covers(dx, dy):
return ink
case circle.A != 0 && math.Hypot(dx, dy) <= circleRadius:
return circle
default:
return titleRow(p, y)
}
}
}
type piece struct {
name string
w, h int
fn shader
}
func pieces(v variant) []piece {
var out []piece
for _, state := range []string{"active", "inactive"} {
p := v.active
if state == "inactive" {
p = v.inactive
}
out = append(out,
piece{"top-left-" + state, cornerWidth, titleHeight, topLeft(p)},
piece{"top-right-" + state, cornerWidth, titleHeight, mirror(topLeft(p), cornerWidth)},
piece{"left-" + state, frameWidth, 1, leftEdge(p)},
piece{"right-" + state, frameWidth, 1, mirror(leftEdge(p), frameWidth)},
piece{"bottom-" + state, 1, frameWidth, bottomEdge(p)},
piece{"bottom-left-" + state, frameWidth, frameWidth, bottomLeft(p)},
piece{"bottom-right-" + state, frameWidth, frameWidth, mirror(bottomLeft(p), frameWidth)},
)
for i := 1; i <= 5; i++ {
out = append(out, piece{fmt.Sprintf("title-%d-%s", i, state), 1, titleHeight,
func(_, y float64) color.NRGBA { return titleRow(p, y) }})
}
}
for name, g := range glyphs {
hover, pressed, ink := v.hover, v.pressed, v.active.glyph
if name == "close" {
hover, pressed, ink = v.closeHover, v.closePressed, v.onClose
}
out = append(out,
piece{name + "-active", buttonWidth, titleHeight, button(v.active, g, color.NRGBA{}, v.active.glyph)},
piece{name + "-inactive", buttonWidth, titleHeight, button(v.inactive, g, color.NRGBA{}, v.inactive.glyph)},
piece{name + "-prelight", buttonWidth, titleHeight, button(v.active, g, hover, ink)},
piece{name + "-pressed", buttonWidth, titleHeight, button(v.active, g, pressed, ink)},
)
}
return out
}
func themerc(v variant) string {
return strings.Join([]string{
"# Generated by xfwm4gen, do not edit.",
"active_text_color=" + hex(v.active.text),
"inactive_text_color=" + hex(v.inactive.text),
"button_offset=4",
"button_spacing=0",
"full_width_title=true",
"maximized_offset=0",
"show_app_icon=true",
"title_alignment=center",
"title_horizontal_offset=0",
"title_shadow_active=false",
"title_shadow_inactive=false",
"title_vertical_offset_active=0",
"title_vertical_offset_inactive=0",
"",
}, "\n")
}
func writePNG(path string, img image.Image) error {
f, err := os.Create(path)
if err != nil {
return err
}
if err := png.Encode(f, img); err != nil {
f.Close()
return fmt.Errorf("encode %s: %w", path, err)
}
return f.Close()
}
func generate(v variant, dir string) error {
if err := os.MkdirAll(dir, 0o755); err != nil {
return err
}
for _, p := range pieces(v) {
if err := writePNG(filepath.Join(dir, p.name+".png"), render(p.w, p.h, p.fn)); err != nil {
return err
}
}
return os.WriteFile(filepath.Join(dir, "themerc"), []byte(themerc(v)), 0o644)
}
func main() {
name := flag.String("variant", "light", "colour variant: light or dark")
out := flag.String("out", "", "output directory (the theme's xfwm4 folder)")
flag.Parse()
v, ok := variants[*name]
if !ok || *out == "" {
flag.Usage()
os.Exit(2)
}
if err := generate(v, *out); err != nil {
log.Fatalf("xfwm4gen: %v", err)
}
}
@@ -0,0 +1,61 @@
package main
import (
"os"
"path/filepath"
"testing"
)
// xfwm4 falls back to an ugly built-in frame when any of these slices is missing.
var requiredSlices = []string{
"top-left", "top-right", "left", "right", "bottom", "bottom-left", "bottom-right",
"title-1", "title-2", "title-3", "title-4", "title-5",
}
var requiredButtons = []string{
"close", "hide", "maximize", "maximize-toggled", "menu",
"shade", "shade-toggled", "stick", "stick-toggled",
}
func TestGenerateWritesCompleteTheme(t *testing.T) {
for name, v := range variants {
t.Run(name, func(t *testing.T) {
dir := t.TempDir()
if err := generate(v, dir); err != nil {
t.Fatal(err)
}
want := []string{"themerc"}
for _, s := range requiredSlices {
want = append(want, s+"-active.png", s+"-inactive.png")
}
for _, b := range requiredButtons {
for _, state := range []string{"active", "inactive", "prelight", "pressed"} {
want = append(want, b+"-"+state+".png")
}
}
for _, f := range want {
if _, err := os.Stat(filepath.Join(dir, f)); err != nil {
t.Errorf("missing %s", f)
}
}
})
}
}
func TestTopLeftCornerIsTransparentOutsideRadius(t *testing.T) {
img := render(cornerWidth, titleHeight, topLeft(variants["light"].active))
if a := img.NRGBAAt(0, 0).A; a != 0 {
t.Errorf("corner pixel alpha = %d, want 0", a)
}
if a := img.NRGBAAt(cornerWidth-1, titleHeight/2).A; a != 0xff {
t.Errorf("title pixel alpha = %d, want 255", a)
}
}
func TestButtonsMatchTitleHeight(t *testing.T) {
for _, p := range pieces(variants["dark"]) {
if p.w == buttonWidth && p.h != titleHeight {
t.Errorf("%s height = %d, want %d", p.name, p.h, titleHeight)
}
}
}