Skip to content

Conversation

tye-exe
Copy link
Contributor

@tye-exe tye-exe commented Oct 9, 2025

Allow any type implementing the required trait (TextType) to be displayed and validated by TextEdit.

This is still a work in progress.
I am opening this as a draft to get feedback on:

  • 1 Is this a change that is still desired
  • 2 Implementation details

Closes:

  • I have followed the instructions in the PR template

Example

I have added an example at ./examples/text_validation/. This gives a brief showcase towards what the functionality and API of this more generic TextEdit might look like.

Implementation Details

TextEdit has been changed to take a generic parameter that is bounded by TextType.
TextType requires two functions, one to convert the value into a string, the other to convert the value from a string. I could not use the existing ToString and FromString because FromString is not required to parse the output from ToString.

Users can define custom validation rules by implementing TextType on new types.

Allowing Invalid States (Implemented)

Currently the TextEdit can only exist in valid states which is not desirable. A simple example of this is the char data type. The only way for a user to edit it currently is for the user to write the new char elsewhere, and then paste it over the existing character. This occurs because a char can only contain one character, so if the user attempts to add their new desired character the TextEdit becomes invalid and the new character is removed, since there was more than one character. The same sequence occurs if the user attempts to delete the character to add a new one.

My idea to resolve this problem is to allow invalid states while a TextEdit is focused, which will be implemented by storing the string representation of the TextEdit value using TextEditState (this will require adding a new attribute to TextEditState).
While Focused: any valid changes will be written to both the string and the represented value.
While Focused: any invalid changes will be written only to the string.
If the TextEdit looses focus while in an invalid state, a valid string representation will be set from the represented value.

This implementation will require all TextEdits that are mutable to use a persistent Id to store the string representation between frames (certain datatypes could be made exempt, See String Optimisations).

String Optimisations

I have not conducted research into which types will be used most often, but I am going to guess that the most common datatype will be &mut String (and other equivalents). Without any optimisation for the raw string datatypes, the TextEdit will be storing two copies, one as the display string and one as the represented value. This is wasteful.

The idea that I have to combat this issue (if it is needed) is to skip using the display string for unvalidated string data types. This can be accomplished in multiple ways. The two that come to mind are:

  • 1 Using generics (impl TextEdit<String>) to have string based TextEdits not execute any display string code.
  • 2 Create a method for TextType that specifies whether a given datatype should use a display string .

TODO

  • Clean up TextBuffer implementations to apply directly to String type.
  • Discuss temporarily allowing invalid states (See Allowing Invalid States).
  • Enable temporarily allowing invalid states (See Allowing Invalid States).
  • Discuss whether the TextType attribute in TextEdit should use generics or dynamic dispatch.
  • Discuss optimisations for string representation (See String Optimisations).
  • Discuss variable, type, method, and file names (I do not plan for the current ones to be the finial ones).
  • Add comments.
  • Add tests(?).
  • Publicly export TextType.
  • Implement TextType as both mutable and immutable for core datatypes, such as strings and integers.

All todo goals are ones that I can achieve without external help, I just want to know whether any of this is desired before I dedicate more time.

Any type implementing the correct trait can be displayed and validated by TextEdit.
Copy link

github-actions bot commented Oct 9, 2025

Preview is being built...

Preview will be available at https://egui-pr-preview.github.io/pr/7621-textedit

View snapshot changes at kitdiff

@tye-exe tye-exe marked this pull request as draft October 9, 2025 13:05
This is to easily allow people to preview the change. It's not intended to be permanent
(though this is up for discussion).
@Mingun
Copy link
Contributor

Mingun commented Oct 9, 2025

Without criticizing the approach itself, I would like to note that in order to solve the original problem with char, the most logical thing would be to change the editing principle for this type: automatically replace the value with the last character typed / pasted. Thus, there will never be a situation where there will be 2 characters or something needs to be erased.

@tye-exe
Copy link
Contributor Author

tye-exe commented Oct 9, 2025

automatically replace the value with the last character typed / pasted.

If you mean in regards to current system, to implement that I could change the read_from_string function to take the current value and the display string. This would be useful for situations like the char implementation, so the implementation can deduce the character was newly added, though I cannot think of any other situations that it may be useful off the top of my head.

E.G. fn read_from_string(&self, displayed: &str) -> Option<Result<Self, Self::Err>>

@Mingun
Copy link
Contributor

Mingun commented Oct 9, 2025

I mean, the typed characters usually appended to the text input, which creates difficulties for the char (because it expects exactly one character). For it the character in text box should be replaced without requiring to manually select it (as you do when you want to replace part of string).

This allows proper parsing of the char datatype, as you can
identify which char is new and which is old.
@tye-exe
Copy link
Contributor Author

tye-exe commented Oct 9, 2025

I've updated the read_from_string(s: &str) function to read_from_strings(previous: &str, modified: &str). This allows the char parsing to detect which characters have been modified and behave accordingly.

Demonstrate custom parsing rules with a newtype
@tye-exe
Copy link
Contributor Author

tye-exe commented Oct 10, 2025

Regarding the todo of Clean up TextBuffer implementations to apply directly to String type.
I am wondering whether it will be better to switch TextBuffer to a newtype around &mut str or to leave it as an interface applying to the String datatype.

Changing it to a newtype will prevent the current implementations from being applied to all instances of String, which could more properly scope the functions. However, this would be a breaking change.

Leaving it as a trait that is implemented by String will reduce/eliminate breaking changes depending on the method taken. Breaking changes depend on if &dyn TextBuffer is replaced with &str in public interfaces, such as layouter in TextEdit

@tye-exe
Copy link
Contributor Author

tye-exe commented Oct 10, 2025

Whilst working on "Allowing Invalid States", I came encountered the following issue and its corresponding PR:

As suggested by willbicks in his response to the issue, combining lost_focus() with clicked_elsewhere() allowed for proper detection of focus loss.

This makes it significantly easier for users to edit values.
The value is updated whilst the user is editing the TextEdit if the
TextEdit contains a valid value.
This prevents breaking changes.
Copy link

@Erik1000 Erik1000 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like this approach and it would solve issues like i had previously. And I like the example since I have some newtypes that wrap a string and enforce a specific format, this would be covered.

A mutable borrow is used instead.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants