| // Copyright 2016 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| package widget |
| |
| import ( |
| "image" |
| "image/draw" |
| |
| "golang.org/x/exp/shiny/text" |
| "golang.org/x/exp/shiny/unit" |
| "golang.org/x/exp/shiny/widget/node" |
| "golang.org/x/exp/shiny/widget/theme" |
| "golang.org/x/image/font" |
| "golang.org/x/image/math/fixed" |
| ) |
| |
| // Text is a leaf widget that holds a text label. |
| type Text struct { |
| node.LeafEmbed |
| frame text.Frame |
| faceSet bool |
| |
| // TODO: scrolling, although should that be the responsibility of this |
| // widget, the parent widget or something else? |
| } |
| |
| // NewText returns a new Text widget. |
| func NewText(text string) *Text { |
| w := &Text{} |
| w.Wrapper = w |
| if text != "" { |
| c := w.frame.NewCaret() |
| c.WriteString(text) |
| c.Close() |
| } |
| return w |
| } |
| |
| func (w *Text) setFace(t *theme.Theme) { |
| // TODO: can a theme change at runtime, or can it be set only once, at |
| // start-up? |
| if !w.faceSet { |
| w.faceSet = true |
| // TODO: when is face released? Should we just unconditionally call |
| // SetFace for every Measure, Layout and Paint? How do we avoid |
| // excessive re-calculation of soft returns when re-using the same |
| // logical face (as in "Times New Roman 12pt") even if using different |
| // physical font.Face values (as each Face may have its own caches)? |
| face := t.AcquireFontFace(theme.FontFaceOptions{}) |
| w.frame.SetFace(face) |
| } |
| } |
| |
| // TODO: should padding (and/or margin and border) be a universal concept and |
| // part of the node.Embed type instead of having each widget implement its own? |
| |
| func (w *Text) padding(t *theme.Theme) int { |
| return t.Pixels(unit.Ems(0.5)).Ceil() |
| } |
| |
| func (w *Text) Measure(t *theme.Theme, widthHint, heightHint int) { |
| w.setFace(t) |
| padding := w.padding(t) |
| |
| if widthHint < 0 { |
| w.frame.SetMaxWidth(0) |
| w.MeasuredSize = image.Point{ |
| 0, // TODO: this isn't right. |
| w.frame.Height() + 2*padding, |
| } |
| return |
| } |
| |
| maxWidth := fixed.I(widthHint - 2*padding) |
| if maxWidth <= 1 { |
| maxWidth = 1 |
| } |
| w.frame.SetMaxWidth(maxWidth) |
| |
| w.MeasuredSize = image.Point{ |
| widthHint, |
| w.frame.Height() + 2*padding, |
| } |
| } |
| |
| func (w *Text) Layout(t *theme.Theme) { |
| w.setFace(t) |
| padding := w.padding(t) |
| maxWidth := fixed.I(w.Rect.Dx() - 2*padding) |
| if maxWidth <= 1 { |
| maxWidth = 1 |
| } |
| w.frame.SetMaxWidth(maxWidth) |
| } |
| |
| func (w *Text) PaintBase(ctx *node.PaintBaseContext, origin image.Point) error { |
| w.Marks.UnmarkNeedsPaintBase() |
| dst := ctx.Dst.SubImage(w.Rect.Add(origin)).(*image.RGBA) |
| if dst.Bounds().Empty() { |
| return nil |
| } |
| |
| face := ctx.Theme.AcquireFontFace(theme.FontFaceOptions{}) |
| defer ctx.Theme.ReleaseFontFace(theme.FontFaceOptions{}, face) |
| m := face.Metrics() |
| ascent := m.Ascent.Ceil() |
| descent := m.Descent.Ceil() |
| height := m.Height.Ceil() |
| |
| padding := w.padding(ctx.Theme) |
| |
| draw.Draw(dst, dst.Bounds(), ctx.Theme.GetPalette().Background(), image.Point{}, draw.Src) |
| |
| minDotY := fixed.I(dst.Bounds().Min.Y - descent) |
| maxDotY := fixed.I(dst.Bounds().Max.Y + ascent) |
| |
| x0 := fixed.I(origin.X + w.Rect.Min.X + padding) |
| d := font.Drawer{ |
| Dst: dst, |
| Src: ctx.Theme.GetPalette().Foreground(), |
| Face: face, |
| Dot: fixed.Point26_6{ |
| X: x0, |
| Y: fixed.I(origin.Y + w.Rect.Min.Y + padding + ascent), |
| }, |
| } |
| f := &w.frame |
| for p := f.FirstParagraph(); p != nil; p = p.Next(f) { |
| for l := p.FirstLine(f); l != nil; l = l.Next(f) { |
| if d.Dot.Y > minDotY { |
| if d.Dot.Y >= maxDotY { |
| return nil |
| } |
| for b := l.FirstBox(f); b != nil; b = b.Next(f) { |
| d.DrawBytes(b.TrimmedText(f)) |
| // TODO: adjust d.Dot.X for any ligatures? |
| } |
| d.Dot.X = x0 |
| } |
| d.Dot.Y += fixed.I(height) |
| } |
| } |
| return nil |
| } |
| |
| func (w *Text) Paint(ctx *node.PaintContext, origin image.Point) error { |
| // TODO: draw an optional border, whose color depends on whether w has the |
| // keyboard focus. |
| return w.LeafEmbed.Paint(ctx, origin) |
| } |