Replaced the chiseled box with the chiseled pattern

This commit is contained in:
Sasha Koshka 2023-01-14 21:01:00 -05:00
parent 9540812a04
commit e83dde2d21
8 changed files with 115 additions and 284 deletions

View File

@ -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
}

View File

@ -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)

View File

@ -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)
}

View File

@ -320,7 +320,7 @@ func (element *Container) draw () {
artist.FillRectangle (
element.core,
theme.BackgroundImage(),
theme.BackgroundPattern(),
bounds)
for _, entry := range element.children {

View File

@ -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,

View File

@ -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)
}

View File

@ -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,

View File

@ -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 {