raw-buffer-api #1
130
artist/chisel.go
130
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
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -320,7 +320,7 @@ func (element *Container) draw () {
|
||||
|
||||
artist.FillRectangle (
|
||||
element.core,
|
||||
theme.BackgroundImage(),
|
||||
theme.BackgroundPattern(),
|
||||
bounds)
|
||||
|
||||
for _, entry := range element.children {
|
||||
|
@ -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,
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
|
228
theme/theme.go
228
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 {
|
||||
|
Reference in New Issue
Block a user