diff options
author | Sacha Chua <sacha@sachachua.com> | 2021-11-28 10:21:32 -0500 |
---|---|---|
committer | Sacha Chua <sacha@sachachua.com> | 2021-11-28 10:21:32 -0500 |
commit | fee6e9625143b4595ada6709a8ee2a3e2dee1297 (patch) | |
tree | 4904e5ed508cd13dbb0c3e69bbec84da3626cd45 | |
parent | 3c7e2842aec25f76d7fdc706e5d72ce619995726 (diff) | |
download | emacsconf-wiki-fee6e9625143b4595ada6709a8ee2a3e2dee1297.tar.xz emacsconf-wiki-fee6e9625143b4595ada6709a8ee2a3e2dee1297.zip |
Update
Diffstat (limited to '')
-rw-r--r-- | 2021/captions/emacsconf-2021-ui--yak-shaving-to-a-ui-framework-help-i-accidentally-yak-shaved-my-way-to-writing-a-ui-framework-because-overlays-were-slow--erik-anderson--main.vtt | 634 |
1 files changed, 634 insertions, 0 deletions
diff --git a/2021/captions/emacsconf-2021-ui--yak-shaving-to-a-ui-framework-help-i-accidentally-yak-shaved-my-way-to-writing-a-ui-framework-because-overlays-were-slow--erik-anderson--main.vtt b/2021/captions/emacsconf-2021-ui--yak-shaving-to-a-ui-framework-help-i-accidentally-yak-shaved-my-way-to-writing-a-ui-framework-because-overlays-were-slow--erik-anderson--main.vtt new file mode 100644 index 00000000..7ee41c3a --- /dev/null +++ b/2021/captions/emacsconf-2021-ui--yak-shaving-to-a-ui-framework-help-i-accidentally-yak-shaved-my-way-to-writing-a-ui-framework-because-overlays-were-slow--erik-anderson--main.vtt @@ -0,0 +1,634 @@ +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)] |