Replaced tomo.Image with tomo.Canvas and tomo.Pattern
This is the first step in transitioning the API over to the new design. The new tomo.Canvas interface gives drawing functions direct access to data buffers and eliminates overhead associated with calling functions for every pixel. The entire artist package will be remade around this.
This commit is contained in:
parent
babc4a4af8
commit
34bf3038ac
@ -1,2 +1,52 @@
|
|||||||
package artist
|
package artist
|
||||||
|
|
||||||
|
import "image"
|
||||||
|
import "image/color"
|
||||||
|
|
||||||
|
// Pattern is capable of generating a pattern pixel by pixel.
|
||||||
|
type Pattern interface {
|
||||||
|
// AtWhen returns the color of the pixel located at (x, y) relative to
|
||||||
|
// the origin point of the pattern (0, 0), when the pattern has the
|
||||||
|
// specified width and height. Patterns may ignore the width and height
|
||||||
|
// parameters, but it may be useful for some patterns such as gradients.
|
||||||
|
AtWhen (x, y, width, height int) (color.RGBA)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Texture is a struct that allows an image to be converted into a tiling
|
||||||
|
// texture pattern.
|
||||||
|
type Texture struct {
|
||||||
|
data []color.RGBA
|
||||||
|
width, height int
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewTexture converts an image into a texture.
|
||||||
|
func NewTexture (source image.Image) (texture Texture) {
|
||||||
|
bounds := source.Bounds()
|
||||||
|
texture.width = bounds.Dx()
|
||||||
|
texture.height = bounds.Dy()
|
||||||
|
texture.data = make([]color.RGBA, texture.width * texture.height)
|
||||||
|
|
||||||
|
index := 0
|
||||||
|
for y := bounds.Min.Y; y < bounds.Max.Y; y ++ {
|
||||||
|
for x := bounds.Min.X; x < bounds.Max.X; x ++ {
|
||||||
|
r, g, b, a := source.At(x, y).RGBA()
|
||||||
|
texture.data[index] = color.RGBA {
|
||||||
|
uint8(r >> 8),
|
||||||
|
uint8(g >> 8),
|
||||||
|
uint8(b >> 8),
|
||||||
|
uint8(a >> 8),
|
||||||
|
}
|
||||||
|
index ++
|
||||||
|
}}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// AtWhen returns the color at the specified x and y coordinates, wrapped to the
|
||||||
|
// image's width. the width and height are ignored.
|
||||||
|
func (texture Texture) AtWhen (x, y, width, height int) (pixel color.RGBA) {
|
||||||
|
x %= texture.width
|
||||||
|
y %= texture.height
|
||||||
|
if x < 0 { x += texture.width }
|
||||||
|
if y < 0 { y += texture.height }
|
||||||
|
return texture.data[x + y * texture.width]
|
||||||
|
}
|
||||||
|
@ -7,10 +7,10 @@ import "git.tebibyte.media/sashakoshka/tomo"
|
|||||||
// ShadingProfile contains shading information that can be used to draw chiseled
|
// ShadingProfile contains shading information that can be used to draw chiseled
|
||||||
// objects.
|
// objects.
|
||||||
type ShadingProfile struct {
|
type ShadingProfile struct {
|
||||||
Highlight tomo.Image
|
Highlight Pattern
|
||||||
Shadow tomo.Image
|
Shadow Pattern
|
||||||
Stroke tomo.Image
|
Stroke Pattern
|
||||||
Fill tomo.Image
|
Fill Pattern
|
||||||
StrokeWeight int
|
StrokeWeight int
|
||||||
ShadingWeight int
|
ShadingWeight int
|
||||||
}
|
}
|
||||||
@ -43,6 +43,7 @@ func ChiseledRectangle (
|
|||||||
strokeWeight := profile.StrokeWeight
|
strokeWeight := profile.StrokeWeight
|
||||||
shadingWeight := profile.ShadingWeight
|
shadingWeight := profile.ShadingWeight
|
||||||
|
|
||||||
|
data, stride := destination.Buffer()
|
||||||
bounds = bounds.Canon()
|
bounds = bounds.Canon()
|
||||||
updatedRegion = bounds
|
updatedRegion = bounds
|
||||||
|
|
||||||
@ -59,11 +60,6 @@ func ChiseledRectangle (
|
|||||||
fillBounds.Max = fillBounds.Max.Sub(shadingWeightVector)
|
fillBounds.Max = fillBounds.Max.Sub(shadingWeightVector)
|
||||||
fillBounds = fillBounds.Canon()
|
fillBounds = fillBounds.Canon()
|
||||||
|
|
||||||
strokeImageMin := stroke.Bounds().Min
|
|
||||||
highlightImageMin := highlight.Bounds().Min
|
|
||||||
shadowImageMin := shadow.Bounds().Min
|
|
||||||
fillImageMin := fill.Bounds().Min
|
|
||||||
|
|
||||||
width := float64(bounds.Dx())
|
width := float64(bounds.Dx())
|
||||||
height := float64(bounds.Dy())
|
height := float64(bounds.Dy())
|
||||||
|
|
||||||
@ -75,11 +71,10 @@ func ChiseledRectangle (
|
|||||||
point := image.Point { x, y }
|
point := image.Point { x, y }
|
||||||
switch {
|
switch {
|
||||||
case point.In(fillBounds):
|
case point.In(fillBounds):
|
||||||
pixel = fill.RGBAAt (
|
pixel = fill.AtWhen (
|
||||||
xx - strokeWeight - shadingWeight +
|
xx - strokeWeight - shadingWeight,
|
||||||
fillImageMin.X,
|
yy - strokeWeight - shadingWeight,
|
||||||
yy - strokeWeight - shadingWeight +
|
fillBounds.Dx(), fillBounds.Dy())
|
||||||
fillImageMin.Y)
|
|
||||||
|
|
||||||
case point.In(shadingBounds):
|
case point.In(shadingBounds):
|
||||||
var highlighted bool
|
var highlighted bool
|
||||||
@ -97,27 +92,21 @@ func ChiseledRectangle (
|
|||||||
width - float64(xx) >
|
width - float64(xx) >
|
||||||
float64(yy)
|
float64(yy)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
shadingSource := shadow
|
||||||
if highlighted {
|
if highlighted {
|
||||||
pixel = highlight.RGBAAt (
|
shadingSource = highlight
|
||||||
xx - strokeWeight +
|
|
||||||
highlightImageMin.X,
|
|
||||||
yy - strokeWeight +
|
|
||||||
highlightImageMin.Y)
|
|
||||||
} else {
|
|
||||||
pixel = shadow.RGBAAt (
|
|
||||||
xx - strokeWeight +
|
|
||||||
shadowImageMin.X,
|
|
||||||
yy - strokeWeight +
|
|
||||||
shadowImageMin.Y)
|
|
||||||
}
|
}
|
||||||
|
pixel = shadingSource.AtWhen (
|
||||||
|
xx - strokeWeight,
|
||||||
|
yy - strokeWeight,
|
||||||
|
shadingBounds.Dx(),
|
||||||
|
shadingBounds.Dy())
|
||||||
default:
|
default:
|
||||||
pixel = stroke.RGBAAt (
|
pixel = stroke.AtWhen (
|
||||||
xx + strokeImageMin.X,
|
xx, yy, bounds.Dx(), bounds.Dy())
|
||||||
yy + strokeImageMin.Y)
|
|
||||||
}
|
}
|
||||||
destination.SetRGBA(x, y, pixel)
|
data[x + y * stride] = pixel
|
||||||
xx ++
|
xx ++
|
||||||
}
|
}
|
||||||
yy ++
|
yy ++
|
||||||
|
@ -5,7 +5,7 @@ import "git.tebibyte.media/sashakoshka/tomo"
|
|||||||
|
|
||||||
func Line (
|
func Line (
|
||||||
destination tomo.Canvas,
|
destination tomo.Canvas,
|
||||||
source tomo.Image,
|
source Pattern,
|
||||||
weight int,
|
weight int,
|
||||||
min image.Point,
|
min image.Point,
|
||||||
max image.Point,
|
max image.Point,
|
||||||
@ -17,6 +17,8 @@ func Line (
|
|||||||
updatedRegion = image.Rectangle { Min: min, Max: max }.Canon()
|
updatedRegion = image.Rectangle { Min: min, Max: max }.Canon()
|
||||||
updatedRegion.Max.X ++
|
updatedRegion.Max.X ++
|
||||||
updatedRegion.Max.Y ++
|
updatedRegion.Max.Y ++
|
||||||
|
width := updatedRegion.Dx()
|
||||||
|
height := updatedRegion.Dy()
|
||||||
|
|
||||||
if abs(max.Y - min.Y) <
|
if abs(max.Y - min.Y) <
|
||||||
abs(max.X - min.X) {
|
abs(max.X - min.X) {
|
||||||
@ -26,7 +28,7 @@ func Line (
|
|||||||
min = max
|
min = max
|
||||||
max = temp
|
max = temp
|
||||||
}
|
}
|
||||||
lineLow(destination, source, weight, min, max)
|
lineLow(destination, source, weight, min, max, width, height)
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
if max.Y < min.Y {
|
if max.Y < min.Y {
|
||||||
@ -34,18 +36,21 @@ func Line (
|
|||||||
min = max
|
min = max
|
||||||
max = temp
|
max = temp
|
||||||
}
|
}
|
||||||
lineHigh(destination, source, weight, min, max)
|
lineHigh(destination, source, weight, min, max, width, height)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func lineLow (
|
func lineLow (
|
||||||
destination tomo.Canvas,
|
destination tomo.Canvas,
|
||||||
source tomo.Image,
|
source Pattern,
|
||||||
weight int,
|
weight int,
|
||||||
min image.Point,
|
min image.Point,
|
||||||
max image.Point,
|
max image.Point,
|
||||||
|
width, height int,
|
||||||
) {
|
) {
|
||||||
|
data, stride := destination.Buffer()
|
||||||
|
|
||||||
deltaX := max.X - min.X
|
deltaX := max.X - min.X
|
||||||
deltaY := max.Y - min.Y
|
deltaY := max.Y - min.Y
|
||||||
yi := 1
|
yi := 1
|
||||||
@ -59,7 +64,7 @@ func lineLow (
|
|||||||
y := min.Y
|
y := min.Y
|
||||||
|
|
||||||
for x := min.X; x < max.X; x ++ {
|
for x := min.X; x < max.X; x ++ {
|
||||||
destination.SetRGBA(x, y, source.RGBAAt(x, y))
|
data[x + y * stride] = source.AtWhen(x, y, width, height)
|
||||||
if D > 0 {
|
if D > 0 {
|
||||||
y += yi
|
y += yi
|
||||||
D += 2 * (deltaY - deltaX)
|
D += 2 * (deltaY - deltaX)
|
||||||
@ -71,11 +76,14 @@ func lineLow (
|
|||||||
|
|
||||||
func lineHigh (
|
func lineHigh (
|
||||||
destination tomo.Canvas,
|
destination tomo.Canvas,
|
||||||
source tomo.Image,
|
source Pattern,
|
||||||
weight int,
|
weight int,
|
||||||
min image.Point,
|
min image.Point,
|
||||||
max image.Point,
|
max image.Point,
|
||||||
|
width, height int,
|
||||||
) {
|
) {
|
||||||
|
data, stride := destination.Buffer()
|
||||||
|
|
||||||
deltaX := max.X - min.X
|
deltaX := max.X - min.X
|
||||||
deltaY := max.Y - min.Y
|
deltaY := max.Y - min.Y
|
||||||
xi := 1
|
xi := 1
|
||||||
@ -89,7 +97,7 @@ func lineHigh (
|
|||||||
x := min.X
|
x := min.X
|
||||||
|
|
||||||
for y := min.Y; y < max.Y; y ++ {
|
for y := min.Y; y < max.Y; y ++ {
|
||||||
destination.SetRGBA(x, y, source.RGBAAt(x, y))
|
data[x + y * stride] = source.AtWhen(x, y, width, height)
|
||||||
if D > 0 {
|
if D > 0 {
|
||||||
x += xi
|
x += xi
|
||||||
D += 2 * (deltaX - deltaY)
|
D += 2 * (deltaX - deltaY)
|
||||||
|
@ -1,105 +1,51 @@
|
|||||||
package artist
|
package artist
|
||||||
|
|
||||||
import "image"
|
import "image"
|
||||||
import "image/color"
|
|
||||||
import "git.tebibyte.media/sashakoshka/tomo"
|
import "git.tebibyte.media/sashakoshka/tomo"
|
||||||
|
|
||||||
// Paste transfers one image onto another, offset by the specified point.
|
// Paste transfers one canvas onto another, offset by the specified point.
|
||||||
func Paste (
|
func Paste (
|
||||||
destination tomo.Canvas,
|
destination tomo.Canvas,
|
||||||
source tomo.Image,
|
source tomo.Canvas,
|
||||||
offset image.Point,
|
offset image.Point,
|
||||||
) (
|
) (
|
||||||
updatedRegion image.Rectangle,
|
updatedRegion image.Rectangle,
|
||||||
) {
|
) {
|
||||||
sourceBounds := source.Bounds().Canon()
|
dstData, dstStride := destination.Buffer()
|
||||||
|
srcData, srcStride := source.Buffer()
|
||||||
|
|
||||||
|
sourceBounds :=
|
||||||
|
source.Bounds().Canon().
|
||||||
|
Intersect(destination.Bounds().Sub(offset))
|
||||||
|
if sourceBounds.Empty() { return }
|
||||||
|
|
||||||
updatedRegion = sourceBounds.Add(offset)
|
updatedRegion = sourceBounds.Add(offset)
|
||||||
for y := sourceBounds.Min.Y; y < sourceBounds.Max.Y; y ++ {
|
for y := sourceBounds.Min.Y; y < sourceBounds.Max.Y; y ++ {
|
||||||
for x := sourceBounds.Min.X; x < sourceBounds.Max.X; x ++ {
|
for x := sourceBounds.Min.X; x < sourceBounds.Max.X; x ++ {
|
||||||
destination.SetRGBA (
|
dstData[x + offset.X + (y + offset.Y) * dstStride] =
|
||||||
x + offset.X, y + offset.Y,
|
srcData[x + y * srcStride]
|
||||||
source.RGBAAt(x, y))
|
|
||||||
}}
|
}}
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Rectangle draws a rectangle with an inset border. If the border image is nil,
|
func FillRectangle (
|
||||||
// no border will be drawn. Likewise, if the fill image is nil, the rectangle
|
|
||||||
// will have no fill.
|
|
||||||
func Rectangle (
|
|
||||||
destination tomo.Canvas,
|
destination tomo.Canvas,
|
||||||
fill tomo.Image,
|
source Pattern,
|
||||||
stroke tomo.Image,
|
|
||||||
weight int,
|
|
||||||
bounds image.Rectangle,
|
bounds image.Rectangle,
|
||||||
) (
|
) (
|
||||||
updatedRegion image.Rectangle,
|
updatedRegion image.Rectangle,
|
||||||
) {
|
) {
|
||||||
bounds = bounds.Canon()
|
data, stride := destination.Buffer()
|
||||||
|
bounds = bounds.Canon().Intersect(destination.Bounds()).Canon()
|
||||||
|
if bounds.Empty() { return }
|
||||||
updatedRegion = bounds
|
updatedRegion = bounds
|
||||||
|
|
||||||
fillBounds := bounds
|
width, height := bounds.Dx(), bounds.Dy()
|
||||||
fillBounds.Min = fillBounds.Min.Add(image.Point { weight, weight })
|
for y := 0; y < height; y ++ {
|
||||||
fillBounds.Max = fillBounds.Max.Sub(image.Point { weight, weight })
|
for x := 0; x < width; x ++ {
|
||||||
fillBounds = fillBounds.Canon()
|
data[x + bounds.Min.X + (y + bounds.Min.Y) * stride] =
|
||||||
|
source.AtWhen(x, y, width, height)
|
||||||
for y := bounds.Min.Y; y < bounds.Max.Y; y ++ {
|
|
||||||
for x := bounds.Min.X; x < bounds.Max.X; x ++ {
|
|
||||||
var pixel color.RGBA
|
|
||||||
if (image.Point { x, y }).In(fillBounds) {
|
|
||||||
pixel = fill.RGBAAt(x, y)
|
|
||||||
} else {
|
|
||||||
pixel = stroke.RGBAAt(x, y)
|
|
||||||
}
|
|
||||||
destination.SetRGBA(x, y, pixel)
|
|
||||||
}}
|
}}
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// OffsetRectangle is the same as Rectangle, but offsets the border image to the
|
|
||||||
// top left corner of the border and the fill image to the top left corner of
|
|
||||||
// the fill.
|
|
||||||
func OffsetRectangle (
|
|
||||||
destination tomo.Canvas,
|
|
||||||
fill tomo.Image,
|
|
||||||
stroke tomo.Image,
|
|
||||||
weight int,
|
|
||||||
bounds image.Rectangle,
|
|
||||||
) (
|
|
||||||
updatedRegion image.Rectangle,
|
|
||||||
) {
|
|
||||||
bounds = bounds.Canon()
|
|
||||||
updatedRegion = bounds
|
|
||||||
|
|
||||||
fillBounds := bounds
|
|
||||||
fillBounds.Min = fillBounds.Min.Add(image.Point { weight, weight })
|
|
||||||
fillBounds.Max = fillBounds.Max.Sub(image.Point { weight, weight })
|
|
||||||
fillBounds = fillBounds.Canon()
|
|
||||||
|
|
||||||
strokeImageMin := stroke.Bounds().Min
|
|
||||||
fillImageMin := fill.Bounds().Min
|
|
||||||
|
|
||||||
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
|
|
||||||
if (image.Point { x, y }).In(fillBounds) {
|
|
||||||
pixel = fill.RGBAAt (
|
|
||||||
xx - weight + fillImageMin.X,
|
|
||||||
yy - weight + fillImageMin.Y)
|
|
||||||
} else {
|
|
||||||
pixel = stroke.RGBAAt (
|
|
||||||
xx + strokeImageMin.X,
|
|
||||||
yy + strokeImageMin.Y)
|
|
||||||
}
|
|
||||||
destination.SetRGBA(x, y, pixel)
|
|
||||||
xx ++
|
|
||||||
}
|
|
||||||
yy ++
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,7 @@ package artist
|
|||||||
// import "fmt"
|
// import "fmt"
|
||||||
import "image"
|
import "image"
|
||||||
import "unicode"
|
import "unicode"
|
||||||
import "image/draw"
|
// import "image/draw"
|
||||||
import "golang.org/x/image/font"
|
import "golang.org/x/image/font"
|
||||||
import "golang.org/x/image/math/fixed"
|
import "golang.org/x/image/math/fixed"
|
||||||
import "git.tebibyte.media/sashakoshka/tomo"
|
import "git.tebibyte.media/sashakoshka/tomo"
|
||||||
@ -95,34 +95,35 @@ func (drawer *TextDrawer) SetAlignment (align Align) {
|
|||||||
// Draw draws the drawer's text onto the specified canvas at the given offset.
|
// Draw draws the drawer's text onto the specified canvas at the given offset.
|
||||||
func (drawer *TextDrawer) Draw (
|
func (drawer *TextDrawer) Draw (
|
||||||
destination tomo.Canvas,
|
destination tomo.Canvas,
|
||||||
source tomo.Image,
|
source Pattern,
|
||||||
offset image.Point,
|
offset image.Point,
|
||||||
) (
|
) (
|
||||||
updatedRegion image.Rectangle,
|
updatedRegion image.Rectangle,
|
||||||
) {
|
) {
|
||||||
if !drawer.layoutClean { drawer.recalculate() }
|
if !drawer.layoutClean { drawer.recalculate() }
|
||||||
for _, word := range drawer.layout {
|
// TODO: reimplement a version of draw mask that takes in a pattern
|
||||||
for _, character := range word.text {
|
// for _, word := range drawer.layout {
|
||||||
destinationRectangle,
|
// for _, character := range word.text {
|
||||||
mask, maskPoint, _, ok := drawer.face.Glyph (
|
// destinationRectangle,
|
||||||
fixed.P (
|
// mask, maskPoint, _, ok := drawer.face.Glyph (
|
||||||
offset.X + word.position.X + character.x,
|
// fixed.P (
|
||||||
offset.Y + word.position.Y),
|
// offset.X + word.position.X + character.x,
|
||||||
character.character)
|
// offset.Y + word.position.Y),
|
||||||
if !ok { continue }
|
// character.character)
|
||||||
|
// if !ok { continue }
|
||||||
|
|
||||||
// FIXME: clip destination rectangle if we are on the cusp of
|
// FIXME: clip destination rectangle if we are on the cusp of
|
||||||
// the maximum height.
|
// the maximum height.
|
||||||
|
|
||||||
draw.DrawMask (
|
// draw.DrawMask (
|
||||||
destination,
|
// destination,
|
||||||
destinationRectangle,
|
// destinationRectangle,
|
||||||
source, image.Point { },
|
// source, image.Point { },
|
||||||
mask, maskPoint,
|
// mask, maskPoint,
|
||||||
draw.Over)
|
// draw.Over)
|
||||||
|
|
||||||
updatedRegion = updatedRegion.Union(destinationRectangle)
|
// updatedRegion = updatedRegion.Union(destinationRectangle)
|
||||||
}}
|
// }}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,8 +3,8 @@ package artist
|
|||||||
import "image"
|
import "image"
|
||||||
import "image/color"
|
import "image/color"
|
||||||
|
|
||||||
// Uniform is an infinite-sized Image of uniform color. It implements the
|
// Uniform is an infinite-sized pattern of uniform color. It implements the
|
||||||
// color.Color, color.Model, and tomo.Image interfaces.
|
// color.Color, color.Model, and image.Image interfaces.
|
||||||
type Uniform struct {
|
type Uniform struct {
|
||||||
C color.RGBA
|
C color.RGBA
|
||||||
}
|
}
|
||||||
@ -29,13 +29,11 @@ func (uniform *Uniform) RGBA () (r, g, b, a uint32) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (uniform *Uniform) ColorModel () (model color.Model) {
|
func (uniform *Uniform) ColorModel () (model color.Model) {
|
||||||
model = uniform
|
return uniform
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (uniform *Uniform) Convert (in color.Color) (out color.Color) {
|
func (uniform *Uniform) Convert (in color.Color) (c color.Color) {
|
||||||
out = uniform.C
|
return uniform.C
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (uniform *Uniform) Bounds () (rectangle image.Rectangle) {
|
func (uniform *Uniform) Bounds () (rectangle image.Rectangle) {
|
||||||
@ -45,13 +43,11 @@ func (uniform *Uniform) Bounds () (rectangle image.Rectangle) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (uniform *Uniform) At (x, y int) (c color.Color) {
|
func (uniform *Uniform) At (x, y int) (c color.Color) {
|
||||||
c = uniform.C
|
return uniform.C
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (uniform *Uniform) RGBAAt (x, y int) (c color.RGBA) {
|
func (uniform *Uniform) AtWhen (x, y, width, height int) (c color.RGBA) {
|
||||||
c = uniform.C
|
return uniform.C
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (uniform *Uniform) RGBA64At (x, y int) (c color.RGBA64) {
|
func (uniform *Uniform) RGBA64At (x, y int) (c color.RGBA64) {
|
||||||
@ -59,13 +55,10 @@ func (uniform *Uniform) RGBA64At (x, y int) (c color.RGBA64) {
|
|||||||
g := uint16(uniform.C.G) << 8 | uint16(uniform.C.G)
|
g := uint16(uniform.C.G) << 8 | uint16(uniform.C.G)
|
||||||
b := uint16(uniform.C.B) << 8 | uint16(uniform.C.B)
|
b := uint16(uniform.C.B) << 8 | uint16(uniform.C.B)
|
||||||
a := uint16(uniform.C.A) << 8 | uint16(uniform.C.A)
|
a := uint16(uniform.C.A) << 8 | uint16(uniform.C.A)
|
||||||
|
return color.RGBA64 { R: r, G: g, B: b, A: a }
|
||||||
c = color.RGBA64 { R: r, G: g, B: b, A: a }
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Opaque scans the entire image and reports whether it is fully opaque.
|
// Opaque scans the entire image and reports whether it is fully opaque.
|
||||||
func (uniform *Uniform) Opaque () (opaque bool) {
|
func (uniform *Uniform) Opaque () (opaque bool) {
|
||||||
opaque = uniform.C.A == 0xFF
|
return uniform.C.A == 0xFF
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
@ -1,99 +1 @@
|
|||||||
package artist
|
package artist
|
||||||
|
|
||||||
import "git.tebibyte.media/sashakoshka/tomo"
|
|
||||||
|
|
||||||
import "image"
|
|
||||||
import "image/draw"
|
|
||||||
import "image/color"
|
|
||||||
|
|
||||||
// WrappedImage wraps an image.Image and allows it to satisfy tomo.Image.
|
|
||||||
type WrappedImage struct { Underlying image.Image }
|
|
||||||
|
|
||||||
// WrapImage wraps a generic image.Image and allows it to satisfy tomo.Image.
|
|
||||||
// Do not use this function to wrap images that already satisfy tomo.Image,
|
|
||||||
// because the resulting wrapped image will be rather slow in comparison.
|
|
||||||
func WrapImage (underlying image.Image) (wrapped tomo.Image) {
|
|
||||||
wrapped = WrappedImage { Underlying: underlying }
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (wrapped WrappedImage) Bounds () (bounds image.Rectangle) {
|
|
||||||
bounds = wrapped.Underlying.Bounds()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (wrapped WrappedImage) ColorModel () (model color.Model) {
|
|
||||||
model = wrapped.Underlying.ColorModel()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (wrapped WrappedImage) At (x, y int) (pixel color.Color) {
|
|
||||||
pixel = wrapped.Underlying.At(x, y)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (wrapped WrappedImage) RGBAAt (x, y int) (pixel color.RGBA) {
|
|
||||||
r, g, b, a := wrapped.Underlying.At(x, y).RGBA()
|
|
||||||
pixel.R = uint8(r >> 8)
|
|
||||||
pixel.G = uint8(g >> 8)
|
|
||||||
pixel.B = uint8(b >> 8)
|
|
||||||
pixel.A = uint8(a >> 8)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// WrappedCanvas wraps a draw.Image and allows it to satisfy tomo.Canvas.
|
|
||||||
type WrappedCanvas struct { Underlying draw.Image }
|
|
||||||
|
|
||||||
// WrapCanvas wraps a generic draw.Image and allows it to satisfy tomo.Canvas.
|
|
||||||
// Do not use this function to wrap images that already satisfy tomo.Canvas,
|
|
||||||
// because the resulting wrapped image will be rather slow in comparison.
|
|
||||||
func WrapCanvas (underlying draw.Image) (wrapped tomo.Canvas) {
|
|
||||||
wrapped = WrappedCanvas { Underlying: underlying }
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (wrapped WrappedCanvas) Bounds () (bounds image.Rectangle) {
|
|
||||||
bounds = wrapped.Underlying.Bounds()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (wrapped WrappedCanvas) ColorModel () (model color.Model) {
|
|
||||||
model = wrapped.Underlying.ColorModel()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (wrapped WrappedCanvas) At (x, y int) (pixel color.Color) {
|
|
||||||
pixel = wrapped.Underlying.At(x, y)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (wrapped WrappedCanvas) RGBAAt (x, y int) (pixel color.RGBA) {
|
|
||||||
r, g, b, a := wrapped.Underlying.At(x, y).RGBA()
|
|
||||||
pixel.R = uint8(r >> 8)
|
|
||||||
pixel.G = uint8(g >> 8)
|
|
||||||
pixel.B = uint8(b >> 8)
|
|
||||||
pixel.A = uint8(a >> 8)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (wrapped WrappedCanvas) Set (x, y int, pixel color.Color) {
|
|
||||||
wrapped.Underlying.Set(x, y, pixel)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (wrapped WrappedCanvas) SetRGBA (x, y int, pixel color.RGBA) {
|
|
||||||
wrapped.Underlying.Set(x, y, pixel)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ToRGBA clones an existing image.Image into an image.RGBA struct, which
|
|
||||||
// directly satisfies tomo.Image. This is useful for things like icons and
|
|
||||||
// textures.
|
|
||||||
func ToRGBA (input image.Image) (output *image.RGBA) {
|
|
||||||
bounds := input.Bounds()
|
|
||||||
output = image.NewRGBA(bounds)
|
|
||||||
|
|
||||||
for y := bounds.Min.Y; y < bounds.Max.Y; y ++ {
|
|
||||||
for x := bounds.Min.X; x < bounds.Max.X; x ++ {
|
|
||||||
output.Set(x, y, input.At(x, y))
|
|
||||||
}}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
@ -187,8 +187,9 @@ func (window *Window) reallocateCanvas () {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (window *Window) redrawChildEntirely () {
|
func (window *Window) redrawChildEntirely () {
|
||||||
|
data, stride := window.child.Buffer()
|
||||||
window.xCanvas.For (func (x, y int) (c xgraphics.BGRA) {
|
window.xCanvas.For (func (x, y int) (c xgraphics.BGRA) {
|
||||||
rgba := window.child.RGBAAt(x, y)
|
rgba := data[x + y * stride]
|
||||||
c.R, c.G, c.B, c.A = rgba.R, rgba.G, rgba.B, rgba.A
|
c.R, c.G, c.B, c.A = rgba.R, rgba.G, rgba.B, rgba.A
|
||||||
return
|
return
|
||||||
})
|
})
|
||||||
@ -206,13 +207,14 @@ func (window *Window) resizeChildToFit () {
|
|||||||
window.redrawChildEntirely()
|
window.redrawChildEntirely()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (window *Window) childDrawCallback (region tomo.Image) {
|
func (window *Window) childDrawCallback (region tomo.Canvas) {
|
||||||
if window.skipChildDrawCallback { return }
|
if window.skipChildDrawCallback { return }
|
||||||
|
|
||||||
|
data, stride := region.Buffer()
|
||||||
bounds := region.Bounds()
|
bounds := region.Bounds()
|
||||||
for x := bounds.Min.X; x < bounds.Max.X; x ++ {
|
for x := bounds.Min.X; x < bounds.Max.X; x ++ {
|
||||||
for y := bounds.Min.Y; y < bounds.Max.Y; y ++ {
|
for y := bounds.Min.Y; y < bounds.Max.Y; y ++ {
|
||||||
rgba := region.RGBAAt(x, y)
|
rgba := data[x + y * stride]
|
||||||
window.xCanvas.SetBGRA (x, y, xgraphics.BGRA {
|
window.xCanvas.SetBGRA (x, y, xgraphics.BGRA {
|
||||||
R: rgba.R,
|
R: rgba.R,
|
||||||
G: rgba.G,
|
G: rgba.G,
|
||||||
|
70
canvas.go
Normal file
70
canvas.go
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
package tomo
|
||||||
|
|
||||||
|
import "image"
|
||||||
|
import "image/draw"
|
||||||
|
import "image/color"
|
||||||
|
|
||||||
|
// Canvas is like Image but also requires Set and SetRGBA methods. This
|
||||||
|
// interface can be easily satisfied using an image.RGBA struct.
|
||||||
|
type Canvas interface {
|
||||||
|
draw.Image
|
||||||
|
Buffer () (data []color.RGBA, stride int)
|
||||||
|
}
|
||||||
|
|
||||||
|
// BasicCanvas is a general purpose implementation of tomo.Canvas.
|
||||||
|
type BasicCanvas struct {
|
||||||
|
pix []color.RGBA
|
||||||
|
stride int
|
||||||
|
rect image.Rectangle
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewBasicCanvas creates a new basic canvas with the specified width and
|
||||||
|
// height, allocating a buffer for it.
|
||||||
|
func NewBasicCanvas (width, height int) (canvas BasicCanvas) {
|
||||||
|
canvas.pix = make([]color.RGBA, height * width)
|
||||||
|
canvas.stride = width
|
||||||
|
canvas.rect = image.Rect(0, 0, width, height)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// you know what it do
|
||||||
|
func (canvas BasicCanvas) Bounds () (bounds image.Rectangle) {
|
||||||
|
return canvas.rect
|
||||||
|
}
|
||||||
|
|
||||||
|
// you know what it do
|
||||||
|
func (canvas BasicCanvas) At (x, y int) (color.Color) {
|
||||||
|
if !image.Pt(x, y).In(canvas.rect) { return nil }
|
||||||
|
return canvas.pix[x + y * canvas.stride]
|
||||||
|
}
|
||||||
|
|
||||||
|
// you know what it do
|
||||||
|
func (canvas BasicCanvas) ColorModel () (model color.Model) {
|
||||||
|
return color.RGBAModel
|
||||||
|
}
|
||||||
|
|
||||||
|
// you know what it do
|
||||||
|
func (canvas BasicCanvas) Set (x, y int, c color.Color) {
|
||||||
|
if !image.Pt(x, y).In(canvas.rect) { return }
|
||||||
|
r, g, b, a := c.RGBA()
|
||||||
|
canvas.pix[x + y * canvas.stride] = color.RGBA {
|
||||||
|
R: uint8(r >> 8),
|
||||||
|
G: uint8(g >> 8),
|
||||||
|
B: uint8(b >> 8),
|
||||||
|
A: uint8(a >> 8),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// you know what it do
|
||||||
|
func (canvas BasicCanvas) Buffer () (data []color.RGBA, stride int) {
|
||||||
|
return canvas.pix, canvas.stride
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cut returns a sub-canvas of a given canvas.
|
||||||
|
func Cut (canvas Canvas, bounds image.Rectangle) (reduced BasicCanvas) {
|
||||||
|
bounds = bounds.Intersect(canvas.Bounds())
|
||||||
|
if bounds.Empty() { return }
|
||||||
|
reduced.rect = bounds
|
||||||
|
reduced.pix, reduced.stride = canvas.Buffer()
|
||||||
|
return
|
||||||
|
}
|
@ -57,7 +57,7 @@ func (element *Container) Adopt (child tomo.Element, expand bool) {
|
|||||||
|
|
||||||
return
|
return
|
||||||
},
|
},
|
||||||
Draw: func (region tomo.Image) {
|
Draw: func (region tomo.Canvas) {
|
||||||
element.drawChildRegion(child, region)
|
element.drawChildRegion(child, region)
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@ -318,10 +318,9 @@ func (element *Container) recalculate () {
|
|||||||
func (element *Container) draw () {
|
func (element *Container) draw () {
|
||||||
bounds := element.core.Bounds()
|
bounds := element.core.Bounds()
|
||||||
|
|
||||||
artist.Rectangle (
|
artist.FillRectangle (
|
||||||
element.core,
|
element.core,
|
||||||
theme.BackgroundImage(),
|
theme.BackgroundImage(),
|
||||||
nil, 0,
|
|
||||||
bounds)
|
bounds)
|
||||||
|
|
||||||
for _, entry := range element.children {
|
for _, entry := range element.children {
|
||||||
@ -329,7 +328,7 @@ func (element *Container) draw () {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (element *Container) drawChildRegion (child tomo.Element, region tomo.Image) {
|
func (element *Container) drawChildRegion (child tomo.Element, region tomo.Canvas) {
|
||||||
if element.warping { return }
|
if element.warping { return }
|
||||||
for _, entry := range element.children {
|
for _, entry := range element.children {
|
||||||
if entry.Element == child {
|
if entry.Element == child {
|
||||||
|
@ -93,10 +93,9 @@ func (element *Label) updateMinimumSize () {
|
|||||||
func (element *Label) draw () {
|
func (element *Label) draw () {
|
||||||
bounds := element.core.Bounds()
|
bounds := element.core.Bounds()
|
||||||
|
|
||||||
artist.Rectangle (
|
artist.FillRectangle (
|
||||||
element.core,
|
element.core,
|
||||||
theme.BackgroundImage(),
|
theme.BackgroundImage(),
|
||||||
nil, 0,
|
|
||||||
bounds)
|
bounds)
|
||||||
|
|
||||||
textBounds := element.drawer.LayoutBounds()
|
textBounds := element.drawer.LayoutBounds()
|
||||||
|
@ -7,7 +7,7 @@ import "git.tebibyte.media/sashakoshka/tomo"
|
|||||||
// Core is a struct that implements some core functionality common to most
|
// Core is a struct that implements some core functionality common to most
|
||||||
// widgets. It is meant to be embedded directly into a struct.
|
// widgets. It is meant to be embedded directly into a struct.
|
||||||
type Core struct {
|
type Core struct {
|
||||||
canvas *image.RGBA
|
canvas tomo.BasicCanvas
|
||||||
parent tomo.Element
|
parent tomo.Element
|
||||||
|
|
||||||
metrics struct {
|
metrics struct {
|
||||||
@ -32,20 +32,19 @@ func (core Core) ColorModel () (model color.Model) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (core Core) At (x, y int) (pixel color.Color) {
|
func (core Core) At (x, y int) (pixel color.Color) {
|
||||||
if core.canvas == nil { return color.RGBA { } }
|
return core.canvas.At(x, y)
|
||||||
pixel = core.canvas.At(x, y)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (core Core) RGBAAt (x, y int) (pixel color.RGBA) {
|
|
||||||
if core.canvas == nil { return color.RGBA { } }
|
|
||||||
pixel = core.canvas.RGBAAt(x, y)
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (core Core) Bounds () (bounds image.Rectangle) {
|
func (core Core) Bounds () (bounds image.Rectangle) {
|
||||||
if core.canvas != nil { bounds = core.canvas.Bounds() }
|
return core.canvas.Bounds()
|
||||||
return
|
}
|
||||||
|
|
||||||
|
func (core Core) Set (x, y int, c color.Color) () {
|
||||||
|
core.canvas.Set(x, y, c)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (core Core) Buffer () (data []color.RGBA, stride int) {
|
||||||
|
return core.canvas.Buffer()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (core Core) Selectable () (selectable bool) {
|
func (core Core) Selectable () (selectable bool) {
|
||||||
@ -72,13 +71,12 @@ func (core Core) MinimumSize () (width, height int) {
|
|||||||
// be used as a canvas. It must not be directly embedded into an element, but
|
// be used as a canvas. It must not be directly embedded into an element, but
|
||||||
// instead kept as a private member.
|
// instead kept as a private member.
|
||||||
type CoreControl struct {
|
type CoreControl struct {
|
||||||
*image.RGBA
|
tomo.BasicCanvas
|
||||||
core *Core
|
core *Core
|
||||||
}
|
}
|
||||||
|
|
||||||
func (control CoreControl) HasImage () (has bool) {
|
func (control CoreControl) HasImage () (empty bool) {
|
||||||
has = control.RGBA != nil
|
return !control.Bounds().Empty()
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (control CoreControl) Select () (granted bool) {
|
func (control CoreControl) Select () (granted bool) {
|
||||||
@ -98,7 +96,7 @@ func (control CoreControl) SetSelectable (selectable bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (control CoreControl) PushRegion (bounds image.Rectangle) {
|
func (control CoreControl) PushRegion (bounds image.Rectangle) {
|
||||||
control.core.hooks.RunDraw(control.SubImage(bounds).(*image.RGBA))
|
control.core.hooks.RunDraw(tomo.Cut(control, bounds))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (control CoreControl) PushAll () {
|
func (control CoreControl) PushAll () {
|
||||||
@ -108,8 +106,8 @@ func (control CoreControl) PushAll () {
|
|||||||
func (control *CoreControl) AllocateCanvas (width, height int) {
|
func (control *CoreControl) AllocateCanvas (width, height int) {
|
||||||
core := control.core
|
core := control.core
|
||||||
width, height, _ = control.ConstrainSize(width, height)
|
width, height, _ = control.ConstrainSize(width, height)
|
||||||
core.canvas = image.NewRGBA(image.Rect (0, 0, width, height))
|
core.canvas = tomo.NewBasicCanvas(width, height)
|
||||||
control.RGBA = core.canvas
|
control.BasicCanvas = core.canvas
|
||||||
}
|
}
|
||||||
|
|
||||||
func (control CoreControl) SetMinimumSize (width, height int) {
|
func (control CoreControl) SetMinimumSize (width, height int) {
|
||||||
@ -125,19 +123,17 @@ func (control CoreControl) SetMinimumSize (width, height int) {
|
|||||||
|
|
||||||
// if there is an image buffer, and the current size is less
|
// if there is an image buffer, and the current size is less
|
||||||
// than this new minimum size, send core.parent a resize event.
|
// than this new minimum size, send core.parent a resize event.
|
||||||
if control.HasImage() {
|
bounds := control.Bounds()
|
||||||
bounds := control.Bounds()
|
imageWidth,
|
||||||
imageWidth,
|
imageHeight,
|
||||||
imageHeight,
|
constrained := control.ConstrainSize (
|
||||||
constrained := control.ConstrainSize (
|
bounds.Dx(),
|
||||||
bounds.Dx(),
|
bounds.Dy())
|
||||||
bounds.Dy())
|
if constrained {
|
||||||
if constrained {
|
core.parent.Handle (tomo.EventResize {
|
||||||
core.parent.Handle (tomo.EventResize {
|
Width: imageWidth,
|
||||||
Width: imageWidth,
|
Height: imageHeight,
|
||||||
Height: imageHeight,
|
})
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -71,7 +71,7 @@ func (element *AnalogClock) draw () {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (element *AnalogClock) radialLine (
|
func (element *AnalogClock) radialLine (
|
||||||
source tomo.Image,
|
source artist.Pattern,
|
||||||
inner float64,
|
inner float64,
|
||||||
outer float64,
|
outer float64,
|
||||||
radian float64,
|
radian float64,
|
||||||
|
@ -13,7 +13,7 @@ type Mouse struct {
|
|||||||
*core.Core
|
*core.Core
|
||||||
core core.CoreControl
|
core core.CoreControl
|
||||||
drawing bool
|
drawing bool
|
||||||
color tomo.Image
|
color artist.Pattern
|
||||||
lastMousePos image.Point
|
lastMousePos image.Point
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -33,11 +33,11 @@ func (element *Mouse) Handle (event tomo.Event) {
|
|||||||
element.core.AllocateCanvas (
|
element.core.AllocateCanvas (
|
||||||
resizeEvent.Width,
|
resizeEvent.Width,
|
||||||
resizeEvent.Height)
|
resizeEvent.Height)
|
||||||
artist.Rectangle (
|
artist.FillRectangle (
|
||||||
element.core,
|
element.core,
|
||||||
theme.AccentImage(),
|
theme.AccentImage(),
|
||||||
artist.NewUniform(color.Black),
|
element.Bounds())
|
||||||
1, element.Bounds())
|
// TODO: draw a stroked rectangle around the edges
|
||||||
artist.Line (
|
artist.Line (
|
||||||
element.core, artist.NewUniform(color.White), 1,
|
element.core, artist.NewUniform(color.White), 1,
|
||||||
image.Pt(1, 1),
|
image.Pt(1, 1),
|
||||||
|
@ -3,7 +3,6 @@ package theme
|
|||||||
import "image"
|
import "image"
|
||||||
import "image/color"
|
import "image/color"
|
||||||
import "golang.org/x/image/font"
|
import "golang.org/x/image/font"
|
||||||
import "git.tebibyte.media/sashakoshka/tomo"
|
|
||||||
import "git.tebibyte.media/sashakoshka/tomo/artist"
|
import "git.tebibyte.media/sashakoshka/tomo/artist"
|
||||||
import "git.tebibyte.media/sashakoshka/tomo/defaultfont"
|
import "git.tebibyte.media/sashakoshka/tomo/defaultfont"
|
||||||
|
|
||||||
@ -151,34 +150,34 @@ func InputProfile (enabled bool, selected bool) artist.ShadingProfile {
|
|||||||
|
|
||||||
// BackgroundImage returns the texture/color used for the fill of
|
// BackgroundImage returns the texture/color used for the fill of
|
||||||
// BackgroundProfile.
|
// BackgroundProfile.
|
||||||
func BackgroundImage () tomo.Image {
|
func BackgroundImage () artist.Pattern {
|
||||||
return backgroundImage
|
return backgroundImage
|
||||||
}
|
}
|
||||||
|
|
||||||
// RaisedImage returns the texture/color used for the fill of RaisedProfile.
|
// RaisedImage returns the texture/color used for the fill of RaisedProfile.
|
||||||
func RaisedImage () tomo.Image {
|
func RaisedImage () artist.Pattern {
|
||||||
return raisedImage
|
return raisedImage
|
||||||
}
|
}
|
||||||
|
|
||||||
// InputImage returns the texture/color used for the fill of InputProfile.
|
// InputImage returns the texture/color used for the fill of InputProfile.
|
||||||
func InputImage () tomo.Image {
|
func InputImage () artist.Pattern {
|
||||||
return inputImage
|
return inputImage
|
||||||
}
|
}
|
||||||
|
|
||||||
// ForegroundImage returns the texture/color text and monochromatic icons should
|
// ForegroundImage returns the texture/color text and monochromatic icons should
|
||||||
// be drawn with.
|
// be drawn with.
|
||||||
func ForegroundImage () tomo.Image {
|
func ForegroundImage () artist.Pattern {
|
||||||
return foregroundImage
|
return foregroundImage
|
||||||
}
|
}
|
||||||
|
|
||||||
// DisabledForegroundImage returns the texture/color text and monochromatic
|
// DisabledForegroundImage returns the texture/color text and monochromatic
|
||||||
// icons should be drawn with if they are disabled.
|
// icons should be drawn with if they are disabled.
|
||||||
func DisabledForegroundImage () tomo.Image {
|
func DisabledForegroundImage () artist.Pattern {
|
||||||
return disabledForegroundImage
|
return disabledForegroundImage
|
||||||
}
|
}
|
||||||
|
|
||||||
// AccentImage returns the accent texture/color.
|
// AccentImage returns the accent texture/color.
|
||||||
func AccentImage () tomo.Image {
|
func AccentImage () artist.Pattern {
|
||||||
return accentImage
|
return accentImage
|
||||||
}
|
}
|
||||||
|
|
||||||
|
29
tomo.go
29
tomo.go
@ -2,25 +2,6 @@ package tomo
|
|||||||
|
|
||||||
import "image"
|
import "image"
|
||||||
import "errors"
|
import "errors"
|
||||||
import "image/draw"
|
|
||||||
import "image/color"
|
|
||||||
|
|
||||||
// Image represents a simple image buffer that fulfills the image.Image
|
|
||||||
// interface while also having methods that do away with the use of the
|
|
||||||
// color.Color interface to facilitate more efficient drawing. This interface
|
|
||||||
// can be easily satisfied using an image.RGBA struct.
|
|
||||||
type Image interface {
|
|
||||||
image.Image
|
|
||||||
RGBAAt (x, y int) (c color.RGBA)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Canvas is like Image but also requires Set and SetRGBA methods. This
|
|
||||||
// interface can be easily satisfied using an image.RGBA struct.
|
|
||||||
type Canvas interface {
|
|
||||||
draw.Image
|
|
||||||
RGBAAt (x, y int) (c color.RGBA)
|
|
||||||
SetRGBA (x, y int, c color.RGBA)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ParentHooks is a struct that contains callbacks that let child elements send
|
// ParentHooks is a struct that contains callbacks that let child elements send
|
||||||
// information to their parent element without the child element knowing
|
// information to their parent element without the child element knowing
|
||||||
@ -29,7 +10,7 @@ type Canvas interface {
|
|||||||
type ParentHooks struct {
|
type ParentHooks struct {
|
||||||
// Draw is called when a part of the child element's surface is updated.
|
// Draw is called when a part of the child element's surface is updated.
|
||||||
// The updated region will be passed to the callback as a sub-image.
|
// The updated region will be passed to the callback as a sub-image.
|
||||||
Draw func (region Image)
|
Draw func (region Canvas)
|
||||||
|
|
||||||
// MinimumSizeChange is called when the child element's minimum width
|
// MinimumSizeChange is called when the child element's minimum width
|
||||||
// and/or height changes. When this function is called, the element will
|
// and/or height changes. When this function is called, the element will
|
||||||
@ -49,7 +30,7 @@ type ParentHooks struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// RunDraw runs the Draw hook if it is not nil. If it is nil, it does nothing.
|
// RunDraw runs the Draw hook if it is not nil. If it is nil, it does nothing.
|
||||||
func (hooks ParentHooks) RunDraw (region Image) {
|
func (hooks ParentHooks) RunDraw (region Canvas) {
|
||||||
if hooks.Draw != nil {
|
if hooks.Draw != nil {
|
||||||
hooks.Draw(region)
|
hooks.Draw(region)
|
||||||
}
|
}
|
||||||
@ -82,10 +63,10 @@ func (hooks ParentHooks) RunSelectabilityChange (selectable bool) {
|
|||||||
|
|
||||||
// Element represents a basic on-screen object.
|
// Element represents a basic on-screen object.
|
||||||
type Element interface {
|
type Element interface {
|
||||||
// Element must implement the Image interface. Elements should start out
|
// Element must implement the Canvas interface. Elements should start
|
||||||
// with a completely blank image buffer, and only set its size and draw
|
// out with a completely blank buffer, and only allocate memory and draw
|
||||||
// on it for the first time when sent an EventResize event.
|
// on it for the first time when sent an EventResize event.
|
||||||
Image
|
Canvas
|
||||||
|
|
||||||
// Handle handles an event, propagating it to children if necessary.
|
// Handle handles an event, propagating it to children if necessary.
|
||||||
Handle (event Event)
|
Handle (event Event)
|
||||||
|
Reference in New Issue
Block a user