This commit upgrades our version of eliasnaur.com/font to include a color
emoji font and uses that to benchmark displaying large quantities of emoji.
As expected, this is very slow when the strings change frequently, and uses
silly amounts of memory. Future commits will work to improve this.
Signed-off-by: Chris Waldon <christopher.waldon.dev@gmail.com>
This commit supports rendering opentype glyphs containing bitmap data instead of
color data. In order to support returning the shaped bitmap glyphs from the Shaper's
Shape() method, it has gained a second return parameter, an op.CallOp. Adding
that CallOp immediately after or immediately before painting the returned path
will display the bitmap glyphs.
The consequences of supporting colored glyphs forced changes upon the widget APIs
for widgets that display text. Previously text always had a fixed paint material,
so we could rely upon the caller setting the material (e.g. adding a paint.ColorOp)
before painting the glyphs and everything would work. Now that we display image-
based glyphs, we end up changing the painting material to an image midway through
displaying text. This is an awkward consequence of how we currently manage the
painting material, and to work around it widgets now accept an op.CallOp that
is expected to set the proper paint material. Text widgets will use that op.CallOp
before painting text (or other paint operations) to ensure that they are painting
with the proper materials.
This, in turn, changed the APIs for laying out widget.Editor, widget.Label, and
widget.Selectable, and eliminated the need for them to accept a callback (the
callback was only really to set the colors). Dropping that callback function
allowed me to consolidate widget.Label to only need one exported Layout method,
and allowed me to unexport the PaintText, PaintCaret, and PaintSelection methods
from widget.Editor and widget.Selectable. Those methods are useless in the public
API now that they don't need to be invoked after applying a color operation.
Callers of the raw text shaper API will need to make the following changes:
- Where before you used:
var ops *op.Ops // Assume we have an operation list.
var shaper *text.Shaper // Assume we have a shaper.
var col color.NRGBA // Assume we have a text color.
var glyphs []text.Glyph // Assume we have already filled a slice of glyphs.
shape := shaper.Shape(glyphs)
paint.FillShape(ops, col, clip.Outline{Path:shape}.Op())
- Now you should do:
shape, call := shaper.Shape(glyphs)
paint.FillShape(ops, col, clip.Outline{Path:shape}.Op())
call.Add(ops)
Callers of the widget.{Label,Selectable,Editor} APIs will need to make the
following changes:
- Where before you used:
var gtx layout.Context // Assume we have an operation list.
var shaper *text.Shaper // Assume we have a shaper.
var textCol color.NRGBA // Assume we have a text color.
var selectCol color.NRGBA // Assume we have a selection color.
var ed widget.Editor // Assume we have an editor.
var sel widget.Selectable // Assume we have a selectable.
// Lay out an editor.
ed.Layout(gtx, shaper, text.Font{}, unit.Sp(30), func(layout.Context) layout.Dimensions {
// Paint the editor.
})
// Lay out a selectable.
sel.Layout(gtx, shaper, text.Font{}, unit.Sp(30), func(layout.Context) layout.Dimensions {
// Paint the selectable.
})
// Lay out an interactive label.
widget.Label{}.LayoutSelectable(gtx, shaper, text.Font{}, unit.Sp(30), "hello", func(layout.Context) layout.Dimensions {
// Paint the label.
})
// Lay out a non-interactive label.
widget.Label{}.Layout(gtx, shaper, text.Font{}, unit.Sp(30), "hello")
- Now you should do:
// Capture setting the text paint material in a macro.
textColMacro := op.Record(gtx.Ops)
paint.ColorOp{Color: textCol}.Add(gtx.Ops)
textMaterial := textColMacro.Stop()
// Capture setting the selection paint material in a macro.
selectColMacro := op.Record(gtx.Ops)
paint.ColorOp{Color: selectCol}.Add(gtx.Ops)
selectMaterial := selectColMacro.Stop()
// Lay out an editor.
ed.Layout(gtx, shaper, text.Font{}, unit.Sp(30), textMaterial, selectMaterial)
// Lay out a selectable.
sel.Layout(gtx, shaper, text.Font{}, unit.Sp(30), textMaterial, selectMaterial)
// Lay out a label (no difference between interactive and non-interactive)
widget.Label{}.Layout(gtx, shaper, text.Font{}, unit.Sp(30), "hello", textMaterial, selectMaterial)
Callers of the material package API do not need to make any changes.
Signed-off-by: Chris Waldon <christopher.waldon.dev@gmail.com>
This commit upgrades our go-text version to the latest one which internalizes
harfbuzz and supports text truncators. This allows us to drop our dependency
upon Benoit's textlayout package.
Signed-off-by: Chris Waldon <christopher.waldon.dev@gmail.com>
This commit ensures that the Spacer type doesn't break layouts
by ignoring when its min constraints require it to be larger or
its max constraints require it to be smaller.
Signed-off-by: Chris Waldon <christopher.waldon.dev@gmail.com>
When no scale factor is set, scale by 1.0, mapping one image pixel to
one device-independent pixel. This matches the behavior of CSS and other
frameworks.
The old code attempted to convert to Dp while taking the image's DPI
into account. This was wrong in two ways:
- It assumed that the default display DPI is 160, but this is only true
for Android. Other platforms use 96, 162, or leave it undefined. Thus
image.Layout's idea of a dp didn't match that of Gio on most
platforms.
- It tried to account for image DPI, and assumed a default of 72. This
was wrong in that DPI in images is merely metadata meant for printing,
not display. The vast majority of software such as image viewers and
image editors do not take DPI into account, mapping one image pixel
either to one physical pixel or to one device-independent pixel. That
is, users would expect their images to either display 1 to 1, or scaled
based on PxPerDp, but not scaled based on the image's DPI.
We default to a scale of 1 to stay consistent with other parts of Gio
that scale by default. Users who don't want any scaling can continue to
set the scale to the inverse of PxPerDp.
While we're here we clarify the documentation of the Scale field.
This change is backwards incompatible for users that relied on the
default scale.
Signed-off-by: Dominik Honnef <dominik@honnef.co>
faceOrderer.sorted tried to put the "primary" font first by tweaking the
"less" function in sort.Slice, but it didn't work correctly.
If item i equaled the "primary" font, less() always returned true. This
did not take into account if item j was the "primary" font, in which
case it could easily be sorted differently.
Rather than adding another special case for that, which I couldn't
convince myself was actually correct in every case, I just searched for
the "primary" font and moved it to the front of the slice, and then
omitted the first item of the slice from the rest of the sorting.
Signed-off-by: Larry Clapp <larry@theclapp.org>
This commit extends the key event handling for text widgets to always check for
appropriate modifier keys. Previously this wasn't necessary, as the text widgets
would only ever receive key events it registered for, but now it may be the top-level
key event handler and thus receive all key events that aren't handled elsewhere.
Fixes: https://todo.sr.ht/~eliasnaur/gio/487
Signed-off-by: Chris Waldon <christopher.waldon.dev@gmail.com>
The majority of scrolling happens by manipulating the index of the first
displayed item instead of by just manipulating the offset. This lets us
avoid having to render all items that were scrolled past.
Instead of numbers of items we could've accepted a ratio in [0, 1] to
scroll by or to, to match the data we get from scrollbars. However,
there are more use cases for scrolling by items, such as keyboard
shortcuts, go-to dialogs, etc. And converting from [0, 1] to items is
trivial for the user as long as they know the number of items, and will
usually be handled for them by a theme.
Signed-off-by: Dominik Honnef <dominik@honnef.co>
When the line overlaps itself backtracking exactly, e.g.
path.MoveTo(0, 100)
path.LineTo(100, 0)
path.LineTo(0, 100)
then acos calculation is relatively unstable. By using atan2 it avoids
some of such problems in the calculation. Additionally, it simpliflies
the round join calculation.
Fixes: https://todo.sr.ht/~eliasnaur/gio/474
Signed-off-by: Egon Elbre <egonelbre@gmail.com>
Apparently, alphaClose has been overflowing and giving the wrong answer
for a while and hence some of the tests are broken. I currently disabled
those tests, because I'm not quite sure where and how they broke.
Also, bumped alpha tolerance to 8, to ignore false positives.
Signed-off-by: Egon Elbre <egonelbre@gmail.com>
Setting Float.Invert=true not only inverts the order of values (which was already easily done by swapping min and max), it also draws the widget inverted so that the track is darkened on the opposite side from usual.
This patch also fixes a bug wherein a vertical slider was drawn inverted by default.
Signed-off-by: Gordon Klaus <gordon.klaus@gmail.com>
This commit alters the textView API to give callers the option to provide
their own buffers for reading text. This enables some widget usecases to
be zero-allocation if a widget simply needs to examine the contents of the
text without returning it as a string.
Signed-off-by: Chris Waldon <christopher.waldon.dev@gmail.com>
This commit updates the textView to better describe the expectations
and behaviors of the Update and Paint* methods.
Signed-off-by: Chris Waldon <christopher.waldon.dev@gmail.com>
go.mod specifies 1.18, due to go.mod behavior and to avoid some issues
with updating the dependencies. However, we can still support older go
version, as long as it compiles with the older version.
Signed-off-by: Egon Elbre <egonelbre@gmail.com>
PxToDp and PxToSp are useful when you are trying to calculate
text-size or widget size based on dynamically sized container.
Signed-off-by: Egon Elbre <egonelbre@gmail.com>
The io.Reader based API has the potential to be significantly more
efficient, and there are very few users of the runereader API. This
commit simply drops it entirely in favor of the reader API.
Signed-off-by: Chris Waldon <christopher.waldon.dev@gmail.com>
This commit adds exported methods to both LabelState and Editor
allowing callers to locate the text regions representing a range
of runes. This can be used to build interactive subregions of text,
like (for instance) hyperlinks.
Signed-off-by: Chris Waldon <christopher.waldon.dev@gmail.com>
This commit rebuilds the editor and label types on the common
foundation provided by textView. This enables labels to have
optional state that makes them selectable, and allows the
two widgets to share the code for managing cursor positions,
displaying selections, and soforth. Labels now have an additional
Layout function which can be invoked if they have a Selectable.
It accepts a layout.Widget used to paint their contents. Stateless
labels should still use the old Layout method.
Signed-off-by: Chris Waldon <christopher.waldon.dev@gmail.com>
This commit adds a standalone state type for manipulating
and displaying text. It reads text from a minimal interface,
shapes it, tracks valid cursor positions, and provides sizing
and scrolling services to higher-level widgets. My long term
goal with these types is to export them to allow non-core widgets
to build atop them, but I've left them private for now.
Signed-off-by: Chris Waldon <christopher.waldon.dev@gmail.com>
io.Reader is actually a more efficient interface than io.RuneReader,
as we can pull bytes out and check for cache hits without doing
redundant rune<->string conversions. This isn't implemented yet,
however.
Signed-off-by: Chris Waldon <christopher.waldon.dev@gmail.com>
This commit provides a new ReadOnly boolean on the editor. If set, the
editor functions as a selectable label. User interaction cannot change
the contents of the editor (though application code can still use the
API).
Signed-off-by: Chris Waldon <christopher.waldon.dev@gmail.com>
This commit unifies and fixes the shaper's handling of the alignment
minimum width. Previously it was only considered when the text was
a single line, but in hindsight that was clearly a mistake. Now the
maximum width of all shaped lines and the minimum width is used to
set the text alignment.
This commit also fixes an index test in package widget that was
relying on the old (incorrect) alignment behavior.
Signed-off-by: Chris Waldon <christopher.waldon.dev@gmail.com>
This commit extends the editor to keep track of its own minimum constraint
and to provide that value to the text shaper for the purpose of aligning
text. Without this, the shaper does not know how much of the width of the
editor to use for alignment purposes.
Signed-off-by: Chris Waldon <christopher.waldon.dev@gmail.com>
This commit updates the textIterator and glyphIndex types to consume
new flag information provided on glyphs. These changes allow widget.Label
and widget.Editor to correctly compute text bounding boxes and to
generate valid cursor positions at the end of text.
Signed-off-by: Chris Waldon <christopher.waldon.dev@gmail.com>
This commit adds a new flag to glyphs indicating that they are the
beginning of a new paragraph, as well as adding a guarantee that a
glyph with this flag will always follow a glyph with FlagParagraphBreak,
even if a paragraph break is the last rune in the text. This helps
widgets to find the boundaries and positions of text ending with
newlines reliably.
Signed-off-by: Chris Waldon <christopher.waldon.dev@gmail.com>
This commit fixes a subtle discrepancy in the handling of text input
within the shaper. Text provided as an io.RuneReader with a trailing
newline would generate an extra (empty) line of text, whereas the
same input provided as a string would not.
Signed-off-by: Chris Waldon <christopher.waldon.dev@gmail.com>
This commit pushes limiting the maximum number of lines of text into
the shaper implementation. This is more efficient than doing it in
widgets, and also opens the door for future use of the shaper to
insert ellipsis and other truncating characters as appropriate.
I realized that we lost the implementation of limiting the number of
lines of text in my text stack overhaul, so this fixes a regression
from that work.
Signed-off-by: Chris Waldon <christopher.waldon.dev@gmail.com>