Commit Graph

337 Commits

Author SHA1 Message Date
Chris Waldon 5c84cf7e90 widget: do not allow invalid utf8 in editor
This commit replaces invalid UTF8 codepoints with the replacement character
when they are inserted into the editor. This ensures that the editor never
moves the editing gap to an invalid location and reads its contents.

Signed-off-by: Chris Waldon <christopher.waldon.dev@gmail.com>
2022-11-16 16:22:43 -06:00
Chris Waldon 4f5a6b3212 widget: define text rendering benchmarks
This commit adds a series of benchmarks for text rendering. They are intended
to capture the performance of static and continuously changing text within
labels and editors, and will serve as a baseline to compare the post-bidi
text stack against.

Signed-off-by: Chris Waldon <christopher.waldon.dev@gmail.com>
2022-11-09 08:45:51 -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
Elias Naur 5c896eabbb gesture,widget: detect multi-click on pointer.Press
References: https://todo.sr.ht/~eliasnaur/gio/455
Signed-off-by: Elias Naur <mail@eliasnaur.com>
2022-11-07 07:26:19 -06:00
Chris Waldon 9f62230c38 widget: adjust editor tests to new pos iteration
This commit fixes the expectations of our ligature iteration tests to
match the new behavior of the text position iterator. Now the cursor
can reach the position after the final glyph on a line, if that glyph
is not a newline.

Signed-off-by: Chris Waldon <christopher.waldon.dev@gmail.com>
2022-10-22 18:19:56 -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 1be58a2bc4 widget: test firstPos
This commit adds a test to lock in the correct behavior of the
firstPos helper method.

Signed-off-by: Chris Waldon <christopher.waldon.dev@gmail.com>
2022-10-22 18:19:15 -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
Chris Waldon 020eb27ff5 widget: add useful state accessors to scrollbar
This commit adds methods to widget.Scrollbar that enable consuming
code to check if the scroll indicator is processing a drag gesture
or if the scroll track is currently being hovered. These accessors
enable scrollbar style types to have enough information to hide the
scroll indicator when it isn't needed, whereas currently they cannot
differentiate between a scrollbar indicator that is being dragged
but hasn't moved since the last frame and a scrollbar indicator that
is not being dragged.

Signed-off-by: Chris Waldon <christopher.waldon.dev@gmail.com>
2022-08-29 15:11:51 +02: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 61b2e37691 all: format comments with go fmt ./... using Go 1.19
Signed-off-by: Elias Naur <mail@eliasnaur.com>
2022-08-06 12:26:03 +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
Elias Naur 26e71011f5 widget: don't let unfocused Clickables swallow key presses
Signed-off-by: Elias Naur <mail@eliasnaur.com>
2022-07-25 10:57:22 +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 53da73de35 widget: ensure that Border.Layout dimensions fully contains the border
Signed-off-by: Elias Naur <mail@eliasnaur.com>
2022-07-10 14:59:50 +02:00
Egon Elbre b4acc239cd widget: fix Enum with Return
The input op started listening to Return, however the check
was looking for Enter.

Signed-off-by: Egon Elbre <egonelbre@gmail.com>
2022-07-07 09:36:43 +02:00
Dominik Honnef 992f568ac7 widget: when clicking on scrollbar, center on that point
Previously, we'd scroll so the new viewportStart corresponded to the
clicked position. This felt okay if clicking above the current
indicator, but felt jarring when clicking below it. Centering gives a
consistent behavior regardless of the scroll direction.

Signed-off-by: Dominik Honnef <dominik@honnef.co>
2022-06-30 08:01:49 +02:00
Dominik Honnef 6981a88720 widget: move scrollbar indicator if dragging starts outside of it
Signed-off-by: Dominik Honnef <dominik@honnef.co>
2022-06-30 08:01:49 +02:00
Dominik Honnef f229601e2d widget: consider size of indicator when limiting scrollbar dragging
Signed-off-by: Dominik Honnef <dominik@honnef.co>
2022-06-30 08:01:49 +02:00
Dominik Honnef aea376fbaf widget: clicking on the scrollbar indicator shouldn't jump
Signed-off-by: Dominik Honnef <dominik@honnef.co>
2022-06-30 08:01:49 +02:00
Dominik Honnef 8f990a6fdc widget: correctly set s.dragging to false when releasing drag
Before, we would set s.dragging to false on pointer.Release and then
immediately set it back to true because we were processing the event and
saw that s.dragging was false.

