839 lines
23 KiB
Go
839 lines
23 KiB
Go
// 13 december 2015
|
|
|
|
package ui
|
|
|
|
// #include <stdlib.h>
|
|
// #include "ui.h"
|
|
// static uiDrawBrush *newBrush(void)
|
|
// {
|
|
// uiDrawBrush *b;
|
|
//
|
|
// b = (uiDrawBrush *) malloc(sizeof (uiDrawBrush));
|
|
// // TODO
|
|
// return b;
|
|
// }
|
|
// static uiDrawBrushGradientStop *newStops(size_t n)
|
|
// {
|
|
// uiDrawBrushGradientStop *stops;
|
|
//
|
|
// stops = (uiDrawBrushGradientStop *) malloc(n * sizeof (uiDrawBrushGradientStop));
|
|
// // TODO
|
|
// return stops;
|
|
// }
|
|
// static void setStop(uiDrawBrushGradientStop *stops, size_t i, double pos, double r, double g, double b, double a)
|
|
// {
|
|
// stops[i].Pos = pos;
|
|
// stops[i].R = r;
|
|
// stops[i].G = g;
|
|
// stops[i].B = b;
|
|
// stops[i].A = a;
|
|
// }
|
|
// static void freeBrush(uiDrawBrush *b)
|
|
// {
|
|
// if (b->Type == uiDrawBrushTypeLinearGradient || b->Type == uiDrawBrushTypeRadialGradient)
|
|
// free(b->Stops);
|
|
// free(b);
|
|
// }
|
|
// static uiDrawStrokeParams *newStrokeParams(void)
|
|
// {
|
|
// uiDrawStrokeParams *b;
|
|
//
|
|
// b = (uiDrawStrokeParams *) malloc(sizeof (uiDrawStrokeParams));
|
|
// // TODO
|
|
// return b;
|
|
// }
|
|
// static double *newDashes(size_t n)
|
|
// {
|
|
// double *dashes;
|
|
//
|
|
// dashes = (double *) malloc(n * sizeof (double));
|
|
// // TODO
|
|
// return dashes;
|
|
// }
|
|
// static void setDash(double *dashes, size_t i, double dash)
|
|
// {
|
|
// dashes[i] = dash;
|
|
// }
|
|
// static void freeStrokeParams(uiDrawStrokeParams *sp)
|
|
// {
|
|
// if (sp->Dashes != NULL)
|
|
// free(sp->Dashes);
|
|
// free(sp);
|
|
// }
|
|
// static uiDrawMatrix *newMatrix(void)
|
|
// {
|
|
// uiDrawMatrix *m;
|
|
//
|
|
// m = (uiDrawMatrix *) malloc(sizeof (uiDrawMatrix));
|
|
// // TODO
|
|
// return m;
|
|
// }
|
|
// static void freeMatrix(uiDrawMatrix *m)
|
|
// {
|
|
// free(m);
|
|
// }
|
|
// static uiDrawTextFontDescriptor *newFontDescriptor(void)
|
|
// {
|
|
// uiDrawTextFontDescriptor *desc;
|
|
//
|
|
// desc = (uiDrawTextFontDescriptor *) malloc(sizeof (uiDrawTextFontDescriptor));
|
|
// // TODO
|
|
// return desc;
|
|
// }
|
|
// static uiDrawTextFont *newFont(uiDrawTextFontDescriptor *desc)
|
|
// {
|
|
// uiDrawTextFont *font;
|
|
//
|
|
// font = uiDrawLoadClosestFont(desc);
|
|
// free((char *) (desc->Family));
|
|
// free(desc);
|
|
// return font;
|
|
// }
|
|
// static uiDrawTextLayout *newTextLayout(char *text, uiDrawTextFont *defaultFont, double width)
|
|
// {
|
|
// uiDrawTextLayout *layout;
|
|
//
|
|
// layout = uiDrawNewTextLayout(text, defaultFont, width);
|
|
// free(text);
|
|
// return layout;
|
|
// }
|
|
// static uiDrawTextFontMetrics *newFontMetrics(void)
|
|
// {
|
|
// uiDrawTextFontMetrics *m;
|
|
//
|
|
// m = (uiDrawTextFontMetrics *) malloc(sizeof (uiDrawTextFontMetrics));
|
|
// // TODO
|
|
// return m;
|
|
// }
|
|
// static void freeFontMetrics(uiDrawTextFontMetrics *m)
|
|
// {
|
|
// free(m);
|
|
// }
|
|
// static double *newDouble(void)
|
|
// {
|
|
// double *d;
|
|
//
|
|
// d = (double *) malloc(sizeof (double));
|
|
// // TODO
|
|
// return d;
|
|
// }
|
|
// static void freeDoubles(double *a, double *b)
|
|
// {
|
|
// free(a);
|
|
// free(b);
|
|
// }
|
|
import "C"
|
|
|
|
// BUG(andlabs): Ideally, all the drawing APIs should be in another package ui/draw (they all have the "uiDraw" prefix in C to achieve a similar goal of avoiding confusing programmers via namespace pollution); managing the linkage of the libui shared library itself across multiple packages is likely going to be a pain, though. (Custom controls implemented using libui won't have this issue, as they *should* only need libui present when linking the shared object, not when linking the Go wrapper. I'm not sure; I'd have to find out first.)
|
|
|
|
// Path represents a geometric path in a drawing context.
|
|
// This is the basic unit of drawing: all drawing operations consist of
|
|
// forming a path, then stroking, filling, or clipping to that path.
|
|
// A path is an OS resource; you must explicitly free it when finished.
|
|
// Paths consist of multiple figures. Once you have added all the
|
|
// figures to a path, you must "end" the path to make it ready to draw
|
|
// with.
|
|
// TODO rewrite all that
|
|
//
|
|
// Or more visually, the lifecycle of a Path is
|
|
// p := NewPath()
|
|
// for every figure {
|
|
// p.NewFigure(...) // or NewFigureWithArc
|
|
// p.LineTo(...) // any number of these in any order
|
|
// p.ArcTo(...)
|
|
// p.BezierTo(...)
|
|
// if figure should be closed {
|
|
// p.CloseFigure()
|
|
// }
|
|
// }
|
|
// p.End()
|
|
// // ...
|
|
// dp.Context.Stroke(p, ...) // any number of these in any order
|
|
// dp.Context.Fill(p, ...)
|
|
// dp.Context.Clip(p)
|
|
// // ...
|
|
// p.Free() // when done with the path
|
|
//
|
|
// A Path also defines its fill mode. (This should ideally be a fill
|
|
// parameter, but some implementations prevent it.)
|
|
// TODO talk about fill modes
|
|
type Path struct {
|
|
p *C.uiDrawPath
|
|
}
|
|
|
|
// TODO
|
|
//
|
|
// TODO disclaimer
|
|
type FillMode uint
|
|
const (
|
|
Winding FillMode = iota
|
|
Alternate
|
|
)
|
|
|
|
// NewPath creates a new Path with the given fill mode.
|
|
func NewPath(fillMode FillMode) *Path {
|
|
var fm C.uiDrawFillMode
|
|
|
|
switch fillMode {
|
|
case Winding:
|
|
fm = C.uiDrawFillModeWinding
|
|
case Alternate:
|
|
fm = C.uiDrawFillModeAlternate
|
|
default:
|
|
panic("invalid fill mode passed to ui.NewPath()")
|
|
}
|
|
return &Path{
|
|
p: C.uiDrawNewPath(fm),
|
|
}
|
|
}
|
|
|
|
// Free destroys a Path. After calling Free the Path cannot be used.
|
|
func (p *Path) Free() {
|
|
C.uiDrawFreePath(p.p)
|
|
}
|
|
|
|
// NewFigure starts a new figure in the Path. The current point
|
|
// is set to the given point.
|
|
func (p *Path) NewFigure(x float64, y float64) {
|
|
C.uiDrawPathNewFigure(p.p, C.double(x), C.double(y))
|
|
}
|
|
|
|
// NewFigureWithArc starts a new figure in the Path and adds an arc
|
|
// as the first element of the figure. Unlike ArcTo, NewFigureWithArc
|
|
// does not draw an initial line segment. Otherwise, see ArcTo.
|
|
func (p *Path) NewFigureWithArc(xCenter float64, yCenter float64, radius float64, startAngle float64, sweep float64, isNegative bool) {
|
|
C.uiDrawPathNewFigureWithArc(p.p,
|
|
C.double(xCenter), C.double(yCenter),
|
|
C.double(radius),
|
|
C.double(startAngle), C.double(sweep),
|
|
frombool(isNegative))
|
|
}
|
|
|
|
// LineTo adds a line to the current figure of the Path starting from
|
|
// the current point and ending at the given point. The current point
|
|
// is set to the ending point.
|
|
func (p *Path) LineTo(x float64, y float64) {
|
|
C.uiDrawPathLineTo(p.p, C.double(x), C.double(y))
|
|
}
|
|
|
|
// ArcTo adds a circular arc to the current figure of the Path.
|
|
// You pass it the center of the arc, its radius in radians, the starting
|
|
// angle (couterclockwise) in radians, and the number of radians the
|
|
// arc should sweep (counterclockwise). A line segment is drawn from
|
|
// the current point to the start of the arc. The current point is set to
|
|
// the end of the arc.
|
|
func (p *Path) ArcTo(xCenter float64, yCenter float64, radius float64, startAngle float64, sweep float64, isNegative bool) {
|
|
C.uiDrawPathArcTo(p.p,
|
|
C.double(xCenter), C.double(yCenter),
|
|
C.double(radius),
|
|
C.double(startAngle), C.double(sweep),
|
|
frombool(isNegative))
|
|
}
|
|
|
|
// BezierTo adds a cubic Bezier curve to the current figure of the Path.
|
|
// Its start point is the current point. c1x and c1y are the first control
|
|
// point. c2x and c2y are the second control point. endX and endY
|
|
// are the end point. The current point is set to the end point.
|
|
func (p *Path) BezierTo(c1x float64, c1y float64, c2x float64, c2y float64, endX float64, endY float64) {
|
|
C.uiDrawPathBezierTo(p.p,
|
|
C.double(c1x), C.double(c1y),
|
|
C.double(c2x), C.double(c2y),
|
|
C.double(endX), C.double(endY))
|
|
}
|
|
|
|
// CloseFigure draws a line segment from the current point of the
|
|
// current figure of the Path back to its initial point. After calling this,
|
|
// the current figure is over and you must either start a new figure
|
|
// or end the Path. If this is not called and you start a new figure or
|
|
// end the Path, then the current figure will not have this closing line
|
|
// segment added to it (but the figure will still be over).
|
|
func (p *Path) CloseFigure() {
|
|
C.uiDrawPathCloseFigure(p.p)
|
|
}
|
|
|
|
// AddRectangle creates a new figure in the Path that consists entirely
|
|
// of a rectangle whose top-left corner is at the given point and whose
|
|
// size is the given size. The rectangle is a closed figure; you must
|
|
// either start a new figure or end the Path after calling this method.
|
|
func (p *Path) AddRectangle(x float64, y float64, width float64, height float64) {
|
|
C.uiDrawPathAddRectangle(p.p, C.double(x), C.double(y), C.double(width), C.double(height))
|
|
}
|
|
|
|
// End ends the current Path. You cannot add figures to a Path that has
|
|
// been ended. You cannot draw with a Path that has not been ended.
|
|
func (p *Path) End() {
|
|
C.uiDrawPathEnd(p.p)
|
|
}
|
|
|
|
// DrawContext represents a drawing surface that you can draw to.
|
|
// At present the only DrawContexts are surfaces associated with
|
|
// Areas and are provided by package ui; see AreaDrawParams.
|
|
type DrawContext struct {
|
|
c *C.uiDrawContext
|
|
}
|
|
|
|
// BrushType defines the various types of brushes.
|
|
//
|
|
// TODO disclaimer
|
|
type BrushType int
|
|
const (
|
|
Solid BrushType = iota
|
|
LinearGradient
|
|
RadialGradient
|
|
Image // presently unimplemented
|
|
)
|
|
|
|
// TODO
|
|
//
|
|
// TODO disclaimer
|
|
type LineCap int
|
|
const (
|
|
FlatCap LineCap = iota
|
|
RoundCap
|
|
SquareCap
|
|
)
|
|
|
|
// TODO
|
|
//
|
|
// TODO disclaimer
|
|
type LineJoin int
|
|
const (
|
|
MiterJoin LineJoin = iota
|
|
RoundJoin
|
|
BevelJoin
|
|
)
|
|
|
|
// TODO document
|
|
const DefaultMiterLimit = 10.0
|
|
|
|
// TODO
|
|
type Brush struct {
|
|
Type BrushType
|
|
|
|
// If Type is Solid.
|
|
// TODO
|
|
R float64
|
|
G float64
|
|
B float64
|
|
A float64
|
|
|
|
// If Type is LinearGradient or RadialGradient.
|
|
// TODO
|
|
X0 float64 // start point for both
|
|
Y0 float64
|
|
X1 float64 // linear: end point; radial: circle center
|
|
Y1 float64
|
|
OuterRadius float64 // for radial gradients only
|
|
Stops []GradientStop
|
|
}
|
|
|
|
// TODO
|
|
type GradientStop struct {
|
|
Pos float64 // between 0 and 1 inclusive
|
|
R float64
|
|
G float64
|
|
B float64
|
|
A float64
|
|
}
|
|
|
|
func (b *Brush) toC() *C.uiDrawBrush {
|
|
cb := C.newBrush()
|
|
cb.Type = C.uiDrawBrushType(b.Type)
|
|
switch b.Type {
|
|
case Solid:
|
|
cb.R = C.double(b.R)
|
|
cb.G = C.double(b.G)
|
|
cb.B = C.double(b.B)
|
|
cb.A = C.double(b.A)
|
|
case LinearGradient, RadialGradient:
|
|
cb.X0 = C.double(b.X0)
|
|
cb.Y0 = C.double(b.Y0)
|
|
cb.X1 = C.double(b.X1)
|
|
cb.Y1 = C.double(b.Y1)
|
|
cb.OuterRadius = C.double(b.OuterRadius)
|
|
cb.NumStops = C.size_t(len(b.Stops))
|
|
cb.Stops = C.newStops(cb.NumStops)
|
|
for i, s := range b.Stops {
|
|
C.setStop(cb.Stops, C.size_t(i),
|
|
C.double(s.Pos),
|
|
C.double(s.R),
|
|
C.double(s.G),
|
|
C.double(s.B),
|
|
C.double(s.A))
|
|
}
|
|
case Image:
|
|
panic("unimplemented")
|
|
default:
|
|
panic("invalid brush type in Brush.toC()")
|
|
}
|
|
return cb
|
|
}
|
|
|
|
// TODO
|
|
type StrokeParams struct {
|
|
Cap LineCap
|
|
Join LineJoin
|
|
Thickness float64
|
|
MiterLimit float64
|
|
Dashes []float64
|
|
DashPhase float64
|
|
}
|
|
|
|
func (sp *StrokeParams) toC() *C.uiDrawStrokeParams {
|
|
csp := C.newStrokeParams()
|
|
csp.Cap = C.uiDrawLineCap(sp.Cap)
|
|
csp.Join = C.uiDrawLineJoin(sp.Join)
|
|
csp.Thickness = C.double(sp.Thickness)
|
|
csp.MiterLimit = C.double(sp.MiterLimit)
|
|
csp.Dashes = nil
|
|
csp.NumDashes = C.size_t(len(sp.Dashes))
|
|
if csp.NumDashes != 0 {
|
|
csp.Dashes = C.newDashes(csp.NumDashes)
|
|
for i, d := range sp.Dashes {
|
|
C.setDash(csp.Dashes, C.size_t(i), C.double(d))
|
|
}
|
|
}
|
|
csp.DashPhase = C.double(sp.DashPhase)
|
|
return csp
|
|
}
|
|
|
|
// TODO
|
|
func (c *DrawContext) Stroke(p *Path, b *Brush, sp *StrokeParams) {
|
|
cb := b.toC()
|
|
csp := sp.toC()
|
|
C.uiDrawStroke(c.c, p.p, cb, csp)
|
|
C.freeBrush(cb)
|
|
C.freeStrokeParams(csp)
|
|
}
|
|
|
|
// TODO
|
|
func (c *DrawContext) Fill(p *Path, b *Brush) {
|
|
cb := b.toC()
|
|
C.uiDrawFill(c.c, p.p, cb)
|
|
C.freeBrush(cb)
|
|
}
|
|
|
|
// TODO
|
|
type Matrix struct {
|
|
M11 float64
|
|
M12 float64
|
|
M21 float64
|
|
M22 float64
|
|
M31 float64
|
|
M32 float64
|
|
}
|
|
|
|
// TODO identity matrix
|
|
func NewMatrix() *Matrix {
|
|
m := new(Matrix)
|
|
m.SetIdentity()
|
|
return m
|
|
}
|
|
|
|
// TODO
|
|
func (m *Matrix) SetIdentity() {
|
|
m.M11 = 1
|
|
m.M12 = 0
|
|
m.M21 = 0
|
|
m.M22 = 1
|
|
m.M31 = 0
|
|
m.M32 = 0
|
|
}
|
|
|
|
func (m *Matrix) toC() *C.uiDrawMatrix {
|
|
cm := C.newMatrix()
|
|
cm.M11 = C.double(m.M11)
|
|
cm.M12 = C.double(m.M12)
|
|
cm.M21 = C.double(m.M21)
|
|
cm.M22 = C.double(m.M22)
|
|
cm.M31 = C.double(m.M31)
|
|
cm.M32 = C.double(m.M32)
|
|
return cm
|
|
}
|
|
|
|
func (m *Matrix) fromC(cm *C.uiDrawMatrix) {
|
|
m.M11 = float64(cm.M11)
|
|
m.M12 = float64(cm.M12)
|
|
m.M21 = float64(cm.M21)
|
|
m.M22 = float64(cm.M22)
|
|
m.M31 = float64(cm.M31)
|
|
m.M32 = float64(cm.M32)
|
|
C.freeMatrix(cm)
|
|
}
|
|
|
|
// TODO
|
|
func (m *Matrix) Translate(x float64, y float64) {
|
|
cm := m.toC()
|
|
C.uiDrawMatrixTranslate(cm, C.double(x), C.double(y))
|
|
m.fromC(cm)
|
|
}
|
|
|
|
// TODO
|
|
func (m *Matrix) Scale(xCenter float64, yCenter float64, x float64, y float64) {
|
|
cm := m.toC()
|
|
C.uiDrawMatrixScale(cm,
|
|
C.double(xCenter), C.double(yCenter),
|
|
C.double(x), C.double(y))
|
|
m.fromC(cm)
|
|
}
|
|
|
|
// TODO
|
|
func (m *Matrix) Rotate(x float64, y float64, amount float64) {
|
|
cm := m.toC()
|
|
C.uiDrawMatrixRotate(cm, C.double(x), C.double(y), C.double(amount))
|
|
m.fromC(cm)
|
|
}
|
|
|
|
// TODO
|
|
func (m *Matrix) Skew(x float64, y float64, xamount float64, yamount float64) {
|
|
cm := m.toC()
|
|
C.uiDrawMatrixSkew(cm,
|
|
C.double(x), C.double(y),
|
|
C.double(xamount), C.double(yamount))
|
|
m.fromC(cm)
|
|
}
|
|
|
|
// TODO
|
|
func (m *Matrix) Multiply(m2 *Matrix) {
|
|
cm := m.toC()
|
|
cm2 := m2.toC()
|
|
C.uiDrawMatrixMultiply(cm, cm2)
|
|
C.freeMatrix(cm2)
|
|
m.fromC(cm)
|
|
}
|
|
|
|
// TODO
|
|
func (m *Matrix) Invertible() bool {
|
|
cm := m.toC()
|
|
res := C.uiDrawMatrixInvertible(cm)
|
|
C.freeMatrix(cm)
|
|
return tobool(res)
|
|
}
|
|
|
|
// TODO
|
|
//
|
|
// If m is not invertible, false is returned and m is left unchanged.
|
|
func (m *Matrix) Invert() bool {
|
|
cm := m.toC()
|
|
res := C.uiDrawMatrixInvert(cm)
|
|
m.fromC(cm)
|
|
return tobool(res)
|
|
}
|
|
|
|
// TODO unimplemented
|
|
func (m *Matrix) TransformPoint(x float64, y float64) (xout float64, yout float64) {
|
|
panic("TODO")
|
|
}
|
|
|
|
// TODO unimplemented
|
|
func (m *Matrix) TransformSize(x float64, y float64) (xout float64, yout float64) {
|
|
panic("TODO")
|
|
}
|
|
|
|
// TODO
|
|
func (c *DrawContext) Transform(m *Matrix) {
|
|
cm := m.toC()
|
|
C.uiDrawTransform(c.c, cm)
|
|
C.freeMatrix(cm)
|
|
}
|
|
|
|
// TODO
|
|
func (c *DrawContext) Clip(p *Path) {
|
|
C.uiDrawClip(c.c, p.p)
|
|
}
|
|
|
|
// TODO
|
|
func (c *DrawContext) Save() {
|
|
C.uiDrawSave(c.c)
|
|
}
|
|
|
|
// TODO
|
|
func (c *DrawContext) Restore() {
|
|
C.uiDrawRestore(c.c)
|
|
}
|
|
|
|
// FontFamilies represents an enumerator over the font families
|
|
// available for use by package ui. A FontFamilies object behaves
|
|
// similarly to a []string, except that since family names are loaded
|
|
// on demand (depending on the operating system), it is not an
|
|
// actual []string. You call ListFontFamilies to obtain a FontFamilies
|
|
// object, which should reflect the available fonts at the time of the
|
|
// call (TODO verify). Use NumFamilies to get the number of families,
|
|
// and Family to get the name of a given family by index. When
|
|
// finished, call Free.
|
|
//
|
|
// There is no guarantee that the list of families is sorted. You will
|
|
// need to do sorting yourself if you need it.
|
|
//
|
|
// TODO thread affinity
|
|
type FontFamilies struct {
|
|
ff *C.uiDrawFontFamilies
|
|
}
|
|
|
|
// ListFontFamilies creates a new FontFamilies object ready for use.
|
|
func ListFontFamilies() *FontFamilies {
|
|
return &FontFamilies{
|
|
ff: C.uiDrawListFontFamilies(),
|
|
}
|
|
}
|
|
|
|
// Free destroys a FontFamilies. After calling Free, the FontFamilies
|
|
// cannot be used.
|
|
func (f *FontFamilies) Free() {
|
|
C.uiDrawFreeFontFamilies(f.ff)
|
|
}
|
|
|
|
// NumFamilies returns the number of font families available.
|
|
func (f *FontFamilies) NumFamilies() int {
|
|
return int(C.uiDrawFontFamiliesNumFamilies(f.ff))
|
|
}
|
|
|
|
// Family returns the name of the nth family in the list.
|
|
func (f *FontFamilies) Family(n int) string {
|
|
cname := C.uiDrawFontFamiliesFamily(f.ff, C.uintmax_t(n))
|
|
name := C.GoString(cname)
|
|
C.uiFreeText(cname)
|
|
return name
|
|
}
|
|
|
|
// TextWeight defines the various text weights, in order of
|
|
// increasing weight.
|
|
//
|
|
// Note that if you leave this field unset, it will default to
|
|
// TextWeightThin. If you want the normal font weight, explicitly
|
|
// use the constant TextWeightNormal instead.
|
|
// TODO realign these?
|
|
//
|
|
// TODO disclaimer
|
|
type TextWeight int
|
|
const (
|
|
TextWeightThin TextWeight = iota
|
|
TextWeightUltraLight
|
|
TextWeightLight
|
|
TextWeightBook
|
|
TextWeightNormal
|
|
TextWeightMedium
|
|
TextWeightSemiBold
|
|
TextWeightBold
|
|
TextWeightUtraBold
|
|
TextWeightHeavy
|
|
TextWeightUltraHeavy
|
|
)
|
|
|
|
// TextItalic defines the various text italic modes.
|
|
//
|
|
// TODO disclaimer
|
|
type TextItalic int
|
|
const (
|
|
TextItalicNormal TextItalic = iota
|
|
TextItalicOblique // merely slanted text
|
|
TextItalicItalic // true italics
|
|
)
|
|
|
|
// TextStretch defines the various text stretches, in order of
|
|
// increasing wideness.
|
|
//
|
|
// Note that if you leave this field unset, it will default to
|
|
// TextStretchUltraCondensed. If you want the normal font
|
|
// stretch, explicitly use the constant TextStretchNormal
|
|
// instead.
|
|
// TODO realign these?
|
|
//
|
|
// TODO disclaimer
|
|
type TextStretch int
|
|
const (
|
|
TextStretchUltraCondensed TextStretch = iota
|
|
TextStretchExtraCondensed
|
|
TextStretchCondensed
|
|
TextStretchSemiCondensed
|
|
TextStretchNormal
|
|
TextStretchSemiExpanded
|
|
TextStretchExpanded
|
|
TextStretchExtraExpanded
|
|
TextStretchUltraExpanded
|
|
)
|
|
|
|
// TODO put TextGravity here
|
|
|
|
// FontDescriptor describes a Font.
|
|
type FontDescriptor struct {
|
|
Family string
|
|
Size float64 // as a text size, for instance 12 for a 12-point font
|
|
Weight TextWeight
|
|
Italic TextItalic
|
|
SmallCaps bool
|
|
Stretch TextStretch
|
|
// TODO gravity
|
|
}
|
|
|
|
// Font represents an actual font that can be drawn with.
|
|
type Font struct {
|
|
f *C.uiDrawTextFont
|
|
}
|
|
|
|
// LoadClosestFont loads a Font.
|
|
//
|
|
// You pass the properties of the ideal font you want to load in the
|
|
// FontDescriptor you pass to this function. If the requested font
|
|
// is not available on the system, the closest matching font is used.
|
|
// This means that, for instance, if you specify a Weight of
|
|
// TextWeightUltraHeavy and the heaviest weight available for the
|
|
// chosen font family is actually TextWeightBold, that will be used
|
|
// instead. The specific details of font matching beyond this
|
|
// description are implementation defined. This also means that
|
|
// getting a descriptor back out of a Font may return a different
|
|
// desriptor.
|
|
//
|
|
// TODO guarantee that passing *that* back into LoadClosestFont() returns the same font
|
|
func LoadClosestFont(desc *FontDescriptor) *Font {
|
|
d := C.newFontDescriptor() // both of these are freed by C.newFont()
|
|
d.Family = C.CString(desc.Family)
|
|
d.Size = C.double(desc.Size)
|
|
d.Weight = C.uiDrawTextWeight(desc.Weight)
|
|
d.Italic = C.uiDrawTextItalic(desc.Italic)
|
|
d.SmallCaps = frombool(desc.SmallCaps)
|
|
d.Stretch = C.uiDrawTextStretch(desc.Stretch)
|
|
// d.Gravity = C.uiDrawTextGravity(desc.Gravity)
|
|
d.Gravity = C.uiDrawTextGravitySouth
|
|
return &Font{
|
|
f: C.newFont(d),
|
|
}
|
|
}
|
|
|
|
// Free destroys a Font. After calling Free the Font cannot be used.
|
|
func (f *Font) Free() {
|
|
C.uiDrawFreeTextFont(f.f)
|
|
}
|
|
|
|
// Handle returns the OS font object that backs this Font. On OSs
|
|
// that use reference counting for font objects, Handle does not
|
|
// increment the reference count; you are sharing package ui's
|
|
// reference.
|
|
//
|
|
// On Windows this is a pointer to an IDWriteFont.
|
|
//
|
|
// On Unix systems this is a pointer to a PangoFont.
|
|
//
|
|
// On OS X this is a CTFontRef.
|
|
func (f *Font) Handle() uintptr {
|
|
return uintptr(C.uiDrawTextFontHandle(f.f))
|
|
}
|
|
|
|
// Describe returns the FontDescriptor that most closely matches
|
|
// this Font.
|
|
// TODO guarantees about idempotency
|
|
// TODO rewrite that first sentence
|
|
func (f *Font) Describe() *FontDescriptor {
|
|
panic("TODO unimplemented")
|
|
}
|
|
|
|
// FontMetrics holds various measurements about a Font.
|
|
// All metrics are in the same point units used for drawing.
|
|
type FontMetrics struct {
|
|
// Ascent is the ascent of the font; that is, the distance from
|
|
// the top of the character cell to the baseline.
|
|
Ascent float64
|
|
|
|
// Descent is the descent of the font; that is, the distance from
|
|
// the baseline to the bottom of the character cell. The sum of
|
|
// Ascent and Descent is the height of the character cell (and
|
|
// thus, the maximum height of a line of text).
|
|
Descent float64
|
|
|
|
// Leading is the amount of space the font designer suggests
|
|
// to have between lines (between the bottom of the first line's
|
|
// character cell and the top of the second line's character cell).
|
|
// This is a suggestion; it is chosen by the font designer to
|
|
// improve legibility.
|
|
Leading float64
|
|
|
|
// TODO figure out what these are
|
|
UnderlinePos float64
|
|
UnderlineThickness float64
|
|
}
|
|
|
|
// Metrics returns metrics about the given Font.
|
|
func (f *Font) Metrics() *FontMetrics {
|
|
m := new(FontMetrics)
|
|
mm := C.newFontMetrics()
|
|
C.uiDrawTextFontGetMetrics(f.f, mm)
|
|
m.Ascent = float64(mm.Ascent)
|
|
m.Descent = float64(mm.Descent)
|
|
m.Leading = float64(mm.Leading)
|
|
m.UnderlinePos = float64(mm.UnderlinePos)
|
|
m.UnderlineThickness = float64(mm.UnderlineThickness)
|
|
C.freeFontMetrics(mm)
|
|
return m
|
|
}
|
|
|
|
// TextLayout is the entry point for formatting a block of text to be
|
|
// drawn onto a DrawContext.
|
|
//
|
|
// The block of text to lay out and the default font that is used if no
|
|
// font attributes are applied to a given character are provided
|
|
// at TextLayout creation time and cannot be changed later.
|
|
// However, you may add attributes to various points of the text
|
|
// at any time, even after drawing the text once (unlike a DrawPath).
|
|
// Some of these attributes also have initial values; refer to each
|
|
// method to see what they are.
|
|
//
|
|
// The block of text can either be a single line or multiple
|
|
// word-wrapped lines, each with a given maximum width.
|
|
type TextLayout struct {
|
|
l *C.uiDrawTextLayout
|
|
}
|
|
|
|
// NewTextLayout creates a new TextLayout.
|
|
// For details on the width parameter, see SetWidth.
|
|
func NewTextLayout(text string, defaultFont *Font, width float64) *TextLayout {
|
|
l := new(TextLayout)
|
|
ctext := C.CString(text) // freed by C.newTextLayout()
|
|
l.l = C.newTextLayout(ctext, defaultFont.f, C.double(width))
|
|
return l
|
|
}
|
|
|
|
// Free destroys a TextLayout. After calling Free the TextLayout
|
|
// cannot be used.
|
|
func (l *TextLayout) Free() {
|
|
C.uiDrawFreeTextLayout(l.l)
|
|
}
|
|
|
|
// SetWidth sets the maximum width of the lines of text in a
|
|
// TextLayout. If the given width is negative, then the TextLayout
|
|
// will draw as a single line of text instead.
|
|
func (l *TextLayout) SetWidth(width float64) {
|
|
C.uiDrawTextLayoutSetWidth(l.l, C.double(width))
|
|
}
|
|
|
|
// Extents returns the width and height that the TextLayout will
|
|
// actually take up when drawn. This measures full line allocations,
|
|
// even if no glyph reaches to the top of its ascent or bottom of its
|
|
// descent; it does not return a "best fit" rectnagle for the points that
|
|
// are actually drawn.
|
|
//
|
|
// For a single-line TextLayout (where the width is negative), if there
|
|
// are no font changes throughout the TextLayout, then the height
|
|
// returned by TextLayout is equivalent to the sum of the ascent and
|
|
// descent of its default font's metrics. Or in other words, after
|
|
// f := ui.LoadClosestFont(...)
|
|
// l := ui.NewTextLayout("text", f, -1)
|
|
// metrics := f.Metrics()
|
|
// _, height := l.Extents()
|
|
// metrics.Ascent+metrics.Descent and height are equivalent.
|
|
func (l *TextLayout) Extents() (width float64, height float64) {
|
|
cwidth := C.newDouble()
|
|
cheight := C.newDouble()
|
|
C.uiDrawTextLayoutExtents(l.l, cwidth, cheight)
|
|
width = float64(*cwidth)
|
|
height = float64(*cheight)
|
|
C.freeDoubles(cwidth, cheight)
|
|
return width, height
|
|
}
|
|
|
|
// Text draws the given TextLayout onto c at the given point.
|
|
// The point refers to the top-left corner of the text.
|
|
// (TODO bounding box or typographical extent?)
|
|
func (c *DrawContext) Text(x float64, y float64, layout *TextLayout) {
|
|
C.uiDrawText(c.c, C.double(x), C.double(y), layout.l)
|
|
}
|