package x import "image" import "image/color" import "golang.org/x/image/font" import "git.tebibyte.media/tomo/tomo" import "git.tebibyte.media/tomo/typeset" import "git.tebibyte.media/tomo/tomo/event" import "git.tebibyte.media/tomo/tomo/canvas" type textBox struct { *box hOverflow, vOverflow bool contentBounds image.Rectangle scroll image.Point text string textColor color.Color face font.Face wrap bool hAlign tomo.Align drawer typeset.Drawer on struct { contentBoundsChange event.FuncBroadcaster } } func (backend *Backend) NewTextBox() tomo.TextBox { box := &textBox { box: backend.NewBox().(*box), textColor: color.Black, } box.box.drawer = box box.outer = box return box } func (this *textBox) SetOverflow (horizontal, vertical bool) { if this.hOverflow == horizontal && this.vOverflow == vertical { return } this.hOverflow = horizontal this.vOverflow = vertical this.invalidateLayout() } func (this *textBox) ContentBounds () image.Rectangle { return this.contentBounds } func (this *textBox) ScrollTo (point image.Point) { // TODO: constrain scroll this.scroll = point this.invalidateLayout() } func (this *textBox) OnContentBoundsChange (callback func()) event.Cookie { return this.on.contentBoundsChange.Connect(callback) } func (this *textBox) SetText (text string) { if this.text == text { return } this.text = text this.drawer.SetText([]rune(text)) this.recalculateMinimumSize() this.invalidateLayout() } func (this *textBox) SetTextColor (c color.Color) { if this.textColor == c { return } this.textColor = c this.invalidateDraw() } func (this *textBox) SetFace (face font.Face) { if this.face == face { return } this.face = face this.drawer.SetFace(face) this.recalculateMinimumSize() this.invalidateLayout() } func (this *textBox) SetWrap (wrap bool) { if this.wrap == wrap { return } this.wrap = wrap this.recalculateMinimumSize() this.invalidateLayout() } func (this *textBox) SetAlign (x, y tomo.Align) { if this.hAlign == x { return } this.hAlign = x switch x { case tomo.AlignStart: this.drawer.SetAlign(typeset.AlignLeft) case tomo.AlignMiddle: this.drawer.SetAlign(typeset.AlignCenter) case tomo.AlignEnd: this.drawer.SetAlign(typeset.AlignRight) case tomo.AlignEven: this.drawer.SetAlign(typeset.AlignJustify) } this.invalidateDraw() } func (this *textBox) Draw (can canvas.Canvas) { if can == nil { return } this.drawBorders(can) pen := can.Pen() pen.Fill(this.color) pen.Rectangle(can.Bounds()) if this.face == nil { return } offset := this.InnerBounds().Min. Sub(this.scroll). Sub(this.drawer.LayoutBoundsSpace().Min) this.drawer.Draw(can, this.textColor, offset) } func (this *textBox) normalizedLayoutBoundsSpace () image.Rectangle { bounds := this.drawer.LayoutBoundsSpace() return bounds.Sub(bounds.Min) } func (this *textBox) recalculateMinimumSize () { minimum := image.Pt ( this.drawer.Em().Round(), this.drawer.LineHeight().Round()) textSize := this.normalizedLayoutBoundsSpace() if !this.hOverflow && !this.wrap { minimum.X = textSize.Dx() } if !this.vOverflow { minimum.Y = textSize.Dy() } minimum.X += this.padding.Horizontal() minimum.Y += this.padding.Vertical() borderSum := this.borderSum() minimum.X += borderSum.Horizontal() minimum.Y += borderSum.Vertical() this.SetMinimumSize(minimum) } func (this *textBox) doLayout () { this.box.doLayout() previousContentBounds := this.contentBounds innerBounds := this.InnerBounds() if this.hOverflow && !this.wrap { this.drawer.SetMaxWidth(0) } else { this.drawer.SetMaxWidth(innerBounds.Dx()) } if this.vOverflow { this.drawer.SetMaxHeight(0) } else { this.drawer.SetMaxHeight(innerBounds.Dy()) } this.contentBounds = this.normalizedLayoutBoundsSpace().Sub(this.scroll) if previousContentBounds != this.contentBounds { this.on.contentBoundsChange.Broadcast() } }