Working Group for https://github.com/bevyengine/bevy/issues/23014
#Text Input
449 messages Β· Page 1 of 1 (latest)
Awesome
I'm not sure there's much to discuss here; we know what we want, pretty much, and what we need to do. It's just a question of doing it.
@cedar sentinel 's earlier PR, based on cosmic-edit, had pretty much everything I could have asked for; it had a few rough edges but it worked.
Yep there's really not much to do to make a minimal implementation either, I've got most of it done just not had time yet this week to put everything together
Let me summarize what we know:
- We know what features we eventually want (there are several lists, see alice's PR)
- We know roughly the shape of the API, the people working on this have generally good instincts/taste for this sort of thing
- We know what an MVP looks like (Alice's PR sketches this out)
- I have a pretty good idea of what the feathers wrapper will look like (focus rings, adornments like "clear" "search" icons and so on)
I guess there is some controversy around whether or not we are going to adopt a general undo solution
But that can be decided post-MVP
yeah, I'm just going to leave out undo and copy and paste from the initial PR
I can't be bothered to deal with the discussions etc
If it were me, I'd use undo_2 and arboard. While Bevy tries to be minimal dependencies, this is not going to happen with clipboard support.
yes that's what i'd do too, and what I used for my other implementations
only minor snag is how to handle async clipboard access
Yeah, no real choice there for clipboard support IMO. It needs OS bindings
The plain-text clipboard support is so simple that it seems not very controversial to me. It's ~20 LoC in Blitz. And half of that is matching on the keyboard events that trigger the actions. So it'd be easy to just add it and migrate to something else later if desired.
I don't just want plain text support, though. I want the bevy editor (and other editors) to be able to cut and paste parts of scenes or other media. And I don't want to have two clipboard impls, one for text input and one for everything else.
I agree, but starting with plain text seems very sensible
Does seem worth it to reinvent it though. We're probably not going to write a complete clipboard impl for ourselves, and we'll need it => we'll eventually use something like arboard. So might as well do that now. (Unless there's some argument against arboard but it seems good to me as an outsider.)
I think that Bevy, as a team, is not well set up to do platform-specific code; we rely on crates like winit and others to isolate platform differences. The reason is that most Bevy developers don't have a bunch of different devices to test on.
Clipboard support is one of those areas where the different OSs have significantly different features and semantics, and coming up with a "least common denominator" (other than plain text) is a non-trivial puzzle.
Yeah that's exactly why I don't foresee bevy pulling through this effort
Ime clipboard is one of those domains where people are tolerant of jank for a while
If it isn't supported at all, that's too far. If it's limited but works in an intuitive way (binary blobs don't get dumped into text input boxes) there is understanding.
Undo is a different story; we could certainly write our own undo manager, as it doesn't rely on any platform APIs; however, coming up with an abstraction that satisfies the needs of every stakeholder (text input, bevy editor, third-party editors, etc.) is going to require a very broad vision, and the existing third-party crates have already solved this adequately.
Up to this point we've seen numerous proposals for undo coming from stakeholders that solve that particular stakeholder's needs.
Do we know how other projects (i.e. Godot, blender) solve this?
I don't know if that's relevant. Undo as a concept is not hard. The challenges are translating that into a Rust and ECS context, which wouldn't apply to Godot or Blender.
I've done undo many times in different languages (TypeScript, C#, C++, etc.)
I think that comment was WRT clipboards π
Oh!
Actually, the question that has me scratching my head is "how do browsers solve this?"
Yep! Should have been more clear, sorry!
Same kind of scale project, to be fair.
That is, there's a perfectly nice API exposed to JavaScript in the browser. But I don't see how you get from there to the Windows clipboard API.
In Linux and MacOS, clipboard types are identified by MIME types, but windows has its own bespoke system of type ids.
Crates like arboard offer an abstraction which somehow papers over the fact that windows is an outlier.
I'm looking through the firefox repo and I'm struggling to get a clear answer, there's a lot of different contexts in which a clipboard needs to be wrapped. Mozilla has its XPCOM framework to abstract over these things, with nsiClipboard/nsiClipboardHelper being the central classes clipboard functionality is based around. I'll be back in a bit if I find anything more.
Worth noting that this is not a Bevy-specific thing and seems to apply to basically everyone doing GUI in Rust. Which is why Winit doesn't have these features in the first place (and doesn't even have a maintainer for all platforms).
Regarding browsers: https://www.w3.org/TR/clipboard-apis/#mandatory-data-types-x
And specifically for the OS mappings https://www.w3.org/TR/clipboard-apis/#to-well-known-mime-type-from-os-specific-format
the nsiTransferable class represents type as a mime type, which lends more credence to the idea that what people do with windows' inputs is translate them to look like unix inputs and just build around that (assuming windows doesn't provide a mime type, I don't have a windows machine nor do I work with windows).
On Windows, follow the convention described below:
- If osFormatName "UnicodeText", then set mimeTypeString to "text/plain".
- Else, if osFormatName "HTML Format", then set mimeTypeString to "text/html".
- Else, if osFormatName "PNG", then set mimeTypeString to "image/png".
- Else, if osFormatName CFSTR_MIME_SVG_XML, then set mimeTypeString to "image/svg+xml".
so there is just a consensus in cross-platform management by "windows is weird and inconsistent, match on its inputs and make it look like unix"
which would also mean, trivially, that individual conversions from an OS representing things as a non-mime-type string is just a match statement.
Back to the topic of Undo for a moment. An open question is whether we want to implement "classic" undo, or GURQ-style undo, as implemented in undo_2 and described in this paper: https://github.com/zaboople/klonk/blob/master/TheGURQ.md
"Classic" undo has two stacks: an undo stack and a redo stack, invoking the undo command pops an entry off the undo stack and pushes it on the redo stack.
The difference between these is in classic undo, typing (or doing any edit other than an undo or redo) clears the redo stack.
That means if you undo a whole bunch of edits, and then type a single key, you can no longer redo those edits. This is the way that most apps work.
In GURQ-style undo, there's only a single timeline, and making edits does not clear the redo chain, so you don't lose anything. The downside is that users may not be used to this behavior.
If we were going to go with classic undo, then I would vote for building our own; the two-stack design is trivial.
If we wanted to use GURQ-style undo, however, the algorithm is subtle and somewhat non-intuitive, I'd rather use a battle-tested crate than roll our own.
the GURQ style sounds a bit like a right-click-to-select situation
I'm sure there's people who swear by it but it doesn't make much sense as a default.
I've put together some of @dire ginkgo and @cedar sentinel 's work to have a super minimal solution https://github.com/bevyengine/bevy/pull/23282 - still a bunch of rough edges
GitHub
Objective
Minimal unstyled Text Input, using Parley
Solution
Initial work based off of @alice-i-cecile and @ickshonpe 's branches
EditableText component wraps a parley::PlainEditor
Edits...
I'll take a look today!
I suggest you go commit-by-commit
Super exciting to see that you managed to cobble our work together. Thank you! I've left a smattering of comments here; none of which should be super surprising. Just a bunch of cleanup and small fixes to do now IMO
hiya! coming over from jackdaw - we are heavily relying on bevy_ui_text_input for pretty much all of our text input widgets. was wondering if i could help here at all π
23282 could use some help figuring out how to position cursors etc correctly. Looking at existing parley integrations will probably reveal the critical clues
@cedar sentinel I thought you had a new text input PR in the works, is that still happening?
Or are we going with @golden cypress 's work?
Mine is built on Alice and Ickshonpe's branches
We should combine efforts there at this point I think π
Figured out the cursor/ selection not being in the right spot - scale factor!
parley::PlainEditor is a bit annoying as it doesn't have many getters, this would help https://github.com/linebender/parley/pull/571/changes
New version is much simpler, doesn't unnecessarily measure twice
https://github.com/bevyengine/bevy/pull/23386 will be needed; please review
@golden cypress, reviewed https://github.com/bevyengine/bevy/pull/23282 again π
Thanks @dire ginkgo , should be good to go
Fixed a tiny issue for you and approved. @cedar sentinel, happy to merge once you take a look. Or let us know if you're busy and we'll find another reviewer: I think we've ironed out all the controversy here
No I'm free now again just grinding through it
The main problem left that's concerning me is scrolling support
And the API for editable value fields, an InputField<T> component seems ideal but I don't how well require works with generics
maybe if it just implements From/Into String it's probably okay
@cedar sentinel really nice review here, thanks
I'd like for someone to test bidi text
Although, if it doesn't work we can fix it later
Yeah, this is good "post-MVP" material
I felt bad finding so many problems with it when I haven't been that present lately, I'm not against merging it with minor changes and fixing the issues in follow ups
I'm in favor of this; I think I'll be much easier to split the work like that
Hey @cedar sentinel thank you for the review. I've π most of the comments, some I will need to check when I'm at my computer, and just a few I have some pushback on. I will push some changes then change back to S-Needs-Review
Okay would love your thoughts now π
Merging https://github.com/bevyengine/bevy/pull/23282 as an MVP now
We can and maybe should refine it a bit more before 0.19
But this should still be useful
I'd like to get https://github.com/bevyengine/bevy/pull/23425 merged as well, I've got line height and max char width measure funcs and mouse interaction stuff that can be just be dropped in
needs another review but it's not very complicated
I'm confused about the distinction between the ContentSize component and the content_size field of ComputedNode.
The content_size field of ComputedNode is the size of the content-box in css terms
the size of the node minus padding, margin, and scrollbar areas
What I'm complaining about is two things that have the same name but mean different things
yep should be changed
I think it's probably the ContentSize component that needs to be renamed
the docs need an overhaul as well and there should be a custom measure func example
Also; it seems like this is now effectively a layout input property, like the properties in Node; as you know, I have been a proponent of splitting up Node, but there's also a faction that believes that Node should be unitary. My question would be, is this intended to be a slippery slope introduction to a non-unitary Node?
not really, ContentSize is normally updated by the widget implementations, not users, so it shouldn't be included in Node
imo
I see, gotcha
See, when I read the old docs for ContentSize I thought it was something like width = auto
part of the problem is I wasn't really clear how it was meant to work when I added it originally, there were some confusing bugs in taffy and I got it working through trial and error mostly
One thing I was thinking - and I'm not advocating this, just pointing out an alternative - is that clipboard support could be handled externally, so long as ctrl-c and friends are allowed to propagate.
This would allow you to isolate clipboard support - and its dependencies - in a separate add-on.
in on_focused_keyboard_input if a key is pressed the event is consumed but the corresponding release event is propagated, is that a bug
might be being too fussy but: https://github.com/bevyengine/bevy/pull/23456
GitHub
Objective
In on_focused_keyboard_input if a key mapped to TextEdit is pressed the event is consumed but the corresponding release events are propagated.
Solution
In on_focused_keyboard_input use a ...
I'm not sure I follow, do you mean something like propagating the ctrl-c key press event up to an observer that would then dispatch another event with the clipboard contents to the text editor entity?
I was thinking maybe just calling replaceselection directly, but something like that yeah
it's complicated a bit because of clipboard access being async on web
The most important thing, I think, is to ensure that in an app that supports both clipboard use on an individual text field level, but which also supports cut/copy/paste at some larger scope (such as copying parts of a scene or node graph) that we handle both contexts consistently. This probably won't apply to most games, but will to some editors.
So for example, if we build in support for arboard at the text input level (a choice which is convenient for people who only need clipboard support at the text input level), then a user writing an advanced editor either has the choice to also use arboard, or to have two different clipboard crates in their app.
Reviewed, resolved merge conflicts and merging.
I'm going to cut and paste the list of text input features here, we should probably make a roadmap-style issue:
- Home / End key support for moving the cursor to the start / end of the text
- Placeholder text (displayed when the input is empty)
- Click to place cursor
- Cursor blinking
- Clipboard operations (copy, cut, paste)
- Undo/redo functionality
- Newline support for multi-line input
- Input Method Editor (IME) support for complex scripts
- Text validation (e.g., email format, numeric input, max length)
- Password-style character masking
- Soft-wrapping of long lines
- Vertical scrolling for multi-line input
- Horizontal scrolling for long lines
- Mobile pop-up keyboard support
- Overwrite mode (typically toggled by the
Insertkey) - Bidirectional text support (e.g., mixing left-to-right and right-to-left scripts)
- AccessKit integration for screen readers and other assistive technologies
- World-space text input
- Text input labels (used for accessibility, tooltips or form descriptions)
- Input consumption (preventing other systems from receiving keyboard input events when the text input is focused)
- Text form submission handling
Some of these appear to already be done, I'm just pasting what's in the comment
The comment says "these features are planned but currently not implemented" but that's not correct
We should also consolidate https://github.com/bevyengine/bevy/issues/20885 and https://github.com/bevyengine/bevy/issues/6213 with https://github.com/bevyengine/bevy/issues/23014
Actually, I think I'll just put these bullet points in a comment on the Goal issue
next looking into: https://github.com/bevyengine/bevy/issues/23470 and then I'll add intrinsic sizing changes this afternoon
Nice!
@cedar sentinel I'm particularly interested in the re-instatement of your validation logic, as that will be useful for the feathers numeric input widget
yep what design have you got in mind for the feathers input widgets?
there mostly shouldn't be that much difference as the construction for the input widgets is relatively trival because its only one entity
so there's no need for internal communication between entities which is where a lot of the complexity and sychronisation problems seem to come in usually
Well, there are two widgets I have in mind for numeric input: one for values that have a range (which will have a slider), and one for values that don't have a range (which will have a dragger). Both will have "click to edit" like Blender, whereby a click event without dragging will spawn or enable a text input widget
oh yes those dragger inputs I've always hated those but I've had to implement them before for work projects because some people for indecipherable reasons seem to love them
For regular text input, my plan is to have an outer frame entity which draws a border, and then contains internal decorations (like a "clear" button), with the actual text widget being an inner child.
oh yes and for numeric units you'd want to to display the units
That can just be a custom formatter callback
can be but it's maybe simpler just for it to be a separate text node and lay it out with the text node left to right
True, and there are other reasons to make it a separate node
selection might be tricky with a custom formatter too
Sometimes you have combo sliders with the slider label and the value all contained within the slider bounds
and someone might want the units to be a dropdown so you change from kg to pounds or something
For placeholders, we can either do them built-in to the text input widget, or we can do them externally at the feathers level.
- Making them built-in is convenient for people who just want a basic text input
- Making the placeholder external allows for greater stylistic flexibility - I've seen (on the web) widgets where the placeholders smoothly animate out of the way
Or more realistically, say I want the placeholders to be italic - building that flexibility into the text widget itself makes things more complex
for placeholder text I think a second node stacked on top occupying the same layout space is probably a simpler design, and then you just hide one by setting the color to transparent or whatever
We can just make an example showing how to do this
I made builtin placeholders on the same Node with my previous text input implementation and they were a pain
Specifically, what I saw was a version of quantum paper (an early incarnation of material design), where the placeholder turned into a title: the text shrank and moved above the input field. This was a bit overdone - artists being too clever
yep that sort of fanciness isn't uncommon in games
https://github.com/bevyengine/bevy/pull/23425 want another okay so this can get merged
hey @cedar sentinel i'm working on porting jackdaw over to your new text input stuff, but i see the cursor is basically static on all input fields, is this expected?
Yes blinking and disabling the cursor hasn't been added yet
Got it implemented but waiting for the current batch of prs to get merged
amazing, i was hoping it wasn't a bug on my end! i've been testing loads now in jackdaw and can find these features are missing (correct me if i'm wrong here):
- Ctrl+A (select all)
- Ctrl+C/V/X (copy/cut/paste)
- Home/End (line start/end)
- Ctrl+Left/Right (word jump)
- Click to place cursor
- Drag to select
and also correct me if any of these have open PRs
Blitz implements a lot of these on top of Parley in:
- Keyboard: https://github.com/DioxusLabs/blitz/blob/main/packages/blitz-dom/src/events/keyboard.rs#L102
- Mouse: https://github.com/DioxusLabs/blitz/blob/main/packages/blitz-dom/src/events/pointer.rs#L216
if anyone is looking for an implementation to copy-paste.
(it's mostly just translating the desired shortcut into a Parley action)
On macOS, it should be Meta rather than Ctrl
yep all those have open PRs waiting for final review: https://github.com/bevyengine/bevy/pull/23475, https://github.com/bevyengine/bevy/pull/23479
woo! i might be able to cherry-pick those PRs and test them in jackdaw if you like?
they've got enough reviews I think but got more PRs I'm going to publish in an hour or two
just wanted to say this amazing work - itβs accelerated my removal of bevy_ui_text_input as a dependency
https://github.com/bevyengine/bevy/pull/23494 intrinsic height support
Merging. I must have missed pressing the button yesterday
Approved with nits
Q: It's been a while since I used a terminal that had a blinking cursor; do you remember if the blink rate is completely independent of user action, or does the timer reset when the user types?
in vs code the cursor stays visible while you're typing
An idea for the future: when we exceed the limit (or fail validation) emit an event - this can be used to beep / flash or otherwise give feedback. Although TBH this should be a general part of the validation framework.
yep, maybe needs a mechanism to queue errors or suspend processing the edits buffer until the next update
oh queue errors what am I thinking of, that would just be an event
GitHub
Objective
Allow borders and padding on text and images.
Solution
Remove filter for border values on content sized nodes.
Apply content offsets to content in rendering and picking.
Testing
cargo run...
I've got scrolling working, once https://github.com/bevyengine/bevy/pull/23568 is merged I can push a PR and that's all the more technical PRs done
from then it's just bikeshedding the API and features like placeholder text, filters and overwrite cursors etc
@pallid hemlock @fervent mirage @pale pecan could I get a second approval from one of you?
done
My vote is for filters / validation. I'd like to update the feathers color picker demo to have an input field for hex rgb colors.
this is in π
I think for hex colors it's enough to just limit the input to six or eight characters and only allow 0..9 and a..f
Then there's no need for any validation, as every input is valid? Except for an empty string but that can just be interpreted as zero.
we don't have any notion of locking InputFocus yet right? That's useful as well for text inputs where you need some input from the user to proceed
This would have been easily accomplished using your previous input design
@cedar sentinel In order for EditableText to be used in BSN, it needs to impl either Clone or FromTemplate. Any thoughts?
PlainEditor is cloneable, so there's no problem with implementing Clone for EditableText
Also, I kind of wish that the text edit state and the configuration params were separated into different components
yeah I agree very strongly I've just been going along with the design of the initial PR for now
and figured once all the features are added it can get split up
If you look at this shot from the editor mocks, you can see that the text input has extra decorations like the filter dropdown button. I always assumed that feathers would add this stuff by wrapping the text input in an outer entity which would hold both the raw text input and the decorations. However, it means that when we want to get and set the state of the text input, we have to work with a child element.
that's the same for a lot of widgets
or is the current design making that more awkward?
No, it's feathers and bsn that's making it awkward
It also means that the text input is going to have to handle focus indicators in a custom way
because it's the opposite of a checkbox: the outer element wants an indicator when the inner entity is focused
which is fine; I kind of like focus outlines on text inputs to be inset rather than outset anyway
ah
yeah because the EditableText node has InputFocus
when normally with the other designs the root entity of the widget has the focus
@cedar sentinel Seeing a couple problems:
- Selection highlight drawn on top of text
- Can't figure out how to disable soft wrap
yep those were two problems with the inital PR implementation
I've fixed the wrapping in the scrolling branch I was working there was just edge cases with cursors I was trying to work out the cleanest way to fix
but
I'll make a PR
with just the basic changes
I can also push my WiP if it would be of any help
yeah it'll be good to have as many examples as we can get
I'd forgotten about the selection highlight thing it should be possible to set separate colors for highlighted text and text under the cursor as well
I've hooked up the TextCursorStyle to the theme system
fn update_text_cursor_color(
mut q_text_input: Query<&mut TextCursorStyle, With<FeathersTextInputInner>>,
theme: Res<UiTheme>,
) {
if theme.is_changed() {
for mut cursor_style in q_text_input.iter_mut() {
cursor_style.color = theme.color(&tokens::TEXT_INPUT_CURSOR);
cursor_style.selection_color = theme.color(&tokens::TEXT_INPUT_SELECTION);
}
}
}
there's some flaws, depending on the width of the node sometimes the cursor can get clipped, allow_newlines: false doesn't block pasting in a section of text with a newline
there's no support for scroll margins yet
Just a general comment. The allow_newlines option seems, erm, rather fine-grained in terms of customization options. In most traditional UI toolkits (Java Swing, HTML), you have a choice of either multiline or not. This controls both what you can type and how scrolling behaves. Of course, code editing apps don't fit this model, as they permit both horizontal and vertical scrolling.
I think I'd prefer a relatively opinionated API that offers them a few sensible choices:
- single line with no wrapping - useful for asset names in the editor, user login, passwords
- multi-line with soft wrap - useful for user chat, editing character dialogues
- and perhaps multi-line without soft wrap, useful for editing scripts (although it might be better to have a dedicated editor for this, one that supports syntax highlighting)
Yes I agree, the allow_newlines field is terrible, it's just meant to be temporary
But thinking about it maybe it makes sense to do the editabletext split up now as hsvong a more opinionated API would make it a lot simpler to fix the remaining issues
I mean, I could offer the simpler choices at the feathers level, but I think there could be advantages to doing this lower down
No it's much simpler to do it lower down, especially as like multiline without soft wrap is the problematic case and the least important
I'm ok with leaving that out for now then
Yeah numeric fields are far more important for instance
We can make the choice an enum rather than a boolean flag, so that if someone wants to add a third option later they can
Do you have an opinion on what the primary components should be called, I was calling single line inputs fields before and multiline inputs boxes
But some people seem to prefer it the other way around
On the web the former is called input (a terrible name) and the latter textarea.
I'd probably pick textinput and textarea
TextArea is okay
This would at least be familiar to web coders
It has the right implication of 2d sizeness
And textinput seems fine too
Let's go with those for now
BTW, on the web a single line text input is <input type="text">, so even though it's not literally called "textinput", if you say those words everyone will know what you mean.
Particularly as we are calling the other one an area
@cedar sentinel @livid cape I've decided to trade two awkwardnesses for one: separating the feathers text input into a separate "frame" widget like so:
(
:text_input_frame
Node {
flex_grow: 1.0,
}
Children [
(
text_input(TextInputProps {
max_characters: None,
})
HexColorInput
)
]
)
While this means that users have to know how to arrange the two widgets, it has some benefits:
- It means that we no longer have to worry about slots for adornments, they can just be regular children
- It means that users can access the text content directly instead of having to probe the widget's children to get access to the buffer.
I love this trade-off. They can learn from the examples
@cedar sentinel How hard would it be to emit an observable event when the text buffer changed?
My recollection is "quite easy"
I think I also need to come up with a general "lost focus" event
I've managed to hook up the color sliders to set the text input buffer to the hex color, but now I need to go the other direction
Yes, I was gonna do this on Monday (long weekend this week apparently)
Just a simple Focus Gained / Focus Lost pair
On the web, the "lost focus" event is called "blur" which is funny
For editing hex colors, we can either update the color as you type, or update on lost focus
I hate that lol. We can add a doc alias though
I don't care that much TBH
@cedar sentinel Another thing to mention on the topic of multiline: in single-line mode, ENTER should propagate - so that observers can treat that as a submit. In multiline mode, however, it should not propagate.
This goes with the general principle that "widgets consume what they use"
yep that's what I was thinking, any key press that isn't consumed must be propagated
Hmm, so this is weird, taking screen recordings on my Mac for text editing isn't working for me, maybe something to do with Focus changes?
i frequently hit that with egui apps and have to hit a modifier key to unblock some interactions
i forget which modifier key
@cedar sentinel @dire ginkgo So I'm still not clear on the "right" way to monitor the text buffer for changes. Since this is going to be part of the official feathers example, I want to do it the "canonical" way.
The use case I'm working on adding a hex color input field for the color picker, so that you can type ff88ff etc. I've decided to update the colors in real time rather than waiting for focus loss / submit, since I want the color swatch and sliders to update immediately on paste.
I can think of two ways to do this:
- Using regular change detection on the
EditableTextcomponent. However, this will likely trigger on cursor movement (since that also mutates the component), leading to lots of spurious updates. - Using observers. The problem here is that because text changes are queued, we don't want to react to changes until the queue is empty, after the changes have been applied. So the event emitter needs to be hooked into the queue machinery.
I'll also need to add logic to avoid cyclic updates: typing into the field modifies the color resource, which triggers replacement of the hex color text; we don't want that to happen while you are typing, so probably I will need to disable those updates while the input has focus.
We should use 1, but split apart EditableText to avoid that problem.
Unfortunately we can't split the parley PlainEditor
Hmm, right. Can we bypass change detection for cursor movement?
It's not just cursor movement, though. The editor has a bunch of state variables; we only want to react when the actual text content changes.
Mhmm, but we can lock down all of the APIs to mutate the internal editor, and selectively trigger change detection there
(and note this behavior)
Option 2 is to emit something like ValueChange after we have applied the changes to the buffer
Although ValueChange takes a payload (the new value) and I'm not sure we want to copy the text buffer for every event
Yeah, that may be better / simpler. I'm not actually convinced that my approach would work well, since our methods have &mut self, not Mut<self> π€
Hooking into the queue machinery here seems fine
What I think is copying/cloning the input contents doesn't matter too much
There is only one input active at a time and when they are it's usually exclusive
So we should just have like a TextInput(pub String) component
That's copied into the plaineditor on changes
And copied back after the textedit buffer is applied
Then everything will work seamlessly with change detection
One idea is to modify TextEdit::apply to return a boolean, indicating whether the action actually changed the text; then change apply_text_edits to trigger an event
I thought it already did something like this
You might be thinking of
editable_text.text_edited = !editable_text.pending_edits.is_empty();
That only tells us if there were any actions, not whether those actions changed the buffer or not. For example, we probably don't want to emit ValueChange on a Copy action
Three possibilities:
- ValueChange(String)
- ValueChange(()) and let them poll the buffer themselves
- a new event type,
TextEditChange
I think the first is easiest
Unless we're doing a full text editor for large files copying or cloning one string on frames where there is an edit won't matter
And even more so for non text inputs
As then it can just store the numeric value
Actually, I thought of a reason not to use ValueChange
For all other widgets, ValueChange is the proposed new value - because they are all controlled widgets
It's the difference between "here's what's going to happen" and "here's what's already happened"
oh right I see so it's where you click on a button -> an event is sent -> an observer system or something decides whether to update the button state
instead of directly updating the button state
Why not just emit an event at the end of EditableText::apply_pending_edits() (with an exit early if pending_edits is empty), and just eat the cost of cursor moves?
Yeah it seems like you've struck a solid balance to me.
Yes, but I have a caveat: I don't want this to become a habit. What I mean is, I dodged a bullet here by breaking up a unitary scene into fragments. But this is not a pattern that should be replicated too much - because it undermines scene integrity, it breaks encapsulation and exposes the internals of the scene to the caller.
Up to this point we haven't seen too many problems of this kind because all of the feathers widgets are leaf widgets
But I expect to see it crop up more as we start moving up the hierarchy
Yup I acknowledge the tradeoff being made here to work within the current constraints
I believe a combination of βstruct scenesβ, βBsn in Bsn ergonomicsβ, and reactivity are the path to building proper abstractions.
However I donβt think abstracting over structure is universally a good idea. I think the current approach has a lot going for it
https://github.com/bevyengine/bevy/pull/23646 seems to have gotten stuck on auto-merge
I just patched it in, seems to work. Now I just need the change notifications and I will have everything I need to wrap up the feathers text input
GitHub
Objective
Add an option to reject TextEdits based on a per character filter.
Solution
New component: EditableTextFilter. Can be used to set a per character filter for an EditableText entity.
TextE...
@dire ginkgo @cedar sentinel A comment about the hex color input, since you both had things to say about validation: I was trying to keep the example simple, and since Srgb::to_hex() produces a string with a leading # I went with that. A proper color picker would likely make the # a fixed decoration, which would also mean stripping the # or any other extraneous characters on paste or typing.
I don't feel super strongly about this, however
yes it's not that important atm
Maybe we can revisit this after character filtering is in
It can be useful to have the # editable for copying. I think a color picker that normalised it to having a # at the start on paste might be ideal. And it definitely ought accept a pasted color either with or without the #
Yep with "#" it doesn't bother me either way, we should go whatever users expect or whichever is the lowest friction option
@Talin I was going to start to split up EditableText next, and I was wondering if it would make sense to just get rid of the text edits buffer and make TextEdits into events
The honest answer is, I don't know. The "state" of the text input is more than just the text buffer, it's also the selection range. For now I'd go with whatever is simplest.
What's the goal of splitting it?
Well, one goal is to make change detection more fine-grained; another goal is to make it easier to override the initial state of the buffer. For example, if you have a standard text input widget which adds all the needed components, but you want to give it an initial value, right now you have to replace everything.
Yes, it's awkward for stuff like the line height measure function, either we need to put those options in another component or copy them somewhere so we can do a comparison to detect changes
Oooh neat. I wonder, can we use Mut::map_unchanged to selectively set the change detection tick?
well to detect changes to the layout with it you can just query for changed TextLayoutInfo or EditableTextGeneration
and there's no need to mess around with those flags any more
Lovely!
and then the EditableText isn't Changed on changes to the parley plain editor, as that can be tracked by EditableTextGeneration
What about observers?
we could add a EditableTextModified entity event that triggers on the generation value changing I guess?
There's already a TextValueChange, but it triggers on everything, even cursor movement.
The idea would be to only fire it when there's an actual change
to do that we'd have to have keep a second copy of the text buffer in a string and compare it on changes
as the generation changes with cursor moves too
Ah, ok, never mind then
we could make a fully controlled input that way it's not very hard but we would need to copy the buffers
for numeric inputs though it's easier as you can just parse and copy the number
In React, the text input is either controlled or uncontrolled depending on what parameters you pass in:
- If the element has a
valueparameter, it's controlled - If the element has a
default_valueparameter, it's uncontrolled
The assumption is thatdefault_valueis only sampled once on creation, whereasvalueis assumed to be dynamic
(I suspect it would not be easy for us to make it bi-modal like this)
BTW react's implementation of controlled text has some gnarly hacks, for example the controlled callback can't be asynchronous because otherwise the browser's built in text editing behavior overrules what the javascript is doing
maybe not, the main thing I'm not happy about is whether the primary component should contain the PlainEditor
we could have a TextInput component with a default: Option<String> field
but it seems annoying how the default value string would only be used once and then ignored?
oh wait I'm being stupid
we only need to store the cursor values
when the generation changes we can compare the cached cursor + selection with the PlainEditor's
and if they are equal, then the text must have changed
or the style values, but normally those aren't changed very often or at all once the input is created
That would yield false negatives, but maybe that's not important
we could compare the styles as well i guess
You said "if they are equal the text must have changed", but if they are NOT equal, then the text might have changed
So I'm not sure what that buys you
oh yeah, it's not a solution
then
backspace will move the cursor and delete a character
Does the generation advance in response to idempotent actions (like hitting backspace at the beginning of the text, or cursor right while at the end)?
no, not in those cases anyway
In Blitz we manually track whether an event triggers an action that might possibly cause a text change (e.g. backspace -> .backdelete() might change the text but left-arrow -> .move_left() can only change the selection).
(upstreaming this logic would make a lot of sense)
still need reviews for https://github.com/bevyengine/bevy/pull/23785, need to get the bug fixes and structual changes merged before anything more can be done
On my list for today
Currently about to take a nap though, so expect a few hours
I'm just about to go to sleep too so np
I noticed that we don't have double-click to select, which I'm guessing is because we don't have double click support in Bevy.
I have it on the text input crate
It shouldn't be difficult I think, it just wasn't a priority
@dire ginkgo @cedar sentinel I could have made use of cut and paste today; would it be possible to get the clipboard PR back on track?
Yes, I'll take a look tomorrow. Can you please review https://github.com/bevyengine/bevy/pull/23841 ?
I need to fix some ambiguities but it's basically done
That reminds me, I noticed a certain amount of friction dealing with String / SmolStr / SplitString - for example, say I have a string slice, and I want to:
- test the current value of the text buffer to see if it matches the slice
- if it doesn't match, update the text buffer, replacing the text
Since I can't compare a str slice with a SplitString, I need to convert the StringSlice to a common representation - String - and then compare them; then afterwards I need to convert the slice to a SmolStr so that I can queue it.
Just the ability to compare without converting would eliminate the need for extra copies
We don't own any of the types though π€
Could we compare as &str or something?
SplitString is non-contiguous, so we'd need a custom comparator impl. I'm not even sure how one does PartialEq between different types in Rust.
Fill in Rhs π https://doc.rust-lang.org/std/cmp/trait.PartialEq.html
Trait for comparisons using the equality operator.
But yeah, custom comparator trait or something is probably the way
Thanks @pale pecan for the IME testing; much harder to debug as a non-speaker
The two longer comments I wrote can be addressed after merge; I'm more interested in moving things forward
Okay, great β€οΈ I'm going to merge it now then, and spin up follow-up issues
Thanks y'all; this was significantly more involved than I expected
just took three lines on top of the ime changes
also there's some sort of bug that shifts the ime window position if you summon it when it's already active
Oh perfect!
@dire ginkgo On the list of text input features: there are two items on that list (placeholder support, and mobile pop-up keyboard support) which might be better to implement at a higher level.
- placeholder support: in CSS, placeholders can be styled differently than the regular input text - often dimmer, italicized, and so on. Having to pass in a bunch of additional style parameters to the input widget might be too much. Feathers could support placeholders directly, with theming support; the question is, do we need to support placeholders for non-feathers inputs? I don't know of too many games that use this.
- mobile pop-up: I suspect that this is something enabled by a compile-time flag - that is, when compiling for android (or in the future, xbox or ps), the entire editable text machinery will be swapped out, won't even be included in the binary. Instead, it will be replaced by some simple text display widget which, when clicked, will pull up the os keyboard.
Form submission handling is another one. So long as the ENTER key is allowed to propagate (which it is), it can be handled at a higher level.
Mhmm
Yeah placeholders should just be another text entity drawn in the same position and automatically hidden when the input is active
There's also completion suggestion text which would need lower level support
But that's low priority for now
Agreed, on both counts
For the mobile pop-up work, what we could do right now is build a simple read-only replacement for EditableText to run on console / mobile platforms, and then later work out how to invoke the popup keyboard on click.
Hmmm, maybe not, I need to think about this
There's also previous entry history that's not too difficult using a drop down menu or something
Needed for file dialogs and things
Actually, what we really want for mobile is a way to embed the platform's native input widget. The native input has features that would be hard for us to replicate.
Yes that's true
This also comes up on Steam Deck
Why would you not run the regular EditableText on mobile?
If we can get proper IME support into Winit (or whatever windowing is used for mobile) then we can replicate these kind of features. The https://github.com/rust-mobile/android-view demo gets pretty far down this road on Android (things like "drag space bar to move cursor" and "select suggestion from list" work).
And I believe Slint has selection handles working on Android
Calling Winit's set_ime_enabled is sufficient on iOS. Android that kinda sorta works, but support in Winit is currently pretty poor. I currently have it working in Blitz by using this custom Activity to extend NativeActivity https://github.com/DioxusLabs/blitz/blob/main/apps/browser/MainActivity.kt
Also, support is better in Winit 0.31-beta
This has, so far, been a pretty straightforward migration from bevy_ui_text_input. To be expected I guess, but I'm grateful :)
I just have some glitches with to fix with scrolling I'm going to work on now, then the fundamentals are pretty good.
@analog moon @cedar sentinel @split magnet @golden cypress I think I'm finally happy with 19106 as an MVP for clipboard support
That was rather complex; definitely not just a simple arboard wrapper to do right.
The problem is that clipboard APIs are seriously messed up at the OS level, and that's because it's ancient legacy architecture. The clipboard APIs from more recent platforms (linux, wasm) are quite pleasant compared to the windows one.
Unfortunately the "lowest common denominator" is quite low.
Yeah, that's the impression I'm gathering
We might want to consider other clipboard crates underneath this in the future, but this should be Good Enoughβ’ for an MVP
All of the recent APIs have standardized on using MIME types, but windows clipboard predates this
MIME support is something that I would definitely like to do in follow-up
Even if we have to do weird stuff on Windows
GitHub
Objective
Closes Text input should hide text highlighting when focus lostΒ #23889
Solution
Added unfocused_selection_color: Color to TextCursorStyle
Testing
cargo run --example multiple_text_in...
Heh, TEXT_INPUT_SELECTION is listed twice in dark_theme.rs
@dire ginkgo @cedar sentinel In the list of text input features, I'm not sure what "Text input labels" means. Is this the same thing as what I call "adornments" (that's what they are called in Material UI): https://mui.com/material-ui/react-text-field/?_gl=1*15q3rk5*_up*MQ.._gaMTAwODUzMTMzMC4xNzc2OTY2NTEy*_ga_5NXDQLC2ZK*czE3NzY5NjY1MTIkbzEkZzAkdDE3NzY5NjY1MTIkajYwJGwwJGgw#input-adornments
Let's just drop it from the list then
Maybe it means the AccessibilityNode labels for AccessKit
There's a separate bullet point for "AccessKit integration"
In any case, I put in a PR to update the doc comment for the list of unimplemented features: https://github.com/bevyengine/bevy/pull/23951
It must mean popover tooltips then
I think I meant placeholder text??
but it should say tooltip
Or tooltip!
Thank you π I kept putting this off π
I think I have a workable design for paste-polling BTW
I ran each of the examples and verified the behavior in each bullet point
rounded selection corners would be nice to have, if anyones looking for something to do: https://github.com/bevyengine/bevy/issues/23952
@cedar sentinel @analog moon clipboard support is good to go once we have final sign-off: https://github.com/bevyengine/bevy/pull/19106#issuecomment-4306776280
okay, taking a look now
@cedar sentinel thanks for being gracious about me just working directly on your branch; this was a really helpful workflow
np, I kind of hated that PR I only made it to push things along
the clipboard apis are so annoying
Yeah, both this and IME support were very much something I did out of a sense of duty and honor lol
IME support alone is very much worthy of a dedicated section in the 0.19 announcement on bevy.org
Most people don't even bother supporting it or internationalization at all, so bevy doing it puts it above just another "indie" endeavor into something a little more serious
nod I'll make sure to add a section to that effect
We're not done with text input yet, but we are trying to do it right
I still have a few things I'd like to fix with it
Nothing that should block the 0.19 release but still things I'd expect a text input to do
@cedar sentinel the example used for testing has been
, and I've split apart the feature. I didn't realize that arboard's image functionality was broken on web, ty
Yeah that clipboard example I wrote was really terrible, it was only included as an "it works" sanity test, there's no need for it now with the text input widgets available for testing
oh so confused for a moment what was going on, I can't approve my pull request ofc hehe
I've been in a lot of conversations over the past N years of being involved in Rust UI where someone has said something like "Winit has poor IME support because it was primarily designed for games and games don't care about IME. So it's fun to see Bevy excited about implementing IME.
"excited"
And if I can get y'all pushing for better IME APIs in Winit that would be a big win for everyone.
What we need is a game where the IME is the central game mechanic
I would actually be quite interested to play that
Like, typing tutor for emojis
I imagine it could be quite tricky!
I've found it's a difficult thing to test, because different IMEs seem to behave very differently to each other. That's partly platforms differences, but also different language IMEs on the same platform!
And the output usually ends up being text in a language I don't understand!
System clipboard support is finally in!
Very pleased to have both that and IME support in the initial release
Was not expecting that
Next cycle we should do a real pass on our AccessKit integration
There's a ton of tech debt there
same as with IME it needs someone who uses a screenreader to take charge of it really, I look at it sometimes and fix some issues but I'm just guessing at the intent a lot of the time
My hope is that we can get to "good enough to use" in order to actually attract and retain those users π
Tomorrow if I'm not busy after work I'll try my hand at adding the ability to tell if a widget was "tabbed into"
This could either be a new event, or it could be something like a reason field added to the focus event.
My plan was the latter
I like that plan
Need reviews on: https://github.com/bevyengine/bevy/pull/24032
GitHub
Objective
Fix some scrolling jitters and bugs.
The cursor can be scrolled out of view.
Horizontal scrolling is jittery with a cursor taller then the line height.
Vertical scrolling jitters.
Scroll...