Merge pull request 'flexible-elements-were-a-mistake' (#11) from flexible-elements-were-a-mistake into main
Reviewed-on: sashakoshka/tomo#11
This commit is contained in:
		
						commit
						9d84c50db3
					
				@ -7,61 +7,55 @@ import "git.tebibyte.media/sashakoshka/tomo/shatter"
 | 
				
			|||||||
// Pattern is capable of drawing to a canvas within the bounds of a given
 | 
					// Pattern is capable of drawing to a canvas within the bounds of a given
 | 
				
			||||||
// clipping rectangle.
 | 
					// clipping rectangle.
 | 
				
			||||||
type Pattern interface {
 | 
					type Pattern interface {
 | 
				
			||||||
	// Draw draws to destination, using the bounds of destination as a width
 | 
						// Draw draws the pattern onto the destination canvas, using the
 | 
				
			||||||
	// and height for things like gradients, bevels, etc. The pattern may
 | 
						// specified bounds. The given bounds can be smaller or larger than the
 | 
				
			||||||
	// not draw outside the union of destination.Bounds() and clip. The
 | 
						// bounds of the destination canvas. The destination canvas can be cut
 | 
				
			||||||
	// clipping rectangle effectively takes a subset of the pattern. To
 | 
						// using canvas.Cut() to draw only a specific subset of a pattern.
 | 
				
			||||||
	// change the bounds of the pattern itself, use canvas.Cut() on the
 | 
						Draw (destination canvas.Canvas, bounds image.Rectangle)
 | 
				
			||||||
	// destination before passing it to Draw().
 | 
					 | 
				
			||||||
	Draw (destination canvas.Canvas, clip image.Rectangle)
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Draw lets you use several clipping rectangles to draw a pattern.
 | 
					// Fill fills the destination canvas with the given pattern.
 | 
				
			||||||
func Draw (
 | 
					func Fill (destination canvas.Canvas, source Pattern) (updated image.Rectangle) {
 | 
				
			||||||
 | 
						source.Draw(destination, destination.Bounds())
 | 
				
			||||||
 | 
						return destination.Bounds()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// DrawClip lets you draw several subsets of a pattern at once.
 | 
				
			||||||
 | 
					func DrawClip (
 | 
				
			||||||
	destination canvas.Canvas,
 | 
						destination canvas.Canvas,
 | 
				
			||||||
	source      Pattern,
 | 
						source      Pattern,
 | 
				
			||||||
	clips       ...image.Rectangle,
 | 
						bounds      image.Rectangle,
 | 
				
			||||||
 | 
						subsets     ...image.Rectangle,
 | 
				
			||||||
) (
 | 
					) (
 | 
				
			||||||
	updatedRegion image.Rectangle,
 | 
						updatedRegion image.Rectangle,
 | 
				
			||||||
) {
 | 
					) {
 | 
				
			||||||
	for _, clip := range clips {
 | 
						for _, subset := range subsets {
 | 
				
			||||||
		source.Draw(destination, clip)
 | 
							source.Draw(canvas.Cut(destination, subset), bounds)
 | 
				
			||||||
		updatedRegion = updatedRegion.Union(clip)
 | 
							updatedRegion = updatedRegion.Union(subset)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	return
 | 
						return
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// DrawBounds lets you specify an overall bounding rectangle for drawing a
 | 
					// DrawShatter is like an inverse of DrawClip, drawing nothing in the areas
 | 
				
			||||||
// pattern. The destination is cut to this rectangle.
 | 
					// specified by "rocks".
 | 
				
			||||||
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 (
 | 
					func DrawShatter (
 | 
				
			||||||
	destination canvas.Canvas,
 | 
						destination canvas.Canvas,
 | 
				
			||||||
	source      Pattern,
 | 
						source      Pattern,
 | 
				
			||||||
 | 
						bounds      image.Rectangle,
 | 
				
			||||||
	rocks       ...image.Rectangle,
 | 
						rocks       ...image.Rectangle,
 | 
				
			||||||
) (
 | 
					) (
 | 
				
			||||||
	updatedRegion image.Rectangle,
 | 
						updatedRegion image.Rectangle,
 | 
				
			||||||
) {
 | 
					) {
 | 
				
			||||||
	tiles := shatter.Shatter(destination.Bounds(), rocks...)
 | 
						tiles := shatter.Shatter(bounds, rocks...)
 | 
				
			||||||
	return Draw(destination, source, tiles...)
 | 
						return DrawClip(destination, source, bounds, tiles...)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// AllocateSample returns a new canvas containing the result of a pattern. The
 | 
					// 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
 | 
					// 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
 | 
					// 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.
 | 
					// it because that is horrible and cruel to the computer.
 | 
				
			||||||
func AllocateSample (source Pattern, width, height int) (allocated canvas.Canvas) {
 | 
					func AllocateSample (source Pattern, width, height int) canvas.Canvas {
 | 
				
			||||||
	allocated = canvas.NewBasicCanvas(width, height)
 | 
						allocated := canvas.NewBasicCanvas(width, height)
 | 
				
			||||||
	source.Draw(allocated, allocated.Bounds())
 | 
						Fill(allocated, source)
 | 
				
			||||||
	return
 | 
						return allocated
 | 
				
			||||||
} 
 | 
					} 
 | 
				
			||||||
 | 
				
			|||||||
@ -37,9 +37,9 @@ type Border struct {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
// Draw draws the border pattern onto the destination canvas within the clipping
 | 
					// Draw draws the border pattern onto the destination canvas within the clipping
 | 
				
			||||||
// bounds.
 | 
					// bounds.
 | 
				
			||||||
func (pattern Border) Draw (destination canvas.Canvas, clip image.Rectangle) {
 | 
					func (pattern Border) Draw (destination canvas.Canvas, bounds image.Rectangle) {
 | 
				
			||||||
	bounds := clip.Canon().Intersect(destination.Bounds())
 | 
						drawBounds := bounds.Canon().Intersect(destination.Bounds())
 | 
				
			||||||
	if bounds.Empty() { return }
 | 
						if drawBounds.Empty() { return }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	srcSections := nonasect(pattern.Bounds(), pattern.Inset)
 | 
						srcSections := nonasect(pattern.Bounds(), pattern.Inset)
 | 
				
			||||||
	srcTextures := [9]Texture { }
 | 
						srcTextures := [9]Texture { }
 | 
				
			||||||
@ -47,9 +47,9 @@ func (pattern Border) Draw (destination canvas.Canvas, clip image.Rectangle) {
 | 
				
			|||||||
		srcTextures[index].Canvas = canvas.Cut(pattern, section)
 | 
							srcTextures[index].Canvas = canvas.Cut(pattern, section)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
	dstSections := nonasect(destination.Bounds(), pattern.Inset)
 | 
						dstSections := nonasect(bounds, pattern.Inset)
 | 
				
			||||||
	for index, section := range dstSections {
 | 
						for index, section := range dstSections {
 | 
				
			||||||
		srcTextures[index].Draw(canvas.Cut(destination, section), clip)
 | 
							srcTextures[index].Draw(destination, section)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -9,25 +9,24 @@ type Texture struct {
 | 
				
			|||||||
	canvas.Canvas
 | 
						canvas.Canvas
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Draw tiles the pattern's canvas within the clipping bounds. The minimum
 | 
					// Draw tiles the pattern's canvas within the given bounds. The minimum
 | 
				
			||||||
// points of the pattern's canvas and the destination canvas will be lined up.
 | 
					// points of the pattern's canvas and the destination canvas will be lined up.
 | 
				
			||||||
func (pattern Texture) Draw (destination canvas.Canvas, clip image.Rectangle) {
 | 
					func (pattern Texture) Draw (destination canvas.Canvas, bounds image.Rectangle) {
 | 
				
			||||||
	realBounds := destination.Bounds()
 | 
						drawBounds := bounds.Canon().Intersect(destination.Bounds())
 | 
				
			||||||
	bounds := clip.Canon().Intersect(realBounds)
 | 
						if drawBounds.Empty() { return }
 | 
				
			||||||
	if bounds.Empty() { return }
 | 
					 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
	dstData, dstStride := destination.Buffer()
 | 
						dstData, dstStride := destination.Buffer()
 | 
				
			||||||
	srcData, srcStride := pattern.Buffer()
 | 
						srcData, srcStride := pattern.Buffer()
 | 
				
			||||||
	srcBounds := pattern.Bounds()
 | 
						srcBounds := pattern.Bounds()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	dstPoint := image.Point { }
 | 
						dstPoint := image.Point { }
 | 
				
			||||||
	srcPoint := bounds.Min.Sub(realBounds.Min).Add(srcBounds.Min)
 | 
						srcPoint := drawBounds.Min.Sub(bounds.Min).Add(srcBounds.Min)
 | 
				
			||||||
	srcPoint.X = wrap(srcPoint.X, srcBounds.Min.X, srcBounds.Max.X)
 | 
						srcPoint.X = wrap(srcPoint.X, srcBounds.Min.X, srcBounds.Max.X)
 | 
				
			||||||
	srcPoint.Y = wrap(srcPoint.Y, srcBounds.Min.Y, srcBounds.Max.Y)
 | 
						srcPoint.Y = wrap(srcPoint.Y, srcBounds.Min.Y, srcBounds.Max.Y)
 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
	for dstPoint.Y = bounds.Min.Y; dstPoint.Y < bounds.Max.Y; dstPoint.Y ++ {
 | 
						for dstPoint.Y = drawBounds.Min.Y; dstPoint.Y < drawBounds.Max.Y; dstPoint.Y ++ {
 | 
				
			||||||
		srcPoint.X = srcBounds.Min.X
 | 
							srcPoint.X = srcBounds.Min.X
 | 
				
			||||||
		dstPoint.X = bounds.Min.X
 | 
							dstPoint.X = drawBounds.Min.X
 | 
				
			||||||
		dstYComponent := dstPoint.Y * dstStride
 | 
							dstYComponent := dstPoint.Y * dstStride
 | 
				
			||||||
		srcYComponent := srcPoint.Y * srcStride
 | 
							srcYComponent := srcPoint.Y * srcStride
 | 
				
			||||||
		
 | 
							
 | 
				
			||||||
@ -42,7 +41,7 @@ func (pattern Texture) Draw (destination canvas.Canvas, clip image.Rectangle) {
 | 
				
			|||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			dstPoint.X ++
 | 
								dstPoint.X ++
 | 
				
			||||||
			if dstPoint.X >= bounds.Max.X {
 | 
								if dstPoint.X >= drawBounds.Max.X {
 | 
				
			||||||
				break
 | 
									break
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
				
			|||||||
@ -9,9 +9,9 @@ import "git.tebibyte.media/sashakoshka/tomo/artist/shapes"
 | 
				
			|||||||
// Uniform is a pattern that draws a solid color.
 | 
					// Uniform is a pattern that draws a solid color.
 | 
				
			||||||
type Uniform color.RGBA
 | 
					type Uniform color.RGBA
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Draw fills the clipping rectangle with the pattern's color.
 | 
					// Draw fills the bounding rectangle with the pattern's color.
 | 
				
			||||||
func (pattern Uniform) Draw (destination canvas.Canvas, clip image.Rectangle) {
 | 
					func (pattern Uniform) Draw (destination canvas.Canvas, bounds image.Rectangle) {
 | 
				
			||||||
	shapes.FillColorRectangle(destination, color.RGBA(pattern), clip)
 | 
						shapes.FillColorRectangle(destination, color.RGBA(pattern), bounds)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Uhex creates a new Uniform pattern from an RGBA integer value.
 | 
					// Uhex creates a new Uniform pattern from an RGBA integer value.
 | 
				
			||||||
 | 
				
			|||||||
@ -13,6 +13,7 @@ import "git.tebibyte.media/sashakoshka/tomo/canvas"
 | 
				
			|||||||
func FillEllipse (
 | 
					func FillEllipse (
 | 
				
			||||||
	destination canvas.Canvas,
 | 
						destination canvas.Canvas,
 | 
				
			||||||
	source      canvas.Canvas,
 | 
						source      canvas.Canvas,
 | 
				
			||||||
 | 
						bounds      image.Rectangle,
 | 
				
			||||||
) (
 | 
					) (
 | 
				
			||||||
	updatedRegion image.Rectangle,
 | 
						updatedRegion image.Rectangle,
 | 
				
			||||||
) {
 | 
					) {
 | 
				
			||||||
@ -20,15 +21,17 @@ func FillEllipse (
 | 
				
			|||||||
	srcData, srcStride := source.Buffer()
 | 
						srcData, srcStride := source.Buffer()
 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
	offset := source.Bounds().Min.Sub(destination.Bounds().Min)
 | 
						offset := source.Bounds().Min.Sub(destination.Bounds().Min)
 | 
				
			||||||
	bounds     := source.Bounds().Sub(offset).Intersect(destination.Bounds())
 | 
						drawBounds :=
 | 
				
			||||||
	realBounds := destination.Bounds()
 | 
							source.Bounds().Sub(offset).
 | 
				
			||||||
 | 
							Intersect(destination.Bounds()).
 | 
				
			||||||
 | 
							Intersect(bounds)
 | 
				
			||||||
	if bounds.Empty() { return }
 | 
						if bounds.Empty() { return }
 | 
				
			||||||
	updatedRegion = bounds
 | 
						updatedRegion = bounds
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	point := image.Point { }
 | 
						point := image.Point { }
 | 
				
			||||||
	for point.Y = bounds.Min.Y; point.Y < bounds.Max.Y; point.Y ++ {
 | 
						for point.Y = drawBounds.Min.Y; point.Y < drawBounds.Max.Y; point.Y ++ {
 | 
				
			||||||
	for point.X = bounds.Min.X; point.X < bounds.Max.X; point.X ++ {
 | 
						for point.X = drawBounds.Min.X; point.X < drawBounds.Max.X; point.X ++ {
 | 
				
			||||||
		if inEllipse(point, realBounds) {
 | 
							if inEllipse(point, bounds) {
 | 
				
			||||||
			offsetPoint := point.Add(offset)
 | 
								offsetPoint := point.Add(offset)
 | 
				
			||||||
			dstIndex := point.X       + point.Y       * dstStride
 | 
								dstIndex := point.X       + point.Y       * dstStride
 | 
				
			||||||
			srcIndex := offsetPoint.X + offsetPoint.Y * srcStride
 | 
								srcIndex := offsetPoint.X + offsetPoint.Y * srcStride
 | 
				
			||||||
@ -41,6 +44,7 @@ func FillEllipse (
 | 
				
			|||||||
func StrokeEllipse (
 | 
					func StrokeEllipse (
 | 
				
			||||||
	destination canvas.Canvas,
 | 
						destination canvas.Canvas,
 | 
				
			||||||
	source      canvas.Canvas,
 | 
						source      canvas.Canvas,
 | 
				
			||||||
 | 
						bounds      image.Rectangle,
 | 
				
			||||||
	weight      int,
 | 
						weight      int,
 | 
				
			||||||
) {
 | 
					) {
 | 
				
			||||||
	if weight < 1 { return }
 | 
						if weight < 1 { return }
 | 
				
			||||||
@ -48,10 +52,9 @@ func StrokeEllipse (
 | 
				
			|||||||
	dstData, dstStride := destination.Buffer()
 | 
						dstData, dstStride := destination.Buffer()
 | 
				
			||||||
	srcData, srcStride := source.Buffer()
 | 
						srcData, srcStride := source.Buffer()
 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
	bounds := destination.Bounds().Inset(weight - 1)
 | 
						drawBounds := destination.Bounds().Inset(weight - 1)
 | 
				
			||||||
	offset := source.Bounds().Min.Sub(destination.Bounds().Min)
 | 
						offset := source.Bounds().Min.Sub(destination.Bounds().Min)
 | 
				
			||||||
	realBounds := destination.Bounds()
 | 
						if drawBounds.Empty() { return }
 | 
				
			||||||
	if bounds.Empty() { return }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	context := ellipsePlottingContext {
 | 
						context := ellipsePlottingContext {
 | 
				
			||||||
		plottingContext: plottingContext {
 | 
							plottingContext: plottingContext {
 | 
				
			||||||
@ -61,11 +64,11 @@ func StrokeEllipse (
 | 
				
			|||||||
			srcStride: srcStride,
 | 
								srcStride: srcStride,
 | 
				
			||||||
			weight:    weight,
 | 
								weight:    weight,
 | 
				
			||||||
			offset:    offset,
 | 
								offset:    offset,
 | 
				
			||||||
			bounds:    realBounds,
 | 
								bounds:    bounds,
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
		radii:  image.Pt(bounds.Dx() / 2, bounds.Dy() / 2),
 | 
							radii:  image.Pt(drawBounds.Dx() / 2, drawBounds.Dy() / 2),
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	context.center = bounds.Min.Add(context.radii)
 | 
						context.center = drawBounds.Min.Add(context.radii)
 | 
				
			||||||
	context.plotEllipse()
 | 
						context.plotEllipse()
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -10,20 +10,24 @@ import "git.tebibyte.media/sashakoshka/tomo/shatter"
 | 
				
			|||||||
func FillRectangle (
 | 
					func FillRectangle (
 | 
				
			||||||
	destination canvas.Canvas,
 | 
						destination canvas.Canvas,
 | 
				
			||||||
	source      canvas.Canvas,
 | 
						source      canvas.Canvas,
 | 
				
			||||||
 | 
						bounds      image.Rectangle,
 | 
				
			||||||
) (
 | 
					) (
 | 
				
			||||||
	updatedRegion image.Rectangle,
 | 
						updatedRegion image.Rectangle,
 | 
				
			||||||
) {
 | 
					) {
 | 
				
			||||||
	dstData, dstStride := destination.Buffer()
 | 
						dstData, dstStride := destination.Buffer()
 | 
				
			||||||
	srcData, srcStride := source.Buffer()
 | 
						srcData, srcStride := source.Buffer()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	offset := source.Bounds().Min.Sub(destination.Bounds().Min)
 | 
						offset     := source.Bounds().Min.Sub(destination.Bounds().Min)
 | 
				
			||||||
	bounds     := source.Bounds().Sub(offset).Intersect(destination.Bounds())
 | 
						drawBounds :=
 | 
				
			||||||
	if bounds.Empty() { return }
 | 
							source.Bounds().Sub(offset).
 | 
				
			||||||
	updatedRegion = bounds
 | 
							Intersect(destination.Bounds()).
 | 
				
			||||||
 | 
							Intersect(bounds)
 | 
				
			||||||
 | 
						if drawBounds.Empty() { return }
 | 
				
			||||||
 | 
						updatedRegion = drawBounds
 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
	point := image.Point { }
 | 
						point := image.Point { }
 | 
				
			||||||
	for point.Y = bounds.Min.Y; point.Y < bounds.Max.Y; point.Y ++ {
 | 
						for point.Y = drawBounds.Min.Y; point.Y < drawBounds.Max.Y; point.Y ++ {
 | 
				
			||||||
	for point.X = bounds.Min.X; point.X < bounds.Max.X; point.X ++ {
 | 
						for point.X = drawBounds.Min.X; point.X < drawBounds.Max.X; point.X ++ {
 | 
				
			||||||
		offsetPoint := point.Add(offset)
 | 
							offsetPoint := point.Add(offset)
 | 
				
			||||||
		dstIndex := point.X       + point.Y       * dstStride
 | 
							dstIndex := point.X       + point.Y       * dstStride
 | 
				
			||||||
		srcIndex := offsetPoint.X + offsetPoint.Y * srcStride
 | 
							srcIndex := offsetPoint.X + offsetPoint.Y * srcStride
 | 
				
			||||||
@ -36,15 +40,16 @@ func FillRectangle (
 | 
				
			|||||||
func StrokeRectangle (
 | 
					func StrokeRectangle (
 | 
				
			||||||
	destination canvas.Canvas,
 | 
						destination canvas.Canvas,
 | 
				
			||||||
	source      canvas.Canvas,
 | 
						source      canvas.Canvas,
 | 
				
			||||||
 | 
						bounds      image.Rectangle,
 | 
				
			||||||
	weight      int,
 | 
						weight      int,
 | 
				
			||||||
 | 
					) (
 | 
				
			||||||
 | 
						updatedRegion image.Rectangle,
 | 
				
			||||||
) {
 | 
					) {
 | 
				
			||||||
	bounds := destination.Bounds()
 | 
					 | 
				
			||||||
	insetBounds := bounds.Inset(weight)
 | 
						insetBounds := bounds.Inset(weight)
 | 
				
			||||||
	if insetBounds.Empty() {
 | 
						if insetBounds.Empty() {
 | 
				
			||||||
		FillRectangle(destination, source)
 | 
							return FillRectangle(destination, source, bounds)
 | 
				
			||||||
		return
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	FillRectangleShatter(destination, source, insetBounds)
 | 
						return FillRectangleShatter(destination, source, bounds, insetBounds)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// FillRectangleShatter is like FillRectangle, but it does not draw in areas
 | 
					// FillRectangleShatter is like FillRectangle, but it does not draw in areas
 | 
				
			||||||
@ -52,15 +57,19 @@ func StrokeRectangle (
 | 
				
			|||||||
func FillRectangleShatter (
 | 
					func FillRectangleShatter (
 | 
				
			||||||
	destination canvas.Canvas,
 | 
						destination canvas.Canvas,
 | 
				
			||||||
	source      canvas.Canvas,
 | 
						source      canvas.Canvas,
 | 
				
			||||||
 | 
						bounds      image.Rectangle,
 | 
				
			||||||
	rocks       ...image.Rectangle,
 | 
						rocks       ...image.Rectangle,
 | 
				
			||||||
 | 
					) (
 | 
				
			||||||
 | 
						updatedRegion image.Rectangle,
 | 
				
			||||||
) {
 | 
					) {
 | 
				
			||||||
	tiles  := shatter.Shatter(destination.Bounds(), rocks...)
 | 
						tiles := shatter.Shatter(bounds, rocks...)
 | 
				
			||||||
	offset := source.Bounds().Min.Sub(destination.Bounds().Min)
 | 
					 | 
				
			||||||
	for _, tile := range tiles {
 | 
						for _, tile := range tiles {
 | 
				
			||||||
		FillRectangle (
 | 
							FillRectangle (
 | 
				
			||||||
			canvas.Cut(destination, tile),
 | 
								canvas.Cut(destination, tile),
 | 
				
			||||||
			canvas.Cut(source, tile.Add(offset)))
 | 
								source, tile)
 | 
				
			||||||
 | 
							updatedRegion = updatedRegion.Union(tile)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
						return
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// FillColorRectangle fills a rectangle within the destination canvas with a
 | 
					// FillColorRectangle fills a rectangle within the destination canvas with a
 | 
				
			||||||
@ -92,11 +101,15 @@ func FillColorRectangleShatter (
 | 
				
			|||||||
	color       color.RGBA,
 | 
						color       color.RGBA,
 | 
				
			||||||
	bounds      image.Rectangle,
 | 
						bounds      image.Rectangle,
 | 
				
			||||||
	rocks       ...image.Rectangle,
 | 
						rocks       ...image.Rectangle,
 | 
				
			||||||
 | 
					) (
 | 
				
			||||||
 | 
						updatedRegion image.Rectangle,
 | 
				
			||||||
) {
 | 
					) {
 | 
				
			||||||
	tiles := shatter.Shatter(bounds, rocks...)
 | 
						tiles := shatter.Shatter(bounds, rocks...)
 | 
				
			||||||
	for _, tile := range tiles {
 | 
						for _, tile := range tiles {
 | 
				
			||||||
		FillColorRectangle(destination, color, tile)
 | 
							FillColorRectangle(destination, color, tile)
 | 
				
			||||||
 | 
							updatedRegion = updatedRegion.Union(tile)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
						return
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// StrokeColorRectangle is similar to FillColorRectangle, but it draws an inset
 | 
					// StrokeColorRectangle is similar to FillColorRectangle, but it draws an inset
 | 
				
			||||||
@ -106,11 +119,12 @@ func StrokeColorRectangle (
 | 
				
			|||||||
	color       color.RGBA,
 | 
						color       color.RGBA,
 | 
				
			||||||
	bounds      image.Rectangle,
 | 
						bounds      image.Rectangle,
 | 
				
			||||||
	weight      int,
 | 
						weight      int,
 | 
				
			||||||
 | 
					) (
 | 
				
			||||||
 | 
						updatedRegion image.Rectangle,
 | 
				
			||||||
) {
 | 
					) {
 | 
				
			||||||
	insetBounds := bounds.Inset(weight)
 | 
						insetBounds := bounds.Inset(weight)
 | 
				
			||||||
	if insetBounds.Empty() {
 | 
						if insetBounds.Empty() {
 | 
				
			||||||
		FillColorRectangle(destination, color, bounds)
 | 
							return FillColorRectangle(destination, color, bounds)
 | 
				
			||||||
		return
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	FillColorRectangleShatter(destination, color, bounds, insetBounds)
 | 
						return FillColorRectangleShatter(destination, color, bounds, insetBounds)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -96,9 +96,6 @@ func (window *Window) Adopt (child elements.Element) {
 | 
				
			|||||||
		window.child.OnDamage(nil)
 | 
							window.child.OnDamage(nil)
 | 
				
			||||||
		window.child.OnMinimumSizeChange(nil)
 | 
							window.child.OnMinimumSizeChange(nil)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	if previousChild, ok := window.child.(elements.Flexible); ok {
 | 
					 | 
				
			||||||
		previousChild.OnFlexibleHeightChange(nil)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	if previousChild, ok := window.child.(elements.Focusable); ok {
 | 
						if previousChild, ok := window.child.(elements.Focusable); ok {
 | 
				
			||||||
		previousChild.OnFocusRequest(nil)
 | 
							previousChild.OnFocusRequest(nil)
 | 
				
			||||||
		previousChild.OnFocusMotionRequest(nil)
 | 
							previousChild.OnFocusMotionRequest(nil)
 | 
				
			||||||
@ -115,9 +112,6 @@ func (window *Window) Adopt (child elements.Element) {
 | 
				
			|||||||
	if newChild, ok := child.(elements.Configurable); ok {
 | 
						if newChild, ok := child.(elements.Configurable); ok {
 | 
				
			||||||
		newChild.SetConfig(window.config)
 | 
							newChild.SetConfig(window.config)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	if newChild, ok := child.(elements.Flexible); ok {
 | 
					 | 
				
			||||||
		newChild.OnFlexibleHeightChange(window.resizeChildToFit)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	if newChild, ok := child.(elements.Focusable); ok {
 | 
						if newChild, ok := child.(elements.Focusable); ok {
 | 
				
			||||||
		newChild.OnFocusRequest(window.childSelectionRequestCallback)
 | 
							newChild.OnFocusRequest(window.childSelectionRequestCallback)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@ -263,26 +257,7 @@ func (window *Window) redrawChildEntirely () {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
func (window *Window) resizeChildToFit () {
 | 
					func (window *Window) resizeChildToFit () {
 | 
				
			||||||
	window.skipChildDrawCallback = true
 | 
						window.skipChildDrawCallback = true
 | 
				
			||||||
	if child, ok := window.child.(elements.Flexible); ok {
 | 
						window.child.DrawTo(window.canvas, window.canvas.Bounds())
 | 
				
			||||||
		minimumHeight := child.FlexibleHeightFor(window.metrics.width)
 | 
					 | 
				
			||||||
		minimumWidth, _ := child.MinimumSize()
 | 
					 | 
				
			||||||
		
 | 
					 | 
				
			||||||
		icccm.WmNormalHintsSet (
 | 
					 | 
				
			||||||
			window.backend.connection,
 | 
					 | 
				
			||||||
			window.xWindow.Id,
 | 
					 | 
				
			||||||
			&icccm.NormalHints {
 | 
					 | 
				
			||||||
				Flags:     icccm.SizeHintPMinSize,
 | 
					 | 
				
			||||||
				MinWidth:  uint(minimumWidth),
 | 
					 | 
				
			||||||
				MinHeight: uint(minimumHeight),
 | 
					 | 
				
			||||||
			})
 | 
					 | 
				
			||||||
				
 | 
					 | 
				
			||||||
		if window.metrics.height >= minimumHeight &&
 | 
					 | 
				
			||||||
			window.metrics.width >= minimumWidth {
 | 
					 | 
				
			||||||
			window.child.DrawTo(window.canvas)
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	} else {
 | 
					 | 
				
			||||||
		window.child.DrawTo(window.canvas)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	window.skipChildDrawCallback = false
 | 
						window.skipChildDrawCallback = false
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -4,7 +4,6 @@ import "image"
 | 
				
			|||||||
import "git.tebibyte.media/sashakoshka/tomo/input"
 | 
					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/textdraw"
 | 
					import "git.tebibyte.media/sashakoshka/tomo/textdraw"
 | 
				
			||||||
import "git.tebibyte.media/sashakoshka/tomo/elements/core"
 | 
					import "git.tebibyte.media/sashakoshka/tomo/elements/core"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -176,7 +175,7 @@ func (element *Checkbox) draw () {
 | 
				
			|||||||
	backgroundPattern.Draw(element.core, bounds)
 | 
						backgroundPattern.Draw(element.core, bounds)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	pattern := element.theme.Pattern(theme.PatternButton, state)
 | 
						pattern := element.theme.Pattern(theme.PatternButton, state)
 | 
				
			||||||
	artist.DrawBounds(element.core, pattern, boxBounds)
 | 
						pattern.Draw(element.core, boxBounds)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	textBounds := element.drawer.LayoutBounds()
 | 
						textBounds := element.drawer.LayoutBounds()
 | 
				
			||||||
	margin := element.theme.Margin(theme.PatternBackground)
 | 
						margin := element.theme.Margin(theme.PatternBackground)
 | 
				
			||||||
 | 
				
			|||||||
@ -20,12 +20,10 @@ type Container struct {
 | 
				
			|||||||
	layout    layouts.Layout
 | 
						layout    layouts.Layout
 | 
				
			||||||
	children  []layouts.LayoutEntry
 | 
						children  []layouts.LayoutEntry
 | 
				
			||||||
	warping   bool
 | 
						warping   bool
 | 
				
			||||||
	flexible  bool
 | 
					 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
	config config.Wrapped
 | 
						config config.Wrapped
 | 
				
			||||||
	theme  theme.Wrapped
 | 
						theme  theme.Wrapped
 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
	onFlexibleHeightChange func ()
 | 
					 | 
				
			||||||
	onFocusRequest func () (granted bool)
 | 
						onFocusRequest func () (granted bool)
 | 
				
			||||||
	onFocusMotionRequest func (input.KeynavDirection) (granted bool)
 | 
						onFocusMotionRequest func (input.KeynavDirection) (granted bool)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@ -70,9 +68,6 @@ func (element *Container) Adopt (child elements.Element, expand bool) {
 | 
				
			|||||||
		element.redoAll()
 | 
							element.redoAll()
 | 
				
			||||||
		element.core.DamageAll()
 | 
							element.core.DamageAll()
 | 
				
			||||||
	})
 | 
						})
 | 
				
			||||||
	if child0, ok := child.(elements.Flexible); ok {
 | 
					 | 
				
			||||||
		child0.OnFlexibleHeightChange(element.notifyFlexibleChange)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	if child0, ok := child.(elements.Focusable); ok {
 | 
						if child0, ok := child.(elements.Focusable); ok {
 | 
				
			||||||
		child0.OnFocusRequest (func () (granted bool) {
 | 
							child0.OnFocusRequest (func () (granted bool) {
 | 
				
			||||||
			return element.childFocusRequestCallback(child0)
 | 
								return element.childFocusRequestCallback(child0)
 | 
				
			||||||
@ -92,7 +87,6 @@ func (element *Container) Adopt (child elements.Element, expand bool) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	// refresh stale data
 | 
						// refresh stale data
 | 
				
			||||||
	element.updateMinimumSize()
 | 
						element.updateMinimumSize()
 | 
				
			||||||
	element.reflectChildProperties()
 | 
					 | 
				
			||||||
	if element.core.HasImage() && !element.warping {
 | 
						if element.core.HasImage() && !element.warping {
 | 
				
			||||||
		element.redoAll()
 | 
							element.redoAll()
 | 
				
			||||||
		element.core.DamageAll()
 | 
							element.core.DamageAll()
 | 
				
			||||||
@ -135,7 +129,6 @@ func (element *Container) Disown (child elements.Element) {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	element.updateMinimumSize()
 | 
						element.updateMinimumSize()
 | 
				
			||||||
	element.reflectChildProperties()
 | 
					 | 
				
			||||||
	if element.core.HasImage() && !element.warping {
 | 
						if element.core.HasImage() && !element.warping {
 | 
				
			||||||
		element.redoAll()
 | 
							element.redoAll()
 | 
				
			||||||
		element.core.DamageAll()
 | 
							element.core.DamageAll()
 | 
				
			||||||
@ -143,7 +136,7 @@ func (element *Container) Disown (child elements.Element) {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (element *Container) clearChildEventHandlers (child elements.Element) {
 | 
					func (element *Container) clearChildEventHandlers (child elements.Element) {
 | 
				
			||||||
	child.DrawTo(nil)
 | 
						child.DrawTo(nil, image.Rectangle { })
 | 
				
			||||||
	child.OnDamage(nil)
 | 
						child.OnDamage(nil)
 | 
				
			||||||
	child.OnMinimumSizeChange(nil)
 | 
						child.OnMinimumSizeChange(nil)
 | 
				
			||||||
	if child0, ok := child.(elements.Focusable); ok {
 | 
						if child0, ok := child.(elements.Focusable); ok {
 | 
				
			||||||
@ -153,9 +146,6 @@ func (element *Container) clearChildEventHandlers (child elements.Element) {
 | 
				
			|||||||
			child0.HandleUnfocus()
 | 
								child0.HandleUnfocus()
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	if child0, ok := child.(elements.Flexible); ok {
 | 
					 | 
				
			||||||
		child0.OnFlexibleHeightChange(nil)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// DisownAll removes all child elements from the container at once.
 | 
					// DisownAll removes all child elements from the container at once.
 | 
				
			||||||
@ -166,7 +156,6 @@ func (element *Container) DisownAll () {
 | 
				
			|||||||
	element.children = nil
 | 
						element.children = nil
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	element.updateMinimumSize()
 | 
						element.updateMinimumSize()
 | 
				
			||||||
	element.reflectChildProperties()
 | 
					 | 
				
			||||||
	if element.core.HasImage() && !element.warping {
 | 
						if element.core.HasImage() && !element.warping {
 | 
				
			||||||
		element.redoAll()
 | 
							element.redoAll()
 | 
				
			||||||
		element.core.DamageAll()
 | 
							element.core.DamageAll()
 | 
				
			||||||
@ -211,7 +200,7 @@ func (element *Container) redoAll () {
 | 
				
			|||||||
	// remove child canvasses so that any operations done in here will not
 | 
						// remove child canvasses so that any operations done in here will not
 | 
				
			||||||
	// cause a child to draw to a wack ass canvas.
 | 
						// cause a child to draw to a wack ass canvas.
 | 
				
			||||||
	for _, entry := range element.children {
 | 
						for _, entry := range element.children {
 | 
				
			||||||
		entry.DrawTo(nil)
 | 
							entry.DrawTo(nil, entry.Bounds)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
	// do a layout
 | 
						// do a layout
 | 
				
			||||||
@ -225,12 +214,13 @@ func (element *Container) redoAll () {
 | 
				
			|||||||
	pattern := element.theme.Pattern (
 | 
						pattern := element.theme.Pattern (
 | 
				
			||||||
		theme.PatternBackground,
 | 
							theme.PatternBackground,
 | 
				
			||||||
		theme.State { })
 | 
							theme.State { })
 | 
				
			||||||
	artist.DrawShatter (
 | 
						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 {
 | 
				
			||||||
		entry.DrawTo(canvas.Cut(element.core, entry.Bounds))
 | 
							entry.DrawTo (
 | 
				
			||||||
 | 
								canvas.Cut(element.core, entry.Bounds),
 | 
				
			||||||
 | 
								entry.Bounds)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -251,18 +241,6 @@ func (element *Container) SetConfig (new config.Config) {
 | 
				
			|||||||
	element.redoAll()
 | 
						element.redoAll()
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (element *Container) FlexibleHeightFor (width int) (height int) {
 | 
					 | 
				
			||||||
	margin  := element.theme.Margin(theme.PatternBackground)
 | 
					 | 
				
			||||||
	padding := element.theme.Padding(theme.PatternBackground)
 | 
					 | 
				
			||||||
	return element.layout.FlexibleHeightFor (
 | 
					 | 
				
			||||||
		element.children,
 | 
					 | 
				
			||||||
		margin, padding, width)
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func (element *Container) OnFlexibleHeightChange (callback func ()) {
 | 
					 | 
				
			||||||
	element.onFlexibleHeightChange = callback
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func (element *Container) OnFocusRequest (callback func () (granted bool)) {
 | 
					func (element *Container) OnFocusRequest (callback func () (granted bool)) {
 | 
				
			||||||
	element.onFocusRequest = callback
 | 
						element.onFocusRequest = callback
 | 
				
			||||||
	element.Propagator.OnFocusRequest(callback)
 | 
						element.Propagator.OnFocusRequest(callback)
 | 
				
			||||||
@ -275,35 +253,6 @@ func (element *Container) OnFocusMotionRequest (
 | 
				
			|||||||
	element.Propagator.OnFocusMotionRequest(callback)
 | 
						element.Propagator.OnFocusMotionRequest(callback)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (element *Container) forFlexible (callback func (child elements.Flexible) bool) {
 | 
					 | 
				
			||||||
	for _, entry := range element.children {
 | 
					 | 
				
			||||||
		child, flexible := entry.Element.(elements.Flexible)
 | 
					 | 
				
			||||||
		if flexible {
 | 
					 | 
				
			||||||
			if !callback(child) { break }
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func (element *Container) reflectChildProperties () {
 | 
					 | 
				
			||||||
	focusable := false
 | 
					 | 
				
			||||||
	for _, entry := range element.children {
 | 
					 | 
				
			||||||
		_, focusable := entry.Element.(elements.Focusable)
 | 
					 | 
				
			||||||
		if focusable {
 | 
					 | 
				
			||||||
			focusable = true
 | 
					 | 
				
			||||||
			break
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	if !focusable && element.Focused() {
 | 
					 | 
				
			||||||
		element.Propagator.HandleUnfocus()
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	
 | 
					 | 
				
			||||||
	element.flexible = false
 | 
					 | 
				
			||||||
	element.forFlexible (func (elements.Flexible) bool {
 | 
					 | 
				
			||||||
		element.flexible = true
 | 
					 | 
				
			||||||
		return false
 | 
					 | 
				
			||||||
	})
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func (element *Container) childFocusRequestCallback (
 | 
					func (element *Container) childFocusRequestCallback (
 | 
				
			||||||
	child elements.Focusable,
 | 
						child elements.Focusable,
 | 
				
			||||||
) (
 | 
					) (
 | 
				
			||||||
@ -326,12 +275,6 @@ func (element *Container) updateMinimumSize () {
 | 
				
			|||||||
	element.core.SetMinimumSize(width, height)
 | 
						element.core.SetMinimumSize(width, height)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (element *Container) notifyFlexibleChange () {
 | 
					 | 
				
			||||||
	if element.onFlexibleHeightChange != nil {
 | 
					 | 
				
			||||||
		element.onFlexibleHeightChange()
 | 
					 | 
				
			||||||
	}	
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func (element *Container) doLayout () {
 | 
					func (element *Container) doLayout () {
 | 
				
			||||||
	margin := element.theme.Margin(theme.PatternBackground)
 | 
						margin := element.theme.Margin(theme.PatternBackground)
 | 
				
			||||||
	padding := element.theme.Padding(theme.PatternBackground)
 | 
						padding := element.theme.Padding(theme.PatternBackground)
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										368
									
								
								elements/basic/documentContainer.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										368
									
								
								elements/basic/documentContainer.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,368 @@
 | 
				
			|||||||
 | 
					package basicElements
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import "image"
 | 
				
			||||||
 | 
					import "git.tebibyte.media/sashakoshka/tomo/input"
 | 
				
			||||||
 | 
					import "git.tebibyte.media/sashakoshka/tomo/theme"
 | 
				
			||||||
 | 
					import "git.tebibyte.media/sashakoshka/tomo/config"
 | 
				
			||||||
 | 
					import "git.tebibyte.media/sashakoshka/tomo/canvas"
 | 
				
			||||||
 | 
					import "git.tebibyte.media/sashakoshka/tomo/artist"
 | 
				
			||||||
 | 
					import "git.tebibyte.media/sashakoshka/tomo/layouts"
 | 
				
			||||||
 | 
					import "git.tebibyte.media/sashakoshka/tomo/elements"
 | 
				
			||||||
 | 
					import "git.tebibyte.media/sashakoshka/tomo/elements/core"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type DocumentContainer struct {
 | 
				
			||||||
 | 
						*core.Core
 | 
				
			||||||
 | 
						*core.Propagator
 | 
				
			||||||
 | 
						core core.CoreControl
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						children []layouts.LayoutEntry
 | 
				
			||||||
 | 
						scroll   image.Point
 | 
				
			||||||
 | 
						warping  bool
 | 
				
			||||||
 | 
						contentBounds image.Rectangle
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						config config.Wrapped
 | 
				
			||||||
 | 
						theme  theme.Wrapped
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						onFocusRequest       func () (granted bool)
 | 
				
			||||||
 | 
						onFocusMotionRequest func (input.KeynavDirection) (granted bool)
 | 
				
			||||||
 | 
						onScrollBoundsChange func ()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// NewDocumentContainer creates a new document container.
 | 
				
			||||||
 | 
					func NewDocumentContainer () (element *DocumentContainer) {
 | 
				
			||||||
 | 
						element = &DocumentContainer { }
 | 
				
			||||||
 | 
						element.theme.Case = theme.C("basic", "documentContainer")
 | 
				
			||||||
 | 
						element.Core, element.core = core.NewCore(element.redoAll)
 | 
				
			||||||
 | 
						element.Propagator = core.NewPropagator(element)
 | 
				
			||||||
 | 
						return
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Adopt adds a new child element to the container.
 | 
				
			||||||
 | 
					func (element *DocumentContainer) Adopt (child elements.Element) {
 | 
				
			||||||
 | 
						// set event handlers
 | 
				
			||||||
 | 
						if child0, ok := child.(elements.Themeable); ok {
 | 
				
			||||||
 | 
							child0.SetTheme(element.theme.Theme)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if child0, ok := child.(elements.Configurable); ok {
 | 
				
			||||||
 | 
							child0.SetConfig(element.config.Config)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						child.OnDamage (func (region canvas.Canvas) {
 | 
				
			||||||
 | 
							element.core.DamageRegion(region.Bounds())
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
						child.OnMinimumSizeChange (func () {
 | 
				
			||||||
 | 
							element.redoAll()
 | 
				
			||||||
 | 
							element.core.DamageAll()
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
						if child0, ok := child.(elements.Flexible); ok {
 | 
				
			||||||
 | 
							child0.OnFlexibleHeightChange (func () {
 | 
				
			||||||
 | 
								element.redoAll()
 | 
				
			||||||
 | 
								element.core.DamageAll()
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if child0, ok := child.(elements.Focusable); ok {
 | 
				
			||||||
 | 
							child0.OnFocusRequest (func () (granted bool) {
 | 
				
			||||||
 | 
								return element.childFocusRequestCallback(child0)
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
							child0.OnFocusMotionRequest (
 | 
				
			||||||
 | 
								func (direction input.KeynavDirection) (granted bool) {
 | 
				
			||||||
 | 
									if element.onFocusMotionRequest == nil { return }
 | 
				
			||||||
 | 
									return element.onFocusMotionRequest(direction)
 | 
				
			||||||
 | 
								})
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// add child
 | 
				
			||||||
 | 
						element.children = append (element.children, layouts.LayoutEntry {
 | 
				
			||||||
 | 
							Element: child,
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// refresh stale data
 | 
				
			||||||
 | 
						element.reflectChildProperties()
 | 
				
			||||||
 | 
						if element.core.HasImage() && !element.warping {
 | 
				
			||||||
 | 
							element.redoAll()
 | 
				
			||||||
 | 
							element.core.DamageAll()
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Warp runs the specified callback, deferring all layout and rendering updates
 | 
				
			||||||
 | 
					// until the callback has finished executing. This allows for aplications to
 | 
				
			||||||
 | 
					// perform batch gui updates without flickering and stuff.
 | 
				
			||||||
 | 
					func (element *DocumentContainer) Warp (callback func ()) {
 | 
				
			||||||
 | 
						if element.warping {
 | 
				
			||||||
 | 
							callback()
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						element.warping = true
 | 
				
			||||||
 | 
						callback()
 | 
				
			||||||
 | 
						element.warping = false
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						if element.core.HasImage() {
 | 
				
			||||||
 | 
							element.redoAll()
 | 
				
			||||||
 | 
							element.core.DamageAll()
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Disown removes the given child from the container if it is contained within
 | 
				
			||||||
 | 
					// it.
 | 
				
			||||||
 | 
					func (element *DocumentContainer) Disown (child elements.Element) {
 | 
				
			||||||
 | 
						for index, entry := range element.children {
 | 
				
			||||||
 | 
							if entry.Element == child {
 | 
				
			||||||
 | 
								element.clearChildEventHandlers(entry.Element)
 | 
				
			||||||
 | 
								element.children = append (
 | 
				
			||||||
 | 
									element.children[:index],
 | 
				
			||||||
 | 
									element.children[index + 1:]...)
 | 
				
			||||||
 | 
									break
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						element.reflectChildProperties()
 | 
				
			||||||
 | 
						if element.core.HasImage() && !element.warping {
 | 
				
			||||||
 | 
							element.redoAll()
 | 
				
			||||||
 | 
							element.core.DamageAll()
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (element *DocumentContainer) clearChildEventHandlers (child elements.Element) {
 | 
				
			||||||
 | 
						child.DrawTo(nil, image.Rectangle { })
 | 
				
			||||||
 | 
						child.OnDamage(nil)
 | 
				
			||||||
 | 
						child.OnMinimumSizeChange(nil)
 | 
				
			||||||
 | 
						if child0, ok := child.(elements.Focusable); ok {
 | 
				
			||||||
 | 
							child0.OnFocusRequest(nil)
 | 
				
			||||||
 | 
							child0.OnFocusMotionRequest(nil)
 | 
				
			||||||
 | 
							if child0.Focused() {
 | 
				
			||||||
 | 
								child0.HandleUnfocus()
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// DisownAll removes all child elements from the container at once.
 | 
				
			||||||
 | 
					func (element *DocumentContainer) DisownAll () {
 | 
				
			||||||
 | 
						for _, entry := range element.children {
 | 
				
			||||||
 | 
							element.clearChildEventHandlers(entry.Element)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						element.children = nil
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						element.reflectChildProperties()
 | 
				
			||||||
 | 
						if element.core.HasImage() && !element.warping {
 | 
				
			||||||
 | 
							element.redoAll()
 | 
				
			||||||
 | 
							element.core.DamageAll()
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Children returns a slice containing this element's children.
 | 
				
			||||||
 | 
					func (element *DocumentContainer) Children () (children []elements.Element) {
 | 
				
			||||||
 | 
						children = make([]elements.Element, len(element.children))
 | 
				
			||||||
 | 
						for index, entry := range element.children {
 | 
				
			||||||
 | 
							children[index] = entry.Element
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// CountChildren returns the amount of children contained within this element.
 | 
				
			||||||
 | 
					func (element *DocumentContainer) CountChildren () (count int) {
 | 
				
			||||||
 | 
						return len(element.children)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Child returns the child at the specified index. If the index is out of
 | 
				
			||||||
 | 
					// bounds, this method will return nil.
 | 
				
			||||||
 | 
					func (element *DocumentContainer) Child (index int) (child elements.Element) {
 | 
				
			||||||
 | 
						if index < 0 || index > len(element.children) { return }
 | 
				
			||||||
 | 
						return element.children[index].Element
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ChildAt returns the child that contains the specified x and y coordinates. If
 | 
				
			||||||
 | 
					// there are no children at the coordinates, this method will return nil.
 | 
				
			||||||
 | 
					func (element *DocumentContainer) ChildAt (point image.Point) (child elements.Element) {
 | 
				
			||||||
 | 
						for _, entry := range element.children {
 | 
				
			||||||
 | 
							if point.In(entry.Bounds) {
 | 
				
			||||||
 | 
								child = entry.Element
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (element *DocumentContainer) redoAll () {
 | 
				
			||||||
 | 
						if !element.core.HasImage() { return }
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						// do a layout
 | 
				
			||||||
 | 
						element.doLayout()
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						maxScrollHeight := element.maxScrollHeight()
 | 
				
			||||||
 | 
						if element.scroll.Y > maxScrollHeight {
 | 
				
			||||||
 | 
							element.scroll.Y = maxScrollHeight
 | 
				
			||||||
 | 
							element.doLayout()
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// draw a background
 | 
				
			||||||
 | 
						rocks := make([]image.Rectangle, len(element.children))
 | 
				
			||||||
 | 
						for index, entry := range element.children {
 | 
				
			||||||
 | 
							rocks[index] = entry.Bounds
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						pattern := element.theme.Pattern (
 | 
				
			||||||
 | 
							theme.PatternBackground,
 | 
				
			||||||
 | 
							theme.State { })
 | 
				
			||||||
 | 
						artist.DrawShatter(element.core, pattern, element.Bounds(), rocks...)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						element.partition()
 | 
				
			||||||
 | 
						if element.onScrollBoundsChange != nil {
 | 
				
			||||||
 | 
							element.onScrollBoundsChange()
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (element *DocumentContainer) partition () {
 | 
				
			||||||
 | 
						for _, entry := range element.children {
 | 
				
			||||||
 | 
							entry.DrawTo(nil, entry.Bounds)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// cut our canvas up and give peices to child elements
 | 
				
			||||||
 | 
						for _, entry := range element.children {
 | 
				
			||||||
 | 
							if entry.Bounds.Overlaps(element.Bounds()) {
 | 
				
			||||||
 | 
								entry.DrawTo (	
 | 
				
			||||||
 | 
									canvas.Cut(element.core, entry.Bounds),
 | 
				
			||||||
 | 
									entry.Bounds)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// SetTheme sets the element's theme.
 | 
				
			||||||
 | 
					func (element *DocumentContainer) SetTheme (new theme.Theme) {
 | 
				
			||||||
 | 
						if new == element.theme.Theme { return }
 | 
				
			||||||
 | 
						element.theme.Theme = new
 | 
				
			||||||
 | 
						element.Propagator.SetTheme(new)
 | 
				
			||||||
 | 
						element.redoAll()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// SetConfig sets the element's configuration.
 | 
				
			||||||
 | 
					func (element *DocumentContainer) SetConfig (new config.Config) {
 | 
				
			||||||
 | 
						if new == element.config.Config { return }
 | 
				
			||||||
 | 
						element.Propagator.SetConfig(new)
 | 
				
			||||||
 | 
						element.redoAll()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (element *DocumentContainer) OnFocusRequest (callback func () (granted bool)) {
 | 
				
			||||||
 | 
						element.onFocusRequest = callback
 | 
				
			||||||
 | 
						element.Propagator.OnFocusRequest(callback)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (element *DocumentContainer) OnFocusMotionRequest (
 | 
				
			||||||
 | 
						callback func (direction input.KeynavDirection) (granted bool),
 | 
				
			||||||
 | 
					) {
 | 
				
			||||||
 | 
						element.onFocusMotionRequest = callback
 | 
				
			||||||
 | 
						element.Propagator.OnFocusMotionRequest(callback)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ScrollContentBounds returns the full content size of the element.
 | 
				
			||||||
 | 
					func (element *DocumentContainer) ScrollContentBounds () image.Rectangle {
 | 
				
			||||||
 | 
						return element.contentBounds
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ScrollViewportBounds returns the size and position of the element's
 | 
				
			||||||
 | 
					// viewport relative to ScrollBounds.
 | 
				
			||||||
 | 
					func (element *DocumentContainer) ScrollViewportBounds () image.Rectangle {
 | 
				
			||||||
 | 
						padding := element.theme.Padding(theme.PatternBackground)
 | 
				
			||||||
 | 
						bounds  := padding.Apply(element.Bounds())
 | 
				
			||||||
 | 
						bounds   = bounds.Sub(bounds.Min).Add(element.scroll)
 | 
				
			||||||
 | 
						return bounds
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ScrollTo scrolls the viewport to the specified point relative to
 | 
				
			||||||
 | 
					// ScrollBounds.
 | 
				
			||||||
 | 
					func (element *DocumentContainer) ScrollTo (position image.Point) {
 | 
				
			||||||
 | 
						if position.Y < 0 {
 | 
				
			||||||
 | 
							position.Y = 0
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						maxScrollHeight := element.maxScrollHeight()
 | 
				
			||||||
 | 
						if position.Y > maxScrollHeight {
 | 
				
			||||||
 | 
							position.Y = maxScrollHeight
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						element.scroll = position
 | 
				
			||||||
 | 
						if element.core.HasImage() && !element.warping {
 | 
				
			||||||
 | 
							element.redoAll()
 | 
				
			||||||
 | 
							element.core.DamageAll()
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (element *DocumentContainer) maxScrollHeight () (height int) {
 | 
				
			||||||
 | 
						padding := element.theme.Padding(theme.PatternSunken)
 | 
				
			||||||
 | 
						viewportHeight := element.Bounds().Dy() - padding.Vertical()
 | 
				
			||||||
 | 
						height = element.contentBounds.Dy() - viewportHeight
 | 
				
			||||||
 | 
						if height < 0 { height = 0 }
 | 
				
			||||||
 | 
						return
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ScrollAxes returns the supported axes for scrolling.
 | 
				
			||||||
 | 
					func (element *DocumentContainer) ScrollAxes () (horizontal, vertical bool) {
 | 
				
			||||||
 | 
						return false, true
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// OnScrollBoundsChange sets a function to be called when the element's
 | 
				
			||||||
 | 
					// ScrollContentBounds, ScrollViewportBounds, or ScrollAxes are changed.
 | 
				
			||||||
 | 
					func (element *DocumentContainer) OnScrollBoundsChange(callback func()) {
 | 
				
			||||||
 | 
						element.onScrollBoundsChange = callback
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (element *DocumentContainer) reflectChildProperties () {
 | 
				
			||||||
 | 
						focusable := false
 | 
				
			||||||
 | 
						for _, entry := range element.children {
 | 
				
			||||||
 | 
							_, focusable := entry.Element.(elements.Focusable)
 | 
				
			||||||
 | 
							if focusable {
 | 
				
			||||||
 | 
								focusable = true
 | 
				
			||||||
 | 
								break
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if !focusable && element.Focused() {
 | 
				
			||||||
 | 
							element.Propagator.HandleUnfocus()
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (element *DocumentContainer) childFocusRequestCallback (
 | 
				
			||||||
 | 
						child elements.Focusable,
 | 
				
			||||||
 | 
					) (
 | 
				
			||||||
 | 
						granted bool,
 | 
				
			||||||
 | 
					) {
 | 
				
			||||||
 | 
						if element.onFocusRequest != nil && element.onFocusRequest() {
 | 
				
			||||||
 | 
							element.Propagator.HandleUnfocus()
 | 
				
			||||||
 | 
							element.Propagator.HandleFocus(input.KeynavDirectionNeutral)
 | 
				
			||||||
 | 
							return true
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							return false
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (element *DocumentContainer) doLayout () {
 | 
				
			||||||
 | 
						margin := element.theme.Margin(theme.PatternBackground)
 | 
				
			||||||
 | 
						padding := element.theme.Padding(theme.PatternBackground)
 | 
				
			||||||
 | 
						bounds := padding.Apply(element.Bounds())
 | 
				
			||||||
 | 
						element.contentBounds = image.Rectangle { }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						minimumWidth := 0
 | 
				
			||||||
 | 
						dot := bounds.Min.Sub(element.scroll)
 | 
				
			||||||
 | 
						for index, entry := range element.children {
 | 
				
			||||||
 | 
							if index > 0 {
 | 
				
			||||||
 | 
								dot.Y += margin.Y
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
							width, height := entry.MinimumSize()
 | 
				
			||||||
 | 
							if width > minimumWidth {
 | 
				
			||||||
 | 
								minimumWidth = width
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if width < bounds.Dx() {
 | 
				
			||||||
 | 
								width = bounds.Dx()
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if typedChild, ok := entry.Element.(elements.Flexible); ok {
 | 
				
			||||||
 | 
								height = typedChild.FlexibleHeightFor(width)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							
 | 
				
			||||||
 | 
							entry.Bounds.Min = dot
 | 
				
			||||||
 | 
							entry.Bounds.Max = image.Pt(dot.X + width, dot.Y + height)
 | 
				
			||||||
 | 
							element.children[index] = entry
 | 
				
			||||||
 | 
							element.contentBounds = element.contentBounds.Union(entry.Bounds)
 | 
				
			||||||
 | 
							dot.Y += height
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						element.contentBounds =
 | 
				
			||||||
 | 
							element.contentBounds.Sub(element.contentBounds.Min)	
 | 
				
			||||||
 | 
						element.core.SetMinimumSize (
 | 
				
			||||||
 | 
							minimumWidth + padding.Horizontal(),
 | 
				
			||||||
 | 
							padding.Vertical())
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -1,5 +1,6 @@
 | 
				
			|||||||
package basicElements
 | 
					package basicElements
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import "golang.org/x/image/math/fixed"
 | 
				
			||||||
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/textdraw"
 | 
					import "git.tebibyte.media/sashakoshka/tomo/textdraw"
 | 
				
			||||||
@ -14,6 +15,9 @@ type Label struct {
 | 
				
			|||||||
	text   string
 | 
						text   string
 | 
				
			||||||
	drawer textdraw.Drawer
 | 
						drawer textdraw.Drawer
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						forcedColumns int
 | 
				
			||||||
 | 
						forcedRows    int
 | 
				
			||||||
 | 
						
 | 
				
			||||||
	config config.Wrapped
 | 
						config config.Wrapped
 | 
				
			||||||
	theme  theme.Wrapped
 | 
						theme  theme.Wrapped
 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
@ -56,6 +60,17 @@ func (element *Label) handleResize () {
 | 
				
			|||||||
	return
 | 
						return
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// EmCollapse forces a minimum width and height upon the label. The width is
 | 
				
			||||||
 | 
					// measured in emspaces, and the height is measured in lines. If a zero value is
 | 
				
			||||||
 | 
					// given for a dimension, its minimum will be determined by the label's content.
 | 
				
			||||||
 | 
					// If the label's content is greater than these dimensions, it will be truncated
 | 
				
			||||||
 | 
					// to fit.
 | 
				
			||||||
 | 
					func (element *Label) EmCollapse (columns int, rows int) {
 | 
				
			||||||
 | 
						element.forcedColumns = columns
 | 
				
			||||||
 | 
						element.forcedRows    = rows
 | 
				
			||||||
 | 
						element.updateMinimumSize()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// FlexibleHeightFor returns the reccomended height for this element based on
 | 
					// FlexibleHeightFor returns the reccomended height for this element based on
 | 
				
			||||||
// the given width in order to allow the text to wrap properly.
 | 
					// the given width in order to allow the text to wrap properly.
 | 
				
			||||||
func (element *Label) FlexibleHeightFor (width int) (height int) {
 | 
					func (element *Label) FlexibleHeightFor (width int) (height int) {
 | 
				
			||||||
@ -134,20 +149,35 @@ func (element *Label) SetConfig (new config.Config) {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (element *Label) updateMinimumSize () {
 | 
					func (element *Label) updateMinimumSize () {
 | 
				
			||||||
 | 
						var width, height int
 | 
				
			||||||
 | 
						
 | 
				
			||||||
	if element.wrap {
 | 
						if element.wrap {
 | 
				
			||||||
		em := element.drawer.Em().Round()
 | 
							em := element.drawer.Em().Round()
 | 
				
			||||||
		if em < 1 {
 | 
							if em < 1 {
 | 
				
			||||||
			em = element.theme.Padding(theme.PatternBackground)[0]
 | 
								em = element.theme.Padding(theme.PatternBackground)[0]
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		element.core.SetMinimumSize (
 | 
							width, height = em, element.drawer.LineHeight().Round()
 | 
				
			||||||
			em, element.drawer.LineHeight().Round())
 | 
					 | 
				
			||||||
		if element.onFlexibleHeightChange != nil {
 | 
							if element.onFlexibleHeightChange != nil {
 | 
				
			||||||
			element.onFlexibleHeightChange()
 | 
								element.onFlexibleHeightChange()
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	} else {
 | 
						} else {
 | 
				
			||||||
		bounds := element.drawer.LayoutBounds()
 | 
							bounds := element.drawer.LayoutBounds()
 | 
				
			||||||
		element.core.SetMinimumSize(bounds.Dx(), bounds.Dy())
 | 
							width, height = bounds.Dx(), bounds.Dy()
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if element.forcedColumns > 0 {
 | 
				
			||||||
 | 
							width = int (
 | 
				
			||||||
 | 
								element.drawer.Em().
 | 
				
			||||||
 | 
								Mul(fixed.I(element.forcedColumns)))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if element.forcedRows > 0 {
 | 
				
			||||||
 | 
							height = int (
 | 
				
			||||||
 | 
								element.drawer.LineHeight().
 | 
				
			||||||
 | 
								Mul(fixed.I(element.forcedRows)))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						element.core.SetMinimumSize(width, height)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (element *Label) draw () {
 | 
					func (element *Label) draw () {
 | 
				
			||||||
@ -160,7 +190,7 @@ func (element *Label) draw () {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	textBounds := element.drawer.LayoutBounds()
 | 
						textBounds := element.drawer.LayoutBounds()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	foreground :=  element.theme.Color (
 | 
						foreground := element.theme.Color (
 | 
				
			||||||
		theme.ColorForeground,
 | 
							theme.ColorForeground,
 | 
				
			||||||
		theme.State { })
 | 
							theme.State { })
 | 
				
			||||||
	element.drawer.Draw(element.core, foreground, bounds.Min.Sub(textBounds.Min))
 | 
						element.drawer.Draw(element.core, foreground, bounds.Min.Sub(textBounds.Min))
 | 
				
			||||||
 | 
				
			|||||||
@ -461,5 +461,5 @@ func (element *List) draw () {
 | 
				
			|||||||
	).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.DrawShatter (
 | 
						artist.DrawShatter (
 | 
				
			||||||
		element.core, pattern, covered)
 | 
							element.core, pattern, bounds, covered)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -70,7 +70,7 @@ func (entry *ListEntry) Draw (
 | 
				
			|||||||
	pattern := entry.theme.Pattern(theme.PatternRaised, state)
 | 
						pattern := entry.theme.Pattern(theme.PatternRaised, state)
 | 
				
			||||||
	padding := entry.theme.Padding(theme.PatternRaised)
 | 
						padding := entry.theme.Padding(theme.PatternRaised)
 | 
				
			||||||
	bounds  := entry.Bounds().Add(offset)
 | 
						bounds  := entry.Bounds().Add(offset)
 | 
				
			||||||
	artist.DrawBounds(destination, pattern, bounds)
 | 
						pattern.Draw(destination, bounds)
 | 
				
			||||||
		
 | 
							
 | 
				
			||||||
	foreground := entry.theme.Color (theme.ColorForeground, state)
 | 
						foreground := entry.theme.Color (theme.ColorForeground, state)
 | 
				
			||||||
	return entry.drawer.Draw (
 | 
						return entry.drawer.Draw (
 | 
				
			||||||
 | 
				
			|||||||
@ -3,7 +3,6 @@ package basicElements
 | 
				
			|||||||
import "image"
 | 
					import "image"
 | 
				
			||||||
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"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// ProgressBar displays a visual indication of how far along a task is.
 | 
					// ProgressBar displays a visual indication of how far along a task is.
 | 
				
			||||||
@ -78,5 +77,5 @@ func (element *ProgressBar) draw () {
 | 
				
			|||||||
		bounds.Min.X + int(float64(bounds.Dx()) * element.progress),
 | 
							bounds.Min.X + int(float64(bounds.Dx()) * element.progress),
 | 
				
			||||||
		bounds.Max.Y)
 | 
							bounds.Max.Y)
 | 
				
			||||||
	mercury := element.theme.Pattern(theme.PatternMercury, theme.State { })
 | 
						mercury := element.theme.Pattern(theme.PatternMercury, theme.State { })
 | 
				
			||||||
	artist.DrawBounds(element.core, mercury, meterBounds)
 | 
						mercury.Draw(element.core, meterBounds)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -4,7 +4,6 @@ import "image"
 | 
				
			|||||||
import "git.tebibyte.media/sashakoshka/tomo/input"
 | 
					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/elements/core"
 | 
					import "git.tebibyte.media/sashakoshka/tomo/elements/core"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// ScrollBar is an element similar to Slider, but it has special behavior that
 | 
					// ScrollBar is an element similar to Slider, but it has special behavior that
 | 
				
			||||||
@ -315,12 +314,10 @@ func (element *ScrollBar) draw () {
 | 
				
			|||||||
		Disabled: !element.Enabled(),
 | 
							Disabled: !element.Enabled(),
 | 
				
			||||||
		Pressed:  element.dragging,
 | 
							Pressed:  element.dragging,
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	artist.DrawBounds (
 | 
						element.theme.Pattern(theme.PatternGutter, state).Draw (
 | 
				
			||||||
		element.core,
 | 
							element.core,
 | 
				
			||||||
		element.theme.Pattern(theme.PatternGutter, state),
 | 
					 | 
				
			||||||
		bounds)
 | 
							bounds)
 | 
				
			||||||
	artist.DrawBounds (
 | 
						element.theme.Pattern(theme.PatternHandle, state).Draw (
 | 
				
			||||||
		element.core,
 | 
							element.core,
 | 
				
			||||||
		element.theme.Pattern(theme.PatternHandle, state),
 | 
					 | 
				
			||||||
		element.bar)
 | 
							element.bar)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -120,7 +120,7 @@ func (element *ScrollContainer) setChildEventHandlers (child elements.Element) {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (element *ScrollContainer) clearChildEventHandlers (child elements.Scrollable) {
 | 
					func (element *ScrollContainer) clearChildEventHandlers (child elements.Scrollable) {
 | 
				
			||||||
	child.DrawTo(nil)
 | 
						child.DrawTo(nil, image.Rectangle { })
 | 
				
			||||||
	child.OnDamage(nil)
 | 
						child.OnDamage(nil)
 | 
				
			||||||
	child.OnMinimumSizeChange(nil)
 | 
						child.OnMinimumSizeChange(nil)
 | 
				
			||||||
	child.OnScrollBoundsChange(nil)
 | 
						child.OnScrollBoundsChange(nil)
 | 
				
			||||||
@ -131,9 +131,6 @@ func (element *ScrollContainer) clearChildEventHandlers (child elements.Scrollab
 | 
				
			|||||||
			child0.HandleUnfocus()
 | 
								child0.HandleUnfocus()
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	if child0, ok := child.(elements.Flexible); ok {
 | 
					 | 
				
			||||||
		child0.OnFlexibleHeightChange(nil)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// SetTheme sets the element's theme.
 | 
					// SetTheme sets the element's theme.
 | 
				
			||||||
@ -201,19 +198,26 @@ func (element *ScrollContainer) Child (index int) (child elements.Element) {
 | 
				
			|||||||
func (element *ScrollContainer) redoAll () {
 | 
					func (element *ScrollContainer) redoAll () {
 | 
				
			||||||
	if !element.core.HasImage() { return }
 | 
						if !element.core.HasImage() { return }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if element.child      != nil { element.child.DrawTo(nil)      }
 | 
						zr := image.Rectangle { }
 | 
				
			||||||
	if element.horizontal != nil { element.horizontal.DrawTo(nil) }
 | 
						if element.child      != nil { element.child.DrawTo(nil, zr)      }
 | 
				
			||||||
	if element.vertical   != nil { element.vertical.DrawTo(nil)   }
 | 
						if element.horizontal != nil { element.horizontal.DrawTo(nil, zr) }
 | 
				
			||||||
 | 
						if element.vertical   != nil { element.vertical.DrawTo(nil, zr)   }
 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
	childBounds, horizontalBounds, verticalBounds := element.layout()
 | 
						childBounds, horizontalBounds, verticalBounds := element.layout()
 | 
				
			||||||
	if element.child != nil {
 | 
						if element.child != nil {
 | 
				
			||||||
		element.child.DrawTo(canvas.Cut(element.core, childBounds))
 | 
							element.child.DrawTo (
 | 
				
			||||||
 | 
								canvas.Cut(element.core, childBounds),
 | 
				
			||||||
 | 
								childBounds)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	if element.horizontal != nil {
 | 
						if element.horizontal != nil {
 | 
				
			||||||
		element.horizontal.DrawTo(canvas.Cut(element.core, horizontalBounds))
 | 
							element.horizontal.DrawTo (
 | 
				
			||||||
 | 
								canvas.Cut(element.core, horizontalBounds),
 | 
				
			||||||
 | 
								horizontalBounds)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	if element.vertical != nil {
 | 
						if element.vertical != nil {
 | 
				
			||||||
		element.vertical.DrawTo(canvas.Cut(element.core, verticalBounds))
 | 
							element.vertical.DrawTo (
 | 
				
			||||||
 | 
								canvas.Cut(element.core, verticalBounds),
 | 
				
			||||||
 | 
								verticalBounds)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	element.draw()
 | 
						element.draw()
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@ -270,7 +274,6 @@ func (element *ScrollContainer) layout () (
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (element *ScrollContainer) draw () {
 | 
					func (element *ScrollContainer) draw () {
 | 
				
			||||||
	// XOR
 | 
					 | 
				
			||||||
	if element.horizontal != nil && element.vertical != nil {
 | 
						if element.horizontal != nil && element.vertical != nil {
 | 
				
			||||||
		bounds := element.Bounds()
 | 
							bounds := element.Bounds()
 | 
				
			||||||
		bounds.Min = image.Pt (
 | 
							bounds.Min = image.Pt (
 | 
				
			||||||
 | 
				
			|||||||
@ -4,7 +4,6 @@ import "image"
 | 
				
			|||||||
import "git.tebibyte.media/sashakoshka/tomo/input"
 | 
					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/elements/core"
 | 
					import "git.tebibyte.media/sashakoshka/tomo/elements/core"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Slider is a slider control with a floating point value between zero and one.
 | 
					// Slider is a slider control with a floating point value between zero and one.
 | 
				
			||||||
@ -229,12 +228,10 @@ func (element *Slider) draw () {
 | 
				
			|||||||
		Disabled: !element.Enabled(),
 | 
							Disabled: !element.Enabled(),
 | 
				
			||||||
		Pressed:  element.dragging,
 | 
							Pressed:  element.dragging,
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	artist.DrawBounds (
 | 
						element.theme.Pattern(theme.PatternGutter, state).Draw (
 | 
				
			||||||
		element.core,
 | 
							element.core,
 | 
				
			||||||
		element.theme.Pattern(theme.PatternGutter, state),
 | 
					 | 
				
			||||||
		bounds)
 | 
							bounds)
 | 
				
			||||||
	artist.DrawBounds (
 | 
						element.theme.Pattern(theme.PatternHandle, state).Draw (
 | 
				
			||||||
		element.core,
 | 
							element.core,
 | 
				
			||||||
		element.theme.Pattern(theme.PatternHandle, state),
 | 
					 | 
				
			||||||
		element.bar)
 | 
							element.bar)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -4,7 +4,6 @@ import "image"
 | 
				
			|||||||
import "git.tebibyte.media/sashakoshka/tomo/input"
 | 
					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/textdraw"
 | 
					import "git.tebibyte.media/sashakoshka/tomo/textdraw"
 | 
				
			||||||
import "git.tebibyte.media/sashakoshka/tomo/elements/core"
 | 
					import "git.tebibyte.media/sashakoshka/tomo/elements/core"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -185,11 +184,11 @@ func (element *Switch) draw () {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	gutterPattern := element.theme.Pattern (
 | 
						gutterPattern := element.theme.Pattern (
 | 
				
			||||||
		theme.PatternGutter, state)
 | 
							theme.PatternGutter, state)
 | 
				
			||||||
	artist.DrawBounds(element.core, gutterPattern, gutterBounds)
 | 
						gutterPattern.Draw(element.core, gutterBounds)
 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
	handlePattern := element.theme.Pattern (
 | 
						handlePattern := element.theme.Pattern (
 | 
				
			||||||
		theme.PatternHandle, state)
 | 
							theme.PatternHandle, state)
 | 
				
			||||||
	artist.DrawBounds(element.core, handlePattern, handleBounds)
 | 
						handlePattern.Draw(element.core, handleBounds)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	textBounds := element.drawer.LayoutBounds()
 | 
						textBounds := element.drawer.LayoutBounds()
 | 
				
			||||||
	offset := bounds.Min.Add(image.Point {
 | 
						offset := bounds.Min.Add(image.Point {
 | 
				
			||||||
 | 
				
			|||||||
@ -81,7 +81,6 @@ func (element *TextBox) HandleMouseDown (x, y int, button input.Button) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
func (element *TextBox) HandleMouseMove (x, y int) {
 | 
					func (element *TextBox) HandleMouseMove (x, y int) {
 | 
				
			||||||
	if !element.Enabled() { return }
 | 
						if !element.Enabled() { return }
 | 
				
			||||||
	if !element.Focused() { element.Focus() }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if element.dragging {
 | 
						if element.dragging {
 | 
				
			||||||
		runeIndex := element.atPosition(image.Pt(x, y))
 | 
							runeIndex := element.atPosition(image.Pt(x, y))
 | 
				
			||||||
 | 
				
			|||||||
@ -8,6 +8,7 @@ import "git.tebibyte.media/sashakoshka/tomo/canvas"
 | 
				
			|||||||
// widgets. It is meant to be embedded directly into a struct.
 | 
					// widgets. It is meant to be embedded directly into a struct.
 | 
				
			||||||
type Core struct {
 | 
					type Core struct {
 | 
				
			||||||
	canvas canvas.Canvas
 | 
						canvas canvas.Canvas
 | 
				
			||||||
 | 
						bounds image.Rectangle
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	metrics struct {
 | 
						metrics struct {
 | 
				
			||||||
		minimumWidth  int
 | 
							minimumWidth  int
 | 
				
			||||||
@ -37,7 +38,7 @@ func NewCore (
 | 
				
			|||||||
// overridden.
 | 
					// overridden.
 | 
				
			||||||
func (core *Core) Bounds () (bounds image.Rectangle) {
 | 
					func (core *Core) Bounds () (bounds image.Rectangle) {
 | 
				
			||||||
	if core.canvas == nil { return }
 | 
						if core.canvas == nil { return }
 | 
				
			||||||
	return core.canvas.Bounds()
 | 
						return core.bounds
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// MinimumSize fulfils the tomo.Element interface. This should not need to be
 | 
					// MinimumSize fulfils the tomo.Element interface. This should not need to be
 | 
				
			||||||
@ -48,8 +49,9 @@ func (core *Core) MinimumSize () (width, height int) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
// DrawTo fulfills the tomo.Element interface. This should not need to be
 | 
					// DrawTo fulfills the tomo.Element interface. This should not need to be
 | 
				
			||||||
// overridden.
 | 
					// overridden.
 | 
				
			||||||
func (core *Core) DrawTo (canvas canvas.Canvas) {
 | 
					func (core *Core) DrawTo (canvas canvas.Canvas, bounds image.Rectangle) {
 | 
				
			||||||
	core.canvas = canvas
 | 
						core.canvas = canvas
 | 
				
			||||||
 | 
						core.bounds = bounds
 | 
				
			||||||
	if core.drawSizeChange != nil && core.canvas != nil {
 | 
						if core.drawSizeChange != nil && core.canvas != nil {
 | 
				
			||||||
		core.drawSizeChange()
 | 
							core.drawSizeChange()
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
				
			|||||||
@ -326,16 +326,6 @@ func (propagator *Propagator) forFocusable (callback func (child elements.Focusa
 | 
				
			|||||||
	})
 | 
						})
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (propagator *Propagator) forFlexible (callback func (child elements.Flexible) bool) {
 | 
					 | 
				
			||||||
	propagator.forChildren (func (child elements.Element) bool {
 | 
					 | 
				
			||||||
		typedChild, flexible := child.(elements.Flexible)
 | 
					 | 
				
			||||||
		if flexible {
 | 
					 | 
				
			||||||
			if !callback(typedChild) { return false }
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		return true
 | 
					 | 
				
			||||||
	})
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func (propagator *Propagator) firstFocused () int {
 | 
					func (propagator *Propagator) firstFocused () int {
 | 
				
			||||||
	for index := 0; index < propagator.parent.CountChildren(); index ++ {
 | 
						for index := 0; index < propagator.parent.CountChildren(); index ++ {
 | 
				
			||||||
		child, focusable := propagator.parent.Child(index).(elements.Focusable)
 | 
							child, focusable := propagator.parent.Child(index).(elements.Focusable)
 | 
				
			||||||
 | 
				
			|||||||
@ -1,5 +1,6 @@
 | 
				
			|||||||
package core
 | 
					package core
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// import "runtime/debug"
 | 
				
			||||||
import "git.tebibyte.media/sashakoshka/tomo/input"
 | 
					import "git.tebibyte.media/sashakoshka/tomo/input"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// FocusableCore is a struct that can be embedded into objects to make them
 | 
					// FocusableCore is a struct that can be embedded into objects to make them
 | 
				
			||||||
@ -71,6 +72,7 @@ func (core *FocusableCore) HandleFocus (
 | 
				
			|||||||
// HandleUnfocus causes this element to mark itself as unfocused.
 | 
					// HandleUnfocus causes this element to mark itself as unfocused.
 | 
				
			||||||
func (core *FocusableCore) HandleUnfocus () {
 | 
					func (core *FocusableCore) HandleUnfocus () {
 | 
				
			||||||
	core.focused = false
 | 
						core.focused = false
 | 
				
			||||||
 | 
						// debug.PrintStack()
 | 
				
			||||||
	if core.drawFocusChange != nil { core.drawFocusChange() }
 | 
						if core.drawFocusChange != nil { core.drawFocusChange() }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -9,13 +9,14 @@ import "git.tebibyte.media/sashakoshka/tomo/config"
 | 
				
			|||||||
// Element represents a basic on-screen object.
 | 
					// Element represents a basic on-screen object.
 | 
				
			||||||
type Element interface {
 | 
					type Element interface {
 | 
				
			||||||
	// Bounds reports the element's bounding box. This must reflect the
 | 
						// Bounds reports the element's bounding box. This must reflect the
 | 
				
			||||||
	// bounding box of the last canvas given to the element by DrawTo.
 | 
						// bounding last given to the element by DrawTo.
 | 
				
			||||||
	Bounds () (bounds image.Rectangle)
 | 
						Bounds () (bounds image.Rectangle)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// DrawTo sets this element's canvas. This should only be called by the
 | 
						// DrawTo gives the element a canvas to draw on, along with a bounding
 | 
				
			||||||
	// parent element. This is typically a region of the parent element's
 | 
						// box to be used for laying out the element. This should only be called
 | 
				
			||||||
	// canvas.
 | 
						// by the parent element. This is typically a region of the parent
 | 
				
			||||||
	DrawTo (canvas canvas.Canvas)
 | 
						// element's canvas.
 | 
				
			||||||
 | 
						DrawTo (canvas canvas.Canvas, bounds image.Rectangle)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// OnDamage sets a function to be called when an area of the element is
 | 
						// OnDamage sets a function to be called when an area of the element is
 | 
				
			||||||
	// drawn on and should be pushed to the screen.
 | 
						// drawn on and should be pushed to the screen.
 | 
				
			||||||
 | 
				
			|||||||
@ -305,7 +305,7 @@ func (element *Piano) draw () {
 | 
				
			|||||||
	
 | 
						
 | 
				
			||||||
	pattern := element.theme.Pattern(theme.PatternPinboard, state)
 | 
						pattern := element.theme.Pattern(theme.PatternPinboard, state)
 | 
				
			||||||
	artist.DrawShatter (
 | 
						artist.DrawShatter (
 | 
				
			||||||
		element.core, pattern, element.contentBounds)
 | 
							element.core, pattern, element.Bounds(), element.contentBounds)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (element *Piano) drawFlat (
 | 
					func (element *Piano) drawFlat (
 | 
				
			||||||
@ -316,7 +316,7 @@ func (element *Piano) drawFlat (
 | 
				
			|||||||
	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.DrawBounds(element.core, pattern, bounds)
 | 
						pattern.Draw(element.core, bounds)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (element *Piano) drawSharp (
 | 
					func (element *Piano) drawSharp (
 | 
				
			||||||
@ -327,5 +327,5 @@ func (element *Piano) drawSharp (
 | 
				
			|||||||
	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.DrawBounds(element.core, pattern, bounds)
 | 
						pattern.Draw(element.core, bounds)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -79,34 +79,32 @@ func (element *Artist) draw () {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
	tiles := shatter.Shatter(c41.Bounds(), rocks...)
 | 
						tiles := shatter.Shatter(c41.Bounds(), rocks...)
 | 
				
			||||||
	for index, tile := range tiles {
 | 
						for index, tile := range tiles {
 | 
				
			||||||
		artist.DrawBounds (
 | 
							[]artist.Pattern {
 | 
				
			||||||
			element.core,
 | 
								patterns.Uhex(0xFF0000FF),
 | 
				
			||||||
			[]artist.Pattern {
 | 
								patterns.Uhex(0x00FF00FF),
 | 
				
			||||||
				patterns.Uhex(0xFF0000FF),
 | 
								patterns.Uhex(0xFF00FFFF),
 | 
				
			||||||
				patterns.Uhex(0x00FF00FF),
 | 
								patterns.Uhex(0xFFFF00FF),
 | 
				
			||||||
				patterns.Uhex(0xFF00FFFF),
 | 
								patterns.Uhex(0x00FFFFFF),
 | 
				
			||||||
				patterns.Uhex(0xFFFF00FF),
 | 
							} [index % 5].Draw(element.core, tile)
 | 
				
			||||||
				patterns.Uhex(0x00FFFFFF),
 | 
					 | 
				
			||||||
			} [index % 5], tile)
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// 0, 2
 | 
						// 0, 2
 | 
				
			||||||
	c02 := element.cellAt(0, 2)
 | 
						c02 := element.cellAt(0, 2)
 | 
				
			||||||
	shapes.StrokeColorRectangle(c02, artist.Hex(0x888888FF), c02.Bounds(), 1)
 | 
						shapes.StrokeColorRectangle(c02, artist.Hex(0x888888FF), c02.Bounds(), 1)
 | 
				
			||||||
	shapes.FillEllipse(c02, c41)
 | 
						shapes.FillEllipse(c02, c41, c02.Bounds())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// 1, 2
 | 
						// 1, 2
 | 
				
			||||||
	c12 := element.cellAt(1, 2)
 | 
						c12 := element.cellAt(1, 2)
 | 
				
			||||||
	shapes.StrokeColorRectangle(c12, artist.Hex(0x888888FF), c12.Bounds(), 1)
 | 
						shapes.StrokeColorRectangle(c12, artist.Hex(0x888888FF), c12.Bounds(), 1)
 | 
				
			||||||
	shapes.StrokeEllipse(c12, c41, 5)
 | 
						shapes.StrokeEllipse(c12, c41, c12.Bounds(), 5)
 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
	// 2, 2
 | 
						// 2, 2
 | 
				
			||||||
	c22 := element.cellAt(2, 2)
 | 
						c22 := element.cellAt(2, 2)
 | 
				
			||||||
	shapes.FillRectangle(c22, c41)
 | 
						shapes.FillRectangle(c22, c41, c22.Bounds())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// 3, 2
 | 
						// 3, 2
 | 
				
			||||||
	c32 := element.cellAt(3, 2)
 | 
						c32 := element.cellAt(3, 2)
 | 
				
			||||||
	shapes.StrokeRectangle(c32, c41, 5)
 | 
						shapes.StrokeRectangle(c32, c41, c32.Bounds(), 5)
 | 
				
			||||||
	
 | 
						
 | 
				
			||||||
	// 4, 2
 | 
						// 4, 2
 | 
				
			||||||
	c42 := element.cellAt(4, 2)
 | 
						c42 := element.cellAt(4, 2)
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										64
									
								
								examples/documentContainer/main.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								examples/documentContainer/main.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,64 @@
 | 
				
			|||||||
 | 
					package main
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import "os"
 | 
				
			||||||
 | 
					import "image"
 | 
				
			||||||
 | 
					import _ "image/png"
 | 
				
			||||||
 | 
					import "git.tebibyte.media/sashakoshka/tomo"
 | 
				
			||||||
 | 
					import "git.tebibyte.media/sashakoshka/tomo/layouts/basic"
 | 
				
			||||||
 | 
					import "git.tebibyte.media/sashakoshka/tomo/elements/basic"
 | 
				
			||||||
 | 
					import _ "git.tebibyte.media/sashakoshka/tomo/backends/x"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func main () {
 | 
				
			||||||
 | 
						tomo.Run(run)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func run () {
 | 
				
			||||||
 | 
						window, _ := tomo.NewWindow(383, 360)
 | 
				
			||||||
 | 
						window.SetTitle("Scroll")
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						file, err := os.Open("assets/banner.png")
 | 
				
			||||||
 | 
						if err != nil { panic(err.Error()); return  }
 | 
				
			||||||
 | 
						logo, _, err := image.Decode(file)
 | 
				
			||||||
 | 
						file.Close()
 | 
				
			||||||
 | 
						if err != nil { panic(err.Error()); return  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						scrollContainer := basicElements.NewScrollContainer(false, true)
 | 
				
			||||||
 | 
						document := basicElements.NewDocumentContainer()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						document.Adopt (basicElements.NewLabel (
 | 
				
			||||||
 | 
							"A document container is a vertically stacked container " +
 | 
				
			||||||
 | 
							"capable of properly laying out flexible elements such as " +
 | 
				
			||||||
 | 
							"text-wrapped labels. You can also include normal elements " +
 | 
				
			||||||
 | 
							"like:", true))
 | 
				
			||||||
 | 
						document.Adopt (basicElements.NewButton (
 | 
				
			||||||
 | 
							"Buttons,"))
 | 
				
			||||||
 | 
						document.Adopt (basicElements.NewCheckbox (
 | 
				
			||||||
 | 
							"Checkboxes,", true))
 | 
				
			||||||
 | 
						document.Adopt(basicElements.NewTextBox("", "And text boxes."))
 | 
				
			||||||
 | 
						document.Adopt (basicElements.NewSpacer(true))
 | 
				
			||||||
 | 
						document.Adopt (basicElements.NewLabel (
 | 
				
			||||||
 | 
							"Document containers are meant to be placed inside of a " +
 | 
				
			||||||
 | 
							"ScrollContainer, like this one.", true))
 | 
				
			||||||
 | 
						document.Adopt (basicElements.NewLabel (
 | 
				
			||||||
 | 
							"You could use document containers to do things like display various " +
 | 
				
			||||||
 | 
							"forms of hypertext (like HTML, gemtext, markdown, etc.), " +
 | 
				
			||||||
 | 
							"lay out a settings menu with descriptive label text between " +
 | 
				
			||||||
 | 
							"control groups like in iOS, or list comment or chat histories.", true))
 | 
				
			||||||
 | 
						document.Adopt(basicElements.NewImage(logo))
 | 
				
			||||||
 | 
						document.Adopt (basicElements.NewLabel (
 | 
				
			||||||
 | 
							"Oh, you're a switch? Then name all of these switches:", true))
 | 
				
			||||||
 | 
						for i := 0; i < 3; i ++ {
 | 
				
			||||||
 | 
							switchContainer := basicElements.NewContainer (basicLayouts.Horizontal {
 | 
				
			||||||
 | 
								Gap: true,
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
							for i := 0; i < 10; i ++ {
 | 
				
			||||||
 | 
								switchContainer.Adopt(basicElements.NewSwitch("", false), true)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							document.Adopt(switchContainer)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						scrollContainer.Adopt(document)
 | 
				
			||||||
 | 
						window.Adopt(scrollContainer)
 | 
				
			||||||
 | 
						window.OnClose(tomo.Stop)
 | 
				
			||||||
 | 
						window.Show()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -1,6 +1,7 @@
 | 
				
			|||||||
package main
 | 
					package main
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import "time"
 | 
					import "time"
 | 
				
			||||||
 | 
					import "image"
 | 
				
			||||||
import "git.tebibyte.media/sashakoshka/tomo"
 | 
					import "git.tebibyte.media/sashakoshka/tomo"
 | 
				
			||||||
import "git.tebibyte.media/sashakoshka/tomo/canvas"
 | 
					import "git.tebibyte.media/sashakoshka/tomo/canvas"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -30,14 +31,17 @@ func NewGame (world World, textures Textures) (game *Game) {
 | 
				
			|||||||
	return
 | 
						return
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (game *Game) DrawTo (canvas canvas.Canvas) {
 | 
					func (game *Game) DrawTo (canvas canvas.Canvas, bounds image.Rectangle) {
 | 
				
			||||||
	if canvas == nil {
 | 
						if canvas == nil {
 | 
				
			||||||
		game.stopChan <- true
 | 
							select {
 | 
				
			||||||
 | 
							case game.stopChan <- true:
 | 
				
			||||||
 | 
							default:
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
	} else if !game.running {
 | 
						} else if !game.running {
 | 
				
			||||||
		game.running = true
 | 
							game.running = true
 | 
				
			||||||
		go game.run()
 | 
							go game.run()
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	game.Raycaster.DrawTo(canvas)
 | 
						game.Raycaster.DrawTo(canvas, bounds)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (game *Game) Stamina () float64 {
 | 
					func (game *Game) Stamina () float64 {
 | 
				
			||||||
 | 
				
			|||||||
@ -3,7 +3,6 @@ package basicLayouts
 | 
				
			|||||||
import "image"
 | 
					import "image"
 | 
				
			||||||
import "git.tebibyte.media/sashakoshka/tomo/artist"
 | 
					import "git.tebibyte.media/sashakoshka/tomo/artist"
 | 
				
			||||||
import "git.tebibyte.media/sashakoshka/tomo/layouts"
 | 
					import "git.tebibyte.media/sashakoshka/tomo/layouts"
 | 
				
			||||||
import "git.tebibyte.media/sashakoshka/tomo/elements"
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Dialog arranges elements in the form of a dialog box. The first element is
 | 
					// Dialog arranges elements in the form of a dialog box. The first element is
 | 
				
			||||||
// positioned above as the main focus of the dialog, and is set to expand
 | 
					// positioned above as the main focus of the dialog, and is set to expand
 | 
				
			||||||
@ -132,45 +131,6 @@ func (layout Dialog) MinimumSize (
 | 
				
			|||||||
	return
 | 
						return
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// FlexibleHeightFor Returns the minimum height the layout needs to lay out the
 | 
					 | 
				
			||||||
// specified elements at the given width, taking into account flexible elements.
 | 
					 | 
				
			||||||
func (layout Dialog) FlexibleHeightFor (
 | 
					 | 
				
			||||||
	entries []layouts.LayoutEntry,
 | 
					 | 
				
			||||||
	margin  image.Point,
 | 
					 | 
				
			||||||
	padding artist.Inset,
 | 
					 | 
				
			||||||
	width int,
 | 
					 | 
				
			||||||
) (
 | 
					 | 
				
			||||||
	height int,
 | 
					 | 
				
			||||||
) {
 | 
					 | 
				
			||||||
	if layout.Pad {
 | 
					 | 
				
			||||||
		width -= padding.Horizontal()
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	
 | 
					 | 
				
			||||||
	if len(entries) > 0 {
 | 
					 | 
				
			||||||
		mainChildHeight := 0
 | 
					 | 
				
			||||||
		if child, flexible := entries[0].Element.(elements.Flexible); flexible {
 | 
					 | 
				
			||||||
			mainChildHeight = child.FlexibleHeightFor(width)
 | 
					 | 
				
			||||||
		} else {
 | 
					 | 
				
			||||||
			_, mainChildHeight = entries[0].MinimumSize()
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		height += mainChildHeight
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if len(entries) > 1 {
 | 
					 | 
				
			||||||
		if layout.Gap { height += margin.Y }
 | 
					 | 
				
			||||||
		_, additionalHeight := layout.minimumSizeOfControlRow (
 | 
					 | 
				
			||||||
			entries[1:], margin, padding)
 | 
					 | 
				
			||||||
		height += additionalHeight
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if layout.Pad {
 | 
					 | 
				
			||||||
		height += padding.Vertical()
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	return
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// TODO: possibly flatten this method to account for flexible elements within
 | 
					 | 
				
			||||||
// the control row.
 | 
					 | 
				
			||||||
func (layout Dialog) minimumSizeOfControlRow (
 | 
					func (layout Dialog) minimumSizeOfControlRow (
 | 
				
			||||||
	entries []layouts.LayoutEntry,
 | 
						entries []layouts.LayoutEntry,
 | 
				
			||||||
	margin  image.Point,
 | 
						margin  image.Point,
 | 
				
			||||||
 | 
				
			|||||||
@ -3,7 +3,6 @@ package basicLayouts
 | 
				
			|||||||
import "image"
 | 
					import "image"
 | 
				
			||||||
import "git.tebibyte.media/sashakoshka/tomo/artist"
 | 
					import "git.tebibyte.media/sashakoshka/tomo/artist"
 | 
				
			||||||
import "git.tebibyte.media/sashakoshka/tomo/layouts"
 | 
					import "git.tebibyte.media/sashakoshka/tomo/layouts"
 | 
				
			||||||
import "git.tebibyte.media/sashakoshka/tomo/elements"
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Horizontal arranges elements horizontally. Elements at the start of the entry
 | 
					// Horizontal arranges elements horizontally. Elements at the start of the entry
 | 
				
			||||||
// list will be positioned on the left, and elements at the end of the entry
 | 
					// list will be positioned on the left, and elements at the end of the entry
 | 
				
			||||||
@ -76,49 +75,6 @@ func (layout Horizontal) MinimumSize (
 | 
				
			|||||||
	return
 | 
						return
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// FlexibleHeightFor Returns the minimum height the layout needs to lay out the
 | 
					 | 
				
			||||||
// specified elements at the given width, taking into account flexible elements.
 | 
					 | 
				
			||||||
func (layout Horizontal) FlexibleHeightFor (
 | 
					 | 
				
			||||||
	entries []layouts.LayoutEntry,
 | 
					 | 
				
			||||||
	margin  image.Point,
 | 
					 | 
				
			||||||
	padding artist.Inset,
 | 
					 | 
				
			||||||
	width int,
 | 
					 | 
				
			||||||
) (
 | 
					 | 
				
			||||||
	height int,
 | 
					 | 
				
			||||||
) {
 | 
					 | 
				
			||||||
	if layout.Pad { width -= padding.Horizontal() }
 | 
					 | 
				
			||||||
	
 | 
					 | 
				
			||||||
	// get width of expanding elements
 | 
					 | 
				
			||||||
	expandingElementWidth := layout.expandingElementWidth (
 | 
					 | 
				
			||||||
		entries, margin, padding, width)
 | 
					 | 
				
			||||||
	
 | 
					 | 
				
			||||||
	x, y := 0, 0
 | 
					 | 
				
			||||||
	if layout.Pad {
 | 
					 | 
				
			||||||
		x += padding.Horizontal()
 | 
					 | 
				
			||||||
		y += padding.Vertical()
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// set the size and position of each element
 | 
					 | 
				
			||||||
	for index, entry := range entries {
 | 
					 | 
				
			||||||
		entryWidth, entryHeight := entry.MinimumSize()
 | 
					 | 
				
			||||||
		if entry.Expand {
 | 
					 | 
				
			||||||
			entryWidth = expandingElementWidth
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		if child, flexible := entry.Element.(elements.Flexible); flexible {
 | 
					 | 
				
			||||||
			entryHeight = child.FlexibleHeightFor(entryWidth)
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		if entryHeight > height { height = entryHeight }
 | 
					 | 
				
			||||||
		
 | 
					 | 
				
			||||||
		x += entryWidth
 | 
					 | 
				
			||||||
		if index > 0 && layout.Gap { x += margin.X }
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if layout.Pad {
 | 
					 | 
				
			||||||
		height += padding.Vertical()
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	return
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func (layout Horizontal) expandingElementWidth (
 | 
					func (layout Horizontal) expandingElementWidth (
 | 
				
			||||||
	entries []layouts.LayoutEntry,
 | 
						entries []layouts.LayoutEntry,
 | 
				
			||||||
	margin  image.Point,
 | 
						margin  image.Point,
 | 
				
			||||||
 | 
				
			|||||||
@ -3,7 +3,6 @@ package basicLayouts
 | 
				
			|||||||
import "image"
 | 
					import "image"
 | 
				
			||||||
import "git.tebibyte.media/sashakoshka/tomo/artist"
 | 
					import "git.tebibyte.media/sashakoshka/tomo/artist"
 | 
				
			||||||
import "git.tebibyte.media/sashakoshka/tomo/layouts"
 | 
					import "git.tebibyte.media/sashakoshka/tomo/layouts"
 | 
				
			||||||
import "git.tebibyte.media/sashakoshka/tomo/elements"
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Vertical arranges elements vertically. Elements at the start of the entry
 | 
					// Vertical arranges elements vertically. Elements at the start of the entry
 | 
				
			||||||
// list will be positioned at the top, and elements at the end of the entry list
 | 
					// list will be positioned at the top, and elements at the end of the entry list
 | 
				
			||||||
@ -32,13 +31,7 @@ func (layout Vertical) Arrange (
 | 
				
			|||||||
	minimumHeights := make([]int, len(entries))
 | 
						minimumHeights := make([]int, len(entries))
 | 
				
			||||||
	expandingElements := 0
 | 
						expandingElements := 0
 | 
				
			||||||
	for index, entry := range entries {
 | 
						for index, entry := range entries {
 | 
				
			||||||
		var entryMinHeight int
 | 
							_, entryMinHeight := entry.MinimumSize()
 | 
				
			||||||
 | 
					 | 
				
			||||||
		if child, flexible := entry.Element.(elements.Flexible); flexible {
 | 
					 | 
				
			||||||
			entryMinHeight = child.FlexibleHeightFor(bounds.Dx())
 | 
					 | 
				
			||||||
		} else {
 | 
					 | 
				
			||||||
			_, entryMinHeight = entry.MinimumSize()
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		minimumHeights[index] = entryMinHeight
 | 
							minimumHeights[index] = entryMinHeight
 | 
				
			||||||
		
 | 
							
 | 
				
			||||||
		if entry.Expand {
 | 
							if entry.Expand {
 | 
				
			||||||
@ -101,34 +94,3 @@ func (layout Vertical) MinimumSize (
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
	return
 | 
						return
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					 | 
				
			||||||
// FlexibleHeightFor Returns the minimum height the layout needs to lay out the
 | 
					 | 
				
			||||||
// specified elements at the given width, taking into account flexible elements.
 | 
					 | 
				
			||||||
func (layout Vertical) FlexibleHeightFor (
 | 
					 | 
				
			||||||
	entries []layouts.LayoutEntry,
 | 
					 | 
				
			||||||
	margin  image.Point,
 | 
					 | 
				
			||||||
	padding artist.Inset,
 | 
					 | 
				
			||||||
	width int,
 | 
					 | 
				
			||||||
) (
 | 
					 | 
				
			||||||
	height int,
 | 
					 | 
				
			||||||
) {
 | 
					 | 
				
			||||||
	if layout.Pad {
 | 
					 | 
				
			||||||
		width  -= padding.Horizontal()
 | 
					 | 
				
			||||||
		height += padding.Vertical()
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	
 | 
					 | 
				
			||||||
	for index, entry := range entries {
 | 
					 | 
				
			||||||
		child, flexible := entry.Element.(elements.Flexible)
 | 
					 | 
				
			||||||
		if flexible {
 | 
					 | 
				
			||||||
			height += child.FlexibleHeightFor(width)
 | 
					 | 
				
			||||||
		} else {
 | 
					 | 
				
			||||||
			_, entryHeight := entry.MinimumSize()
 | 
					 | 
				
			||||||
			height += entryHeight
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		
 | 
					 | 
				
			||||||
		if layout.Gap && index > 0 {
 | 
					 | 
				
			||||||
			height += margin.Y
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	return
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
				
			|||||||
@ -12,10 +12,6 @@ type LayoutEntry struct {
 | 
				
			|||||||
	Expand bool
 | 
						Expand bool
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// TODO: have layouts take in artist.Inset for margin and padding
 | 
					 | 
				
			||||||
// TODO: create a layout that only displays the first element and full screen.
 | 
					 | 
				
			||||||
// basically a blank layout for containers that only ever have one element.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// Layout is capable of arranging elements within a container. It is also able
 | 
					// Layout is capable of arranging elements within a container. It is also able
 | 
				
			||||||
// to determine the minimum amount of room it needs to do so.
 | 
					// to determine the minimum amount of room it needs to do so.
 | 
				
			||||||
type Layout interface {
 | 
					type Layout interface {
 | 
				
			||||||
@ -39,16 +35,4 @@ type Layout interface {
 | 
				
			|||||||
	) (
 | 
						) (
 | 
				
			||||||
		width, height int,
 | 
							width, height int,
 | 
				
			||||||
	)
 | 
						)
 | 
				
			||||||
 | 
					 | 
				
			||||||
	// FlexibleHeightFor Returns the minimum height the layout needs to lay
 | 
					 | 
				
			||||||
	// out the specified elements at the given width, taking into account
 | 
					 | 
				
			||||||
	// flexible elements.
 | 
					 | 
				
			||||||
	FlexibleHeightFor (
 | 
					 | 
				
			||||||
		entries []LayoutEntry,
 | 
					 | 
				
			||||||
		margin  image.Point,
 | 
					 | 
				
			||||||
		padding artist.Inset,
 | 
					 | 
				
			||||||
		squeeze int,
 | 
					 | 
				
			||||||
	) (
 | 
					 | 
				
			||||||
		height int,
 | 
					 | 
				
			||||||
	)
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -41,8 +41,7 @@ func (setter *TypeSetter) needLayout () {
 | 
				
			|||||||
	metrics   := setter.face.Metrics()
 | 
						metrics   := setter.face.Metrics()
 | 
				
			||||||
	remaining := setter.text
 | 
						remaining := setter.text
 | 
				
			||||||
	y         := fixed.Int26_6(0)
 | 
						y         := fixed.Int26_6(0)
 | 
				
			||||||
	maxY      := fixed.I(setter.maxHeight) + metrics.Height
 | 
						for len(remaining) > 0 {
 | 
				
			||||||
	for len(remaining) > 0 && (y < maxY || setter.maxHeight == 0) {
 | 
					 | 
				
			||||||
		// process one line
 | 
							// process one line
 | 
				
			||||||
		line, remainingFromLine := DoLine (
 | 
							line, remainingFromLine := DoLine (
 | 
				
			||||||
			remaining, setter.face, fixed.I(setter.maxWidth))
 | 
								remaining, setter.face, fixed.I(setter.maxWidth))
 | 
				
			||||||
 | 
				
			|||||||
										
											Binary file not shown.
										
									
								
							| 
		 Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 1.9 KiB  | 
		Reference in New Issue
	
	Block a user