This commit restructures the entire text shaping stack to enable lines of shaped text to
have non-homogeneous properties like which font face they belong to and which direction
a segment of text is going.
The text package now provides a concrete type text.Shaper which can be used to convert
strings into sequences of renderable text.Glyphs. At a high level, the API is used
like this:
// Prepare some fonts.
var collection []text.FontFace
// Make a shaper with those fonts loaded.
shaper := text.NewShaper(collection)
// Shape a string.
shaper.LayoutString(text.Parameters{
PxPerEm: fixed.I(12),
}, 0, 100, system.Locale{}, "Hello")
// Iterate the glyphs from that string.
for glyph, ok := shaper.NextGlyph(); ok; glyph, ok = shaper.NextGlyph() {
// Convert the glyph data into a path. In real uses, convert batches of glyphs
// rather than single glyphs to reduce the number of individual paths and offsets
// required to display your text.
shape := shaper.Shape([]text.Glyph{glyph})
// Offset the glyph to the position it declares within its fields. This will
// automatically handle correct bidirectional text glyph positioning.
offset := op.Offset(image.Pt(glyph.X.Floor(), int(glyph.Y))).Push(gtx.Ops)
// Create a clip area from the shape of the glyph.
area := clip.Outline{Path: shape}.Push(gtx.Ops)
// Paint whatever the current color is within the glyph's shape.
paint.PaintOp{}.Add(gtx.Ops)
area.Pop()
offset.Pop()
}
This API will transparently handle both font fallback (choosing appropriate fonts
from those loaded when the primary font doesn't contain a required glyph) and
bidirectional text (mixed left-to-right and right-to-left text). Glyphs are
iterated in order of the input runes, not their visual order, but proper use
of the provided offsets will ensure that text always displays correctly.
Thanks to Elias Naur for suggesting this glyph iterator strategy. It let us cut
through a lot of accumulated complexity from trying to match our old text APIs,
meaning that this change actually is a net negative change in lines of code.
This commit consumes the upstream github.com/go-text/typesetting/shaping API
now that my prior work is merged there, removing the need for the font/opentype/internal
package entirely.
As part of my efforts, I fuzzed both the low-level text shaping stack and the
editor widget extensively. I've committed regression tests found that way into
the appropriate testdata files to ensure the fuzzer re-checks them.
Fixes: https://todo.sr.ht/~eliasnaur/gio/425
Fixes: https://todo.sr.ht/~eliasnaur/gio/211
Signed-off-by: Chris Waldon <christopher.waldon.dev@gmail.com>
The unit.Value is a struct and thus more inconvenient to use than its
underlying float32 type. In addition, most uses don't need a general
value, but rather a specific unit given by the context. This change
replaces unit.Value with two float32 units, Dp and Sp. It also changes
variables and parameters of unit.Value to a specific unit type matching
the context. That is, unit.Dp everywhere except for text sizes which are
in Sp.
Switching to typed float32s has multiple advantages
- They can be constants:
const touchSlop = unit.Dp(16)
- Casting untyped constants is no longer necessary:
insets := layout.UniformInset(16)
- Calculation with values is natural:
func (s ScrollbarStyle) Width() unit.Dp {
return s.Indicator.MinorWidth + s.Track.MinorPadding + s.Track.MinorPadding
}
The main API change is that calls to gtx.Px must be replaced with either
gtx.Dp or gtx.Sp depending on the unit.
Idea by Christophe Meessen.
Signed-off-by: Elias Naur <mail@eliasnaur.com>
Like the change to op.Offset before this, clip.RRect and UniformRRect
is usually used with integer coordinates. Change to integer coordinates
to eliminate many useless conversions to float32.
Signed-off-by: Elias Naur <mail@eliasnaur.com>
The op.Save and Load methods exist to support the need for
transformation, clip, pointer area state to behave as stacks. For
example, layout needs to apply an offset to its children but not
subsequent operations.
Before this change, op.Save and Load were used to save and restore the
state:
ops := new(op.Ops)
// Save state.
state := op.Save(ops)
// Apply offset.
op.Offset(...).Add(ops)
// Draw with offset applied.
draw(ops)
// Restore state.
state.Load()
A drawback with the op.Save mechanism is that there is no direct
connection between the state change and the saving and loading of state.
This causes confusion as to when a Save/Load is needed and who is
responsible for performing them, which leads to subtle bugs and over-use
of Save/Loads.
This change gets rid of the general state stack and replaces it with
per-state stacks. There is now a stack for transformation, clip, pointer
areas, and they can only be restored by the code pushing state to them.
The example above now becomes:
ops := new(op.Ops)
// Push offset to the transformation stack.
stack := op.Offset(...).Push(ops)
// Draw with offset applied.
draw(ops)
// Restore state.
stack.Pop()
For convenience, transformation also be Add'ed if the stack operation is
not required.
Simple state such as the current material no longer has a way to be
restored; it is assumed the client of a PaintOp adds their desired
material operation before it.
API change: replace op.Save/Load with explicit Push/Pop scopes for
op.TransformOps, pointer.AreaOps, clip.Ops.
To ease porting, this change retains a version of op.Save/Load that
saves and restores the transformation and clip stacks. It also retains
an Add method for clip.Op.
Signed-off-by: Elias Naur <mail@eliasnaur.com>
Icons are meant to be shared among multiple widgets, but their Color
state may end up with unexpected values after use. Replace the state
with and explicit argument to Layout.
Signed-off-by: Elias Naur <mail@eliasnaur.com>
This is a breaking change as Icon.Layout no longer requests a size.
Before:
sz := unit.Dp(20)
ic.Layout(gtx, sz)
After:
sz := gtx.Metric.Px(unit.Dp(20))
gtx.Constraints.Min = image.Pt(sz, 0)
ic.Layout(gtx)
Fixes gio#240
Signed-off-by: pierre <pierre.curto@gmail.com>
checkable.layout forces the label to take up at least constraints.min
space. However, for min == max, the total checkbox plus label would then
overflow. The minimum constraint doesn't seem necessary anymore, so drop
it.
Remove a superfluous layout.W layout as well.
Signed-off-by: Elias Naur <mail@eliasnaur.com>
color.RGBA has two problems with regards to using it.
First the color values need to be premultiplied, whereas most APIs
have non-premultiplied values. This is mainly to preserve color components
with low alpha values.
Second there are two ways to premultiply with sRGB. One is to premultiply
after sRGB conversion, the other is before. This makes using the API more
confusing.
Using color.NRGBA in sRGB makes it align with CSS.e
Signed-off-by: Egon Elbre <egonelbre@gmail.com>
Package material's ad-hoc mulAlpha didn't take the sRGB color-space
into account, which meant that alpha-scaled colors were subtly wrong.
Introduce f32color.MulAlpha and convert all uses to it.
Thanks to René Post for finding and debugging the issue.
Signed-off-by: Elias Naur <mail@eliasnaur.com>
This commit configures all remaining widgets to draw themselves in a disabled state
when their layout.Context is disabled. A description of the
strategy employed by each follows:
- Checkbox and RadioButton: Draws the icon component in a lighter color. Currently the label text is left
in its default color.
- ProgressBar: The "progress" color is lightened, but not as much as the background color. This makes the current progress value still readable.
- Editor: The cursor is no longer drawn and the text is lightened.
- Switch: The track is unchanged, but the circular "thumb" component is lightened.
Signed-off-by: Chris Waldon <christopher.waldon.dev@gmail.com>
Change the definition of Widget from the implicit
type Widget func()
to the explicit functional
type Widget func(gtx layout.Context) layout.Dimensions
The advantages are numerous:
- Clearer connection between the incoming context and the output dimensions.
- Returning the Dimensions are impossible to omit.
- Contexts passed by value, so its fields can be exported
and freely mutated by the program.
The only disadvantage is the longer function literals and the many "returns".
What tipped the scales in favour of the explicit Widget variant is that type
aliases can dramatically shorten the literals:
type (
C = layout.Context
D = layout.Dimensions
)
widget := func(gtx C) D {
...
}
Note that the aliases are not part of the Gio API and it is up to each user
whether they want to use them.
Finally the Go proposal for lightweight function literals,
https://github.com/golang/go/issues/21498, may remove the disadvantage
completely in future.
Context becomes a plain struct with only public fields, and its Reset is
replaced by a NewContext convenience constructor.
Signed-off-by: Elias Naur <mail@eliasnaur.com>
Instead of
type Contraints struct {
Width, Height Constraint
}
use
type Constraints struct {
Min, Max image.Point
}
which leads to simpler use. For example, the Min method is trivally replaced by
the field, and the RigidConstraints constructor is no longer a net win.
API Change. Rewrites:
gofmt -r 'gtx.Constraints.Min() -> gtx.Constraints.Min'
gofmt -r 'gtx.Constraints.Width.Min -> gtx.Constraints.Min.X'
gofmt -r 'gtx.Constraints.Height.Min -> gtx.Constraints.Min.Y'
gofmt -r 'gtx.Constraints.Height.Max -> gtx.Constraints.Max.Y'
gofmt -r 'gtx.Constraints.Width.Max -> gtx.Constraints.Max.X'
Signed-off-by: Elias Naur <mail@eliasnaur.com>
There is nothing theme-specific about displaying images and icons,
so move the types from the material package to the generic widget
package.
Signed-off-by: Elias Naur <mail@eliasnaur.com>
First, replace LayoutOptions with an explicit maximum width parameter. The
single-field option struct doesn't carry its weight, and I don't think we'll
see more global layout options in the future. Rather, I expect options to cover
spans of text or be part of a Font.
Second, replace the unit.Converter with an scaled text size. It's simpler and
allow the Editor and similar widgets to easily detect whether their cached
layouts are stale. Package text no longer depends on package unit, which is
now dealt with at the widget-level only.
Finally, remove the Size field from Font. It was a design mistake: a Font is
assumed to cover all sizes, as evidenced by the FontRegistry disregarding
Size when looking up fonts.
Signed-off-by: Elias Naur <mail@eliasnaur.com>
It's one less type (Align) and shorter:
Before:
layout.Align(layout.Center).Layout(...)
After
layout.Center.Layout(...)
It is also safer: since `layout.Align(...)` was a casting operation,
the Go compiler would not complain about an incompatible constant.
For example, the widget/material package contained a wrong cast:
layout.Align(layout.Start)
which should have been
layout.Align(layout.W)
After this change, attempting `layout.Start.Layout(...)` result
in a compile error.
Signed-off-by: Elias Naur <mail@eliasnaur.com>
With the simplification of MacroOp, it is now possible to simplify
the Flex API to just a single Layout method, similar to List:
layout.Flex{}.Layout(gtx,
layout.Rigid(func() { ... }),
layout.Flexed(0.5, func() { ... }),
)
Signed-off-by: Elias Naur <mail@eliasnaur.com>