WEBVTT
00:07.600 --> 00:00:09.120
Hi! I'm Erik Anderson,
00:00:09.120 --> 00:00:10.559
and I'll be talking about tui,
00:00:10.559 --> 00:00:11.840
a user interface framework
00:11.840 --> 00:00:15.040
that I've written in Emacs Lisp.
00:15.040 --> 00:00:16.196
First, I want to talk a bit about
00:00:16.196 --> 00:00:17.296
the problem space of
00:00:17.296 --> 00:00:18.728
user interface development,
00:00:18.728 --> 00:00:20.880
specifically, I want to quickly illustrate
00:00:20.880 --> 00:00:22.320
some of the complexities involved
00:00:22.320 --> 00:00:26.480
with UI implementation in Emacs.
00:26.480 --> 00:00:27.920
In Emacs, we have the ubiquitous
00:00:27.920 --> 00:00:29.920
buffer object type that forms the container
00:00:29.920 --> 00:00:31.920
for most content in Emacs.
00:00:31.920 --> 00:00:34.079
Most interfaces we interact with
00:00:34.079 --> 00:00:36.239
consist of character based content
00:00:36.239 --> 00:00:38.239
in a buffer that's presented
00:00:38.239 --> 00:00:40.079
in a window in frame.
00:40.079 --> 00:00:41.520
Although the underlying content
00:00:41.520 --> 00:00:44.320
may be textual, Emacs has capable APIs
00:00:44.320 --> 00:00:47.680
to present rich content.
00:47.680 --> 00:00:49.200
The pervasiveness of buffers
00:00:49.200 --> 00:00:50.879
affords us wonderful flexibility.
00:00:50.879 --> 00:00:52.559
This presentation, for instance,
00:00:52.559 --> 00:00:55.520
is running in an Emacs buffer.
00:55.520 --> 00:00:57.420
Using Emacs's built-in basic
00:00:57.420 --> 00:00:59.199
button library, we can insert
00:00:59.199 --> 00:01:00.884
an interactive button
00:01:00.884 --> 00:01:01.760
that shows a message
00:01:01.760 --> 00:01:06.080
in the minibuffer when clicked.
01:06.080 --> 00:01:09.200
What about UIs that express application state?
01:09.200 --> 00:01:11.439
Most applications don't have a static UI.
01:11.439 --> 00:01:13.280
As application state changes,
00:01:13.280 --> 00:01:14.320
the UI should change
00:01:14.320 --> 00:01:18.479
to display the desired content.
01:18.479 --> 00:01:20.320
One simplifying strategy is to simply
01:20.320 --> 00:01:23.600
re-render the entire UI upon any change.
01:23.600 --> 00:01:25.680
First erase the contents of the buffer,
01:25.680 --> 00:01:27.600
and then reinsert your UI again
00:01:27.600 --> 00:01:29.040
with desired changes,
00:01:29.040 --> 00:01:33.040
and restore things like point and region.
01:33.040 --> 00:01:34.560
Basic composition is possible
00:01:34.560 --> 00:01:35.759
with this approach.
00:01:35.759 --> 00:01:37.200
Simply insert the elements
00:01:37.200 --> 00:01:39.280
of the UI in sequence.
01:39.280 --> 00:01:40.640
Complex elements can be
00:01:40.640 --> 00:01:44.320
composed of multiple sub-elements.
01:44.320 --> 00:01:45.840
UIs can be made extensible,
00:01:45.840 --> 00:01:47.040
and expose this composition,
00:01:47.040 --> 00:01:49.360
for example, with insertion hooks
00:01:49.360 --> 00:01:52.320
like magit's status sections hook.
01:52.320 --> 00:01:54.159
This generally relies on elements
00:01:54.159 --> 00:01:56.640
being well-behaved inserting themselves,
00:01:56.640 --> 00:02:00.399
not affecting the rest of the buffer.
02:00.399 --> 00:02:02.960
If we find ourselves with complex UIs,
02:02.960 --> 00:02:04.640
large buffers, long lines,
00:02:04.640 --> 00:02:06.320
or poor rendering performance,
00:02:06.320 --> 00:02:09.039
we might consider partial UI updates
00:02:09.039 --> 00:02:11.360
rather than re-rendering completely.
02:11.360 --> 00:02:12.800
In that case, the complexity
00:02:12.800 --> 00:02:15.840
for maintaining the UI quickly increases.
00:02:15.840 --> 00:02:17.360
As accessible as buffers are,
00:02:17.360 --> 00:02:19.120
we don't have high level abstractions
00:02:19.120 --> 00:02:20.879
for managing portions of a UI
00:02:20.879 --> 00:02:22.160
rendered to a buffer.
00:02:22.160 --> 00:02:23.520
(It) is left up to the programmers
00:02:23.520 --> 00:02:25.440
to track and update UI state.
00:02:25.440 --> 00:02:26.540
This is generally done by
00:02:26.540 --> 00:02:28.959
one of two methods, reflection,
00:02:28.959 --> 00:02:30.800
searching for strings or text properties
00:02:30.800 --> 00:02:34.239
within the buffer, or tracking segments
00:02:34.239 --> 00:02:36.080
of a UI buffer manually
00:02:36.080 --> 00:02:38.959
using numeric offsets, or marker,
00:02:38.959 --> 00:02:45.280
or overlay objects.
02:45.280 --> 00:02:47.280
Here we have a basic timer component
02:47.280 --> 00:02:48.720
that shows elapsed time
00:02:48.720 --> 00:02:50.319
after it's inserted.
00:02:50.319 --> 00:02:52.160
It works, but has several problems.
00:02:52.160 --> 00:02:55.519
It doesn't restore the user's point or mark,
02:55.519 --> 00:02:59.120
so it snaps back after every render,
02:59.120 --> 00:03:01.040
after every update.
03:01.040 --> 00:03:03.360
It relies on singleton global state,
00:03:03.360 --> 00:03:05.124
so isn't designed to coexist
00:03:05.124 --> 00:03:12.000
with other instances of itself.
03:12.000 --> 00:03:13.200
It doesn't use a marker,
00:03:13.200 --> 00:03:14.640
so it's sensitive to content
00:03:14.640 --> 00:03:16.239
proceeding it, that's following it,
00:03:16.239 --> 00:03:23.519
changing in the buffer.
03:23.519 --> 00:03:25.120
The update logic doesn't even consider
03:25.120 --> 00:03:26.799
which buffer it's trying to update.
00:03:26.799 --> 00:03:27.840
If I switch buffers,
00:03:27.840 --> 00:03:29.360
it will insert into another buffer,
00:03:29.360 --> 00:03:31.519
or even the minibuffer.
03:31.519 --> 00:03:33.360
It can't remove itself, or re-render
00:03:33.360 --> 00:03:34.879
if it gets corrupted, as you see.
00:03:34.879 --> 00:03:35.920
All in all, it's not
00:03:35.920 --> 00:03:38.400
a readily composable component.
03:38.400 --> 00:03:39.519
Addressing these components
00:03:39.519 --> 00:03:41.920
within this logic further increases
00:03:41.920 --> 00:03:43.936
the implementation complexity
00:03:43.936 --> 00:03:45.680
of this component,
00:03:45.680 --> 00:03:46.640
and still this component
00:03:46.640 --> 00:03:47.280
would likely have
00:03:47.280 --> 00:03:49.120
various subtle differences
03:49.120 --> 00:03:52.480
with other components
00:03:52.480 --> 00:03:58.959
implemented by other authors.
03:58.959 --> 00:04:00.319
For those of you unfamiliar
00:04:00.319 --> 00:04:02.159
with this term Yak Shaving,
00:04:02.159 --> 00:04:04.080
that is a quite technical term
00:04:04.080 --> 00:04:07.599
for any seemingly pointless activity,
00:04:07.599 --> 00:04:09.680
which is actually necessary
00:04:09.680 --> 00:04:10.879
to solve a problem,
04:10.879 --> 00:04:11.920
which solves a problem,
00:04:11.920 --> 00:04:14.799
which, several levels of recursion later,
04:14.799 --> 00:04:18.239
solves the real problem you're working on.
04:18.239 --> 00:04:19.943
The itch that led to this project
00:04:19.943 --> 00:04:21.840
was the desire to display a dense summary
00:04:21.840 --> 00:04:24.400
of local Git repository statuses.
04:24.400 --> 00:04:26.560
Encountering various implementation
04:26.560 --> 00:04:30.080
complexity for building UI elements,
04:30.080 --> 00:04:31.680
it led to the yak shaving endeavor
00:04:31.680 --> 00:04:33.680
that produced tui.
04:33.680 --> 00:04:35.440
When I wrote the library,
04:35.440 --> 00:04:36.479
I had recently played with
00:04:36.479 --> 00:04:39.360
a popular UI framework called React,
00:04:39.360 --> 00:04:41.840
and had an interest in learning
00:04:41.840 --> 00:04:44.080
about the internal architecture of React.
00:04:44.080 --> 00:04:45.680
So, rather than implement
00:04:45.680 --> 00:04:46.960
a string caching layer
00:04:46.960 --> 00:04:49.280
on top of tabulated list mode,
00:04:49.280 --> 00:04:50.360
I was rather inclined
00:04:50.360 --> 00:04:52.400
to go down the path of implementing
00:04:52.400 --> 00:04:58.960
the React API for Emacs Lisp.
04:58.960 --> 00:05:00.896
I'll offer a brief view of
00:05:00.896 --> 00:05:05.120
the tui Emacs Lisp API.
05:05.120 --> 00:05:07.360
Inserting component content
00:05:07.360 --> 00:05:08.320
is pretty straightforward.
00:05:08.320 --> 00:05:10.160
You take a component tree
00:05:10.160 --> 00:05:16.240
and render it in an Emacs buffer.
05:16.240 --> 00:05:18.639
If any elements in that tree are updated,
05:18.639 --> 00:05:21.039
their respective content on the tree
00:05:21.039 --> 00:05:26.320
is updated automatically.
05:26.320 --> 00:05:27.919
Here's a basic re-implementation
00:05:27.919 --> 00:05:29.919
of the straw man timer from earlier,
00:05:29.919 --> 00:05:32.400
using a macro for syntactic trigger.
05:32.400 --> 00:05:34.404
You'll notice that
00:05:34.404 --> 00:05:36.164
the signature includes its own
00:05:36.164 --> 00:05:44.560
object reference, arguments, and state.
05:44.560 --> 00:05:46.400
Associating arguments in the state
05:46.400 --> 00:05:51.360
with a component instance out of the box,
05:51.360 --> 00:05:54.400
makes it easy to design reusable components,
00:05:54.400 --> 00:06:06.080
and forms the basis for partial UI updates.
06:06.080 --> 00:06:07.840
The component rendering anchors
00:06:07.840 --> 00:06:09.199
are durable, so content can be
00:06:09.199 --> 00:06:11.360
added and removed surrounding content,
00:06:11.360 --> 00:06:12.479
or even within the region
00:06:12.479 --> 00:06:13.280
of the component,
00:06:13.280 --> 00:06:16.319
and replaced when it re-renders.
06:16.319 --> 00:06:17.600
Components will also
00:06:17.600 --> 00:06:20.880
cleanly remove themselves from a buffer
00:06:20.880 --> 00:06:28.400
when instructed to.
06:28.400 --> 00:06:30.160
tui contains the core implementation
00:06:30.160 --> 00:06:33.440
of the React API, so components,
06:33.440 --> 00:06:35.840
their constituent props, state,
00:06:35.840 --> 00:06:38.000
and all of the lifecycle methods
00:06:38.000 --> 00:06:39.440
associated with them,
06:39.440 --> 00:06:41.120
as well as keys, refs,
00:06:41.120 --> 00:06:43.228
and the fundamental
00:06:43.228 --> 00:06:47.520
reconciliation algorithm of React.
06:47.520 --> 00:06:52.188
A variety of other React APIs
00:06:52.188 --> 00:06:58.080
that haven't been implemented yet.
06:58.080 --> 00:07:00.080
It contains some useful features so far,
07:00.080 --> 00:07:02.639
such as hot reloading, reflection,
00:07:02.639 --> 00:07:06.164
and various debugging tools,
00:07:06.164 --> 00:07:12.639
and some reconciliation logging.
07:12.639 --> 00:07:14.880
Lastly, I'd like to give you some quick
07:14.880 --> 00:07:19.039
visual taste of components built with tui.
07:19.039 --> 00:07:20.240
The grid view that motivated
00:07:20.240 --> 00:07:21.520
my development of this package
00:07:21.520 --> 00:07:23.039
is very similar to Magit's
00:07:23.039 --> 00:07:30.720
list repositories functionality.
07:30.720 --> 00:07:36.080
Essentially, tabulated list mode
07:36.080 --> 00:07:39.440
but portable and has a separated model
00:07:39.440 --> 00:07:49.360
and presentation layers.
07:49.360 --> 00:07:51.840
Here's a basic xkcd comic viewer
00:07:51.840 --> 00:08:07.360
showing a couple classics.
08:07.360 --> 00:08:10.080
A long-standing React tutorial is
08:10.080 --> 00:08:13.280
building a tic-tac-toe game
08:13.280 --> 00:08:14.879
as a bit of a gimmick,
08:14.879 --> 00:08:17.039
and I'm not quite satisfied with
00:08:17.039 --> 00:08:25.599
the buffering direction,
00:08:25.599 --> 00:08:27.120
but it got me thinking about
00:08:27.120 --> 00:08:28.639
layout engines with text,
00:08:28.639 --> 00:08:35.120
so it was interesting.
08:35.120 --> 00:08:38.560
And here's a small
00:08:38.560 --> 00:08:46.080
Unicode character viewer
08:46.080 --> 00:08:55.279
capable of showing a bunch of characters.
08:55.279 --> 00:08:57.279
If this piques your interest,
00:08:57.279 --> 00:08:59.200
I would encourage you to check it out.
08:59.200 --> 00:09:01.440
tui should be usable by anyone
00:09:01.440 --> 00:09:04.640
with some basic Elisp familiarity,
09:04.640 --> 00:09:09.680
no prior knowledge about JavaScript
00:09:09.680 --> 00:09:12.080
or React is necessary.
09:12.080 --> 00:09:13.732
I'd absolutely love to talk with people
00:09:13.732 --> 00:09:14.880
about the tui package,
00:09:14.880 --> 00:09:17.440
textual user interfaces in general,
00:09:17.440 --> 00:09:19.040
and really anything in Emacs.
00:09:19.040 --> 00:09:20.693
If you have any ideas, feedback,
00:09:20.693 --> 00:09:21.680
or want to contribute,
00:09:21.680 --> 00:09:23.360
please reach out.
09:23.360 --> 00:09:24.360
Thank you all for listening.
00:09:24.360 --> 00:09:27.000
[captions by bhavin192 (Bhavin Gandhi)]