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
 | 
			
		||||
// clipping rectangle.
 | 
			
		||||
type Pattern interface {
 | 
			
		||||
	// Draw draws to destination, using the bounds of destination as a width
 | 
			
		||||
	// and height for things like gradients, bevels, etc. The pattern may
 | 
			
		||||
	// not draw outside the union of destination.Bounds() and clip. The
 | 
			
		||||
	// clipping rectangle effectively takes a subset of the pattern. To
 | 
			
		||||
	// change the bounds of the pattern itself, use canvas.Cut() on the
 | 
			
		||||
	// destination before passing it to Draw().
 | 
			
		||||
	Draw (destination canvas.Canvas, clip image.Rectangle)
 | 
			
		||||
	// Draw draws the pattern onto the destination canvas, using the
 | 
			
		||||
	// specified bounds. The given bounds can be smaller or larger than the
 | 
			
		||||
	// bounds of the destination canvas. The destination canvas can be cut
 | 
			
		||||
	// using canvas.Cut() to draw only a specific subset of a pattern.
 | 
			
		||||
	Draw (destination canvas.Canvas, bounds image.Rectangle)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Draw lets you use several clipping rectangles to draw a pattern.
 | 
			
		||||
func Draw (
 | 
			
		||||
// Fill fills the destination canvas with the given pattern.
 | 
			
		||||
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,
 | 
			
		||||
	source      Pattern,
 | 
			
		||||
	clips       ...image.Rectangle,
 | 
			
		||||
	bounds      image.Rectangle,
 | 
			
		||||
	subsets     ...image.Rectangle,
 | 
			
		||||
) (
 | 
			
		||||
	updatedRegion image.Rectangle,
 | 
			
		||||
) {
 | 
			
		||||
	for _, clip := range clips {
 | 
			
		||||
		source.Draw(destination, clip)
 | 
			
		||||
		updatedRegion = updatedRegion.Union(clip)
 | 
			
		||||
	for _, subset := range subsets {
 | 
			
		||||
		source.Draw(canvas.Cut(destination, subset), bounds)
 | 
			
		||||
		updatedRegion = updatedRegion.Union(subset)
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// DrawBounds lets you specify an overall bounding rectangle for drawing a
 | 
			
		||||
// pattern. The destination is cut to this rectangle.
 | 
			
		||||
func DrawBounds (
 | 
			
		||||
	destination canvas.Canvas,
 | 
			
		||||
	source      Pattern,
 | 
			
		||||
	bounds      image.Rectangle,
 | 
			
		||||
) (
 | 
			
		||||
	updatedRegion image.Rectangle,
 | 
			
		||||
) {
 | 
			
		||||
	return Draw(canvas.Cut(destination, bounds), source, bounds)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// DrawShatter is like an inverse of Draw, drawing nothing in the areas
 | 
			
		||||
// specified in "rocks".
 | 
			
		||||
// DrawShatter is like an inverse of DrawClip, drawing nothing in the areas
 | 
			
		||||
// specified by "rocks".
 | 
			
		||||
func DrawShatter (
 | 
			
		||||
	destination canvas.Canvas,
 | 
			
		||||
	source      Pattern,
 | 
			
		||||
	bounds      image.Rectangle,
 | 
			
		||||
	rocks       ...image.Rectangle,
 | 
			
		||||
) (
 | 
			
		||||
	updatedRegion image.Rectangle,
 | 
			
		||||
) {
 | 
			
		||||
	tiles := shatter.Shatter(destination.Bounds(), rocks...)
 | 
			
		||||
	return Draw(destination, source, tiles...)
 | 
			
		||||
	tiles := shatter.Shatter(bounds, rocks...)
 | 
			
		||||
	return DrawClip(destination, source, bounds, tiles...)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// AllocateSample returns a new canvas containing the result of a pattern. The
 | 
			
		||||
// resulting canvas can be sourced from shape drawing functions. I beg of you
 | 
			
		||||
// please do not call this every time you need to draw a shape with a pattern on
 | 
			
		||||
// it because that is horrible and cruel to the computer.
 | 
			
		||||
func AllocateSample (source Pattern, width, height int) (allocated canvas.Canvas) {
 | 
			
		||||
	allocated = canvas.NewBasicCanvas(width, height)
 | 
			
		||||
	source.Draw(allocated, allocated.Bounds())
 | 
			
		||||
	return
 | 
			
		||||
func AllocateSample (source Pattern, width, height int) canvas.Canvas {
 | 
			
		||||
	allocated := canvas.NewBasicCanvas(width, height)
 | 
			
		||||
	Fill(allocated, source)
 | 
			
		||||
	return allocated
 | 
			
		||||
} 
 | 
			
		||||
 | 
			
		||||
@ -37,9 +37,9 @@ type Border struct {
 | 
			
		||||
 | 
			
		||||
// Draw draws the border pattern onto the destination canvas within the clipping
 | 
			
		||||
// bounds.
 | 
			
		||||
func (pattern Border) Draw (destination canvas.Canvas, clip image.Rectangle) {
 | 
			
		||||
	bounds := clip.Canon().Intersect(destination.Bounds())
 | 
			
		||||
	if bounds.Empty() { return }
 | 
			
		||||
func (pattern Border) Draw (destination canvas.Canvas, bounds image.Rectangle) {
 | 
			
		||||
	drawBounds := bounds.Canon().Intersect(destination.Bounds())
 | 
			
		||||
	if drawBounds.Empty() { return }
 | 
			
		||||
 | 
			
		||||
	srcSections := nonasect(pattern.Bounds(), pattern.Inset)
 | 
			
		||||
	srcTextures := [9]Texture { }
 | 
			
		||||
@ -47,9 +47,9 @@ func (pattern Border) Draw (destination canvas.Canvas, clip image.Rectangle) {
 | 
			
		||||
		srcTextures[index].Canvas = canvas.Cut(pattern, section)
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	dstSections := nonasect(destination.Bounds(), pattern.Inset)
 | 
			
		||||
	dstSections := nonasect(bounds, pattern.Inset)
 | 
			
		||||
	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
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 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.
 | 
			
		||||
func (pattern Texture) Draw (destination canvas.Canvas, clip image.Rectangle) {
 | 
			
		||||
	realBounds := destination.Bounds()
 | 
			
		||||
	bounds := clip.Canon().Intersect(realBounds)
 | 
			
		||||
	if bounds.Empty() { return }
 | 
			
		||||
func (pattern Texture) Draw (destination canvas.Canvas, bounds image.Rectangle) {
 | 
			
		||||
	drawBounds := bounds.Canon().Intersect(destination.Bounds())
 | 
			
		||||
	if drawBounds.Empty() { return }
 | 
			
		||||
	
 | 
			
		||||
	dstData, dstStride := destination.Buffer()
 | 
			
		||||
	srcData, srcStride := pattern.Buffer()
 | 
			
		||||
	srcBounds := pattern.Bounds()
 | 
			
		||||
 | 
			
		||||
	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.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
 | 
			
		||||
		dstPoint.X = bounds.Min.X
 | 
			
		||||
		dstPoint.X = drawBounds.Min.X
 | 
			
		||||
		dstYComponent := dstPoint.Y * dstStride
 | 
			
		||||
		srcYComponent := srcPoint.Y * srcStride
 | 
			
		||||
		
 | 
			
		||||
@ -42,7 +41,7 @@ func (pattern Texture) Draw (destination canvas.Canvas, clip image.Rectangle) {
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			dstPoint.X ++
 | 
			
		||||
			if dstPoint.X >= bounds.Max.X {
 | 
			
		||||
			if dstPoint.X >= drawBounds.Max.X {
 | 
			
		||||
				break
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
@ -9,9 +9,9 @@ import "git.tebibyte.media/sashakoshka/tomo/artist/shapes"
 | 
			
		||||
// Uniform is a pattern that draws a solid color.
 | 
			
		||||
type Uniform color.RGBA
 | 
			
		||||
 | 
			
		||||
// Draw fills the clipping rectangle with the pattern's color.
 | 
			
		||||
func (pattern Uniform) Draw (destination canvas.Canvas, clip image.Rectangle) {
 | 
			
		||||
	shapes.FillColorRectangle(destination, color.RGBA(pattern), clip)
 | 
			
		||||
// Draw fills the bounding rectangle with the pattern's color.
 | 
			
		||||
func (pattern Uniform) Draw (destination canvas.Canvas, bounds image.Rectangle) {
 | 
			
		||||
	shapes.FillColorRectangle(destination, color.RGBA(pattern), bounds)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Uhex creates a new Uniform pattern from an RGBA integer value.
 | 
			
		||||
 | 
			
		||||
@ -13,6 +13,7 @@ import "git.tebibyte.media/sashakoshka/tomo/canvas"
 | 
			
		||||
func FillEllipse (
 | 
			
		||||
	destination canvas.Canvas,
 | 
			
		||||
	source      canvas.Canvas,
 | 
			
		||||
	bounds      image.Rectangle,
 | 
			
		||||
) (
 | 
			
		||||
	updatedRegion image.Rectangle,
 | 
			
		||||
) {
 | 
			
		||||
@ -20,15 +21,17 @@ func FillEllipse (
 | 
			
		||||
	srcData, srcStride := source.Buffer()
 | 
			
		||||
	
 | 
			
		||||
	offset := source.Bounds().Min.Sub(destination.Bounds().Min)
 | 
			
		||||
	bounds     := source.Bounds().Sub(offset).Intersect(destination.Bounds())
 | 
			
		||||
	realBounds := destination.Bounds()
 | 
			
		||||
	drawBounds :=
 | 
			
		||||
		source.Bounds().Sub(offset).
 | 
			
		||||
		Intersect(destination.Bounds()).
 | 
			
		||||
		Intersect(bounds)
 | 
			
		||||
	if bounds.Empty() { return }
 | 
			
		||||
	updatedRegion = bounds
 | 
			
		||||
 | 
			
		||||
	point := image.Point { }
 | 
			
		||||
	for point.Y = bounds.Min.Y; point.Y < bounds.Max.Y; point.Y ++ {
 | 
			
		||||
	for point.X = bounds.Min.X; point.X < bounds.Max.X; point.X ++ {
 | 
			
		||||
		if inEllipse(point, realBounds) {
 | 
			
		||||
	for point.Y = drawBounds.Min.Y; point.Y < drawBounds.Max.Y; point.Y ++ {
 | 
			
		||||
	for point.X = drawBounds.Min.X; point.X < drawBounds.Max.X; point.X ++ {
 | 
			
		||||
		if inEllipse(point, bounds) {
 | 
			
		||||
			offsetPoint := point.Add(offset)
 | 
			
		||||
			dstIndex := point.X       + point.Y       * dstStride
 | 
			
		||||
			srcIndex := offsetPoint.X + offsetPoint.Y * srcStride
 | 
			
		||||
@ -41,6 +44,7 @@ func FillEllipse (
 | 
			
		||||
func StrokeEllipse (
 | 
			
		||||
	destination canvas.Canvas,
 | 
			
		||||
	source      canvas.Canvas,
 | 
			
		||||
	bounds      image.Rectangle,
 | 
			
		||||
	weight      int,
 | 
			
		||||
) {
 | 
			
		||||
	if weight < 1 { return }
 | 
			
		||||
@ -48,10 +52,9 @@ func StrokeEllipse (
 | 
			
		||||
	dstData, dstStride := destination.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)
 | 
			
		||||
	realBounds := destination.Bounds()
 | 
			
		||||
	if bounds.Empty() { return }
 | 
			
		||||
	if drawBounds.Empty() { return }
 | 
			
		||||
 | 
			
		||||
	context := ellipsePlottingContext {
 | 
			
		||||
		plottingContext: plottingContext {
 | 
			
		||||
@ -61,11 +64,11 @@ func StrokeEllipse (
 | 
			
		||||
			srcStride: srcStride,
 | 
			
		||||
			weight:    weight,
 | 
			
		||||
			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()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -10,20 +10,24 @@ import "git.tebibyte.media/sashakoshka/tomo/shatter"
 | 
			
		||||
func FillRectangle (
 | 
			
		||||
	destination canvas.Canvas,
 | 
			
		||||
	source      canvas.Canvas,
 | 
			
		||||
	bounds      image.Rectangle,
 | 
			
		||||
) (
 | 
			
		||||
	updatedRegion image.Rectangle,
 | 
			
		||||
) {
 | 
			
		||||
	dstData, dstStride := destination.Buffer()
 | 
			
		||||
	srcData, srcStride := source.Buffer()
 | 
			
		||||
 | 
			
		||||
	offset := source.Bounds().Min.Sub(destination.Bounds().Min)
 | 
			
		||||
	bounds     := source.Bounds().Sub(offset).Intersect(destination.Bounds())
 | 
			
		||||
	if bounds.Empty() { return }
 | 
			
		||||
	updatedRegion = bounds
 | 
			
		||||
	offset     := source.Bounds().Min.Sub(destination.Bounds().Min)
 | 
			
		||||
	drawBounds :=
 | 
			
		||||
		source.Bounds().Sub(offset).
 | 
			
		||||
		Intersect(destination.Bounds()).
 | 
			
		||||
		Intersect(bounds)
 | 
			
		||||
	if drawBounds.Empty() { return }
 | 
			
		||||
	updatedRegion = drawBounds
 | 
			
		||||
	
 | 
			
		||||
	point := image.Point { }
 | 
			
		||||
	for point.Y = bounds.Min.Y; point.Y < bounds.Max.Y; point.Y ++ {
 | 
			
		||||
	for point.X = bounds.Min.X; point.X < bounds.Max.X; point.X ++ {
 | 
			
		||||
	for point.Y = drawBounds.Min.Y; point.Y < drawBounds.Max.Y; point.Y ++ {
 | 
			
		||||
	for point.X = drawBounds.Min.X; point.X < drawBounds.Max.X; point.X ++ {
 | 
			
		||||
		offsetPoint := point.Add(offset)
 | 
			
		||||
		dstIndex := point.X       + point.Y       * dstStride
 | 
			
		||||
		srcIndex := offsetPoint.X + offsetPoint.Y * srcStride
 | 
			
		||||
@ -36,15 +40,16 @@ func FillRectangle (
 | 
			
		||||
func StrokeRectangle (
 | 
			
		||||
	destination canvas.Canvas,
 | 
			
		||||
	source      canvas.Canvas,
 | 
			
		||||
	bounds      image.Rectangle,
 | 
			
		||||
	weight      int,
 | 
			
		||||
) (
 | 
			
		||||
	updatedRegion image.Rectangle,
 | 
			
		||||
) {
 | 
			
		||||
	bounds := destination.Bounds()
 | 
			
		||||
	insetBounds := bounds.Inset(weight)
 | 
			
		||||
	if insetBounds.Empty() {
 | 
			
		||||
		FillRectangle(destination, source)
 | 
			
		||||
		return
 | 
			
		||||
		return FillRectangle(destination, source, bounds)
 | 
			
		||||
	}
 | 
			
		||||
	FillRectangleShatter(destination, source, insetBounds)
 | 
			
		||||
	return FillRectangleShatter(destination, source, bounds, insetBounds)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// FillRectangleShatter is like FillRectangle, but it does not draw in areas
 | 
			
		||||
@ -52,15 +57,19 @@ func StrokeRectangle (
 | 
			
		||||
func FillRectangleShatter (
 | 
			
		||||
	destination canvas.Canvas,
 | 
			
		||||
	source      canvas.Canvas,
 | 
			
		||||
	bounds      image.Rectangle,
 | 
			
		||||
	rocks       ...image.Rectangle,
 | 
			
		||||
) (
 | 
			
		||||
	updatedRegion image.Rectangle,
 | 
			
		||||
) {
 | 
			
		||||
	tiles  := shatter.Shatter(destination.Bounds(), rocks...)
 | 
			
		||||
	offset := source.Bounds().Min.Sub(destination.Bounds().Min)
 | 
			
		||||
	tiles := shatter.Shatter(bounds, rocks...)
 | 
			
		||||
	for _, tile := range tiles {
 | 
			
		||||
		FillRectangle (
 | 
			
		||||
			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
 | 
			
		||||
@ -92,11 +101,15 @@ func FillColorRectangleShatter (
 | 
			
		||||
	color       color.RGBA,
 | 
			
		||||
	bounds      image.Rectangle,
 | 
			
		||||
	rocks       ...image.Rectangle,
 | 
			
		||||
) (
 | 
			
		||||
	updatedRegion image.Rectangle,
 | 
			
		||||
) {
 | 
			
		||||
	tiles := shatter.Shatter(bounds, rocks...)
 | 
			
		||||
	for _, tile := range tiles {
 | 
			
		||||
		FillColorRectangle(destination, color, tile)
 | 
			
		||||
		updatedRegion = updatedRegion.Union(tile)
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// StrokeColorRectangle is similar to FillColorRectangle, but it draws an inset
 | 
			
		||||
@ -106,11 +119,12 @@ func StrokeColorRectangle (
 | 
			
		||||
	color       color.RGBA,
 | 
			
		||||
	bounds      image.Rectangle,
 | 
			
		||||
	weight      int,
 | 
			
		||||
) (
 | 
			
		||||
	updatedRegion image.Rectangle,
 | 
			
		||||
) {
 | 
			
		||||
	insetBounds := bounds.Inset(weight)
 | 
			
		||||
	if insetBounds.Empty() {
 | 
			
		||||
		FillColorRectangle(destination, color, bounds)
 | 
			
		||||
		return
 | 
			
		||||
		return FillColorRectangle(destination, color, bounds)
 | 
			
		||||
	}
 | 
			
		||||
	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.OnMinimumSizeChange(nil)
 | 
			
		||||
	}
 | 
			
		||||
	if previousChild, ok := window.child.(elements.Flexible); ok {
 | 
			
		||||
		previousChild.OnFlexibleHeightChange(nil)
 | 
			
		||||
	}
 | 
			
		||||
	if previousChild, ok := window.child.(elements.Focusable); ok {
 | 
			
		||||
		previousChild.OnFocusRequest(nil)
 | 
			
		||||
		previousChild.OnFocusMotionRequest(nil)
 | 
			
		||||
@ -115,9 +112,6 @@ func (window *Window) Adopt (child elements.Element) {
 | 
			
		||||
	if newChild, ok := child.(elements.Configurable); ok {
 | 
			
		||||
		newChild.SetConfig(window.config)
 | 
			
		||||
	}
 | 
			
		||||
	if newChild, ok := child.(elements.Flexible); ok {
 | 
			
		||||
		newChild.OnFlexibleHeightChange(window.resizeChildToFit)
 | 
			
		||||
	}
 | 
			
		||||
	if newChild, ok := child.(elements.Focusable); ok {
 | 
			
		||||
		newChild.OnFocusRequest(window.childSelectionRequestCallback)
 | 
			
		||||
	}
 | 
			
		||||
@ -263,26 +257,7 @@ func (window *Window) redrawChildEntirely () {
 | 
			
		||||
 | 
			
		||||
func (window *Window) resizeChildToFit () {
 | 
			
		||||
	window.skipChildDrawCallback = true
 | 
			
		||||
	if child, ok := window.child.(elements.Flexible); ok {
 | 
			
		||||
		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.child.DrawTo(window.canvas, window.canvas.Bounds())
 | 
			
		||||
	window.skipChildDrawCallback = false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -4,7 +4,6 @@ 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/artist"
 | 
			
		||||
import "git.tebibyte.media/sashakoshka/tomo/textdraw"
 | 
			
		||||
import "git.tebibyte.media/sashakoshka/tomo/elements/core"
 | 
			
		||||
 | 
			
		||||
@ -176,7 +175,7 @@ func (element *Checkbox) draw () {
 | 
			
		||||
	backgroundPattern.Draw(element.core, bounds)
 | 
			
		||||
 | 
			
		||||
	pattern := element.theme.Pattern(theme.PatternButton, state)
 | 
			
		||||
	artist.DrawBounds(element.core, pattern, boxBounds)
 | 
			
		||||
	pattern.Draw(element.core, boxBounds)
 | 
			
		||||
 | 
			
		||||
	textBounds := element.drawer.LayoutBounds()
 | 
			
		||||
	margin := element.theme.Margin(theme.PatternBackground)
 | 
			
		||||
 | 
			
		||||
@ -20,12 +20,10 @@ type Container struct {
 | 
			
		||||
	layout    layouts.Layout
 | 
			
		||||
	children  []layouts.LayoutEntry
 | 
			
		||||
	warping   bool
 | 
			
		||||
	flexible  bool
 | 
			
		||||
	
 | 
			
		||||
	config config.Wrapped
 | 
			
		||||
	theme  theme.Wrapped
 | 
			
		||||
	
 | 
			
		||||
	onFlexibleHeightChange func ()
 | 
			
		||||
	onFocusRequest func () (granted bool)
 | 
			
		||||
	onFocusMotionRequest func (input.KeynavDirection) (granted bool)
 | 
			
		||||
}
 | 
			
		||||
@ -70,9 +68,6 @@ func (element *Container) Adopt (child elements.Element, expand bool) {
 | 
			
		||||
		element.redoAll()
 | 
			
		||||
		element.core.DamageAll()
 | 
			
		||||
	})
 | 
			
		||||
	if child0, ok := child.(elements.Flexible); ok {
 | 
			
		||||
		child0.OnFlexibleHeightChange(element.notifyFlexibleChange)
 | 
			
		||||
	}
 | 
			
		||||
	if child0, ok := child.(elements.Focusable); ok {
 | 
			
		||||
		child0.OnFocusRequest (func () (granted bool) {
 | 
			
		||||
			return element.childFocusRequestCallback(child0)
 | 
			
		||||
@ -92,7 +87,6 @@ func (element *Container) Adopt (child elements.Element, expand bool) {
 | 
			
		||||
 | 
			
		||||
	// refresh stale data
 | 
			
		||||
	element.updateMinimumSize()
 | 
			
		||||
	element.reflectChildProperties()
 | 
			
		||||
	if element.core.HasImage() && !element.warping {
 | 
			
		||||
		element.redoAll()
 | 
			
		||||
		element.core.DamageAll()
 | 
			
		||||
@ -135,7 +129,6 @@ func (element *Container) Disown (child elements.Element) {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	element.updateMinimumSize()
 | 
			
		||||
	element.reflectChildProperties()
 | 
			
		||||
	if element.core.HasImage() && !element.warping {
 | 
			
		||||
		element.redoAll()
 | 
			
		||||
		element.core.DamageAll()
 | 
			
		||||
@ -143,7 +136,7 @@ func (element *Container) Disown (child elements.Element) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (element *Container) clearChildEventHandlers (child elements.Element) {
 | 
			
		||||
	child.DrawTo(nil)
 | 
			
		||||
	child.DrawTo(nil, image.Rectangle { })
 | 
			
		||||
	child.OnDamage(nil)
 | 
			
		||||
	child.OnMinimumSizeChange(nil)
 | 
			
		||||
	if child0, ok := child.(elements.Focusable); ok {
 | 
			
		||||
@ -153,9 +146,6 @@ func (element *Container) clearChildEventHandlers (child elements.Element) {
 | 
			
		||||
			child0.HandleUnfocus()
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if child0, ok := child.(elements.Flexible); ok {
 | 
			
		||||
		child0.OnFlexibleHeightChange(nil)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// DisownAll removes all child elements from the container at once.
 | 
			
		||||
@ -166,7 +156,6 @@ func (element *Container) DisownAll () {
 | 
			
		||||
	element.children = nil
 | 
			
		||||
 | 
			
		||||
	element.updateMinimumSize()
 | 
			
		||||
	element.reflectChildProperties()
 | 
			
		||||
	if element.core.HasImage() && !element.warping {
 | 
			
		||||
		element.redoAll()
 | 
			
		||||
		element.core.DamageAll()
 | 
			
		||||
@ -211,7 +200,7 @@ func (element *Container) redoAll () {
 | 
			
		||||
	// remove child canvasses so that any operations done in here will not
 | 
			
		||||
	// cause a child to draw to a wack ass canvas.
 | 
			
		||||
	for _, entry := range element.children {
 | 
			
		||||
		entry.DrawTo(nil)
 | 
			
		||||
		entry.DrawTo(nil, entry.Bounds)
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	// do a layout
 | 
			
		||||
@ -225,12 +214,13 @@ func (element *Container) redoAll () {
 | 
			
		||||
	pattern := element.theme.Pattern (
 | 
			
		||||
		theme.PatternBackground,
 | 
			
		||||
		theme.State { })
 | 
			
		||||
	artist.DrawShatter (
 | 
			
		||||
		element.core, pattern, rocks...)
 | 
			
		||||
	artist.DrawShatter(element.core, pattern, element.Bounds(), rocks...)
 | 
			
		||||
 | 
			
		||||
	// cut our canvas up and give peices to child elements
 | 
			
		||||
	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()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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)) {
 | 
			
		||||
	element.onFocusRequest = callback
 | 
			
		||||
	element.Propagator.OnFocusRequest(callback)
 | 
			
		||||
@ -275,35 +253,6 @@ func (element *Container) OnFocusMotionRequest (
 | 
			
		||||
	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 (
 | 
			
		||||
	child elements.Focusable,
 | 
			
		||||
) (
 | 
			
		||||
@ -326,12 +275,6 @@ func (element *Container) updateMinimumSize () {
 | 
			
		||||
	element.core.SetMinimumSize(width, height)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (element *Container) notifyFlexibleChange () {
 | 
			
		||||
	if element.onFlexibleHeightChange != nil {
 | 
			
		||||
		element.onFlexibleHeightChange()
 | 
			
		||||
	}	
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (element *Container) doLayout () {
 | 
			
		||||
	margin := element.theme.Margin(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
 | 
			
		||||
 | 
			
		||||
import "golang.org/x/image/math/fixed"
 | 
			
		||||
import "git.tebibyte.media/sashakoshka/tomo/theme"
 | 
			
		||||
import "git.tebibyte.media/sashakoshka/tomo/config"
 | 
			
		||||
import "git.tebibyte.media/sashakoshka/tomo/textdraw"
 | 
			
		||||
@ -14,6 +15,9 @@ type Label struct {
 | 
			
		||||
	text   string
 | 
			
		||||
	drawer textdraw.Drawer
 | 
			
		||||
 | 
			
		||||
	forcedColumns int
 | 
			
		||||
	forcedRows    int
 | 
			
		||||
	
 | 
			
		||||
	config config.Wrapped
 | 
			
		||||
	theme  theme.Wrapped
 | 
			
		||||
	
 | 
			
		||||
@ -56,6 +60,17 @@ func (element *Label) handleResize () {
 | 
			
		||||
	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
 | 
			
		||||
// the given width in order to allow the text to wrap properly.
 | 
			
		||||
func (element *Label) FlexibleHeightFor (width int) (height int) {
 | 
			
		||||
@ -134,20 +149,35 @@ func (element *Label) SetConfig (new config.Config) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (element *Label) updateMinimumSize () {
 | 
			
		||||
	var width, height int
 | 
			
		||||
	
 | 
			
		||||
	if element.wrap {
 | 
			
		||||
		em := element.drawer.Em().Round()
 | 
			
		||||
		if em < 1 {
 | 
			
		||||
			em = element.theme.Padding(theme.PatternBackground)[0]
 | 
			
		||||
		}
 | 
			
		||||
		element.core.SetMinimumSize (
 | 
			
		||||
			em, element.drawer.LineHeight().Round())
 | 
			
		||||
		width, height = em, element.drawer.LineHeight().Round()
 | 
			
		||||
		if element.onFlexibleHeightChange != nil {
 | 
			
		||||
			element.onFlexibleHeightChange()
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		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 () {
 | 
			
		||||
@ -160,7 +190,7 @@ func (element *Label) draw () {
 | 
			
		||||
 | 
			
		||||
	textBounds := element.drawer.LayoutBounds()
 | 
			
		||||
 | 
			
		||||
	foreground :=  element.theme.Color (
 | 
			
		||||
	foreground := element.theme.Color (
 | 
			
		||||
		theme.ColorForeground,
 | 
			
		||||
		theme.State { })
 | 
			
		||||
	element.drawer.Draw(element.core, foreground, bounds.Min.Sub(textBounds.Min))
 | 
			
		||||
 | 
			
		||||
@ -461,5 +461,5 @@ func (element *List) draw () {
 | 
			
		||||
	).Add(innerBounds.Min).Intersect(innerBounds)
 | 
			
		||||
	pattern := element.theme.Pattern(theme.PatternSunken, state)
 | 
			
		||||
	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)
 | 
			
		||||
	padding := entry.theme.Padding(theme.PatternRaised)
 | 
			
		||||
	bounds  := entry.Bounds().Add(offset)
 | 
			
		||||
	artist.DrawBounds(destination, pattern, bounds)
 | 
			
		||||
	pattern.Draw(destination, bounds)
 | 
			
		||||
		
 | 
			
		||||
	foreground := entry.theme.Color (theme.ColorForeground, state)
 | 
			
		||||
	return entry.drawer.Draw (
 | 
			
		||||
 | 
			
		||||
@ -3,7 +3,6 @@ package basicElements
 | 
			
		||||
import "image"
 | 
			
		||||
import "git.tebibyte.media/sashakoshka/tomo/theme"
 | 
			
		||||
import "git.tebibyte.media/sashakoshka/tomo/config"
 | 
			
		||||
import "git.tebibyte.media/sashakoshka/tomo/artist"
 | 
			
		||||
import "git.tebibyte.media/sashakoshka/tomo/elements/core"
 | 
			
		||||
 | 
			
		||||
// 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.Max.Y)
 | 
			
		||||
	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/theme"
 | 
			
		||||
import "git.tebibyte.media/sashakoshka/tomo/config"
 | 
			
		||||
import "git.tebibyte.media/sashakoshka/tomo/artist"
 | 
			
		||||
import "git.tebibyte.media/sashakoshka/tomo/elements/core"
 | 
			
		||||
 | 
			
		||||
// ScrollBar is an element similar to Slider, but it has special behavior that
 | 
			
		||||
@ -315,12 +314,10 @@ func (element *ScrollBar) draw () {
 | 
			
		||||
		Disabled: !element.Enabled(),
 | 
			
		||||
		Pressed:  element.dragging,
 | 
			
		||||
	}
 | 
			
		||||
	artist.DrawBounds (
 | 
			
		||||
	element.theme.Pattern(theme.PatternGutter, state).Draw (
 | 
			
		||||
		element.core,
 | 
			
		||||
		element.theme.Pattern(theme.PatternGutter, state),
 | 
			
		||||
		bounds)
 | 
			
		||||
	artist.DrawBounds (
 | 
			
		||||
	element.theme.Pattern(theme.PatternHandle, state).Draw (
 | 
			
		||||
		element.core,
 | 
			
		||||
		element.theme.Pattern(theme.PatternHandle, state),
 | 
			
		||||
		element.bar)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -120,7 +120,7 @@ func (element *ScrollContainer) setChildEventHandlers (child elements.Element) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (element *ScrollContainer) clearChildEventHandlers (child elements.Scrollable) {
 | 
			
		||||
	child.DrawTo(nil)
 | 
			
		||||
	child.DrawTo(nil, image.Rectangle { })
 | 
			
		||||
	child.OnDamage(nil)
 | 
			
		||||
	child.OnMinimumSizeChange(nil)
 | 
			
		||||
	child.OnScrollBoundsChange(nil)
 | 
			
		||||
@ -131,9 +131,6 @@ func (element *ScrollContainer) clearChildEventHandlers (child elements.Scrollab
 | 
			
		||||
			child0.HandleUnfocus()
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if child0, ok := child.(elements.Flexible); ok {
 | 
			
		||||
		child0.OnFlexibleHeightChange(nil)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SetTheme sets the element's theme.
 | 
			
		||||
@ -201,19 +198,26 @@ func (element *ScrollContainer) Child (index int) (child elements.Element) {
 | 
			
		||||
func (element *ScrollContainer) redoAll () {
 | 
			
		||||
	if !element.core.HasImage() { return }
 | 
			
		||||
 | 
			
		||||
	if element.child      != nil { element.child.DrawTo(nil)      }
 | 
			
		||||
	if element.horizontal != nil { element.horizontal.DrawTo(nil) }
 | 
			
		||||
	if element.vertical   != nil { element.vertical.DrawTo(nil)   }
 | 
			
		||||
	zr := image.Rectangle { }
 | 
			
		||||
	if element.child      != nil { element.child.DrawTo(nil, zr)      }
 | 
			
		||||
	if element.horizontal != nil { element.horizontal.DrawTo(nil, zr) }
 | 
			
		||||
	if element.vertical   != nil { element.vertical.DrawTo(nil, zr)   }
 | 
			
		||||
	
 | 
			
		||||
	childBounds, horizontalBounds, verticalBounds := element.layout()
 | 
			
		||||
	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 {
 | 
			
		||||
		element.horizontal.DrawTo(canvas.Cut(element.core, horizontalBounds))
 | 
			
		||||
		element.horizontal.DrawTo (
 | 
			
		||||
			canvas.Cut(element.core, horizontalBounds),
 | 
			
		||||
			horizontalBounds)
 | 
			
		||||
	}
 | 
			
		||||
	if element.vertical != nil {
 | 
			
		||||
		element.vertical.DrawTo(canvas.Cut(element.core, verticalBounds))
 | 
			
		||||
		element.vertical.DrawTo (
 | 
			
		||||
			canvas.Cut(element.core, verticalBounds),
 | 
			
		||||
			verticalBounds)
 | 
			
		||||
	}
 | 
			
		||||
	element.draw()
 | 
			
		||||
}
 | 
			
		||||
@ -270,7 +274,6 @@ func (element *ScrollContainer) layout () (
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (element *ScrollContainer) draw () {
 | 
			
		||||
	// XOR
 | 
			
		||||
	if element.horizontal != nil && element.vertical != nil {
 | 
			
		||||
		bounds := element.Bounds()
 | 
			
		||||
		bounds.Min = image.Pt (
 | 
			
		||||
 | 
			
		||||
@ -4,7 +4,6 @@ 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/artist"
 | 
			
		||||
import "git.tebibyte.media/sashakoshka/tomo/elements/core"
 | 
			
		||||
 | 
			
		||||
// 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(),
 | 
			
		||||
		Pressed:  element.dragging,
 | 
			
		||||
	}
 | 
			
		||||
	artist.DrawBounds (
 | 
			
		||||
	element.theme.Pattern(theme.PatternGutter, state).Draw (
 | 
			
		||||
		element.core,
 | 
			
		||||
		element.theme.Pattern(theme.PatternGutter, state),
 | 
			
		||||
		bounds)
 | 
			
		||||
	artist.DrawBounds (
 | 
			
		||||
	element.theme.Pattern(theme.PatternHandle, state).Draw (
 | 
			
		||||
		element.core,
 | 
			
		||||
		element.theme.Pattern(theme.PatternHandle, state),
 | 
			
		||||
		element.bar)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -4,7 +4,6 @@ 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/artist"
 | 
			
		||||
import "git.tebibyte.media/sashakoshka/tomo/textdraw"
 | 
			
		||||
import "git.tebibyte.media/sashakoshka/tomo/elements/core"
 | 
			
		||||
 | 
			
		||||
@ -185,11 +184,11 @@ func (element *Switch) draw () {
 | 
			
		||||
 | 
			
		||||
	gutterPattern := element.theme.Pattern (
 | 
			
		||||
		theme.PatternGutter, state)
 | 
			
		||||
	artist.DrawBounds(element.core, gutterPattern, gutterBounds)
 | 
			
		||||
	gutterPattern.Draw(element.core, gutterBounds)
 | 
			
		||||
	
 | 
			
		||||
	handlePattern := element.theme.Pattern (
 | 
			
		||||
		theme.PatternHandle, state)
 | 
			
		||||
	artist.DrawBounds(element.core, handlePattern, handleBounds)
 | 
			
		||||
	handlePattern.Draw(element.core, handleBounds)
 | 
			
		||||
 | 
			
		||||
	textBounds := element.drawer.LayoutBounds()
 | 
			
		||||
	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) {
 | 
			
		||||
	if !element.Enabled() { return }
 | 
			
		||||
	if !element.Focused() { element.Focus() }
 | 
			
		||||
 | 
			
		||||
	if element.dragging {
 | 
			
		||||
		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.
 | 
			
		||||
type Core struct {
 | 
			
		||||
	canvas canvas.Canvas
 | 
			
		||||
	bounds image.Rectangle
 | 
			
		||||
 | 
			
		||||
	metrics struct {
 | 
			
		||||
		minimumWidth  int
 | 
			
		||||
@ -37,7 +38,7 @@ func NewCore (
 | 
			
		||||
// overridden.
 | 
			
		||||
func (core *Core) Bounds () (bounds image.Rectangle) {
 | 
			
		||||
	if core.canvas == nil { return }
 | 
			
		||||
	return core.canvas.Bounds()
 | 
			
		||||
	return core.bounds
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 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
 | 
			
		||||
// overridden.
 | 
			
		||||
func (core *Core) DrawTo (canvas canvas.Canvas) {
 | 
			
		||||
func (core *Core) DrawTo (canvas canvas.Canvas, bounds image.Rectangle) {
 | 
			
		||||
	core.canvas = canvas
 | 
			
		||||
	core.bounds = bounds
 | 
			
		||||
	if core.drawSizeChange != nil && core.canvas != nil {
 | 
			
		||||
		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 {
 | 
			
		||||
	for index := 0; index < propagator.parent.CountChildren(); index ++ {
 | 
			
		||||
		child, focusable := propagator.parent.Child(index).(elements.Focusable)
 | 
			
		||||
 | 
			
		||||
@ -1,5 +1,6 @@
 | 
			
		||||
package core
 | 
			
		||||
 | 
			
		||||
// import "runtime/debug"
 | 
			
		||||
import "git.tebibyte.media/sashakoshka/tomo/input"
 | 
			
		||||
 | 
			
		||||
// 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.
 | 
			
		||||
func (core *FocusableCore) HandleUnfocus () {
 | 
			
		||||
	core.focused = false
 | 
			
		||||
	// debug.PrintStack()
 | 
			
		||||
	if core.drawFocusChange != nil { core.drawFocusChange() }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -9,13 +9,14 @@ import "git.tebibyte.media/sashakoshka/tomo/config"
 | 
			
		||||
// Element represents a basic on-screen object.
 | 
			
		||||
type Element interface {
 | 
			
		||||
	// 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)
 | 
			
		||||
 | 
			
		||||
	// DrawTo sets this element's canvas. This should only be called by the
 | 
			
		||||
	// parent element. This is typically a region of the parent element's
 | 
			
		||||
	// canvas.
 | 
			
		||||
	DrawTo (canvas canvas.Canvas)
 | 
			
		||||
	// DrawTo gives the element a canvas to draw on, along with a bounding
 | 
			
		||||
	// box to be used for laying out the element. This should only be called
 | 
			
		||||
	// by the parent element. This is typically a region of the parent
 | 
			
		||||
	// element's canvas.
 | 
			
		||||
	DrawTo (canvas canvas.Canvas, bounds image.Rectangle)
 | 
			
		||||
 | 
			
		||||
	// OnDamage sets a function to be called when an area of the element is
 | 
			
		||||
	// drawn on and should be pushed to the screen.
 | 
			
		||||
 | 
			
		||||
@ -305,7 +305,7 @@ func (element *Piano) draw () {
 | 
			
		||||
	
 | 
			
		||||
	pattern := element.theme.Pattern(theme.PatternPinboard, state)
 | 
			
		||||
	artist.DrawShatter (
 | 
			
		||||
		element.core, pattern, element.contentBounds)
 | 
			
		||||
		element.core, pattern, element.Bounds(), element.contentBounds)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (element *Piano) drawFlat (
 | 
			
		||||
@ -316,7 +316,7 @@ func (element *Piano) drawFlat (
 | 
			
		||||
	state.Pressed = pressed
 | 
			
		||||
	pattern := element.theme.Theme.Pattern (
 | 
			
		||||
		theme.PatternButton, state, theme.C("fun", "flatKey"))
 | 
			
		||||
	artist.DrawBounds(element.core, pattern, bounds)
 | 
			
		||||
	pattern.Draw(element.core, bounds)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (element *Piano) drawSharp (
 | 
			
		||||
@ -327,5 +327,5 @@ func (element *Piano) drawSharp (
 | 
			
		||||
	state.Pressed = pressed
 | 
			
		||||
	pattern := element.theme.Theme.Pattern (
 | 
			
		||||
		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...)
 | 
			
		||||
	for index, tile := range tiles {
 | 
			
		||||
		artist.DrawBounds (
 | 
			
		||||
			element.core,
 | 
			
		||||
			[]artist.Pattern {
 | 
			
		||||
				patterns.Uhex(0xFF0000FF),
 | 
			
		||||
				patterns.Uhex(0x00FF00FF),
 | 
			
		||||
				patterns.Uhex(0xFF00FFFF),
 | 
			
		||||
				patterns.Uhex(0xFFFF00FF),
 | 
			
		||||
				patterns.Uhex(0x00FFFFFF),
 | 
			
		||||
			} [index % 5], tile)
 | 
			
		||||
		[]artist.Pattern {
 | 
			
		||||
			patterns.Uhex(0xFF0000FF),
 | 
			
		||||
			patterns.Uhex(0x00FF00FF),
 | 
			
		||||
			patterns.Uhex(0xFF00FFFF),
 | 
			
		||||
			patterns.Uhex(0xFFFF00FF),
 | 
			
		||||
			patterns.Uhex(0x00FFFFFF),
 | 
			
		||||
		} [index % 5].Draw(element.core, tile)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// 0, 2
 | 
			
		||||
	c02 := element.cellAt(0, 2)
 | 
			
		||||
	shapes.StrokeColorRectangle(c02, artist.Hex(0x888888FF), c02.Bounds(), 1)
 | 
			
		||||
	shapes.FillEllipse(c02, c41)
 | 
			
		||||
	shapes.FillEllipse(c02, c41, c02.Bounds())
 | 
			
		||||
 | 
			
		||||
	// 1, 2
 | 
			
		||||
	c12 := element.cellAt(1, 2)
 | 
			
		||||
	shapes.StrokeColorRectangle(c12, artist.Hex(0x888888FF), c12.Bounds(), 1)
 | 
			
		||||
	shapes.StrokeEllipse(c12, c41, 5)
 | 
			
		||||
	shapes.StrokeEllipse(c12, c41, c12.Bounds(), 5)
 | 
			
		||||
	
 | 
			
		||||
	// 2, 2
 | 
			
		||||
	c22 := element.cellAt(2, 2)
 | 
			
		||||
	shapes.FillRectangle(c22, c41)
 | 
			
		||||
	shapes.FillRectangle(c22, c41, c22.Bounds())
 | 
			
		||||
 | 
			
		||||
	// 3, 2
 | 
			
		||||
	c32 := element.cellAt(3, 2)
 | 
			
		||||
	shapes.StrokeRectangle(c32, c41, 5)
 | 
			
		||||
	shapes.StrokeRectangle(c32, c41, c32.Bounds(), 5)
 | 
			
		||||
	
 | 
			
		||||
	// 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
 | 
			
		||||
 | 
			
		||||
import "time"
 | 
			
		||||
import "image"
 | 
			
		||||
import "git.tebibyte.media/sashakoshka/tomo"
 | 
			
		||||
import "git.tebibyte.media/sashakoshka/tomo/canvas"
 | 
			
		||||
 | 
			
		||||
@ -30,14 +31,17 @@ func NewGame (world World, textures Textures) (game *Game) {
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (game *Game) DrawTo (canvas canvas.Canvas) {
 | 
			
		||||
func (game *Game) DrawTo (canvas canvas.Canvas, bounds image.Rectangle) {
 | 
			
		||||
	if canvas == nil {
 | 
			
		||||
		game.stopChan <- true
 | 
			
		||||
		select {
 | 
			
		||||
		case game.stopChan <- true:
 | 
			
		||||
		default:
 | 
			
		||||
		}
 | 
			
		||||
	} else if !game.running {
 | 
			
		||||
		game.running = true
 | 
			
		||||
		go game.run()
 | 
			
		||||
	}
 | 
			
		||||
	game.Raycaster.DrawTo(canvas)
 | 
			
		||||
	game.Raycaster.DrawTo(canvas, bounds)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (game *Game) Stamina () float64 {
 | 
			
		||||
 | 
			
		||||
@ -3,7 +3,6 @@ package basicLayouts
 | 
			
		||||
import "image"
 | 
			
		||||
import "git.tebibyte.media/sashakoshka/tomo/artist"
 | 
			
		||||
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
 | 
			
		||||
// positioned above as the main focus of the dialog, and is set to expand
 | 
			
		||||
@ -132,45 +131,6 @@ func (layout Dialog) MinimumSize (
 | 
			
		||||
	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 (
 | 
			
		||||
	entries []layouts.LayoutEntry,
 | 
			
		||||
	margin  image.Point,
 | 
			
		||||
 | 
			
		||||
@ -3,7 +3,6 @@ package basicLayouts
 | 
			
		||||
import "image"
 | 
			
		||||
import "git.tebibyte.media/sashakoshka/tomo/artist"
 | 
			
		||||
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
 | 
			
		||||
// list will be positioned on the left, and elements at the end of the entry
 | 
			
		||||
@ -76,49 +75,6 @@ func (layout Horizontal) MinimumSize (
 | 
			
		||||
	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 (
 | 
			
		||||
	entries []layouts.LayoutEntry,
 | 
			
		||||
	margin  image.Point,
 | 
			
		||||
 | 
			
		||||
@ -3,7 +3,6 @@ package basicLayouts
 | 
			
		||||
import "image"
 | 
			
		||||
import "git.tebibyte.media/sashakoshka/tomo/artist"
 | 
			
		||||
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
 | 
			
		||||
// 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))
 | 
			
		||||
	expandingElements := 0
 | 
			
		||||
	for index, entry := range entries {
 | 
			
		||||
		var entryMinHeight int
 | 
			
		||||
 | 
			
		||||
		if child, flexible := entry.Element.(elements.Flexible); flexible {
 | 
			
		||||
			entryMinHeight = child.FlexibleHeightFor(bounds.Dx())
 | 
			
		||||
		} else {
 | 
			
		||||
			_, entryMinHeight = entry.MinimumSize()
 | 
			
		||||
		}
 | 
			
		||||
		_, entryMinHeight := entry.MinimumSize()
 | 
			
		||||
		minimumHeights[index] = entryMinHeight
 | 
			
		||||
		
 | 
			
		||||
		if entry.Expand {
 | 
			
		||||
@ -101,34 +94,3 @@ func (layout Vertical) MinimumSize (
 | 
			
		||||
	}
 | 
			
		||||
	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
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 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
 | 
			
		||||
// to determine the minimum amount of room it needs to do so.
 | 
			
		||||
type Layout interface {
 | 
			
		||||
@ -39,16 +35,4 @@ type Layout interface {
 | 
			
		||||
	) (
 | 
			
		||||
		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()
 | 
			
		||||
	remaining := setter.text
 | 
			
		||||
	y         := fixed.Int26_6(0)
 | 
			
		||||
	maxY      := fixed.I(setter.maxHeight) + metrics.Height
 | 
			
		||||
	for len(remaining) > 0 && (y < maxY || setter.maxHeight == 0) {
 | 
			
		||||
	for len(remaining) > 0 {
 | 
			
		||||
		// process one line
 | 
			
		||||
		line, remainingFromLine := DoLine (
 | 
			
		||||
			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