Owl.LiveScreen
terminal input (proof of concept)
#24
Replies: 6 comments
-
Wow, I'm impressed!
|
Beta Was this translation helpful? Give feedback.
-
Yup! For this proof of concept, I really didn’t put any effort towards maintaining compatibility. I think it should be doable though, but the checks needed would have made things more complicated here so I didn’t add it for now.
No reason! It got changed at some point while I was debugging so that I could see what was going on a bit easier and I think I just didn’t change it back. I didn’t exactly stress test it, but I don’t think there are any major performance issues.
I wonder if a behaviour-driven approach would be most natural here? defmodule MyLiveScreen do
use Owl.LiveScreen
@impl true
def init(_arg, screen) do
{:ok, screen, input: true}
end
@impl true
def render(screen) do
%{assigns: assigns} = screen
# return owl data
end
@impl true
def handle_input(input, screen) do
# assign stuff, etc.
{:noreply, screen}
end
end This is just a sketch but for more complex terminal UIs, something like this would feel more natural, to me at least. It’s also possible to build something like this on top of the function-based API, so perhaps both could coexist?
Thats another good option! The concern here is the prompt and where it should be located. I’d want to “block out” that space ahead of time so that the entire layout doesn’t shift when I prompt, so you might want some way to declare up front that you’re going to be prompting. In this demo as well, I used a static |
Beta Was this translation helpful? Give feedback.
-
Re: API, I actually don't think a behaviour-driven approach is needed, as seen in your own widget implementations. Creating a GenServer yourself to manage state is fine. |
Beta Was this translation helpful? Give feedback.
-
The current system with blocks works in a way, that when a block grows in size and shrinks back after then the size of the block remains the same with empty lines, so it doesn't jump. Mix.install([:owl])
Owl.LiveScreen.add_block(:block1, state: "")
Owl.LiveScreen.add_block(:block2, state: "static")
[1, 2, 3, 4, 3, 2, 1]
|> Enum.each(fn lines_number ->
Owl.LiveScreen.update(:block1, 1..lines_number |> Enum.map_join("\n", &"line#{&1}"))
Process.sleep(1000)
end)
Owl.LiveScreen.flush() |
Beta Was this translation helpful? Give feedback.
-
By the way, only because I’m not sure if it notifies, but I shot you a message on ElixirForum chat 🙂 |
Beta Was this translation helpful? Give feedback.
-
It notifies 🙂 |
Beta Was this translation helpful? Give feedback.
-
Since it's unclear how far we are from a portable getch, I thought I'd experiment with some way of getting user input while using
Owl.LiveScreen
.After some hacking around, I have a proof of concept that I wanted to share:
The basic idea is to allow
Owl.LiveScreen
to render a singleton prompt, and then use\e7
(cursor position save) and\e8
(cursor position restore) to maintain the cursor position during renders. The changes I made in the linked branch are very rough -- basically just getting something working. There are a number of open questions:\e7
and\e8
? It seems like most modern terminals support them, but they're non-standard, I believe. There's also\e[s
and\e[u
-- there are some notes about them in this gist.:on_input
option toadd_block
that can be a function taking the input and block state and returning a new state.LiveScreen.update
replaces the whole state and the owl demo usedStream.iterate
to move the owl, but I wanted the color to react to the user input. For this proof of concept, I just broke the API and decided thatLiveScreen.update
would merge the maps so that the owl color wouldn't be overriden by every update from the stream iteration. It may be desirable forLiveScreen.update
to take an update function instead that can be applied to the current state of the block.Beta Was this translation helpful? Give feedback.
All reactions