ab5tract / Terminal-Print

Terminal::Print is a pure Raku layer for printing to terminal screens
Other
25 stars 18 forks source link

Markup syntax for widget contents? #75

Open jjatria opened 4 years ago

jjatria commented 4 years ago

Do we want to support some sort of markup for styling content?

Looking at other TUI projects in other languages, most of them have some way to specify styles in widget contents:

termui

Some [styled](fg:black,bg:red,mod:underline) text

All widgets parse this. The parsing process converts this to a list of cells, each with its own styling. Widgets take these cells and print them.

blessed

Some {black-fg}{red-bg}{underline}styled{/underline}{/red-bg}{/black-fg} text

or

Some {black-fg}{red-bg}{underline}styled{/} text

Parsing this is enabled per widget on construction.

Widgets can also receive SGR sequences directly, which allows blessed to take input directly from existing terminal programs.

They also support additional tags for alignment and escaping.

tui-rs

Looks like they have no special markup. They accept spans of styled text:

let text = [
    Text::raw("Some"),
    Text::styled("styled", Style::default().fg(Color::Black).bg(Color::Red).modifier(Modifier::UNDERLINED)),
    Text::raw("text"),
];

These are, I think, some informative examples.

During the New Year's break I started playing with a port of Termui to Raku, and one of the things I finished was a Grammar to more-or-less parse their markup. I've been experimenting with using it with Terminal::Print, and it seems to work (although it currently does not handle more than one modifier).

Is this something we are interested in doing? And if so, what do we want the markup to look like?

japhb commented 4 years ago

This is a doozy of a question, because it opens a can of worms. Essentially Terminal::Print as it exists today is pretty low level; it cares about the screen as a grid, has nearly the least amount of extra code to allow treating a sub-rectangle of a grid as the drawing space for an abstract widget, and includes some features for making use of Unicode box drawing characters and half-block "pixels", as well as driving simple animations.

But there are many places where the current Terminal::Print is too low-level for people to actually get useful work done. It doesn't do a great job of handling wide characters (basically forcing the user to know whether adjacent cells will overlap or not), doesn't attempt RTL layout at all, and doesn't provide any automatic layout, any pre-made widgets, or as you point out any way to have embedded style within strings. The user has to handle all that in their own code, as rpg-ui does in manually painting basic widgets.

In a phrase, this sucks.

When ab5tract and I talked about this some time ago, we agreed that Terminal::Print needs a higher-level API -- though whether that should be part of the core, or distributed as a separate bundle so we can have an ecosystem of widget libraries built on a common base, or some combination (e.g. accelerated functions added to the core to handle mixed wide and narrow glyphs transparently, but layout and widget libraries separate) is a reasonable question.

Unfortunately, about the time ab5tract and I agreed, we both ran out of tuits for a project as large as creating a widget library. It turns out it's actually really hard, and if you look at something the size of the medium-level GTK+ or Qt widget libraries, or even something higher-level like WebKit/Blink or Gecko, it's clear that it's pretty much an arbitrarily complex problem.

What about just emulating an existing terminal-based widget library? Maybe the complexity of those other libraries comes down to caring about rendering pixels instead of just laying out monospaced cells? I think that's a large contributor, sure -- but it's still an enormous project for one or two people, especially as it's important to me that anything I build has a reasonably Raku-ish flavor to it and doesn't have slowness "built in", meaning that the API has to be built to be thread-safe, able to handle high update rate without horrible screen artifacts, and so on. I'm pretty sure these concerns were not in the goals of a number of other libraries I've taken a look at. Some of them are so anti-Rakuish that there's not much of value worth taking from their APIs other than ideas. Others are as slow as molasses and are basically only useful for writing data entry forms.

That said, I have a couple personal projects that could really use a good terminal widget library, so if someone else wanted to build one that sat on top of Terminal::Print, I'd be happy to smooth any impedance mismatches or performance issues.

jjatria commented 4 years ago

I'd be happy to see this as existing at a higher level than Terminal::Print, and indeed most of the examples I mentioned seem to be high-level APIs to low-level terminal handling libraries (with the exception of blessed, I guess).

I haven't been thinking about this half as long as you or @ab5tract have, but I have some tuits and plenty of motivation to put into a project like this. I'd love to see some progress made on a base widget library in Raku, and would be more than happy to contribute and get something going.

Do you have any ideas of where that effort would be best spent?

I can start by doing a bit more research into how the widgets are structured in other libraries, and take it from there. I'll continue playing around with my experiments for the moment. If nothing else, just because they're a lot of fun to hack around with. :)

japhb commented 4 years ago

I think you're on the right path there: figure out how other libraries structure things, just to expand your mental model of what a widget library should be (and be capable of). Raku has some advantages compared to other languages in that a great deal of concurrency and code reuse power is built in; we can design in terms of composeable units of supplies and roles and so forth, rather than trying to hack thread safety and object models into languages that don't have them natively.

That said, there's probably quite a bit to be learned by looking at what the GTK+ and Qt designers were able to achieve in their native languages. Both have stood the test of time and been used to implement very complex systems with (these days at least) a fair level of efficiency. HTML5 forms include a lot of features that give a good idea of what people really use day to day.

Rather than look at the details of what exact functions and options and such are available, look at use cases: For instance, it's useful to know that people want to wrap things in borders, and they want to be able to style them different ways, and perhaps whether they have padding on either side, but there's no point in being able to specify pixel widths or arbitrary levels of line transparency, since those don't really make sense. (But a drop shadow effect could reasonably have transparency, since you could just alter the foreground and background colors of "shadowed" cells.)

Where to start when building your widget library? Here's one possible path, but don't feel stuck on it -- it's daunting, and frankly the only way through is whatever is fun and useful to you.

Start with a few very basic widgets that people might need to create a basic form: label, text/password input, button, checkbox, radio button, etc. Have some way of packing the widgets automatically, to make lists, columns, sections, forms, and so on, without the user having to calculate locations. Better still, decide on an API for layout, and simply provide default form and list layout implementations. Make a table widget / grid layout too. Make sure you can scroll widgets, and have all their contents move in DWIM fashion.

Make sure programs can set up UX flows: verification of fields and whole forms, retry with marked errors, asynchronous events, multi-page form flows, being able to deactivate or hide widgets based on the state of other widgets (like checking a box and thus hiding or revealing more fields in the form), etc. @jnthn has been looking at Rakuish versions of this for his Cro::WebApp::Form work.

Then get into looks. Allow a styled text strand to be rendered automatically. Have a way to deal with "gravity", being able to specify whether subwidgets stick to edges, corners, or the center of their parent. Have a way of dealing with resize and reflow, overflow, line wrapping, clipping, etc. Allow any widget to have padding, styled borders, and so on. Make sure styles can be treated independently of the thing they are applied to, so it is easy to apply a single style to multiple widgets.

Make mouse input work. Make keyboard-only input work. Make it accessible. Make it translatable. Make it work with mixed RTL and LTR text, including locale-dependent formatting and layout. Make text editing understand some set(s) of standard key bindings: Vim, Emacs, Mac, CUA, or what have you.

Add more advanced widgets: accordions, carousels, multi-level menus, date/time pickers, file pickers, color pickers, charts, canvases, etc.

Note that if you even get through the first couple stages of this work, people are likely to want to help push it farther. Make sure you have at least the minimum documentation, such as your list of todos, that will allow people to help.

Above all, HAVE FUN. :-)