2023-02-02 22:57:18 -07:00
|
|
|
package theme
|
|
|
|
|
|
|
|
import "image"
|
2023-02-27 10:48:44 -07:00
|
|
|
import "bytes"
|
|
|
|
import _ "embed"
|
|
|
|
import _ "image/png"
|
2023-02-26 20:20:17 -07:00
|
|
|
import "image/color"
|
2023-02-02 22:57:18 -07:00
|
|
|
import "golang.org/x/image/font"
|
2023-03-30 23:06:29 -06:00
|
|
|
import "git.tebibyte.media/sashakoshka/tomo"
|
2023-03-04 14:18:43 -07:00
|
|
|
import "git.tebibyte.media/sashakoshka/tomo/data"
|
2023-02-02 22:57:18 -07:00
|
|
|
import "git.tebibyte.media/sashakoshka/tomo/artist"
|
2023-04-30 11:47:36 -06:00
|
|
|
import "git.tebibyte.media/sashakoshka/tomo/artist/artutil"
|
2023-03-31 11:45:52 -06:00
|
|
|
import defaultfont "git.tebibyte.media/sashakoshka/tomo/default/font"
|
2023-02-25 22:44:44 -07:00
|
|
|
import "git.tebibyte.media/sashakoshka/tomo/artist/patterns"
|
2023-02-02 22:57:18 -07:00
|
|
|
|
2023-02-27 10:48:44 -07:00
|
|
|
//go:embed assets/wintergreen.png
|
|
|
|
var defaultAtlasBytes []byte
|
2023-04-30 11:47:36 -06:00
|
|
|
var defaultAtlas artist.Canvas
|
2023-04-20 16:40:05 -06:00
|
|
|
var defaultTextures [17][9]artist.Pattern
|
2023-03-04 20:07:59 -07:00
|
|
|
//go:embed assets/wintergreen-icons-small.png
|
|
|
|
var defaultIconsSmallAtlasBytes []byte
|
|
|
|
var defaultIconsSmall [640]binaryIcon
|
2023-03-10 16:53:27 -07:00
|
|
|
//go:embed assets/wintergreen-icons-large.png
|
|
|
|
var defaultIconsLargeAtlasBytes []byte
|
|
|
|
var defaultIconsLarge [640]binaryIcon
|
2023-02-27 10:48:44 -07:00
|
|
|
|
|
|
|
func atlasCell (col, row int, border artist.Inset) {
|
|
|
|
bounds := image.Rect(0, 0, 16, 16).Add(image.Pt(col, row).Mul(16))
|
|
|
|
defaultTextures[col][row] = patterns.Border {
|
2023-04-30 11:47:36 -06:00
|
|
|
Canvas: artist.Cut(defaultAtlas, bounds),
|
2023-02-27 10:48:44 -07:00
|
|
|
Inset: border,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func atlasCol (col int, border artist.Inset) {
|
|
|
|
for index, _ := range defaultTextures[col] {
|
|
|
|
atlasCell(col, index, border)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-03-04 20:07:59 -07:00
|
|
|
type binaryIcon struct {
|
|
|
|
data []bool
|
|
|
|
stride int
|
|
|
|
}
|
|
|
|
|
2023-04-30 11:47:36 -06:00
|
|
|
func (icon binaryIcon) Draw (destination artist.Canvas, color color.RGBA, at image.Point) {
|
2023-03-04 20:07:59 -07:00
|
|
|
bounds := icon.Bounds().Add(at).Intersect(destination.Bounds())
|
|
|
|
point := image.Point { }
|
|
|
|
data, stride := destination.Buffer()
|
|
|
|
|
|
|
|
for point.Y = bounds.Min.Y; point.Y < bounds.Max.Y; point.Y ++ {
|
|
|
|
for point.X = bounds.Min.X; point.X < bounds.Max.X; point.X ++ {
|
|
|
|
srcPoint := point.Sub(at)
|
|
|
|
srcIndex := srcPoint.X + srcPoint.Y * icon.stride
|
|
|
|
dstIndex := point.X + point.Y * stride
|
|
|
|
if icon.data[srcIndex] {
|
|
|
|
data[dstIndex] = color
|
|
|
|
}
|
|
|
|
}}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (icon binaryIcon) Bounds () image.Rectangle {
|
|
|
|
return image.Rect(0, 0, icon.stride, len(icon.data) / icon.stride)
|
|
|
|
}
|
|
|
|
|
|
|
|
func binaryIconFrom (source image.Image, clip image.Rectangle) (icon binaryIcon) {
|
|
|
|
bounds := source.Bounds().Intersect(clip)
|
|
|
|
if bounds.Empty() { return }
|
|
|
|
|
|
|
|
icon.stride = bounds.Dx()
|
|
|
|
icon.data = make([]bool, bounds.Dx() * bounds.Dy())
|
|
|
|
|
|
|
|
point := image.Point { }
|
|
|
|
dstIndex := 0
|
|
|
|
for point.Y = bounds.Min.Y; point.Y < bounds.Max.Y; point.Y ++ {
|
|
|
|
for point.X = bounds.Min.X; point.X < bounds.Max.X; point.X ++ {
|
|
|
|
r, g, b, a := source.At(point.X, point.Y).RGBA()
|
|
|
|
if a > 0x8000 && (r + g + b) / 3 < 0x8000 {
|
|
|
|
icon.data[dstIndex] = true
|
|
|
|
}
|
|
|
|
dstIndex ++
|
|
|
|
}}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2023-02-27 10:48:44 -07:00
|
|
|
func init () {
|
|
|
|
defaultAtlasImage, _, _ := image.Decode(bytes.NewReader(defaultAtlasBytes))
|
2023-04-30 11:47:36 -06:00
|
|
|
defaultAtlas = artist.FromImage(defaultAtlasImage)
|
2023-02-27 10:48:44 -07:00
|
|
|
|
|
|
|
// PatternDead
|
|
|
|
atlasCol(0, artist.Inset { })
|
|
|
|
// PatternRaised
|
2023-02-27 22:17:05 -07:00
|
|
|
atlasCol(1, artist.Inset { 6, 6, 6, 6 })
|
2023-02-27 10:48:44 -07:00
|
|
|
// PatternSunken
|
|
|
|
atlasCol(2, artist.Inset { 4, 4, 4, 4 })
|
|
|
|
// PatternPinboard
|
|
|
|
atlasCol(3, artist.Inset { 2, 2, 2, 2 })
|
|
|
|
// PatternButton
|
2023-02-27 22:17:05 -07:00
|
|
|
atlasCol(4, artist.Inset { 6, 6, 6, 6 })
|
2023-02-27 10:48:44 -07:00
|
|
|
// PatternInput
|
|
|
|
atlasCol(5, artist.Inset { 4, 4, 4, 4 })
|
|
|
|
// PatternGutter
|
2023-02-28 15:15:20 -07:00
|
|
|
atlasCol(6, artist.Inset { 7, 7, 7, 7 })
|
2023-02-27 10:48:44 -07:00
|
|
|
// PatternHandle
|
2023-03-01 11:06:34 -07:00
|
|
|
atlasCol(7, artist.Inset { 3, 3, 3, 3 })
|
2023-02-27 22:17:05 -07:00
|
|
|
// PatternLine
|
|
|
|
atlasCol(8, artist.Inset { 1, 1, 1, 1 })
|
2023-02-28 17:00:34 -07:00
|
|
|
// PatternMercury
|
|
|
|
atlasCol(13, artist.Inset { 2, 2, 2, 2 })
|
2023-04-02 19:15:16 -06:00
|
|
|
// PatternTableHead:
|
|
|
|
atlasCol(14, artist.Inset { 4, 4, 4, 4 })
|
|
|
|
// PatternTableCell:
|
|
|
|
atlasCol(15, artist.Inset { 4, 4, 4, 4 })
|
2023-04-20 16:40:05 -06:00
|
|
|
// PatternLamp:
|
|
|
|
atlasCol(16, artist.Inset { 4, 3, 4, 3 })
|
2023-02-27 22:17:05 -07:00
|
|
|
|
|
|
|
// PatternButton: basic.checkbox
|
|
|
|
atlasCol(9, artist.Inset { 3, 3, 3, 3 })
|
|
|
|
// PatternRaised: basic.listEntry
|
|
|
|
atlasCol(10, artist.Inset { 3, 3, 3, 3 })
|
|
|
|
// PatternRaised: fun.flatKey
|
|
|
|
atlasCol(11, artist.Inset { 3, 3, 5, 3 })
|
|
|
|
// PatternRaised: fun.sharpKey
|
|
|
|
atlasCol(12, artist.Inset { 3, 3, 4, 3 })
|
2023-03-04 20:07:59 -07:00
|
|
|
|
|
|
|
// set up small icons
|
|
|
|
defaultIconsSmallAtlasImage, _, _ := image.Decode (
|
|
|
|
bytes.NewReader(defaultIconsSmallAtlasBytes))
|
|
|
|
point := image.Point { }
|
|
|
|
iconIndex := 0
|
2023-03-10 16:53:27 -07:00
|
|
|
for point.Y = 0; point.Y < 20; point.Y ++ {
|
|
|
|
for point.X = 0; point.X < 32; point.X ++ {
|
2023-03-04 20:07:59 -07:00
|
|
|
defaultIconsSmall[iconIndex] = binaryIconFrom (
|
|
|
|
defaultIconsSmallAtlasImage,
|
2023-03-10 16:53:27 -07:00
|
|
|
image.Rect(0, 0, 16, 16).Add(point.Mul(16)))
|
|
|
|
iconIndex ++
|
|
|
|
}}
|
|
|
|
|
|
|
|
// set up large icons
|
|
|
|
defaultIconsLargeAtlasImage, _, _ := image.Decode (
|
|
|
|
bytes.NewReader(defaultIconsLargeAtlasBytes))
|
|
|
|
point = image.Point { }
|
|
|
|
iconIndex = 0
|
|
|
|
for point.Y = 0; point.Y < 8; point.Y ++ {
|
|
|
|
for point.X = 0; point.X < 32; point.X ++ {
|
|
|
|
defaultIconsLarge[iconIndex] = binaryIconFrom (
|
|
|
|
defaultIconsLargeAtlasImage,
|
|
|
|
image.Rect(0, 0, 32, 32).Add(point.Mul(32)))
|
|
|
|
iconIndex ++
|
|
|
|
}}
|
|
|
|
iconIndex = 384
|
|
|
|
for point.Y = 8; point.Y < 12; point.Y ++ {
|
|
|
|
for point.X = 0; point.X < 32; point.X ++ {
|
|
|
|
defaultIconsLarge[iconIndex] = binaryIconFrom (
|
|
|
|
defaultIconsLargeAtlasImage,
|
|
|
|
image.Rect(0, 0, 32, 32).Add(point.Mul(32)))
|
2023-03-04 20:07:59 -07:00
|
|
|
iconIndex ++
|
|
|
|
}}
|
2023-02-27 10:48:44 -07:00
|
|
|
}
|
|
|
|
|
2023-02-02 22:57:18 -07:00
|
|
|
// Default is the default theme.
|
|
|
|
type Default struct { }
|
|
|
|
|
|
|
|
// FontFace returns the default font face.
|
2023-03-30 23:06:29 -06:00
|
|
|
func (Default) FontFace (style tomo.FontStyle, size tomo.FontSize, c tomo.Case) font.Face {
|
2023-02-02 22:57:18 -07:00
|
|
|
switch style {
|
2023-03-30 23:06:29 -06:00
|
|
|
case tomo.FontStyleBold:
|
2023-02-02 22:57:18 -07:00
|
|
|
return defaultfont.FaceBold
|
2023-03-30 23:06:29 -06:00
|
|
|
case tomo.FontStyleItalic:
|
2023-02-02 22:57:18 -07:00
|
|
|
return defaultfont.FaceItalic
|
2023-03-30 23:06:29 -06:00
|
|
|
case tomo.FontStyleBoldItalic:
|
2023-02-02 22:57:18 -07:00
|
|
|
return defaultfont.FaceBoldItalic
|
|
|
|
default:
|
|
|
|
return defaultfont.FaceRegular
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Icon returns an icon from the default set corresponding to the given name.
|
2023-03-30 23:06:29 -06:00
|
|
|
func (Default) Icon (id tomo.Icon, size tomo.IconSize, c tomo.Case) artist.Icon {
|
|
|
|
if size == tomo.IconSizeLarge {
|
2023-03-10 16:53:27 -07:00
|
|
|
if id < 0 || int(id) >= len(defaultIconsLarge) {
|
|
|
|
return nil
|
|
|
|
} else {
|
|
|
|
return defaultIconsLarge[id]
|
|
|
|
}
|
2023-03-04 20:07:59 -07:00
|
|
|
} else {
|
|
|
|
if id < 0 || int(id) >= len(defaultIconsSmall) {
|
|
|
|
return nil
|
|
|
|
} else {
|
|
|
|
return defaultIconsSmall[id]
|
|
|
|
}
|
|
|
|
}
|
2023-02-02 22:57:18 -07:00
|
|
|
}
|
|
|
|
|
2023-03-04 14:18:43 -07:00
|
|
|
// MimeIcon returns an icon from the default set corresponding to the given mime.
|
|
|
|
// type.
|
2023-03-30 23:06:29 -06:00
|
|
|
func (Default) MimeIcon (data.Mime, tomo.IconSize, tomo.Case) artist.Icon {
|
2023-03-04 14:18:43 -07:00
|
|
|
// TODO
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2023-02-02 22:57:18 -07:00
|
|
|
// Pattern returns a pattern from the default theme corresponding to the given
|
|
|
|
// pattern ID.
|
2023-03-30 23:06:29 -06:00
|
|
|
func (Default) Pattern (id tomo.Pattern, state tomo.State, c tomo.Case) artist.Pattern {
|
2023-02-27 10:48:44 -07:00
|
|
|
offset := 0; switch {
|
2023-04-02 17:01:06 -06:00
|
|
|
case state.Disabled: offset = 1
|
|
|
|
case state.Pressed && state.On: offset = 4
|
|
|
|
case state.Focused && state.On: offset = 6
|
|
|
|
case state.On: offset = 2
|
|
|
|
case state.Pressed: offset = 3
|
|
|
|
case state.Focused: offset = 5
|
2023-02-27 10:48:44 -07:00
|
|
|
}
|
|
|
|
|
2023-02-26 20:20:17 -07:00
|
|
|
switch id {
|
2023-03-30 23:06:29 -06:00
|
|
|
case tomo.PatternBackground: return patterns.Uhex(0xaaaaaaFF)
|
|
|
|
case tomo.PatternDead: return defaultTextures[0][offset]
|
2023-04-18 00:59:44 -06:00
|
|
|
case tomo.PatternRaised: return defaultTextures[1][offset]
|
|
|
|
case tomo.PatternSunken: return defaultTextures[2][offset]
|
|
|
|
case tomo.PatternPinboard: return defaultTextures[3][offset]
|
2023-03-30 23:06:29 -06:00
|
|
|
case tomo.PatternButton:
|
2023-03-23 15:34:08 -06:00
|
|
|
switch {
|
2023-03-30 21:19:04 -06:00
|
|
|
case c.Match("tomo", "checkbox", ""):
|
2023-03-23 15:34:08 -06:00
|
|
|
return defaultTextures[9][offset]
|
2023-03-30 21:19:04 -06:00
|
|
|
case c.Match("tomo", "piano", "flatKey"):
|
2023-03-23 15:34:08 -06:00
|
|
|
return defaultTextures[11][offset]
|
2023-03-30 21:19:04 -06:00
|
|
|
case c.Match("tomo", "piano", "sharpKey"):
|
2023-03-23 15:34:08 -06:00
|
|
|
return defaultTextures[12][offset]
|
|
|
|
default:
|
|
|
|
return defaultTextures[4][offset]
|
2023-02-27 22:17:05 -07:00
|
|
|
}
|
2023-04-02 19:15:16 -06:00
|
|
|
case tomo.PatternInput: return defaultTextures[5][offset]
|
|
|
|
case tomo.PatternGutter: return defaultTextures[6][offset]
|
|
|
|
case tomo.PatternHandle: return defaultTextures[7][offset]
|
|
|
|
case tomo.PatternLine: return defaultTextures[8][offset]
|
|
|
|
case tomo.PatternMercury: return defaultTextures[13][offset]
|
|
|
|
case tomo.PatternTableHead: return defaultTextures[14][offset]
|
|
|
|
case tomo.PatternTableCell: return defaultTextures[15][offset]
|
2023-04-20 16:40:05 -06:00
|
|
|
case tomo.PatternLamp: return defaultTextures[16][offset]
|
2023-04-02 20:02:55 -06:00
|
|
|
default: return patterns.Uhex(0xFF00FFFF)
|
2023-02-02 22:57:18 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-03-30 23:06:29 -06:00
|
|
|
func (Default) Color (id tomo.Color, state tomo.State, c tomo.Case) color.RGBA {
|
2023-04-30 11:47:36 -06:00
|
|
|
if state.Disabled { return artutil.Hex(0x444444FF) }
|
2023-04-01 23:56:19 -06:00
|
|
|
|
2023-04-30 11:47:36 -06:00
|
|
|
return artutil.Hex (map[tomo.Color] uint32 {
|
2023-04-20 17:57:55 -06:00
|
|
|
tomo.ColorBlack: 0x272d24FF,
|
|
|
|
tomo.ColorRed: 0x8c4230FF,
|
|
|
|
tomo.ColorGreen: 0x69905fFF,
|
|
|
|
tomo.ColorYellow: 0x9a973dFF,
|
|
|
|
tomo.ColorBlue: 0x3d808fFF,
|
|
|
|
tomo.ColorPurple: 0x8c608bFF,
|
|
|
|
tomo.ColorCyan: 0x3d8f84FF,
|
|
|
|
tomo.ColorWhite: 0xaea894FF,
|
|
|
|
tomo.ColorBrightBlack: 0x4f5142FF,
|
|
|
|
tomo.ColorBrightRed: 0xbd6f59FF,
|
|
|
|
tomo.ColorBrightGreen: 0x8dad84FF,
|
|
|
|
tomo.ColorBrightYellow: 0xe2c558FF,
|
|
|
|
tomo.ColorBrightBlue: 0x77b1beFF,
|
|
|
|
tomo.ColorBrightPurple: 0xc991c8FF,
|
|
|
|
tomo.ColorBrightCyan: 0x74c7b7FF,
|
|
|
|
tomo.ColorBrightWhite: 0xcfd7d2FF,
|
2023-04-01 23:56:19 -06:00
|
|
|
|
|
|
|
tomo.ColorForeground: 0x000000FF,
|
2023-04-20 17:57:55 -06:00
|
|
|
tomo.ColorMidground: 0x97A09BFF,
|
2023-04-01 23:56:19 -06:00
|
|
|
tomo.ColorBackground: 0xAAAAAAFF,
|
2023-04-20 17:57:55 -06:00
|
|
|
tomo.ColorShadow: 0x445754FF,
|
|
|
|
tomo.ColorShine: 0xCFD7D2FF,
|
2023-04-01 23:56:19 -06:00
|
|
|
tomo.ColorAccent: 0x408090FF,
|
|
|
|
} [id])
|
2023-02-26 20:20:17 -07:00
|
|
|
}
|
|
|
|
|
2023-02-25 22:44:44 -07:00
|
|
|
// Padding returns the default padding value for the given pattern.
|
2023-03-30 23:06:29 -06:00
|
|
|
func (Default) Padding (id tomo.Pattern, c tomo.Case) artist.Inset {
|
2023-02-27 15:00:28 -07:00
|
|
|
switch id {
|
2023-03-30 23:06:29 -06:00
|
|
|
case tomo.PatternSunken:
|
2023-04-18 00:59:44 -06:00
|
|
|
if c.Match("tomo", "progressBar", "") {
|
2023-03-23 16:05:30 -06:00
|
|
|
return artist.I(2, 1, 1, 2)
|
2023-04-21 15:49:27 -06:00
|
|
|
} else if c.Match("tomo", "list", "") {
|
|
|
|
return artist.I(2)
|
2023-04-21 20:24:28 -06:00
|
|
|
} else if c.Match("tomo", "flowList", "") {
|
|
|
|
return artist.I(2)
|
2023-02-27 22:17:05 -07:00
|
|
|
} else {
|
2023-03-23 16:05:30 -06:00
|
|
|
return artist.I(8)
|
2023-02-27 22:17:05 -07:00
|
|
|
}
|
2023-03-30 23:06:29 -06:00
|
|
|
case tomo.PatternPinboard:
|
2023-03-30 21:19:04 -06:00
|
|
|
if c.Match("tomo", "piano", "") {
|
2023-03-23 16:05:30 -06:00
|
|
|
return artist.I(2)
|
2023-02-27 15:00:28 -07:00
|
|
|
} else {
|
2023-03-23 16:05:30 -06:00
|
|
|
return artist.I(8)
|
2023-02-27 15:00:28 -07:00
|
|
|
}
|
2023-04-18 01:12:36 -06:00
|
|
|
case tomo.PatternTableCell: return artist.I(5)
|
|
|
|
case tomo.PatternTableHead: return artist.I(5)
|
2023-03-30 23:06:29 -06:00
|
|
|
case tomo.PatternGutter: return artist.I(0)
|
|
|
|
case tomo.PatternLine: return artist.I(1)
|
|
|
|
case tomo.PatternMercury: return artist.I(5)
|
2023-04-20 16:40:05 -06:00
|
|
|
case tomo.PatternLamp: return artist.I(5, 5, 5, 6)
|
2023-04-18 01:12:36 -06:00
|
|
|
default: return artist.I(8)
|
2023-02-27 15:00:28 -07:00
|
|
|
}
|
2023-02-25 22:44:44 -07:00
|
|
|
}
|
2023-02-09 09:38:01 -07:00
|
|
|
|
2023-02-25 22:44:44 -07:00
|
|
|
// Margin returns the default margin value for the given pattern.
|
2023-03-30 23:06:29 -06:00
|
|
|
func (Default) Margin (id tomo.Pattern, c tomo.Case) image.Point {
|
2023-04-18 01:12:36 -06:00
|
|
|
switch id {
|
|
|
|
case tomo.PatternSunken:
|
|
|
|
if c.Match("tomo", "list", "") {
|
|
|
|
return image.Pt(-1, -1)
|
2023-04-21 20:24:28 -06:00
|
|
|
} else if c.Match("tomo", "flowList", "") {
|
|
|
|
return image.Pt(-1, -1)
|
2023-04-18 01:12:36 -06:00
|
|
|
} else {
|
|
|
|
return image.Pt(8, 8)
|
|
|
|
}
|
|
|
|
default: return image.Pt(8, 8)
|
|
|
|
}
|
2023-02-02 22:57:18 -07:00
|
|
|
}
|
|
|
|
|
2023-02-16 16:00:15 -07:00
|
|
|
// Hints returns rendering optimization hints for a particular pattern.
|
|
|
|
// These are optional, but following them may result in improved
|
|
|
|
// performance.
|
2023-03-30 23:06:29 -06:00
|
|
|
func (Default) Hints (pattern tomo.Pattern, c tomo.Case) (hints tomo.Hints) {
|
2023-02-16 16:00:15 -07:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2023-02-02 22:57:18 -07:00
|
|
|
// Sink returns the default sink vector for the given pattern.
|
2023-03-30 23:06:29 -06:00
|
|
|
func (Default) Sink (pattern tomo.Pattern, c tomo.Case) image.Point {
|
2023-02-02 22:57:18 -07:00
|
|
|
return image.Point { 1, 1 }
|
|
|
|
}
|