Fork me on GitHub
#membrane
<
2021-08-17
>
Jakub21:08:00

Hi, I finally gave membrane a try and it is really cool! I am using the lanterna renderer and playing with terminal rendering. Is there a way to get current terminal size (number of rows and columns)?

phronmophobic21:08:37

I don't know off the top of my head. Let me check!

Jakub21:08:13

Also the vertical-layout seems to be off by one. If I render following

(defn app []
  (ui/with-color [1 1 1]
    (apply ui/vertical-layout
           (for [i (range 5)]
             (label (str "Item: " i))))))
I get blank lines between items. I would expect them next to each other by default, since if there is a need for spacing the items could be wrapped in padding .

👀 3
phronmophobic21:08:23

for the layout issue, membrane.ui/vertical-layout adds 1 pixel when stacking elements vertically which is generally what you want with pixel based contexts. You're right that it doesn't make sense for the terminal's coordinate system.

phronmophobic21:08:45

as a short-term workaround, you could define the following in your namespace to replace membrane.ui/vertical-layout:

(defn vertical-layout
  "Returns a graphical elem of elems stacked on top of each other"
  [& elems]
  (let [elems (seq elems)
        first-elem (first elems)
        offset-y (+ (height first-elem)
                    (origin-y first-elem))]
    (when elems
      (loop [elems (next elems)
             offset-y offset-y
             group-elems [first-elem]]
        (if elems
          (let [elem (first elems)
                dy (+ (height elem)
                      (origin-y elem))]
            (recur
             (next elems)
             (+ offset-y dy)
             (conj group-elems
                   (translate 0 offset-y
                              elem))))
          group-elems)))))

Jakub21:08:45

cool, that's a good suggestion

phronmophobic21:08:56

in the medium term, it might make sense to add an appropriate version to membrane.lanterna. In the long term, it seems like making a better differentiation between coordinate systems (floating point, integral) and not mix everything together in membrane.ui (simplify further!)

phronmophobic21:08:28

ok, laterna does have a way to query the screen size, http://mabe02.github.io/lanterna/apidocs/3.1/com/googlecode/lanterna/screen/VirtualScreen.html#getViewportSize-- just need to decide the best way to expose that

phronmophobic22:08:20

and presumably, you'd like to know when the screen size changes?

Jakub22:08:23

It would be good to listen to resize events to recalculate the layout.

👍 3
Jakub22:08:05

But if there are no listeners available, the root component could just get the size and pass it down, the layout would be calculated in functional way, so it would update on next re-render.

Jakub22:08:42

There also seems to be https://mabe02.github.io/lanterna/apidocs/3.1/com/googlecode/lanterna/screen/Screen.html#getTerminalSize-- on the Screen class, which might be even better. VirtualScreen seems to provide additional functionality like scrollback, so its could end up different than the terminal size.

phronmophobic22:08:02

> Most commonly, calling getTerminalSize() will involve some kind of hack to retrieve the size of the terminal, like moving the cursor to position 5000x5000 and then read back the location, unless the terminal implementation has a more smooth way of getting this data. Keep this in mind and see if you can avoid calling this method too often. There is a helper class, SimpleTerminalResizeListener, that you can use to cache the size and update it only when resize events are received (which depends on if a resize is detectable, which they are not on all platforms).

Jakub22:08:29

I see, there are quite a few moving parts 🙂 So we want to probably cache it as part of the state. In the React based TUI frameworks it was nicely wrapped as a hook.

phronmophobic22:08:48

The getTerminalSize on Screen is a cached version

phronmophobic22:08:55

so that does seem like a good option

Jakub22:08:34

Or one could set up the listener and as a callback provide a function that would set the values on a global state atom.

phronmophobic22:08:52

just thinking out loud: not exactly sure the best way to surface the terminal size. lanterna/run could accept an optional callback that gets called when it changes

phronmophobic22:08:01

all the other optional parameters are channels

phronmophobic22:08:13

but a callback seems reasonable

phronmophobic22:08:04

the callback route is also similar to how the skia backend works where you can provide callbacks for different window events

Jakub22:08:24

Also thinking out loud, it could also be an event subsribable with ui/on ?

Jakub22:08:37

Feels little bit more generic then adding additional parameters to the run API. But I am not too familiar with the membrane event system yet, so there might be some downsides.

phronmophobic22:08:21

subscribe-able event also makes sense. with windows, the initial size is provided, but it's automatically provided for terminals

phronmophobic22:08:39

however, even with window based systems, you create the window and receive the framebuffer size (window size and framebuffer size may be different for retina displays) as a callback before you draw

phronmophobic22:08:00

so it would still be consistent to always fire a resize event before the first draw in terminals as well

👍 3
phronmophobic22:08:30

I created an issue at https://github.com/phronmophobic/membrane/issues/21. It doesn't seem like that much work, so I'll try to make a release later today or tomorrow.

Jakub22:08:50

awesome, thanks a lot 👍