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
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
							
								
								
									
										139
									
								
								artist/chisel.go
									
									
									
									
									
								
							
							
						
						
									
										139
									
								
								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) {
 | 
						var highlighted bool
 | 
				
			||||||
	reversed = profile
 | 
						// FIXME: this doesn't work quite right, the
 | 
				
			||||||
	reversed.Highlight = profile.Shadow
 | 
						// slope of the line is somewhat off.
 | 
				
			||||||
	reversed.Shadow    = profile.Highlight
 | 
						bottomCorner :=
 | 
				
			||||||
	return
 | 
							float64(x) < float64(y) *
 | 
				
			||||||
}
 | 
							(float64(width) / float64(height))
 | 
				
			||||||
 | 
						if bottomCorner {
 | 
				
			||||||
// ChiseledRectangle draws a rectangle with a chiseled/embossed appearance,
 | 
							highlighted = float64(x) < float64(height) - float64(y)
 | 
				
			||||||
// according to the ShadingProfile passed to it.
 | 
						} else {
 | 
				
			||||||
func ChiseledRectangle (
 | 
							highlighted = float64(width) - float64(x) > float64(y)
 | 
				
			||||||
	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
 | 
					 | 
				
			||||||
				// FIXME: this doesn't work quite right, the
 | 
					 | 
				
			||||||
				// slope of the line is somewhat off.
 | 
					 | 
				
			||||||
				bottomCorner :=
 | 
					 | 
				
			||||||
					float64(xx) < float64(yy) *
 | 
					 | 
				
			||||||
					(width / height)
 | 
					 | 
				
			||||||
				if bottomCorner {
 | 
					 | 
				
			||||||
					highlighted =
 | 
					 | 
				
			||||||
						float64(xx) <
 | 
					 | 
				
			||||||
						height - float64(yy)
 | 
					 | 
				
			||||||
				} else {
 | 
					 | 
				
			||||||
					highlighted =
 | 
					 | 
				
			||||||
						width - float64(xx) >
 | 
					 | 
				
			||||||
						float64(yy)
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
			
 | 
					 | 
				
			||||||
				if highlighted {
 | 
					 | 
				
			||||||
					pixel = highlight.RGBAAt (
 | 
					 | 
				
			||||||
						xx - strokeWeight +
 | 
					 | 
				
			||||||
						highlightImageMin.X,
 | 
					 | 
				
			||||||
						yy - strokeWeight +
 | 
					 | 
				
			||||||
						highlightImageMin.Y)
 | 
					 | 
				
			||||||
				} else {
 | 
					 | 
				
			||||||
					pixel = shadow.RGBAAt (
 | 
					 | 
				
			||||||
						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
 | 
						if highlighted {
 | 
				
			||||||
 | 
							return chiseled.Highlight.AtWhen(x, y, width, height)
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							return chiseled.Shadow.AtWhen(x, y, width, height)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -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 })
 | 
							return
 | 
				
			||||||
	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
 | 
						// 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,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,
 | 
							})
 | 
				
			||||||
			})
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -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,
 | 
				
			||||||
 | 
							Stroke: artist.Chiseled {
 | 
				
			||||||
 | 
								Highlight: artist.NewUniform(hex(0xCCD5D2FF)),
 | 
				
			||||||
 | 
								Shadow:    artist.NewUniform(hex(0x4B5B59FF)),
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
						artist.Border { Stroke: artist.NewUniform(hex(0x8D9894FF)) })
 | 
				
			||||||
 | 
					var selectedButtonPattern = artist.NewMultiBorder (
 | 
				
			||||||
 | 
						artist.Border { Weight: 1, Stroke: strokePattern },
 | 
				
			||||||
 | 
						artist.Border {
 | 
				
			||||||
 | 
							Weight: 1,
 | 
				
			||||||
 | 
							Stroke: artist.Chiseled {
 | 
				
			||||||
 | 
								Highlight: artist.NewUniform(hex(0xCCD5D2FF)),
 | 
				
			||||||
 | 
								Shadow:    artist.NewUniform(hex(0x4B5B59FF)),
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
						artist.Border { Weight: 1, Stroke: accentPattern },
 | 
				
			||||||
 | 
						artist.Border { Stroke: artist.NewUniform(hex(0x8D9894FF)) })
 | 
				
			||||||
 | 
					var pressedButtonPattern = artist.NewMultiBorder (
 | 
				
			||||||
 | 
						artist.Border { Weight: 1, Stroke: strokePattern },
 | 
				
			||||||
 | 
						artist.Border {
 | 
				
			||||||
 | 
							Weight: 1,
 | 
				
			||||||
 | 
							Stroke: artist.Chiseled {
 | 
				
			||||||
 | 
								Highlight: artist.NewUniform(hex(0x4B5B59FF)),
 | 
				
			||||||
 | 
								Shadow:    artist.NewUniform(hex(0x8D9894FF)),
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
						artist.Border { Stroke: artist.NewUniform(hex(0x8D9894FF)) })
 | 
				
			||||||
 | 
					var disabledButtonPattern = artist.NewMultiBorder (
 | 
				
			||||||
 | 
						artist.Border { Weight: 1, Stroke: weakForegroundPattern },
 | 
				
			||||||
 | 
						artist.Border { Stroke: backgroundPattern })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var sunkenPattern = artist.NewMultiBorder (
 | 
				
			||||||
 | 
						artist.Border { Weight: 1, Stroke: strokePattern },
 | 
				
			||||||
 | 
						artist.Border {
 | 
				
			||||||
 | 
							Weight: 1,
 | 
				
			||||||
 | 
							Stroke: artist.Chiseled {
 | 
				
			||||||
 | 
								Highlight: artist.NewUniform(hex(0x373C3AFF)),
 | 
				
			||||||
 | 
								Shadow:    artist.NewUniform(hex(0xDBDBDBFF)),
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
						artist.Border { Stroke: backgroundPattern })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func AccentPattern () (artist.Pattern) { return accentPattern }
 | 
				
			||||||
 | 
					func BackgroundPattern () (artist.Pattern) { return backgroundPattern }
 | 
				
			||||||
 | 
					func SunkenPattern () (artist.Pattern) { return sunkenPattern}
 | 
				
			||||||
 | 
					func ForegroundPattern (enabled bool) (artist.Pattern) {
 | 
				
			||||||
 | 
						if enabled {
 | 
				
			||||||
 | 
							return foregroundPattern
 | 
				
			||||||
	} else {
 | 
						} else {
 | 
				
			||||||
		return backgroundProfile
 | 
							return weakForegroundPattern
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					func ButtonPattern (enabled, selected, pressed bool) (artist.Pattern) {
 | 
				
			||||||
// RaisedProfile returns the shading profile to be used for raised objects such
 | 
					 | 
				
			||||||
// as buttons.
 | 
					 | 
				
			||||||
func RaisedProfile (
 | 
					 | 
				
			||||||
	engraved bool,
 | 
					 | 
				
			||||||
	enabled  bool,
 | 
					 | 
				
			||||||
	selected bool,
 | 
					 | 
				
			||||||
) (
 | 
					 | 
				
			||||||
	artist.ShadingProfile,
 | 
					 | 
				
			||||||
) {
 | 
					 | 
				
			||||||
	if enabled {
 | 
						if enabled {
 | 
				
			||||||
		if engraved {
 | 
							if pressed {
 | 
				
			||||||
			if selected {
 | 
								return pressedButtonPattern
 | 
				
			||||||
				return selectedEngravedRaisedProfile
 | 
					 | 
				
			||||||
			} else {
 | 
					 | 
				
			||||||
				return engravedRaisedProfile
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		} else {
 | 
							} else {
 | 
				
			||||||
			if selected {
 | 
								if selected {
 | 
				
			||||||
				return selectedRaisedProfile
 | 
									return selectedButtonPattern
 | 
				
			||||||
			} else {
 | 
								} else {
 | 
				
			||||||
				return raisedProfile
 | 
									return buttonPattern
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	} else {
 | 
						} else {
 | 
				
			||||||
		return disabledRaisedProfile
 | 
							return disabledButtonPattern
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// InputProfile returns the shading profile to be used for input fields.
 | 
					// TODO: load fonts from an actual source instead of using defaultfont
 | 
				
			||||||
func InputProfile (enabled bool, selected bool) artist.ShadingProfile {
 | 
					 | 
				
			||||||
	if enabled {
 | 
					 | 
				
			||||||
		if selected {
 | 
					 | 
				
			||||||
			return selectedInputProfile
 | 
					 | 
				
			||||||
		} else {
 | 
					 | 
				
			||||||
			return inputProfile
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	} else {
 | 
					 | 
				
			||||||
		return disabledInputProfile
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// BackgroundImage returns the texture/color used for the fill of
 | 
					 | 
				
			||||||
// BackgroundProfile.
 | 
					 | 
				
			||||||
func BackgroundImage () 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