Commit Graph

2734 Commits

Author SHA1 Message Date
Chris Waldon 9d0a53fc9f widget{,/material}: [API] split interactive and non-interactive text widgets
This commit separates the types for interactive and non-interactive text within
package widget. widget.Selectable is used for all interactive text. widget.Label
is used for all non-interactive text. There is no longer a field on widget.Label
to provide it with a Selectable. If you want selectable text and are not relying
upon the material pacakge API, you need to create widget.Selectables instead of
widget.Labels. The material package's LabelStyle API is unchanged.

Signed-off-by: Chris Waldon <christopher.waldon.dev@gmail.com>
2023-03-28 09:25:33 -06:00
Chris Waldon 5e6e1217da widget: expose truncation status of Selectable
This commit adds an exported method to enable widgets to detect
when the text displayed by a Selectable has been truncated. This
can be used to implement proper show-full-text-in-an-overlay
behavior in a parent widget. I haven't attempted to implement
that in core yet, as it is a complex feature involving animation
and pointer interaction.

Signed-off-by: Chris Waldon <christopher.waldon.dev@gmail.com>
2023-03-28 09:25:31 -06:00
Chris Waldon 959f5889a1 go.*,text,widget{,/material}: implement text truncators
This commit adds support for the idea of a text "Truncator", a string
that is shown at the end of truncated text to indicate that it has been
shortened because it would not fit within the requested number of lines.

When specifying a maximum number of lines, a truncator symbol is always
used. If the user does not provide one, the rune `…` is used. This
requirement results in a better user experience and significantly simpler
code, as we can rely upon the presence of one or more truncator glyphs in
the output glyph stream when truncation has occurred.

When interacting with truncated text, the truncator glyphs all act as
a single, indivisible unit. They can be selected or not, and if selected
they act as the entire contents of the truncated portion of the text.
This means that copying all of a truncated label will copy the entire
label text content, with the truncator symbol not appearing at all.

Concretely, the exposed text API now accepts a Truncator string in
text.Parameters, and there is a new glyph flag FlagTruncator which indicates
that the glyph is part of the truncator run. The truncator run will only
have a single FlagClusterBreak (even if the run would usually have many),
and the glyph with both FlagClusterBreak and FlagTruncator will have the
quantity of truncated runes in its Runes field. This necessitated increasing
the size of the Runes field from a byte to an int, as it's theoretically possible
for quite a lot of text to be truncated.

This commit necessarily bumps our go-text/typesetting dependency to the version
exposing truncation in the exported API.

Signed-off-by: Chris Waldon <christopher.waldon.dev@gmail.com>
2023-03-28 09:25:28 -06:00
Chris Waldon 5c54268d40 widget: [API] implement UAX#29 grapheme clustering in text widgets
This commit teaches the text widgets how to position their cursor according to
grapheme cluster boundaries rather than rune boundaries. While this is more work,
the results better match the expectations of users. A "grapheme cluster" is a
user-perceived character that may be composed of arbitrarily many runes.

I chose to implement this within widgets for two reasons:

- grapheme cluster boundaries would be extremely difficult to encode within the
glyph stream returned by the text shaper
- not all text needs to be segmented, only text that can be interacted with

All mutation operations exposed by widget.Editor now work in terms of grapheme
clusters instead of runes.

Signed-off-by: Chris Waldon <christopher.waldon.dev@gmail.com>
2023-03-28 09:25:25 -06:00
Chris Waldon 36e768e716 widget: make glyphIndex reusable
This commit allows the glyph index type to be reset and reused, preventing the
reallocation of numerous buffers when indexing glyphs.

Signed-off-by: Chris Waldon <christopher.waldon.dev@gmail.com>
2023-03-28 09:25:24 -06:00
Chris Waldon 25171df66a text: cache bitmap glyph image operations
This commit adds caching to the process of extracting bitmap images
from glyphs, ensuring that we only do so once for a given glyph so long
as it isn't evicted from our LRU.