Signed-off-by: Dominik Honnef <dominik@honnef.co>
2022-06-30 08:01:49 +02:00
Dominik Honnef ea37124686 widget: constrain drag offset to [0, 1]
Once the user begins dragging, the cursor can move outside the clip
area (or even the window on at least X11), leading to events with
positions that are either negative, or larger than the clip area.

Negative values outright break the delta tracking and cause the
scrollbar to misbehave. Positive values "only" break the invariant of
Scrollbar.ScrollDistance that the returned value is in the range [-1, 1].

Signed-off-by: Dominik Honnef <dominik@honnef.co>
2022-06-30 08:01:49 +02:00
Elias Naur 3f38e67ce0 io/system: add ActionInputOp to register window move gesture areas
The app.Window.Perform(ActionMove) is the wrong abstraction for
initiating a move gesture: Windows needs to know the move gesture
area at pointer move, and macOS needs to know the pointer button
down event that triggers the move gesture. This change replaces
Perform(ActionMove) with a new system.ActionInputOp that marks an
area movable.

Signed-off-by: Elias Naur <mail@eliasnaur.com>
2022-06-25 19:40:46 +02:00
Elias Naur b53cdfef8d io/system: remove resize actions
Allowing clients to initiate resize gestures is a waste: macOS
doesn't support them, and the only reason we added them was to
implement client-side decorations for Wayland. Now all desktop
platforms implement resize gestures as needed, and we no longer
need the system.Action actions.

Signed-off-by: Elias Naur <mail@eliasnaur.com>
2022-06-25 18:41:32 +02:00
Elias Naur 8a9382940a widget/material: make DecorationsStyle method receivers by-value
Style values are ephemeral, and pointer methods can't be called in
the same expression a style value is constructed. Matches other style
types.

Signed-off-by: Elias Naur <mail@eliasnaur.com>
2022-06-24 19:56:56 +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 48a8540a68 all: [API] change clip.RRect and UniformRRect to take integer coordinates
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>
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
Elias Naur 14805af367 gesture,widget,f32: [API] use integer coordinates for gesture coordinates
Most widget code operate in integer coordinates. This change makes
gesture pointer coordinates integer, to lessen the number of float32
to int conversions.

Signed-off-by: Elias Naur <mail@eliasnaur.com>
2022-05-31 10:24:09 +02:00
Chris Waldon 87be31cbec widget/material: ensure scrollbar within dimensions
This commit fixes a visual-only bug in the ListStyle that could
make the scrollbar float at the edge of the maximum constraints
when the list did not occupy the full constraints. The list
would still reserve layout space for the scrollbar in the correct
position, but the scrollbar would not be displayed there.

Signed-off-by: Chris Waldon <christopher.waldon.dev@gmail.com>
2022-05-31 09:37:27 +02:00
Chris Waldon 99d0332067 widget/material: prevent invalid list item constraints
Previously, a bug in the ListStyle could result in items being
passed a negative value in the minimum constraints.

Signed-off-by: Chris Waldon <christopher.waldon.dev@gmail.com>
2022-05-31 09:37: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 2381c5ad70 io/router,widget: give every key.InputOp a chance to process events
If the currently focused handler don't want the key event, try every
other handler, from top to bottom. This change requires widgets to
only react when focused.

Fixes: https://todo.sr.ht/~eliasnaur/gio/406
Signed-off-by: Elias Naur <mail@eliasnaur.com>
2022-04-23 15:36:45 +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 7629874237 widget/material: remove redundant offset op
Signed-off-by: Elias Naur <mail@eliasnaur.com>
2022-04-19 08:47:15 +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 8833a6738a widget: drop debug prints from tests
This commit removes some lingering editor debug prints
from the test code.

Signed-off-by: Chris Waldon <christopher.waldon.dev@gmail.com>
2022-03-18 08:05:19 +01: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