Liking cljdoc? Tell your friends :D

Terminal UIs

Terminal UIs can be created using the membrane.lanterna backend. For a full example, check out the example projects:

Differences

Compared to most of the other backends, there are a few major differences:

  • Terminal UIs have an integer coordinate system. Most other backends have a floating point coordinate system
  • Terminals don't really have a way to draw multiple elements on top of each other. There is a foreground and a background, but the background can only consist a solid color.
  • Terminals often have a limited color palette
  • Text is always monospaced and 1 unit high

Many of the utilities found in membrane.ui can be used as-is for building terminal UIs. For example: bounds, origin, vertical-layout, on, translate, with-color, and others all work as you would expect. However, some of the membrane.ui primitives don't really make sense for terminals UIs. For example, label, checkbox, rectangle, and some others will not work for terminal user interfaces because of the differences from most other backends. For primitives in membrane.ui that don't make sense for terminal UIs, you can find counterparts in the membrane.lanterna namespace.

Supporting user interfaces that simultaneously work in terminals and with other backends is a non-goal. While it might be tempting to try to make the exact same primitives work for these two different mediums, the differences would make the development experience unfun in the general case. However, the primitives in membrane.ui and membrane.lanterna are just regular clojure data structures. Building a reduced set of elements for a constrained use case that work in terminal and for desktop is possible, but is left as an exercise for the reader.

Repl Driven Development

Repl driven development requires a config step since the default repl will consume System/in and System/out.

Below is the setup for cider/nrepl. If you need support for a different REPL, please file an issue or drop a request in #membrane on the clojurians slack.

Cider/nREPL

To setup a repl driven workflow, start up the nrepl server in the terminal that will be displaying your UI. Probably something like:

clojure -Sdeps '{:deps {nrepl/nrepl {:mvn/version "0.8.3"} cider/cider-nrepl {:mvn/version "0.25.6"}}}' -M:dev:nrepl --middleware '["cider.nrepl/cider-middleware" "membrane.lanterna/preserve-system-io"]' --port 7888

This command will start an nrepl server on port 7888. The default nrepl middleware will replace System/in and System/out. Add the membrane.lanterna/preserve-system-io to store System/in and System/out in the respective vars: membrane.lanterna/in and membrane.lanterna/out.

To connect to nrepl server in emacs, use M-x cider-connect and pass localhost as the host and 7888 for the port.

To run a UI that uses the correct I/O streams, pass the :in and :out as options like so:

(lanterna/run #'app-root
      {:in membrane.lanterna/in
       :out membrane.lanterna/out})

In some cases, you may want to restart the UI or display a different UI. You can stop a UI by passing a channel as the :close-ch option that will stop rendering when it receives a value.

(require '[clojure.core.async :as async])

;; Rich comment block
(comment
  ;; start the ui
  (do
    (def close-ch (async/chan))
    (lanterna/run #'app-root
      {:in membrane.lanterna/in
       :out membrane.lanterna/out
       :close-ch close-ch}))

  ;; Some time later, stop the UI. You can call `lanterna/run` to start a new UI.
  (async/close! close-ch)
  
  ,)

Can you improve this documentation? These fine people already did:
Adrian & Adrian Smith
Edit on GitHub

cljdoc is a website building & hosting documentation for Clojure/Script libraries

× close