pythonarcade / arcade

Easy to use Python library for creating 2D arcade games.
http://arcade.academy
Other
1.72k stars 329 forks source link

Making an advanced `UIInputText` #1318

Open eschan145 opened 2 years ago

eschan145 commented 2 years ago

I've looked at many other GUI toolkits, like tkinter, PyQt5, and some other libraries, and I noticed they each have two separate input widgets: a text entry and an advanced one. I was thinking if we could implement something like that in Arcade. We should call it something like UIText or UIAdvancedText. tkinter provides a Text widget, which handles formatting in specific tags, which have names, start, and end attributes. The Text widget also can insert images and other widgets. And PyQt5 has a TextEdit widget which allows easy formatting for creating text editors. An advanced text entry in Arcade would support rich text formatting, complete with shortcuts like Control-b for bold, easy functions for adding images, copy and paste support, select all support, and much more. Pyglet does provide formatted documents that are pretty easy to use. You create text, and you can add styles to it in different ranges with specified formatting. We could even add insert and delete functions, which accept ranges and text, to make it easy for users to delete text. Like many GUI toolkits, the basic text entry is only singleline, but the multiline text widget supports all of these formats. I've been working on something like this in my own Arcade GUI toolkit I'm planning to largely distribute, but I'm getting glyph errors when I use formatted documents in the IncrementalTextLayout. Because in pyglet, documents can be attached to layouts, we can just make the document formatted like a pyglet.text.document.FormattedDocument. Adding keyboard shortcuts and methods for formatting shouldn't be too hard. Below is some of the code I've used for my own toolkit. It's intended to be used programmatically. (BTW I'm using a custom documentation format)

def format_insert(self, *args, **kwargs):
    """Insert a formatted range to a range of indices in the entry. Formats
    are specified in **kwargs. Keep in mind that this is pyglet document
    formatting, not the custom formatting that has been provided by this
    module.

    >>> entry.format_insert(5, 10, bold=True, color=(255, 0, 0, 255)) 

    Overlapping formats are supported, so you can call this over the same
    range of text. This fixes the problem in tkinter, where overlapping
    tags are not supported.

    Dictionary from arguments from:
    https://stackoverflow.com/a/44412830/19573533

    indices - a tuple of the start and end indices of the range. Defaults
                to None, which is the whole range of the text.

    parameters: tuple (start, end)
    """

    indices = args or (0, self.length)

    formats = {("arg" + str(index + 1)): argument for index, argument in enumerate(args)}
    formats.update(kwargs)
    del formats["arg1"]
    del formats["arg2"]

    self.document.set_style(*indices, formats)

This widget could be really helpful in making rich text user descriptions, notes, and more.

UPDATE: Currently I'm experiencing issues with glyph management; pyglet thinks some glyphs are empty sometimes, and the selected background color is messed up so the selected text and its background color are completely transparent. There is another bug where two areas are supposedly selected, but one is with the correct background and the other one is without.

image

Another system we would have to add is a system that would check for overlapping styles, and then append them to the style stack. For example, if a range of text was made bold by the user, then italic sometime later on, then a system would have to be made so that the bold is added to the range. It could just check the added styles there, then format the style dictionary to have the included style.

pushfoo commented 2 years ago

Adding keyboard shortcuts and methods for formatting shouldn't be too hard.

easy functions for adding images, copy and paste support, select all support, and much more.

I know you have requested copy and paste before in https://github.com/pyglet/pyglet/issues/593. However, supporting copy and paste inside arcade or pyglet is a project on its own.

There are multiple reasons that pyglet only detects copy and paste related events without handling them:

  1. pyglet is primarily an OpenGL wrapper with a few extras
  2. pyglet explicitly has a goal of 0 run dependencies
  3. handling copy and paste requires abstracting and binding platform-specific functionality

We have two choices for implementing copy and paste, and either will get complicated because both require handling the concept of different types of paste data. For example, what happens when a user tries to paste into a text box when there is image data in the clipboard? Does that trigger an exception?

Option 1: Write our own binding

This could be put in pyglet itself. In general, binding Win32 C APIs in ctypes is tedious but effective if you know exactly what you want to achieve. I've done it before.

However, there are multiple additional complications in addition to the ones I've already mentioned. First, there appear to be at least two different ways of handling copy and paste in Windows. Second, Win32's OLE model introduces multiple layers of additional complexity such as clients, servers, and compound documents.

Option 2: Use an external dependency

There are multiple potential issues here. In addition to having to make sure all our target platforms are supported, we also get into some of the same issues mentioned for writing our own binding. Also, there's licensing.

eschan145 commented 2 years ago

I used tkinter for the copy and paste methods. This probably slows down startup times, a little, but it works. I just create a tkinter window, then call its withdraw() function, which keeps it hidden. A created Tk() instance has methods like clipboard_get() and clipboard_append(), and stuff like that. That way, I don't need to install any modules. tkinter doesn't support images and rich text in clipboards, so I doubt there'd be an error.

eschan145 commented 2 years ago

If we don't want external dependencies, we could make something that only keeps the copy and pasted text within the application. We could just create a list during initialization, then add copied and pasted text to it. The downside of this is that it can be exported to other applications. Also, I'm experiencing performance issues. At first, the fps is around 300+, but as soon as I bold a range of text, the fps plummets immediately until Arcade hangs and then eventually crashes.