package aluminum import "bytes" import "image" import _ "embed" import _ "image/png" import "image/color" import "git.tebibyte.media/tomo/tomo" import "golang.org/x/image/font/basicfont" import "git.tebibyte.media/tomo/tomo/data" import "git.tebibyte.media/tomo/tomo/theme" import "git.tebibyte.media/tomo/tomo/input" import "git.tebibyte.media/tomo/tomo/event" import "git.tebibyte.media/tomo/tomo/canvas" //go:embed assets/mime-generic-medium.png var genericMimeIconsMedium []byte //go:embed assets/mime-medium.png var mimeIconsMedium []byte func hex (color uint32) (c color.RGBA) { c.A = uint8(color) c.B = uint8(color >> 8) c.G = uint8(color >> 16) c.R = uint8(color >> 24) return } func border (top, right, bottom, left uint32, width ...int) tomo.Border { return tomo.Border { Width: tomo.I(width...), Color: [4]color.Color { hex(top), hex(right), hex(bottom), hex(left), }, } } var engravedBorder = border(0xc3c3c5FF, 0xe3e3e3FF, 0xe9e9e9ff, 0xc2c2c2ff, 1) var gapBorder = border(0x697c7cFF, 0x566767FF, 0x566767ff, 0x697c7cff, 1) var focusedBorder = border(0x5f8bc4FF, 0x5f8bc4FF, 0x5f8bc4ff, 0x5f8bc4ff, 1) var buttonBorder = border(0xf9fafcFF, 0xc2c8d3FF, 0xa4afc0ff, 0xf5f6f8ff, 1) var sunkenShadow = border(0xa4afc0FF, 0xa4afc0FF, 0xa4afc0ff, 0xa4afc0ff, 1, 0, 0, 1) var outerShadow = border(0xa4afc0FF, 0xa4afc0FF, 0xa4afc0ff, 0xa4afc0ff, 0, 1, 1, 0) var buttonColor = hex(0xe9eaeaFF) var buttonColorPressed = hex(0xe3e4e4FF) var buttonColorFocused = hex(0xe4e6e8FF) var buttonColorHovered = hex(0xf1f3f5FF) var dotColor = hex(0x7391c080) var inputColor = hex(0xe3e4e4FF) var textColor = hex(0x000000FF) var backgroundColor = hex(0xd6d6d6FF) var gutterColor = hex(0xbfc6d1FF) var gutterColorHovered = hex(0xc5cbd6FF) type Theme struct { unpackedIcons bool icons iconSet[theme.Icon] mimeIcons iconSet[string] genericMimeIcons iconSet[string] appIcons iconSet[string] genericAppIcons iconSet[tomo.ApplicationRole] } func (this *Theme) RGBA (id theme.Color) (r, g, b, a uint32) { switch id { case theme.ColorBackground: return backgroundColor .RGBA() case theme.ColorForeground: return textColor .RGBA() case theme.ColorRaised: return buttonColor .RGBA() case theme.ColorSunken: return inputColor .RGBA() case theme.ColorAccent: return hex(0x5f8bc4FF) .RGBA() default: return color.Transparent.RGBA() } } func (this *Theme) Apply (object tomo.Object, role theme.Role) event.Cookie { box := object.GetBox() if textBox, ok := box.(tomo.TextBox); ok { textBox.SetDotColor(dotColor) textBox.SetTextColor(textColor) textBox.SetFace(basicfont.Face7x13) } if containerBox, ok := box.(tomo.ContainerBox); ok { containerBox.SetGap(image.Pt(8, 8)) } switch role.Object { case "Button": pressed := false hovered := false updateStyle := func () { border := []tomo.Border { engravedBorder, gapBorder, buttonBorder, } padding := tomo.I(4, 8) box.SetColor(buttonColor) if box.Focused() { box.SetColor(buttonColorFocused) border[1] = focusedBorder } if pressed { box.SetColor(buttonColorPressed) padding = tomo.I(5, 8, 4, 9) border[2] = sunkenShadow } else if hovered { box.SetColor(buttonColorHovered) } box.SetBorder(border...) box.SetPadding(padding) } updateStyle() return event.MultiCookie ( box.OnMouseDown(func (button input.Button) { if button != input.ButtonLeft { return } pressed = true updateStyle() }), box.OnMouseUp(func (button input.Button) { if button != input.ButtonLeft { return } pressed = false updateStyle() }), box.OnFocusEnter(updateStyle), box.OnFocusLeave(updateStyle), box.OnMouseEnter(func () { hovered = true updateStyle() }), box.OnMouseLeave(func () { hovered = false updateStyle() })) case "TextInput", "NumberInput": box.SetPadding(tomo.I(5, 4, 4, 5)) updateStyle := func () { border := []tomo.Border { engravedBorder, gapBorder, sunkenShadow, } box.SetColor(inputColor) if box.Focused() { border[1] = focusedBorder } box.SetBorder(border...) } updateStyle() return event.MultiCookie ( box.OnFocusEnter(updateStyle), box.OnFocusLeave(updateStyle)) case "Container": if role.Variant == "outer" { box.SetColor(backgroundColor) box.SetPadding(tomo.I(8)) } case "Heading": if textBox, ok := box.(tomo.TextBox); ok { textBox.SetAlign(tomo.AlignMiddle, tomo.AlignMiddle) } case "Separator": box.SetBorder(engravedBorder) case "Slider": pressed := false hovered := false if role.Variant == "vertical" { box.SetMinimumSize(image.Pt(0, 48)) } else { box.SetMinimumSize(image.Pt(48, 0)) } updateStyle := func () { border := []tomo.Border { engravedBorder, gapBorder, sunkenShadow, } box.SetColor(gutterColor) if box.Focused() { border[1] = focusedBorder } if hovered && !pressed { box.SetColor(gutterColorHovered) } box.SetBorder(border...) } updateStyle() return event.MultiCookie ( box.OnFocusEnter(updateStyle), box.OnFocusLeave(updateStyle), box.OnMouseDown(func (button input.Button) { pressed = true updateStyle() }), box.OnMouseUp(func (button input.Button) { pressed = false updateStyle() }), box.OnMouseEnter(func () { hovered = true updateStyle() }), box.OnMouseLeave(func () { hovered = false updateStyle() })) case "SliderHandle": box.SetMinimumSize(image.Pt(12, 12)) border := []tomo.Border { outerShadow, gapBorder, buttonBorder, } box.SetBorder(border...) box.SetColor(buttonColor) } return event.MultiCookie() } func (this *Theme) Icon (id theme.Icon, size theme.IconSize) canvas.Texture { this.ensureIcons() return this.icons.Get(id, size) } func (this *Theme) MimeIcon (mime data.Mime, size theme.IconSize) canvas.Texture { this.ensureIcons() texture := this.mimeIcons.Get(mime.String(), size) if texture != nil { return texture } else { return this.genericMimeIcons.Get(mime.Type, size) } } func (this *Theme) ApplicationIcon (id theme.ApplicationIcon, size theme.IconSize) canvas.Texture { this.ensureIcons() texture := this.appIcons.Get(id.Name, size) if texture != nil { return texture } else { return this.genericAppIcons.Get(id.Role, size) } } func (this *Theme) ensureIcons () { if this.unpackedIcons { return } this.unpackedIcons = true // this.icons.Init () // TODO this.mimeIcons.Init ( nil, // TODO bytes.NewReader(mimeIconsMedium), nil, // TODO 32, 12, // TODO ) this.genericMimeIcons.Init ( nil, // TODO bytes.NewReader(genericMimeIconsMedium), nil, // TODO 1, 5, i(0, 0, "text"), i(1, 0, "image"), i(2, 0, "audio"), i(3, 0, "video"), i(4, 0, "application"), ) // this.appIcons.Init () // TODO // this.genericAppIcons.Init () // TODO }