Signed-off-by: Chris Waldon <christopher.waldon.dev@gmail.com>
2023-03-28 09:25:21 -06:00
Chris Waldon 3bdbcab874 go.*,widget: add initial emoji rendering benchmarks
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>
2023-03-28 09:25:18 -06:00
Chris Waldon 6ab3ff40a6 font/opentype,text,widget{,/material}: [API] support bitmap glyph rendering
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>
2023-03-28 09:25:15 -06:00
Chris Waldon 47d25c1394 go.*,font/opentype,text: switch to latest go-text/typesetting api
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>
2023-03-28 09:25:09 -06:00
Chris Waldon d7b1c7c33b layout: ensure Spacer obeys constraints
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>
2023-03-23 17:08:41 -06:00
Dominik Honnef 51b11486c5 widget: [API] correct default scaling of images
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>
2023-03-23 17:06:43 -06:00
Larry Clapp fa34121f00 text: fix sorting in faceOrderer.sorted
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>
2023-03-23 17:02:43 -06:00
Chris Waldon b09ef80d9f widget: ensure proper modifiers on key events
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>
2023-03-23 16:57:52 -06:00
Dominik Honnef 107401cf07 layout: improve documentation for List.ScrollTo and List.ScrollBy
Signed-off-by: Dominik Honnef <dominik@honnef.co>
2023-03-23 16:44:32 -06:00
Dominik Honnef dc9a4a4009 layout: simplify implementation of List.ScrollTo
Signed-off-by: Dominik Honnef <dominik@honnef.co>
2023-03-23 16:44:12 -06:00
Serhat Sevki Dincer 4a1962e5e8 text: simplify font weights
Signed-off-by: Serhat Sevki Dincer <jfcgauss@gmail.com>
2023-03-23 16:37:46 -06:00
Serhat Sevki Dincer 35a8231963 text,widget: remove ineffective assignments
Signed-off-by: Serhat Sevki Dincer <jfcgauss@gmail.com>
2023-03-23 16:37:46 -06:00
Serhat Sevki Dincer 39b1158410 app,gpu{,/headless,/internal/rendertest}: replace io/ioutil with io & os
Signed-off-by: Serhat Sevki Dincer <jfcgauss@gmail.com>
2023-03-23 16:37:46 -06:00
Chris Waldon 1210bbb34a text: test maxlines with exported API
This commit changes _how_ the test for line wrapping is implemented to rely on the
exported API rather than internal symbols.

Thanks to https://github.com/gioui/gio/pull/109 for pointing this out.

Signed-off-by: Chris Waldon <christopher.waldon.dev@gmail.com>
2023-03-23 16:29:02 -06:00
Dominik Honnef 5f818bc5e7 widget/material: use more efficient way of scrolling lists
Signed-off-by: Dominik Honnef <dominik@honnef.co>
2023-02-23 18:43:50 -06:00
Dominik Honnef 8af4472672 layout: add API for efficiently scrolling to and by items
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>
2023-02-23 18:43:43 -06:00
Elias Naur bb12508a8a go.*: bump golang.org/x/text
Avoids CVE-2022-32149.

Signed-off-by: Elias Naur <mail@eliasnaur.com>
2023-02-23 16:43:53 -06:00
Elias Naur 0dba85f52e io,app: route all unhandled key events to the topmost handler
Signed-off-by: Elias Naur <mail@eliasnaur.com>
2023-02-06 17:10:44 -06:00
Elias Naur 32c6a9b10d gpu/internal/rendertest: add issue references to broken tests
References: https://todo.sr.ht/~eliasnaur/gio/479
Signed-off-by: Elias Naur <mail@eliasnaur.com>
2023-02-06 12:08:04 -06:00
Egon Elbre bce4153640 internal/stroke: fix line overlap
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>
2023-02-06 12:01:52 -06:00
Egon Elbre 14a33f3cb7 gpu/internal/rendertest: fix alphaClose check
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>
2023-02-06 11:51:02 -06:00
Gordon Klaus db6b4de0f7 widget/material: [API] move widget.Float.{Axis,Invert} into material.SliderStyle
Signed-off-by: Gordon Klaus <gordon.klaus@gmail.com>
2023-01-27 21:04:32 -06:00
Gordon Klaus 22aa00f476 widget/material: add Float.Invert
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>
2023-01-27 21:04:01 -06:00
Elias Naur ac2c284d16 app: [Android] sanitize IME snippet bounds
Fixes: https://todo.sr.ht/~eliasnaur/gio/473
Signed-off-by: Elias Naur <mail@eliasnaur.com>
2023-01-27 20:36:12 -06:00
Larry Clapp e0cf570339 widget: add a Focus() method to widget.Clickable
Signed-off-by: Larry Clapp <larry@theclapp.org>
2023-01-18 16:28:18 -06:00
Elias Naur af7afea5a3 gpu: don't allocate null materials buffer when running on the CPU
References: https://todo.sr.ht/~eliasnaur/gio/469
Signed-off-by: Elias Naur <mail@eliasnaur.com>
2023-01-14 09:59:55 -06:00
Chris Waldon 1eb5c7dbcd widget: use caller-provided buffers for reading out text
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>
2023-01-14 09:54:06 -06:00
Chris Waldon 940f0f6021 widget: document MoveWord current limitations
Signed-off-by: Chris Waldon <christopher.waldon.dev@gmail.com>
2023-01-14 09:53:22 -06:00
Chris Waldon 1c49532447 widget: update ByteOffset method docs
Signed-off-by: Chris Waldon <christopher.waldon.dev@gmail.com>
2023-01-14 09:53:09 -06:00
Chris Waldon 044390c9df widget: document update and paint methods on textView
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>
2023-01-14 09:52:55 -06:00
Chris Waldon f6d56dba89 widget: drop obsolete comment fragment
Signed-off-by: Chris Waldon <christopher.waldon.dev@gmail.com>
2023-01-14 09:52:51 -06:00
Elias Naur f8221bb2ab gpu/internal/opengl: don't query FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING on GLES2
It's not supported in OpenGL ES 2.

References: https://todo.sr.ht/~eliasnaur/gio/469
Signed-off-by: Elias Naur <mail@eliasnaur.com>
2023-01-06 18:51:20 -06:00
Elias Naur 1a84517b12 gpu/internal/opengl: avoid UNPACK_ROW_LENGTH/PACK_ROW_LENGTH on GLES2
Similarly to WebGL1, they're not supported in OpenGL ES 2.0.

References: https://todo.sr.ht/~eliasnaur/gio/469
Signed-off-by: Elias Naur <mail@eliasnaur.com>
2023-01-06 18:40:22 -06:00
Egon Elbre 827e20d84d gpu: optimize pack.tryAdd
name       old time/op  new time/op  delta
Packer-32   559µs ± 2%   295µs ± 1%  -47.18%  (p=0.008 n=5+5)

Signed-off-by: Egon Elbre <egonelbre@gmail.com>
2023-01-06 18:26:42 -06:00
Egon Elbre 8bc6737dea gpu: optimize encodeQuadTo
name             old time/op  new time/op  delta
EncodeQuadTo-32  35.4ns ± 1%  11.9ns ± 3%  -66.34%  (p=0.008 n=5+5)

Signed-off-by: Egon Elbre <egonelbre@gmail.com>
2023-01-06 18:26:38 -06:00
Egon Elbre c81a1f9671 widget: fix build for go1.17
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>
2023-01-06 18:26:10 -06:00
Egon Elbre e9bce02b24 unit: add PxToDp and PxToSp
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>
2023-01-01 10:19:50 -06:00
Chris Waldon aa2a948b86 text,widget: [API] drop runereader based shaping API
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>
2022-12-23 09:31:52 -06:00
Chris Waldon dc6fbf07f0 widget: expose text region resolution
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>
2022-12-23 09:31:48 -06:00
Chris Waldon e98c8955bb widget{,/material}: rebuild label and editor with textView
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>
2022-12-23 09:31:45 -06:00
Chris Waldon f99aff96ee widget: create standalone textView
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>
2022-12-23 09:31:40 -06:00
Chris Waldon 5d6cc2892d text: consume io.Reader in shaper
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>
2022-12-23 09:31:36 -06:00
Chris Waldon 0b456579a9 widget: add ReadOnly mode to editor
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>
2022-12-20 11:08:02 -06:00
Chris Waldon c455f0f342 text,widget: test and fix minWidth alignment
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>
2022-12-19 11:17:16 -06:00
Chris Waldon fe5878bc63 widget: track minWidth of editor for alignment
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>
2022-12-19 11:17:12 -06:00