data-oriented-patterns #9
@ -1,46 +0,0 @@
 | 
				
			|||||||
package artist
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import "image/color"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// Beveled is a pattern that has a highlight section and a shadow section.
 | 
					 | 
				
			||||||
type Beveled [2]Pattern
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// AtWhen satisfies the Pattern interface.
 | 
					 | 
				
			||||||
func (pattern Beveled) AtWhen (x, y, width, height int) (c color.RGBA) {
 | 
					 | 
				
			||||||
	return QuadBeveled {
 | 
					 | 
				
			||||||
		pattern[0],
 | 
					 | 
				
			||||||
		pattern[1],
 | 
					 | 
				
			||||||
		pattern[1],
 | 
					 | 
				
			||||||
		pattern[0],
 | 
					 | 
				
			||||||
	}.AtWhen(x, y, width, height)
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// QuadBeveled is like Beveled, but with four sides. A pattern can be specified
 | 
					 | 
				
			||||||
// for each one.
 | 
					 | 
				
			||||||
type QuadBeveled [4]Pattern
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// AtWhen satisfies the Pattern interface.
 | 
					 | 
				
			||||||
func (pattern QuadBeveled) AtWhen (x, y, width, height int) (c color.RGBA) {
 | 
					 | 
				
			||||||
	bottom := y > height / 2
 | 
					 | 
				
			||||||
	right  := x > width / 2
 | 
					 | 
				
			||||||
	top    := !bottom
 | 
					 | 
				
			||||||
	left   := !right
 | 
					 | 
				
			||||||
	side := 0
 | 
					 | 
				
			||||||
	
 | 
					 | 
				
			||||||
	switch {
 | 
					 | 
				
			||||||
	case top && left:
 | 
					 | 
				
			||||||
		if x < y { side = 3 } else { side = 0 }
 | 
					 | 
				
			||||||
		
 | 
					 | 
				
			||||||
	case top && right:
 | 
					 | 
				
			||||||
		if width - x > y { side = 0 } else { side = 1 }
 | 
					 | 
				
			||||||
		
 | 
					 | 
				
			||||||
	case bottom && left:
 | 
					 | 
				
			||||||
		if x < height - y { side = 3 } else { side = 2 }
 | 
					 | 
				
			||||||
		
 | 
					 | 
				
			||||||
	case bottom && right:
 | 
					 | 
				
			||||||
		if width - x > height - y { side = 2 } else { side = 1 }
 | 
					 | 
				
			||||||
		
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return pattern[side].AtWhen(x, y, width, height)
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@ -1,111 +0,0 @@
 | 
				
			|||||||
package artist
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import "image"
 | 
					 | 
				
			||||||
import "image/color"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// Bordered is a pattern with a border and a fill.
 | 
					 | 
				
			||||||
type Bordered struct {
 | 
					 | 
				
			||||||
	Fill Pattern
 | 
					 | 
				
			||||||
	Stroke
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// AtWhen satisfies the Pattern interface.
 | 
					 | 
				
			||||||
func (pattern Bordered) AtWhen (x, y, width, height int) (c color.RGBA) {
 | 
					 | 
				
			||||||
	outerBounds := image.Rectangle { Max: image.Point { width, height }}
 | 
					 | 
				
			||||||
	innerBounds := outerBounds.Inset(pattern.Weight)
 | 
					 | 
				
			||||||
	if (image.Point { x, y }).In (innerBounds) {
 | 
					 | 
				
			||||||
		return pattern.Fill.AtWhen (
 | 
					 | 
				
			||||||
			x - pattern.Weight,
 | 
					 | 
				
			||||||
			y - pattern.Weight,
 | 
					 | 
				
			||||||
			innerBounds.Dx(), innerBounds.Dy())
 | 
					 | 
				
			||||||
	} else {
 | 
					 | 
				
			||||||
		return pattern.Stroke.AtWhen(x, y, width, height)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// Stroke represents a stoke that has a weight and a pattern.
 | 
					 | 
				
			||||||
type Stroke struct {
 | 
					 | 
				
			||||||
	Weight int
 | 
					 | 
				
			||||||
	Pattern
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
type borderInternal struct {
 | 
					 | 
				
			||||||
	weight int
 | 
					 | 
				
			||||||
	stroke Pattern
 | 
					 | 
				
			||||||
	bounds image.Rectangle
 | 
					 | 
				
			||||||
	dx, dy int
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// MultiBordered 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 MultiBordered struct {
 | 
					 | 
				
			||||||
	borders []borderInternal
 | 
					 | 
				
			||||||
	lastWidth, lastHeight int
 | 
					 | 
				
			||||||
	maxBorder int
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// NewMultiBordered creates a new MultiBordered pattern from the given list of
 | 
					 | 
				
			||||||
// borders.
 | 
					 | 
				
			||||||
func NewMultiBordered (borders ...Stroke) (multi *MultiBordered) {
 | 
					 | 
				
			||||||
	internalBorders := make([]borderInternal, len(borders))
 | 
					 | 
				
			||||||
	for index, border := range borders {
 | 
					 | 
				
			||||||
		internalBorders[index].weight = border.Weight
 | 
					 | 
				
			||||||
		internalBorders[index].stroke = border.Pattern
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	return &MultiBordered { borders: internalBorders }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// AtWhen satisfies the Pattern interface.
 | 
					 | 
				
			||||||
func (multi *MultiBordered) 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 *MultiBordered) 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 }
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// Padded is a pattern that surrounds a central fill pattern with a border that
 | 
					 | 
				
			||||||
// can have a different width for each side.
 | 
					 | 
				
			||||||
type Padded struct {
 | 
					 | 
				
			||||||
	Fill   Pattern
 | 
					 | 
				
			||||||
	Stroke Pattern
 | 
					 | 
				
			||||||
	Sides []int
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// AtWhen satisfies the Pattern interface.
 | 
					 | 
				
			||||||
func (pattern Padded) AtWhen (x, y, width, height int) (c color.RGBA) {
 | 
					 | 
				
			||||||
	innerBounds := image.Rect (
 | 
					 | 
				
			||||||
		pattern.Sides[3], pattern.Sides[0],
 | 
					 | 
				
			||||||
		width - pattern.Sides[1], height - pattern.Sides[2])
 | 
					 | 
				
			||||||
	if (image.Point { x, y }).In (innerBounds) {
 | 
					 | 
				
			||||||
		return pattern.Fill.AtWhen (
 | 
					 | 
				
			||||||
			x - pattern.Sides[3],
 | 
					 | 
				
			||||||
			y - pattern.Sides[0],
 | 
					 | 
				
			||||||
			innerBounds.Dx(), innerBounds.Dy())
 | 
					 | 
				
			||||||
	} else {
 | 
					 | 
				
			||||||
		return pattern.Stroke.AtWhen(x, y, width, height)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@ -1,51 +0,0 @@
 | 
				
			|||||||
package artist
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import "image/color"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// Checkered is a pattern that produces a grid of two alternating colors.
 | 
					 | 
				
			||||||
type Checkered struct {
 | 
					 | 
				
			||||||
	First  Pattern
 | 
					 | 
				
			||||||
	Second Pattern
 | 
					 | 
				
			||||||
	CellWidth, CellHeight int
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// AtWhen satisfies the Pattern interface.
 | 
					 | 
				
			||||||
func (pattern Checkered) AtWhen (x, y, width, height int) (c color.RGBA) {
 | 
					 | 
				
			||||||
	twidth  := pattern.CellWidth  * 2
 | 
					 | 
				
			||||||
	theight := pattern.CellHeight * 2
 | 
					 | 
				
			||||||
	x %= twidth
 | 
					 | 
				
			||||||
	y %= theight
 | 
					 | 
				
			||||||
	if x < 0 { x += twidth  }
 | 
					 | 
				
			||||||
	if y < 0 { x += theight }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	n := 0
 | 
					 | 
				
			||||||
	if x >= pattern.CellWidth  { n ++ }
 | 
					 | 
				
			||||||
	if y >= pattern.CellHeight { n ++ }
 | 
					 | 
				
			||||||
	
 | 
					 | 
				
			||||||
	x %= pattern.CellWidth
 | 
					 | 
				
			||||||
	y %= pattern.CellHeight
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if n % 2 == 0 {
 | 
					 | 
				
			||||||
		return pattern.First.AtWhen (
 | 
					 | 
				
			||||||
			x, y, pattern.CellWidth, pattern.CellHeight)
 | 
					 | 
				
			||||||
	} else {
 | 
					 | 
				
			||||||
		return pattern.Second.AtWhen (
 | 
					 | 
				
			||||||
			x, y, pattern.CellWidth, pattern.CellHeight)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// Tiled is a pattern that tiles another pattern accross a grid.
 | 
					 | 
				
			||||||
type Tiled struct {
 | 
					 | 
				
			||||||
	Pattern
 | 
					 | 
				
			||||||
	CellWidth, CellHeight int
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// AtWhen satisfies the Pattern interface.
 | 
					 | 
				
			||||||
func (pattern Tiled) AtWhen (x, y, width, height int) (c color.RGBA) {
 | 
					 | 
				
			||||||
	x %= pattern.CellWidth
 | 
					 | 
				
			||||||
	y %= pattern.CellHeight
 | 
					 | 
				
			||||||
	if x < 0 { x += pattern.CellWidth  }
 | 
					 | 
				
			||||||
	if y < 0 { y += pattern.CellHeight }
 | 
					 | 
				
			||||||
	return pattern.Pattern.AtWhen (
 | 
					 | 
				
			||||||
		x, y, pattern.CellWidth, pattern.CellHeight)
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@ -1,33 +0,0 @@
 | 
				
			|||||||
package artist
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import "math"
 | 
					 | 
				
			||||||
import "image/color"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// EllipticallyBordered is a pattern with a border and a fill that is elliptical
 | 
					 | 
				
			||||||
// in shape.
 | 
					 | 
				
			||||||
type EllipticallyBordered struct {
 | 
					 | 
				
			||||||
	Fill Pattern
 | 
					 | 
				
			||||||
	Stroke
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// AtWhen satisfies the Pattern interface.
 | 
					 | 
				
			||||||
func (pattern EllipticallyBordered) AtWhen (x, y, width, height int) (c color.RGBA) {
 | 
					 | 
				
			||||||
	xf := (float64(x) + 0.5) / float64(width ) * 2 - 1
 | 
					 | 
				
			||||||
	yf := (float64(y) + 0.5) / float64(height) * 2 - 1
 | 
					 | 
				
			||||||
	distance := math.Sqrt(xf * xf + yf * yf)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	var radius float64
 | 
					 | 
				
			||||||
	if width < height {
 | 
					 | 
				
			||||||
		// vertical
 | 
					 | 
				
			||||||
		radius = 1 - float64(pattern.Weight * 2) / float64(width)
 | 
					 | 
				
			||||||
	} else {
 | 
					 | 
				
			||||||
		// horizontal
 | 
					 | 
				
			||||||
		radius = 1 - float64(pattern.Weight * 2) / float64(height)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if distance < radius {
 | 
					 | 
				
			||||||
		return pattern.Fill.AtWhen(x, y, width, height)
 | 
					 | 
				
			||||||
	} else {
 | 
					 | 
				
			||||||
		return pattern.Stroke.AtWhen(x, y, width, height)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
							
								
								
									
										12
									
								
								artist/color.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								artist/color.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,12 @@
 | 
				
			|||||||
 | 
					package artist
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import "image/color"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Hex creates a color.RGBA value from an RGBA integer value.
 | 
				
			||||||
 | 
					func Hex (color uint32) (c color.RGBA) {
 | 
				
			||||||
 | 
						c.A = uint8(color)
 | 
				
			||||||
 | 
						c.B = uint8(color >>  8)
 | 
				
			||||||
 | 
						c.G = uint8(color >> 16)
 | 
				
			||||||
 | 
						c.R = uint8(color >> 24)
 | 
				
			||||||
 | 
						return
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -1,5 +1,2 @@
 | 
				
			|||||||
// Package artist provides a simple 2D drawing library for canvas.Canvas.
 | 
					// Package artist provides a simple 2D drawing library for canvas.Canvas.
 | 
				
			||||||
// Artist's drawing functions take in things called patterns, which are sampled
 | 
					 | 
				
			||||||
// as a source in order to color and texture drawn shapes. Patterns can be
 | 
					 | 
				
			||||||
// mixed together and composited to create new, more complex patterns.
 | 
					 | 
				
			||||||
package artist
 | 
					package artist
 | 
				
			||||||
 | 
				
			|||||||
@ -1,30 +0,0 @@
 | 
				
			|||||||
package artist
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import "math"
 | 
					 | 
				
			||||||
import "image/color"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// Dotted is a pattern that produces a grid of circles.
 | 
					 | 
				
			||||||
type Dotted struct {
 | 
					 | 
				
			||||||
	Background Pattern
 | 
					 | 
				
			||||||
	Foreground Pattern
 | 
					 | 
				
			||||||
	Size int
 | 
					 | 
				
			||||||
	Spacing int
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// AtWhen satisfies the Pattern interface.
 | 
					 | 
				
			||||||
func (pattern Dotted) AtWhen (x, y, width, height int) (c color.RGBA) {
 | 
					 | 
				
			||||||
	xm := x % pattern.Spacing
 | 
					 | 
				
			||||||
	ym := y % pattern.Spacing
 | 
					 | 
				
			||||||
	if xm < 0 { xm += pattern.Spacing }
 | 
					 | 
				
			||||||
	if ym < 0 { xm += pattern.Spacing }
 | 
					 | 
				
			||||||
	radius  := float64(pattern.Size) / 2
 | 
					 | 
				
			||||||
	spacing := float64(pattern.Spacing) / 2 - 0.5
 | 
					 | 
				
			||||||
	xf := float64(xm) - spacing
 | 
					 | 
				
			||||||
	yf := float64(ym) - spacing
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if math.Sqrt(xf * xf + yf * yf) > radius {
 | 
					 | 
				
			||||||
		return pattern.Background.AtWhen(x, y, width, height)
 | 
					 | 
				
			||||||
	} else {
 | 
					 | 
				
			||||||
		return pattern.Foreground.AtWhen(x, y, width, height)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@ -1,145 +0,0 @@
 | 
				
			|||||||
package artist
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import "math"
 | 
					 | 
				
			||||||
import "image"
 | 
					 | 
				
			||||||
import "image/color"
 | 
					 | 
				
			||||||
import "git.tebibyte.media/sashakoshka/tomo/canvas"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// FillEllipse draws a filled ellipse with the specified pattern.
 | 
					 | 
				
			||||||
func FillEllipse (
 | 
					 | 
				
			||||||
	destination canvas.Canvas,
 | 
					 | 
				
			||||||
	source Pattern,
 | 
					 | 
				
			||||||
	bounds image.Rectangle,
 | 
					 | 
				
			||||||
) (
 | 
					 | 
				
			||||||
	updatedRegion image.Rectangle,
 | 
					 | 
				
			||||||
) {
 | 
					 | 
				
			||||||
	bounds = bounds.Canon()
 | 
					 | 
				
			||||||
	data, stride := destination.Buffer()
 | 
					 | 
				
			||||||
	realWidth, realHeight := bounds.Dx(), bounds.Dy()
 | 
					 | 
				
			||||||
	bounds = bounds.Intersect(destination.Bounds()).Canon()
 | 
					 | 
				
			||||||
	if bounds.Empty() { return }
 | 
					 | 
				
			||||||
	updatedRegion = bounds
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	width, height := bounds.Dx(), bounds.Dy()
 | 
					 | 
				
			||||||
	for y := 0; y < height; y ++ {
 | 
					 | 
				
			||||||
	for x := 0; x < width;  x ++ {
 | 
					 | 
				
			||||||
		xf := (float64(x) + 0.5) / float64(realWidth)  - 0.5
 | 
					 | 
				
			||||||
		yf := (float64(y) + 0.5) / float64(realHeight) - 0.5
 | 
					 | 
				
			||||||
		if math.Sqrt(xf * xf + yf * yf) <= 0.5 {
 | 
					 | 
				
			||||||
			data[x + bounds.Min.X + (y + bounds.Min.Y) * stride] =
 | 
					 | 
				
			||||||
				source.AtWhen(x, y, realWidth, realHeight)
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}}
 | 
					 | 
				
			||||||
	return
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// StrokeEllipse draws the outline of an ellipse with the specified line weight
 | 
					 | 
				
			||||||
// and pattern.
 | 
					 | 
				
			||||||
func StrokeEllipse (
 | 
					 | 
				
			||||||
	destination canvas.Canvas,
 | 
					 | 
				
			||||||
	source Pattern,
 | 
					 | 
				
			||||||
	weight int,
 | 
					 | 
				
			||||||
	bounds image.Rectangle,
 | 
					 | 
				
			||||||
) {
 | 
					 | 
				
			||||||
	if weight < 1 { return }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	data, stride := destination.Buffer()
 | 
					 | 
				
			||||||
	bounds = bounds.Canon().Inset(weight - 1)
 | 
					 | 
				
			||||||
	width, height := bounds.Dx(), bounds.Dy()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	context := ellipsePlottingContext {
 | 
					 | 
				
			||||||
		data: data,
 | 
					 | 
				
			||||||
		stride: stride,
 | 
					 | 
				
			||||||
		source: source,
 | 
					 | 
				
			||||||
		width: width,
 | 
					 | 
				
			||||||
		height: height,
 | 
					 | 
				
			||||||
		weight: weight,
 | 
					 | 
				
			||||||
		bounds: bounds,
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	
 | 
					 | 
				
			||||||
	bounds.Max.X -= 1
 | 
					 | 
				
			||||||
	bounds.Max.Y -= 1
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	radii := image.Pt (
 | 
					 | 
				
			||||||
		bounds.Dx() / 2,
 | 
					 | 
				
			||||||
		bounds.Dy() / 2)
 | 
					 | 
				
			||||||
	center := bounds.Min.Add(radii)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	x := float64(0)
 | 
					 | 
				
			||||||
	y := float64(radii.Y)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// region 1 decision parameter
 | 
					 | 
				
			||||||
	decision1 :=
 | 
					 | 
				
			||||||
		float64(radii.Y * radii.Y) -
 | 
					 | 
				
			||||||
		float64(radii.X * radii.X * radii.Y) +
 | 
					 | 
				
			||||||
		(0.25 * float64(radii.X) * float64(radii.X))
 | 
					 | 
				
			||||||
	decisionX := float64(2 * radii.Y * radii.Y * int(x))
 | 
					 | 
				
			||||||
	decisionY := float64(2 * radii.X * radii.X * int(y))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// draw region 1
 | 
					 | 
				
			||||||
	for decisionX < decisionY {
 | 
					 | 
				
			||||||
		context.plot( int(x) + center.X,  int(y) + center.Y)
 | 
					 | 
				
			||||||
		context.plot(-int(x) + center.X,  int(y) + center.Y)
 | 
					 | 
				
			||||||
		context.plot( int(x) + center.X, -int(y) + center.Y)
 | 
					 | 
				
			||||||
		context.plot(-int(x) + center.X, -int(y) + center.Y)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		if (decision1 < 0) {
 | 
					 | 
				
			||||||
			x ++
 | 
					 | 
				
			||||||
			decisionX += float64(2 * radii.Y * radii.Y)
 | 
					 | 
				
			||||||
			decision1 += decisionX + float64(radii.Y * radii.Y)
 | 
					 | 
				
			||||||
		} else {
 | 
					 | 
				
			||||||
			x ++
 | 
					 | 
				
			||||||
			y --
 | 
					 | 
				
			||||||
			decisionX += float64(2 * radii.Y * radii.Y)
 | 
					 | 
				
			||||||
			decisionY -= float64(2 * radii.X * radii.X)
 | 
					 | 
				
			||||||
			decision1 +=
 | 
					 | 
				
			||||||
				decisionX - decisionY +
 | 
					 | 
				
			||||||
				float64(radii.Y * radii.Y)
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// region 2 decision parameter
 | 
					 | 
				
			||||||
	decision2 :=
 | 
					 | 
				
			||||||
		float64(radii.Y * radii.Y) * (x + 0.5) * (x + 0.5) +
 | 
					 | 
				
			||||||
		float64(radii.X * radii.X) * (y - 1)   * (y - 1) -
 | 
					 | 
				
			||||||
		float64(radii.X * radii.X * radii.Y * radii.Y)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// draw region 2
 | 
					 | 
				
			||||||
	for y >= 0 {
 | 
					 | 
				
			||||||
		context.plot( int(x) + center.X,  int(y) + center.Y)
 | 
					 | 
				
			||||||
		context.plot(-int(x) + center.X,  int(y) + center.Y)
 | 
					 | 
				
			||||||
		context.plot( int(x) + center.X, -int(y) + center.Y)
 | 
					 | 
				
			||||||
		context.plot(-int(x) + center.X, -int(y) + center.Y)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		if decision2 > 0 {
 | 
					 | 
				
			||||||
			y --
 | 
					 | 
				
			||||||
			decisionY -= float64(2 * radii.X * radii.X)
 | 
					 | 
				
			||||||
			decision2 += float64(radii.X * radii.X) - decisionY
 | 
					 | 
				
			||||||
		} else {
 | 
					 | 
				
			||||||
			y --
 | 
					 | 
				
			||||||
			x ++
 | 
					 | 
				
			||||||
			decisionX += float64(2 * radii.Y * radii.Y)
 | 
					 | 
				
			||||||
			decisionY -= float64(2 * radii.X * radii.X)
 | 
					 | 
				
			||||||
			decision2 +=
 | 
					 | 
				
			||||||
				decisionX - decisionY +
 | 
					 | 
				
			||||||
				float64(radii.X * radii.X)
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
type ellipsePlottingContext struct {
 | 
					 | 
				
			||||||
	data []color.RGBA
 | 
					 | 
				
			||||||
	stride int
 | 
					 | 
				
			||||||
	source Pattern
 | 
					 | 
				
			||||||
	width, height int
 | 
					 | 
				
			||||||
	weight int
 | 
					 | 
				
			||||||
	bounds image.Rectangle
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func (context ellipsePlottingContext) plot (x, y int) {
 | 
					 | 
				
			||||||
	if (image.Point { x, y }).In(context.bounds) {
 | 
					 | 
				
			||||||
		squareAround (
 | 
					 | 
				
			||||||
			context.data, context.stride, context.source, x, y,
 | 
					 | 
				
			||||||
			context.width, context.height, context.weight)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@ -1,45 +0,0 @@
 | 
				
			|||||||
package artist
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import "image/color"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// Gradient is a pattern that interpolates between two colors.
 | 
					 | 
				
			||||||
type Gradient struct {
 | 
					 | 
				
			||||||
	First  Pattern
 | 
					 | 
				
			||||||
	Second Pattern
 | 
					 | 
				
			||||||
	Orientation
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// AtWhen satisfies the Pattern interface.
 | 
					 | 
				
			||||||
func (pattern Gradient) AtWhen (x, y, width, height int) (c color.RGBA) {
 | 
					 | 
				
			||||||
	var position float64
 | 
					 | 
				
			||||||
	switch pattern.Orientation {
 | 
					 | 
				
			||||||
	case OrientationVertical:
 | 
					 | 
				
			||||||
		position = float64(y) / float64(height)
 | 
					 | 
				
			||||||
	case OrientationDiagonalRight:
 | 
					 | 
				
			||||||
		position = (float64(width - x) / float64(width) +
 | 
					 | 
				
			||||||
			float64(y) / float64(height)) / 2
 | 
					 | 
				
			||||||
	case OrientationHorizontal:
 | 
					 | 
				
			||||||
		position = float64(x) / float64(width)
 | 
					 | 
				
			||||||
	case OrientationDiagonalLeft:
 | 
					 | 
				
			||||||
		position = (float64(x) / float64(width) +
 | 
					 | 
				
			||||||
			float64(y) / float64(height)) / 2
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	firstColor  := pattern.First.AtWhen(x, y, width, height)
 | 
					 | 
				
			||||||
	secondColor := pattern.Second.AtWhen(x, y, width, height)
 | 
					 | 
				
			||||||
	return LerpRGBA(firstColor, secondColor, position)
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// Lerp linearally interpolates between two integer values.
 | 
					 | 
				
			||||||
func Lerp (first, second int, fac float64) (n int) {
 | 
					 | 
				
			||||||
	return int(float64(first) * (1 - fac) + float64(second) * fac)
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// LerpRGBA linearally interpolates between two color.RGBA values.
 | 
					 | 
				
			||||||
func LerpRGBA (first, second color.RGBA, fac float64) (c color.RGBA) {
 | 
					 | 
				
			||||||
	return color.RGBA {
 | 
					 | 
				
			||||||
		R: uint8(Lerp(int(first.R), int(second.R), fac)),
 | 
					 | 
				
			||||||
		G: uint8(Lerp(int(first.G), int(second.G), fac)),
 | 
					 | 
				
			||||||
		B: uint8(Lerp(int(first.G), int(second.B), fac)),
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@ -1,7 +1,15 @@
 | 
				
			|||||||
package theme
 | 
					package artist
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import "image"
 | 
					import "image"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Side represents one side of a rectangle.
 | 
				
			||||||
 | 
					type Side int; const (
 | 
				
			||||||
 | 
						SideTop Side = iota
 | 
				
			||||||
 | 
						SideRight
 | 
				
			||||||
 | 
						SideBottom
 | 
				
			||||||
 | 
						SideLeft
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Inset represents an inset amount for all four sides of a rectangle. The top
 | 
					// Inset represents an inset amount for all four sides of a rectangle. The top
 | 
				
			||||||
// side is at index zero, the right at index one, the bottom at index two, and
 | 
					// side is at index zero, the right at index one, the bottom at index two, and
 | 
				
			||||||
// the left at index three. These values may be negative.
 | 
					// the left at index three. These values may be negative.
 | 
				
			||||||
@ -40,3 +48,13 @@ func (inset Inset) Inverse () (prime Inset) {
 | 
				
			|||||||
		inset[3] * -1,
 | 
							inset[3] * -1,
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Horizontal returns the sum of SideRight and SideLeft.
 | 
				
			||||||
 | 
					func (inset Inset) Horizontal () int {
 | 
				
			||||||
 | 
						return inset[SideRight] + inset[SideLeft]
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Vertical returns the sum of SideTop and SideBottom.
 | 
				
			||||||
 | 
					func (inset Inset) Vertical () int {
 | 
				
			||||||
 | 
						return inset[SideTop] + inset[SideBottom]
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										143
									
								
								artist/line.go
									
									
									
									
									
								
							
							
						
						
									
										143
									
								
								artist/line.go
									
									
									
									
									
								
							@ -1,143 +0,0 @@
 | 
				
			|||||||
package artist
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import "image"
 | 
					 | 
				
			||||||
import "image/color"
 | 
					 | 
				
			||||||
import "git.tebibyte.media/sashakoshka/tomo/canvas"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// TODO: draw thick lines more efficiently
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// Line draws a line from one point to another with the specified weight and
 | 
					 | 
				
			||||||
// pattern.
 | 
					 | 
				
			||||||
func Line (
 | 
					 | 
				
			||||||
	destination canvas.Canvas,
 | 
					 | 
				
			||||||
	source Pattern,
 | 
					 | 
				
			||||||
	weight int,
 | 
					 | 
				
			||||||
	min image.Point,
 | 
					 | 
				
			||||||
	max image.Point,
 | 
					 | 
				
			||||||
) (
 | 
					 | 
				
			||||||
	updatedRegion image.Rectangle,
 | 
					 | 
				
			||||||
) {
 | 
					 | 
				
			||||||
	
 | 
					 | 
				
			||||||
	updatedRegion = image.Rectangle { Min: min, Max: max }.Canon()
 | 
					 | 
				
			||||||
	updatedRegion.Max.X ++
 | 
					 | 
				
			||||||
	updatedRegion.Max.Y ++
 | 
					 | 
				
			||||||
	width  := updatedRegion.Dx()
 | 
					 | 
				
			||||||
	height := updatedRegion.Dy()
 | 
					 | 
				
			||||||
	
 | 
					 | 
				
			||||||
	if abs(max.Y - min.Y) <
 | 
					 | 
				
			||||||
		abs(max.X - min.X) {
 | 
					 | 
				
			||||||
		
 | 
					 | 
				
			||||||
		if max.X < min.X {
 | 
					 | 
				
			||||||
			temp := min
 | 
					 | 
				
			||||||
			min = max
 | 
					 | 
				
			||||||
			max = temp
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		lineLow(destination, source, weight, min, max, width, height)
 | 
					 | 
				
			||||||
	} else {
 | 
					 | 
				
			||||||
	
 | 
					 | 
				
			||||||
		if max.Y < min.Y {
 | 
					 | 
				
			||||||
			temp := min
 | 
					 | 
				
			||||||
			min = max
 | 
					 | 
				
			||||||
			max = temp
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		lineHigh(destination, source, weight, min, max, width, height)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	return
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func lineLow (
 | 
					 | 
				
			||||||
	destination canvas.Canvas,
 | 
					 | 
				
			||||||
	source Pattern,
 | 
					 | 
				
			||||||
	weight int,
 | 
					 | 
				
			||||||
	min image.Point,
 | 
					 | 
				
			||||||
	max image.Point,
 | 
					 | 
				
			||||||
	width, height int,
 | 
					 | 
				
			||||||
) {
 | 
					 | 
				
			||||||
	data, stride := destination.Buffer()
 | 
					 | 
				
			||||||
	bounds := destination.Bounds()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	deltaX := max.X - min.X
 | 
					 | 
				
			||||||
	deltaY := max.Y - min.Y
 | 
					 | 
				
			||||||
	yi     := 1
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if deltaY < 0 {
 | 
					 | 
				
			||||||
		yi      = -1
 | 
					 | 
				
			||||||
		deltaY *= -1
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	D := (2 * deltaY) - deltaX
 | 
					 | 
				
			||||||
	y := min.Y
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	for x := min.X; x < max.X; x ++ {
 | 
					 | 
				
			||||||
		if !(image.Point { x, y }).In(bounds) { break }
 | 
					 | 
				
			||||||
		squareAround(data, stride, source, x, y, width, height, weight)
 | 
					 | 
				
			||||||
		// data[x + y * stride] = source.AtWhen(x, y, width, height)
 | 
					 | 
				
			||||||
		if D > 0 {
 | 
					 | 
				
			||||||
			y += yi
 | 
					 | 
				
			||||||
			D += 2 * (deltaY - deltaX)
 | 
					 | 
				
			||||||
		} else {
 | 
					 | 
				
			||||||
			D += 2 * deltaY
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func lineHigh (
 | 
					 | 
				
			||||||
	destination canvas.Canvas,
 | 
					 | 
				
			||||||
	source Pattern,
 | 
					 | 
				
			||||||
	weight int,
 | 
					 | 
				
			||||||
	min image.Point,
 | 
					 | 
				
			||||||
	max image.Point,
 | 
					 | 
				
			||||||
	width, height int,
 | 
					 | 
				
			||||||
) {
 | 
					 | 
				
			||||||
	data, stride := destination.Buffer()
 | 
					 | 
				
			||||||
	bounds := destination.Bounds()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	deltaX := max.X - min.X
 | 
					 | 
				
			||||||
	deltaY := max.Y - min.Y
 | 
					 | 
				
			||||||
	xi     := 1
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if deltaX < 0 {
 | 
					 | 
				
			||||||
		xi      = -1
 | 
					 | 
				
			||||||
		deltaX *= -1
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	D := (2 * deltaX) - deltaY
 | 
					 | 
				
			||||||
	x := min.X
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	for y := min.Y; y < max.Y; y ++ {
 | 
					 | 
				
			||||||
		if !(image.Point { x, y }).In(bounds) { break }
 | 
					 | 
				
			||||||
		squareAround(data, stride, source, x, y, width, height, weight)
 | 
					 | 
				
			||||||
		// data[x + y * stride] = source.AtWhen(x, y, width, height)
 | 
					 | 
				
			||||||
		if D > 0 {
 | 
					 | 
				
			||||||
			x += xi
 | 
					 | 
				
			||||||
			D += 2 * (deltaX - deltaY)
 | 
					 | 
				
			||||||
		} else {
 | 
					 | 
				
			||||||
			D += 2 * deltaX
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func abs (in int) (out int) {
 | 
					 | 
				
			||||||
	if in < 0 { in *= -1}
 | 
					 | 
				
			||||||
	out = in
 | 
					 | 
				
			||||||
	return
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// TODO: this method of doing things sucks and can cause a segfault. we should
 | 
					 | 
				
			||||||
// not be doing it this way
 | 
					 | 
				
			||||||
func squareAround (
 | 
					 | 
				
			||||||
	data   []color.RGBA,
 | 
					 | 
				
			||||||
	stride int,
 | 
					 | 
				
			||||||
	source Pattern,
 | 
					 | 
				
			||||||
	x, y, patternWidth, patternHeight, diameter int,
 | 
					 | 
				
			||||||
) {
 | 
					 | 
				
			||||||
	minY := y - diameter + 1
 | 
					 | 
				
			||||||
	minX := x - diameter + 1
 | 
					 | 
				
			||||||
	maxY := y + diameter
 | 
					 | 
				
			||||||
	maxX := x + diameter
 | 
					 | 
				
			||||||
	for y = minY; y < maxY; y ++ {
 | 
					 | 
				
			||||||
	for x = minX; x < maxX; x ++ {
 | 
					 | 
				
			||||||
		data[x + y * stride] =
 | 
					 | 
				
			||||||
			source.AtWhen(x, y, patternWidth, patternHeight)
 | 
					 | 
				
			||||||
	}}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@ -1,33 +0,0 @@
 | 
				
			|||||||
package artist
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import "image/color"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// Noisy is a pattern that randomly interpolates between two patterns in a
 | 
					 | 
				
			||||||
// deterministic fashion.
 | 
					 | 
				
			||||||
type Noisy struct {
 | 
					 | 
				
			||||||
	Low   Pattern
 | 
					 | 
				
			||||||
	High  Pattern
 | 
					 | 
				
			||||||
	Seed  uint32
 | 
					 | 
				
			||||||
	Harsh bool
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// AtWhen satisfies the pattern interface.
 | 
					 | 
				
			||||||
func (pattern Noisy) AtWhen (x, y, width, height int) (c color.RGBA) {
 | 
					 | 
				
			||||||
	// FIXME: this will occasionally generate "clumps"
 | 
					 | 
				
			||||||
	special := uint32(x + y * 348905)
 | 
					 | 
				
			||||||
	special += (pattern.Seed + 1) * 15485863
 | 
					 | 
				
			||||||
	random := (special * special * special % 2038074743)
 | 
					 | 
				
			||||||
	fac := float64(random) / 2038074743.0
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if pattern.Harsh {
 | 
					 | 
				
			||||||
		if fac > 0.5 {
 | 
					 | 
				
			||||||
			return pattern.High.AtWhen(x, y, width, height)
 | 
					 | 
				
			||||||
		} else {
 | 
					 | 
				
			||||||
			return pattern.Low.AtWhen(x, y, width, height)
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	} else {
 | 
					 | 
				
			||||||
		return LerpRGBA (
 | 
					 | 
				
			||||||
			pattern.Low.AtWhen(x, y, width, height),
 | 
					 | 
				
			||||||
			pattern.High.AtWhen(x, y, width, height), fac)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@ -1,12 +1,67 @@
 | 
				
			|||||||
package artist
 | 
					package artist
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import "image/color"
 | 
					import "image"
 | 
				
			||||||
 | 
					import "git.tebibyte.media/sashakoshka/tomo/canvas"
 | 
				
			||||||
 | 
					import "git.tebibyte.media/sashakoshka/tomo/shatter"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Pattern is capable of generating a pattern pixel by pixel.
 | 
					// Pattern is capable of drawing to a canvas within the bounds of a given
 | 
				
			||||||
 | 
					// clipping rectangle.
 | 
				
			||||||
type Pattern interface {
 | 
					type Pattern interface {
 | 
				
			||||||
	// AtWhen returns the color of the pixel located at (x, y) relative to
 | 
						// Draw draws to destination, using the bounds of destination as a width
 | 
				
			||||||
	// the origin point of the pattern (0, 0), when the pattern has the
 | 
						// and height for things like gradients, bevels, etc. The pattern may
 | 
				
			||||||
	// specified width and height. Patterns may ignore the width and height
 | 
						// not draw outside the union of destination.Bounds() and clip. The
 | 
				
			||||||
	// parameters, but it may be useful for some patterns such as gradients.
 | 
						// clipping rectangle effectively takes a subset of the pattern. To
 | 
				
			||||||
	AtWhen (x, y, width, height int) (color.RGBA)
 | 
						// change the bounds of the pattern itself, use canvas.Cut() on the
 | 
				
			||||||
 | 
						// destination before passing it to Draw().
 | 
				
			||||||
 | 
						Draw (destination canvas.Canvas, clip image.Rectangle)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Draw lets you use several clipping rectangles to draw a pattern.
 | 
				
			||||||
 | 
					func Draw (
 | 
				
			||||||
 | 
						destination canvas.Canvas,
 | 
				
			||||||
 | 
						source      Pattern,
 | 
				
			||||||
 | 
						clips       ...image.Rectangle,
 | 
				
			||||||
 | 
					) (
 | 
				
			||||||
 | 
						updatedRegion image.Rectangle,
 | 
				
			||||||
 | 
					) {
 | 
				
			||||||
 | 
						for _, clip := range clips {
 | 
				
			||||||
 | 
							source.Draw(destination, clip)
 | 
				
			||||||
 | 
							updatedRegion = updatedRegion.Union(clip)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// DrawBounds lets you specify an overall bounding rectangle for drawing a
 | 
				
			||||||
 | 
					// pattern. The destination is cut to this rectangle.
 | 
				
			||||||
 | 
					func DrawBounds (
 | 
				
			||||||
 | 
						destination canvas.Canvas,
 | 
				
			||||||
 | 
						source      Pattern,
 | 
				
			||||||
 | 
						bounds      image.Rectangle,
 | 
				
			||||||
 | 
					) (
 | 
				
			||||||
 | 
						updatedRegion image.Rectangle,
 | 
				
			||||||
 | 
					) {
 | 
				
			||||||
 | 
						return Draw(canvas.Cut(destination, bounds), source, bounds)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// DrawShatter is like an inverse of Draw, drawing nothing in the areas
 | 
				
			||||||
 | 
					// specified in "rocks".
 | 
				
			||||||
 | 
					func DrawShatter (
 | 
				
			||||||
 | 
						destination canvas.Canvas,
 | 
				
			||||||
 | 
						source      Pattern,
 | 
				
			||||||
 | 
						rocks       ...image.Rectangle,
 | 
				
			||||||
 | 
					) (
 | 
				
			||||||
 | 
						updatedRegion image.Rectangle,
 | 
				
			||||||
 | 
					) {
 | 
				
			||||||
 | 
						tiles := shatter.Shatter(destination.Bounds(), rocks...)
 | 
				
			||||||
 | 
						return Draw(destination, source, tiles...)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// AllocateSample returns a new canvas containing the result of a pattern. The
 | 
				
			||||||
 | 
					// resulting canvas can be sourced from shape drawing functions. I beg of you
 | 
				
			||||||
 | 
					// please do not call this every time you need to draw a shape with a pattern on
 | 
				
			||||||
 | 
					// it because that is horrible and cruel to the computer.
 | 
				
			||||||
 | 
					func AllocateSample (source Pattern, width, height int) (allocated canvas.Canvas) {
 | 
				
			||||||
 | 
						allocated = canvas.NewBasicCanvas(width, height)
 | 
				
			||||||
 | 
						source.Draw(allocated, allocated.Bounds())
 | 
				
			||||||
 | 
						return
 | 
				
			||||||
} 
 | 
					} 
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										90
									
								
								artist/patterns/border.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										90
									
								
								artist/patterns/border.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,90 @@
 | 
				
			|||||||
 | 
					package patterns
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import "image"
 | 
				
			||||||
 | 
					import "git.tebibyte.media/sashakoshka/tomo/canvas"
 | 
				
			||||||
 | 
					import "git.tebibyte.media/sashakoshka/tomo/artist"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Border is a pattern that behaves similarly to border-image in CSS. It divides
 | 
				
			||||||
 | 
					// a source canvas into nine sections...
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					//                         Inset[1]
 | 
				
			||||||
 | 
					//                         ┌──┴──┐
 | 
				
			||||||
 | 
					//           ┌─┌─────┬─────┬─────┐
 | 
				
			||||||
 | 
					//  Inset[0]─┤ │  0  │  1  │  2  │
 | 
				
			||||||
 | 
					//           └─├─────┼─────┼─────┤
 | 
				
			||||||
 | 
					//             │  3  │  4  │  5  │
 | 
				
			||||||
 | 
					//             ├─────┼─────┼─────┤─┐
 | 
				
			||||||
 | 
					//             │  6  │  7  │  8  │ ├─Inset[2]
 | 
				
			||||||
 | 
					//             └─────┴─────┴─────┘─┘
 | 
				
			||||||
 | 
					//             └──┬──┘
 | 
				
			||||||
 | 
					//             Inset[3]
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// ... Where the bounds of section 4 are defined as the application of the
 | 
				
			||||||
 | 
					// pattern's inset to the canvas's bounds. The bounds of the other eight
 | 
				
			||||||
 | 
					// sections are automatically sized around it.
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// When drawn to a destination canvas, the bounds of sections 1, 3, 4, 5, and 7
 | 
				
			||||||
 | 
					// are expanded or contracted to fit the destination's bounds. All sections
 | 
				
			||||||
 | 
					// are rendered as if they are Texture patterns, meaning these flexible sections
 | 
				
			||||||
 | 
					// will repeat to fill in any empty space.
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// This pattern can be used to make a static image texture into something that
 | 
				
			||||||
 | 
					// responds well to being resized.
 | 
				
			||||||
 | 
					type Border struct {
 | 
				
			||||||
 | 
						canvas.Canvas
 | 
				
			||||||
 | 
						artist.Inset
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Draw draws the border pattern onto the destination canvas within the clipping
 | 
				
			||||||
 | 
					// bounds.
 | 
				
			||||||
 | 
					func (pattern Border) Draw (destination canvas.Canvas, clip image.Rectangle) {
 | 
				
			||||||
 | 
						bounds := clip.Canon().Intersect(destination.Bounds())
 | 
				
			||||||
 | 
						if bounds.Empty() { return }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						srcSections := nonasect(pattern.Bounds(), pattern.Inset)
 | 
				
			||||||
 | 
						srcTextures := [9]Texture { }
 | 
				
			||||||
 | 
						for index, section := range srcSections {
 | 
				
			||||||
 | 
							srcTextures[index].Canvas = canvas.Cut(pattern, section)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						dstSections := nonasect(destination.Bounds(), pattern.Inset)
 | 
				
			||||||
 | 
						for index, section := range dstSections {
 | 
				
			||||||
 | 
							srcTextures[index].Draw(canvas.Cut(destination, section), clip)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func nonasect (bounds image.Rectangle, inset artist.Inset) [9]image.Rectangle {
 | 
				
			||||||
 | 
						center := inset.Apply(bounds)
 | 
				
			||||||
 | 
						return [9]image.Rectangle {
 | 
				
			||||||
 | 
							// top
 | 
				
			||||||
 | 
							image.Rectangle {
 | 
				
			||||||
 | 
								bounds.Min,
 | 
				
			||||||
 | 
								center.Min },
 | 
				
			||||||
 | 
							image.Rect (
 | 
				
			||||||
 | 
								center.Min.X, bounds.Min.Y,
 | 
				
			||||||
 | 
								center.Max.X, center.Min.Y),
 | 
				
			||||||
 | 
							image.Rect (
 | 
				
			||||||
 | 
								center.Max.X, bounds.Min.Y,
 | 
				
			||||||
 | 
								bounds.Max.X, center.Min.Y),
 | 
				
			||||||
 | 
								
 | 
				
			||||||
 | 
							// center
 | 
				
			||||||
 | 
							image.Rect (
 | 
				
			||||||
 | 
								bounds.Min.X, center.Min.Y,
 | 
				
			||||||
 | 
								center.Min.X, center.Max.Y),
 | 
				
			||||||
 | 
							center,
 | 
				
			||||||
 | 
							image.Rect (
 | 
				
			||||||
 | 
								center.Max.X, center.Min.Y,
 | 
				
			||||||
 | 
								bounds.Max.X, center.Max.Y),
 | 
				
			||||||
 | 
								
 | 
				
			||||||
 | 
							// bottom
 | 
				
			||||||
 | 
							image.Rect (
 | 
				
			||||||
 | 
								bounds.Min.X, center.Max.Y,
 | 
				
			||||||
 | 
								center.Min.X, bounds.Max.Y),
 | 
				
			||||||
 | 
							image.Rect (
 | 
				
			||||||
 | 
								center.Min.X, center.Max.Y,
 | 
				
			||||||
 | 
								center.Max.X, bounds.Max.Y),
 | 
				
			||||||
 | 
							image.Rect (
 | 
				
			||||||
 | 
								center.Max.X, center.Max.Y,
 | 
				
			||||||
 | 
								bounds.Max.X, bounds.Max.Y),
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										3
									
								
								artist/patterns/doc.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								artist/patterns/doc.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,3 @@
 | 
				
			|||||||
 | 
					// Package patterns provides a basic set of types that satisfy the
 | 
				
			||||||
 | 
					// artist.Pattern interface.
 | 
				
			||||||
 | 
					package patterns
 | 
				
			||||||
							
								
								
									
										41
									
								
								artist/patterns/texture.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								artist/patterns/texture.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,41 @@
 | 
				
			|||||||
 | 
					package patterns
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import "image"
 | 
				
			||||||
 | 
					import "git.tebibyte.media/sashakoshka/tomo/canvas"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Texture is a pattern that tiles the content of a canvas both horizontally and
 | 
				
			||||||
 | 
					// vertically.
 | 
				
			||||||
 | 
					type Texture struct {
 | 
				
			||||||
 | 
						canvas.Canvas
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Draw tiles the pattern's canvas within the clipping bounds. The minimum
 | 
				
			||||||
 | 
					// points of the pattern's canvas and the destination canvas will be lined up.
 | 
				
			||||||
 | 
					func (pattern Texture) Draw (destination canvas.Canvas, clip image.Rectangle) {
 | 
				
			||||||
 | 
						realBounds := destination.Bounds()
 | 
				
			||||||
 | 
						bounds := clip.Canon().Intersect(realBounds)
 | 
				
			||||||
 | 
						if bounds.Empty() { return }
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						dstData, dstStride := destination.Buffer()
 | 
				
			||||||
 | 
						srcData, srcStride := pattern.Buffer()
 | 
				
			||||||
 | 
						srcBounds := pattern.Bounds()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						point := image.Point { }
 | 
				
			||||||
 | 
						for point.Y = bounds.Min.Y; point.Y < bounds.Max.Y; point.Y ++ {
 | 
				
			||||||
 | 
						for point.X = bounds.Min.X; point.X < bounds.Max.X; point.X ++ {
 | 
				
			||||||
 | 
							srcPoint := point.Sub(realBounds.Min).Add(srcBounds.Min)
 | 
				
			||||||
 | 
							
 | 
				
			||||||
 | 
							dstIndex := point.X + point.Y * dstStride
 | 
				
			||||||
 | 
							srcIndex :=
 | 
				
			||||||
 | 
								wrap(srcPoint.X, srcBounds.Min.X, srcBounds.Max.X) +
 | 
				
			||||||
 | 
								wrap(srcPoint.Y, srcBounds.Min.Y, srcBounds.Max.Y) * srcStride
 | 
				
			||||||
 | 
							dstData[dstIndex] = srcData[srcIndex]
 | 
				
			||||||
 | 
						}}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func wrap (value, min, max int) int {
 | 
				
			||||||
 | 
						difference := max - min
 | 
				
			||||||
 | 
						value = (value - min) % difference
 | 
				
			||||||
 | 
						if value < 0 { value += difference }
 | 
				
			||||||
 | 
						return value + min
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										20
									
								
								artist/patterns/uniform.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								artist/patterns/uniform.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,20 @@
 | 
				
			|||||||
 | 
					package patterns
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import "image"
 | 
				
			||||||
 | 
					import "image/color"
 | 
				
			||||||
 | 
					import "git.tebibyte.media/sashakoshka/tomo/canvas"
 | 
				
			||||||
 | 
					import "git.tebibyte.media/sashakoshka/tomo/artist"
 | 
				
			||||||
 | 
					import "git.tebibyte.media/sashakoshka/tomo/artist/shapes"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Uniform is a pattern that draws a solid color.
 | 
				
			||||||
 | 
					type Uniform color.RGBA
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Draw fills the clipping rectangle with the pattern's color.
 | 
				
			||||||
 | 
					func (pattern Uniform) Draw (destination canvas.Canvas, clip image.Rectangle) {
 | 
				
			||||||
 | 
						shapes.FillColorRectangle(destination, color.RGBA(pattern), clip)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Uhex creates a new Uniform pattern from an RGBA integer value.
 | 
				
			||||||
 | 
					func Uhex (color uint32) (uniform Uniform) {
 | 
				
			||||||
 | 
						return Uniform(artist.Hex(color))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -1,130 +0,0 @@
 | 
				
			|||||||
package artist
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import "image"
 | 
					 | 
				
			||||||
import "git.tebibyte.media/sashakoshka/tomo/canvas"
 | 
					 | 
				
			||||||
import "git.tebibyte.media/sashakoshka/tomo/shatter"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// Paste transfers one canvas onto another, offset by the specified point.
 | 
					 | 
				
			||||||
func Paste (
 | 
					 | 
				
			||||||
	destination canvas.Canvas,
 | 
					 | 
				
			||||||
	source canvas.Canvas,
 | 
					 | 
				
			||||||
	offset image.Point,
 | 
					 | 
				
			||||||
) (
 | 
					 | 
				
			||||||
	updatedRegion image.Rectangle,
 | 
					 | 
				
			||||||
) {
 | 
					 | 
				
			||||||
	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)
 | 
					 | 
				
			||||||
	for y := sourceBounds.Min.Y; y < sourceBounds.Max.Y; y ++ {
 | 
					 | 
				
			||||||
	for x := sourceBounds.Min.X; x < sourceBounds.Max.X; x ++ {
 | 
					 | 
				
			||||||
		dstData[x + offset.X + (y + offset.Y) * dstStride] =
 | 
					 | 
				
			||||||
			srcData[x + y * srcStride]
 | 
					 | 
				
			||||||
	}}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// FillRectangle draws a filled rectangle with the specified pattern.
 | 
					 | 
				
			||||||
func FillRectangle (
 | 
					 | 
				
			||||||
	destination canvas.Canvas,
 | 
					 | 
				
			||||||
	source Pattern,
 | 
					 | 
				
			||||||
	bounds image.Rectangle,
 | 
					 | 
				
			||||||
) (
 | 
					 | 
				
			||||||
	updatedRegion image.Rectangle,
 | 
					 | 
				
			||||||
) {
 | 
					 | 
				
			||||||
	return FillRectangleClip(destination, source, bounds, bounds)
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// FillRectangleClip is similar to FillRectangle, but it clips the pattern to
 | 
					 | 
				
			||||||
// a specified rectangle mask. That is—the pattern will be queried as if it
 | 
					 | 
				
			||||||
// were drawn without the mask, but only the area specified by the intersection
 | 
					 | 
				
			||||||
// of bounds and mask will be drawn to.
 | 
					 | 
				
			||||||
func FillRectangleClip (
 | 
					 | 
				
			||||||
	destination canvas.Canvas,
 | 
					 | 
				
			||||||
	source Pattern,
 | 
					 | 
				
			||||||
	bounds image.Rectangle,
 | 
					 | 
				
			||||||
	mask   image.Rectangle,
 | 
					 | 
				
			||||||
) (
 | 
					 | 
				
			||||||
	updatedRegion image.Rectangle,
 | 
					 | 
				
			||||||
) {
 | 
					 | 
				
			||||||
	data, stride := destination.Buffer()
 | 
					 | 
				
			||||||
	realBounds := bounds
 | 
					 | 
				
			||||||
	bounds =
 | 
					 | 
				
			||||||
		bounds.Canon().
 | 
					 | 
				
			||||||
		Intersect(mask.Canon()).
 | 
					 | 
				
			||||||
		Intersect(destination.Bounds())
 | 
					 | 
				
			||||||
	if bounds.Empty() { return }
 | 
					 | 
				
			||||||
	updatedRegion = bounds
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	realWidth, realHeight := realBounds.Dx(), realBounds.Dy()
 | 
					 | 
				
			||||||
	patternOffset := realBounds.Min.Sub(bounds.Min)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	width, height := bounds.Dx(), bounds.Dy()
 | 
					 | 
				
			||||||
	for y := 0; y < height; y ++ {
 | 
					 | 
				
			||||||
	for x := 0; x < width;  x ++ {
 | 
					 | 
				
			||||||
		data[x + bounds.Min.X + (y + bounds.Min.Y) * stride] =
 | 
					 | 
				
			||||||
			source.AtWhen (
 | 
					 | 
				
			||||||
				x - patternOffset.X, y - patternOffset.Y,
 | 
					 | 
				
			||||||
				realWidth, realHeight)
 | 
					 | 
				
			||||||
	}}
 | 
					 | 
				
			||||||
	return
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// FillRectangleShatter shatters a bounding rectangle and draws its tiles in one
 | 
					 | 
				
			||||||
// fell swoop.
 | 
					 | 
				
			||||||
func FillRectangleShatter (
 | 
					 | 
				
			||||||
	destination canvas.Canvas,
 | 
					 | 
				
			||||||
	source Pattern,
 | 
					 | 
				
			||||||
	glass image.Rectangle,
 | 
					 | 
				
			||||||
	rocks ...image.Rectangle,
 | 
					 | 
				
			||||||
) (
 | 
					 | 
				
			||||||
	updatedRegions []image.Rectangle,
 | 
					 | 
				
			||||||
) {
 | 
					 | 
				
			||||||
	tiles := shatter.Shatter(glass, rocks...)
 | 
					 | 
				
			||||||
	for _, tile := range tiles {
 | 
					 | 
				
			||||||
		FillRectangleClip(destination, source, glass, tile)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	return tiles
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// StrokeRectangle draws the outline of a rectangle with the specified line
 | 
					 | 
				
			||||||
// weight and pattern.
 | 
					 | 
				
			||||||
func StrokeRectangle (
 | 
					 | 
				
			||||||
	destination canvas.Canvas,
 | 
					 | 
				
			||||||
	source Pattern,
 | 
					 | 
				
			||||||
	weight int,
 | 
					 | 
				
			||||||
	bounds image.Rectangle,
 | 
					 | 
				
			||||||
) {
 | 
					 | 
				
			||||||
	bounds = bounds.Canon()
 | 
					 | 
				
			||||||
	insetBounds := bounds.Inset(weight)
 | 
					 | 
				
			||||||
	if insetBounds.Empty() {
 | 
					 | 
				
			||||||
		FillRectangle(destination, source, bounds)
 | 
					 | 
				
			||||||
		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))
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
							
								
								
									
										11
									
								
								artist/shapes/doc.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								artist/shapes/doc.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,11 @@
 | 
				
			|||||||
 | 
					// Package shapes provides some basic shape drawing routines.
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// A word about using patterns with shape routines:
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// Most drawing routines have a version that samples from other canvases, and a
 | 
				
			||||||
 | 
					// version that samples from a solid color. None of these routines can use
 | 
				
			||||||
 | 
					// patterns directly, but it is entirely possible to have a pattern draw to an
 | 
				
			||||||
 | 
					// off-screen canvas and then draw a shape based on that canvas. As a little
 | 
				
			||||||
 | 
					// bonus, you can save the canvas for later so you don't have to render the
 | 
				
			||||||
 | 
					// pattern again when you need to redraw the shape.
 | 
				
			||||||
 | 
					package shapes
 | 
				
			||||||
							
								
								
									
										228
									
								
								artist/shapes/ellipse.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										228
									
								
								artist/shapes/ellipse.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,228 @@
 | 
				
			|||||||
 | 
					package shapes
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import "math"
 | 
				
			||||||
 | 
					import "image"
 | 
				
			||||||
 | 
					import "image/color"
 | 
				
			||||||
 | 
					import "git.tebibyte.media/sashakoshka/tomo/canvas"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// TODO: redo fill ellipse, stroke ellipse, etc. so that it only takes in
 | 
				
			||||||
 | 
					// destination and source, using the bounds of destination as the bounds of the
 | 
				
			||||||
 | 
					// ellipse and the bounds of source as the "clipping rectangle". Line up the Min
 | 
				
			||||||
 | 
					// of both canvases.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func FillEllipse (
 | 
				
			||||||
 | 
						destination canvas.Canvas,
 | 
				
			||||||
 | 
						source      canvas.Canvas,
 | 
				
			||||||
 | 
					) (
 | 
				
			||||||
 | 
						updatedRegion image.Rectangle,
 | 
				
			||||||
 | 
					) {
 | 
				
			||||||
 | 
						dstData, dstStride := destination.Buffer()
 | 
				
			||||||
 | 
						srcData, srcStride := source.Buffer()
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						offset := source.Bounds().Min.Sub(destination.Bounds().Min)
 | 
				
			||||||
 | 
						bounds     := source.Bounds().Sub(offset).Intersect(destination.Bounds())
 | 
				
			||||||
 | 
						realBounds := destination.Bounds()
 | 
				
			||||||
 | 
						if bounds.Empty() { return }
 | 
				
			||||||
 | 
						updatedRegion = bounds
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						point := image.Point { }
 | 
				
			||||||
 | 
						for point.Y = bounds.Min.Y; point.Y < bounds.Max.Y; point.Y ++ {
 | 
				
			||||||
 | 
						for point.X = bounds.Min.X; point.X < bounds.Max.X; point.X ++ {
 | 
				
			||||||
 | 
							if inEllipse(point, realBounds) {
 | 
				
			||||||
 | 
								offsetPoint := point.Add(offset)
 | 
				
			||||||
 | 
								dstIndex := point.X       + point.Y       * dstStride
 | 
				
			||||||
 | 
								srcIndex := offsetPoint.X + offsetPoint.Y * srcStride
 | 
				
			||||||
 | 
								dstData[dstIndex] = srcData[srcIndex]
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}}
 | 
				
			||||||
 | 
						return
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func StrokeEllipse (
 | 
				
			||||||
 | 
						destination canvas.Canvas,
 | 
				
			||||||
 | 
						source      canvas.Canvas,
 | 
				
			||||||
 | 
						weight      int,
 | 
				
			||||||
 | 
					) {
 | 
				
			||||||
 | 
						if weight < 1 { return }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						dstData, dstStride := destination.Buffer()
 | 
				
			||||||
 | 
						srcData, srcStride := source.Buffer()
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						bounds := destination.Bounds().Inset(weight - 1)
 | 
				
			||||||
 | 
						offset := source.Bounds().Min.Sub(destination.Bounds().Min)
 | 
				
			||||||
 | 
						realBounds := destination.Bounds()
 | 
				
			||||||
 | 
						if bounds.Empty() { return }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						context := ellipsePlottingContext {
 | 
				
			||||||
 | 
							plottingContext: plottingContext {
 | 
				
			||||||
 | 
								dstData:   dstData,
 | 
				
			||||||
 | 
								dstStride: dstStride,
 | 
				
			||||||
 | 
								srcData:   srcData,
 | 
				
			||||||
 | 
								srcStride: srcStride,
 | 
				
			||||||
 | 
								weight:    weight,
 | 
				
			||||||
 | 
								offset:    offset,
 | 
				
			||||||
 | 
								bounds:    realBounds,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							radii:  image.Pt(bounds.Dx() / 2, bounds.Dy() / 2),
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						context.center = bounds.Min.Add(context.radii)
 | 
				
			||||||
 | 
						context.plotEllipse()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type ellipsePlottingContext struct {
 | 
				
			||||||
 | 
						plottingContext
 | 
				
			||||||
 | 
						radii  image.Point
 | 
				
			||||||
 | 
						center image.Point
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (context ellipsePlottingContext) plotEllipse () {
 | 
				
			||||||
 | 
						x := float64(0)
 | 
				
			||||||
 | 
						y := float64(context.radii.Y)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// region 1 decision parameter
 | 
				
			||||||
 | 
						decision1 :=
 | 
				
			||||||
 | 
							float64(context.radii.Y * context.radii.Y) -
 | 
				
			||||||
 | 
							float64(context.radii.X * context.radii.X * context.radii.Y) +
 | 
				
			||||||
 | 
							(0.25 * float64(context.radii.X) * float64(context.radii.X))
 | 
				
			||||||
 | 
						decisionX := float64(2 * context.radii.Y * context.radii.Y * int(x))
 | 
				
			||||||
 | 
						decisionY := float64(2 * context.radii.X * context.radii.X * int(y))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// draw region 1
 | 
				
			||||||
 | 
						for decisionX < decisionY {
 | 
				
			||||||
 | 
							points := []image.Point {
 | 
				
			||||||
 | 
								image.Pt(-int(x) + context.center.X, -int(y) + context.center.Y),
 | 
				
			||||||
 | 
								image.Pt( int(x) + context.center.X, -int(y) + context.center.Y),
 | 
				
			||||||
 | 
								image.Pt(-int(x) + context.center.X,  int(y) + context.center.Y),
 | 
				
			||||||
 | 
								image.Pt( int(x) + context.center.X,  int(y) + context.center.Y),			
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if context.srcData == nil {
 | 
				
			||||||
 | 
								context.plotColor(points[0])
 | 
				
			||||||
 | 
								context.plotColor(points[1])
 | 
				
			||||||
 | 
								context.plotColor(points[2])
 | 
				
			||||||
 | 
								context.plotColor(points[3])
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								context.plotSource(points[0])
 | 
				
			||||||
 | 
								context.plotSource(points[1])
 | 
				
			||||||
 | 
								context.plotSource(points[2])
 | 
				
			||||||
 | 
								context.plotSource(points[3])
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if (decision1 < 0) {
 | 
				
			||||||
 | 
								x ++
 | 
				
			||||||
 | 
								decisionX += float64(2 * context.radii.Y * context.radii.Y)
 | 
				
			||||||
 | 
								decision1 += decisionX + float64(context.radii.Y * context.radii.Y)
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								x ++
 | 
				
			||||||
 | 
								y --
 | 
				
			||||||
 | 
								decisionX += float64(2 * context.radii.Y * context.radii.Y)
 | 
				
			||||||
 | 
								decisionY -= float64(2 * context.radii.X * context.radii.X)
 | 
				
			||||||
 | 
								decision1 +=
 | 
				
			||||||
 | 
									decisionX - decisionY +
 | 
				
			||||||
 | 
									float64(context.radii.Y * context.radii.Y)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// region 2 decision parameter
 | 
				
			||||||
 | 
						decision2 :=
 | 
				
			||||||
 | 
							float64(context.radii.Y * context.radii.Y) * (x + 0.5) * (x + 0.5) +
 | 
				
			||||||
 | 
							float64(context.radii.X * context.radii.X) * (y - 1)   * (y - 1) -
 | 
				
			||||||
 | 
							float64(context.radii.X * context.radii.X * context.radii.Y * context.radii.Y)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// draw region 2
 | 
				
			||||||
 | 
						for y >= 0 {
 | 
				
			||||||
 | 
							points := []image.Point {
 | 
				
			||||||
 | 
								image.Pt( int(x) + context.center.X,  int(y) + context.center.Y),
 | 
				
			||||||
 | 
								image.Pt(-int(x) + context.center.X,  int(y) + context.center.Y),
 | 
				
			||||||
 | 
								image.Pt( int(x) + context.center.X, -int(y) + context.center.Y),
 | 
				
			||||||
 | 
								image.Pt(-int(x) + context.center.X, -int(y) + context.center.Y),
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if context.srcData == nil {
 | 
				
			||||||
 | 
								context.plotColor(points[0])
 | 
				
			||||||
 | 
								context.plotColor(points[1])
 | 
				
			||||||
 | 
								context.plotColor(points[2])
 | 
				
			||||||
 | 
								context.plotColor(points[3])
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								context.plotSource(points[0])
 | 
				
			||||||
 | 
								context.plotSource(points[1])
 | 
				
			||||||
 | 
								context.plotSource(points[2])
 | 
				
			||||||
 | 
								context.plotSource(points[3])
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if decision2 > 0 {
 | 
				
			||||||
 | 
								y --
 | 
				
			||||||
 | 
								decisionY -= float64(2 * context.radii.X * context.radii.X)
 | 
				
			||||||
 | 
								decision2 += float64(context.radii.X * context.radii.X) - decisionY
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								y --
 | 
				
			||||||
 | 
								x ++
 | 
				
			||||||
 | 
								decisionX += float64(2 * context.radii.Y * context.radii.Y)
 | 
				
			||||||
 | 
								decisionY -= float64(2 * context.radii.X * context.radii.X)
 | 
				
			||||||
 | 
								decision2 +=
 | 
				
			||||||
 | 
									decisionX - decisionY +
 | 
				
			||||||
 | 
									float64(context.radii.X * context.radii.X)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// FillColorEllipse fills an ellipse within the destination canvas with a solid
 | 
				
			||||||
 | 
					// color.
 | 
				
			||||||
 | 
					func FillColorEllipse (
 | 
				
			||||||
 | 
						destination canvas.Canvas,
 | 
				
			||||||
 | 
						color       color.RGBA,
 | 
				
			||||||
 | 
						bounds      image.Rectangle,
 | 
				
			||||||
 | 
					) (
 | 
				
			||||||
 | 
						updatedRegion image.Rectangle,
 | 
				
			||||||
 | 
					) {
 | 
				
			||||||
 | 
						dstData, dstStride := destination.Buffer()
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						realBounds := bounds
 | 
				
			||||||
 | 
						bounds = bounds.Intersect(destination.Bounds()).Canon()
 | 
				
			||||||
 | 
						if bounds.Empty() { return }
 | 
				
			||||||
 | 
						updatedRegion = bounds
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						point := image.Point { }
 | 
				
			||||||
 | 
						for point.Y = bounds.Min.Y; point.Y < bounds.Max.Y; point.Y ++ {
 | 
				
			||||||
 | 
						for point.X = bounds.Min.X; point.X < bounds.Max.X; point.X ++ {
 | 
				
			||||||
 | 
							if inEllipse(point, realBounds) {
 | 
				
			||||||
 | 
								dstData[point.X + point.Y * dstStride] = color
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}}
 | 
				
			||||||
 | 
						return
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// StrokeColorEllipse is similar to FillColorEllipse, but it draws an inset
 | 
				
			||||||
 | 
					// outline of an ellipse instead.
 | 
				
			||||||
 | 
					func StrokeColorEllipse (
 | 
				
			||||||
 | 
						destination canvas.Canvas,
 | 
				
			||||||
 | 
						color       color.RGBA,
 | 
				
			||||||
 | 
						bounds      image.Rectangle,
 | 
				
			||||||
 | 
						weight      int,
 | 
				
			||||||
 | 
					) (
 | 
				
			||||||
 | 
						updatedRegion image.Rectangle,
 | 
				
			||||||
 | 
					) {
 | 
				
			||||||
 | 
						if weight < 1 { return }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						dstData, dstStride := destination.Buffer()
 | 
				
			||||||
 | 
						insetBounds := bounds.Inset(weight - 1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						context := ellipsePlottingContext {
 | 
				
			||||||
 | 
							plottingContext: plottingContext {
 | 
				
			||||||
 | 
								dstData:   dstData,
 | 
				
			||||||
 | 
								dstStride: dstStride,
 | 
				
			||||||
 | 
								color:     color,
 | 
				
			||||||
 | 
								weight:    weight,
 | 
				
			||||||
 | 
								bounds:    bounds.Intersect(destination.Bounds()),
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							radii: image.Pt(insetBounds.Dx() / 2, insetBounds.Dy() / 2),
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						context.center = insetBounds.Min.Add(context.radii)
 | 
				
			||||||
 | 
						context.plotEllipse()
 | 
				
			||||||
 | 
						return
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func inEllipse (point image.Point, bounds image.Rectangle) bool {
 | 
				
			||||||
 | 
						point = point.Sub(bounds.Min)
 | 
				
			||||||
 | 
						x := (float64(point.X) + 0.5) / float64(bounds.Dx()) - 0.5
 | 
				
			||||||
 | 
						y := (float64(point.Y) + 0.5) / float64(bounds.Dy()) - 0.5
 | 
				
			||||||
 | 
						return math.Hypot(x, y) <= 0.5
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										112
									
								
								artist/shapes/line.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										112
									
								
								artist/shapes/line.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,112 @@
 | 
				
			|||||||
 | 
					package shapes
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import "image"
 | 
				
			||||||
 | 
					import "image/color"
 | 
				
			||||||
 | 
					import "git.tebibyte.media/sashakoshka/tomo/canvas"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ColorLine draws a line from one point to another with the specified weight
 | 
				
			||||||
 | 
					// and color.
 | 
				
			||||||
 | 
					func ColorLine (
 | 
				
			||||||
 | 
						destination canvas.Canvas,
 | 
				
			||||||
 | 
						color       color.RGBA,
 | 
				
			||||||
 | 
						weight      int,
 | 
				
			||||||
 | 
						min         image.Point,
 | 
				
			||||||
 | 
						max         image.Point,
 | 
				
			||||||
 | 
					) (
 | 
				
			||||||
 | 
						updatedRegion image.Rectangle,
 | 
				
			||||||
 | 
					) {
 | 
				
			||||||
 | 
						updatedRegion = image.Rectangle { Min: min, Max: max }.Canon()
 | 
				
			||||||
 | 
						updatedRegion.Max.X ++
 | 
				
			||||||
 | 
						updatedRegion.Max.Y ++
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						data, stride := destination.Buffer()
 | 
				
			||||||
 | 
						bounds := destination.Bounds()
 | 
				
			||||||
 | 
						context := linePlottingContext {
 | 
				
			||||||
 | 
							plottingContext: plottingContext {
 | 
				
			||||||
 | 
								dstData:   data,
 | 
				
			||||||
 | 
								dstStride: stride,
 | 
				
			||||||
 | 
								color:     color,
 | 
				
			||||||
 | 
								weight:    weight,
 | 
				
			||||||
 | 
								bounds:    bounds,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							min: min,
 | 
				
			||||||
 | 
							max: max,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						if abs(max.Y - min.Y) < abs(max.X - min.X) {
 | 
				
			||||||
 | 
							if max.X < min.X { context.swap() }
 | 
				
			||||||
 | 
							context.lineLow()
 | 
				
			||||||
 | 
							
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							if max.Y < min.Y { context.swap() }
 | 
				
			||||||
 | 
							context.lineHigh()
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type linePlottingContext struct {
 | 
				
			||||||
 | 
						plottingContext
 | 
				
			||||||
 | 
						min image.Point
 | 
				
			||||||
 | 
						max image.Point
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (context *linePlottingContext) swap () {
 | 
				
			||||||
 | 
						temp := context.max
 | 
				
			||||||
 | 
						context.max = context.min
 | 
				
			||||||
 | 
						context.min = temp
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (context linePlottingContext) lineLow () {
 | 
				
			||||||
 | 
						deltaX := context.max.X - context.min.X
 | 
				
			||||||
 | 
						deltaY := context.max.Y - context.min.Y
 | 
				
			||||||
 | 
						yi     := 1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if deltaY < 0 {
 | 
				
			||||||
 | 
							yi      = -1
 | 
				
			||||||
 | 
							deltaY *= -1
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						D := (2 * deltaY) - deltaX
 | 
				
			||||||
 | 
						point := context.min
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for ; point.X < context.max.X; point.X ++ {
 | 
				
			||||||
 | 
							if !point.In(context.bounds) { break }
 | 
				
			||||||
 | 
							context.plotColor(point)
 | 
				
			||||||
 | 
							if D > 0 {
 | 
				
			||||||
 | 
								D += 2 * (deltaY - deltaX)
 | 
				
			||||||
 | 
								point.Y += yi
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								D += 2 * deltaY
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (context linePlottingContext) lineHigh () {
 | 
				
			||||||
 | 
						deltaX := context.max.X - context.min.X
 | 
				
			||||||
 | 
						deltaY := context.max.Y - context.min.Y
 | 
				
			||||||
 | 
						xi     := 1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if deltaX < 0 {
 | 
				
			||||||
 | 
							xi      = -1
 | 
				
			||||||
 | 
							deltaX *= -1
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						D := (2 * deltaX) - deltaY
 | 
				
			||||||
 | 
						point := context.min
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for ; point.Y < context.max.Y; point.Y ++ {
 | 
				
			||||||
 | 
							if !point.In(context.bounds) { break }
 | 
				
			||||||
 | 
							context.plotColor(point)
 | 
				
			||||||
 | 
							if D > 0 {
 | 
				
			||||||
 | 
								point.X += xi
 | 
				
			||||||
 | 
								D += 2 * (deltaX - deltaY)
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								D += 2 * deltaX
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func abs (n int) int {
 | 
				
			||||||
 | 
						if n < 0 { n *= -1}
 | 
				
			||||||
 | 
						return n
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										47
									
								
								artist/shapes/plot.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								artist/shapes/plot.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,47 @@
 | 
				
			|||||||
 | 
					package shapes
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import "image"
 | 
				
			||||||
 | 
					import "image/color"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// FIXME? drawing a ton of overlapping squares might be a bit wasteful.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type plottingContext struct {
 | 
				
			||||||
 | 
						dstData   []color.RGBA
 | 
				
			||||||
 | 
						dstStride int
 | 
				
			||||||
 | 
						srcData   []color.RGBA
 | 
				
			||||||
 | 
						srcStride int
 | 
				
			||||||
 | 
						color     color.RGBA
 | 
				
			||||||
 | 
						weight    int
 | 
				
			||||||
 | 
						offset    image.Point
 | 
				
			||||||
 | 
						bounds    image.Rectangle
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (context plottingContext) square (center image.Point) (square image.Rectangle) {
 | 
				
			||||||
 | 
						return image.Rect(0, 0, context.weight, context.weight).
 | 
				
			||||||
 | 
							Sub(image.Pt(context.weight / 2, context.weight / 2)).
 | 
				
			||||||
 | 
							Add(center).
 | 
				
			||||||
 | 
							Intersect(context.bounds)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (context plottingContext) plotColor (center image.Point) {
 | 
				
			||||||
 | 
						square := context.square(center)
 | 
				
			||||||
 | 
						for y := square.Min.Y; y < square.Max.Y; y ++ {
 | 
				
			||||||
 | 
						for x := square.Min.X; x < square.Max.X; x ++ {
 | 
				
			||||||
 | 
							context.dstData[x + y * context.dstStride] = context.color
 | 
				
			||||||
 | 
						}}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (context plottingContext) plotSource (center image.Point) {
 | 
				
			||||||
 | 
						square := context.square(center)
 | 
				
			||||||
 | 
						for y := square.Min.Y; y < square.Max.Y; y ++ {
 | 
				
			||||||
 | 
						for x := square.Min.X; x < square.Max.X; x ++ {
 | 
				
			||||||
 | 
							// we offset srcIndex here because we have already applied the
 | 
				
			||||||
 | 
							// offset to the square, and we need to reverse that to get the
 | 
				
			||||||
 | 
							// proper source coordinates.
 | 
				
			||||||
 | 
							srcIndex := 
 | 
				
			||||||
 | 
								x + context.offset.X +
 | 
				
			||||||
 | 
								(y + context.offset.Y) * context.dstStride
 | 
				
			||||||
 | 
							dstIndex := x + y * context.dstStride
 | 
				
			||||||
 | 
							context.dstData[dstIndex] = context.srcData [srcIndex]
 | 
				
			||||||
 | 
						}}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										116
									
								
								artist/shapes/rectangle.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										116
									
								
								artist/shapes/rectangle.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,116 @@
 | 
				
			|||||||
 | 
					package shapes
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import "image"
 | 
				
			||||||
 | 
					import "image/color"
 | 
				
			||||||
 | 
					import "git.tebibyte.media/sashakoshka/tomo/canvas"
 | 
				
			||||||
 | 
					import "git.tebibyte.media/sashakoshka/tomo/shatter"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// TODO: return updatedRegion for all routines in this package
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func FillRectangle (
 | 
				
			||||||
 | 
						destination canvas.Canvas,
 | 
				
			||||||
 | 
						source      canvas.Canvas,
 | 
				
			||||||
 | 
					) (
 | 
				
			||||||
 | 
						updatedRegion image.Rectangle,
 | 
				
			||||||
 | 
					) {
 | 
				
			||||||
 | 
						dstData, dstStride := destination.Buffer()
 | 
				
			||||||
 | 
						srcData, srcStride := source.Buffer()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						offset := source.Bounds().Min.Sub(destination.Bounds().Min)
 | 
				
			||||||
 | 
						bounds     := source.Bounds().Sub(offset).Intersect(destination.Bounds())
 | 
				
			||||||
 | 
						if bounds.Empty() { return }
 | 
				
			||||||
 | 
						updatedRegion = bounds
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						point := image.Point { }
 | 
				
			||||||
 | 
						for point.Y = bounds.Min.Y; point.Y < bounds.Max.Y; point.Y ++ {
 | 
				
			||||||
 | 
						for point.X = bounds.Min.X; point.X < bounds.Max.X; point.X ++ {
 | 
				
			||||||
 | 
							offsetPoint := point.Add(offset)
 | 
				
			||||||
 | 
							dstIndex := point.X       + point.Y       * dstStride
 | 
				
			||||||
 | 
							srcIndex := offsetPoint.X + offsetPoint.Y * srcStride
 | 
				
			||||||
 | 
							dstData[dstIndex] = srcData[srcIndex]
 | 
				
			||||||
 | 
						}}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func StrokeRectangle (
 | 
				
			||||||
 | 
						destination canvas.Canvas,
 | 
				
			||||||
 | 
						source      canvas.Canvas,
 | 
				
			||||||
 | 
						weight      int,
 | 
				
			||||||
 | 
					) {
 | 
				
			||||||
 | 
						bounds := destination.Bounds()
 | 
				
			||||||
 | 
						insetBounds := bounds.Inset(weight)
 | 
				
			||||||
 | 
						if insetBounds.Empty() {
 | 
				
			||||||
 | 
							FillRectangle(destination, source)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						FillRectangleShatter(destination, source, insetBounds)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// FillRectangleShatter is like FillRectangle, but it does not draw in areas
 | 
				
			||||||
 | 
					// specified in "rocks".
 | 
				
			||||||
 | 
					func FillRectangleShatter (
 | 
				
			||||||
 | 
						destination canvas.Canvas,
 | 
				
			||||||
 | 
						source      canvas.Canvas,
 | 
				
			||||||
 | 
						rocks       ...image.Rectangle,
 | 
				
			||||||
 | 
					) {
 | 
				
			||||||
 | 
						tiles  := shatter.Shatter(destination.Bounds(), rocks...)
 | 
				
			||||||
 | 
						offset := source.Bounds().Min.Sub(destination.Bounds().Min)
 | 
				
			||||||
 | 
						for _, tile := range tiles {
 | 
				
			||||||
 | 
							FillRectangle (
 | 
				
			||||||
 | 
								canvas.Cut(destination, tile),
 | 
				
			||||||
 | 
								canvas.Cut(source, tile.Add(offset)))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// FillColorRectangle fills a rectangle within the destination canvas with a
 | 
				
			||||||
 | 
					// solid color.
 | 
				
			||||||
 | 
					func FillColorRectangle (
 | 
				
			||||||
 | 
						destination canvas.Canvas,
 | 
				
			||||||
 | 
						color       color.RGBA,
 | 
				
			||||||
 | 
						bounds      image.Rectangle,
 | 
				
			||||||
 | 
					) (
 | 
				
			||||||
 | 
						updatedRegion image.Rectangle,
 | 
				
			||||||
 | 
					) {
 | 
				
			||||||
 | 
						dstData, dstStride := destination.Buffer()
 | 
				
			||||||
 | 
						bounds = bounds.Canon().Intersect(destination.Bounds())
 | 
				
			||||||
 | 
						if bounds.Empty() { return }
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						updatedRegion = bounds
 | 
				
			||||||
 | 
						for y := bounds.Min.Y; y < bounds.Max.Y; y ++ {
 | 
				
			||||||
 | 
						for x := bounds.Min.X; x < bounds.Max.X; x ++ {
 | 
				
			||||||
 | 
							dstData[x + y * dstStride] = color
 | 
				
			||||||
 | 
						}}
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						return
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// FillColorRectangleShatter is like FillColorRectangle, but it does not draw in
 | 
				
			||||||
 | 
					// areas specified in "rocks".
 | 
				
			||||||
 | 
					func FillColorRectangleShatter (
 | 
				
			||||||
 | 
						destination canvas.Canvas,
 | 
				
			||||||
 | 
						color       color.RGBA,
 | 
				
			||||||
 | 
						bounds      image.Rectangle,
 | 
				
			||||||
 | 
						rocks       ...image.Rectangle,
 | 
				
			||||||
 | 
					) {
 | 
				
			||||||
 | 
						tiles := shatter.Shatter(bounds, rocks...)
 | 
				
			||||||
 | 
						for _, tile := range tiles {
 | 
				
			||||||
 | 
							FillColorRectangle(destination, color, tile)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// StrokeColorRectangle is similar to FillColorRectangle, but it draws an inset
 | 
				
			||||||
 | 
					// outline of the given rectangle instead.
 | 
				
			||||||
 | 
					func StrokeColorRectangle (
 | 
				
			||||||
 | 
						destination canvas.Canvas,
 | 
				
			||||||
 | 
						color       color.RGBA,
 | 
				
			||||||
 | 
						bounds      image.Rectangle,
 | 
				
			||||||
 | 
						weight      int,
 | 
				
			||||||
 | 
					) {
 | 
				
			||||||
 | 
						insetBounds := bounds.Inset(weight)
 | 
				
			||||||
 | 
						if insetBounds.Empty() {
 | 
				
			||||||
 | 
							FillColorRectangle(destination, color, bounds)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						FillColorRectangleShatter(destination, color, bounds, insetBounds)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -1,43 +0,0 @@
 | 
				
			|||||||
package artist
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import "image/color"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// Orientation specifies an eight-way pattern orientation.
 | 
					 | 
				
			||||||
type Orientation int
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const (
 | 
					 | 
				
			||||||
	OrientationVertical Orientation = iota
 | 
					 | 
				
			||||||
	OrientationDiagonalRight
 | 
					 | 
				
			||||||
	OrientationHorizontal
 | 
					 | 
				
			||||||
	OrientationDiagonalLeft
 | 
					 | 
				
			||||||
)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// Split is a pattern that is divided in half between two sub-patterns.
 | 
					 | 
				
			||||||
type Split struct {
 | 
					 | 
				
			||||||
	First  Pattern
 | 
					 | 
				
			||||||
	Second Pattern
 | 
					 | 
				
			||||||
	Orientation
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// AtWhen satisfies the Pattern interface.
 | 
					 | 
				
			||||||
func (pattern Split) AtWhen (x, y, width, height int) (c color.RGBA) {
 | 
					 | 
				
			||||||
	var first bool
 | 
					 | 
				
			||||||
	switch pattern.Orientation {
 | 
					 | 
				
			||||||
	case OrientationVertical:
 | 
					 | 
				
			||||||
		first = x < width / 2
 | 
					 | 
				
			||||||
	case OrientationDiagonalRight:
 | 
					 | 
				
			||||||
		first = float64(x) / float64(width) +
 | 
					 | 
				
			||||||
			float64(y) / float64(height) < 1
 | 
					 | 
				
			||||||
	case OrientationHorizontal:
 | 
					 | 
				
			||||||
		first = y < height / 2
 | 
					 | 
				
			||||||
	case OrientationDiagonalLeft:
 | 
					 | 
				
			||||||
		first = float64(width - x) / float64(width) +
 | 
					 | 
				
			||||||
			float64(y) / float64(height) < 1
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	
 | 
					 | 
				
			||||||
	if first {
 | 
					 | 
				
			||||||
		return pattern.First.AtWhen(x, y, width, height)
 | 
					 | 
				
			||||||
	} else {
 | 
					 | 
				
			||||||
		return pattern.Second.AtWhen(x, y, width, height)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@ -1,37 +0,0 @@
 | 
				
			|||||||
package artist
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import "image/color"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// Striped is a pattern that produces stripes of two alternating colors.
 | 
					 | 
				
			||||||
type Striped struct {
 | 
					 | 
				
			||||||
	First  Stroke
 | 
					 | 
				
			||||||
	Second Stroke
 | 
					 | 
				
			||||||
	Orientation
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// AtWhen satisfies the Pattern interface.
 | 
					 | 
				
			||||||
func (pattern Striped) AtWhen (x, y, width, height int) (c color.RGBA) {
 | 
					 | 
				
			||||||
	position := 0
 | 
					 | 
				
			||||||
	switch pattern.Orientation {
 | 
					 | 
				
			||||||
	case OrientationVertical:
 | 
					 | 
				
			||||||
		position = x
 | 
					 | 
				
			||||||
	case OrientationDiagonalRight:
 | 
					 | 
				
			||||||
		position = x + y
 | 
					 | 
				
			||||||
	case OrientationHorizontal:
 | 
					 | 
				
			||||||
		position = y
 | 
					 | 
				
			||||||
	case OrientationDiagonalLeft:
 | 
					 | 
				
			||||||
		position = x - y
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	phase := pattern.First.Weight + pattern.Second.Weight
 | 
					 | 
				
			||||||
	position %= phase
 | 
					 | 
				
			||||||
	if position < 0 {
 | 
					 | 
				
			||||||
		position += phase
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	
 | 
					 | 
				
			||||||
	if position < pattern.First.Weight {
 | 
					 | 
				
			||||||
		return pattern.First.AtWhen(x, y, width, height)
 | 
					 | 
				
			||||||
	} else {
 | 
					 | 
				
			||||||
		return pattern.Second.AtWhen(x, y, width, height)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@ -1,43 +0,0 @@
 | 
				
			|||||||
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]
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@ -1,68 +0,0 @@
 | 
				
			|||||||
package artist
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import "image"
 | 
					 | 
				
			||||||
import "image/color"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// Uniform is an infinite-sized pattern of uniform color. It implements the
 | 
					 | 
				
			||||||
// Pattern, color.Color, color.Model, and image.Image interfaces.
 | 
					 | 
				
			||||||
type Uniform color.RGBA
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// NewUniform returns a new Uniform pattern of the given color.
 | 
					 | 
				
			||||||
func NewUniform (c color.Color) (uniform Uniform) {
 | 
					 | 
				
			||||||
	r, g, b, a := c.RGBA()
 | 
					 | 
				
			||||||
	uniform.R = uint8(r >> 8)
 | 
					 | 
				
			||||||
	uniform.G = uint8(g >> 8)
 | 
					 | 
				
			||||||
	uniform.B = uint8(b >> 8)
 | 
					 | 
				
			||||||
	uniform.A = uint8(a >> 8)
 | 
					 | 
				
			||||||
	return
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func hex (color uint32) (c color.RGBA) {
 | 
					 | 
				
			||||||
	c.A = uint8(color)
 | 
					 | 
				
			||||||
	c.B = uint8(color >>  8)
 | 
					 | 
				
			||||||
	c.G = uint8(color >> 16)
 | 
					 | 
				
			||||||
	c.R = uint8(color >> 24)
 | 
					 | 
				
			||||||
	return
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// Uhex creates a new Uniform pattern from an RGBA integer value.
 | 
					 | 
				
			||||||
func Uhex (color uint32) (uniform Uniform) {
 | 
					 | 
				
			||||||
	return NewUniform(hex(color))
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// ColorModel satisfies the image.Image interface.
 | 
					 | 
				
			||||||
func (uniform Uniform) ColorModel () (model color.Model) {
 | 
					 | 
				
			||||||
	return uniform
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// Convert satisfies the color.Model interface.
 | 
					 | 
				
			||||||
func (uniform Uniform) Convert (in color.Color) (c color.Color) {
 | 
					 | 
				
			||||||
	return color.RGBA(uniform)
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// Bounds satisfies the image.Image interface.
 | 
					 | 
				
			||||||
func (uniform Uniform) Bounds () (rectangle image.Rectangle) {
 | 
					 | 
				
			||||||
	rectangle.Min = image.Point { -1e9, -1e9 }
 | 
					 | 
				
			||||||
	rectangle.Max = image.Point {  1e9,  1e9 }
 | 
					 | 
				
			||||||
	return
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// At satisfies the image.Image interface.
 | 
					 | 
				
			||||||
func (uniform Uniform) At (x, y int) (c color.Color) {
 | 
					 | 
				
			||||||
	return color.RGBA(uniform)
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// AtWhen satisfies the Pattern interface.
 | 
					 | 
				
			||||||
func (uniform Uniform) AtWhen (x, y, width, height int) (c color.RGBA) {
 | 
					 | 
				
			||||||
	return color.RGBA(uniform)
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// RGBA satisfies the color.Color interface.
 | 
					 | 
				
			||||||
func (uniform Uniform) RGBA () (r, g, b, a uint32) {
 | 
					 | 
				
			||||||
	return color.RGBA(uniform).RGBA()
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// Opaque scans the entire image and reports whether it is fully opaque.
 | 
					 | 
				
			||||||
func (uniform Uniform) Opaque () (opaque bool) {
 | 
					 | 
				
			||||||
	return uniform.A == 0xFF
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@ -1,27 +0,0 @@
 | 
				
			|||||||
package artist
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import "image"
 | 
					 | 
				
			||||||
import "image/color"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// WrappedPattern is a pattern that is able to behave like an image.Image.
 | 
					 | 
				
			||||||
type WrappedPattern struct {
 | 
					 | 
				
			||||||
	Pattern
 | 
					 | 
				
			||||||
	Width, Height int
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// At satisfies the image.Image interface.
 | 
					 | 
				
			||||||
func (pattern WrappedPattern) At (x, y int) (c color.Color) {
 | 
					 | 
				
			||||||
	return pattern.Pattern.AtWhen(x, y, pattern.Width, pattern.Height)
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// 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
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// ColorModel satisfies the image.Image interface.
 | 
					 | 
				
			||||||
func (pattern WrappedPattern) ColorModel () (model color.Model) {
 | 
					 | 
				
			||||||
	return color.RGBAModel
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@ -34,6 +34,21 @@ func NewBasicCanvas (width, height int) (canvas BasicCanvas) {
 | 
				
			|||||||
	return
 | 
						return
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// FromImage creates a new BasicCanvas from an image.Image.
 | 
				
			||||||
 | 
					func FromImage (img image.Image) (canvas BasicCanvas) {
 | 
				
			||||||
 | 
						bounds := img.Bounds()
 | 
				
			||||||
 | 
						canvas = NewBasicCanvas(bounds.Dx(), bounds.Dy())
 | 
				
			||||||
 | 
						point := image.Point { }
 | 
				
			||||||
 | 
						for point.Y = bounds.Min.Y; point.Y < bounds.Max.Y; point.Y ++ {
 | 
				
			||||||
 | 
						for point.X = bounds.Min.X; point.X < bounds.Max.X; point.X ++ {
 | 
				
			||||||
 | 
							canvasPoint := point.Sub(bounds.Min)
 | 
				
			||||||
 | 
							canvas.Set (
 | 
				
			||||||
 | 
								canvasPoint.X, canvasPoint.Y,
 | 
				
			||||||
 | 
								img.At(point.X, point.Y))
 | 
				
			||||||
 | 
						}}
 | 
				
			||||||
 | 
						return
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// you know what it do
 | 
					// you know what it do
 | 
				
			||||||
func (canvas BasicCanvas) Bounds () (bounds image.Rectangle) {
 | 
					func (canvas BasicCanvas) Bounds () (bounds image.Rectangle) {
 | 
				
			||||||
	return canvas.rect
 | 
						return canvas.rect
 | 
				
			||||||
 | 
				
			|||||||
@ -2,15 +2,6 @@ package config
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
// Config can return global configuration parameters.
 | 
					// Config can return global configuration parameters.
 | 
				
			||||||
type Config interface {
 | 
					type Config interface {
 | 
				
			||||||
	// Padding returns the amount of internal padding elements should have.
 | 
					 | 
				
			||||||
	// An element's inner content (such as text) should be inset by this
 | 
					 | 
				
			||||||
	// amount, in addition to the inset returned by the pattern of its
 | 
					 | 
				
			||||||
	// background.
 | 
					 | 
				
			||||||
	Padding () int
 | 
					 | 
				
			||||||
	
 | 
					 | 
				
			||||||
	// Margin returns how much space should be put in between elements.
 | 
					 | 
				
			||||||
	Margin () int
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// HandleWidth returns how large grab handles should typically be. This
 | 
						// HandleWidth returns how large grab handles should typically be. This
 | 
				
			||||||
	// is important for accessibility reasons.
 | 
						// is important for accessibility reasons.
 | 
				
			||||||
	HandleWidth () int
 | 
						HandleWidth () int
 | 
				
			||||||
@ -26,15 +17,6 @@ type Config interface {
 | 
				
			|||||||
// Default specifies default configuration values.
 | 
					// Default specifies default configuration values.
 | 
				
			||||||
type Default struct { }
 | 
					type Default struct { }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Padding returns the default padding value.
 | 
					 | 
				
			||||||
func (Default) Padding () int {
 | 
					 | 
				
			||||||
	return 7
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// Margin returns the default margin value.
 | 
					 | 
				
			||||||
func (Default) Margin () int {
 | 
					 | 
				
			||||||
	return 8
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
// HandleWidth returns the default handle width value.
 | 
					// HandleWidth returns the default handle width value.
 | 
				
			||||||
func (Default) HandleWidth () int {
 | 
					func (Default) HandleWidth () int {
 | 
				
			||||||
@ -56,19 +38,6 @@ type Wrapped struct {
 | 
				
			|||||||
	Config
 | 
						Config
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Padding returns the amount of internal padding elements should have.
 | 
					 | 
				
			||||||
// An element's inner content (such as text) should be inset by this
 | 
					 | 
				
			||||||
// amount, in addition to the inset returned by the pattern of its
 | 
					 | 
				
			||||||
// background.
 | 
					 | 
				
			||||||
func (wrapped Wrapped) Padding () int {
 | 
					 | 
				
			||||||
	return wrapped.ensure().Padding()
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// Margin returns how much space should be put in between elements.
 | 
					 | 
				
			||||||
func (wrapped Wrapped) Margin () int {
 | 
					 | 
				
			||||||
	return wrapped.ensure().Margin()
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// HandleWidth returns how large grab handles should typically be. This
 | 
					// HandleWidth returns how large grab handles should typically be. This
 | 
				
			||||||
// is important for accessibility reasons.
 | 
					// is important for accessibility reasons.
 | 
				
			||||||
func (wrapped Wrapped) HandleWidth () int {
 | 
					func (wrapped Wrapped) HandleWidth () int {
 | 
				
			||||||
 | 
				
			|||||||
@ -6,6 +6,7 @@ import "git.tebibyte.media/sashakoshka/tomo/input"
 | 
				
			|||||||
import "git.tebibyte.media/sashakoshka/tomo/theme"
 | 
					import "git.tebibyte.media/sashakoshka/tomo/theme"
 | 
				
			||||||
import "git.tebibyte.media/sashakoshka/tomo/config"
 | 
					import "git.tebibyte.media/sashakoshka/tomo/config"
 | 
				
			||||||
import "git.tebibyte.media/sashakoshka/tomo/artist"
 | 
					import "git.tebibyte.media/sashakoshka/tomo/artist"
 | 
				
			||||||
 | 
					import "git.tebibyte.media/sashakoshka/tomo/shatter"
 | 
				
			||||||
import "git.tebibyte.media/sashakoshka/tomo/textdraw"
 | 
					import "git.tebibyte.media/sashakoshka/tomo/textdraw"
 | 
				
			||||||
import "git.tebibyte.media/sashakoshka/tomo/elements/core"
 | 
					import "git.tebibyte.media/sashakoshka/tomo/elements/core"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -121,7 +122,8 @@ func (element *Button) SetConfig (new config.Config) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
func (element *Button) updateMinimumSize () {
 | 
					func (element *Button) updateMinimumSize () {
 | 
				
			||||||
	textBounds := element.drawer.LayoutBounds()
 | 
						textBounds := element.drawer.LayoutBounds()
 | 
				
			||||||
	minimumSize := textBounds.Inset(-element.config.Padding())
 | 
						padding    := element.theme.Padding(theme.PatternButton)
 | 
				
			||||||
 | 
						minimumSize := padding.Inverse().Apply(textBounds)
 | 
				
			||||||
	element.core.SetMinimumSize(minimumSize.Dx(), minimumSize.Dy())
 | 
						element.core.SetMinimumSize(minimumSize.Dx(), minimumSize.Dy())
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -138,8 +140,8 @@ func (element *Button) drawAndPush (partial bool) {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (element *Button) state () theme.PatternState {
 | 
					func (element *Button) state () theme.State {
 | 
				
			||||||
	return theme.PatternState {
 | 
						return theme.State {
 | 
				
			||||||
		Disabled: !element.Enabled(),
 | 
							Disabled: !element.Enabled(),
 | 
				
			||||||
		Focused:  element.Focused(),
 | 
							Focused:  element.Focused(),
 | 
				
			||||||
		Pressed:  element.pressed,
 | 
							Pressed:  element.pressed,
 | 
				
			||||||
@ -152,20 +154,20 @@ func (element *Button) drawBackground (partial bool) []image.Rectangle {
 | 
				
			|||||||
	pattern := element.theme.Pattern(theme.PatternButton, state)
 | 
						pattern := element.theme.Pattern(theme.PatternButton, state)
 | 
				
			||||||
	static  := element.theme.Hints(theme.PatternButton).StaticInset
 | 
						static  := element.theme.Hints(theme.PatternButton).StaticInset
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if partial && static != (theme.Inset { }) {
 | 
						if partial && static != (artist.Inset { }) {
 | 
				
			||||||
		return artist.FillRectangleShatter (
 | 
							tiles := shatter.Shatter(bounds, static.Apply(bounds))
 | 
				
			||||||
			element.core, pattern, bounds, static.Apply(bounds))
 | 
							artist.Draw(element.core, pattern, tiles...)
 | 
				
			||||||
 | 
							return tiles
 | 
				
			||||||
	} else {
 | 
						} else {
 | 
				
			||||||
		return []image.Rectangle {
 | 
							pattern.Draw(element.core, bounds)
 | 
				
			||||||
			artist.FillRectangle(element.core, pattern, bounds),
 | 
							return []image.Rectangle { bounds }
 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (element *Button) drawText (partial bool) image.Rectangle {
 | 
					func (element *Button) drawText (partial bool) image.Rectangle {
 | 
				
			||||||
	state      := element.state()
 | 
						state      := element.state()
 | 
				
			||||||
	bounds     := element.Bounds()
 | 
						bounds     := element.Bounds()
 | 
				
			||||||
	foreground := element.theme.Pattern(theme.PatternForeground, state)
 | 
						foreground := element.theme.Color(theme.ColorForeground, state)
 | 
				
			||||||
	sink       := element.theme.Sink(theme.PatternButton)
 | 
						sink       := element.theme.Sink(theme.PatternButton)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	textBounds := element.drawer.LayoutBounds()
 | 
						textBounds := element.drawer.LayoutBounds()
 | 
				
			||||||
@ -183,8 +185,7 @@ func (element *Button) drawText (partial bool) image.Rectangle {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	if partial {
 | 
						if partial {
 | 
				
			||||||
		pattern := element.theme.Pattern(theme.PatternButton, state)
 | 
							pattern := element.theme.Pattern(theme.PatternButton, state)
 | 
				
			||||||
		artist.FillRectangleClip (
 | 
							pattern.Draw(element.core, region)
 | 
				
			||||||
			element.core, pattern, bounds, region)
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
	element.drawer.Draw(element.core, foreground, offset)
 | 
						element.drawer.Draw(element.core, foreground, offset)
 | 
				
			||||||
 | 
				
			|||||||
@ -146,8 +146,9 @@ func (element *Checkbox) updateMinimumSize () {
 | 
				
			|||||||
	if element.text == "" {
 | 
						if element.text == "" {
 | 
				
			||||||
		element.core.SetMinimumSize(textBounds.Dy(), textBounds.Dy())
 | 
							element.core.SetMinimumSize(textBounds.Dy(), textBounds.Dy())
 | 
				
			||||||
	} else {
 | 
						} else {
 | 
				
			||||||
 | 
							margin := element.theme.Margin(theme.PatternBackground)
 | 
				
			||||||
		element.core.SetMinimumSize (
 | 
							element.core.SetMinimumSize (
 | 
				
			||||||
			textBounds.Dy() + element.config.Padding() + textBounds.Dx(),
 | 
								textBounds.Dy() + margin.X + textBounds.Dx(),
 | 
				
			||||||
			textBounds.Dy())
 | 
								textBounds.Dy())
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@ -163,7 +164,7 @@ func (element *Checkbox) draw () {
 | 
				
			|||||||
	bounds := element.Bounds()
 | 
						bounds := element.Bounds()
 | 
				
			||||||
	boxBounds := image.Rect(0, 0, bounds.Dy(), bounds.Dy()).Add(bounds.Min)
 | 
						boxBounds := image.Rect(0, 0, bounds.Dy(), bounds.Dy()).Add(bounds.Min)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	state := theme.PatternState {
 | 
						state := theme.State {
 | 
				
			||||||
		Disabled: !element.Enabled(),
 | 
							Disabled: !element.Enabled(),
 | 
				
			||||||
		Focused:  element.Focused(),
 | 
							Focused:  element.Focused(),
 | 
				
			||||||
		Pressed:  element.pressed,
 | 
							Pressed:  element.pressed,
 | 
				
			||||||
@ -172,19 +173,20 @@ func (element *Checkbox) draw () {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	backgroundPattern := element.theme.Pattern (
 | 
						backgroundPattern := element.theme.Pattern (
 | 
				
			||||||
		theme.PatternBackground, state)
 | 
							theme.PatternBackground, state)
 | 
				
			||||||
	artist.FillRectangle(element.core, backgroundPattern, bounds)
 | 
						backgroundPattern.Draw(element.core, bounds)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	pattern := element.theme.Pattern(theme.PatternButton, state)
 | 
						pattern := element.theme.Pattern(theme.PatternButton, state)
 | 
				
			||||||
	artist.FillRectangle(element.core, pattern, boxBounds)
 | 
						artist.DrawBounds(element.core, pattern, boxBounds)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	textBounds := element.drawer.LayoutBounds()
 | 
						textBounds := element.drawer.LayoutBounds()
 | 
				
			||||||
 | 
						margin := element.theme.Margin(theme.PatternBackground)
 | 
				
			||||||
	offset := bounds.Min.Add(image.Point {
 | 
						offset := bounds.Min.Add(image.Point {
 | 
				
			||||||
		X: bounds.Dy() + element.config.Padding(),
 | 
							X: bounds.Dy() + margin.X,
 | 
				
			||||||
	})
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	offset.Y -= textBounds.Min.Y
 | 
						offset.Y -= textBounds.Min.Y
 | 
				
			||||||
	offset.X -= textBounds.Min.X
 | 
						offset.X -= textBounds.Min.X
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	foreground := element.theme.Pattern(theme.PatternForeground, state)
 | 
						foreground := element.theme.Color(theme.ColorForeground, state)
 | 
				
			||||||
	element.drawer.Draw(element.core, foreground, offset)
 | 
						element.drawer.Draw(element.core, foreground, offset)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -226,9 +226,9 @@ func (element *Container) redoAll () {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
	pattern := element.theme.Pattern (
 | 
						pattern := element.theme.Pattern (
 | 
				
			||||||
		theme.PatternBackground,
 | 
							theme.PatternBackground,
 | 
				
			||||||
		theme.PatternState { })
 | 
							theme.State { })
 | 
				
			||||||
	artist.FillRectangleShatter (
 | 
						artist.DrawShatter (
 | 
				
			||||||
		element.core, pattern, element.Bounds(), rocks...)
 | 
							element.core, pattern, rocks...)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// cut our canvas up and give peices to child elements
 | 
						// cut our canvas up and give peices to child elements
 | 
				
			||||||
	for _, entry := range element.children {
 | 
						for _, entry := range element.children {
 | 
				
			||||||
@ -311,9 +311,11 @@ func (element *Container) HandleKeyUp (key input.Key, modifiers input.Modifiers)
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (element *Container) FlexibleHeightFor (width int) (height int) {
 | 
					func (element *Container) FlexibleHeightFor (width int) (height int) {
 | 
				
			||||||
 | 
						margin := element.theme.Margin(theme.PatternBackground)
 | 
				
			||||||
 | 
						// TODO: have layouts take in x and y margins
 | 
				
			||||||
	return element.layout.FlexibleHeightFor (
 | 
						return element.layout.FlexibleHeightFor (
 | 
				
			||||||
		element.children,
 | 
							element.children,
 | 
				
			||||||
		element.config.Margin(), width)
 | 
							margin.X, width)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (element *Container) OnFlexibleHeightChange (callback func ()) {
 | 
					func (element *Container) OnFlexibleHeightChange (callback func ()) {
 | 
				
			||||||
@ -515,16 +517,20 @@ func (element *Container) childFocusRequestCallback (
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (element *Container) updateMinimumSize () {
 | 
					func (element *Container) updateMinimumSize () {
 | 
				
			||||||
	width, height := element.layout.MinimumSize (
 | 
						margin := element.theme.Margin(theme.PatternBackground)
 | 
				
			||||||
		element.children, element.config.Margin())
 | 
						// TODO: have layouts take in x and y margins
 | 
				
			||||||
 | 
						width, height := element.layout.MinimumSize(element.children, margin.X)
 | 
				
			||||||
	if element.flexible {
 | 
						if element.flexible {
 | 
				
			||||||
		height = element.layout.FlexibleHeightFor (
 | 
							height = element.layout.FlexibleHeightFor (
 | 
				
			||||||
			element.children, element.config.Margin(), width)
 | 
								element.children,
 | 
				
			||||||
 | 
								margin.X, width)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	element.core.SetMinimumSize(width, height)
 | 
						element.core.SetMinimumSize(width, height)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (element *Container) doLayout () {
 | 
					func (element *Container) doLayout () {
 | 
				
			||||||
 | 
						margin := element.theme.Margin(theme.PatternBackground)
 | 
				
			||||||
 | 
						// TODO: have layouts take in x and y margins
 | 
				
			||||||
	element.layout.Arrange (
 | 
						element.layout.Arrange (
 | 
				
			||||||
		element.children, element.config.Margin(), element.Bounds())
 | 
							element.children, margin.X, element.Bounds())
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -1,17 +1,18 @@
 | 
				
			|||||||
package basicElements
 | 
					package basicElements
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import "image"
 | 
					import "image"
 | 
				
			||||||
import "git.tebibyte.media/sashakoshka/tomo/artist"
 | 
					import "git.tebibyte.media/sashakoshka/tomo/canvas"
 | 
				
			||||||
import "git.tebibyte.media/sashakoshka/tomo/elements/core"
 | 
					import "git.tebibyte.media/sashakoshka/tomo/elements/core"
 | 
				
			||||||
 | 
					import "git.tebibyte.media/sashakoshka/tomo/artist/patterns"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type Image struct {
 | 
					type Image struct {
 | 
				
			||||||
	*core.Core
 | 
						*core.Core
 | 
				
			||||||
	core core.CoreControl
 | 
						core core.CoreControl
 | 
				
			||||||
	buffer artist.Pattern
 | 
						buffer canvas.Canvas
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func NewImage (image image.Image) (element *Image) {
 | 
					func NewImage (image image.Image) (element *Image) {
 | 
				
			||||||
	element = &Image { buffer: artist.NewTexture(image) }
 | 
						element = &Image { buffer: canvas.FromImage(image) }
 | 
				
			||||||
	element.Core, element.core = core.NewCore(element.draw)
 | 
						element.Core, element.core = core.NewCore(element.draw)
 | 
				
			||||||
	bounds := image.Bounds()
 | 
						bounds := image.Bounds()
 | 
				
			||||||
	element.core.SetMinimumSize(bounds.Dx(), bounds.Dy())
 | 
						element.core.SetMinimumSize(bounds.Dx(), bounds.Dy())
 | 
				
			||||||
@ -19,5 +20,6 @@ func NewImage (image image.Image) (element *Image) {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (element *Image) draw () {
 | 
					func (element *Image) draw () {
 | 
				
			||||||
	artist.FillRectangle(element.core, element.buffer, element.Bounds())
 | 
						(patterns.Texture { Canvas: element.buffer }).
 | 
				
			||||||
 | 
							Draw(element.core, element.Bounds())
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -2,7 +2,6 @@ package basicElements
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import "git.tebibyte.media/sashakoshka/tomo/theme"
 | 
					import "git.tebibyte.media/sashakoshka/tomo/theme"
 | 
				
			||||||
import "git.tebibyte.media/sashakoshka/tomo/config"
 | 
					import "git.tebibyte.media/sashakoshka/tomo/config"
 | 
				
			||||||
import "git.tebibyte.media/sashakoshka/tomo/artist"
 | 
					 | 
				
			||||||
import "git.tebibyte.media/sashakoshka/tomo/textdraw"
 | 
					import "git.tebibyte.media/sashakoshka/tomo/textdraw"
 | 
				
			||||||
import "git.tebibyte.media/sashakoshka/tomo/elements/core"
 | 
					import "git.tebibyte.media/sashakoshka/tomo/elements/core"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -137,7 +136,9 @@ func (element *Label) SetConfig (new config.Config) {
 | 
				
			|||||||
func (element *Label) updateMinimumSize () {
 | 
					func (element *Label) updateMinimumSize () {
 | 
				
			||||||
	if element.wrap {
 | 
						if element.wrap {
 | 
				
			||||||
		em := element.drawer.Em().Round()
 | 
							em := element.drawer.Em().Round()
 | 
				
			||||||
		if em < 1 { em = element.config.Padding() }
 | 
							if em < 1 {
 | 
				
			||||||
 | 
								em = element.theme.Padding(theme.PatternBackground)[0]
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
		element.core.SetMinimumSize (
 | 
							element.core.SetMinimumSize (
 | 
				
			||||||
			em, element.drawer.LineHeight().Round())
 | 
								em, element.drawer.LineHeight().Round())
 | 
				
			||||||
		if element.onFlexibleHeightChange != nil {
 | 
							if element.onFlexibleHeightChange != nil {
 | 
				
			||||||
@ -154,13 +155,13 @@ func (element *Label) draw () {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	pattern := element.theme.Pattern (
 | 
						pattern := element.theme.Pattern (
 | 
				
			||||||
		theme.PatternBackground,
 | 
							theme.PatternBackground,
 | 
				
			||||||
		theme.PatternState { })
 | 
							theme.State { })
 | 
				
			||||||
	artist.FillRectangle(element.core, pattern, bounds)
 | 
						pattern.Draw(element.core, bounds)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	textBounds := element.drawer.LayoutBounds()
 | 
						textBounds := element.drawer.LayoutBounds()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	foreground :=  element.theme.Pattern (
 | 
						foreground :=  element.theme.Color (
 | 
				
			||||||
		theme.PatternForeground,
 | 
							theme.ColorForeground,
 | 
				
			||||||
		theme.PatternState { })
 | 
							theme.State { })
 | 
				
			||||||
	element.drawer.Draw(element.core, foreground, bounds.Min.Sub(textBounds.Min))
 | 
						element.drawer.Draw(element.core, foreground, bounds.Min.Sub(textBounds.Min))
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -221,8 +221,8 @@ func (element *List) ScrollAxes () (horizontal, vertical bool) {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (element *List) scrollViewportHeight () (height int) {
 | 
					func (element *List) scrollViewportHeight () (height int) {
 | 
				
			||||||
	inset := element.theme.Inset(theme.PatternSunken)
 | 
						padding := element.theme.Padding(theme.PatternSunken)
 | 
				
			||||||
	return element.Bounds().Dy() - inset[0] - inset[2]
 | 
						return element.Bounds().Dy() - padding[0] - padding[2]
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (element *List) maxScrollHeight () (height int) {
 | 
					func (element *List) maxScrollHeight () (height int) {
 | 
				
			||||||
@ -355,8 +355,8 @@ func (element *List) Select (index int) {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (element *List) selectUnderMouse (x, y int) (updated bool) {
 | 
					func (element *List) selectUnderMouse (x, y int) (updated bool) {
 | 
				
			||||||
	inset := element.theme.Inset(theme.PatternSunken)
 | 
						padding := element.theme.Padding(theme.PatternSunken)
 | 
				
			||||||
	bounds := inset.Apply(element.Bounds())
 | 
						bounds := padding.Apply(element.Bounds())
 | 
				
			||||||
	mousePoint := image.Pt(x, y)
 | 
						mousePoint := image.Pt(x, y)
 | 
				
			||||||
	dot := image.Pt (
 | 
						dot := image.Pt (
 | 
				
			||||||
		bounds.Min.X,
 | 
							bounds.Min.X,
 | 
				
			||||||
@ -398,8 +398,8 @@ func (element *List) changeSelectionBy (delta int) (updated bool) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
func (element *List) resizeEntryToFit (entry ListEntry) (resized ListEntry) {
 | 
					func (element *List) resizeEntryToFit (entry ListEntry) (resized ListEntry) {
 | 
				
			||||||
	bounds := element.Bounds()
 | 
						bounds := element.Bounds()
 | 
				
			||||||
	inset := element.theme.Inset(theme.PatternSunken)
 | 
						padding := element.theme.Padding(theme.PatternSunken)
 | 
				
			||||||
	entry.Resize(inset.Apply(bounds).Dx())
 | 
						entry.Resize(padding.Apply(bounds).Dx())
 | 
				
			||||||
	return entry
 | 
						return entry
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -425,17 +425,17 @@ func (element *List) updateMinimumSize () {
 | 
				
			|||||||
		minimumHeight = element.contentHeight
 | 
							minimumHeight = element.contentHeight
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	inset := element.theme.Inset(theme.PatternSunken)
 | 
						padding := element.theme.Padding(theme.PatternSunken)
 | 
				
			||||||
	minimumHeight += inset[0] + inset[2]
 | 
						minimumHeight += padding[0] + padding[2]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	element.core.SetMinimumSize(minimumWidth, minimumHeight)
 | 
						element.core.SetMinimumSize(minimumWidth, minimumHeight)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (element *List) draw () {
 | 
					func (element *List) draw () {
 | 
				
			||||||
	bounds      := element.Bounds()
 | 
						bounds      := element.Bounds()
 | 
				
			||||||
	inset       := element.theme.Inset(theme.PatternSunken)
 | 
						padding     := element.theme.Padding(theme.PatternSunken)
 | 
				
			||||||
	innerBounds := inset.Apply(bounds)
 | 
						innerBounds := padding.Apply(bounds)
 | 
				
			||||||
	state := theme.PatternState {
 | 
						state := theme.State {
 | 
				
			||||||
		Disabled: !element.Enabled(),
 | 
							Disabled: !element.Enabled(),
 | 
				
			||||||
		Focused: element.Focused(),
 | 
							Focused: element.Focused(),
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@ -460,6 +460,6 @@ func (element *List) draw () {
 | 
				
			|||||||
		innerBounds.Dx(), element.contentHeight,
 | 
							innerBounds.Dx(), element.contentHeight,
 | 
				
			||||||
	).Add(innerBounds.Min).Intersect(innerBounds)
 | 
						).Add(innerBounds.Min).Intersect(innerBounds)
 | 
				
			||||||
	pattern := element.theme.Pattern(theme.PatternSunken, state)
 | 
						pattern := element.theme.Pattern(theme.PatternSunken, state)
 | 
				
			||||||
	artist.FillRectangleShatter (
 | 
						artist.DrawShatter (
 | 
				
			||||||
		element.core, pattern, bounds, covered)
 | 
							element.core, pattern, covered)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -47,8 +47,8 @@ func (entry *ListEntry) SetConfig (new config.Config) {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (entry *ListEntry) updateBounds () {
 | 
					func (entry *ListEntry) updateBounds () {
 | 
				
			||||||
	inset := entry.theme.Inset(theme.PatternRaised)
 | 
						padding := entry.theme.Padding(theme.PatternRaised)
 | 
				
			||||||
	entry.bounds = inset.Inverse().Apply(entry.drawer.LayoutBounds())
 | 
						entry.bounds = padding.Inverse().Apply(entry.drawer.LayoutBounds())
 | 
				
			||||||
	entry.bounds = entry.bounds.Sub(entry.bounds.Min)
 | 
						entry.bounds = entry.bounds.Sub(entry.bounds.Min)
 | 
				
			||||||
	entry.minimumWidth = entry.bounds.Dx()
 | 
						entry.minimumWidth = entry.bounds.Dx()
 | 
				
			||||||
	entry.bounds.Max.X = entry.width
 | 
						entry.bounds.Max.X = entry.width
 | 
				
			||||||
@ -62,23 +62,21 @@ func (entry *ListEntry) Draw (
 | 
				
			|||||||
) (
 | 
					) (
 | 
				
			||||||
	updatedRegion image.Rectangle,
 | 
						updatedRegion image.Rectangle,
 | 
				
			||||||
) {
 | 
					) {
 | 
				
			||||||
	state := theme.PatternState {
 | 
						state := theme.State {
 | 
				
			||||||
		Focused: focused,
 | 
							Focused: focused,
 | 
				
			||||||
		On: on,
 | 
							On: on,
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	pattern := entry.theme.Pattern (theme.PatternRaised, state)
 | 
						pattern := entry.theme.Pattern(theme.PatternRaised, state)
 | 
				
			||||||
	inset := entry.theme.Inset(theme.PatternRaised)
 | 
						padding := entry.theme.Padding(theme.PatternRaised)
 | 
				
			||||||
	artist.FillRectangle (
 | 
						bounds  := entry.Bounds().Add(offset)
 | 
				
			||||||
		destination,
 | 
						artist.DrawBounds(destination, pattern, bounds)
 | 
				
			||||||
		pattern,
 | 
					 | 
				
			||||||
		entry.Bounds().Add(offset))	
 | 
					 | 
				
			||||||
		
 | 
							
 | 
				
			||||||
	foreground := entry.theme.Pattern (theme.PatternForeground, state)
 | 
						foreground := entry.theme.Color (theme.ColorForeground, state)
 | 
				
			||||||
	return entry.drawer.Draw (
 | 
						return entry.drawer.Draw (
 | 
				
			||||||
		destination,
 | 
							destination,
 | 
				
			||||||
		foreground,
 | 
							foreground,
 | 
				
			||||||
		offset.Add(image.Pt(inset[3], inset[0])).
 | 
							offset.Add(image.Pt(padding[artist.SideLeft], padding[artist.SideTop])).
 | 
				
			||||||
		Sub(entry.drawer.LayoutBounds().Min))
 | 
							Sub(entry.drawer.LayoutBounds().Min))
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -52,9 +52,11 @@ func (element *ProgressBar) SetConfig (new config.Config) {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (element (ProgressBar)) updateMinimumSize() {
 | 
					func (element (ProgressBar)) updateMinimumSize() {
 | 
				
			||||||
 | 
						padding      := element.theme.Padding(theme.PatternSunken)
 | 
				
			||||||
 | 
						innerPadding := element.theme.Padding(theme.PatternMercury)
 | 
				
			||||||
	element.core.SetMinimumSize (
 | 
						element.core.SetMinimumSize (
 | 
				
			||||||
		element.config.Padding() * 2,
 | 
							padding.Horizontal() + innerPadding.Horizontal(),
 | 
				
			||||||
		element.config.Padding() * 2)
 | 
							padding.Vertical()   + innerPadding.Vertical())
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (element *ProgressBar) redo () {
 | 
					func (element *ProgressBar) redo () {
 | 
				
			||||||
@ -67,18 +69,14 @@ func (element *ProgressBar) redo () {
 | 
				
			|||||||
func (element *ProgressBar) draw () {
 | 
					func (element *ProgressBar) draw () {
 | 
				
			||||||
	bounds := element.Bounds()
 | 
						bounds := element.Bounds()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	pattern := element.theme.Pattern (
 | 
						pattern := element.theme.Pattern(theme.PatternSunken, theme.State { })
 | 
				
			||||||
		theme.PatternSunken,
 | 
						padding := element.theme.Padding(theme.PatternSunken)
 | 
				
			||||||
		theme.PatternState { })
 | 
						pattern.Draw(element.core, bounds)
 | 
				
			||||||
	inset := element.theme.Inset(theme.PatternSunken)
 | 
						bounds = padding.Apply(bounds)
 | 
				
			||||||
	artist.FillRectangle(element.core, pattern, bounds)
 | 
					 | 
				
			||||||
	bounds = inset.Apply(bounds)
 | 
					 | 
				
			||||||
	meterBounds := image.Rect (
 | 
						meterBounds := image.Rect (
 | 
				
			||||||
		bounds.Min.X, bounds.Min.Y,
 | 
							bounds.Min.X, bounds.Min.Y,
 | 
				
			||||||
		bounds.Min.X + int(float64(bounds.Dx()) * element.progress),
 | 
							bounds.Min.X + int(float64(bounds.Dx()) * element.progress),
 | 
				
			||||||
		bounds.Max.Y)
 | 
							bounds.Max.Y)
 | 
				
			||||||
	accent := element.theme.Pattern (
 | 
						mercury := element.theme.Pattern(theme.PatternMercury, theme.State { })
 | 
				
			||||||
		theme.PatternAccent,
 | 
						artist.DrawBounds(element.core, mercury, meterBounds)
 | 
				
			||||||
		theme.PatternState { })
 | 
					 | 
				
			||||||
	artist.FillRectangle(element.core, accent, meterBounds)
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -302,7 +302,7 @@ func (element *ScrollContainer) OnFocusMotionRequest (
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (element *ScrollContainer) childDamageCallback (region canvas.Canvas) {
 | 
					func (element *ScrollContainer) childDamageCallback (region canvas.Canvas) {
 | 
				
			||||||
	element.core.DamageRegion(artist.Paste(element.core, region, image.Point { }))
 | 
						element.core.DamageRegion(region.Bounds())
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (element *ScrollContainer) childFocusRequestCallback () (granted bool) {
 | 
					func (element *ScrollContainer) childFocusRequestCallback () (granted bool) {
 | 
				
			||||||
@ -352,8 +352,8 @@ func (element *ScrollContainer) recalculate () {
 | 
				
			|||||||
	horizontal := &element.horizontal
 | 
						horizontal := &element.horizontal
 | 
				
			||||||
	vertical   := &element.vertical
 | 
						vertical   := &element.vertical
 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
	gutterInsetHorizontal := horizontal.theme.Inset(theme.PatternGutter)
 | 
						gutterInsetHorizontal := horizontal.theme.Padding(theme.PatternGutter)
 | 
				
			||||||
	gutterInsetVertical   := vertical.theme.Inset(theme.PatternGutter)
 | 
						gutterInsetVertical   := vertical.theme.Padding(theme.PatternGutter)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	bounds     := element.Bounds()
 | 
						bounds     := element.Bounds()
 | 
				
			||||||
	thicknessHorizontal :=
 | 
						thicknessHorizontal :=
 | 
				
			||||||
@ -438,8 +438,8 @@ func (element *ScrollContainer) recalculate () {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
func (element *ScrollContainer) draw () {
 | 
					func (element *ScrollContainer) draw () {
 | 
				
			||||||
	deadPattern := element.theme.Pattern (
 | 
						deadPattern := element.theme.Pattern (
 | 
				
			||||||
		theme.PatternDead, theme.PatternState { })
 | 
							theme.PatternDead, theme.State { })
 | 
				
			||||||
	artist.FillRectangle (
 | 
						artist.DrawBounds (
 | 
				
			||||||
		element.core, deadPattern,
 | 
							element.core, deadPattern,
 | 
				
			||||||
		image.Rect (
 | 
							image.Rect (
 | 
				
			||||||
			element.vertical.gutter.Min.X,
 | 
								element.vertical.gutter.Min.X,
 | 
				
			||||||
@ -451,27 +451,27 @@ func (element *ScrollContainer) draw () {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (element *ScrollContainer) drawHorizontalBar () {
 | 
					func (element *ScrollContainer) drawHorizontalBar () {
 | 
				
			||||||
	state := theme.PatternState {
 | 
						state := theme.State {
 | 
				
			||||||
		Disabled: !element.horizontal.enabled,
 | 
							Disabled: !element.horizontal.enabled,
 | 
				
			||||||
		Pressed:  element.horizontal.dragging,
 | 
							Pressed:  element.horizontal.dragging,
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	gutterPattern := element.horizontal.theme.Pattern(theme.PatternGutter, state)
 | 
						gutterPattern := element.horizontal.theme.Pattern(theme.PatternGutter, state)
 | 
				
			||||||
	artist.FillRectangle(element.core, gutterPattern, element.horizontal.gutter)
 | 
						artist.DrawBounds(element.core, gutterPattern, element.horizontal.gutter)
 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
	handlePattern := element.horizontal.theme.Pattern(theme.PatternHandle, state)
 | 
						handlePattern := element.horizontal.theme.Pattern(theme.PatternHandle, state)
 | 
				
			||||||
	artist.FillRectangle(element.core, handlePattern, element.horizontal.bar)
 | 
						artist.DrawBounds(element.core, handlePattern, element.horizontal.bar)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (element *ScrollContainer) drawVerticalBar () {
 | 
					func (element *ScrollContainer) drawVerticalBar () {
 | 
				
			||||||
	state := theme.PatternState {
 | 
						state := theme.State {
 | 
				
			||||||
		Disabled: !element.vertical.enabled,
 | 
							Disabled: !element.vertical.enabled,
 | 
				
			||||||
		Pressed:  element.vertical.dragging,
 | 
							Pressed:  element.vertical.dragging,
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	gutterPattern := element.vertical.theme.Pattern(theme.PatternGutter, state)
 | 
						gutterPattern := element.vertical.theme.Pattern(theme.PatternGutter, state)
 | 
				
			||||||
	artist.FillRectangle(element.core, gutterPattern, element.vertical.gutter)
 | 
						artist.DrawBounds(element.core, gutterPattern, element.vertical.gutter)
 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
	handlePattern := element.vertical.theme.Pattern(theme.PatternHandle, state)
 | 
						handlePattern := element.vertical.theme.Pattern(theme.PatternHandle, state)
 | 
				
			||||||
	artist.FillRectangle(element.core, handlePattern, element.vertical.bar)
 | 
						artist.DrawBounds(element.core, handlePattern, element.vertical.bar)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (element *ScrollContainer) dragHorizontalBar (mousePosition image.Point) {
 | 
					func (element *ScrollContainer) dragHorizontalBar (mousePosition image.Point) {
 | 
				
			||||||
@ -493,8 +493,8 @@ func (element *ScrollContainer) dragVerticalBar (mousePosition image.Point) {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (element *ScrollContainer) updateMinimumSize () {
 | 
					func (element *ScrollContainer) updateMinimumSize () {
 | 
				
			||||||
	gutterInsetHorizontal := element.horizontal.theme.Inset(theme.PatternGutter)
 | 
						gutterInsetHorizontal := element.horizontal.theme.Padding(theme.PatternGutter)
 | 
				
			||||||
	gutterInsetVertical   := element.vertical.theme.Inset(theme.PatternGutter)
 | 
						gutterInsetVertical   := element.vertical.theme.Padding(theme.PatternGutter)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	thicknessHorizontal :=
 | 
						thicknessHorizontal :=
 | 
				
			||||||
		element.config.HandleWidth() +
 | 
							element.config.HandleWidth() +
 | 
				
			||||||
 | 
				
			|||||||
@ -173,7 +173,7 @@ func (element *Slider) redo () {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
func (element *Slider) draw () {
 | 
					func (element *Slider) draw () {
 | 
				
			||||||
	bounds := element.Bounds()
 | 
						bounds := element.Bounds()
 | 
				
			||||||
	element.track = element.theme.Inset(theme.PatternGutter).Apply(bounds)
 | 
						element.track = element.theme.Padding(theme.PatternGutter).Apply(bounds)
 | 
				
			||||||
	if element.vertical {
 | 
						if element.vertical {
 | 
				
			||||||
		barSize := element.track.Dx()
 | 
							barSize := element.track.Dx()
 | 
				
			||||||
		element.bar = image.Rect(0, 0, barSize, barSize).Add(bounds.Min)
 | 
							element.bar = image.Rect(0, 0, barSize, barSize).Add(bounds.Min)
 | 
				
			||||||
@ -190,16 +190,16 @@ func (element *Slider) draw () {
 | 
				
			|||||||
		element.bar = element.bar.Add(image.Pt(int(barOffset), 0))
 | 
							element.bar = element.bar.Add(image.Pt(int(barOffset), 0))
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	state := theme.PatternState {
 | 
						state := theme.State {
 | 
				
			||||||
		Focused:  element.Focused(),
 | 
							Focused:  element.Focused(),
 | 
				
			||||||
		Disabled: !element.Enabled(),
 | 
							Disabled: !element.Enabled(),
 | 
				
			||||||
		Pressed:  element.dragging,
 | 
							Pressed:  element.dragging,
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	artist.FillRectangle (
 | 
						artist.DrawBounds (
 | 
				
			||||||
		element.core,
 | 
							element.core,
 | 
				
			||||||
		element.theme.Pattern(theme.PatternGutter, state),
 | 
							element.theme.Pattern(theme.PatternGutter, state),
 | 
				
			||||||
		bounds)
 | 
							bounds)
 | 
				
			||||||
	artist.FillRectangle (
 | 
						artist.DrawBounds (
 | 
				
			||||||
		element.core,
 | 
							element.core,
 | 
				
			||||||
		element.theme.Pattern(theme.PatternHandle, state),
 | 
							element.theme.Pattern(theme.PatternHandle, state),
 | 
				
			||||||
		element.bar)
 | 
							element.bar)
 | 
				
			||||||
 | 
				
			|||||||
@ -2,7 +2,6 @@ package basicElements
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import "git.tebibyte.media/sashakoshka/tomo/theme"
 | 
					import "git.tebibyte.media/sashakoshka/tomo/theme"
 | 
				
			||||||
import "git.tebibyte.media/sashakoshka/tomo/config"
 | 
					import "git.tebibyte.media/sashakoshka/tomo/config"
 | 
				
			||||||
import "git.tebibyte.media/sashakoshka/tomo/artist"
 | 
					 | 
				
			||||||
import "git.tebibyte.media/sashakoshka/tomo/elements/core"
 | 
					import "git.tebibyte.media/sashakoshka/tomo/elements/core"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Spacer can be used to put space between two elements..
 | 
					// Spacer can be used to put space between two elements..
 | 
				
			||||||
@ -22,7 +21,7 @@ func NewSpacer (line bool) (element *Spacer) {
 | 
				
			|||||||
	element = &Spacer { line: line }
 | 
						element = &Spacer { line: line }
 | 
				
			||||||
	element.theme.Case = theme.C("basic", "spacer")
 | 
						element.theme.Case = theme.C("basic", "spacer")
 | 
				
			||||||
	element.Core, element.core = core.NewCore(element.draw)
 | 
						element.Core, element.core = core.NewCore(element.draw)
 | 
				
			||||||
	element.core.SetMinimumSize(1, 1)
 | 
						element.updateMinimumSize()
 | 
				
			||||||
	return
 | 
						return
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -30,6 +29,7 @@ func NewSpacer (line bool) (element *Spacer) {
 | 
				
			|||||||
func (element *Spacer) SetLine (line bool) {
 | 
					func (element *Spacer) SetLine (line bool) {
 | 
				
			||||||
	if element.line == line { return }
 | 
						if element.line == line { return }
 | 
				
			||||||
	element.line = line
 | 
						element.line = line
 | 
				
			||||||
 | 
						element.updateMinimumSize()
 | 
				
			||||||
	if element.core.HasImage() {
 | 
						if element.core.HasImage() {
 | 
				
			||||||
		element.draw()
 | 
							element.draw()
 | 
				
			||||||
		element.core.DamageAll()
 | 
							element.core.DamageAll()
 | 
				
			||||||
@ -50,6 +50,17 @@ func (element *Spacer) SetConfig (new config.Config) {
 | 
				
			|||||||
	element.redo()
 | 
						element.redo()
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (element *Spacer) updateMinimumSize () {
 | 
				
			||||||
 | 
						if element.line {
 | 
				
			||||||
 | 
							padding := element.theme.Padding(theme.PatternLine)
 | 
				
			||||||
 | 
							element.core.SetMinimumSize (
 | 
				
			||||||
 | 
								padding.Horizontal(),
 | 
				
			||||||
 | 
								padding.Vertical())
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							element.core.SetMinimumSize(1, 1)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (element *Spacer) redo () {
 | 
					func (element *Spacer) redo () {
 | 
				
			||||||
	if !element.core.HasImage() {
 | 
						if !element.core.HasImage() {
 | 
				
			||||||
		element.draw()
 | 
							element.draw()
 | 
				
			||||||
@ -62,13 +73,13 @@ func (element *Spacer) draw () {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	if element.line {
 | 
						if element.line {
 | 
				
			||||||
		pattern := element.theme.Pattern (
 | 
							pattern := element.theme.Pattern (
 | 
				
			||||||
			theme.PatternForeground,
 | 
								theme.PatternLine,
 | 
				
			||||||
			theme.PatternState { })
 | 
								theme.State { })
 | 
				
			||||||
		artist.FillRectangle(element.core, pattern, bounds)
 | 
							pattern.Draw(element.core, bounds)
 | 
				
			||||||
	} else {
 | 
						} else {
 | 
				
			||||||
		pattern := element.theme.Pattern (
 | 
							pattern := element.theme.Pattern (
 | 
				
			||||||
			theme.PatternBackground,
 | 
								theme.PatternBackground,
 | 
				
			||||||
			theme.PatternState { })
 | 
								theme.State { })
 | 
				
			||||||
		artist.FillRectangle(element.core, pattern, bounds)
 | 
							pattern.Draw(element.core, bounds)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -149,7 +149,7 @@ func (element *Switch) updateMinimumSize () {
 | 
				
			|||||||
	} else {
 | 
						} else {
 | 
				
			||||||
		element.core.SetMinimumSize (
 | 
							element.core.SetMinimumSize (
 | 
				
			||||||
			lineHeight * 2 +
 | 
								lineHeight * 2 +
 | 
				
			||||||
			element.config.Padding() +
 | 
								element.theme.Margin(theme.PatternBackground).X +
 | 
				
			||||||
			textBounds.Dx(),
 | 
								textBounds.Dx(),
 | 
				
			||||||
			lineHeight)
 | 
								lineHeight)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@ -160,14 +160,14 @@ func (element *Switch) draw () {
 | 
				
			|||||||
	handleBounds := image.Rect(0, 0, bounds.Dy(), bounds.Dy()).Add(bounds.Min)
 | 
						handleBounds := image.Rect(0, 0, bounds.Dy(), bounds.Dy()).Add(bounds.Min)
 | 
				
			||||||
	gutterBounds := image.Rect(0, 0, bounds.Dy() * 2, bounds.Dy()).Add(bounds.Min)
 | 
						gutterBounds := image.Rect(0, 0, bounds.Dy() * 2, bounds.Dy()).Add(bounds.Min)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	state := theme.PatternState {
 | 
						state := theme.State {
 | 
				
			||||||
		Disabled: !element.Enabled(),
 | 
							Disabled: !element.Enabled(),
 | 
				
			||||||
		Focused:  element.Focused(),
 | 
							Focused:  element.Focused(),
 | 
				
			||||||
		Pressed:  element.pressed,
 | 
							Pressed:  element.pressed,
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	backgroundPattern := element.theme.Pattern (
 | 
						backgroundPattern := element.theme.Pattern (
 | 
				
			||||||
		theme.PatternBackground, state)
 | 
							theme.PatternBackground, state)
 | 
				
			||||||
	artist.FillRectangle (element.core, backgroundPattern, bounds)
 | 
						backgroundPattern.Draw(element.core, bounds)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if element.checked {
 | 
						if element.checked {
 | 
				
			||||||
		handleBounds.Min.X += bounds.Dy()
 | 
							handleBounds.Min.X += bounds.Dy()
 | 
				
			||||||
@ -185,21 +185,22 @@ func (element *Switch) draw () {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	gutterPattern := element.theme.Pattern (
 | 
						gutterPattern := element.theme.Pattern (
 | 
				
			||||||
		theme.PatternGutter, state)
 | 
							theme.PatternGutter, state)
 | 
				
			||||||
	artist.FillRectangle(element.core, gutterPattern, gutterBounds)
 | 
						artist.DrawBounds(element.core, gutterPattern, gutterBounds)
 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
	handlePattern := element.theme.Pattern (
 | 
						handlePattern := element.theme.Pattern (
 | 
				
			||||||
		theme.PatternHandle, state)
 | 
							theme.PatternHandle, state)
 | 
				
			||||||
	artist.FillRectangle(element.core, handlePattern, handleBounds)
 | 
						artist.DrawBounds(element.core, handlePattern, handleBounds)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	textBounds := element.drawer.LayoutBounds()
 | 
						textBounds := element.drawer.LayoutBounds()
 | 
				
			||||||
	offset := bounds.Min.Add(image.Point {
 | 
						offset := bounds.Min.Add(image.Point {
 | 
				
			||||||
		X: bounds.Dy() * 2 + element.config.Padding(),
 | 
							X: bounds.Dy() * 2 +
 | 
				
			||||||
 | 
								element.theme.Margin(theme.PatternBackground).X,
 | 
				
			||||||
	})
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	offset.Y -= textBounds.Min.Y
 | 
						offset.Y -= textBounds.Min.Y
 | 
				
			||||||
	offset.X -= textBounds.Min.X
 | 
						offset.X -= textBounds.Min.X
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	foreground := element.theme.Pattern (
 | 
						foreground := element.theme.Color (
 | 
				
			||||||
		theme.PatternForeground, state)
 | 
							theme.ColorForeground, state)
 | 
				
			||||||
	element.drawer.Draw(element.core, foreground, offset)
 | 
						element.drawer.Draw(element.core, foreground, offset)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -9,6 +9,7 @@ import "git.tebibyte.media/sashakoshka/tomo/canvas"
 | 
				
			|||||||
import "git.tebibyte.media/sashakoshka/tomo/textdraw"
 | 
					import "git.tebibyte.media/sashakoshka/tomo/textdraw"
 | 
				
			||||||
import "git.tebibyte.media/sashakoshka/tomo/textmanip"
 | 
					import "git.tebibyte.media/sashakoshka/tomo/textmanip"
 | 
				
			||||||
import "git.tebibyte.media/sashakoshka/tomo/fixedutil"
 | 
					import "git.tebibyte.media/sashakoshka/tomo/fixedutil"
 | 
				
			||||||
 | 
					import "git.tebibyte.media/sashakoshka/tomo/artist/shapes"
 | 
				
			||||||
import "git.tebibyte.media/sashakoshka/tomo/elements/core"
 | 
					import "git.tebibyte.media/sashakoshka/tomo/elements/core"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// TextBox is a single-line text input.
 | 
					// TextBox is a single-line text input.
 | 
				
			||||||
@ -92,9 +93,10 @@ func (element *TextBox) HandleMouseMove (x, y int) {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (element *TextBox) atPosition (position image.Point) int {
 | 
					func (element *TextBox) atPosition (position image.Point) int {
 | 
				
			||||||
 | 
						padding := element.theme.Padding(theme.PatternInput)
 | 
				
			||||||
	offset := element.Bounds().Min.Add (image.Pt (
 | 
						offset := element.Bounds().Min.Add (image.Pt (
 | 
				
			||||||
		element.config.Padding() - element.scroll,
 | 
							padding[artist.SideLeft] - element.scroll,
 | 
				
			||||||
		element.config.Padding()))
 | 
							padding[artist.SideTop]))
 | 
				
			||||||
	textBoundsMin := element.valueDrawer.LayoutBounds().Min
 | 
						textBoundsMin := element.valueDrawer.LayoutBounds().Min
 | 
				
			||||||
	return element.valueDrawer.AtPosition (
 | 
						return element.valueDrawer.AtPosition (
 | 
				
			||||||
		fixedutil.Pt(position.Sub(offset).Add(textBoundsMin)))
 | 
							fixedutil.Pt(position.Sub(offset).Add(textBoundsMin)))
 | 
				
			||||||
@ -251,7 +253,8 @@ func (element *TextBox) ScrollViewportBounds () (bounds image.Rectangle) {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (element *TextBox) scrollViewportWidth () (width int) {
 | 
					func (element *TextBox) scrollViewportWidth () (width int) {
 | 
				
			||||||
	return element.Bounds().Inset(element.config.Padding()).Dx()
 | 
						padding := element.theme.Padding(theme.PatternInput)
 | 
				
			||||||
 | 
						return padding.Apply(element.Bounds()).Dx()
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// ScrollTo scrolls the viewport to the specified point relative to
 | 
					// ScrollTo scrolls the viewport to the specified point relative to
 | 
				
			||||||
@ -290,7 +293,8 @@ func (element *TextBox) runOnChange () {
 | 
				
			|||||||
func (element *TextBox) scrollToCursor () {
 | 
					func (element *TextBox) scrollToCursor () {
 | 
				
			||||||
	if !element.core.HasImage() { return }
 | 
						if !element.core.HasImage() { return }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	bounds := element.Bounds().Inset(element.config.Padding())
 | 
						padding := element.theme.Padding(theme.PatternInput)
 | 
				
			||||||
 | 
						bounds  := padding.Apply(element.Bounds())
 | 
				
			||||||
	bounds = bounds.Sub(bounds.Min)
 | 
						bounds = bounds.Sub(bounds.Min)
 | 
				
			||||||
	bounds.Max.X -= element.valueDrawer.Em().Round()
 | 
						bounds.Max.X -= element.valueDrawer.Em().Round()
 | 
				
			||||||
	cursorPosition := fixedutil.RoundPt (
 | 
						cursorPosition := fixedutil.RoundPt (
 | 
				
			||||||
@ -329,11 +333,11 @@ func (element *TextBox) SetConfig (new config.Config) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
func (element *TextBox) updateMinimumSize () {
 | 
					func (element *TextBox) updateMinimumSize () {
 | 
				
			||||||
	textBounds := element.placeholderDrawer.LayoutBounds()
 | 
						textBounds := element.placeholderDrawer.LayoutBounds()
 | 
				
			||||||
 | 
						padding := element.theme.Padding(theme.PatternInput)
 | 
				
			||||||
	element.core.SetMinimumSize (
 | 
						element.core.SetMinimumSize (
 | 
				
			||||||
		textBounds.Dx() +
 | 
							padding.Horizontal() + textBounds.Dx(),
 | 
				
			||||||
		element.config.Padding() * 2,
 | 
							padding.Vertical()   +
 | 
				
			||||||
		element.placeholderDrawer.LineHeight().Round() +
 | 
							element.placeholderDrawer.LineHeight().Round())
 | 
				
			||||||
		element.config.Padding() * 2)
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (element *TextBox) redo () {
 | 
					func (element *TextBox) redo () {
 | 
				
			||||||
@ -346,30 +350,29 @@ func (element *TextBox) redo () {
 | 
				
			|||||||
func (element *TextBox) draw () {
 | 
					func (element *TextBox) draw () {
 | 
				
			||||||
	bounds := element.Bounds()
 | 
						bounds := element.Bounds()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	state := theme.PatternState {
 | 
						state := theme.State {
 | 
				
			||||||
		Disabled: !element.Enabled(),
 | 
							Disabled: !element.Enabled(),
 | 
				
			||||||
		Focused:  element.Focused(),
 | 
							Focused:  element.Focused(),
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	pattern := element.theme.Pattern(theme.PatternSunken, state)
 | 
						pattern := element.theme.Pattern(theme.PatternInput, state)
 | 
				
			||||||
	inset   := element.theme.Inset(theme.PatternSunken)
 | 
						padding := element.theme.Padding(theme.PatternInput)
 | 
				
			||||||
	innerCanvas := canvas.Cut(element.core, inset.Apply(bounds))
 | 
						innerCanvas := canvas.Cut(element.core, padding.Apply(bounds))
 | 
				
			||||||
	artist.FillRectangle(element.core, pattern, bounds)
 | 
						pattern.Draw(element.core, bounds)
 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
	offset := bounds.Min.Add (image.Point {
 | 
						offset := bounds.Min.Add (image.Point {
 | 
				
			||||||
		X: element.config.Padding() - element.scroll,
 | 
							X: padding[artist.SideLeft] - element.scroll,
 | 
				
			||||||
		Y: element.config.Padding(),
 | 
							Y: padding[artist.SideTop],
 | 
				
			||||||
	})
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if element.Focused() && !element.dot.Empty() {
 | 
						if element.Focused() && !element.dot.Empty() {
 | 
				
			||||||
		// draw selection bounds
 | 
							// draw selection bounds
 | 
				
			||||||
		accent := element.theme.Pattern (
 | 
							accent := element.theme.Color(theme.ColorAccent,  state)
 | 
				
			||||||
			theme.PatternAccent,  state)
 | 
					 | 
				
			||||||
		canon := element.dot.Canon()
 | 
							canon := element.dot.Canon()
 | 
				
			||||||
		foff  := fixedutil.Pt(offset)
 | 
							foff  := fixedutil.Pt(offset)
 | 
				
			||||||
		start := element.valueDrawer.PositionAt(canon.Start).Add(foff)
 | 
							start := element.valueDrawer.PositionAt(canon.Start).Add(foff)
 | 
				
			||||||
		end   := element.valueDrawer.PositionAt(canon.End).Add(foff)
 | 
							end   := element.valueDrawer.PositionAt(canon.End).Add(foff)
 | 
				
			||||||
		end.Y += element.valueDrawer.LineHeight()
 | 
							end.Y += element.valueDrawer.LineHeight()
 | 
				
			||||||
		artist.FillRectangle (
 | 
							shapes.FillColorRectangle (
 | 
				
			||||||
			innerCanvas,
 | 
								innerCanvas,
 | 
				
			||||||
			accent,
 | 
								accent,
 | 
				
			||||||
			image.Rectangle {
 | 
								image.Rectangle {
 | 
				
			||||||
@ -381,9 +384,9 @@ func (element *TextBox) draw () {
 | 
				
			|||||||
	if len(element.text) == 0 {
 | 
						if len(element.text) == 0 {
 | 
				
			||||||
		// draw placeholder
 | 
							// draw placeholder
 | 
				
			||||||
		textBounds := element.placeholderDrawer.LayoutBounds()
 | 
							textBounds := element.placeholderDrawer.LayoutBounds()
 | 
				
			||||||
		foreground := element.theme.Pattern (
 | 
							foreground := element.theme.Color (
 | 
				
			||||||
			theme.PatternForeground,
 | 
								theme.ColorForeground,
 | 
				
			||||||
			theme.PatternState { Disabled: true })
 | 
								theme.State { Disabled: true })
 | 
				
			||||||
		element.placeholderDrawer.Draw (
 | 
							element.placeholderDrawer.Draw (
 | 
				
			||||||
			innerCanvas,
 | 
								innerCanvas,
 | 
				
			||||||
			foreground,
 | 
								foreground,
 | 
				
			||||||
@ -391,8 +394,7 @@ func (element *TextBox) draw () {
 | 
				
			|||||||
	} else {
 | 
						} else {
 | 
				
			||||||
		// draw input value
 | 
							// draw input value
 | 
				
			||||||
		textBounds := element.valueDrawer.LayoutBounds()
 | 
							textBounds := element.valueDrawer.LayoutBounds()
 | 
				
			||||||
		foreground := element.theme.Pattern (
 | 
							foreground := element.theme.Color(theme.ColorForeground, state)
 | 
				
			||||||
			theme.PatternForeground,  state)
 | 
					 | 
				
			||||||
		element.valueDrawer.Draw (
 | 
							element.valueDrawer.Draw (
 | 
				
			||||||
			innerCanvas,
 | 
								innerCanvas,
 | 
				
			||||||
			foreground,
 | 
								foreground,
 | 
				
			||||||
@ -401,11 +403,10 @@ func (element *TextBox) draw () {
 | 
				
			|||||||
	
 | 
						
 | 
				
			||||||
	if element.Focused() && element.dot.Empty() {
 | 
						if element.Focused() && element.dot.Empty() {
 | 
				
			||||||
		// draw cursor
 | 
							// draw cursor
 | 
				
			||||||
		foreground := element.theme.Pattern (
 | 
							foreground := element.theme.Color(theme.ColorForeground, state)
 | 
				
			||||||
			theme.PatternForeground,  state)
 | 
					 | 
				
			||||||
		cursorPosition := fixedutil.RoundPt (
 | 
							cursorPosition := fixedutil.RoundPt (
 | 
				
			||||||
			element.valueDrawer.PositionAt(element.dot.End))
 | 
								element.valueDrawer.PositionAt(element.dot.End))
 | 
				
			||||||
		artist.Line (
 | 
							shapes.ColorLine (
 | 
				
			||||||
			innerCanvas,
 | 
								innerCanvas,
 | 
				
			||||||
			foreground, 1,
 | 
								foreground, 1,
 | 
				
			||||||
			cursorPosition.Add(offset),
 | 
								cursorPosition.Add(offset),
 | 
				
			||||||
 | 
				
			|||||||
@ -3,9 +3,10 @@ package fun
 | 
				
			|||||||
import "time"
 | 
					import "time"
 | 
				
			||||||
import "math"
 | 
					import "math"
 | 
				
			||||||
import "image"
 | 
					import "image"
 | 
				
			||||||
 | 
					import "image/color"
 | 
				
			||||||
import "git.tebibyte.media/sashakoshka/tomo/theme"
 | 
					import "git.tebibyte.media/sashakoshka/tomo/theme"
 | 
				
			||||||
import "git.tebibyte.media/sashakoshka/tomo/config"
 | 
					import "git.tebibyte.media/sashakoshka/tomo/config"
 | 
				
			||||||
import "git.tebibyte.media/sashakoshka/tomo/artist"
 | 
					import "git.tebibyte.media/sashakoshka/tomo/artist/shapes"
 | 
				
			||||||
import "git.tebibyte.media/sashakoshka/tomo/elements/core"
 | 
					import "git.tebibyte.media/sashakoshka/tomo/elements/core"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// AnalogClock can display the time of day in an analog format.
 | 
					// AnalogClock can display the time of day in an analog format.
 | 
				
			||||||
@ -58,15 +59,15 @@ func (element *AnalogClock) redo () {
 | 
				
			|||||||
func (element *AnalogClock) draw () {
 | 
					func (element *AnalogClock) draw () {
 | 
				
			||||||
	bounds := element.Bounds()
 | 
						bounds := element.Bounds()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	state := theme.PatternState { }
 | 
						state   := theme.State { }
 | 
				
			||||||
	pattern := element.theme.Pattern(theme.PatternSunken, state)
 | 
						pattern := element.theme.Pattern(theme.PatternSunken, state)
 | 
				
			||||||
	inset   := element.theme.Inset(theme.PatternSunken)
 | 
						padding := element.theme.Padding(theme.PatternSunken)
 | 
				
			||||||
	artist.FillRectangle(element.core, pattern, bounds)
 | 
						pattern.Draw(element.core, bounds)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	bounds = inset.Apply(bounds)
 | 
						bounds = padding.Apply(bounds)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	foreground := element.theme.Pattern(theme.PatternForeground, state)
 | 
						foreground := element.theme.Color(theme.ColorForeground, state)
 | 
				
			||||||
	accent     := element.theme.Pattern(theme.PatternAccent, state)
 | 
						accent     := element.theme.Color(theme.ColorAccent, state)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	for hour := 0; hour < 12; hour ++ {
 | 
						for hour := 0; hour < 12; hour ++ {
 | 
				
			||||||
		element.radialLine (
 | 
							element.radialLine (
 | 
				
			||||||
@ -93,7 +94,7 @@ func (element *AnalogClock) FlexibleHeightFor (width int) (height int) {
 | 
				
			|||||||
func (element *AnalogClock) OnFlexibleHeightChange (func ()) { }
 | 
					func (element *AnalogClock) OnFlexibleHeightChange (func ()) { }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (element *AnalogClock) radialLine (
 | 
					func (element *AnalogClock) radialLine (
 | 
				
			||||||
	source artist.Pattern,
 | 
						source color.RGBA,
 | 
				
			||||||
	inner  float64,
 | 
						inner  float64,
 | 
				
			||||||
	outer  float64,
 | 
						outer  float64,
 | 
				
			||||||
	radian float64,
 | 
						radian float64,
 | 
				
			||||||
@ -107,5 +108,5 @@ func (element *AnalogClock) radialLine (
 | 
				
			|||||||
	max := element.Bounds().Min.Add(image.Pt (
 | 
						max := element.Bounds().Min.Add(image.Pt (
 | 
				
			||||||
		int(math.Cos(radian) * outer * width + width),
 | 
							int(math.Cos(radian) * outer * width + width),
 | 
				
			||||||
		int(math.Sin(radian) * outer * height + height)))
 | 
							int(math.Sin(radian) * outer * height + height)))
 | 
				
			||||||
	artist.Line(element.core, source, 1, min, max)
 | 
						shapes.ColorLine(element.core, source, 1, min, max)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -218,10 +218,11 @@ func (element *Piano) SetConfig (new config.Config) {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (element *Piano) updateMinimumSize () {
 | 
					func (element *Piano) updateMinimumSize () {
 | 
				
			||||||
	inset := element.theme.Inset(theme.PatternSunken)
 | 
						padding := element.theme.Padding(theme.PatternPinboard)
 | 
				
			||||||
	element.core.SetMinimumSize (
 | 
						element.core.SetMinimumSize (
 | 
				
			||||||
		pianoKeyWidth * 7 * element.countOctaves() + inset[1] + inset[3],
 | 
							pianoKeyWidth * 7 * element.countOctaves() +
 | 
				
			||||||
		64 + inset[0] + inset[2])
 | 
							padding.Horizontal(),
 | 
				
			||||||
 | 
							64 + padding.Vertical())
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (element *Piano) countOctaves () int {
 | 
					func (element *Piano) countOctaves () int {
 | 
				
			||||||
@ -247,8 +248,8 @@ func (element *Piano) recalculate () {
 | 
				
			|||||||
	element.flatKeys  = make([]pianoKey, element.countFlats())
 | 
						element.flatKeys  = make([]pianoKey, element.countFlats())
 | 
				
			||||||
	element.sharpKeys = make([]pianoKey, element.countSharps())
 | 
						element.sharpKeys = make([]pianoKey, element.countSharps())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	inset  := element.theme.Inset(theme.PatternPinboard)
 | 
						padding  := element.theme.Padding(theme.PatternPinboard)
 | 
				
			||||||
	bounds := inset.Apply(element.Bounds())
 | 
						bounds := padding.Apply(element.Bounds())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	dot := bounds.Min
 | 
						dot := bounds.Min
 | 
				
			||||||
	note := element.low.Note(0)
 | 
						note := element.low.Note(0)
 | 
				
			||||||
@ -280,7 +281,7 @@ func (element *Piano) recalculate () {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (element *Piano) draw () {
 | 
					func (element *Piano) draw () {
 | 
				
			||||||
	state := theme.PatternState {
 | 
						state := theme.State {
 | 
				
			||||||
		Focused: element.Focused(),
 | 
							Focused: element.Focused(),
 | 
				
			||||||
		Disabled: !element.Enabled(),
 | 
							Disabled: !element.Enabled(),
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@ -303,28 +304,28 @@ func (element *Piano) draw () {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
	pattern := element.theme.Pattern(theme.PatternPinboard, state)
 | 
						pattern := element.theme.Pattern(theme.PatternPinboard, state)
 | 
				
			||||||
	artist.FillRectangleShatter (
 | 
						artist.DrawShatter (
 | 
				
			||||||
		element.core, pattern, element.Bounds(), element.contentBounds)
 | 
							element.core, pattern, element.contentBounds)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (element *Piano) drawFlat (
 | 
					func (element *Piano) drawFlat (
 | 
				
			||||||
	bounds image.Rectangle,
 | 
						bounds image.Rectangle,
 | 
				
			||||||
	pressed bool,
 | 
						pressed bool,
 | 
				
			||||||
	state theme.PatternState,
 | 
						state theme.State,
 | 
				
			||||||
) {
 | 
					) {
 | 
				
			||||||
	state.Pressed = pressed
 | 
						state.Pressed = pressed
 | 
				
			||||||
	pattern := element.theme.Theme.Pattern (
 | 
						pattern := element.theme.Theme.Pattern (
 | 
				
			||||||
		theme.PatternButton, state, theme.C("fun", "flatKey"))
 | 
							theme.PatternButton, state, theme.C("fun", "flatKey"))
 | 
				
			||||||
	artist.FillRectangle(element.core, pattern, bounds)
 | 
						artist.DrawBounds(element.core, pattern, bounds)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (element *Piano) drawSharp (
 | 
					func (element *Piano) drawSharp (
 | 
				
			||||||
	bounds image.Rectangle,
 | 
						bounds image.Rectangle,
 | 
				
			||||||
	pressed bool,
 | 
						pressed bool,
 | 
				
			||||||
	state theme.PatternState,
 | 
						state theme.State,
 | 
				
			||||||
) {
 | 
					) {
 | 
				
			||||||
	state.Pressed = pressed
 | 
						state.Pressed = pressed
 | 
				
			||||||
	pattern := element.theme.Theme.Pattern (
 | 
						pattern := element.theme.Theme.Pattern (
 | 
				
			||||||
		theme.PatternButton, state, theme.C("fun", "sharpKey"))
 | 
							theme.PatternButton, state, theme.C("fun", "sharpKey"))
 | 
				
			||||||
	artist.FillRectangle(element.core, pattern, bounds)
 | 
						artist.DrawBounds(element.core, pattern, bounds)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -4,11 +4,14 @@ import "fmt"
 | 
				
			|||||||
import "time"
 | 
					import "time"
 | 
				
			||||||
import "image"
 | 
					import "image"
 | 
				
			||||||
import "image/color"
 | 
					import "image/color"
 | 
				
			||||||
 | 
					import "git.tebibyte.media/sashakoshka/tomo/canvas"
 | 
				
			||||||
import "git.tebibyte.media/sashakoshka/tomo/artist"
 | 
					import "git.tebibyte.media/sashakoshka/tomo/artist"
 | 
				
			||||||
import "git.tebibyte.media/sashakoshka/tomo/shatter"
 | 
					import "git.tebibyte.media/sashakoshka/tomo/shatter"
 | 
				
			||||||
import "git.tebibyte.media/sashakoshka/tomo/textdraw"
 | 
					import "git.tebibyte.media/sashakoshka/tomo/textdraw"
 | 
				
			||||||
import "git.tebibyte.media/sashakoshka/tomo/defaultfont"
 | 
					import "git.tebibyte.media/sashakoshka/tomo/defaultfont"
 | 
				
			||||||
import "git.tebibyte.media/sashakoshka/tomo/elements/core"
 | 
					import "git.tebibyte.media/sashakoshka/tomo/elements/core"
 | 
				
			||||||
 | 
					import "git.tebibyte.media/sashakoshka/tomo/artist/shapes"
 | 
				
			||||||
 | 
					import "git.tebibyte.media/sashakoshka/tomo/artist/patterns"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Artist is an element that displays shapes and patterns drawn by the artist
 | 
					// Artist is an element that displays shapes and patterns drawn by the artist
 | 
				
			||||||
// package in order to test it.
 | 
					// package in order to test it.
 | 
				
			||||||
@ -21,103 +24,52 @@ type Artist struct {
 | 
				
			|||||||
func NewArtist () (element *Artist) {
 | 
					func NewArtist () (element *Artist) {
 | 
				
			||||||
	element = &Artist { }
 | 
						element = &Artist { }
 | 
				
			||||||
	element.Core, element.core = core.NewCore(element.draw)
 | 
						element.Core, element.core = core.NewCore(element.draw)
 | 
				
			||||||
	element.core.SetMinimumSize(240, 360)
 | 
						element.core.SetMinimumSize(240, 240)
 | 
				
			||||||
	return
 | 
						return
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (element *Artist) draw () {
 | 
					func (element *Artist) draw () {
 | 
				
			||||||
	bounds := element.Bounds()
 | 
						bounds := element.Bounds()
 | 
				
			||||||
	artist.FillRectangle(element.core, artist.NewUniform(hex(0)), bounds)
 | 
						patterns.Uhex(0x000000FF).Draw(element.core, bounds)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	drawStart := time.Now()
 | 
						drawStart := time.Now()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// 0, 0
 | 
						// 0, 0 - 3, 0
 | 
				
			||||||
	artist.FillRectangle (
 | 
						for x := 0; x < 4; x ++ {
 | 
				
			||||||
		element.core,
 | 
							element.colorLines(x + 1, element.cellAt(x, 0).Bounds())
 | 
				
			||||||
		artist.Beveled {
 | 
						}
 | 
				
			||||||
			artist.NewUniform(hex(0xFF0000FF)),
 | 
					 | 
				
			||||||
			artist.NewUniform(hex(0x0000FFFF)),
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
		element.cellAt(0, 0))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// 1, 0
 | 
					 | 
				
			||||||
	artist.StrokeRectangle (
 | 
					 | 
				
			||||||
		element.core,
 | 
					 | 
				
			||||||
		artist.NewUniform(hex(0x00FF00FF)), 3,
 | 
					 | 
				
			||||||
		element.cellAt(1, 0))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// 2, 0
 | 
					 | 
				
			||||||
	artist.FillRectangle (
 | 
					 | 
				
			||||||
		element.core,
 | 
					 | 
				
			||||||
		artist.NewMultiBordered (
 | 
					 | 
				
			||||||
			artist.Stroke { Pattern: uhex(0xFF0000FF), Weight: 1 },
 | 
					 | 
				
			||||||
			artist.Stroke { Pattern: uhex(0x888800FF), Weight: 2 },
 | 
					 | 
				
			||||||
			artist.Stroke { Pattern: uhex(0x00FF00FF), Weight: 3 },
 | 
					 | 
				
			||||||
			artist.Stroke { Pattern: uhex(0x008888FF), Weight: 4 },
 | 
					 | 
				
			||||||
			artist.Stroke { Pattern: uhex(0x0000FFFF), Weight: 5 },
 | 
					 | 
				
			||||||
			),
 | 
					 | 
				
			||||||
		element.cellAt(2, 0))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// 3, 0
 | 
					 | 
				
			||||||
	artist.FillRectangle (
 | 
					 | 
				
			||||||
		element.core,
 | 
					 | 
				
			||||||
		artist.Bordered {
 | 
					 | 
				
			||||||
			Stroke: artist.Stroke { Pattern: uhex(0x0000FFFF), Weight: 5 },
 | 
					 | 
				
			||||||
			Fill: uhex(0xFF0000FF),
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
		element.cellAt(3, 0))
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// 4, 0
 | 
						// 4, 0
 | 
				
			||||||
	artist.FillRectangle (
 | 
						c40 := element.cellAt(4, 0)
 | 
				
			||||||
		element.core,
 | 
						shapes.StrokeColorRectangle(c40, artist.Hex(0x888888FF), c40.Bounds(), 1)
 | 
				
			||||||
		artist.Padded {
 | 
						shapes.ColorLine (
 | 
				
			||||||
			Stroke: uhex(0xFFFFFFFF),
 | 
							c40, artist.Hex(0xFF0000FF), 1,
 | 
				
			||||||
			Fill: uhex(0x666666FF),
 | 
							c40.Bounds().Min, c40.Bounds().Max)
 | 
				
			||||||
			Sides: []int { 4, 13, 2, 0 },
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
		element.cellAt(4, 0))
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// 0, 1 - 3, 1
 | 
						// 0, 1
 | 
				
			||||||
	for x := 0; x < 4; x ++ {
 | 
						c01 := element.cellAt(0, 1)
 | 
				
			||||||
		artist.FillRectangle (
 | 
						shapes.StrokeColorRectangle(c01, artist.Hex(0x888888FF), c01.Bounds(), 1)
 | 
				
			||||||
			element.core,
 | 
						shapes.FillColorEllipse(element.core, artist.Hex(0x00FF00FF), c01.Bounds())
 | 
				
			||||||
			artist.Striped {
 | 
					 | 
				
			||||||
				First:  artist.Stroke { Pattern: uhex(0xFF8800FF), Weight: 7 },
 | 
					 | 
				
			||||||
				Second: artist.Stroke { Pattern: uhex(0x0088FFFF), Weight: 2 },
 | 
					 | 
				
			||||||
				Orientation: artist.Orientation(x),
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
			},
 | 
						// 1, 1 - 3, 1
 | 
				
			||||||
			element.cellAt(x, 1))
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// 0, 2 - 3, 2
 | 
					 | 
				
			||||||
	for x := 0; x < 4; x ++ {
 | 
					 | 
				
			||||||
		element.lines(x + 1, element.cellAt(x, 2))
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// 0, 3
 | 
					 | 
				
			||||||
	artist.StrokeRectangle (
 | 
					 | 
				
			||||||
		element.core, uhex(0x888888FF), 1,
 | 
					 | 
				
			||||||
		element.cellAt(0, 3))
 | 
					 | 
				
			||||||
	artist.FillEllipse(element.core, uhex(0x00FF00FF), element.cellAt(0, 3))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// 1, 3 - 3, 3
 | 
					 | 
				
			||||||
	for x := 1; x < 4; x ++ {
 | 
						for x := 1; x < 4; x ++ {
 | 
				
			||||||
		artist.StrokeRectangle (
 | 
							c := element.cellAt(x, 1)
 | 
				
			||||||
			element.core,uhex(0x888888FF), 1,
 | 
							shapes.StrokeColorRectangle (
 | 
				
			||||||
			element.cellAt(x, 3))
 | 
								element.core, artist.Hex(0x888888FF),
 | 
				
			||||||
		artist.StrokeEllipse (
 | 
								c.Bounds(), 1)
 | 
				
			||||||
 | 
							shapes.StrokeColorEllipse (
 | 
				
			||||||
			element.core,
 | 
								element.core,
 | 
				
			||||||
			[]artist.Pattern {
 | 
								[]color.RGBA {
 | 
				
			||||||
				uhex(0xFF0000FF),
 | 
									artist.Hex(0xFF0000FF),
 | 
				
			||||||
				uhex(0x00FF00FF),
 | 
									artist.Hex(0x00FF00FF),
 | 
				
			||||||
				uhex(0xFF00FFFF),
 | 
									artist.Hex(0xFF00FFFF),
 | 
				
			||||||
			} [x - 1],
 | 
								} [x - 1],
 | 
				
			||||||
			x, element.cellAt(x, 3))
 | 
								c.Bounds(), x)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// 4, 3
 | 
						// 4, 1
 | 
				
			||||||
	shatterPos := element.cellAt(4, 3).Min
 | 
						c41 := element.cellAt(4, 1)
 | 
				
			||||||
 | 
						shatterPos := c41.Bounds().Min
 | 
				
			||||||
	rocks := []image.Rectangle {
 | 
						rocks := []image.Rectangle {
 | 
				
			||||||
		image.Rect(3, 12, 13, 23).Add(shatterPos),
 | 
							image.Rect(3, 12, 13, 23).Add(shatterPos),
 | 
				
			||||||
		// image.Rect(30, 10, 40, 23).Add(shatterPos),
 | 
							// image.Rect(30, 10, 40, 23).Add(shatterPos),
 | 
				
			||||||
@ -125,159 +77,53 @@ func (element *Artist) draw () {
 | 
				
			|||||||
		image.Rect(30, -10, 40, 43).Add(shatterPos),
 | 
							image.Rect(30, -10, 40, 43).Add(shatterPos),
 | 
				
			||||||
		image.Rect(80, 30, 90, 45).Add(shatterPos),
 | 
							image.Rect(80, 30, 90, 45).Add(shatterPos),
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	tiles := shatter.Shatter(element.cellAt(4, 3), rocks...)
 | 
						tiles := shatter.Shatter(c41.Bounds(), rocks...)
 | 
				
			||||||
	for _, tile := range tiles {
 | 
						for index, tile := range tiles {
 | 
				
			||||||
		artist.FillRectangle (
 | 
							artist.DrawBounds (
 | 
				
			||||||
			element.core,
 | 
								element.core,
 | 
				
			||||||
			artist.Bordered {
 | 
								[]artist.Pattern {
 | 
				
			||||||
				Fill: uhex(0x888888FF),
 | 
									patterns.Uhex(0xFF0000FF),
 | 
				
			||||||
				Stroke: artist.Stroke {
 | 
									patterns.Uhex(0x00FF00FF),
 | 
				
			||||||
					Pattern: artist.Beveled {
 | 
									patterns.Uhex(0xFF00FFFF),
 | 
				
			||||||
						uhex(0xCCCCCCFF),
 | 
									patterns.Uhex(0xFFFF00FF),
 | 
				
			||||||
						uhex(0x444444FF),
 | 
									patterns.Uhex(0x00FFFFFF),
 | 
				
			||||||
					},
 | 
								} [index % 5], tile)
 | 
				
			||||||
					Weight: 1,
 | 
					 | 
				
			||||||
				},
 | 
					 | 
				
			||||||
			},
 | 
					 | 
				
			||||||
			tile)
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// 0, 4 - 3, 4
 | 
						// 0, 2
 | 
				
			||||||
	for x := 0; x < 4; x ++ {
 | 
						c02 := element.cellAt(0, 2)
 | 
				
			||||||
		artist.FillEllipse (
 | 
						shapes.StrokeColorRectangle(c02, artist.Hex(0x888888FF), c02.Bounds(), 1)
 | 
				
			||||||
			element.core, 
 | 
						shapes.FillEllipse(c02, c41)
 | 
				
			||||||
			artist.Split {
 | 
					 | 
				
			||||||
				First:  uhex(0xFF0000FF),
 | 
					 | 
				
			||||||
				Second: uhex(0x0000FFFF),
 | 
					 | 
				
			||||||
				Orientation: artist.Orientation(x),
 | 
					 | 
				
			||||||
			},
 | 
					 | 
				
			||||||
			element.cellAt(x, 4))
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// 0, 5
 | 
						// 1, 2
 | 
				
			||||||
	artist.FillRectangle (
 | 
						c12 := element.cellAt(1, 2)
 | 
				
			||||||
		element.core,
 | 
						shapes.StrokeColorRectangle(c12, artist.Hex(0x888888FF), c12.Bounds(), 1)
 | 
				
			||||||
		artist.QuadBeveled {
 | 
						shapes.StrokeEllipse(c12, c41, 5)
 | 
				
			||||||
			uhex(0x880000FF),
 | 
					 | 
				
			||||||
			uhex(0x00FF00FF),
 | 
					 | 
				
			||||||
			uhex(0x0000FFFF),
 | 
					 | 
				
			||||||
			uhex(0xFF00FFFF),
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
		element.cellAt(0, 5))
 | 
					 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
	// 1, 5
 | 
						// 2, 2
 | 
				
			||||||
	artist.FillRectangle (
 | 
						c22 := element.cellAt(2, 2)
 | 
				
			||||||
		element.core,
 | 
						shapes.FillRectangle(c22, c41)
 | 
				
			||||||
		artist.Checkered {
 | 
					 | 
				
			||||||
			First: artist.QuadBeveled {
 | 
					 | 
				
			||||||
				uhex(0x880000FF),
 | 
					 | 
				
			||||||
				uhex(0x00FF00FF),
 | 
					 | 
				
			||||||
				uhex(0x0000FFFF),
 | 
					 | 
				
			||||||
				uhex(0xFF00FFFF),
 | 
					 | 
				
			||||||
			},
 | 
					 | 
				
			||||||
			Second: artist.Striped {
 | 
					 | 
				
			||||||
				First:  artist.Stroke { Pattern: uhex(0xFF8800FF), Weight: 1 },
 | 
					 | 
				
			||||||
				Second: artist.Stroke { Pattern: uhex(0x0088FFFF), Weight: 1 },
 | 
					 | 
				
			||||||
				Orientation: artist.OrientationVertical,
 | 
					 | 
				
			||||||
			},
 | 
					 | 
				
			||||||
			CellWidth: 32,
 | 
					 | 
				
			||||||
			CellHeight: 16,
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
		element.cellAt(1, 5))
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// 2, 5
 | 
						// 3, 2
 | 
				
			||||||
	artist.FillRectangle (
 | 
						c32 := element.cellAt(3, 2)
 | 
				
			||||||
		element.core,
 | 
						shapes.StrokeRectangle(c32, c41, 5)
 | 
				
			||||||
		artist.Dotted {
 | 
					 | 
				
			||||||
			Foreground: uhex(0x00FF00FF),
 | 
					 | 
				
			||||||
			Background: artist.Checkered {
 | 
					 | 
				
			||||||
				First:  uhex(0x444444FF),
 | 
					 | 
				
			||||||
				Second: uhex(0x888888FF),
 | 
					 | 
				
			||||||
				CellWidth: 16,
 | 
					 | 
				
			||||||
				CellHeight: 16,
 | 
					 | 
				
			||||||
			},
 | 
					 | 
				
			||||||
			Size: 8,
 | 
					 | 
				
			||||||
			Spacing: 16,
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
		element.cellAt(2, 5))
 | 
					 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
	// 3, 5
 | 
						// 4, 2
 | 
				
			||||||
	artist.FillRectangle (
 | 
						c42 := element.cellAt(4, 2)
 | 
				
			||||||
		element.core,
 | 
					 | 
				
			||||||
		artist.Tiled {
 | 
					 | 
				
			||||||
			Pattern: artist.QuadBeveled {
 | 
					 | 
				
			||||||
				uhex(0x880000FF),
 | 
					 | 
				
			||||||
				uhex(0x00FF00FF),
 | 
					 | 
				
			||||||
				uhex(0x0000FFFF),
 | 
					 | 
				
			||||||
				uhex(0xFF00FFFF),
 | 
					 | 
				
			||||||
			},
 | 
					 | 
				
			||||||
			CellWidth: 17,
 | 
					 | 
				
			||||||
			CellHeight: 23,
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
		element.cellAt(3, 5))
 | 
					 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
	// 0, 6 - 3, 6
 | 
						// 0, 3
 | 
				
			||||||
	for x := 0; x < 4; x ++ {
 | 
						c03 := element.cellAt(0, 3)
 | 
				
			||||||
		artist.FillRectangle (
 | 
						patterns.Border {
 | 
				
			||||||
			element.core, 
 | 
							Canvas: element.thingy(c42),
 | 
				
			||||||
			artist.Gradient {
 | 
							Inset:  artist.Inset { 8, 8, 8, 8 },
 | 
				
			||||||
				First:  uhex(0xFF0000FF),
 | 
						}.Draw(c03, c03.Bounds())
 | 
				
			||||||
				Second: uhex(0x0000FFFF),
 | 
					 | 
				
			||||||
				Orientation: artist.Orientation(x),
 | 
					 | 
				
			||||||
			},
 | 
					 | 
				
			||||||
			element.cellAt(x, 6))
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
	// 0, 7
 | 
						// 1, 3
 | 
				
			||||||
	artist.FillEllipse (
 | 
						c13 := element.cellAt(1, 3)
 | 
				
			||||||
		element.core,
 | 
						patterns.Border {
 | 
				
			||||||
		artist.EllipticallyBordered {
 | 
							Canvas: element.thingy(c42),
 | 
				
			||||||
			Fill: artist.Gradient {
 | 
							Inset:  artist.Inset { 8, 8, 8, 8 },
 | 
				
			||||||
				First:  uhex(0x00FF00FF),
 | 
						}.Draw(c13, c13.Bounds().Inset(10))
 | 
				
			||||||
				Second: uhex(0x0000FFFF),
 | 
					 | 
				
			||||||
				Orientation: artist.OrientationVertical,
 | 
					 | 
				
			||||||
			},
 | 
					 | 
				
			||||||
			Stroke: artist.Stroke { Pattern: uhex(0x00FF00), Weight: 5 },
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
		element.cellAt(0, 7))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// 1, 7
 | 
					 | 
				
			||||||
	artist.FillRectangle (
 | 
					 | 
				
			||||||
		element.core,
 | 
					 | 
				
			||||||
		artist.Noisy {
 | 
					 | 
				
			||||||
			Low:  uhex(0x000000FF),
 | 
					 | 
				
			||||||
			High: uhex(0xFFFFFFFF),
 | 
					 | 
				
			||||||
			Seed: 0,
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
		element.cellAt(1, 7),
 | 
					 | 
				
			||||||
	)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// 2, 7
 | 
					 | 
				
			||||||
	artist.FillRectangle (
 | 
					 | 
				
			||||||
		element.core,
 | 
					 | 
				
			||||||
		artist.Noisy {
 | 
					 | 
				
			||||||
			Low:  uhex(0x000000FF),
 | 
					 | 
				
			||||||
			High: artist.Gradient {
 | 
					 | 
				
			||||||
				First:  uhex(0x000000FF),
 | 
					 | 
				
			||||||
				Second: uhex(0xFFFFFFFF),
 | 
					 | 
				
			||||||
				Orientation: artist.OrientationVertical,
 | 
					 | 
				
			||||||
			},
 | 
					 | 
				
			||||||
			Seed: 0,
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
		element.cellAt(2, 7),
 | 
					 | 
				
			||||||
	)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// 3, 7
 | 
					 | 
				
			||||||
	artist.FillRectangle (
 | 
					 | 
				
			||||||
		element.core,
 | 
					 | 
				
			||||||
		artist.Noisy {
 | 
					 | 
				
			||||||
			Low:  uhex(0x000000FF),
 | 
					 | 
				
			||||||
			High: uhex(0xFFFFFFFF),
 | 
					 | 
				
			||||||
			Seed: 0,
 | 
					 | 
				
			||||||
			Harsh: true,
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
		element.cellAt(3, 7),
 | 
					 | 
				
			||||||
	)
 | 
					 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
	// how long did that take to render?
 | 
						// how long did that take to render?
 | 
				
			||||||
	drawTime := time.Since(drawStart)
 | 
						drawTime := time.Since(drawStart)
 | 
				
			||||||
@ -288,68 +134,62 @@ func (element *Artist) draw () {
 | 
				
			|||||||
		drawTime.Milliseconds(),
 | 
							drawTime.Milliseconds(),
 | 
				
			||||||
		drawTime.Microseconds())))
 | 
							drawTime.Microseconds())))
 | 
				
			||||||
	textDrawer.Draw (
 | 
						textDrawer.Draw (
 | 
				
			||||||
		element.core, uhex(0xFFFFFFFF),
 | 
							element.core, artist.Hex(0xFFFFFFFF),
 | 
				
			||||||
		image.Pt(bounds.Min.X + 8, bounds.Max.Y - 24))
 | 
							image.Pt(bounds.Min.X + 8, bounds.Max.Y - 24))
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (element *Artist) lines (weight int, bounds image.Rectangle) {
 | 
					func (element *Artist) colorLines (weight int, bounds image.Rectangle) {
 | 
				
			||||||
	bounds = bounds.Inset(4)
 | 
						bounds = bounds.Inset(4)
 | 
				
			||||||
	c := uhex(0xFFFFFFFF)
 | 
						c := artist.Hex(0xFFFFFFFF)
 | 
				
			||||||
	artist.Line(element.core, c, weight, bounds.Min, bounds.Max)
 | 
						shapes.ColorLine(element.core, c, weight, bounds.Min, bounds.Max)
 | 
				
			||||||
	artist.Line (
 | 
						shapes.ColorLine (
 | 
				
			||||||
		element.core, c, weight,
 | 
							element.core, c, weight,
 | 
				
			||||||
		image.Pt(bounds.Max.X, bounds.Min.Y),
 | 
							image.Pt(bounds.Max.X, bounds.Min.Y),
 | 
				
			||||||
		image.Pt(bounds.Min.X, bounds.Max.Y))
 | 
							image.Pt(bounds.Min.X, bounds.Max.Y))
 | 
				
			||||||
	artist.Line (
 | 
						shapes.ColorLine (
 | 
				
			||||||
		element.core, c, weight,
 | 
							element.core, c, weight,
 | 
				
			||||||
		image.Pt(bounds.Max.X, bounds.Min.Y + 16),
 | 
							image.Pt(bounds.Max.X, bounds.Min.Y + 16),
 | 
				
			||||||
		image.Pt(bounds.Min.X, bounds.Max.Y - 16))
 | 
							image.Pt(bounds.Min.X, bounds.Max.Y - 16))
 | 
				
			||||||
	artist.Line (
 | 
						shapes.ColorLine (
 | 
				
			||||||
		element.core, c, weight,
 | 
							element.core, c, weight,
 | 
				
			||||||
		image.Pt(bounds.Min.X, bounds.Min.Y + 16),
 | 
							image.Pt(bounds.Min.X, bounds.Min.Y + 16),
 | 
				
			||||||
		image.Pt(bounds.Max.X, bounds.Max.Y - 16))
 | 
							image.Pt(bounds.Max.X, bounds.Max.Y - 16))
 | 
				
			||||||
	artist.Line (
 | 
						shapes.ColorLine (
 | 
				
			||||||
		element.core, c, weight,
 | 
							element.core, c, weight,
 | 
				
			||||||
		image.Pt(bounds.Min.X + 20, bounds.Min.Y),
 | 
							image.Pt(bounds.Min.X + 20, bounds.Min.Y),
 | 
				
			||||||
		image.Pt(bounds.Max.X - 20, bounds.Max.Y))
 | 
							image.Pt(bounds.Max.X - 20, bounds.Max.Y))
 | 
				
			||||||
	artist.Line (
 | 
						shapes.ColorLine (
 | 
				
			||||||
		element.core, c, weight,
 | 
							element.core, c, weight,
 | 
				
			||||||
		image.Pt(bounds.Max.X - 20, bounds.Min.Y),
 | 
							image.Pt(bounds.Max.X - 20, bounds.Min.Y),
 | 
				
			||||||
		image.Pt(bounds.Min.X + 20, bounds.Max.Y))
 | 
							image.Pt(bounds.Min.X + 20, bounds.Max.Y))
 | 
				
			||||||
	artist.Line (
 | 
						shapes.ColorLine (
 | 
				
			||||||
		element.core, c, weight,
 | 
							element.core, c, weight,
 | 
				
			||||||
		image.Pt(bounds.Min.X, bounds.Min.Y + bounds.Dy() / 2),
 | 
							image.Pt(bounds.Min.X, bounds.Min.Y + bounds.Dy() / 2),
 | 
				
			||||||
		image.Pt(bounds.Max.X, bounds.Min.Y + bounds.Dy() / 2))
 | 
							image.Pt(bounds.Max.X, bounds.Min.Y + bounds.Dy() / 2))
 | 
				
			||||||
	artist.Line (
 | 
						shapes.ColorLine (
 | 
				
			||||||
		element.core, c, weight,
 | 
							element.core, c, weight,
 | 
				
			||||||
		image.Pt(bounds.Min.X + bounds.Dx() / 2, bounds.Min.Y),
 | 
							image.Pt(bounds.Min.X + bounds.Dx() / 2, bounds.Min.Y),
 | 
				
			||||||
		image.Pt(bounds.Min.X + bounds.Dx() / 2, bounds.Max.Y))
 | 
							image.Pt(bounds.Min.X + bounds.Dx() / 2, bounds.Max.Y))
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (element *Artist) cellAt (x, y int) (image.Rectangle) {
 | 
					func (element *Artist) cellAt (x, y int) (canvas.Canvas) {
 | 
				
			||||||
	bounds := element.Bounds()
 | 
						bounds := element.Bounds()
 | 
				
			||||||
	cellBounds := image.Rectangle { }
 | 
						cellBounds := image.Rectangle { }
 | 
				
			||||||
	cellBounds.Min = bounds.Min
 | 
						cellBounds.Min = bounds.Min
 | 
				
			||||||
	cellBounds.Max.X = bounds.Min.X + bounds.Dx() / 5
 | 
						cellBounds.Max.X = bounds.Min.X + bounds.Dx() / 5
 | 
				
			||||||
	cellBounds.Max.Y = bounds.Min.Y + (bounds.Dy() - 48) / 8
 | 
						cellBounds.Max.Y = bounds.Min.Y + (bounds.Dy() - 48) / 4
 | 
				
			||||||
	return cellBounds.Add (image.Pt (
 | 
						return canvas.Cut (element.core, cellBounds.Add (image.Pt (
 | 
				
			||||||
		x * cellBounds.Dx(),
 | 
							x * cellBounds.Dx(),
 | 
				
			||||||
		y * cellBounds.Dy()))
 | 
							y * cellBounds.Dy())))
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func hex (n uint32) (c color.RGBA) {
 | 
					func (element *Artist) thingy (destination canvas.Canvas) (result canvas.Canvas) {
 | 
				
			||||||
	c.A = uint8(n)
 | 
						bounds := destination.Bounds()
 | 
				
			||||||
	c.B = uint8(n >>  8)
 | 
						bounds = image.Rect(0, 0, 32, 32).Add(bounds.Min)
 | 
				
			||||||
	c.G = uint8(n >> 16)
 | 
						shapes.FillColorRectangle(destination, artist.Hex(0x440000FF), bounds)
 | 
				
			||||||
	c.R = uint8(n >> 24)
 | 
						shapes.StrokeColorRectangle(destination, artist.Hex(0xFF0000FF), bounds, 1)
 | 
				
			||||||
	return
 | 
						shapes.StrokeColorRectangle(destination, artist.Hex(0x004400FF), bounds.Inset(4), 1)
 | 
				
			||||||
}
 | 
						shapes.FillColorRectangle(destination, artist.Hex(0x004444FF), bounds.Inset(12))
 | 
				
			||||||
 | 
						shapes.StrokeColorRectangle(destination, artist.Hex(0x888888FF), bounds.Inset(8), 1)
 | 
				
			||||||
func uhex (n uint32) (artist.Pattern) {
 | 
						return canvas.Cut(destination, bounds)
 | 
				
			||||||
	return artist.NewUniform (color.RGBA {
 | 
					 | 
				
			||||||
		A: uint8(n),
 | 
					 | 
				
			||||||
		B: uint8(n >>  8),
 | 
					 | 
				
			||||||
		G: uint8(n >> 16),
 | 
					 | 
				
			||||||
		R: uint8(n >> 24),
 | 
					 | 
				
			||||||
	})
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -1,11 +1,11 @@
 | 
				
			|||||||
package testing
 | 
					package testing
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import "image"
 | 
					import "image"
 | 
				
			||||||
import "image/color"
 | 
					 | 
				
			||||||
import "git.tebibyte.media/sashakoshka/tomo/input"
 | 
					import "git.tebibyte.media/sashakoshka/tomo/input"
 | 
				
			||||||
import "git.tebibyte.media/sashakoshka/tomo/config"
 | 
					 | 
				
			||||||
import "git.tebibyte.media/sashakoshka/tomo/theme"
 | 
					import "git.tebibyte.media/sashakoshka/tomo/theme"
 | 
				
			||||||
 | 
					import "git.tebibyte.media/sashakoshka/tomo/config"
 | 
				
			||||||
import "git.tebibyte.media/sashakoshka/tomo/artist"
 | 
					import "git.tebibyte.media/sashakoshka/tomo/artist"
 | 
				
			||||||
 | 
					import "git.tebibyte.media/sashakoshka/tomo/artist/shapes"
 | 
				
			||||||
import "git.tebibyte.media/sashakoshka/tomo/elements/core"
 | 
					import "git.tebibyte.media/sashakoshka/tomo/elements/core"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Mouse is an element capable of testing mouse input. When the mouse is clicked
 | 
					// Mouse is an element capable of testing mouse input. When the mouse is clicked
 | 
				
			||||||
@ -14,7 +14,6 @@ type Mouse struct {
 | 
				
			|||||||
	*core.Core
 | 
						*core.Core
 | 
				
			||||||
	core core.CoreControl
 | 
						core core.CoreControl
 | 
				
			||||||
	drawing      bool
 | 
						drawing      bool
 | 
				
			||||||
	color        artist.Pattern
 | 
					 | 
				
			||||||
	lastMousePos image.Point
 | 
						lastMousePos image.Point
 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
	config config.Config
 | 
						config config.Config
 | 
				
			||||||
@ -27,7 +26,6 @@ func NewMouse () (element *Mouse) {
 | 
				
			|||||||
	element = &Mouse { c: theme.C("testing", "mouse") }
 | 
						element = &Mouse { c: theme.C("testing", "mouse") }
 | 
				
			||||||
	element.Core, element.core = core.NewCore(element.draw)
 | 
						element.Core, element.core = core.NewCore(element.draw)
 | 
				
			||||||
	element.core.SetMinimumSize(32, 32)
 | 
						element.core.SetMinimumSize(32, 32)
 | 
				
			||||||
	element.color = artist.NewUniform(color.Black)
 | 
					 | 
				
			||||||
	return
 | 
						return
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -51,21 +49,21 @@ func (element *Mouse) redo () {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
func (element *Mouse) draw () {
 | 
					func (element *Mouse) draw () {
 | 
				
			||||||
	bounds := element.Bounds()
 | 
						bounds := element.Bounds()
 | 
				
			||||||
	pattern := element.theme.Pattern (
 | 
						accent := element.theme.Color (
 | 
				
			||||||
		theme.PatternAccent,
 | 
							theme.ColorAccent,
 | 
				
			||||||
		theme.PatternState { },
 | 
							theme.State { },
 | 
				
			||||||
		element.c)
 | 
							element.c)
 | 
				
			||||||
	artist.FillRectangle(element.core, pattern, bounds)
 | 
						shapes.FillColorRectangle(element.core, accent, bounds)
 | 
				
			||||||
	artist.StrokeRectangle (
 | 
						shapes.StrokeColorRectangle (
 | 
				
			||||||
		element.core,
 | 
							element.core,
 | 
				
			||||||
		artist.NewUniform(color.Black), 1,
 | 
							artist.Hex(0x000000FF),
 | 
				
			||||||
		bounds)
 | 
							bounds, 1)
 | 
				
			||||||
	artist.Line (
 | 
						shapes.ColorLine (
 | 
				
			||||||
		element.core, artist.NewUniform(color.White), 1,
 | 
							element.core, artist.Hex(0xFFFFFFFF), 1,
 | 
				
			||||||
		bounds.Min.Add(image.Pt(1, 1)),
 | 
							bounds.Min.Add(image.Pt(1, 1)),
 | 
				
			||||||
		bounds.Min.Add(image.Pt(bounds.Dx() - 2, bounds.Dy() - 2)))
 | 
							bounds.Min.Add(image.Pt(bounds.Dx() - 2, bounds.Dy() - 2)))
 | 
				
			||||||
	artist.Line (
 | 
						shapes.ColorLine (
 | 
				
			||||||
		element.core, artist.NewUniform(color.White), 1,
 | 
							element.core, artist.Hex(0xFFFFFFFF), 1,
 | 
				
			||||||
		bounds.Min.Add(image.Pt(1, bounds.Dy() - 2)),
 | 
							bounds.Min.Add(image.Pt(1, bounds.Dy() - 2)),
 | 
				
			||||||
		bounds.Min.Add(image.Pt(bounds.Dx() - 2, 1)))
 | 
							bounds.Min.Add(image.Pt(bounds.Dx() - 2, 1)))
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@ -78,8 +76,8 @@ func (element *Mouse) HandleMouseDown (x, y int, button input.Button) {
 | 
				
			|||||||
func (element *Mouse) HandleMouseUp (x, y int, button input.Button) {
 | 
					func (element *Mouse) HandleMouseUp (x, y int, button input.Button) {
 | 
				
			||||||
	element.drawing = false
 | 
						element.drawing = false
 | 
				
			||||||
	mousePos := image.Pt(x, y)
 | 
						mousePos := image.Pt(x, y)
 | 
				
			||||||
	element.core.DamageRegion (artist.Line (
 | 
						element.core.DamageRegion (shapes.ColorLine (
 | 
				
			||||||
		element.core, element.color, 1,
 | 
							element.core, artist.Hex(0x000000FF), 1,
 | 
				
			||||||
		element.lastMousePos, mousePos))
 | 
							element.lastMousePos, mousePos))
 | 
				
			||||||
	element.lastMousePos = mousePos
 | 
						element.lastMousePos = mousePos
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@ -87,8 +85,8 @@ func (element *Mouse) HandleMouseUp (x, y int, button input.Button) {
 | 
				
			|||||||
func (element *Mouse) HandleMouseMove (x, y int) {
 | 
					func (element *Mouse) HandleMouseMove (x, y int) {
 | 
				
			||||||
	if !element.drawing { return }
 | 
						if !element.drawing { return }
 | 
				
			||||||
	mousePos := image.Pt(x, y)
 | 
						mousePos := image.Pt(x, y)
 | 
				
			||||||
	element.core.DamageRegion (artist.Line (
 | 
						element.core.DamageRegion (shapes.ColorLine (
 | 
				
			||||||
		element.core, element.color, 1,
 | 
							element.core, artist.Hex(0x000000FF), 1,
 | 
				
			||||||
		element.lastMousePos, mousePos))
 | 
							element.lastMousePos, mousePos))
 | 
				
			||||||
	element.lastMousePos = mousePos
 | 
						element.lastMousePos = mousePos
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -11,12 +11,12 @@ func main () {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func run () {
 | 
					func run () {
 | 
				
			||||||
	window, _ := tomo.NewWindow(128, 128)
 | 
						window, _ := tomo.NewWindow(480, 360)
 | 
				
			||||||
	window.SetTitle("Draw Test")
 | 
						window.SetTitle("Draw Test")
 | 
				
			||||||
	window.Adopt(testing.NewArtist())
 | 
						window.Adopt(testing.NewArtist())
 | 
				
			||||||
	window.OnClose(tomo.Stop)
 | 
						window.OnClose(tomo.Stop)
 | 
				
			||||||
	window.Show()
 | 
						window.Show()
 | 
				
			||||||
	go func () {
 | 
						go func () {
 | 
				
			||||||
		http.ListenAndServe("localhost:6060", nil)
 | 
							http.ListenAndServe("localhost:9090", nil)
 | 
				
			||||||
	} ()
 | 
						} ()
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -7,6 +7,7 @@ import "image/color"
 | 
				
			|||||||
import "git.tebibyte.media/sashakoshka/tomo/input"
 | 
					import "git.tebibyte.media/sashakoshka/tomo/input"
 | 
				
			||||||
import "git.tebibyte.media/sashakoshka/tomo/config"
 | 
					import "git.tebibyte.media/sashakoshka/tomo/config"
 | 
				
			||||||
import "git.tebibyte.media/sashakoshka/tomo/artist"
 | 
					import "git.tebibyte.media/sashakoshka/tomo/artist"
 | 
				
			||||||
 | 
					import "git.tebibyte.media/sashakoshka/tomo/artist/shapes"
 | 
				
			||||||
import "git.tebibyte.media/sashakoshka/tomo/elements/core"
 | 
					import "git.tebibyte.media/sashakoshka/tomo/elements/core"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type ControlState struct {
 | 
					type ControlState struct {
 | 
				
			||||||
@ -202,9 +203,9 @@ func (element *Raycaster) drawMinimap () {
 | 
				
			|||||||
		if cell > 0 {
 | 
							if cell > 0 {
 | 
				
			||||||
			cellColor = color.RGBA { 0xFF, 0xFF, 0xFF, 0xFF }
 | 
								cellColor = color.RGBA { 0xFF, 0xFF, 0xFF, 0xFF }
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		artist.FillRectangle (
 | 
							shapes.FillColorRectangle (
 | 
				
			||||||
			element.core,
 | 
								element.core,
 | 
				
			||||||
			artist.NewUniform(cellColor),
 | 
								cellColor,
 | 
				
			||||||
			cellBounds.Inset(1))
 | 
								cellBounds.Inset(1))
 | 
				
			||||||
	}}
 | 
						}}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -217,18 +218,18 @@ func (element *Raycaster) drawMinimap () {
 | 
				
			|||||||
	hitPt := hit.Mul(float64(scale)).Point().Add(bounds.Min)
 | 
						hitPt := hit.Mul(float64(scale)).Point().Add(bounds.Min)
 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
	playerBounds := image.Rectangle { playerPt, playerPt }.Inset(scale / -8)
 | 
						playerBounds := image.Rectangle { playerPt, playerPt }.Inset(scale / -8)
 | 
				
			||||||
	artist.FillEllipse (
 | 
						shapes.FillColorEllipse (
 | 
				
			||||||
		element.core,
 | 
							element.core,
 | 
				
			||||||
		artist.Uhex(0xFFFFFFFF),
 | 
							artist.Hex(0xFFFFFFFF),
 | 
				
			||||||
		playerBounds)
 | 
							playerBounds)
 | 
				
			||||||
	artist.Line (
 | 
						shapes.ColorLine (
 | 
				
			||||||
		element.core,
 | 
							element.core,
 | 
				
			||||||
		artist.Uhex(0xFFFFFFFF), 1,
 | 
							artist.Hex(0xFFFFFFFF), 1,
 | 
				
			||||||
		playerPt,
 | 
							playerPt,
 | 
				
			||||||
		playerAnglePt)
 | 
							playerAnglePt)
 | 
				
			||||||
	artist.Line (
 | 
						shapes.ColorLine (
 | 
				
			||||||
		element.core,
 | 
							element.core,
 | 
				
			||||||
		artist.Uhex(0x00FF00FF), 1,
 | 
							artist.Hex(0x00FF00FF), 1,
 | 
				
			||||||
		playerPt,
 | 
							playerPt,
 | 
				
			||||||
		hitPt)
 | 
							hitPt)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -3,9 +3,9 @@ package textdraw
 | 
				
			|||||||
import "image"
 | 
					import "image"
 | 
				
			||||||
import "unicode"
 | 
					import "unicode"
 | 
				
			||||||
import "image/draw"
 | 
					import "image/draw"
 | 
				
			||||||
 | 
					import "image/color"
 | 
				
			||||||
import "golang.org/x/image/math/fixed"
 | 
					import "golang.org/x/image/math/fixed"
 | 
				
			||||||
import "git.tebibyte.media/sashakoshka/tomo/canvas"
 | 
					import "git.tebibyte.media/sashakoshka/tomo/canvas"
 | 
				
			||||||
import "git.tebibyte.media/sashakoshka/tomo/artist"
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Drawer is an extended TypeSetter that is able to draw text. Much like
 | 
					// Drawer is an extended TypeSetter that is able to draw text. Much like
 | 
				
			||||||
// TypeSetter, It has no constructor and its zero value can be used safely.
 | 
					// TypeSetter, It has no constructor and its zero value can be used safely.
 | 
				
			||||||
@ -14,17 +14,11 @@ type Drawer struct { TypeSetter }
 | 
				
			|||||||
// 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 Drawer) Draw (
 | 
					func (drawer Drawer) Draw (
 | 
				
			||||||
	destination canvas.Canvas,
 | 
						destination canvas.Canvas,
 | 
				
			||||||
	source      artist.Pattern,
 | 
						color       color.RGBA,
 | 
				
			||||||
	offset      image.Point,
 | 
						offset      image.Point,
 | 
				
			||||||
) (
 | 
					) (
 | 
				
			||||||
	updatedRegion image.Rectangle,
 | 
						updatedRegion image.Rectangle,
 | 
				
			||||||
) {
 | 
					) {
 | 
				
			||||||
	wrappedSource := artist.WrappedPattern {
 | 
					 | 
				
			||||||
		Pattern: source,
 | 
					 | 
				
			||||||
		Width:  0,
 | 
					 | 
				
			||||||
		Height: 0, // TODO: choose a better width and height
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	
 | 
					 | 
				
			||||||
	drawer.For (func (
 | 
						drawer.For (func (
 | 
				
			||||||
		index    int,
 | 
							index    int,
 | 
				
			||||||
		char     rune,
 | 
							char     rune,
 | 
				
			||||||
@ -46,7 +40,7 @@ func (drawer Drawer) Draw (
 | 
				
			|||||||
		draw.DrawMask (
 | 
							draw.DrawMask (
 | 
				
			||||||
			destination,
 | 
								destination,
 | 
				
			||||||
			destinationRectangle,
 | 
								destinationRectangle,
 | 
				
			||||||
			wrappedSource, image.Point { },
 | 
								image.NewUniform(color), image.Point { },
 | 
				
			||||||
			mask, maskPoint,
 | 
								mask, maskPoint,
 | 
				
			||||||
			draw.Over)
 | 
								draw.Over)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										
											BIN
										
									
								
								theme/assets/wintergreen.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								theme/assets/wintergreen.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 1.9 KiB  | 
							
								
								
									
										300
									
								
								theme/default.go
									
									
									
									
									
								
							
							
						
						
									
										300
									
								
								theme/default.go
									
									
									
									
									
								
							@ -1,10 +1,69 @@
 | 
				
			|||||||
package theme
 | 
					package theme
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import "image"
 | 
					import "image"
 | 
				
			||||||
 | 
					import "bytes"
 | 
				
			||||||
 | 
					import _ "embed"
 | 
				
			||||||
 | 
					import _ "image/png"
 | 
				
			||||||
 | 
					import "image/color"
 | 
				
			||||||
import "golang.org/x/image/font"
 | 
					import "golang.org/x/image/font"
 | 
				
			||||||
import "git.tebibyte.media/sashakoshka/tomo/artist"
 | 
					import "git.tebibyte.media/sashakoshka/tomo/artist"
 | 
				
			||||||
import "git.tebibyte.media/sashakoshka/tomo/canvas"
 | 
					import "git.tebibyte.media/sashakoshka/tomo/canvas"
 | 
				
			||||||
import "git.tebibyte.media/sashakoshka/tomo/defaultfont"
 | 
					import "git.tebibyte.media/sashakoshka/tomo/defaultfont"
 | 
				
			||||||
 | 
					import "git.tebibyte.media/sashakoshka/tomo/artist/patterns"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					//go:embed assets/wintergreen.png
 | 
				
			||||||
 | 
					var defaultAtlasBytes []byte
 | 
				
			||||||
 | 
					var defaultAtlas      canvas.Canvas
 | 
				
			||||||
 | 
					var defaultTextures   [14][9]artist.Pattern
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func atlasCell (col, row int, border artist.Inset) {
 | 
				
			||||||
 | 
						bounds := image.Rect(0, 0, 16, 16).Add(image.Pt(col, row).Mul(16))
 | 
				
			||||||
 | 
						defaultTextures[col][row] = patterns.Border {
 | 
				
			||||||
 | 
							Canvas: canvas.Cut(defaultAtlas, bounds),
 | 
				
			||||||
 | 
							Inset:  border,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func atlasCol (col int, border artist.Inset) {
 | 
				
			||||||
 | 
						for index, _ := range defaultTextures[col] {
 | 
				
			||||||
 | 
							atlasCell(col, index, border)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func init () {
 | 
				
			||||||
 | 
						defaultAtlasImage, _, _ := image.Decode(bytes.NewReader(defaultAtlasBytes))
 | 
				
			||||||
 | 
						defaultAtlas = canvas.FromImage(defaultAtlasImage)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// PatternDead
 | 
				
			||||||
 | 
						atlasCol(0, artist.Inset { })
 | 
				
			||||||
 | 
						// PatternRaised
 | 
				
			||||||
 | 
						atlasCol(1, artist.Inset { 6, 6, 6, 6 })
 | 
				
			||||||
 | 
						// PatternSunken
 | 
				
			||||||
 | 
						atlasCol(2, artist.Inset { 4, 4, 4, 4 })
 | 
				
			||||||
 | 
						// PatternPinboard
 | 
				
			||||||
 | 
						atlasCol(3, artist.Inset { 2, 2, 2, 2 })
 | 
				
			||||||
 | 
						// PatternButton
 | 
				
			||||||
 | 
						atlasCol(4, artist.Inset { 6, 6, 6, 6 })
 | 
				
			||||||
 | 
						// PatternInput
 | 
				
			||||||
 | 
						atlasCol(5, artist.Inset { 4, 4, 4, 4 })
 | 
				
			||||||
 | 
						// PatternGutter
 | 
				
			||||||
 | 
						atlasCol(6, artist.Inset { 7, 7, 7, 7 })
 | 
				
			||||||
 | 
						// PatternHandle
 | 
				
			||||||
 | 
						atlasCol(7, artist.Inset { 3, 3, 3, 3 })
 | 
				
			||||||
 | 
						// PatternLine
 | 
				
			||||||
 | 
						atlasCol(8, artist.Inset { 1, 1, 1, 1 })
 | 
				
			||||||
 | 
						// PatternMercury
 | 
				
			||||||
 | 
						atlasCol(13, artist.Inset { 2, 2, 2, 2 })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// PatternButton: basic.checkbox
 | 
				
			||||||
 | 
						atlasCol(9, artist.Inset { 3, 3, 3, 3 })
 | 
				
			||||||
 | 
						// PatternRaised: basic.listEntry
 | 
				
			||||||
 | 
						atlasCol(10, artist.Inset { 3, 3, 3, 3 })
 | 
				
			||||||
 | 
						// PatternRaised: fun.flatKey
 | 
				
			||||||
 | 
						atlasCol(11, artist.Inset { 3, 3, 5, 3 })
 | 
				
			||||||
 | 
						// PatternRaised: fun.sharpKey
 | 
				
			||||||
 | 
						atlasCol(12, artist.Inset { 3, 3, 4, 3 })
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Default is the default theme.
 | 
					// Default is the default theme.
 | 
				
			||||||
type Default struct { }
 | 
					type Default struct { }
 | 
				
			||||||
@ -31,211 +90,96 @@ func (Default) Icon (string, IconSize, Case) canvas.Image {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
// Pattern returns a pattern from the default theme corresponding to the given
 | 
					// Pattern returns a pattern from the default theme corresponding to the given
 | 
				
			||||||
// pattern ID.
 | 
					// pattern ID.
 | 
				
			||||||
func (Default) Pattern (
 | 
					func (Default) Pattern (id Pattern, state State, c Case) artist.Pattern {
 | 
				
			||||||
	pattern Pattern,
 | 
						offset := 0; switch {
 | 
				
			||||||
	state PatternState,
 | 
						case state.Disabled:                 offset = 1
 | 
				
			||||||
	c Case,
 | 
						case state.Pressed && state.On:      offset = 4
 | 
				
			||||||
) artist.Pattern {
 | 
						case state.Focused && state.On:      offset = 7
 | 
				
			||||||
	switch pattern {
 | 
						case state.Invalid && state.On:      offset = 8
 | 
				
			||||||
	case PatternAccent:
 | 
						case state.On:                       offset = 2
 | 
				
			||||||
		return accentPattern
 | 
						case state.Pressed:                  offset = 3
 | 
				
			||||||
	case PatternBackground:
 | 
						case state.Focused:                  offset = 5
 | 
				
			||||||
		return backgroundPattern
 | 
						case state.Invalid:                  offset = 6
 | 
				
			||||||
	case PatternForeground:
 | 
					 | 
				
			||||||
		if state.Disabled || c == C("basic", "spacer") {
 | 
					 | 
				
			||||||
			return weakForegroundPattern
 | 
					 | 
				
			||||||
		} else {
 | 
					 | 
				
			||||||
			return foregroundPattern
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	case PatternDead:
 | 
					
 | 
				
			||||||
		return deadPattern
 | 
						switch id {
 | 
				
			||||||
 | 
						case PatternBackground: return patterns.Uhex(0xaaaaaaFF)
 | 
				
			||||||
 | 
						case PatternDead:       return defaultTextures[0][offset]
 | 
				
			||||||
	case PatternRaised:
 | 
						case PatternRaised:
 | 
				
			||||||
		if c == C("basic", "listEntry") {
 | 
							if c == C("basic", "listEntry") {
 | 
				
			||||||
			if state.Focused {
 | 
								return defaultTextures[10][offset]
 | 
				
			||||||
				if state.On {
 | 
					 | 
				
			||||||
					return focusedOnListEntryPattern
 | 
					 | 
				
			||||||
		} else {
 | 
							} else {
 | 
				
			||||||
					return focusedListEntryPattern
 | 
								return defaultTextures[1][offset]
 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
			} else {
 | 
					 | 
				
			||||||
				if state.On {
 | 
					 | 
				
			||||||
					return onListEntryPattern
 | 
					 | 
				
			||||||
				} else {
 | 
					 | 
				
			||||||
					return listEntryPattern
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		} else {
 | 
					 | 
				
			||||||
			if state.Focused {
 | 
					 | 
				
			||||||
				return selectedRaisedPattern
 | 
					 | 
				
			||||||
			} else {
 | 
					 | 
				
			||||||
				return raisedPattern
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	case PatternSunken:
 | 
					 | 
				
			||||||
		if c == C("basic", "list") {
 | 
					 | 
				
			||||||
			if state.Focused {
 | 
					 | 
				
			||||||
				return focusedListPattern
 | 
					 | 
				
			||||||
			} else {
 | 
					 | 
				
			||||||
				return listPattern
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		} else if c == C("basic", "textBox") {
 | 
					 | 
				
			||||||
			if state.Disabled {
 | 
					 | 
				
			||||||
				return disabledInputPattern
 | 
					 | 
				
			||||||
			} else {
 | 
					 | 
				
			||||||
				if state.Focused {
 | 
					 | 
				
			||||||
					return selectedInputPattern
 | 
					 | 
				
			||||||
				} else {
 | 
					 | 
				
			||||||
					return inputPattern
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		} else {
 | 
					 | 
				
			||||||
			if state.Focused {
 | 
					 | 
				
			||||||
				return focusedSunkenPattern
 | 
					 | 
				
			||||||
			} else {
 | 
					 | 
				
			||||||
				return sunkenPattern
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	case PatternPinboard:
 | 
					 | 
				
			||||||
		if state.Focused {
 | 
					 | 
				
			||||||
			return focusedTexturedSunkenPattern
 | 
					 | 
				
			||||||
		} else {
 | 
					 | 
				
			||||||
			return texturedSunkenPattern
 | 
					 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
						case PatternSunken:     return defaultTextures[2][offset]
 | 
				
			||||||
 | 
						case PatternPinboard:   return defaultTextures[3][offset]
 | 
				
			||||||
	case PatternButton:
 | 
						case PatternButton:
 | 
				
			||||||
		if state.Disabled {
 | 
							switch c {
 | 
				
			||||||
			return disabledButtonPattern
 | 
							case C("basic", "checkbox"): return defaultTextures[9][offset]
 | 
				
			||||||
		} else {
 | 
							case C("fun", "flatKey"):    return defaultTextures[11][offset]
 | 
				
			||||||
			if c == C("fun", "sharpKey") {
 | 
							case C("fun", "sharpKey"):   return defaultTextures[12][offset]
 | 
				
			||||||
				if state.Pressed {
 | 
							default:                     return defaultTextures[4][offset]
 | 
				
			||||||
					return pressedDarkButtonPattern
 | 
					 | 
				
			||||||
				} else {
 | 
					 | 
				
			||||||
					return darkButtonPattern
 | 
					 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
			} else if c == C("fun", "flatKey") {
 | 
						case PatternInput:      return defaultTextures[5][offset]
 | 
				
			||||||
				if state.Pressed {
 | 
						case PatternGutter:     return defaultTextures[6][offset]
 | 
				
			||||||
					return pressedButtonPattern
 | 
						case PatternHandle:     return defaultTextures[7][offset]
 | 
				
			||||||
				} else {
 | 
						case PatternLine:       return defaultTextures[8][offset]
 | 
				
			||||||
					return buttonPattern
 | 
						case PatternMercury:    return defaultTextures[13][offset]
 | 
				
			||||||
				}	
 | 
						default:                return patterns.Uhex(0xFF00FFFF)
 | 
				
			||||||
			} else {
 | 
					 | 
				
			||||||
				if state.Pressed || state.On && c == C("basic", "checkbox") {
 | 
					 | 
				
			||||||
					if state.Focused {
 | 
					 | 
				
			||||||
						return pressedSelectedButtonPattern
 | 
					 | 
				
			||||||
					} else {
 | 
					 | 
				
			||||||
						return pressedButtonPattern
 | 
					 | 
				
			||||||
					}
 | 
					 | 
				
			||||||
				} else {
 | 
					 | 
				
			||||||
					if state.Focused {
 | 
					 | 
				
			||||||
						return selectedButtonPattern
 | 
					 | 
				
			||||||
					} else {
 | 
					 | 
				
			||||||
						return buttonPattern
 | 
					 | 
				
			||||||
					}
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	case PatternInput:
 | 
					 | 
				
			||||||
		if state.Disabled {
 | 
					 | 
				
			||||||
			return disabledInputPattern
 | 
					 | 
				
			||||||
		} else {
 | 
					 | 
				
			||||||
			if state.Focused {
 | 
					 | 
				
			||||||
				return selectedInputPattern
 | 
					 | 
				
			||||||
			} else {
 | 
					 | 
				
			||||||
				return inputPattern
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	case PatternGutter:
 | 
					 | 
				
			||||||
		if c == C("basic", "sliderVertical") || c == C("basic", "sliderHorizontal") {
 | 
					 | 
				
			||||||
			if state.Disabled {
 | 
					 | 
				
			||||||
				return disabledThinScrollGutterPattern
 | 
					 | 
				
			||||||
			} else {
 | 
					 | 
				
			||||||
				return thinScrollGutterPattern
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		} else {
 | 
					 | 
				
			||||||
			if state.Disabled {
 | 
					 | 
				
			||||||
				return disabledScrollGutterPattern
 | 
					 | 
				
			||||||
			} else {
 | 
					 | 
				
			||||||
				return scrollGutterPattern
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	case PatternHandle:
 | 
					 | 
				
			||||||
		if state.Disabled {
 | 
					 | 
				
			||||||
			return disabledScrollBarPattern
 | 
					 | 
				
			||||||
		} else {
 | 
					 | 
				
			||||||
			if state.Focused {
 | 
					 | 
				
			||||||
				if state.Pressed {
 | 
					 | 
				
			||||||
					return pressedSelectedScrollBarPattern
 | 
					 | 
				
			||||||
				} else {
 | 
					 | 
				
			||||||
					return selectedScrollBarPattern
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
			} else {
 | 
					 | 
				
			||||||
				if state.Pressed {
 | 
					 | 
				
			||||||
					return pressedScrollBarPattern
 | 
					 | 
				
			||||||
				} else {
 | 
					 | 
				
			||||||
					return scrollBarPattern
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	default:
 | 
					 | 
				
			||||||
		return uhex(0)
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Inset returns the default inset value for the given pattern.
 | 
					func (Default) Color (id Color, state State, c Case) color.RGBA {
 | 
				
			||||||
func (Default) Inset (pattern Pattern, c Case) Inset {
 | 
						if state.Disabled {
 | 
				
			||||||
	switch pattern {
 | 
							return artist.Hex(0x444444FF)
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							switch id {
 | 
				
			||||||
 | 
							case ColorAccent:     return artist.Hex(0x408090FF)
 | 
				
			||||||
 | 
							case ColorForeground: return artist.Hex(0x000000FF)
 | 
				
			||||||
 | 
							default:              return artist.Hex(0x888888FF)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Padding returns the default padding value for the given pattern.
 | 
				
			||||||
 | 
					func (Default) Padding (id Pattern, c Case) artist.Inset {
 | 
				
			||||||
 | 
						switch id {
 | 
				
			||||||
	case PatternRaised:
 | 
						case PatternRaised:
 | 
				
			||||||
		if c == C("basic", "listEntry") {
 | 
							if c == C("basic", "listEntry") {
 | 
				
			||||||
			return Inset { 4, 6, 4, 6 }
 | 
								return artist.Inset { 4, 8, 4, 8 }
 | 
				
			||||||
		} else {
 | 
							} else {
 | 
				
			||||||
			return Inset { 2, 2, 2, 2 }
 | 
								return artist.Inset { 8, 8, 8, 8 }
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		
 | 
					 | 
				
			||||||
	case PatternSunken:
 | 
						case PatternSunken:
 | 
				
			||||||
		if c == C("basic", "list") {
 | 
							if c == C("basic", "list") {
 | 
				
			||||||
			return Inset { 2, 1, 2, 1 }
 | 
								return artist.Inset { 4, 0, 3, 0 }
 | 
				
			||||||
		} else if c == C("basic", "progressBar") {
 | 
							} else if c == C("basic", "progressBar") {
 | 
				
			||||||
			return Inset { 2, 1, 1, 2 }
 | 
								return artist.Inset { 2, 1, 1, 2 }
 | 
				
			||||||
		} else {
 | 
							} else {
 | 
				
			||||||
			return Inset { 2, 2, 2, 2 }
 | 
								return artist.Inset { 8, 8, 8, 8 }
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					 | 
				
			||||||
	case PatternPinboard:
 | 
						case PatternPinboard:
 | 
				
			||||||
		return Inset { 2, 2, 2, 2 }
 | 
							if c == C("fun", "piano") {
 | 
				
			||||||
	
 | 
								return artist.Inset { 2, 2, 2, 2 }
 | 
				
			||||||
	case PatternInput, PatternButton, PatternHandle:
 | 
							} else {
 | 
				
			||||||
		return Inset { 2, 2, 2, 2}
 | 
								return artist.Inset { 8, 8, 8, 8 }
 | 
				
			||||||
 | 
					 | 
				
			||||||
	default: return Inset { }
 | 
					 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
						case PatternGutter:     return artist.Inset { }
 | 
				
			||||||
 | 
						case PatternLine:       return artist.Inset { 1, 1, 1, 1 }
 | 
				
			||||||
 | 
						case PatternMercury:    return artist.Inset { 5, 5, 5, 5 }
 | 
				
			||||||
 | 
						default:                return artist.Inset { 8, 8, 8, 8 }
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Margin returns the default margin value for the given pattern.
 | 
				
			||||||
 | 
					func (Default) Margin (id Pattern, c Case) image.Point {
 | 
				
			||||||
 | 
						return image.Pt(8, 8)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Hints returns rendering optimization hints for a particular pattern.
 | 
					// Hints returns rendering optimization hints for a particular pattern.
 | 
				
			||||||
// These are optional, but following them may result in improved
 | 
					// These are optional, but following them may result in improved
 | 
				
			||||||
// performance.
 | 
					// performance.
 | 
				
			||||||
func (Default) Hints (pattern Pattern, c Case) (hints Hints) {
 | 
					func (Default) Hints (pattern Pattern, c Case) (hints Hints) {
 | 
				
			||||||
	switch pattern {
 | 
					 | 
				
			||||||
	case PatternRaised:
 | 
					 | 
				
			||||||
		if c == C("basic", "listEntry") {
 | 
					 | 
				
			||||||
			hints.StaticInset = Inset { 0, 1, 0, 1 }
 | 
					 | 
				
			||||||
		} else {
 | 
					 | 
				
			||||||
			hints.StaticInset = Inset { 3, 3, 3, 3 }
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		
 | 
					 | 
				
			||||||
	case PatternSunken:
 | 
					 | 
				
			||||||
		if c == C("basic", "list") {
 | 
					 | 
				
			||||||
			hints.StaticInset = Inset { 2, 1, 2, 1 }
 | 
					 | 
				
			||||||
		} else {
 | 
					 | 
				
			||||||
			hints.StaticInset = Inset { 3, 3, 3, 3 }
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		
 | 
					 | 
				
			||||||
	case
 | 
					 | 
				
			||||||
		PatternPinboard,
 | 
					 | 
				
			||||||
		PatternInput,
 | 
					 | 
				
			||||||
		PatternButton,
 | 
					 | 
				
			||||||
		PatternHandle:
 | 
					 | 
				
			||||||
		
 | 
					 | 
				
			||||||
		hints.StaticInset = Inset { 3, 3, 3, 3 }
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	return
 | 
						return
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -1,272 +0,0 @@
 | 
				
			|||||||
package theme
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import "image/color"
 | 
					 | 
				
			||||||
import "git.tebibyte.media/sashakoshka/tomo/artist"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// var backgroundPattern     = artist.Gradient {
 | 
					 | 
				
			||||||
	// First: uhex(0xFF0000FF),
 | 
					 | 
				
			||||||
	// Second: uhex(0x00FF00FF),
 | 
					 | 
				
			||||||
// }
 | 
					 | 
				
			||||||
var accentPattern         = artist.NewUniform(hex(0x408090FF))
 | 
					 | 
				
			||||||
var backgroundPattern     = artist.NewUniform(color.Gray16 { 0xAAAA })
 | 
					 | 
				
			||||||
var foregroundPattern     = artist.NewUniform(color.Gray16 { 0x0000 })
 | 
					 | 
				
			||||||
var weakForegroundPattern = artist.NewUniform(color.Gray16 { 0x4444 })
 | 
					 | 
				
			||||||
var strokePattern         = artist.NewUniform(color.Gray16 { 0x0000 })
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
var sunkenPattern = artist.NewMultiBordered (
 | 
					 | 
				
			||||||
	artist.Stroke { Weight: 1, Pattern: strokePattern },
 | 
					 | 
				
			||||||
	artist.Stroke {
 | 
					 | 
				
			||||||
		Weight: 1,
 | 
					 | 
				
			||||||
		Pattern: artist.Beveled {
 | 
					 | 
				
			||||||
			artist.NewUniform(hex(0x3b534eFF)),
 | 
					 | 
				
			||||||
			artist.NewUniform(hex(0x97a09cFF)),
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
	},
 | 
					 | 
				
			||||||
	artist.Stroke { Pattern: artist.NewUniform(hex(0x97a09cFF)) })
 | 
					 | 
				
			||||||
var focusedSunkenPattern = artist.NewMultiBordered (
 | 
					 | 
				
			||||||
	artist.Stroke { Weight: 1, Pattern: strokePattern },
 | 
					 | 
				
			||||||
	artist.Stroke { Weight: 1, Pattern: accentPattern },
 | 
					 | 
				
			||||||
	artist.Stroke { Pattern: artist.NewUniform(hex(0x97a09cFF)) })
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
var texturedSunkenPattern = artist.NewMultiBordered (
 | 
					 | 
				
			||||||
	artist.Stroke { Weight: 1, Pattern: strokePattern },
 | 
					 | 
				
			||||||
	artist.Stroke {
 | 
					 | 
				
			||||||
		Weight: 1,
 | 
					 | 
				
			||||||
		Pattern: artist.Beveled {
 | 
					 | 
				
			||||||
			artist.NewUniform(hex(0x3b534eFF)),
 | 
					 | 
				
			||||||
			artist.NewUniform(hex(0x97a09cFF)),
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
	},
 | 
					 | 
				
			||||||
	
 | 
					 | 
				
			||||||
	artist.Stroke { Pattern: artist.Noisy {
 | 
					 | 
				
			||||||
		Low:  artist.NewUniform(hex(0x97a09cFF)),
 | 
					 | 
				
			||||||
		High: artist.NewUniform(hex(0x6e8079FF)),
 | 
					 | 
				
			||||||
	}})
 | 
					 | 
				
			||||||
var focusedTexturedSunkenPattern = artist.NewMultiBordered (
 | 
					 | 
				
			||||||
	artist.Stroke { Weight: 1, Pattern: strokePattern },
 | 
					 | 
				
			||||||
	artist.Stroke { Weight: 1, Pattern: accentPattern },
 | 
					 | 
				
			||||||
	artist.Stroke { Pattern: artist.Noisy {
 | 
					 | 
				
			||||||
		Low:  artist.NewUniform(hex(0x97a09cFF)),
 | 
					 | 
				
			||||||
		High: artist.NewUniform(hex(0x6e8079FF)),
 | 
					 | 
				
			||||||
	}})
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
var raisedPattern = artist.NewMultiBordered (
 | 
					 | 
				
			||||||
	artist.Stroke { Weight: 1, Pattern: strokePattern },
 | 
					 | 
				
			||||||
	artist.Stroke {
 | 
					 | 
				
			||||||
		Weight: 1,
 | 
					 | 
				
			||||||
		Pattern: artist.Beveled {
 | 
					 | 
				
			||||||
			artist.NewUniform(hex(0xDBDBDBFF)),
 | 
					 | 
				
			||||||
			artist.NewUniform(hex(0x383C3AFF)),
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
	},
 | 
					 | 
				
			||||||
	artist.Stroke { Pattern: artist.NewUniform(hex(0xAAAAAAFF)) })
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
var selectedRaisedPattern = artist.NewMultiBordered (
 | 
					 | 
				
			||||||
	artist.Stroke { Weight: 1, Pattern: strokePattern },
 | 
					 | 
				
			||||||
	artist.Stroke {
 | 
					 | 
				
			||||||
		Weight: 1,
 | 
					 | 
				
			||||||
		Pattern: artist.Beveled {
 | 
					 | 
				
			||||||
			artist.NewUniform(hex(0xDBDBDBFF)),
 | 
					 | 
				
			||||||
			artist.NewUniform(hex(0x383C3AFF)),
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
	},
 | 
					 | 
				
			||||||
	artist.Stroke { Weight: 1, Pattern: accentPattern },
 | 
					 | 
				
			||||||
	artist.Stroke { Pattern: artist.NewUniform(hex(0xAAAAAAFF)) })
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
var deadPattern = artist.NewMultiBordered (
 | 
					 | 
				
			||||||
	artist.Stroke { Weight: 1, Pattern: strokePattern },
 | 
					 | 
				
			||||||
	artist.Stroke { Pattern: artist.NewUniform(hex(0x97a09cFF)) })
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
var buttonPattern = artist.NewMultiBordered (
 | 
					 | 
				
			||||||
	artist.Stroke { Weight: 1, Pattern: strokePattern },
 | 
					 | 
				
			||||||
	artist.Stroke {
 | 
					 | 
				
			||||||
		Weight: 1,
 | 
					 | 
				
			||||||
		Pattern: artist.Beveled {
 | 
					 | 
				
			||||||
			artist.NewUniform(hex(0xCCD5D2FF)),
 | 
					 | 
				
			||||||
			artist.NewUniform(hex(0x4B5B59FF)),
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
	},
 | 
					 | 
				
			||||||
	artist.Stroke { Pattern: artist.NewUniform(hex(0x8D9894FF)) })
 | 
					 | 
				
			||||||
var selectedButtonPattern = artist.NewMultiBordered (
 | 
					 | 
				
			||||||
	artist.Stroke { Weight: 1, Pattern: strokePattern },
 | 
					 | 
				
			||||||
	artist.Stroke {
 | 
					 | 
				
			||||||
		Weight: 1,
 | 
					 | 
				
			||||||
		Pattern: artist.Beveled {
 | 
					 | 
				
			||||||
			artist.NewUniform(hex(0xCCD5D2FF)),
 | 
					 | 
				
			||||||
			artist.NewUniform(hex(0x4B5B59FF)),
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
	},
 | 
					 | 
				
			||||||
	artist.Stroke { Weight: 1, Pattern: accentPattern },
 | 
					 | 
				
			||||||
	artist.Stroke { Pattern: artist.NewUniform(hex(0x8D9894FF)) })
 | 
					 | 
				
			||||||
var pressedButtonPattern = artist.NewMultiBordered (
 | 
					 | 
				
			||||||
	artist.Stroke { Weight: 1, Pattern: strokePattern },
 | 
					 | 
				
			||||||
	artist.Stroke {
 | 
					 | 
				
			||||||
		Weight: 1,
 | 
					 | 
				
			||||||
		Pattern: artist.Beveled {
 | 
					 | 
				
			||||||
			artist.NewUniform(hex(0x4B5B59FF)),
 | 
					 | 
				
			||||||
			artist.NewUniform(hex(0x8D9894FF)),
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
	},
 | 
					 | 
				
			||||||
	artist.Stroke { Pattern: artist.NewUniform(hex(0x8D9894FF)) })
 | 
					 | 
				
			||||||
var pressedSelectedButtonPattern = artist.NewMultiBordered (
 | 
					 | 
				
			||||||
	artist.Stroke { Weight: 1, Pattern: strokePattern },
 | 
					 | 
				
			||||||
	artist.Stroke {
 | 
					 | 
				
			||||||
		Weight: 1,
 | 
					 | 
				
			||||||
		Pattern: artist.Beveled {
 | 
					 | 
				
			||||||
			artist.NewUniform(hex(0x4B5B59FF)),
 | 
					 | 
				
			||||||
			artist.NewUniform(hex(0x8D9894FF)),
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
	},
 | 
					 | 
				
			||||||
	artist.Stroke { Pattern: artist.NewUniform(hex(0x8D9894FF)) })
 | 
					 | 
				
			||||||
var disabledButtonPattern = artist.NewMultiBordered (
 | 
					 | 
				
			||||||
	artist.Stroke { Weight: 1, Pattern: weakForegroundPattern },
 | 
					 | 
				
			||||||
	artist.Stroke { Pattern: backgroundPattern })
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
var darkButtonPattern = artist.NewMultiBordered (
 | 
					 | 
				
			||||||
	artist.Stroke { Weight: 1, Pattern: strokePattern },
 | 
					 | 
				
			||||||
	artist.Stroke {
 | 
					 | 
				
			||||||
		Weight: 1,
 | 
					 | 
				
			||||||
		Pattern: artist.Beveled {
 | 
					 | 
				
			||||||
			artist.NewUniform(hex(0xaebdb9FF)),
 | 
					 | 
				
			||||||
			artist.NewUniform(hex(0x3b4947FF)),
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
	},
 | 
					 | 
				
			||||||
	artist.Stroke { Pattern: artist.NewUniform(hex(0x6b7a75FF)) })
 | 
					 | 
				
			||||||
var pressedDarkButtonPattern = artist.NewMultiBordered (
 | 
					 | 
				
			||||||
	artist.Stroke { Weight: 1, Pattern: strokePattern },
 | 
					 | 
				
			||||||
	artist.Stroke {
 | 
					 | 
				
			||||||
		Weight: 1,
 | 
					 | 
				
			||||||
		Pattern: artist.Beveled {
 | 
					 | 
				
			||||||
			artist.NewUniform(hex(0x3b4947FF)),
 | 
					 | 
				
			||||||
			artist.NewUniform(hex(0x6b7a75FF)),
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
	},
 | 
					 | 
				
			||||||
	artist.Stroke { Pattern: artist.NewUniform(hex(0x6b7a75FF)) })
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
var inputPattern = artist.NewMultiBordered (
 | 
					 | 
				
			||||||
	artist.Stroke { Weight: 1, Pattern: strokePattern },
 | 
					 | 
				
			||||||
	artist.Stroke {
 | 
					 | 
				
			||||||
		Weight: 1,
 | 
					 | 
				
			||||||
		Pattern: artist.Beveled {
 | 
					 | 
				
			||||||
			artist.NewUniform(hex(0x89925AFF)),
 | 
					 | 
				
			||||||
			artist.NewUniform(hex(0xD2CB9AFF)),
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
	},
 | 
					 | 
				
			||||||
	artist.Stroke { Pattern: artist.NewUniform(hex(0xD2CB9AFF)) })
 | 
					 | 
				
			||||||
var selectedInputPattern = artist.NewMultiBordered (
 | 
					 | 
				
			||||||
	artist.Stroke { Weight: 1, Pattern: strokePattern },
 | 
					 | 
				
			||||||
	artist.Stroke { Weight: 1, Pattern: accentPattern },
 | 
					 | 
				
			||||||
	artist.Stroke { Pattern: artist.NewUniform(hex(0xD2CB9AFF)) })
 | 
					 | 
				
			||||||
var disabledInputPattern = artist.NewMultiBordered (
 | 
					 | 
				
			||||||
	artist.Stroke { Weight: 1, Pattern: weakForegroundPattern },
 | 
					 | 
				
			||||||
	artist.Stroke { Pattern: backgroundPattern })
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
var listPattern = artist.NewMultiBordered (
 | 
					 | 
				
			||||||
	artist.Stroke { Weight: 1, Pattern: strokePattern },
 | 
					 | 
				
			||||||
	artist.Stroke {
 | 
					 | 
				
			||||||
		Weight: 1,
 | 
					 | 
				
			||||||
		Pattern: artist.Beveled {
 | 
					 | 
				
			||||||
			uhex(0x383C3AFF),
 | 
					 | 
				
			||||||
			uhex(0x999C99FF),
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
	},
 | 
					 | 
				
			||||||
	artist.Stroke { Pattern: uhex(0x999C99FF) })
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
var focusedListPattern = artist.NewMultiBordered (
 | 
					 | 
				
			||||||
	artist.Stroke { Weight: 1, Pattern: strokePattern },
 | 
					 | 
				
			||||||
	artist.Stroke { Weight: 1, Pattern: accentPattern },
 | 
					 | 
				
			||||||
	artist.Stroke { Pattern: uhex(0x999C99FF) })
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
var listEntryPattern = artist.Padded {
 | 
					 | 
				
			||||||
	Stroke: uhex(0x383C3AFF),
 | 
					 | 
				
			||||||
	Fill:   uhex(0x999C99FF),
 | 
					 | 
				
			||||||
	Sides:  []int { 0, 0, 0, 1 },
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
var onListEntryPattern = artist.Padded {
 | 
					 | 
				
			||||||
	Stroke: uhex(0x383C3AFF),
 | 
					 | 
				
			||||||
	Fill:   uhex(0x6e8079FF),
 | 
					 | 
				
			||||||
	Sides:  []int { 0, 0, 0, 1 },
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
var focusedListEntryPattern = artist.Padded {
 | 
					 | 
				
			||||||
	Stroke: accentPattern,
 | 
					 | 
				
			||||||
	Fill:   uhex(0x999C99FF),
 | 
					 | 
				
			||||||
	Sides:  []int { 0, 1, 0, 1 },
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
var focusedOnListEntryPattern = artist.Padded {
 | 
					 | 
				
			||||||
	Stroke: accentPattern,
 | 
					 | 
				
			||||||
	Fill:   uhex(0x6e8079FF),
 | 
					 | 
				
			||||||
	Sides:  []int { 0, 1, 0, 1 },
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
var scrollGutterPattern = artist.NewMultiBordered (
 | 
					 | 
				
			||||||
	artist.Stroke { Weight: 1, Pattern: strokePattern },
 | 
					 | 
				
			||||||
	artist.Stroke {
 | 
					 | 
				
			||||||
		Weight: 1,
 | 
					 | 
				
			||||||
		Pattern: artist.Beveled {
 | 
					 | 
				
			||||||
			artist.NewUniform(hex(0x3b534eFF)),
 | 
					 | 
				
			||||||
			artist.NewUniform(hex(0x6e8079FF)),
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
	},
 | 
					 | 
				
			||||||
	artist.Stroke { Pattern: artist.NewUniform(hex(0x6e8079FF)) })
 | 
					 | 
				
			||||||
var thinScrollGutterPattern = artist.Padded {
 | 
					 | 
				
			||||||
	Fill: scrollGutterPattern,
 | 
					 | 
				
			||||||
	Stroke: sunkenPattern,
 | 
					 | 
				
			||||||
	Sides: []int{  6, 6, 6, 6 },
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
var disabledScrollGutterPattern = artist.NewMultiBordered (
 | 
					 | 
				
			||||||
	artist.Stroke { Weight: 1, Pattern: weakForegroundPattern },
 | 
					 | 
				
			||||||
	artist.Stroke { Pattern: backgroundPattern })
 | 
					 | 
				
			||||||
var disabledThinScrollGutterPattern = artist.Padded {
 | 
					 | 
				
			||||||
	Fill: disabledScrollGutterPattern,
 | 
					 | 
				
			||||||
	Stroke: disabledButtonPattern,
 | 
					 | 
				
			||||||
	Sides: []int{ 6, 6, 6, 6},
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
var scrollBarPattern = artist.NewMultiBordered (
 | 
					 | 
				
			||||||
	artist.Stroke { Weight: 1, Pattern: strokePattern },
 | 
					 | 
				
			||||||
	artist.Stroke {
 | 
					 | 
				
			||||||
		Weight: 1,
 | 
					 | 
				
			||||||
		Pattern: artist.Beveled {
 | 
					 | 
				
			||||||
			artist.NewUniform(hex(0xCCD5D2FF)),
 | 
					 | 
				
			||||||
			artist.NewUniform(hex(0x4B5B59FF)),
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
	},
 | 
					 | 
				
			||||||
	artist.Stroke { Pattern: artist.NewUniform(hex(0x8D9894FF)) })
 | 
					 | 
				
			||||||
var selectedScrollBarPattern = artist.NewMultiBordered (
 | 
					 | 
				
			||||||
	artist.Stroke { Weight: 1, Pattern: strokePattern },
 | 
					 | 
				
			||||||
	artist.Stroke {
 | 
					 | 
				
			||||||
		Weight: 1,
 | 
					 | 
				
			||||||
		Pattern: artist.Beveled {
 | 
					 | 
				
			||||||
			artist.NewUniform(hex(0xCCD5D2FF)),
 | 
					 | 
				
			||||||
			artist.NewUniform(hex(0x4B5B59FF)),
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
	},
 | 
					 | 
				
			||||||
	artist.Stroke { Weight: 1, Pattern: accentPattern },
 | 
					 | 
				
			||||||
	artist.Stroke { Pattern: artist.NewUniform(hex(0x8D9894FF)) })
 | 
					 | 
				
			||||||
var pressedScrollBarPattern = artist.NewMultiBordered (
 | 
					 | 
				
			||||||
	artist.Stroke { Weight: 1, Pattern: strokePattern },
 | 
					 | 
				
			||||||
	artist.Stroke {
 | 
					 | 
				
			||||||
		Weight: 1,
 | 
					 | 
				
			||||||
		Pattern: artist.Beveled {
 | 
					 | 
				
			||||||
			artist.NewUniform(hex(0xCCD5D2FF)),
 | 
					 | 
				
			||||||
			artist.NewUniform(hex(0x4B5B59FF)),
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
	},
 | 
					 | 
				
			||||||
	artist.Stroke { Weight: 1, Pattern: artist.NewUniform(hex(0x8D9894FF)) },
 | 
					 | 
				
			||||||
	artist.Stroke { Pattern: artist.NewUniform(hex(0x7f8c89FF)) })
 | 
					 | 
				
			||||||
var pressedSelectedScrollBarPattern = artist.NewMultiBordered (
 | 
					 | 
				
			||||||
	artist.Stroke { Weight: 1, Pattern: strokePattern },
 | 
					 | 
				
			||||||
	artist.Stroke {
 | 
					 | 
				
			||||||
		Weight: 1,
 | 
					 | 
				
			||||||
		Pattern: artist.Beveled {
 | 
					 | 
				
			||||||
			artist.NewUniform(hex(0xCCD5D2FF)),
 | 
					 | 
				
			||||||
			artist.NewUniform(hex(0x4B5B59FF)),
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
	},
 | 
					 | 
				
			||||||
	artist.Stroke { Weight: 1, Pattern: accentPattern },
 | 
					 | 
				
			||||||
	artist.Stroke { Pattern: artist.NewUniform(hex(0x7f8c89FF)) })
 | 
					 | 
				
			||||||
var disabledScrollBarPattern = artist.NewMultiBordered (
 | 
					 | 
				
			||||||
	artist.Stroke { Weight: 1, Pattern: weakForegroundPattern },
 | 
					 | 
				
			||||||
	artist.Stroke { Pattern: backgroundPattern })
 | 
					 | 
				
			||||||
@ -8,7 +8,7 @@ package theme
 | 
				
			|||||||
// specific elements.
 | 
					// specific elements.
 | 
				
			||||||
type Case struct { Namespace, Element string } 
 | 
					type Case struct { Namespace, Element string } 
 | 
				
			||||||
 
 | 
					 
 | 
				
			||||||
// C can be used as shorthand to generate a case struct as used in PatternState.
 | 
					// C can be used as shorthand to generate a case struct as used in State.
 | 
				
			||||||
func C (namespace, element string) (c Case) {
 | 
					func C (namespace, element string) (c Case) {
 | 
				
			||||||
	return Case {
 | 
						return Case {
 | 
				
			||||||
		Namespace: namespace,
 | 
							Namespace: namespace,
 | 
				
			||||||
@ -16,14 +16,14 @@ func C (namespace, element string) (c Case) {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// PatternState lists parameters which can change the appearance of some
 | 
					// State lists parameters which can change the appearance of some patterns and
 | 
				
			||||||
// patterns. For example, passing a PatternState with Selected set to true may
 | 
					// colors. For example, passing a State with Selected set to true may result in
 | 
				
			||||||
// result in a pattern that has a colored border within it.
 | 
					// a pattern that has a colored border within it.
 | 
				
			||||||
type PatternState struct {
 | 
					type State struct {
 | 
				
			||||||
	// On should be set to true if the element that is using this pattern is
 | 
						// On should be set to true if the element that is using this pattern is
 | 
				
			||||||
	// in some sort of "on" state, such as if a checkbox is checked or a
 | 
						// in some sort of selected or "on" state, such as if a checkbox is
 | 
				
			||||||
	// switch is toggled on. This is only necessary if the element in
 | 
						// checked or a switch is toggled on. This is only necessary if the
 | 
				
			||||||
	// question is capable of being toggled.
 | 
						// element in question is capable of being toggled or selected.
 | 
				
			||||||
	On bool
 | 
						On bool
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Focused should be set to true if the element that is using this
 | 
						// Focused should be set to true if the element that is using this
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										101
									
								
								theme/theme.go
									
									
									
									
									
								
							
							
						
						
									
										101
									
								
								theme/theme.go
									
									
									
									
									
								
							@ -18,17 +18,9 @@ const (
 | 
				
			|||||||
// This allows custom elements to follow themes, even those that do not
 | 
					// This allows custom elements to follow themes, even those that do not
 | 
				
			||||||
// explicitly support them.
 | 
					// explicitly support them.
 | 
				
			||||||
type Pattern int; const (
 | 
					type Pattern int; const (
 | 
				
			||||||
	// PatternAccent is the accent color of the theme. It is safe to assume
 | 
						// PatternBackground is the window background of the theme. It appears
 | 
				
			||||||
	// that this is, by default, a solid color.
 | 
						// in things like containers and behind text.
 | 
				
			||||||
	PatternAccent Pattern = iota
 | 
						PatternBackground Pattern = iota
 | 
				
			||||||
 | 
					 | 
				
			||||||
	// PatternBackground is the background color of the theme. It is safe to
 | 
					 | 
				
			||||||
	// assume that this is, by default, a solid color.
 | 
					 | 
				
			||||||
	PatternBackground
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// PatternForeground is the foreground text color of the theme. It is
 | 
					 | 
				
			||||||
	// safe to assume that this is, by default, a solid color.
 | 
					 | 
				
			||||||
	PatternForeground
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// PatternDead is a pattern that is displayed on a "dead area" where no
 | 
						// PatternDead is a pattern that is displayed on a "dead area" where no
 | 
				
			||||||
	// controls exist, but there still must be some indication of visual
 | 
						// controls exist, but there still must be some indication of visual
 | 
				
			||||||
@ -55,6 +47,20 @@ type Pattern int; const (
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	// PatternHandle is a handle that slides along a gutter.
 | 
						// PatternHandle is a handle that slides along a gutter.
 | 
				
			||||||
	PatternHandle
 | 
						PatternHandle
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// PatternLine is an engraved line that separates things.
 | 
				
			||||||
 | 
						PatternLine
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// PatternMercury is a fill pattern for progress bars, meters, etc.
 | 
				
			||||||
 | 
						PatternMercury
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type Color int; const (
 | 
				
			||||||
 | 
						// ColorAccent is the accent color of the theme.
 | 
				
			||||||
 | 
						ColorAccent Color = iota
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// ColorForeground is the text/icon color of the theme.
 | 
				
			||||||
 | 
						ColorForeground
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Hints specifies rendering hints for a particular pattern. Elements can take
 | 
					// Hints specifies rendering hints for a particular pattern. Elements can take
 | 
				
			||||||
@ -63,7 +69,7 @@ type Hints struct {
 | 
				
			|||||||
	// StaticInset defines an inset rectangular area in the middle of the
 | 
						// StaticInset defines an inset rectangular area in the middle of the
 | 
				
			||||||
	// pattern that does not change between PatternStates. If the inset is
 | 
						// pattern that does not change between PatternStates. If the inset is
 | 
				
			||||||
	// zero on all sides, this hint does not apply.
 | 
						// zero on all sides, this hint does not apply.
 | 
				
			||||||
	StaticInset Inset
 | 
						StaticInset artist.Inset
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Uniform specifies a singular color for the entire pattern. If the
 | 
						// Uniform specifies a singular color for the entire pattern. If the
 | 
				
			||||||
	// alpha channel is zero, this hint does not apply.
 | 
						// alpha channel is zero, this hint does not apply.
 | 
				
			||||||
@ -80,11 +86,20 @@ type Theme interface {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	// Pattern returns an appropriate pattern given a pattern name, case,
 | 
						// Pattern returns an appropriate pattern given a pattern name, case,
 | 
				
			||||||
	// and state.
 | 
						// and state.
 | 
				
			||||||
	Pattern (Pattern, PatternState, Case) artist.Pattern
 | 
						Pattern (Pattern, State, Case) artist.Pattern
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Inset returns the area on all sides of a given pattern that is not
 | 
						// Color returns an appropriate pattern given a color name, case, and
 | 
				
			||||||
	// meant to be drawn on.
 | 
						// state.
 | 
				
			||||||
	Inset (Pattern, Case) Inset
 | 
						Color (Color, State, Case) color.RGBA
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Padding returns how much space should be between the bounds of a
 | 
				
			||||||
 | 
						// pattern whatever an element draws inside of it.
 | 
				
			||||||
 | 
						Padding (Pattern, Case) artist.Inset
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Margin returns the left/right (x) and top/bottom (y) margins that
 | 
				
			||||||
 | 
						// should be put between any self-contained objects drawn within this
 | 
				
			||||||
 | 
						// pattern (if applicable).
 | 
				
			||||||
 | 
						Margin (Pattern, Case) image.Point
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Sink returns a vector that should be added to an element's inner
 | 
						// Sink returns a vector that should be added to an element's inner
 | 
				
			||||||
	// content when it is pressed down (if applicable) to simulate a 3D
 | 
						// content when it is pressed down (if applicable) to simulate a 3D
 | 
				
			||||||
@ -96,57 +111,3 @@ type Theme interface {
 | 
				
			|||||||
	// performance.
 | 
						// performance.
 | 
				
			||||||
	Hints (Pattern, Case) Hints
 | 
						Hints (Pattern, Case) Hints
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					 | 
				
			||||||
// Wrapped wraps any theme and injects a case into it automatically so that it
 | 
					 | 
				
			||||||
// doesn't need to be specified for each query. Additionally, if the underlying
 | 
					 | 
				
			||||||
// theme is nil, it just uses the default theme instead.
 | 
					 | 
				
			||||||
type Wrapped struct {
 | 
					 | 
				
			||||||
	Theme
 | 
					 | 
				
			||||||
	Case
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// FontFace returns the proper font for a given style and size.
 | 
					 | 
				
			||||||
func (wrapped Wrapped) FontFace (style FontStyle, size FontSize) font.Face {
 | 
					 | 
				
			||||||
	real := wrapped.ensure()
 | 
					 | 
				
			||||||
	return real.FontFace(style, size, wrapped.Case)
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// Icon returns an appropriate icon given an icon name.
 | 
					 | 
				
			||||||
func (wrapped Wrapped) Icon (name string, size IconSize) canvas.Image {
 | 
					 | 
				
			||||||
	real := wrapped.ensure()
 | 
					 | 
				
			||||||
	return real.Icon(name, size, wrapped.Case)
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// Pattern returns an appropriate pattern given a pattern name and state.
 | 
					 | 
				
			||||||
func (wrapped Wrapped) Pattern (id Pattern, state PatternState) artist.Pattern {
 | 
					 | 
				
			||||||
	real := wrapped.ensure()
 | 
					 | 
				
			||||||
	return real.Pattern(id, state, wrapped.Case)
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// Inset returns the area on all sides of a given pattern that is not meant to
 | 
					 | 
				
			||||||
// be drawn on.
 | 
					 | 
				
			||||||
func (wrapped Wrapped) Inset (id Pattern) Inset {
 | 
					 | 
				
			||||||
	real := wrapped.ensure()
 | 
					 | 
				
			||||||
	return real.Inset(id, wrapped.Case)
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// Sink returns a vector that should be added to an element's inner content when
 | 
					 | 
				
			||||||
// it is pressed down (if applicable) to simulate a 3D sinking effect.
 | 
					 | 
				
			||||||
func (wrapped Wrapped) Sink (id Pattern) image.Point {
 | 
					 | 
				
			||||||
	real := wrapped.ensure()
 | 
					 | 
				
			||||||
	return real.Sink(id, wrapped.Case)
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// Hints returns rendering optimization hints for a particular pattern.
 | 
					 | 
				
			||||||
// These are optional, but following them may result in improved
 | 
					 | 
				
			||||||
// performance.
 | 
					 | 
				
			||||||
func (wrapped Wrapped) Hints (id Pattern) Hints {
 | 
					 | 
				
			||||||
	real := wrapped.ensure()
 | 
					 | 
				
			||||||
	return real.Hints(id, wrapped.Case)
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func (wrapped Wrapped) ensure () (real Theme) {
 | 
					 | 
				
			||||||
	real = wrapped.Theme
 | 
					 | 
				
			||||||
	if real == nil { real = Default { } }
 | 
					 | 
				
			||||||
	return
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
				
			|||||||
@ -1,16 +0,0 @@
 | 
				
			|||||||
package theme
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import "image/color"
 | 
					 | 
				
			||||||
import "git.tebibyte.media/sashakoshka/tomo/artist"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func hex (color uint32) (c color.RGBA) {
 | 
					 | 
				
			||||||
	c.A = uint8(color)
 | 
					 | 
				
			||||||
	c.B = uint8(color >>  8)
 | 
					 | 
				
			||||||
	c.G = uint8(color >> 16)
 | 
					 | 
				
			||||||
	c.R = uint8(color >> 24)
 | 
					 | 
				
			||||||
	return
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func uhex (color uint32) (pattern artist.Pattern) {
 | 
					 | 
				
			||||||
	return artist.NewUniform(hex(color))
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
							
								
								
									
										75
									
								
								theme/wrapped.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										75
									
								
								theme/wrapped.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,75 @@
 | 
				
			|||||||
 | 
					package theme
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import "image"
 | 
				
			||||||
 | 
					import "image/color"
 | 
				
			||||||
 | 
					import "golang.org/x/image/font"
 | 
				
			||||||
 | 
					import "git.tebibyte.media/sashakoshka/tomo/artist"
 | 
				
			||||||
 | 
					import "git.tebibyte.media/sashakoshka/tomo/canvas"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Wrapped wraps any theme and injects a case into it automatically so that it
 | 
				
			||||||
 | 
					// doesn't need to be specified for each query. Additionally, if the underlying
 | 
				
			||||||
 | 
					// theme is nil, it just uses the default theme instead.
 | 
				
			||||||
 | 
					type Wrapped struct {
 | 
				
			||||||
 | 
						Theme
 | 
				
			||||||
 | 
						Case
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// FontFace returns the proper font for a given style and size.
 | 
				
			||||||
 | 
					func (wrapped Wrapped) FontFace (style FontStyle, size FontSize) font.Face {
 | 
				
			||||||
 | 
						real := wrapped.ensure()
 | 
				
			||||||
 | 
						return real.FontFace(style, size, wrapped.Case)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Icon returns an appropriate icon given an icon name.
 | 
				
			||||||
 | 
					func (wrapped Wrapped) Icon (name string, size IconSize) canvas.Image {
 | 
				
			||||||
 | 
						real := wrapped.ensure()
 | 
				
			||||||
 | 
						return real.Icon(name, size, wrapped.Case)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Pattern returns an appropriate pattern given a pattern name and state.
 | 
				
			||||||
 | 
					func (wrapped Wrapped) Pattern (id Pattern, state State) artist.Pattern {
 | 
				
			||||||
 | 
						real := wrapped.ensure()
 | 
				
			||||||
 | 
						return real.Pattern(id, state, wrapped.Case)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Color returns an appropriate color given a color name and state.
 | 
				
			||||||
 | 
					func (wrapped Wrapped) Color (id Color, state State) color.RGBA {
 | 
				
			||||||
 | 
						real := wrapped.ensure()
 | 
				
			||||||
 | 
						return real.Color(id, state, wrapped.Case)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Padding returns how much space should be between the bounds of a
 | 
				
			||||||
 | 
					// pattern whatever an element draws inside of it.
 | 
				
			||||||
 | 
					func (wrapped Wrapped) Padding (id Pattern) artist.Inset {
 | 
				
			||||||
 | 
						real := wrapped.ensure()
 | 
				
			||||||
 | 
						return real.Padding(id, wrapped.Case)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Margin returns the left/right (x) and top/bottom (y) margins that
 | 
				
			||||||
 | 
					// should be put between any self-contained objects drawn within this
 | 
				
			||||||
 | 
					// pattern (if applicable).
 | 
				
			||||||
 | 
					func (wrapped Wrapped) Margin (id Pattern) image.Point {
 | 
				
			||||||
 | 
						real := wrapped.ensure()
 | 
				
			||||||
 | 
						return real.Margin(id, wrapped.Case)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Sink returns a vector that should be added to an element's inner content when
 | 
				
			||||||
 | 
					// it is pressed down (if applicable) to simulate a 3D sinking effect.
 | 
				
			||||||
 | 
					func (wrapped Wrapped) Sink (id Pattern) image.Point {
 | 
				
			||||||
 | 
						real := wrapped.ensure()
 | 
				
			||||||
 | 
						return real.Sink(id, wrapped.Case)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Hints returns rendering optimization hints for a particular pattern.
 | 
				
			||||||
 | 
					// These are optional, but following them may result in improved
 | 
				
			||||||
 | 
					// performance.
 | 
				
			||||||
 | 
					func (wrapped Wrapped) Hints (id Pattern) Hints {
 | 
				
			||||||
 | 
						real := wrapped.ensure()
 | 
				
			||||||
 | 
						return real.Hints(id, wrapped.Case)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (wrapped Wrapped) ensure () (real Theme) {
 | 
				
			||||||
 | 
						real = wrapped.Theme
 | 
				
			||||||
 | 
						if real == nil { real = Default { } }
 | 
				
			||||||
 | 
						return
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
		Reference in New Issue
	
	Block a user