Commit Graph

140 Commits

Author SHA1 Message Date
Elias Naur 1686874d07 gesture: [API] rename ClickType to ClickKind
"Kind" is the Go idiomatic name for distinguishing structs outside of
the type system.

Signed-off-by: Elias Naur <mail@eliasnaur.com>
2023-10-06 19:11:11 -05:00
Elias Naur 650ccea28d io/pointer: [API] rename PointerEvent.Type to Kind
Kind is the idiomatic field name for distinguishing a struct without
using separate types.

Signed-off-by: Elias Naur <mail@eliasnaur.com>
2023-10-06 19:11:08 -05:00
Chris Waldon ddf770b9d5 widget{,/material}: surface line height manipulation
This commit surfaces fields to manipulate the line height of all label and editor
types. It's unfortunate how this spreads through the API, but I don't see a good
way to eliminate that right now.

Signed-off-by: Chris Waldon <christopher.waldon.dev@gmail.com>
2023-07-19 10:05:33 +02:00
Chris Waldon c6e4eecf21 go.*,text,widget{,/material}: enable configurable line wrapping within words
This commit enables consumers of the text shaper to select a policy for how
line breaking candidates will be chosen. The new default policy can break lines
within "words" (UAX#14 segments) when words do not fit by themselves on a line.
This ensures that text does not horizontally overflow its bounding box unless
the available width is insufficient to display a single UAX#29 grapheme cluster.

Fixes: https://todo.sr.ht/~eliasnaur/gio/467
Signed-off-by: Chris Waldon <christopher.waldon.dev@gmail.com>
2023-06-07 16:41:14 -06:00
Elias Naur a252394356 widget: update Editor.Delete documentation to refer to graphemes
Signed-off-by: Elias Naur <mail@eliasnaur.com>
2023-06-07 11:29:36 -06:00
Chris Waldon f77bf9a42c font/opentype: [API] support font collection loading
This commit adds back support for loading font collections, which we
lost when switching to the harfbuzz-based shaper last January. In
addition, this commit takes advantage of our new font loading library's
metadata facilities to automatically construct text.FontFaces for all
fonts within a collection. This is significantly more ergonomic for
users, and can be used to load single fonts with automatic metadata
detection as well.

I've exposed a opentype.Face.Font() method that can be used to get the
font metadata for a given face as well, though you have to type assert to
see it:

var myFace text.Face
if asOpentype, ok := myFace.(opentype.Face); ok {
    myFont := asOpentype.Font()
}

The one problem with this approach is that the font variant field always
be automatically populated. Mono font detection is supported, but
other variants like SmallCaps are more complicated and may need to be
expressed differently in the future (smallcaps is a feature that any font
file can have, not necessarily a separate font file). See this [0] upstream
issue for details.

Additionally, in order to avoid import cycles, I've moved the declarations
of font attributes to package font. You can fix your code automatically to
refer to the new definitions by running the following:

    gofmt -w -r 'text.FontFace -> font.FontFace' .
    gofmt -w -r 'text.Variant -> font.Variant' .
    gofmt -w -r 'text.Style -> font.Style' .
    gofmt -w -r 'text.Typeface -> font.Typeface' .
    gofmt -w -r 'text.Font -> font.Font' .
    gofmt -w -r 'text.Regular -> font.Regular' .
    gofmt -w -r 'text.Italic -> font.Italic' .
    gofmt -w -r 'text.Thin -> font.Thin' .
    gofmt -w -r 'text.ExtraLight -> font.ExtraLight' .
    gofmt -w -r 'text.Light -> font.Light' .
    gofmt -w -r 'text.Normal -> font.Normal' .
    gofmt -w -r 'text.Medium -> font.Medium' .
    gofmt -w -r 'text.SemiBold -> font.SemiBold' .
    gofmt -w -r 'text.Bold -> font.Bold' .
    gofmt -w -r 'text.ExtraBold -> font.ExtraBold' .
    gofmt -w -r 'text.Black -> font.Black' .
    gofmt -w -r 'text.Hairline -> font.Thin' .
    gofmt -w -r 'text.UltraLight -> font.ExtraLight' .
    gofmt -w -r 'text.DemiBold -> font.SemiBold' .
    gofmt -w -r 'text.UltraBold -> font.ExtraBold' .
    gofmt -w -r 'text.Heavy -> font.Black' .
    gofmt -w -r 'text.ExtraBlack -> font.Black+50' .
    gofmt -w -r 'text.UltraBlack -> font.ExtraBlack' .

Make sure each affected file imports gioui.org/font.

[0] https://github.com/go-text/typesetting/issues/57

Signed-off-by: Chris Waldon <christopher.waldon.dev@gmail.com>
2023-04-18 16:22:48 -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 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 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
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
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
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 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 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
Chris Waldon 719278bb36 widget: unify text painting and fix premature termination
This commit unifies all widget text painting to use a single function
and fixes two bugs that could result in visible glyphs failing to be
painted.

The first bug was that we checked whether a particular glyph's
outline was visible within the viewport and terminated iteration the
first time that we found a glyph that wasn't visible. If the very top
of the next line of text was visible within the viewport, taller glyphs
should be painted since part of them is visible. We would stop as soon
as we got to a short glyph, preventing the rest of the line (and any
tall glyphs it contained) from being painted.

I fixed this first problem by using the ascent/descent of the line containing
a glyph to determine whether it's "visible". While this will conclude that
a small glyph is visible when it may be entirely off-screen, the net result
will be that we will paint the entire line containing the glyph rather than
constructing a special version of the line with only the tall glyphs. This
has better path caching performance, as we don't need a bespoke path for when
the line is partially visible.

The second bug was that when the glyph iterator concluded that the
current glyph was out of the viewport, we would immediately terminate
the loop for painting glyphs without painting any buffered glyphs that
had been determined to be visible.

This second bug was easily fixed by ensuring that we always paint all buffered
glyphs when terminating iteration.

As part of this work, I pulled the (fairly complex) logic of buffering and
painting glyphs into the glyph iterator so that label and editor can share
a single implementation.

I was unable to completely encapsulate the array storing buffered glyphs within
the iterator without it being moved to the heap, so the current glyph iteration
API requires the caller to juggle a slice of glyphs. Hopefully someone in
the future can find a structure that the compiler's escape analysis understands.

Signed-off-by: Chris Waldon <christopher.waldon.dev@gmail.com>
2022-12-14 09:00:31 -06:00
Chris Waldon b7d126e24c font/{gofont,opentype},text,widget{,/material}: [API] add font fallback and bidi support
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>
2022-12-13 22:06:57 -06:00
Elias Naur c67d8cde4b widget: implement triple click line selection in Editor
Fixes: https://todo.sr.ht/~eliasnaur/gio/455
Signed-off-by: Elias Naur <mail@eliasnaur.com>
2022-11-07 07:30:04 -06:00
Chris Waldon f7c14e9964 widget: redefine >= and ++ on combinedPos
This commit redefines incrementing a combinedPos to either move a single
rune forward, *or* transition from EOL->BOL, *or* both. This allows traversal
of lines without a trailing newline character to reach the position after the
final glyph of content.

Additionally, this commit updates positionGreaterOrEqual to explicitly handle
hard newlines via special-case logic, allowing lines without a hard newline to
avoid the newline-based short-circuit logic that would prevent them from iteratively
reaching the combinedPos following the final glyph on the line.

Fixes: https://todo.sr.ht/~eliasnaur/gio/400

Signed-off-by: Chris Waldon <christopher.waldon.dev@gmail.com>
2022-10-22 18:19:51 -06:00
Chris Waldon 2340664570 widget: test and document seekPosition
This commit adds a test for the seekPosition helper, a function which can
be used to move a combinedPos forward through a body of text until it approaches
a position.

Signed-off-by: Chris Waldon <christopher.waldon.dev@gmail.com>
2022-10-22 18:19:46 -06:00
Chris Waldon dee3cc44f9 widget: test positionGreaterOrEqual
This commit adds an exhaustive test case for the positionGreaterOrEqual
helper function that our text widgets use to compare locations within
shaped text.

Signed-off-by: Chris Waldon <christopher.waldon.dev@gmail.com>
2022-10-22 18:19:41 -06:00
Chris Waldon 64db3720fc widget: test and document clusterIndexFor
This commit adds documentation and tests for the clusterIndexFor helper,
making it easier to understand what it does and how to use it safely.

Signed-off-by: Chris Waldon <christopher.waldon.dev@gmail.com>
2022-10-22 18:19:35 -06:00
Chris Waldon b67b322978 widget: define incrementing combinedPos and test
This commit restructures seekPosition from a complex state-manipulating
loop into a simple loop of iteratively applying an increment operation
to the combinedPos. The increment operation itself is now tested, and
much easier to understand.

Signed-off-by: Chris Waldon <christopher.waldon.dev@gmail.com>
2022-10-22 18:19:19 -06:00
Chris Waldon b46c0f5907 widget: use reliable text direction checks, not heuristics
This commit switches the way in which the editor and helper functions check
for RTL text from a heuristic to using the actual text direction.

Signed-off-by: Chris Waldon <christopher.waldon.dev@gmail.com>
2022-10-22 18:19:08 -06:00
Elias Naur 24eb1a4fc5 widget: make the InputOp key.Set empty for unfocused Editors
Fixes: https://todo.sr.ht/~eliasnaur/gio/448
Signed-off-by: Elias Naur <mail@eliasnaur.com>
2022-09-24 08:48:30 -06:00
Elias Naur a55065af9c widget: take deleted runes into account when applying Editor.MaxLen
Signed-off-by: Elias Naur <mail@eliasnaur.com>
2022-08-08 16:15:21 +02:00
Elias Naur 5fc9312f46 widget: report SubmitEvents for IME newlines in submit mode
Before this change, an IME text edit would always have its newlines
replaced with spaces. However, for Editors where Submit is enabled
we want newlines to result in SubmitEvents.

Signed-off-by: Elias Naur <mail@eliasnaur.com>
2022-08-08 15:43:36 +02:00
Elias Naur f7bc744a24 widget: add Editor.Filter for filtering unwanted characters
Signed-off-by: Elias Naur <mail@eliasnaur.com>
2022-07-26 15:22:27 +02:00
Chris Waldon 41de0048db widget: implement editor undo/redo
This commit adds a simple linear-history undo/redo mechanism to
widget.Editor bound to Short-(Shift)-Z as well as tests for this
new feature.

Notes on the implementation:

- using a slice to hold the history does mean that we incur
  allocations as the user types, but I hope that the Go slice
  growth heuristic means that the number of times we pay this
  penalty is very small. We also never shrink the slice in this
  implementation, which ensures that undoing work and then making
  additional modifications is very efficient, but could be framed
  as a memory leak.
- this implementation creates a new history element every time
  we call replace(). This means that, on desktop, it's essentially
  one per rune of input. Users likely want to be able to undo larger
  units of change, so a future improvement could be to coalesce
  changes so long as the selection doesn't change between them.
- I think it's possible to store only one of the Apply/Reverse
  change contents in the history slice, but it's significantly
  more complicated. To implement this, you'd need to add a field
  indicating if the modification represented a forward or backward
  change, and then rewrite the modification's content as you performed
  undo/redo operations.For the time being, I'm not sure it's worth
  this complexity.
- Future work could introduce a limit to the number of history
  entries stored. If we did this, we should also change the
  data structure for storing history. Enforcing such a limit
  using a simple slice like this would be extremely inefficient.
  Perhaps a ring buffer or a linked list would make more sense?
- Applications will likely want to be able to manipulate undo
  history in the future. We may wish to export undo() and redo()
  from the editor. Applications will also likely want a mechanism
  to save the undo history to disk and restore it (implementing
  persistent undo). I'm not sure what the most suitable API for
  that is yet, so I decided not to try to tackle it yet.

Signed-off-by: Chris Waldon <christopher.waldon.dev@gmail.com>
2022-07-17 14:45:59 +02:00
Elias Naur 1d9ab65313 widget: add Editor.MaxLen for limiting the content length og Editor
Signed-off-by: Elias Naur <mail@eliasnaur.com>
2022-07-13 18:45:02 +02:00
Elias Naur 48e9cdaffd widget: emit only one ChangeEvent per Editor.Layout
ChangeEvent contains no information, so emitting multiple instances
per layout is pointless.

Signed-off-by: Elias Naur <mail@eliasnaur.com>
2022-07-13 18:13:59 +02:00
Elias Naur 916efb4612 all: apply suggestions from staticcheck.io
Signed-off-by: Elias Naur <mail@eliasnaur.com>
2022-06-07 12:28:28 +02:00
Elias Naur 3d37491342 all: [API] replace unit.Value with separate unit.Dp, unit.Sp types
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>
2022-05-31 10:24:09 +02:00
Elias Naur a63e0cb44a all: [API] change op.Offset to take integer coordinates
op.Offset is a convenience function most often used by layouts. Layouts
usually operate in integer coordinates, and the float32 version of op.Offset
needlessly force conversions from int to float32. This change makes op.Offset
take integer coordinates, to better match its intended use.

Signed-off-by: Elias Naur <mail@eliasnaur.com>
2022-05-31 10:24:09 +02:00
Mearaj 7ced0d29ab app,widget: use arrow keys for Android navigation
Android doesn't distinguish between the arrow keys on a keyboard and the
directional keys on a remote control, so there's no way to move the caret
in an Editor with arrow keys. This change updates the Android port to map
Android's DPAD_* key codes to the arrow key names, fixing caret movement.
The change also updates Editor to only request arrow keys that actually move
the caret, to keep directional focus movement working.

Fixes: https://todo.sr.ht/~eliasnaur/gio/410
Signed-off-by: Mearaj <mearajbhagad@gmail.com>
2022-05-10 16:41:32 +02:00
Chris Waldon 4996337d26 widget: ensure empty editor makes space for caret
Prior to this change an editor with no content and a zero minimum
constraint would return itself has having width zero. This
prevented users from being able to see the editor when they
moved focus to it, as it could not display its caret. This
simple change ensures that, at minimum, the editor returns
its dimensions to include the width of a caret.

Signed-off-by: Chris Waldon <christopher.waldon.dev@gmail.com>
2022-05-07 09:14:10 +02:00
Elias Naur 3c45a6d420 widget: don't draw Editor selection when not focused
Signed-off-by: Elias Naur <mail@eliasnaur.com>
2022-04-23 15:45:41 +02:00
Elias Naur 6ddc13ce66 widget: fix Editor key set
Arrow and delete/backspace shortcuts use ShortcutAlt, not Shortcut.

References: https://todo.sr.ht/~eliasnaur/gio/399
Signed-off-by: Elias Naur <mail@eliasnaur.com>
2022-04-19 08:58:47 +02:00
Elias Naur 380f96b3fc io/key: [API] implement key event propagation
Before this change, every Event would be passed to the focused InputOp
tag, making it impossible to implement, say, program-wide shortcuts.
This change implements key.Event routing similar to how pointer.Events
are routed: every InputOp describes the set of keys it can handle, and
the router use that information to deliver an Event to the matching
handler.

This is an API change, because every InputOp must now include a filter
matching the keys it wants to handle.

Fixes: https://todo.sr.ht/~eliasnaur/gio/395
Signed-off-by: Elias Naur <mail@eliasnaur.com>
2022-04-14 19:09:00 +02:00
Elias Naur b2d10c2f28 widget: include the Editor key handler in the editor clip area
A meaningful clip area for a key handler will matter when we start
auto-scrolling to move focused handlers into view.

Signed-off-by: Elias Naur <mail@eliasnaur.com>
2022-03-30 22:20:27 +02:00
Chris Waldon 7daab97fab widget: [API] make text.Alignment direction-sensitive
This commit ensures that text.Alignment is intuitive for
the direction of the text being aligned. RTL text with
Alignment Start will be aligned to the right edge of the area,
whereas LTR text with Alignment Start will continue to be
aligned to the left edge. Vice versa for the End alignment.

References: https://todo.sr.ht/~eliasnaur/gio/146
Signed-off-by: Chris Waldon <christopher.waldon.dev@gmail.com>
2022-03-18 08:05:06 +01:00
Chris Waldon 9576b659d7 text: [API] remove Text and Advances from Layout
These fields are no longer needed with the new text shaper.
Advances is redundant to the glyph information, and Text
should never be used during layout, as you should
traverse the cluster list instead. This commit also removed
the now-unused string field from the path LRU cache key.

References: https://todo.sr.ht/~eliasnaur/gio/146
Signed-off-by: Chris Waldon <christopher.waldon.dev@gmail.com>
2022-03-18 08:04:27 +01:00
Chris Waldon 42c99a5cb2 widget{,/material}: [API] update editor to support complex scripts
This commit updates material.Editor and material.Label to support the
new text shaper. This requires breaking their assumption that glyphs
of font data map 1:1 to runes of text data.

References: https://todo.sr.ht/~eliasnaur/gio/146
Signed-off-by: Chris Waldon <christopher.waldon.dev@gmail.com>
2022-03-18 08:03:46 +01:00
Chris Waldon 1e5a3696f5 deps,text,widget,font/opentype: [API] add harfbuzz-powered text shaper
This commit introduces a new text shaping infrastructure
powered by Benoit Kugler's Go source-port of harfbuzz.
This shaper can properly display complex scripts and RTL
text. This commit changes the signature of the text.Shaper
function, which is a breaking API change.

The new functionality is available via opentype.ParseHarfbuzz,
which configures a text.Shaper leveraging the new backend.

References: https://todo.sr.ht/~eliasnaur/gio/146
Signed-off-by: Chris Waldon <christopher.waldon.dev@gmail.com>
2022-03-18 08:01:44 +01:00
Chris Waldon b0ab5ae06e widget: remove unneeded editor flicker logic
We cannot find a way to trigger this flickering
condition anymore, and so we're removing the logic
guarding against it.

Signed-off-by: Chris Waldon <christopher.waldon.dev@gmail.com>
2022-03-17 09:05:35 +01:00
Chris Waldon 1ad785658e widget: optimize painting editor selection
This commit introduces logic to skip painting the
selection rectangle on lines prior to the line
containing the beginning of the selection.

Signed-off-by: Chris Waldon <christopher.waldon.dev@gmail.com>
2022-03-17 08:15:31 +01:00
Egon Elbre cdb288d1f9 app,io/pointer: [API] remove CursorNameOp and rename CursorName -> Cursor
It's now possible to directly user pointer.Cursor to add to the ops.

   pointer.CursorText.Add(gtx.Ops)

This is an API change. Use pointer.Cursor directly instead of CursorNameOp.

Signed-off-by: Egon Elbre <egonelbre@gmail.com>
2022-03-01 14:05:46 +01:00
Chris Waldon a401d7aaff widget: fix Editor.CaretCoords when scrolled
This commit fixes the position returned by Editor.CaretCoords
to account for the scroll position of the editor. Without this
change, the returned coordinates can easily overflow the boundaries
of the editor widget when it has been scrolled on either axis.

Signed-off-by: Chris Waldon <christopher.waldon.dev@gmail.com>
2022-03-01 10:25:38 +01:00
Elias Naur 8ff10a2068 widget: only ask for software keyboard once per click
Signed-off-by: Elias Naur <mail@eliasnaur.com>
2022-02-26 17:27:51 +01:00