Merge pull request 'raw-buffer-api' (#1) from raw-buffer-api into main
Reviewed-on: sashakoshka/tomo#1
This commit is contained in:
commit
972f4d3af7
@ -1,2 +0,0 @@
|
|||||||
package artist
|
|
||||||
|
|
121
artist/chisel.go
121
artist/chisel.go
@ -1,127 +1,30 @@
|
|||||||
package artist
|
package artist
|
||||||
|
|
||||||
import "image"
|
|
||||||
import "image/color"
|
import "image/color"
|
||||||
import "git.tebibyte.media/sashakoshka/tomo"
|
|
||||||
|
|
||||||
// ShadingProfile contains shading information that can be used to draw chiseled
|
// Chiseled is a pattern that has a highlight section and a shadow section.
|
||||||
// objects.
|
type Chiseled struct {
|
||||||
type ShadingProfile struct {
|
Highlight Pattern
|
||||||
Highlight tomo.Image
|
Shadow Pattern
|
||||||
Shadow tomo.Image
|
|
||||||
Stroke tomo.Image
|
|
||||||
Fill tomo.Image
|
|
||||||
StrokeWeight int
|
|
||||||
ShadingWeight int
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Engraved reverses the shadown and highlight colors of the ShadingProfile to
|
// AtWhen satisfies the Pattern interface.
|
||||||
// produce a new ShadingProfile with an engraved appearance.
|
func (chiseled Chiseled) AtWhen (x, y, width, height int) (c color.RGBA) {
|
||||||
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
|
|
||||||
|
|
||||||
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()
|
|
||||||
|
|
||||||
strokeImageMin := stroke.Bounds().Min
|
|
||||||
highlightImageMin := highlight.Bounds().Min
|
|
||||||
shadowImageMin := shadow.Bounds().Min
|
|
||||||
fillImageMin := fill.Bounds().Min
|
|
||||||
|
|
||||||
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.RGBAAt (
|
|
||||||
xx - strokeWeight - shadingWeight +
|
|
||||||
fillImageMin.X,
|
|
||||||
yy - strokeWeight - shadingWeight +
|
|
||||||
fillImageMin.Y)
|
|
||||||
|
|
||||||
case point.In(shadingBounds):
|
|
||||||
var highlighted bool
|
var highlighted bool
|
||||||
// FIXME: this doesn't work quite right, the
|
// FIXME: this doesn't work quite right, the
|
||||||
// slope of the line is somewhat off.
|
// slope of the line is somewhat off.
|
||||||
bottomCorner :=
|
bottomCorner :=
|
||||||
float64(xx) < float64(yy) *
|
float64(x) < float64(y) *
|
||||||
(width / height)
|
(float64(width) / float64(height))
|
||||||
if bottomCorner {
|
if bottomCorner {
|
||||||
highlighted =
|
highlighted = float64(x) < float64(height) - float64(y)
|
||||||
float64(xx) <
|
|
||||||
height - float64(yy)
|
|
||||||
} else {
|
} else {
|
||||||
highlighted =
|
highlighted = float64(width) - float64(x) > float64(y)
|
||||||
width - float64(xx) >
|
|
||||||
float64(yy)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if highlighted {
|
if highlighted {
|
||||||
pixel = highlight.RGBAAt (
|
return chiseled.Highlight.AtWhen(x, y, width, height)
|
||||||
xx - strokeWeight +
|
|
||||||
highlightImageMin.X,
|
|
||||||
yy - strokeWeight +
|
|
||||||
highlightImageMin.Y)
|
|
||||||
} else {
|
} else {
|
||||||
pixel = shadow.RGBAAt (
|
return chiseled.Shadow.AtWhen(x, y, width, height)
|
||||||
xx - strokeWeight +
|
|
||||||
shadowImageMin.X,
|
|
||||||
yy - strokeWeight +
|
|
||||||
shadowImageMin.Y)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
default:
|
|
||||||
pixel = stroke.RGBAAt (
|
|
||||||
xx + strokeImageMin.X,
|
|
||||||
yy + strokeImageMin.Y)
|
|
||||||
}
|
|
||||||
destination.SetRGBA(x, y, pixel)
|
|
||||||
xx ++
|
|
||||||
}
|
|
||||||
yy ++
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
@ -3,9 +3,11 @@ package artist
|
|||||||
import "image"
|
import "image"
|
||||||
import "git.tebibyte.media/sashakoshka/tomo"
|
import "git.tebibyte.media/sashakoshka/tomo"
|
||||||
|
|
||||||
|
// Line draws a line from one point to another with the specified weight and
|
||||||
|
// pattern.
|
||||||
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 +19,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 +30,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 +38,22 @@ 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()
|
||||||
|
bounds := destination.Bounds()
|
||||||
|
|
||||||
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 +67,8 @@ 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))
|
if !(image.Point { x, y }).In(bounds) { break }
|
||||||
|
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 +80,15 @@ 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()
|
||||||
|
bounds := destination.Bounds()
|
||||||
|
|
||||||
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 +102,8 @@ 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))
|
if !(image.Point { x, y }).In(bounds) { break }
|
||||||
|
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)
|
||||||
|
58
artist/multiborder.go
Normal file
58
artist/multiborder.go
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
package artist
|
||||||
|
|
||||||
|
import "image"
|
||||||
|
import "image/color"
|
||||||
|
|
||||||
|
// Border represents a border that can be fed to MultiBorder.
|
||||||
|
type Border struct {
|
||||||
|
Weight int
|
||||||
|
Stroke Pattern
|
||||||
|
bounds image.Rectangle
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
point := image.Point { x, y }
|
||||||
|
for index := multi.maxBorder; index >= 0; index -- {
|
||||||
|
border := multi.borders[index]
|
||||||
|
if point.In(border.bounds) {
|
||||||
|
return border.Stroke.AtWhen (
|
||||||
|
point.X - border.bounds.Min.X,
|
||||||
|
point.Y - border.bounds.Min.Y,
|
||||||
|
border.dx, border.dy)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (multi *MultiBorder) recalculate (width, height int) {
|
||||||
|
bounds := image.Rect (0, 0, width, height)
|
||||||
|
multi.maxBorder = 0
|
||||||
|
for index, border := range multi.borders {
|
||||||
|
multi.maxBorder = index
|
||||||
|
multi.borders[index].bounds = bounds
|
||||||
|
multi.borders[index].dx = bounds.Dx()
|
||||||
|
multi.borders[index].dy = bounds.Dy()
|
||||||
|
bounds = bounds.Inset(border.Weight)
|
||||||
|
if bounds.Empty() { break }
|
||||||
|
}
|
||||||
|
}
|
12
artist/pattern.go
Normal file
12
artist/pattern.go
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
package artist
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
@ -1,105 +1,93 @@
|
|||||||
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,
|
// FillRectangle draws a filled rectangle with the specified pattern.
|
||||||
// no border will be drawn. Likewise, if the fill image is nil, the rectangle
|
func FillRectangle (
|
||||||
// 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
|
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
|
// StrokeRectangle draws the outline of a rectangle with the specified line
|
||||||
// the fill.
|
// weight and pattern.
|
||||||
func OffsetRectangle (
|
func StrokeRectangle (
|
||||||
destination tomo.Canvas,
|
destination tomo.Canvas,
|
||||||
fill tomo.Image,
|
source Pattern,
|
||||||
stroke tomo.Image,
|
|
||||||
weight int,
|
weight int,
|
||||||
bounds image.Rectangle,
|
bounds image.Rectangle,
|
||||||
) (
|
|
||||||
updatedRegion image.Rectangle,
|
|
||||||
) {
|
) {
|
||||||
bounds = bounds.Canon()
|
bounds = bounds.Canon()
|
||||||
updatedRegion = bounds
|
insetBounds := bounds.Inset(weight)
|
||||||
|
if insetBounds.Empty() {
|
||||||
fillBounds := bounds
|
FillRectangle(destination, source, 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
|
||||||
|
}
|
||||||
|
|
||||||
|
// top
|
||||||
|
FillRectangle (destination, source, image.Rect (
|
||||||
|
bounds.Min.X, bounds.Min.Y,
|
||||||
|
bounds.Max.X, insetBounds.Min.Y))
|
||||||
|
|
||||||
|
// bottom
|
||||||
|
FillRectangle (destination, source, image.Rect (
|
||||||
|
bounds.Min.X, insetBounds.Max.Y,
|
||||||
|
bounds.Max.X, bounds.Max.Y))
|
||||||
|
|
||||||
|
// left
|
||||||
|
FillRectangle (destination, source, image.Rect (
|
||||||
|
bounds.Min.X, insetBounds.Min.Y,
|
||||||
|
insetBounds.Min.X, insetBounds.Max.Y))
|
||||||
|
|
||||||
|
// right
|
||||||
|
FillRectangle (destination, source, image.Rect (
|
||||||
|
insetBounds.Max.X, insetBounds.Min.Y,
|
||||||
|
bounds.Max.X, insetBounds.Max.Y))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: FillEllipse
|
||||||
|
|
||||||
|
// TODO: StrokeEllipse
|
||||||
|
@ -95,12 +95,20 @@ 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,
|
||||||
) {
|
) {
|
||||||
|
wrappedSource := WrappedPattern {
|
||||||
|
Pattern: source,
|
||||||
|
Width: 0,
|
||||||
|
Height: 0, // TODO: choose a better width and height
|
||||||
|
}
|
||||||
|
|
||||||
if !drawer.layoutClean { drawer.recalculate() }
|
if !drawer.layoutClean { drawer.recalculate() }
|
||||||
|
// TODO: reimplement a version of draw mask that takes in a pattern and
|
||||||
|
// only draws to a tomo.Canvas.
|
||||||
for _, word := range drawer.layout {
|
for _, word := range drawer.layout {
|
||||||
for _, character := range word.text {
|
for _, character := range word.text {
|
||||||
destinationRectangle,
|
destinationRectangle,
|
||||||
@ -117,7 +125,7 @@ func (drawer *TextDrawer) Draw (
|
|||||||
draw.DrawMask (
|
draw.DrawMask (
|
||||||
destination,
|
destination,
|
||||||
destinationRectangle,
|
destinationRectangle,
|
||||||
source, image.Point { },
|
wrappedSource, image.Point { },
|
||||||
mask, maskPoint,
|
mask, maskPoint,
|
||||||
draw.Over)
|
draw.Over)
|
||||||
|
|
||||||
|
43
artist/texture.go
Normal file
43
artist/texture.go
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
package artist
|
||||||
|
|
||||||
|
import "image"
|
||||||
|
import "image/color"
|
||||||
|
|
||||||
|
// 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]
|
||||||
|
}
|
@ -3,69 +3,53 @@ 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.
|
// Pattern, color.Color, color.Model, and image.Image interfaces.
|
||||||
type Uniform struct {
|
type Uniform color.RGBA
|
||||||
C color.RGBA
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewUniform returns a new Uniform image of the given color.
|
// NewUniform returns a new Uniform image of the given color.
|
||||||
func NewUniform (c color.Color) (uniform *Uniform) {
|
func NewUniform (c color.Color) (uniform Uniform) {
|
||||||
uniform = &Uniform { }
|
|
||||||
r, g, b, a := c.RGBA()
|
r, g, b, a := c.RGBA()
|
||||||
uniform.C.R = uint8(r >> 8)
|
uniform.R = uint8(r >> 8)
|
||||||
uniform.C.G = uint8(g >> 8)
|
uniform.G = uint8(g >> 8)
|
||||||
uniform.C.B = uint8(b >> 8)
|
uniform.B = uint8(b >> 8)
|
||||||
uniform.C.A = uint8(a >> 8)
|
uniform.A = uint8(a >> 8)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (uniform *Uniform) RGBA () (r, g, b, a uint32) {
|
// ColorModel satisfies the image.Image interface.
|
||||||
r = uint32(uniform.C.R) << 8 | uint32(uniform.C.R)
|
func (uniform Uniform) ColorModel () (model color.Model) {
|
||||||
g = uint32(uniform.C.G) << 8 | uint32(uniform.C.G)
|
return uniform
|
||||||
b = uint32(uniform.C.B) << 8 | uint32(uniform.C.B)
|
|
||||||
a = uint32(uniform.C.A) << 8 | uint32(uniform.C.A)
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (uniform *Uniform) ColorModel () (model color.Model) {
|
// Convert satisfies the color.Model interface.
|
||||||
model = uniform
|
func (uniform Uniform) Convert (in color.Color) (c color.Color) {
|
||||||
return
|
return color.RGBA(uniform)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (uniform *Uniform) Convert (in color.Color) (out color.Color) {
|
// Bounds satisfies the image.Image interface.
|
||||||
out = uniform.C
|
func (uniform Uniform) Bounds () (rectangle image.Rectangle) {
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (uniform *Uniform) Bounds () (rectangle image.Rectangle) {
|
|
||||||
rectangle.Min = image.Point { -1e9, -1e9 }
|
rectangle.Min = image.Point { -1e9, -1e9 }
|
||||||
rectangle.Max = image.Point { 1e9, 1e9 }
|
rectangle.Max = image.Point { 1e9, 1e9 }
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (uniform *Uniform) At (x, y int) (c color.Color) {
|
// At satisfies the image.Image interface.
|
||||||
c = uniform.C
|
func (uniform Uniform) At (x, y int) (c color.Color) {
|
||||||
return
|
return color.RGBA(uniform)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (uniform *Uniform) RGBAAt (x, y int) (c color.RGBA) {
|
// AtWhen satisfies the Pattern interface.
|
||||||
c = uniform.C
|
func (uniform Uniform) AtWhen (x, y, width, height int) (c color.RGBA) {
|
||||||
return
|
return color.RGBA(uniform)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (uniform *Uniform) RGBA64At (x, y int) (c color.RGBA64) {
|
// RGBA satisfies the color.Color interface.
|
||||||
r := uint16(uniform.C.R) << 8 | uint16(uniform.C.R)
|
func (uniform Uniform) RGBA () (r, g, b, a uint32) {
|
||||||
g := uint16(uniform.C.G) << 8 | uint16(uniform.C.G)
|
return color.RGBA(uniform).RGBA()
|
||||||
b := uint16(uniform.C.B) << 8 | uint16(uniform.C.B)
|
|
||||||
a := uint16(uniform.C.A) << 8 | uint16(uniform.C.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.A == 0xFF
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
106
artist/wrap.go
106
artist/wrap.go
@ -1,99 +1,27 @@
|
|||||||
package artist
|
package artist
|
||||||
|
|
||||||
import "git.tebibyte.media/sashakoshka/tomo"
|
|
||||||
|
|
||||||
import "image"
|
import "image"
|
||||||
import "image/draw"
|
|
||||||
import "image/color"
|
import "image/color"
|
||||||
|
|
||||||
// WrappedImage wraps an image.Image and allows it to satisfy tomo.Image.
|
// WrappedPattern is a pattern that is able to behave like an image.Image.
|
||||||
type WrappedImage struct { Underlying image.Image }
|
type WrappedPattern struct {
|
||||||
|
Pattern
|
||||||
|
Width, Height int
|
||||||
|
}
|
||||||
|
|
||||||
// WrapImage wraps a generic image.Image and allows it to satisfy tomo.Image.
|
// At satisfies the image.Image interface.
|
||||||
// Do not use this function to wrap images that already satisfy tomo.Image,
|
func (pattern WrappedPattern) At (x, y int) (c color.Color) {
|
||||||
// because the resulting wrapped image will be rather slow in comparison.
|
return pattern.Pattern.AtWhen(x, y, pattern.Width, pattern.Height)
|
||||||
func WrapImage (underlying image.Image) (wrapped tomo.Image) {
|
}
|
||||||
wrapped = WrappedImage { Underlying: underlying }
|
|
||||||
|
// Bounds satisfies the image.Image interface.
|
||||||
|
func (pattern WrappedPattern) Bounds () (rectangle image.Rectangle) {
|
||||||
|
rectangle.Min = image.Point { -1e9, -1e9 }
|
||||||
|
rectangle.Max = image.Point { 1e9, 1e9 }
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (wrapped WrappedImage) Bounds () (bounds image.Rectangle) {
|
// ColorModel satisfies the image.Image interface.
|
||||||
bounds = wrapped.Underlying.Bounds()
|
func (pattern WrappedPattern) ColorModel () (model color.Model) {
|
||||||
return
|
return color.RGBAModel
|
||||||
}
|
|
||||||
|
|
||||||
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
|
||||||
|
}
|
@ -150,12 +150,12 @@ func (element *Button) SetText (text string) {
|
|||||||
func (element *Button) draw () {
|
func (element *Button) draw () {
|
||||||
bounds := element.core.Bounds()
|
bounds := element.core.Bounds()
|
||||||
|
|
||||||
artist.ChiseledRectangle (
|
artist.FillRectangle (
|
||||||
element.core,
|
element.core,
|
||||||
theme.RaisedProfile (
|
theme.ButtonPattern (
|
||||||
element.pressed,
|
|
||||||
element.enabled,
|
element.enabled,
|
||||||
element.Selected()),
|
element.Selected(),
|
||||||
|
element.pressed),
|
||||||
bounds)
|
bounds)
|
||||||
|
|
||||||
innerBounds := bounds
|
innerBounds := bounds
|
||||||
@ -179,10 +179,6 @@ func (element *Button) draw () {
|
|||||||
offset = offset.Add(theme.SinkOffsetVector())
|
offset = offset.Add(theme.SinkOffsetVector())
|
||||||
}
|
}
|
||||||
|
|
||||||
foreground := theme.ForegroundImage()
|
foreground := theme.ForegroundPattern(element.enabled)
|
||||||
if !element.enabled {
|
|
||||||
foreground = theme.DisabledForegroundImage()
|
|
||||||
}
|
|
||||||
|
|
||||||
element.drawer.Draw(element.core, foreground, offset)
|
element.drawer.Draw(element.core, foreground, offset)
|
||||||
}
|
}
|
||||||
|
@ -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.BackgroundPattern(),
|
||||||
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,15 +93,14 @@ 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.BackgroundPattern(),
|
||||||
nil, 0,
|
|
||||||
bounds)
|
bounds)
|
||||||
|
|
||||||
textBounds := element.drawer.LayoutBounds()
|
textBounds := element.drawer.LayoutBounds()
|
||||||
|
|
||||||
foreground := theme.ForegroundImage()
|
foreground := theme.ForegroundPattern(true)
|
||||||
element.drawer.Draw (element.core, foreground, image.Point {
|
element.drawer.Draw (element.core, foreground, image.Point {
|
||||||
X: 0 - textBounds.Min.X,
|
X: 0 - textBounds.Min.X,
|
||||||
Y: 0 - textBounds.Min.Y,
|
Y: 0 - textBounds.Min.Y,
|
||||||
|
@ -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,7 +123,6 @@ 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,
|
||||||
@ -138,7 +135,6 @@ func (control CoreControl) SetMinimumSize (width, height int) {
|
|||||||
Height: imageHeight,
|
Height: imageHeight,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (control CoreControl) ConstrainSize (
|
func (control CoreControl) ConstrainSize (
|
||||||
|
@ -44,14 +44,14 @@ func (element *AnalogClock) SetTime (newTime time.Time) {
|
|||||||
func (element *AnalogClock) draw () {
|
func (element *AnalogClock) draw () {
|
||||||
bounds := element.core.Bounds()
|
bounds := element.core.Bounds()
|
||||||
|
|
||||||
artist.ChiseledRectangle (
|
artist.FillRectangle (
|
||||||
element.core,
|
element.core,
|
||||||
theme.BackgroundProfile(true),
|
theme.SunkenPattern(),
|
||||||
bounds)
|
bounds)
|
||||||
|
|
||||||
for hour := 0; hour < 12; hour ++ {
|
for hour := 0; hour < 12; hour ++ {
|
||||||
element.radialLine (
|
element.radialLine (
|
||||||
theme.ForegroundImage(),
|
theme.ForegroundPattern(true),
|
||||||
0.8, 0.9, float64(hour) / 6 * math.Pi)
|
0.8, 0.9, float64(hour) / 6 * math.Pi)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -60,18 +60,18 @@ func (element *AnalogClock) draw () {
|
|||||||
hour := float64(element.time.Hour()) + minute / 60
|
hour := float64(element.time.Hour()) + minute / 60
|
||||||
|
|
||||||
element.radialLine (
|
element.radialLine (
|
||||||
theme.ForegroundImage(),
|
theme.ForegroundPattern(true),
|
||||||
0, 0.5, (hour - 3) / 6 * math.Pi)
|
0, 0.5, (hour - 3) / 6 * math.Pi)
|
||||||
element.radialLine (
|
element.radialLine (
|
||||||
theme.ForegroundImage(),
|
theme.ForegroundPattern(true),
|
||||||
0, 0.7, (minute - 15) / 30 * math.Pi)
|
0, 0.7, (minute - 15) / 30 * math.Pi)
|
||||||
element.radialLine (
|
element.radialLine (
|
||||||
theme.AccentImage(),
|
theme.AccentPattern(),
|
||||||
0, 0.7, (second - 15) / 30 * math.Pi)
|
0, 0.7, (second - 15) / 30 * math.Pi)
|
||||||
}
|
}
|
||||||
|
|
||||||
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,13 +33,16 @@ 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.AccentPattern(),
|
||||||
artist.NewUniform(color.Black),
|
element.Bounds())
|
||||||
1, element.Bounds())
|
artist.StrokeRectangle (
|
||||||
|
element.core,
|
||||||
|
artist.NewUniform(color.Black), 1,
|
||||||
|
element.Bounds())
|
||||||
artist.Line (
|
artist.Line (
|
||||||
element.core, artist.NewUniform(color.White), 1,
|
element.core, artist.NewUniform(color.White), 3,
|
||||||
image.Pt(1, 1),
|
image.Pt(1, 1),
|
||||||
image.Pt(resizeEvent.Width - 2, resizeEvent.Height - 2))
|
image.Pt(resizeEvent.Width - 2, resizeEvent.Height - 2))
|
||||||
artist.Line (
|
artist.Line (
|
||||||
@ -66,6 +69,7 @@ func (element *Mouse) Handle (event tomo.Event) {
|
|||||||
element.lastMousePos = mousePos
|
element.lastMousePos = mousePos
|
||||||
|
|
||||||
case tomo.EventMouseMove:
|
case tomo.EventMouseMove:
|
||||||
|
if !element.drawing { return }
|
||||||
mouseMoveEvent := event.(tomo.EventMouseMove)
|
mouseMoveEvent := event.(tomo.EventMouseMove)
|
||||||
mousePos := image.Pt (
|
mousePos := image.Pt (
|
||||||
mouseMoveEvent.X,
|
mouseMoveEvent.X,
|
||||||
|
229
theme/theme.go
229
theme/theme.go
@ -3,186 +3,99 @@ 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"
|
||||||
|
|
||||||
// none of these colors are final! TODO: generate these values from a theme
|
// none of these colors are final! TODO: generate these values from a theme
|
||||||
// file at startup.
|
// file at startup.
|
||||||
|
|
||||||
var foregroundImage = artist.NewUniform(color.Gray16 { 0x0000})
|
func hex (color uint32) (c color.RGBA) {
|
||||||
var disabledForegroundImage = artist.NewUniform(color.Gray16 { 0x5555})
|
c.A = uint8(color)
|
||||||
var accentImage = artist.NewUniform(color.RGBA { 0x40, 0x80, 0x90, 0xFF})
|
c.B = uint8(color >> 8)
|
||||||
var highlightImage = artist.NewUniform(color.Gray16 { 0xEEEE })
|
c.G = uint8(color >> 16)
|
||||||
var shadowImage = artist.NewUniform(color.Gray16 { 0x3333 })
|
c.R = uint8(color >> 24)
|
||||||
var weakShadeImage = artist.NewUniform(color.Gray16 { 0x7777 })
|
return
|
||||||
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,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var inputImage = artist.NewUniform(color.Gray16 { 0xFFFF })
|
var accentPattern = artist.NewUniform(hex(0x408090FF))
|
||||||
var inputProfile = artist.ShadingProfile {
|
var backgroundPattern = artist.NewUniform(color.Gray16 { 0xAAAA })
|
||||||
Highlight: insetShadowImage,
|
var foregroundPattern = artist.NewUniform(color.Gray16 { 0x0000 })
|
||||||
Shadow: inputImage,
|
var weakForegroundPattern = artist.NewUniform(color.Gray16 { 0x4444 })
|
||||||
Stroke: strokeImage,
|
var strokePattern = artist.NewUniform(color.Gray16 { 0x0000 })
|
||||||
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,
|
|
||||||
}
|
|
||||||
|
|
||||||
// BackgroundProfile returns the shading profile to be used for backgrounds.
|
var buttonPattern = artist.NewMultiBorder (
|
||||||
func BackgroundProfile (engraved bool) artist.ShadingProfile {
|
artist.Border { Weight: 1, Stroke: strokePattern },
|
||||||
if engraved {
|
artist.Border {
|
||||||
return engravedBackgroundProfile
|
Weight: 1,
|
||||||
} else {
|
Stroke: artist.Chiseled {
|
||||||
return backgroundProfile
|
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 })
|
||||||
|
|
||||||
// RaisedProfile returns the shading profile to be used for raised objects such
|
var sunkenPattern = artist.NewMultiBorder (
|
||||||
// as buttons.
|
artist.Border { Weight: 1, Stroke: strokePattern },
|
||||||
func RaisedProfile (
|
artist.Border {
|
||||||
engraved bool,
|
Weight: 1,
|
||||||
enabled bool,
|
Stroke: artist.Chiseled {
|
||||||
selected bool,
|
Highlight: artist.NewUniform(hex(0x373C3AFF)),
|
||||||
) (
|
Shadow: artist.NewUniform(hex(0xDBDBDBFF)),
|
||||||
artist.ShadingProfile,
|
},
|
||||||
) {
|
},
|
||||||
|
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 {
|
if enabled {
|
||||||
if engraved {
|
return foregroundPattern
|
||||||
if selected {
|
|
||||||
return selectedEngravedRaisedProfile
|
|
||||||
} else {
|
} else {
|
||||||
return engravedRaisedProfile
|
return weakForegroundPattern
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if selected {
|
|
||||||
return selectedRaisedProfile
|
|
||||||
} else {
|
|
||||||
return raisedProfile
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return disabledRaisedProfile
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
func ButtonPattern (enabled, selected, pressed bool) (artist.Pattern) {
|
||||||
// InputProfile returns the shading profile to be used for input fields.
|
|
||||||
func InputProfile (enabled bool, selected bool) artist.ShadingProfile {
|
|
||||||
if enabled {
|
if enabled {
|
||||||
|
if pressed {
|
||||||
|
return pressedButtonPattern
|
||||||
|
} else {
|
||||||
if selected {
|
if selected {
|
||||||
return selectedInputProfile
|
return selectedButtonPattern
|
||||||
} else {
|
} else {
|
||||||
return inputProfile
|
return buttonPattern
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return disabledInputProfile
|
return disabledButtonPattern
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// BackgroundImage returns the texture/color used for the fill of
|
// TODO: load fonts from an actual source instead of using defaultfont
|
||||||
// BackgroundProfile.
|
|
||||||
func BackgroundImage () tomo.Image {
|
|
||||||
return backgroundImage
|
|
||||||
}
|
|
||||||
|
|
||||||
// RaisedImage returns the texture/color used for the fill of RaisedProfile.
|
|
||||||
func RaisedImage () tomo.Image {
|
|
||||||
return raisedImage
|
|
||||||
}
|
|
||||||
|
|
||||||
// InputImage returns the texture/color used for the fill of InputProfile.
|
|
||||||
func InputImage () tomo.Image {
|
|
||||||
return inputImage
|
|
||||||
}
|
|
||||||
|
|
||||||
// ForegroundImage returns the texture/color text and monochromatic icons should
|
|
||||||
// be drawn with.
|
|
||||||
func ForegroundImage () tomo.Image {
|
|
||||||
return foregroundImage
|
|
||||||
}
|
|
||||||
|
|
||||||
// DisabledForegroundImage returns the texture/color text and monochromatic
|
|
||||||
// icons should be drawn with if they are disabled.
|
|
||||||
func DisabledForegroundImage () tomo.Image {
|
|
||||||
return disabledForegroundImage
|
|
||||||
}
|
|
||||||
|
|
||||||
// AccentImage returns the accent texture/color.
|
|
||||||
func AccentImage () tomo.Image {
|
|
||||||
return accentImage
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: load fonts from an actual source instead of using basicfont
|
|
||||||
|
|
||||||
// FontFaceRegular returns the font face to be used for normal text.
|
// FontFaceRegular returns the font face to be used for normal text.
|
||||||
func FontFaceRegular () font.Face {
|
func FontFaceRegular () font.Face {
|
||||||
|
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