Files
aego/ui/theme/theme.go
sanspie ab6ea7ef58
ci-platforms / darwin amd64 smoke (push) Skipped
ci-platforms / windows amd64 smoke (push) Skipped
ci-platforms / darwin arm64 smoke (push) Skipped
github-native / linux amd64 native (push) Skipped
ci / checks (push) Successful in 6m33s
ci / build (amd64, linux, ubuntu-24.04) (push) Failing after 12m26s
ci / release (push) Skipped
github-native / darwin amd64 native (push) Canceled after 0s
github-native / windows amd64 native (push) Canceled after 0s
github-native / darwin arm64 native (push) Canceled after 0s
editor: improve scene tools and prepare native CI
2026-11-06 16:49:11 +03:00

410 lines
13 KiB
Go

package theme
import (
"strings"
"aego/core/mathx"
)
type GutterMetrics struct {
MarkerW float32
NumberPad float32
CodePad float32
}
type CodeTheme struct {
Background mathx.Color
GutterBg mathx.Color
Gutter GutterMetrics
LineNo mathx.Color
LineNoActive mathx.Color
CurrentLine mathx.Color
CurrentBlock mathx.Color
Guide mathx.Color
IndentGuide mathx.Color
IndentGuideActive mathx.Color
GuideRainbow [6]mathx.Color
GuideRainbowActive [6]mathx.Color
BracketMatch mathx.Color
BracketMismatch mathx.Color
Rainbow [6]mathx.Color
Occurrence mathx.Color
OccurrenceDim mathx.Color
OccurrenceWrite mathx.Color
Plain mathx.Color
Keyword mathx.Color
Type mathx.Color
BuiltinType mathx.Color
Function mathx.Color
Method mathx.Color
Field mathx.Color
Property mathx.Color
Param mathx.Color
Local mathx.Color
Const mathx.Color
EnumMember mathx.Color
String mathx.Color
Number mathx.Color
Comment mathx.Color
Attribute mathx.Color
Namespace mathx.Color
Script mathx.Color
Signal mathx.Color
Coroutine mathx.Color
Builtin mathx.Color
Operator mathx.Color
Punctuation mathx.Color
Error mathx.Color
// MinimapView frames the visible part of the file on the minimap.
MinimapView mathx.Color
GuideColumn int
}
type Radius struct {
Button float32
Field float32
Menu float32
Popup float32
Tab float32
Card float32
}
type Theme struct {
Text mathx.Color
TextDim mathx.Color
TextDisabled mathx.Color
Background mathx.Color
Panel mathx.Color
PanelAlt mathx.Color
Header mathx.Color
Widget mathx.Color
WidgetHot mathx.Color
WidgetDown mathx.Color
Accent mathx.Color
AccentDim mathx.Color
Selection mathx.Color
SelectionDim mathx.Color
Border mathx.Color
Separator mathx.Color
Error mathx.Color
Warn mathx.Color
Ok mathx.Color
DropTarget mathx.Color
Overlay mathx.Color
Tab mathx.Color
TabHot mathx.Color
TabActive mathx.Color
AxisX mathx.Color
AxisY mathx.Color
AxisZ mathx.Color
AxisHot mathx.Color
PlayTint mathx.Color
CardBg mathx.Color
CardHeader mathx.Color
Badge mathx.Color
Unit mathx.Color
BarBg mathx.Color
BarBorder mathx.Color
ButtonHover mathx.Color
ButtonActive mathx.Color
Code CodeTheme
Radius Radius
Shadow mathx.Color
ShadowBlur float32
ShadowY float32
TreeGuide mathx.Color
// Highlight marks the hovered column of a chart.
Highlight mathx.Color
FieldGizmo mathx.Color
Record mathx.Color
StateEntry mathx.Color
StateAny mathx.Color
// Series colours the lines and bars of charts, in order.
Series [8]mathx.Color
// Events colours animation events by mode: gameplay, presentation, preview.
Events [3]mathx.Color
// Agent colours the Agent panel: the user's messages, tool steps and the two sides of a change (11b5a).
Agent AgentTheme
// Game viewport colours stay the same in every theme. Scene colours follow
// the editor theme so the grid and gizmos remain visible on the canvas.
ViewportBg mathx.Color
SceneBg mathx.Color
ViewportText mathx.Color
ViewportLine mathx.Color
SceneText mathx.Color
SceneLine mathx.Color
GridMinor mathx.Color
GridMajor mathx.Color
Dark bool
Padding float32
BarHeight float32
RowHeight float32
Spacing float32
Indent float32
ScrollbarW float32
SplitterW float32
TabHeight float32
MenuHeight float32
FontSize float32
Mixed string
}
func Default() Theme {
t := Editor()
t.Text = mathx.ColorRGB8(0xEA, 0xEA, 0xEA)
t.TextDim = mathx.ColorRGB8(0x9A, 0x9A, 0x9A)
t.Panel = mathx.Color{R: 0.02, G: 0.02, B: 0.03, A: 0.78}
t.Widget = mathx.Color{R: 0.16, G: 0.17, B: 0.20, A: 0.95}
t.WidgetHot = mathx.Color{R: 0.24, G: 0.26, B: 0.31, A: 0.95}
t.WidgetDown = mathx.Color{R: 0.10, G: 0.11, B: 0.14, A: 0.95}
t.Accent = mathx.ColorRGB8(0xE0, 0x7B, 0x39)
t.Border = mathx.Color{R: 1, G: 1, B: 1, A: 0.12}
t.Padding = 6
t.RowHeight = 16
t.Spacing = 4
t.FontSize = 13
t.Radius = Radius{}
t.Shadow = mathx.Color{}
return t
}
func Mix(a, b mathx.Color, t float32) mathx.Color {
ch := func(x, y float32) float32 {
s := mathx.LinearToSRGB(mathx.Clamp(x, 0, 1))
d := mathx.LinearToSRGB(mathx.Clamp(y, 0, 1))
return mathx.SRGBToLinear(s + (d-s)*t)
}
return mathx.Color{R: ch(a.R, b.R), G: ch(a.G, b.G), B: ch(a.B, b.B), A: 1}
}
func mixAll(bg mathx.Color, cs [6]mathx.Color, t float32) [6]mathx.Color {
var out [6]mathx.Color
for i, c := range cs {
out[i] = Mix(bg, c, t)
}
return out
}
func Code() CodeTheme {
bg := mathx.ColorRGB8(0x1A, 0x1D, 0x22)
white := mathx.Color{R: 1, G: 1, B: 1, A: 1}
blue := mathx.ColorRGB8(0x4D, 0x78, 0xB8)
warm := mathx.ColorRGB8(0xE5, 0x9B, 0x4B)
rainbow := [6]mathx.Color{
mathx.ColorRGB8(0xE5, 0xC0, 0x7B),
mathx.ColorRGB8(0xC6, 0x78, 0xDD),
mathx.ColorRGB8(0x61, 0xAF, 0xEF),
mathx.ColorRGB8(0x98, 0xC3, 0x79),
mathx.ColorRGB8(0xD1, 0x9A, 0x66),
mathx.ColorRGB8(0x56, 0xB6, 0xC2),
}
return CodeTheme{
Background: bg,
GutterBg: mathx.ColorRGB8(0x17, 0x19, 0x1D),
Gutter: GutterMetrics{MarkerW: 1, NumberPad: 1, CodePad: 1},
LineNo: mathx.ColorRGB8(0x5C, 0x61, 0x6B),
LineNoActive: mathx.ColorRGB8(0xB0, 0xB5, 0xBF),
CurrentLine: Mix(bg, white, 0.035),
CurrentBlock: Mix(bg, white, 0.012),
Guide: Mix(bg, white, 0.05),
IndentGuide: Mix(bg, white, 0.07),
IndentGuideActive: Mix(bg, white, 0.16),
GuideRainbow: mixAll(bg, rainbow, 0.18),
GuideRainbowActive: mixAll(bg, rainbow, 0.4),
BracketMatch: mathx.ColorRGB8(0xE5, 0xC0, 0x7B),
BracketMismatch: mathx.ColorRGB8(0xE5, 0x53, 0x4B),
Rainbow: rainbow,
Occurrence: Mix(bg, blue, 0.22),
OccurrenceDim: Mix(bg, blue, 0.12),
OccurrenceWrite: Mix(bg, warm, 0.55),
Plain: mathx.ColorRGB8(0xD8, 0xDB, 0xE2),
Keyword: mathx.ColorRGB8(0xC6, 0x78, 0xDD),
Type: mathx.ColorRGB8(0x56, 0xB6, 0xC2),
BuiltinType: mathx.ColorRGB8(0x56, 0xB6, 0xC2),
Function: mathx.ColorRGB8(0x61, 0xAF, 0xEF),
Method: mathx.ColorRGB8(0x61, 0xAF, 0xEF),
Field: mathx.ColorRGB8(0xE0, 0x6C, 0x75),
Property: mathx.ColorRGB8(0xE0, 0x6C, 0x75),
Param: mathx.ColorRGB8(0xE8, 0xA8, 0x7C),
Local: mathx.ColorRGB8(0xD8, 0xDB, 0xE2),
Const: mathx.ColorRGB8(0xD1, 0x9A, 0x66),
EnumMember: mathx.ColorRGB8(0xD1, 0x9A, 0x66),
String: mathx.ColorRGB8(0x98, 0xC3, 0x79),
Number: mathx.ColorRGB8(0xD1, 0x9A, 0x66),
Comment: mathx.ColorRGB8(0x6B, 0x72, 0x80),
Attribute: mathx.ColorRGB8(0xE5, 0xC0, 0x7B),
Namespace: mathx.ColorRGB8(0xE0, 0x7B, 0x39),
Script: mathx.ColorRGB8(0x56, 0xB6, 0xC2),
Signal: mathx.ColorRGB8(0xF2, 0x9E, 0x74),
Coroutine: mathx.ColorRGB8(0x7F, 0xCF, 0xC9),
Builtin: mathx.ColorRGB8(0xE5, 0x8E, 0xC6),
Operator: mathx.ColorRGB8(0xAB, 0xB2, 0xBF),
Punctuation: mathx.ColorRGB8(0x8B, 0x92, 0x9E),
Error: mathx.ColorRGB8(0xE5, 0x53, 0x4B),
MinimapView: mathx.Color{R: 1, G: 1, B: 1, A: 0.06},
GuideColumn: 100,
}
}
func Editor() Theme {
t := Theme{
Text: mathx.ColorRGB8(0xE4, 0xE6, 0xEB),
TextDim: mathx.ColorRGB8(0x93, 0x98, 0xA3),
TextDisabled: mathx.ColorRGB8(0x5C, 0x61, 0x6B),
Background: mathx.ColorRGB8(0x15, 0x17, 0x1B),
Panel: mathx.ColorRGB8(0x1E, 0x21, 0x26),
PanelAlt: mathx.ColorRGB8(0x23, 0x26, 0x2C),
Header: mathx.ColorRGB8(0x18, 0x1A, 0x1E),
Widget: mathx.ColorRGB8(0x2B, 0x2F, 0x36),
WidgetHot: mathx.ColorRGB8(0x36, 0x3B, 0x44),
WidgetDown: mathx.ColorRGB8(0x22, 0x25, 0x2B),
Accent: mathx.ColorRGB8(0xE0, 0x7B, 0x39),
AccentDim: mathx.Color{R: 0.75, G: 0.2, B: 0.05, A: 0.5},
Selection: mathx.ColorRGB8(0x2F, 0x5A, 0x8C),
SelectionDim: mathx.ColorRGB8(0x2A, 0x3A, 0x4F),
Border: mathx.Color{R: 1, G: 1, B: 1, A: 0.08},
Separator: mathx.Color{R: 1, G: 1, B: 1, A: 0.06},
Error: mathx.ColorRGB8(0xE5, 0x53, 0x4B),
Warn: mathx.ColorRGB8(0xE5, 0xB5, 0x4B),
Ok: mathx.ColorRGB8(0x6B, 0xC4, 0x6D),
DropTarget: mathx.Color{R: 0.2, G: 0.35, B: 0.6, A: 0.45},
Overlay: mathx.Color{A: 0.45},
Tab: mathx.ColorRGB8(0x1B, 0x1D, 0x22),
TabHot: mathx.ColorRGB8(0x26, 0x29, 0x30),
TabActive: mathx.ColorRGB8(0x1E, 0x21, 0x26),
AxisX: mathx.ColorRGB8(0xE5, 0x53, 0x4B),
AxisY: mathx.ColorRGB8(0x6B, 0xC4, 0x6D),
AxisZ: mathx.ColorRGB8(0x4B, 0x8B, 0xE5),
AxisHot: mathx.ColorRGB8(0xF5, 0xD7, 0x4B),
PlayTint: mathx.Color{R: 0.88, G: 0.48, B: 0.22, A: 0.85},
CardBg: mathx.ColorRGB8(0x22, 0x25, 0x2B),
CardHeader: mathx.ColorRGB8(0x29, 0x2D, 0x34),
Badge: mathx.ColorRGB8(0x34, 0x38, 0x40),
Unit: mathx.ColorRGB8(0x7A, 0x80, 0x8C),
BarBg: mathx.ColorRGB8(0x1A, 0x1C, 0x21),
BarBorder: mathx.Color{R: 1, G: 1, B: 1, A: 0.06},
ButtonHover: mathx.ColorRGB8(0x30, 0x34, 0x3C),
ButtonActive: mathx.ColorRGB8(0x2F, 0x5A, 0x8C),
Code: Code(),
Radius: Radius{Button: 4, Field: 4, Menu: 4, Popup: 7, Tab: 5, Card: 6},
Shadow: mathx.Color{A: 0.45},
ShadowBlur: 12,
ShadowY: 4,
TreeGuide: mathx.Color{R: 1, G: 1, B: 1, A: 0.07},
BarHeight: 28,
Padding: 6,
RowHeight: 22,
Spacing: 2,
Indent: 14,
ScrollbarW: 8,
SplitterW: 4,
TabHeight: 26,
MenuHeight: 24,
FontSize: 14,
Mixed: "—",
}
t.Highlight = mathx.Color{R: 1, G: 1, B: 1, A: 0.08}
t.FieldGizmo = mathx.Color{R: 0.35, G: 0.55, B: 1, A: 1}
t.Record = mathx.Color{R: 0.9, G: 0.25, B: 0.25, A: 1}
t.StateEntry = mathx.Color{R: 0.3, G: 0.7, B: 0.4, A: 1}
t.StateAny = mathx.Color{R: 0.55, G: 0.5, B: 0.8, A: 1}
t.Series = series()
t.Events = [3]mathx.Color{{R: 0.95, G: 0.6, B: 0.2, A: 1}, {R: 0.4, G: 0.7, B: 1, A: 1}, {R: 0.6, G: 0.9, B: 0.4, A: 1}}
t.Dark = true
t.Agent = agentDark()
viewport(&t)
return t
}
type AgentTheme struct {
User mathx.Color
Tool mathx.Color
DiffAdd mathx.Color
DiffRemove mathx.Color
// Presence marks where the agent works: its avatar and the second selection outline.
Presence mathx.Color
}
func agentDark() AgentTheme {
return AgentTheme{
User: mathx.ColorRGB8(0x2A, 0x33, 0x40),
Tool: mathx.ColorRGB8(0x22, 0x25, 0x2B),
DiffAdd: mathx.ColorRGB8(0x7C, 0xC9, 0x7E),
DiffRemove: mathx.ColorRGB8(0xE5, 0x86, 0x7E),
Presence: mathx.ColorRGB8(0xB3, 0x8D, 0xF5),
}
}
func agentLight() AgentTheme {
return AgentTheme{
User: mathx.ColorRGB8(0xE2, 0xEA, 0xF5),
Tool: mathx.ColorRGB8(0xEB, 0xED, 0xF0),
DiffAdd: mathx.ColorRGB8(0x23, 0x7A, 0x33),
DiffRemove: mathx.ColorRGB8(0xB3, 0x30, 0x26),
Presence: mathx.ColorRGB8(0x6E, 0x47, 0xC2),
}
}
func series() [8]mathx.Color {
return [8]mathx.Color{
mathx.ColorRGB8(0x4B, 0x8B, 0xE5),
mathx.ColorRGB8(0x6B, 0xC4, 0x6D),
mathx.ColorRGB8(0xE0, 0x7B, 0x39),
mathx.ColorRGB8(0xA0, 0x6B, 0xD5),
mathx.ColorRGB8(0x3F, 0xB6, 0xB0),
mathx.ColorRGB8(0xD5, 0x6B, 0x9A),
mathx.ColorRGB8(0xC9, 0xB0, 0x3F),
mathx.ColorRGB8(0x7A, 0x8C, 0xA3),
}
}
// viewport sets the dark viewport and scene colours.
func viewport(t *Theme) {
t.ViewportBg = mathx.Color{R: 0.02, G: 0.02, B: 0.03, A: 1}
t.SceneBg = mathx.ColorRGB8(0x15, 0x17, 0x1B)
t.ViewportText = mathx.ColorRGB8(0x93, 0x98, 0xA3)
t.ViewportText.A = 0.7
t.ViewportLine = mathx.ColorRGB8(0xE4, 0xE6, 0xEB)
t.SceneText = t.ViewportText
t.SceneLine = t.ViewportLine
t.GridMinor = mathx.Color{R: 1, G: 1, B: 1, A: 0.018}
t.GridMajor = mathx.Color{R: 1, G: 1, B: 1, A: 0.045}
}
// Mode is the theme the user picked; System follows the OS (D345).
type Mode uint8
const (
ModeSystem Mode = iota
ModeDark
ModeLight
)
var modeNames = [...]string{"System", "Dark", "Light"}
func (m Mode) String() string {
if int(m) < len(modeNames) {
return modeNames[m]
}
return modeNames[0]
}
func ParseMode(s string) Mode {
for i, n := range modeNames {
if strings.EqualFold(n, s) {
return Mode(i)
}
}
return ModeSystem
}
// For is the theme of a mode, given whether the OS is dark.
func For(m Mode, systemDark bool) Theme {
if m == ModeLight || (m == ModeSystem && !systemDark) {
return Light()
}
return Editor()
}