diff --git a/layout/format.go b/layout/format.go deleted file mode 100644 index 99eb72a9..00000000 --- a/layout/format.go +++ /dev/null @@ -1,544 +0,0 @@ -// SPDX-License-Identifier: Unlicense OR MIT - -package layout - -import ( - "fmt" - "strconv" - - "gioui.org/unit" -) - -type formatter struct { - current int - orig string - expr string - skip int -} - -type formatError string - -// Format lays out widgets according to a format string, similar to -// how fmt.Printf interpolates a string. -// -// The format string is an epxression where layouts are similar to -// function calls, and the underscore denotes a widget from the -// arguments. The ith _ invokes the ith widget from the arguments. -// -// If the layout format is invalid, Format panics with an error where -// a cross, ✗, marks the error position. -// -// For example, -// -// layout.Format(gtx, "inset(8dp, _)", w) -// -// is equivalent to -// -// layout.UniformInset(unit.Dp(8)).Layout(gtx, w) -// -// Available layouts: -// -// inset(insets, widget) applies Inset to widget. Insets are either: -// one value for uniform insets; two values for top/bottom and -// right/left insets; three values for top, bottom and right/left -// insets; or four values for top, right, bottom, left insets. -// -// direction(widget) applies a directed Align to widget. Direction -// is one of north, northeast, east, southeast, south, southwest, west, -// northwest, center. -// -// hmax/vmax/max(widget) forces the horizontal, vertical or both -// constraints to their maximum before laying out widget. -// -// hmin/vmin/min(widget) forces the horizontal, vertical or both -// constraints to their minimum before laying out widget. -// -// hcap/vcap(size, widget) caps the maximum horizontal or vertical -// constraints to size. -// -// hflex/vflex(alignment, children...) lays out children with a -// horizontal or vertical Flex. Each rigid child must be on the form -// r(widget), and each flex child on the form f(, widget). -// If alignment is specified, it must be one of: start, middle, end, -// baseline. The default alignment is start. -// -// stack(alignment, children) lays out children with a Stack. Each -// Rigid child must be on the form r(widget), and each expand child -// on the form e(widget). -// If alignment is specified it must be one of the directions listed -// above. -func Format(gtx *Context, format string, widgets ...Widget) { - if format == "" { - return - } - f := formatter{ - orig: format, - expr: format, - } - defer func() { - if err := recover(); err != nil { - if _, ok := err.(formatError); !ok { - panic(err) - } - pos := len(f.orig) - len(f.expr) - msg := f.orig[:pos] + "✗" + f.orig[pos:] - panic(fmt.Errorf("Format: %s:%d: %s", msg, pos, err)) - } - }() - formatExpr(gtx, &f, widgets) -} - -func formatExpr(gtx *Context, f *formatter, widgets []Widget) { - switch peek(f) { - case '_': - formatWidget(gtx, f, widgets) - default: - formatLayout(gtx, f, widgets) - } -} - -func formatLayout(gtx *Context, f *formatter, widgets []Widget) { - name := parseName(f) - if name == "" { - errorf("missing layout name") - } - expect(f, "(") - fexpr := func() { - formatExpr(gtx, f, widgets) - } - align, ok := dirFor(name) - if ok { - Align(align).Layout(gtx, fexpr) - expect(f, ")") - return - } - switch name { - case "inset": - in := parseInset(gtx, f, widgets) - in.Layout(gtx, fexpr) - case "hflex": - formatFlex(gtx, Horizontal, f, widgets) - case "vflex": - formatFlex(gtx, Vertical, f, widgets) - case "stack": - formatStack(gtx, f, widgets) - case "hmax": - cs := gtx.Constraints - cs.Width.Min = cs.Width.Max - ctxLayout(gtx, cs, func() { - formatExpr(gtx, f, widgets) - }) - case "vmax": - cs := gtx.Constraints - cs.Height.Min = cs.Height.Max - ctxLayout(gtx, cs, func() { - formatExpr(gtx, f, widgets) - }) - case "max": - cs := gtx.Constraints - cs.Width.Min = cs.Width.Max - cs.Height.Min = cs.Height.Max - ctxLayout(gtx, cs, func() { - formatExpr(gtx, f, widgets) - }) - case "hmin": - cs := gtx.Constraints - cs.Width.Max = cs.Width.Min - ctxLayout(gtx, cs, func() { - formatExpr(gtx, f, widgets) - }) - case "vmin": - cs := gtx.Constraints - cs.Height.Max = cs.Height.Min - ctxLayout(gtx, cs, func() { - formatExpr(gtx, f, widgets) - }) - case "min": - cs := gtx.Constraints - cs.Width.Max = cs.Width.Min - cs.Height.Max = cs.Height.Min - ctxLayout(gtx, cs, func() { - formatExpr(gtx, f, widgets) - }) - case "hcap": - w := parseValue(f) - expect(f, ",") - cs := gtx.Constraints - cs.Width.Max = cs.Width.Constrain(gtx.Px(w)) - ctxLayout(gtx, cs, func() { - formatExpr(gtx, f, widgets) - }) - case "vcap": - h := parseValue(f) - expect(f, ",") - cs := gtx.Constraints - cs.Height.Max = cs.Height.Constrain(gtx.Px(h)) - ctxLayout(gtx, cs, func() { - formatExpr(gtx, f, widgets) - }) - default: - errorf("invalid layout %q", name) - } - expect(f, ")") -} - -func formatWidget(gtx *Context, f *formatter, widgets []Widget) { - expect(f, "_") - if i, max := f.current, len(widgets)-1; i > max { - errorf("widget index %d out of bounds [0;%d]", i, max) - } - if f.skip == 0 { - widgets[f.current]() - } - f.current++ -} - -func formatStack(gtx *Context, f *formatter, widgets []Widget) { - st := Stack{} - backup := *f - // Parse alignment, if present. - name := parseName(f) - align, ok := dirFor(name) - if ok { - st.Alignment = align - expect(f, ",") - backup = *f - } else { - *f = backup - } - var children []StackChild - commaOK := false - // First, lay out rigid children. -loop: - for { - switch peek(f) { - case ')': - break loop - case ',': - if !commaOK { - errorf("unexpected ,") - } - commaOK = false - expect(f, ",") - case 'r': - expect(f, "r(") - children = append(children, st.Rigid(gtx, func() { - formatExpr(gtx, f, widgets) - })) - expect(f, ")") - commaOK = true - case 'e': - expect(f, "e(") - f.skip++ - formatExpr(gtx, f, widgets) - children = append(children, StackChild{}) - f.skip-- - expect(f, ")") - commaOK = true - default: - errorf("invalid flex child") - } - } - // Then, lay out expanded children. - *f = backup - child := 0 - for { - switch peek(f) { - case ')': - if f.skip == 0 { - st.Layout(gtx, children...) - } - return - case ',': - expect(f, ",") - case 'r': - expect(f, "r(") - f.skip++ - formatExpr(gtx, f, widgets) - f.skip-- - expect(f, ")") - child++ - case 'e': - expect(f, "e(") - children[child] = st.Expand(gtx, func() { - formatExpr(gtx, f, widgets) - }) - expect(f, ")") - child++ - default: - errorf("invalid flex child") - } - } -} - -func formatFlex(gtx *Context, axis Axis, f *formatter, widgets []Widget) { - fl := Flex{Axis: axis} - backup := *f - // Parse alignment, if present. - name := parseName(f) - al, ok := alignmentFor(name) - if ok { - fl.Alignment = al - expect(f, ",") - backup = *f - } else { - *f = backup - } - var children []FlexChild - commaOK := false - // First, lay out rigid children. -loop: - for { - switch peek(f) { - case ')': - break loop - case ',': - if !commaOK { - errorf("unexpected ,") - } - expect(f, ",") - case 'r': - expect(f, "r(") - children = append(children, fl.Rigid(gtx, func() { - formatExpr(gtx, f, widgets) - })) - expect(f, ")") - commaOK = true - case 'f': - expect(f, "f(") - parseFloat(f) - expect(f, ",") - f.skip++ - formatExpr(gtx, f, widgets) - children = append(children, FlexChild{}) - f.skip-- - expect(f, ")") - commaOK = true - default: - errorf("invalid flex child") - } - } - // Then, lay out flexible children. - *f = backup - child := 0 - for { - switch peek(f) { - case ')': - if f.skip == 0 { - fl.Layout(gtx, children...) - } - return - case ',': - expect(f, ",") - case 'r': - expect(f, "r(") - f.skip++ - formatExpr(gtx, f, widgets) - f.skip-- - expect(f, ")") - child++ - case 'f': - expect(f, "f(") - weight := parseFloat(f) - expect(f, ",") - children[child] = fl.Flex(gtx, weight, func() { - formatExpr(gtx, f, widgets) - }) - expect(f, ")") - child++ - default: - errorf("invalid flex child") - } - } -} - -func parseInset(gtx *Context, f *formatter, widgets []Widget) Inset { - v1 := parseValue(f) - if peek(f) == ',' { - expect(f, ",") - return UniformInset(v1) - } - v2 := parseValue(f) - if peek(f) == ',' { - expect(f, ",") - return Inset{ - Top: v1, - Right: v2, - Bottom: v1, - Left: v2, - } - } - v3 := parseValue(f) - if peek(f) == ',' { - expect(f, ",") - return Inset{ - Top: v1, - Right: v2, - Bottom: v3, - Left: v2, - } - } - v4 := parseValue(f) - expect(f, ",") - return Inset{ - Top: v1, - Right: v2, - Bottom: v3, - Left: v4, - } -} - -func parseValue(f *formatter) unit.Value { - i := parseFloat(f) - if len(f.expr) < 2 { - errorf("missing unit") - } - u := f.expr[:2] - var v unit.Value - switch u { - case "dp": - v = unit.Dp(i) - case "sp": - v = unit.Sp(i) - case "px": - v = unit.Px(i) - default: - errorf("unknown unit") - } - f.expr = f.expr[len(u):] - return v -} - -func parseName(f *formatter) string { - skipWhitespace(f) - i := 0 -loop: - for ; i < len(f.expr); i++ { - c := f.expr[i] - switch { - case c == '(' || c == ',' || c == ')': - break loop - case c < 'a' || 'z' < c: - errorf("invalid character '%c' in layout name", c) - } - } - fname := f.expr[:i] - f.expr = f.expr[i:] - return fname -} - -func parseFloat(f *formatter) float32 { - skipWhitespace(f) - i := 0 - for ; i < len(f.expr); i++ { - c := f.expr[i] - if (c < '0' || c > '9') && c != '.' { - break - } - } - expr := f.expr[:i] - v, err := strconv.ParseFloat(expr, 32) - if err != nil { - errorf("invalid number %q", expr) - } - f.expr = f.expr[i:] - return float32(v) -} - -func parseInt(f *formatter) int { - skipWhitespace(f) - i := 0 - for ; i < len(f.expr); i++ { - c := f.expr[i] - if c < '0' || c > '9' { - break - } - } - expr := f.expr[:i] - v, err := strconv.Atoi(expr) - if err != nil { - errorf("invalid number %q", expr) - } - f.expr = f.expr[i:] - return v -} - -func peek(f *formatter) rune { - skipWhitespace(f) - if len(f.expr) == 0 { - errorf("unexpected end") - } - return rune(f.expr[0]) -} - -func expect(f *formatter, str string) { - skipWhitespace(f) - n := len(str) - if len(f.expr) < n || f.expr[:n] != str { - errorf("expected %q", str) - } - f.expr = f.expr[n:] -} - -func skipWhitespace(f *formatter) { - for len(f.expr) > 0 { - switch f.expr[0] { - case '\t', '\n', '\v', '\f', '\r', ' ': - f.expr = f.expr[1:] - default: - return - } - } -} - -func alignmentFor(name string) (Alignment, bool) { - var a Alignment - switch name { - case "start": - a = Start - case "middle": - a = Middle - case "end": - a = End - case "baseline": - a = Baseline - default: - return 0, false - } - return a, true -} - -func dirFor(name string) (Direction, bool) { - var d Direction - switch name { - case "center": - d = Center - case "northwest": - d = NW - case "north": - d = N - case "northeast": - d = NE - case "east": - d = E - case "southeast": - d = SE - case "south": - d = S - case "southwest": - d = SW - case "west": - d = W - default: - return 0, false - } - return d, true -} - -func errorf(f string, args ...interface{}) { - panic(formatError(fmt.Sprintf(f, args...))) -} - -func (e formatError) Error() string { - return string(e) -}