diff --git a/artist/chisel.go b/artist/chisel.go index 873046b..b172ddb 100644 --- a/artist/chisel.go +++ b/artist/chisel.go @@ -1,116 +1,30 @@ package artist -import "image" import "image/color" -import "git.tebibyte.media/sashakoshka/tomo" -// ShadingProfile contains shading information that can be used to draw chiseled -// objects. -type ShadingProfile struct { - Highlight Pattern - Shadow Pattern - Stroke Pattern - Fill Pattern - StrokeWeight int - ShadingWeight int +// Chiseled is a pattern that has a highlight section and a shadow section. +type Chiseled struct { + Highlight Pattern + Shadow Pattern } -// Engraved reverses the shadown and highlight colors of the ShadingProfile to -// produce a new ShadingProfile with an engraved appearance. -func (profile ShadingProfile) Engraved () (reversed ShadingProfile) { - reversed = profile - reversed.Highlight = profile.Shadow - reversed.Shadow = profile.Highlight - return -} - -// ChiseledRectangle draws a rectangle with a chiseled/embossed appearance, -// according to the ShadingProfile passed to it. -func ChiseledRectangle ( - destination tomo.Canvas, - profile ShadingProfile, - bounds image.Rectangle, -) ( - updatedRegion image.Rectangle, -) { - // FIXME: this breaks when the bounds are smaller than the border or - // shading weight - - stroke := profile.Stroke - highlight := profile.Highlight - shadow := profile.Shadow - fill := profile.Fill - strokeWeight := profile.StrokeWeight - shadingWeight := profile.ShadingWeight - - data, stride := destination.Buffer() - bounds = bounds.Canon() - updatedRegion = bounds - - strokeWeightVector := image.Point { strokeWeight, strokeWeight } - shadingWeightVector := image.Point { shadingWeight, shadingWeight } - - shadingBounds := bounds - shadingBounds.Min = shadingBounds.Min.Add(strokeWeightVector) - shadingBounds.Max = shadingBounds.Max.Sub(strokeWeightVector) - shadingBounds = shadingBounds.Canon() - - fillBounds := shadingBounds - fillBounds.Min = fillBounds.Min.Add(shadingWeightVector) - fillBounds.Max = fillBounds.Max.Sub(shadingWeightVector) - fillBounds = fillBounds.Canon() - - width := float64(bounds.Dx()) - height := float64(bounds.Dy()) - - yy := 0 - for y := bounds.Min.Y; y < bounds.Max.Y; y ++ { - xx := 0 - for x := bounds.Min.X; x < bounds.Max.X; x ++ { - var pixel color.RGBA - point := image.Point { x, y } - switch { - case point.In(fillBounds): - pixel = fill.AtWhen ( - xx - strokeWeight - shadingWeight, - yy - strokeWeight - shadingWeight, - fillBounds.Dx(), fillBounds.Dy()) - - case point.In(shadingBounds): - var highlighted bool - // FIXME: this doesn't work quite right, the - // slope of the line is somewhat off. - bottomCorner := - float64(xx) < float64(yy) * - (width / height) - if bottomCorner { - highlighted = - float64(xx) < - height - float64(yy) - } else { - highlighted = - width - float64(xx) > - float64(yy) - } - - shadingSource := shadow - if highlighted { - shadingSource = highlight - } - pixel = shadingSource.AtWhen ( - xx - strokeWeight, - yy - strokeWeight, - shadingBounds.Dx(), - shadingBounds.Dy()) - default: - pixel = stroke.AtWhen ( - xx, yy, bounds.Dx(), bounds.Dy()) - } - data[x + y * stride] = pixel - xx ++ - } - yy ++ +// AtWhen satisfies the Pattern interface. +func (chiseled Chiseled) AtWhen (x, y, width, height int) (c color.RGBA) { + var highlighted bool + // FIXME: this doesn't work quite right, the + // slope of the line is somewhat off. + bottomCorner := + float64(x) < float64(y) * + (float64(width) / float64(height)) + if bottomCorner { + highlighted = float64(x) < float64(height) - float64(y) + } else { + highlighted = float64(width) - float64(x) > float64(y) + } + + if highlighted { + return chiseled.Highlight.AtWhen(x, y, width, height) + } else { + return chiseled.Shadow.AtWhen(x, y, width, height) } - - return } diff --git a/artist/multiborder.go b/artist/multiborder.go index 6119a88..da5f42d 100644 --- a/artist/multiborder.go +++ b/artist/multiborder.go @@ -3,6 +3,7 @@ package artist import "image" import "image/color" +// Border represents a border that can be fed to MultiBorder. type Border struct { Weight int Stroke Pattern @@ -10,16 +11,22 @@ type Border struct { dx, dy int } +// MultiBorder is a pattern that allows multiple borders of different lengths to +// be inset within one another. The final border is treated as a fill color, and +// its weight does not matter. type MultiBorder struct { borders []Border lastWidth, lastHeight int maxBorder int } +// NewMultiBorder creates a new MultiBorder pattern from the given list of +// borders. func NewMultiBorder (borders ...Border) (multi *MultiBorder) { return &MultiBorder { borders: borders } } +// AtWhen satisfies the Pattern interface. func (multi *MultiBorder) AtWhen (x, y, width, height int) (c color.RGBA) { if multi.lastWidth != width || multi.lastHeight != height { multi.recalculate(width, height) diff --git a/elements/basic/button.go b/elements/basic/button.go index fe7d87a..d7e80e7 100644 --- a/elements/basic/button.go +++ b/elements/basic/button.go @@ -150,12 +150,12 @@ func (element *Button) SetText (text string) { func (element *Button) draw () { bounds := element.core.Bounds() - artist.ChiseledRectangle ( + artist.FillRectangle ( element.core, - theme.RaisedProfile ( - element.pressed, + theme.ButtonPattern ( element.enabled, - element.Selected()), + element.Selected(), + element.pressed), bounds) innerBounds := bounds @@ -179,10 +179,6 @@ func (element *Button) draw () { offset = offset.Add(theme.SinkOffsetVector()) } - foreground := theme.ForegroundImage() - if !element.enabled { - foreground = theme.DisabledForegroundImage() - } - + foreground := theme.ForegroundPattern(element.enabled) element.drawer.Draw(element.core, foreground, offset) } diff --git a/elements/basic/container.go b/elements/basic/container.go index 886f98c..d3eb74b 100644 --- a/elements/basic/container.go +++ b/elements/basic/container.go @@ -320,7 +320,7 @@ func (element *Container) draw () { artist.FillRectangle ( element.core, - theme.BackgroundImage(), + theme.BackgroundPattern(), bounds) for _, entry := range element.children { diff --git a/elements/basic/label.go b/elements/basic/label.go index 6304afd..d80f781 100644 --- a/elements/basic/label.go +++ b/elements/basic/label.go @@ -95,12 +95,12 @@ func (element *Label) draw () { artist.FillRectangle ( element.core, - theme.BackgroundImage(), + theme.BackgroundPattern(), bounds) textBounds := element.drawer.LayoutBounds() - foreground := theme.ForegroundImage() + foreground := theme.ForegroundPattern(true) element.drawer.Draw (element.core, foreground, image.Point { X: 0 - textBounds.Min.X, Y: 0 - textBounds.Min.Y, diff --git a/elements/fun/clock.go b/elements/fun/clock.go index 6a97314..4e532bd 100644 --- a/elements/fun/clock.go +++ b/elements/fun/clock.go @@ -44,14 +44,14 @@ func (element *AnalogClock) SetTime (newTime time.Time) { func (element *AnalogClock) draw () { bounds := element.core.Bounds() - artist.ChiseledRectangle ( + artist.FillRectangle ( element.core, - theme.BackgroundProfile(true), + theme.SunkenPattern(), bounds) for hour := 0; hour < 12; hour ++ { element.radialLine ( - theme.ForegroundImage(), + theme.ForegroundPattern(true), 0.8, 0.9, float64(hour) / 6 * math.Pi) } @@ -60,13 +60,13 @@ func (element *AnalogClock) draw () { hour := float64(element.time.Hour()) + minute / 60 element.radialLine ( - theme.ForegroundImage(), + theme.ForegroundPattern(true), 0, 0.5, (hour - 3) / 6 * math.Pi) element.radialLine ( - theme.ForegroundImage(), + theme.ForegroundPattern(true), 0, 0.7, (minute - 15) / 30 * math.Pi) element.radialLine ( - theme.AccentImage(), + theme.AccentPattern(), 0, 0.7, (second - 15) / 30 * math.Pi) } diff --git a/elements/testing/mouse.go b/elements/testing/mouse.go index 08680a5..2c3bfa6 100644 --- a/elements/testing/mouse.go +++ b/elements/testing/mouse.go @@ -35,7 +35,7 @@ func (element *Mouse) Handle (event tomo.Event) { resizeEvent.Height) artist.FillRectangle ( element.core, - theme.AccentImage(), + theme.AccentPattern(), element.Bounds()) artist.StrokeRectangle ( element.core, diff --git a/theme/theme.go b/theme/theme.go index 6ee9921..1305ac3 100644 --- a/theme/theme.go +++ b/theme/theme.go @@ -9,179 +9,93 @@ import "git.tebibyte.media/sashakoshka/tomo/defaultfont" // none of these colors are final! TODO: generate these values from a theme // file at startup. -var foregroundImage = artist.NewUniform(color.Gray16 { 0x0000}) -var disabledForegroundImage = artist.NewUniform(color.Gray16 { 0x5555}) -var accentImage = artist.NewUniform(color.RGBA { 0x40, 0x80, 0x90, 0xFF}) -var highlightImage = artist.NewUniform(color.Gray16 { 0xEEEE }) -var shadowImage = artist.NewUniform(color.Gray16 { 0x3333 }) -var weakShadeImage = artist.NewUniform(color.Gray16 { 0x7777 }) -var strokeImage = artist.NewUniform(color.Gray16 { 0x0000 }) -var weakStrokeImage = artist.NewUniform(color.Gray16 { 0x3333 }) -var insetShadowImage = artist.NewUniform(color.Gray16 { 0x7777 }) - -var backgroundImage = artist.NewUniform(color.Gray16 { 0xAAAA}) -var backgroundProfile = artist.ShadingProfile { - Highlight: highlightImage, - Shadow: shadowImage, - Stroke: strokeImage, - Fill: backgroundImage, - StrokeWeight: 1, - ShadingWeight: 1, -} -var engravedBackgroundProfile = backgroundProfile.Engraved() - -var raisedImage = artist.NewUniform(color.RGBA { 0x8D, 0x98, 0x94, 0xFF}) -var raisedProfile = artist.ShadingProfile { - Highlight: highlightImage, - Shadow: shadowImage, - Stroke: strokeImage, - Fill: raisedImage, - StrokeWeight: 1, - ShadingWeight: 1, -} -var selectedRaisedProfile = artist.ShadingProfile { - Highlight: highlightImage, - Shadow: shadowImage, - Stroke: accentImage, - Fill: raisedImage, - StrokeWeight: 1, - ShadingWeight: 1, -} -var engravedRaisedProfile = artist.ShadingProfile { - Highlight: weakShadeImage, - Shadow: raisedImage, - Stroke: strokeImage, - Fill: raisedImage, - StrokeWeight: 1, - ShadingWeight: 1, -} -var selectedEngravedRaisedProfile = artist.ShadingProfile { - Highlight: insetShadowImage, - Shadow: raisedImage, - Stroke: accentImage, - Fill: raisedImage, - StrokeWeight: 1, - ShadingWeight: 1, -} -var disabledRaisedProfile = artist.ShadingProfile { - Highlight: weakShadeImage, - Shadow: weakShadeImage, - Stroke: weakStrokeImage, - Fill: backgroundImage, - StrokeWeight: 1, - ShadingWeight: 0, +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 } -var inputImage = artist.NewUniform(color.Gray16 { 0xFFFF }) -var inputProfile = artist.ShadingProfile { - Highlight: insetShadowImage, - Shadow: inputImage, - Stroke: strokeImage, - Fill: inputImage, - StrokeWeight: 1, - ShadingWeight: 1, -} -var selectedInputProfile = artist.ShadingProfile { - Highlight: insetShadowImage, - Shadow: inputImage, - Stroke: accentImage, - Fill: inputImage, - StrokeWeight: 1, - ShadingWeight: 1, -} -var disabledInputProfile = artist.ShadingProfile { - Highlight: weakShadeImage, - Shadow: backgroundImage, - Stroke: accentImage, - Fill: backgroundImage, - StrokeWeight: 1, - ShadingWeight: 0, -} +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 }) -// BackgroundProfile returns the shading profile to be used for backgrounds. -func BackgroundProfile (engraved bool) artist.ShadingProfile { - if engraved { - return engravedBackgroundProfile +var buttonPattern = artist.NewMultiBorder ( + artist.Border { Weight: 1, Stroke: strokePattern }, + artist.Border { + Weight: 1, + Stroke: artist.Chiseled { + Highlight: artist.NewUniform(hex(0xCCD5D2FF)), + Shadow: artist.NewUniform(hex(0x4B5B59FF)), + }, + }, + artist.Border { Stroke: artist.NewUniform(hex(0x8D9894FF)) }) +var selectedButtonPattern = artist.NewMultiBorder ( + artist.Border { Weight: 1, Stroke: strokePattern }, + artist.Border { + Weight: 1, + Stroke: artist.Chiseled { + Highlight: artist.NewUniform(hex(0xCCD5D2FF)), + Shadow: artist.NewUniform(hex(0x4B5B59FF)), + }, + }, + artist.Border { Weight: 1, Stroke: accentPattern }, + artist.Border { Stroke: artist.NewUniform(hex(0x8D9894FF)) }) +var pressedButtonPattern = artist.NewMultiBorder ( + artist.Border { Weight: 1, Stroke: strokePattern }, + artist.Border { + Weight: 1, + Stroke: artist.Chiseled { + Highlight: artist.NewUniform(hex(0x4B5B59FF)), + Shadow: artist.NewUniform(hex(0x8D9894FF)), + }, + }, + artist.Border { Stroke: artist.NewUniform(hex(0x8D9894FF)) }) +var disabledButtonPattern = artist.NewMultiBorder ( + artist.Border { Weight: 1, Stroke: weakForegroundPattern }, + artist.Border { Stroke: backgroundPattern }) + +var sunkenPattern = artist.NewMultiBorder ( + artist.Border { Weight: 1, Stroke: strokePattern }, + artist.Border { + Weight: 1, + Stroke: artist.Chiseled { + Highlight: artist.NewUniform(hex(0x373C3AFF)), + Shadow: artist.NewUniform(hex(0xDBDBDBFF)), + }, + }, + artist.Border { Stroke: backgroundPattern }) + +func AccentPattern () (artist.Pattern) { return accentPattern } +func BackgroundPattern () (artist.Pattern) { return backgroundPattern } +func SunkenPattern () (artist.Pattern) { return sunkenPattern} +func ForegroundPattern (enabled bool) (artist.Pattern) { + if enabled { + return foregroundPattern } else { - return backgroundProfile + return weakForegroundPattern } } - -// RaisedProfile returns the shading profile to be used for raised objects such -// as buttons. -func RaisedProfile ( - engraved bool, - enabled bool, - selected bool, -) ( - artist.ShadingProfile, -) { +func ButtonPattern (enabled, selected, pressed bool) (artist.Pattern) { if enabled { - if engraved { - if selected { - return selectedEngravedRaisedProfile - } else { - return engravedRaisedProfile - } + if pressed { + return pressedButtonPattern } else { if selected { - return selectedRaisedProfile + return selectedButtonPattern } else { - return raisedProfile + return buttonPattern } } } else { - return disabledRaisedProfile + return disabledButtonPattern } } -// InputProfile returns the shading profile to be used for input fields. -func InputProfile (enabled bool, selected bool) artist.ShadingProfile { - if enabled { - if selected { - return selectedInputProfile - } else { - return inputProfile - } - } else { - return disabledInputProfile - } -} - -// BackgroundImage returns the texture/color used for the fill of -// BackgroundProfile. -func BackgroundImage () artist.Pattern { - return backgroundImage -} - -// RaisedImage returns the texture/color used for the fill of RaisedProfile. -func RaisedImage () artist.Pattern { - return raisedImage -} - -// InputImage returns the texture/color used for the fill of InputProfile. -func InputImage () artist.Pattern { - return inputImage -} - -// ForegroundImage returns the texture/color text and monochromatic icons should -// be drawn with. -func ForegroundImage () artist.Pattern { - return foregroundImage -} - -// DisabledForegroundImage returns the texture/color text and monochromatic -// icons should be drawn with if they are disabled. -func DisabledForegroundImage () artist.Pattern { - return disabledForegroundImage -} - -// AccentImage returns the accent texture/color. -func AccentImage () artist.Pattern { - return accentImage -} - -// TODO: load fonts from an actual source instead of using basicfont +// 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 {