flexible-elements-were-a-mistake #11
@ -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