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
|
||||
// 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"
|
||||
@ -13,6 +14,9 @@ type Label struct {
|
||||
wrap bool
|
||||
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