diff --git a/config/config.go b/config/config.go index ab962be..cdc4868 100644 --- a/config/config.go +++ b/config/config.go @@ -1,5 +1,6 @@ package config +// Config can return global configuration parameters. type Config interface { // Padding returns the amount of internal padding elements should have. // An element's inner content (such as text) should be inset by this diff --git a/theme/button.go b/theme/button.go deleted file mode 100644 index 213d341..0000000 --- a/theme/button.go +++ /dev/null @@ -1,48 +0,0 @@ -package theme - -import "git.tebibyte.media/sashakoshka/tomo/artist" - -var buttonPattern = artist.NewMultiBordered ( - artist.Stroke { Weight: 1, Pattern: strokePattern }, - artist.Stroke { - Weight: 1, - Pattern: artist.Beveled { - artist.NewUniform(hex(0xCCD5D2FF)), - artist.NewUniform(hex(0x4B5B59FF)), - }, - }, - artist.Stroke { Pattern: artist.NewUniform(hex(0x8D9894FF)) }) -var selectedButtonPattern = artist.NewMultiBordered ( - artist.Stroke { Weight: 1, Pattern: strokePattern }, - artist.Stroke { - Weight: 1, - Pattern: artist.Beveled { - artist.NewUniform(hex(0xCCD5D2FF)), - artist.NewUniform(hex(0x4B5B59FF)), - }, - }, - artist.Stroke { Weight: 1, Pattern: accentPattern }, - artist.Stroke { Pattern: artist.NewUniform(hex(0x8D9894FF)) }) -var pressedButtonPattern = artist.NewMultiBordered ( - artist.Stroke { Weight: 1, Pattern: strokePattern }, - artist.Stroke { - Weight: 1, - Pattern: artist.Beveled { - artist.NewUniform(hex(0x4B5B59FF)), - artist.NewUniform(hex(0x8D9894FF)), - }, - }, - artist.Stroke { Pattern: artist.NewUniform(hex(0x8D9894FF)) }) -var pressedSelectedButtonPattern = artist.NewMultiBordered ( - artist.Stroke { Weight: 1, Pattern: strokePattern }, - artist.Stroke { - Weight: 1, - Pattern: artist.Beveled { - artist.NewUniform(hex(0x4B5B59FF)), - artist.NewUniform(hex(0x8D9894FF)), - }, - }, - artist.Stroke { Pattern: artist.NewUniform(hex(0x8D9894FF)) }) -var disabledButtonPattern = artist.NewMultiBordered ( - artist.Stroke { Weight: 1, Pattern: weakForegroundPattern }, - artist.Stroke { Pattern: backgroundPattern }) diff --git a/theme/default.go b/theme/default.go new file mode 100644 index 0000000..5d2eec6 --- /dev/null +++ b/theme/default.go @@ -0,0 +1,167 @@ +package theme + +import "image" +import "golang.org/x/image/font" +import "git.tebibyte.media/sashakoshka/tomo/artist" +import "git.tebibyte.media/sashakoshka/tomo/defaultfont" + +// Default is the default theme. +type Default struct { } + +// FontFace returns the default font face. +func (Default) FontFace (style FontStyle, size FontSize, c Case) font.Face { + switch style { + case FontStyleBold: + return defaultfont.FaceBold + case FontStyleItalic: + return defaultfont.FaceItalic + case FontStyleBoldItalic: + return defaultfont.FaceBoldItalic + default: + return defaultfont.FaceRegular + } +} + +// Icon returns an icon from the default set corresponding to the given name. +func (Default) Icon (string, Case) artist.Pattern { + // TODO + return uhex(0) +} + +// Pattern returns a pattern from the default theme corresponding to the given +// pattern ID. +func (Default) Pattern ( + pattern Pattern, + c Case, + state PatternState, +) artist.Pattern { + switch pattern { + case PatternAccent: + return accentPattern + case PatternBackground: + return backgroundPattern + case PatternForeground: + if state.Disabled { + return weakForegroundPattern + } else { + return foregroundPattern + } + case PatternDead: + return deadPattern + case PatternRaised: + if c == C("basic", "listEntry") { + if state.Focused { + if state.On { + return focusedOnListEntryPattern + } else { + return focusedListEntryPattern + } + } else { + if state.On { + return onListEntryPattern + } else { + return listEntryPattern + } + } + } else { + if state.Focused { + return selectedRaisedPattern + } else { + return raisedPattern + } + } + case PatternSunken: + if c == C("basic", "list") { + if state.Focused { + return focusedListPattern + } else { + return listPattern + } + } else { + return sunkenPattern + } + case PatternPinboard: + return texturedSunkenPattern + case PatternButton: + if state.Disabled { + return disabledButtonPattern + } else { + if state.Pressed { + if state.Focused { + return pressedSelectedButtonPattern + } else { + return pressedButtonPattern + } + } else { + if state.Focused { + return selectedButtonPattern + } else { + return buttonPattern + } + } + } + case PatternInput: + if state.Disabled { + return disabledInputPattern + } else { + if state.Focused { + return selectedInputPattern + } else { + return inputPattern + } + } + case PatternGutter: + if state.Disabled { + return disabledScrollGutterPattern + } else { + return scrollGutterPattern + } + case PatternHandle: + if state.Disabled { + return disabledScrollBarPattern + } else { + if state.Focused { + if state.Pressed { + return pressedSelectedScrollBarPattern + } else { + return selectedScrollBarPattern + } + } else { + if state.Pressed { + return pressedScrollBarPattern + } else { + return scrollBarPattern + } + } + } + default: + return uhex(0) + } +} + +// Inset returns the default inset value for the given pattern. +func (Default) Inset (pattern Pattern, c Case) Inset { + switch pattern { + case PatternRaised: + if c == C("basic", "listEntry") { + return Inset { 2, 1, 2, 1 } + } else { + return Inset { 1, 1, 1, 1 } + } + case PatternSunken: + if c == C("basic", "list") { + return Inset { 4, 6, 4, 6 } + } else { + return Inset { 1, 1, 1, 1 } + } + + case PatternInput, PatternButton, PatternHandle, PatternPinboard: + return Inset { 1, 1, 1, 1} + default: return Inset { } + } +} + +// Sink returns the default sink vector for the given pattern. +func (Default) Sink (pattern Pattern, c Case) image.Point { + return image.Point { 1, 1 } +} diff --git a/theme/defaultpatterns.go b/theme/defaultpatterns.go new file mode 100644 index 0000000..6d32a42 --- /dev/null +++ b/theme/defaultpatterns.go @@ -0,0 +1,237 @@ +package theme + +import "image/color" +import "git.tebibyte.media/sashakoshka/tomo/artist" + +var accentPattern = artist.NewUniform(hex(0x408090FF)) +var backgroundPattern = artist.NewUniform(color.Gray16 { 0xAAAA }) +var foregroundPattern = artist.NewUniform(color.Gray16 { 0x0000 }) +var weakForegroundPattern = artist.NewUniform(color.Gray16 { 0x4444 }) +var strokePattern = artist.NewUniform(color.Gray16 { 0x0000 }) + +var sunkenPattern = artist.NewMultiBordered ( + artist.Stroke { Weight: 1, Pattern: strokePattern }, + artist.Stroke { + Weight: 1, + Pattern: artist.Beveled { + artist.NewUniform(hex(0x3b534eFF)), + artist.NewUniform(hex(0x97a09cFF)), + }, + }, + artist.Stroke { Pattern: artist.NewUniform(hex(0x97a09cFF)) }) + +var texturedSunkenPattern = artist.NewMultiBordered ( + artist.Stroke { Weight: 1, Pattern: strokePattern }, + artist.Stroke { + Weight: 1, + Pattern: artist.Beveled { + artist.NewUniform(hex(0x3b534eFF)), + artist.NewUniform(hex(0x97a09cFF)), + }, + }, + // artist.Stroke { Pattern: artist.Striped { + // First: artist.Stroke { + // Weight: 2, + // Pattern: artist.NewUniform(hex(0x97a09cFF)), + // }, + // Second: artist.Stroke { + // Weight: 1, + // Pattern: artist.NewUniform(hex(0x6e8079FF)), + // }, + // }}) + + artist.Stroke { Pattern: artist.Noisy { + Low: artist.NewUniform(hex(0x97a09cFF)), + High: artist.NewUniform(hex(0x6e8079FF)), + }}) + +var raisedPattern = artist.NewMultiBordered ( + artist.Stroke { Weight: 1, Pattern: strokePattern }, + artist.Stroke { + Weight: 1, + Pattern: artist.Beveled { + artist.NewUniform(hex(0xDBDBDBFF)), + artist.NewUniform(hex(0x383C3AFF)), + }, + }, + artist.Stroke { Pattern: artist.NewUniform(hex(0xAAAAAAFF)) }) + +var selectedRaisedPattern = artist.NewMultiBordered ( + artist.Stroke { Weight: 1, Pattern: strokePattern }, + artist.Stroke { + Weight: 1, + Pattern: artist.Beveled { + artist.NewUniform(hex(0xDBDBDBFF)), + artist.NewUniform(hex(0x383C3AFF)), + }, + }, + artist.Stroke { Weight: 1, Pattern: accentPattern }, + artist.Stroke { Pattern: artist.NewUniform(hex(0xAAAAAAFF)) }) + +var deadPattern = artist.NewMultiBordered ( + artist.Stroke { Weight: 1, Pattern: strokePattern }, + artist.Stroke { Pattern: artist.NewUniform(hex(0x97a09cFF)) }) + +var buttonPattern = artist.NewMultiBordered ( + artist.Stroke { Weight: 1, Pattern: strokePattern }, + artist.Stroke { + Weight: 1, + Pattern: artist.Beveled { + artist.NewUniform(hex(0xCCD5D2FF)), + artist.NewUniform(hex(0x4B5B59FF)), + }, + }, + artist.Stroke { Pattern: artist.NewUniform(hex(0x8D9894FF)) }) +var selectedButtonPattern = artist.NewMultiBordered ( + artist.Stroke { Weight: 1, Pattern: strokePattern }, + artist.Stroke { + Weight: 1, + Pattern: artist.Beveled { + artist.NewUniform(hex(0xCCD5D2FF)), + artist.NewUniform(hex(0x4B5B59FF)), + }, + }, + artist.Stroke { Weight: 1, Pattern: accentPattern }, + artist.Stroke { Pattern: artist.NewUniform(hex(0x8D9894FF)) }) + +var pressedButtonPattern = artist.NewMultiBordered ( + artist.Stroke { Weight: 1, Pattern: strokePattern }, + artist.Stroke { + Weight: 1, + Pattern: artist.Beveled { + artist.NewUniform(hex(0x4B5B59FF)), + artist.NewUniform(hex(0x8D9894FF)), + }, + }, + artist.Stroke { Pattern: artist.NewUniform(hex(0x8D9894FF)) }) +var pressedSelectedButtonPattern = artist.NewMultiBordered ( + artist.Stroke { Weight: 1, Pattern: strokePattern }, + artist.Stroke { + Weight: 1, + Pattern: artist.Beveled { + artist.NewUniform(hex(0x4B5B59FF)), + artist.NewUniform(hex(0x8D9894FF)), + }, + }, + artist.Stroke { Pattern: artist.NewUniform(hex(0x8D9894FF)) }) +var disabledButtonPattern = artist.NewMultiBordered ( + artist.Stroke { Weight: 1, Pattern: weakForegroundPattern }, + artist.Stroke { Pattern: backgroundPattern }) + +var inputPattern = artist.NewMultiBordered ( + artist.Stroke { Weight: 1, Pattern: strokePattern }, + artist.Stroke { + Weight: 1, + Pattern: artist.Beveled { + artist.NewUniform(hex(0x89925AFF)), + artist.NewUniform(hex(0xD2CB9AFF)), + }, + }, + artist.Stroke { Pattern: artist.NewUniform(hex(0xD2CB9AFF)) }) +var selectedInputPattern = artist.NewMultiBordered ( + artist.Stroke { Weight: 1, Pattern: strokePattern }, + artist.Stroke { Weight: 1, Pattern: accentPattern }, + artist.Stroke { Pattern: artist.NewUniform(hex(0xD2CB9AFF)) }) +var disabledInputPattern = artist.NewMultiBordered ( + artist.Stroke { Weight: 1, Pattern: weakForegroundPattern }, + artist.Stroke { Pattern: backgroundPattern }) + +var listPattern = artist.NewMultiBordered ( + artist.Stroke { Weight: 1, Pattern: strokePattern }, + artist.Stroke { + Weight: 1, + Pattern: artist.Beveled { + uhex(0x383C3AFF), + uhex(0x999C99FF), + }, + }, + artist.Stroke { Pattern: uhex(0x999C99FF) }) + +var focusedListPattern = artist.NewMultiBordered ( + artist.Stroke { Weight: 1, Pattern: strokePattern }, + artist.Stroke { Weight: 1, Pattern: accentPattern }, + artist.Stroke { Pattern: uhex(0x999C99FF) }) + +var listEntryPattern = artist.Padded { + Stroke: uhex(0x383C3AFF), + Fill: uhex(0x999C99FF), + Sides: []int { 0, 0, 0, 1 }, +} + +var onListEntryPattern = artist.Padded { + Stroke: uhex(0x383C3AFF), + Fill: uhex(0x6e8079FF), + Sides: []int { 0, 0, 0, 1 }, +} + +var focusedListEntryPattern = artist.Padded { + Stroke: accentPattern, + Fill: uhex(0x999C99FF), + Sides: []int { 0, 1, 0, 1 }, +} + +var focusedOnListEntryPattern = artist.Padded { + Stroke: accentPattern, + Fill: uhex(0x6e8079FF), + Sides: []int { 0, 1, 0, 1 }, +} + +var scrollGutterPattern = artist.NewMultiBordered ( + artist.Stroke { Weight: 1, Pattern: strokePattern }, + artist.Stroke { + Weight: 1, + Pattern: artist.Beveled { + artist.NewUniform(hex(0x3b534eFF)), + artist.NewUniform(hex(0x6e8079FF)), + }, + }, + artist.Stroke { Pattern: artist.NewUniform(hex(0x6e8079FF)) }) +var disabledScrollGutterPattern = artist.NewMultiBordered ( + artist.Stroke { Weight: 1, Pattern: weakForegroundPattern }, + artist.Stroke { Pattern: backgroundPattern }) +var scrollBarPattern = artist.NewMultiBordered ( + artist.Stroke { Weight: 1, Pattern: strokePattern }, + artist.Stroke { + Weight: 1, + Pattern: artist.Beveled { + artist.NewUniform(hex(0xCCD5D2FF)), + artist.NewUniform(hex(0x4B5B59FF)), + }, + }, + artist.Stroke { Pattern: artist.NewUniform(hex(0x8D9894FF)) }) +var selectedScrollBarPattern = artist.NewMultiBordered ( + artist.Stroke { Weight: 1, Pattern: strokePattern }, + artist.Stroke { + Weight: 1, + Pattern: artist.Beveled { + artist.NewUniform(hex(0xCCD5D2FF)), + artist.NewUniform(hex(0x4B5B59FF)), + }, + }, + artist.Stroke { Weight: 1, Pattern: accentPattern }, + artist.Stroke { Pattern: artist.NewUniform(hex(0x8D9894FF)) }) +var pressedScrollBarPattern = artist.NewMultiBordered ( + artist.Stroke { Weight: 1, Pattern: strokePattern }, + artist.Stroke { + Weight: 1, + Pattern: artist.Beveled { + artist.NewUniform(hex(0xCCD5D2FF)), + artist.NewUniform(hex(0x4B5B59FF)), + }, + }, + artist.Stroke { Weight: 1, Pattern: artist.NewUniform(hex(0x8D9894FF)) }, + artist.Stroke { Pattern: artist.NewUniform(hex(0x7f8c89FF)) }) +var pressedSelectedScrollBarPattern = artist.NewMultiBordered ( + artist.Stroke { Weight: 1, Pattern: strokePattern }, + artist.Stroke { + Weight: 1, + Pattern: artist.Beveled { + artist.NewUniform(hex(0xCCD5D2FF)), + artist.NewUniform(hex(0x4B5B59FF)), + }, + }, + artist.Stroke { Weight: 1, Pattern: accentPattern }, + artist.Stroke { Pattern: artist.NewUniform(hex(0x7f8c89FF)) }) +var disabledScrollBarPattern = artist.NewMultiBordered ( + artist.Stroke { Weight: 1, Pattern: weakForegroundPattern }, + artist.Stroke { Pattern: backgroundPattern }) diff --git a/theme/input.go b/theme/input.go deleted file mode 100644 index e201beb..0000000 --- a/theme/input.go +++ /dev/null @@ -1,21 +0,0 @@ -package theme - -import "git.tebibyte.media/sashakoshka/tomo/artist" - -var inputPattern = artist.NewMultiBordered ( - artist.Stroke { Weight: 1, Pattern: strokePattern }, - artist.Stroke { - Weight: 1, - Pattern: artist.Beveled { - artist.NewUniform(hex(0x89925AFF)), - artist.NewUniform(hex(0xD2CB9AFF)), - }, - }, - artist.Stroke { Pattern: artist.NewUniform(hex(0xD2CB9AFF)) }) -var selectedInputPattern = artist.NewMultiBordered ( - artist.Stroke { Weight: 1, Pattern: strokePattern }, - artist.Stroke { Weight: 1, Pattern: accentPattern }, - artist.Stroke { Pattern: artist.NewUniform(hex(0xD2CB9AFF)) }) -var disabledInputPattern = artist.NewMultiBordered ( - artist.Stroke { Weight: 1, Pattern: weakForegroundPattern }, - artist.Stroke { Pattern: backgroundPattern }) diff --git a/theme/inset.go b/theme/inset.go new file mode 100644 index 0000000..1cace51 --- /dev/null +++ b/theme/inset.go @@ -0,0 +1,42 @@ +package theme + +import "image" + +// Inset represents an inset amount for all four sides of a rectangle. The top +// side is at index zero, the right at index one, the bottom at index two, and +// the left at index three. These values may be negative. +type Inset [4]int + +// Apply returns the given rectangle, shrunk on all four sides by the given +// inset. If a measurment of the inset is negative, that side will instead be +// expanded outward. If the rectangle's dimensions cannot be reduced any +// further, an empty rectangle near its center will be returned. +func (inset Inset) Apply (bigger image.Rectangle) (smaller image.Rectangle) { + smaller = bigger + if smaller.Dx() < inset[3] + inset[1] { + smaller.Min.X = (smaller.Min.X + smaller.Max.X) / 2 + smaller.Max.X = smaller.Min.X + } else { + smaller.Min.X += inset[3] + smaller.Max.X -= inset[1] + } + + if smaller.Dy() < inset[0] + inset[2] { + smaller.Min.Y = (smaller.Min.Y + smaller.Max.Y) / 2 + smaller.Max.Y = smaller.Min.Y + } else { + smaller.Min.Y += inset[0] + smaller.Max.Y -= inset[2] + } + return +} + +// Inverse returns a negated version of the inset. +func (inset Inset) Inverse () (prime Inset) { + return Inset { + inset[0] * -1, + inset[1] * -1, + inset[2] * -1, + inset[3] * -1, + } +} diff --git a/theme/list.go b/theme/list.go deleted file mode 100644 index faf12e3..0000000 --- a/theme/list.go +++ /dev/null @@ -1,43 +0,0 @@ -package theme - -import "git.tebibyte.media/sashakoshka/tomo/artist" - -var listPattern = artist.NewMultiBordered ( - artist.Stroke { Weight: 1, Pattern: strokePattern }, - artist.Stroke { - Weight: 1, - Pattern: artist.Beveled { - uhex(0x383C3AFF), - uhex(0x999C99FF), - }, - }, - artist.Stroke { Pattern: uhex(0x999C99FF) }) - -var focusedListPattern = artist.NewMultiBordered ( - artist.Stroke { Weight: 1, Pattern: strokePattern }, - artist.Stroke { Weight: 1, Pattern: accentPattern }, - artist.Stroke { Pattern: uhex(0x999C99FF) }) - -var listEntryPattern = artist.Padded { - Stroke: uhex(0x383C3AFF), - Fill: uhex(0x999C99FF), - Sides: []int { 0, 0, 0, 1 }, -} - -var onListEntryPattern = artist.Padded { - Stroke: uhex(0x383C3AFF), - Fill: uhex(0x6e8079FF), - Sides: []int { 0, 0, 0, 1 }, -} - -var focusedListEntryPattern = artist.Padded { - Stroke: accentPattern, - Fill: uhex(0x999C99FF), - Sides: []int { 0, 1, 0, 1 }, -} - -var focusedOnListEntryPattern = artist.Padded { - Stroke: accentPattern, - Fill: uhex(0x6e8079FF), - Sides: []int { 0, 1, 0, 1 }, -} diff --git a/theme/parse.go b/theme/parse.go new file mode 100644 index 0000000..db96c86 --- /dev/null +++ b/theme/parse.go @@ -0,0 +1,9 @@ +package theme + +import "io" + +// Parse parses a theme file and returns it as a Theme. +func Parse (io.Reader) (Theme) { + // TODO + return Default { } +} diff --git a/theme/patterns.go b/theme/patterns.go deleted file mode 100644 index aab2b18..0000000 --- a/theme/patterns.go +++ /dev/null @@ -1,239 +0,0 @@ -package theme - -import "image" -import "git.tebibyte.media/sashakoshka/tomo/artist" - -// Case sepecifies what kind of element is using a pattern. It contains a -// namespace parameter and an element parameter. The element parameter does not -// necissarily need to match an element name, but if it can, it should. Both -// parameters should be written in camel case. Themes can change their styling -// based on this parameter for fine-grained control over the look and feel of -// specific elements. -type Case struct { Namespace, Element string } - -// C can be used as shorthand to generate a case struct as used in PatternState. -func C (namespace, element string) (c Case) { - return Case { - Namespace: namespace, - Element: element, - } -} - -// PatternState lists parameters which can change the appearance of some -// patterns. For example, passing a PatternState with Selected set to true may -// result in a pattern that has a colored border within it. -type PatternState struct { - Case - - // On should be set to true if the element that is using this pattern is - // in some sort of "on" state, such as if a checkbox is checked or a - // switch is toggled on. This is only necessary if the element in - // question is capable of being toggled. - On bool - - // Focused should be set to true if the element that is using this - // pattern is currently focused. - Focused bool - - // Pressed should be set to true if the element that is using this - // pattern is being pressed down by the mouse. This is only necessary if - // the element in question processes mouse button events. - Pressed bool - - // Disabled should be set to true if the element that is using this - // pattern is locked and cannot be interacted with. Disabled variations - // of patterns are typically flattened and greyed-out. - Disabled bool - - // Invalid should be set to true if th element that is using this - // pattern wants to warn the user of an invalid interaction or data - // entry. Invalid variations typically have some sort of reddish tint - // or outline. - Invalid bool -} - -// Inset represents an inset amount for all four sides of a rectangle. The top -// side is at index zero, the right at index one, the bottom at index two, and -// the left at index three. These values may be negative. -type Inset [4]int - -// Apply returns the given rectangle, shrunk on all four sides by the given -// inset. If a measurment of the inset is negative, that side will instead be -// expanded outward. If the rectangle's dimensions cannot be reduced any -// further, an empty rectangle near its center will be returned. -func (inset Inset) Apply (bigger image.Rectangle) (smaller image.Rectangle) { - smaller = bigger - if smaller.Dx() < inset[3] + inset[1] { - smaller.Min.X = (smaller.Min.X + smaller.Max.X) / 2 - smaller.Max.X = smaller.Min.X - } else { - smaller.Min.X += inset[3] - smaller.Max.X -= inset[1] - } - - if smaller.Dy() < inset[0] + inset[2] { - smaller.Min.Y = (smaller.Min.Y + smaller.Max.Y) / 2 - smaller.Max.Y = smaller.Min.Y - } else { - smaller.Min.Y += inset[0] - smaller.Max.Y -= inset[2] - } - return -} - -// Inverse returns a negated version of the inset. -func (inset Inset) Inverse () (prime Inset) { - return Inset { - inset[0] * -1, - inset[1] * -1, - inset[2] * -1, - inset[3] * -1, - } -} - -// AccentPattern returns the accent pattern, which is usually just a solid -// color. -func AccentPattern (state PatternState) (pattern artist.Pattern, inset Inset) { - return accentPattern, Inset { } -} - -// BackgroundPattern returns the main background pattern. -func BackgroundPattern (state PatternState) (pattern artist.Pattern, inset Inset) { - return backgroundPattern, Inset { } -} - -// DeadPattern returns a pattern that can be used to mark an area or gap that -// serves no purpose, but still needs aesthetic structure. -func DeadPattern (state PatternState) (pattern artist.Pattern, inset Inset) { - return deadPattern, Inset { } -} - -// ForegroundPattern returns the color text should be. -func ForegroundPattern (state PatternState) (pattern artist.Pattern, inset Inset) { - if state.Disabled { - return weakForegroundPattern, Inset { } - } else { - return foregroundPattern, Inset { } - } -} - -// InputPattern returns a background pattern for any input field that can be -// edited by typing with the keyboard. -func InputPattern (state PatternState) (pattern artist.Pattern, inset Inset) { - if state.Disabled { - return disabledInputPattern, Inset { 1, 1, 1, 1 } - } else { - if state.Focused { - return selectedInputPattern, Inset { 1, 1, 1, 1 } - } else { - return inputPattern, Inset { 1, 1, 1, 1 } - } - } -} - -// ListPattern returns a background pattern for a list of things. -func ListPattern (state PatternState) (pattern artist.Pattern, inset Inset) { - if state.Focused { - pattern = focusedListPattern - inset = Inset { 2, 1, 2, 1 } - } else { - pattern = listPattern - inset = Inset { 2, 1, 1, 1 } - } - return -} - -// ItemPattern returns a background pattern for a list item. -func ItemPattern (state PatternState) (pattern artist.Pattern, inset Inset) { - if state.Focused { - if state.On { - pattern = focusedOnListEntryPattern - } else { - pattern = focusedListEntryPattern - } - } else { - if state.On { - pattern = onListEntryPattern - } else { - pattern = listEntryPattern - } - } - inset = Inset { 4, 6, 4, 6 } - return -} - -// ButtonPattern returns a pattern to be displayed on buttons. -func ButtonPattern (state PatternState) (pattern artist.Pattern, inset Inset) { - if state.Disabled { - return disabledButtonPattern, Inset { 1, 1, 1, 1 } - } else { - if state.Pressed { - if state.Focused { - return pressedSelectedButtonPattern, Inset { - 2, 0, 0, 2 } - } else { - return pressedButtonPattern, Inset { 2, 0, 0, 2 } - } - } else { - if state.Focused { - return selectedButtonPattern, Inset { 1, 1, 1, 1 } - } else { - return buttonPattern, Inset { 1, 1, 1, 1 } - } - } - } -} - -// GutterPattern returns a pattern to be used to mark a track along which -// something slides. -func GutterPattern (state PatternState) (pattern artist.Pattern, inset Inset) { - if state.Disabled { - return disabledScrollGutterPattern, Inset { 0, 0, 0, 0 } - } else { - return scrollGutterPattern, Inset { 0, 0, 0, 0 } - } -} - -// HandlePattern returns a pattern to be displayed on a grab handle that slides -// along a gutter. -func HandlePattern (state PatternState) (pattern artist.Pattern, inset Inset) { - if state.Disabled { - return disabledScrollBarPattern, Inset { 1, 1, 1, 1 } - } else { - if state.Focused { - if state.Pressed { - return pressedSelectedScrollBarPattern, Inset { 1, 1, 1, 1 } - } else { - return selectedScrollBarPattern, Inset { 1, 1, 1, 1 } - } - } else { - if state.Pressed { - return pressedScrollBarPattern, Inset { 1, 1, 1, 1 } - } else { - return scrollBarPattern, Inset { 1, 1, 1, 1 } - } - } - } -} - -// SunkenPattern returns a general purpose pattern that is sunken/engraved into -// the background. -func SunkenPattern (state PatternState) (pattern artist.Pattern, inset Inset) { - return sunkenPattern, Inset { 1, 1, 1, 1 } -} - -// RaisedPattern returns a general purpose pattern that is raised up out of the -// background. -func RaisedPattern (state PatternState) (pattern artist.Pattern, inset Inset) { - if state.Focused { - return selectedRaisedPattern, Inset { 1, 1, 1, 1 } - } else { - return raisedPattern, Inset { 1, 1, 1, 1 } - } -} - -// PinboardPattern returns a textured backdrop pattern. Anything drawn within it -// should have its own background pattern. -func PinboardPattern (state PatternState) (pattern artist.Pattern, inset Inset) { - return texturedSunkenPattern, Inset { 1, 1, 1, 1 } -} diff --git a/theme/scroll.go b/theme/scroll.go deleted file mode 100644 index a12cdd3..0000000 --- a/theme/scroll.go +++ /dev/null @@ -1,63 +0,0 @@ -package theme - -import "git.tebibyte.media/sashakoshka/tomo/artist" - -var scrollGutterPattern = artist.NewMultiBordered ( - artist.Stroke { Weight: 1, Pattern: strokePattern }, - artist.Stroke { - Weight: 1, - Pattern: artist.Beveled { - artist.NewUniform(hex(0x3b534eFF)), - artist.NewUniform(hex(0x6e8079FF)), - }, - }, - artist.Stroke { Pattern: artist.NewUniform(hex(0x6e8079FF)) }) -var disabledScrollGutterPattern = artist.NewMultiBordered ( - artist.Stroke { Weight: 1, Pattern: weakForegroundPattern }, - artist.Stroke { Pattern: backgroundPattern }) -var scrollBarPattern = artist.NewMultiBordered ( - artist.Stroke { Weight: 1, Pattern: strokePattern }, - artist.Stroke { - Weight: 1, - Pattern: artist.Beveled { - artist.NewUniform(hex(0xCCD5D2FF)), - artist.NewUniform(hex(0x4B5B59FF)), - }, - }, - artist.Stroke { Pattern: artist.NewUniform(hex(0x8D9894FF)) }) -var selectedScrollBarPattern = artist.NewMultiBordered ( - artist.Stroke { Weight: 1, Pattern: strokePattern }, - artist.Stroke { - Weight: 1, - Pattern: artist.Beveled { - artist.NewUniform(hex(0xCCD5D2FF)), - artist.NewUniform(hex(0x4B5B59FF)), - }, - }, - artist.Stroke { Weight: 1, Pattern: accentPattern }, - artist.Stroke { Pattern: artist.NewUniform(hex(0x8D9894FF)) }) -var pressedScrollBarPattern = artist.NewMultiBordered ( - artist.Stroke { Weight: 1, Pattern: strokePattern }, - artist.Stroke { - Weight: 1, - Pattern: artist.Beveled { - artist.NewUniform(hex(0xCCD5D2FF)), - artist.NewUniform(hex(0x4B5B59FF)), - }, - }, - artist.Stroke { Weight: 1, Pattern: artist.NewUniform(hex(0x8D9894FF)) }, - artist.Stroke { Pattern: artist.NewUniform(hex(0x7f8c89FF)) }) -var pressedSelectedScrollBarPattern = artist.NewMultiBordered ( - artist.Stroke { Weight: 1, Pattern: strokePattern }, - artist.Stroke { - Weight: 1, - Pattern: artist.Beveled { - artist.NewUniform(hex(0xCCD5D2FF)), - artist.NewUniform(hex(0x4B5B59FF)), - }, - }, - artist.Stroke { Weight: 1, Pattern: accentPattern }, - artist.Stroke { Pattern: artist.NewUniform(hex(0x7f8c89FF)) }) -var disabledScrollBarPattern = artist.NewMultiBordered ( - artist.Stroke { Weight: 1, Pattern: weakForegroundPattern }, - artist.Stroke { Pattern: backgroundPattern }) diff --git a/theme/state.go b/theme/state.go new file mode 100644 index 0000000..51e0ab2 --- /dev/null +++ b/theme/state.go @@ -0,0 +1,48 @@ +package theme + +// Case sepecifies what kind of element is using a pattern. It contains a +// namespace parameter and an element parameter. The element parameter does not +// necissarily need to match an element name, but if it can, it should. Both +// parameters should be written in camel case. Themes can change their styling +// based on this parameter for fine-grained control over the look and feel of +// specific elements. +type Case struct { Namespace, Element string } + +// C can be used as shorthand to generate a case struct as used in PatternState. +func C (namespace, element string) (c Case) { + return Case { + Namespace: namespace, + Element: element, + } +} + +// PatternState lists parameters which can change the appearance of some +// patterns. For example, passing a PatternState with Selected set to true may +// result in a pattern that has a colored border within it. +type PatternState struct { + // On should be set to true if the element that is using this pattern is + // in some sort of "on" state, such as if a checkbox is checked or a + // switch is toggled on. This is only necessary if the element in + // question is capable of being toggled. + On bool + + // Focused should be set to true if the element that is using this + // pattern is currently focused. + Focused bool + + // Pressed should be set to true if the element that is using this + // pattern is being pressed down by the mouse. This is only necessary if + // the element in question processes mouse button events. + Pressed bool + + // Disabled should be set to true if the element that is using this + // pattern is locked and cannot be interacted with. Disabled variations + // of patterns are typically flattened and greyed-out. + Disabled bool + + // Invalid should be set to true if th element that is using this + // pattern wants to warn the user of an invalid interaction or data + // entry. Invalid variations typically have some sort of reddish tint + // or outline. + Invalid bool +} diff --git a/theme/theme.go b/theme/theme.go index 3f59fe6..bc0f370 100644 --- a/theme/theme.go +++ b/theme/theme.go @@ -1,113 +1,100 @@ package theme -import "image/color" +import "image" import "golang.org/x/image/font" import "git.tebibyte.media/sashakoshka/tomo/artist" -import "git.tebibyte.media/sashakoshka/tomo/defaultfont" -// none of these colors are final! TODO: generate these values from a theme -// file at startup. +// FontStyle specifies stylistic alterations to a font face. +type FontStyle int; const ( + FontStyleRegular FontStyle = 0 + FontStyleBold FontStyle = 1 + FontStyleItalic FontStyle = 2 + FontStyleBoldItalic FontStyle = 1 | 2 +) -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 uhex (color uint32) (pattern artist.Pattern) { - return artist.NewUniform(hex(color)) -} - -var accentPattern = artist.NewUniform(hex(0x408090FF)) -var backgroundPattern = artist.NewUniform(color.Gray16 { 0xAAAA }) -var foregroundPattern = artist.NewUniform(color.Gray16 { 0x0000 }) -var weakForegroundPattern = artist.NewUniform(color.Gray16 { 0x4444 }) -var strokePattern = artist.NewUniform(color.Gray16 { 0x0000 }) - -var sunkenPattern = artist.NewMultiBordered ( - artist.Stroke { Weight: 1, Pattern: strokePattern }, - artist.Stroke { - Weight: 1, - Pattern: artist.Beveled { - artist.NewUniform(hex(0x3b534eFF)), - artist.NewUniform(hex(0x97a09cFF)), - }, - }, - artist.Stroke { Pattern: artist.NewUniform(hex(0x97a09cFF)) }) - -var texturedSunkenPattern = artist.NewMultiBordered ( - artist.Stroke { Weight: 1, Pattern: strokePattern }, - artist.Stroke { - Weight: 1, - Pattern: artist.Beveled { - artist.NewUniform(hex(0x3b534eFF)), - artist.NewUniform(hex(0x97a09cFF)), - }, - }, - // artist.Stroke { Pattern: artist.Striped { - // First: artist.Stroke { - // Weight: 2, - // Pattern: artist.NewUniform(hex(0x97a09cFF)), - // }, - // Second: artist.Stroke { - // Weight: 1, - // Pattern: artist.NewUniform(hex(0x6e8079FF)), - // }, - // }}) - - artist.Stroke { Pattern: artist.Noisy { - Low: artist.NewUniform(hex(0x97a09cFF)), - High: artist.NewUniform(hex(0x6e8079FF)), - }}) - -var raisedPattern = artist.NewMultiBordered ( - artist.Stroke { Weight: 1, Pattern: strokePattern }, - artist.Stroke { - Weight: 1, - Pattern: artist.Beveled { - artist.NewUniform(hex(0xDBDBDBFF)), - artist.NewUniform(hex(0x383C3AFF)), - }, - }, - artist.Stroke { Pattern: artist.NewUniform(hex(0xAAAAAAFF)) }) - -var selectedRaisedPattern = artist.NewMultiBordered ( - artist.Stroke { Weight: 1, Pattern: strokePattern }, - artist.Stroke { - Weight: 1, - Pattern: artist.Beveled { - artist.NewUniform(hex(0xDBDBDBFF)), - artist.NewUniform(hex(0x383C3AFF)), - }, - }, - artist.Stroke { Weight: 1, Pattern: accentPattern }, - artist.Stroke { Pattern: artist.NewUniform(hex(0xAAAAAAFF)) }) - -var deadPattern = artist.NewMultiBordered ( - artist.Stroke { Weight: 1, Pattern: strokePattern }, - artist.Stroke { Pattern: artist.NewUniform(hex(0x97a09cFF)) }) - -// TODO: load fonts from an actual source instead of using defaultfont - -// FontFaceRegular returns the font face to be used for normal text. -func FontFaceRegular () font.Face { - return defaultfont.FaceRegular -} - -// FontFaceBold returns the font face to be used for bolded text. -func FontFaceBold () font.Face { - return defaultfont.FaceBold -} - -// FontFaceItalic returns the font face to be used for italicized text. -func FontFaceItalic () font.Face { - return defaultfont.FaceItalic -} - -// FontFaceBoldItalic returns the font face to be used for text that is both -// bolded and italicized. -func FontFaceBoldItalic () font.Face { - return defaultfont.FaceBoldItalic +// FontSize specifies the general size of a font face in a semantic way. +type FontSize int; const ( + // FontSizeNormal is the default font size that should be used for most + // things. + FontSizeNormal FontSize = iota + + // FontSizeLarge is a larger font size suitable for things like section + // headings. + FontSizeLarge + + // FontSizeHuge is a very large font size suitable for things like + // titles, wizard step names, digital clocks, etc. + FontSizeHuge + + // FontSizeSmall is a smaller font size. Try not to use this unless it + // makes a lot of sense to do so, because it can negatively impact + // accessibility. It is useful for things like copyright notices at the + // bottom of some window that the average user doesn't actually care + // about. + FontSizeSmall +) + +// Pattern lists a number of cannonical pattern types, each with its own ID. +// This allows custom elements to follow themes, even those that do not +// explicitly support them. +type Pattern int; const ( + // PatternAccent is the accent color of the theme. It is safe to assume + // that this is, by default, a solid color. + PatternAccent Pattern = iota + + // PatternBackground is the background color of the theme. It is safe to + // assume that this is, by default, a solid color. + PatternBackground + + // PatternForeground is the foreground text color of the theme. It is + // safe to assume that this is, by default, a solid color. + PatternForeground + + // PatternDead is a pattern that is displayed on a "dead area" where no + // controls exist, but there still must be some indication of visual + // structure (such as in the corner between two scroll bars). + PatternDead + + // PatternRaised is a generic raised pattern. + PatternRaised + + // PatternSunken is a generic sunken pattern. + PatternSunken + + // PatternPinboard is similar to PatternSunken, but it is textured. + PatternPinboard + + // PatternButton is a button pattern. + PatternButton + + // PatternInput is a pattern for input fields, editable text areas, etc. + PatternInput + + // PatternGutter is a track for things to slide on. + PatternGutter + + // PatternHandle is a handle that slides along a gutter. + PatternHandle +) + +// Theme represents a visual style configuration, +type Theme interface { + // FontFace returns the proper font for a given style, size, and case. + FontFace (FontStyle, FontSize, Case) font.Face + + // Icon returns an appropriate icon given an icon name and case. + Icon (string, Case) artist.Pattern + + // Pattern returns an appropriate pattern given a pattern name, case, + // and state. + Pattern (Pattern, Case, PatternState) artist.Pattern + + // Inset returns the area on all sides of a given pattern that is not + // meant to be drawn on. + Inset (Pattern, Case) Inset + + // Sink returns a vector that should be added to an element's inner + // content when it is pressed down (if applicable) to simulate a 3D + // sinking effect. + Sink (Pattern, Case) image.Point } diff --git a/theme/util.go b/theme/util.go new file mode 100644 index 0000000..2e9723e --- /dev/null +++ b/theme/util.go @@ -0,0 +1,16 @@ +package theme + +import "image/color" +import "git.tebibyte.media/sashakoshka/tomo/artist" + +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 uhex (color uint32) (pattern artist.Pattern) { + return artist.NewUniform(hex(color)) +}