Current section

Files

Jump to
issuer lib cli io ncurses.ex
Raw

lib/cli/io/ncurses.ex

defmodule Issuer.CLI.IO.Ncurses do
@title_color 36
@chosen_color 32
@chosen_many_no "☐"
@chosen_many_yes "☑"
@chosen_one_no "◎"
@chosen_one_yes "🔘" # ● ◉ ⬤ 🔘
defstruct title: "Hello, please answer our survey:",
questions: []
defimpl Issuer.Survey, for: Issuer.CLI.IO.Ncurses do
# FIXME Currently we have to pass the title that fits on the line
# to properly render a caption, which is ugly.
# Need to calculate it!
def survey!(data, row \\ 0) do
ExNcurses.n_begin()
Issuer.CLI.IO.Ncurses.refresh(false)
IO.puts "\e[#{row};0H\e[#{@title_color};1m#{data.title}\e[0m\n"
{results, _} = data.questions |> Enum.map_reduce(row + 3, fn {title, choices, chosen, position}, acc ->
# FIXME store the whole screen text in the string array and print everything
rows = ExNcurses.lines()
choice_size = Issuer.CLI.IO.Ncurses.size(choices) + 2
left = (acc + choice_size) - rows
if left > 0, do: (1..left) |> Enum.each(fn _ -> IO.puts("\e[#{rows};0H") end) # scroll it
acc = Enum.min [acc, ExNcurses.lines() - choice_size]
{Issuer.CLI.IO.Ncurses.ask(acc, {title, choices, chosen, position}), acc + choice_size}
end)
Issuer.CLI.IO.Ncurses.refresh(true)
ExNcurses.clear()
ExNcurses.n_end()
results
end
end
##############################################################################
def ask(row, questionnaire, final \\ false)
# input
def ask(row, {title, choices, _chosen, position}, final) when is_binary(choices) do
refresh(false)
IO.puts "\e[#{row};0H\e[#{@title_color}m#{title}\e[0m"
IO.puts "\e[#{row + 1};0H\e[2K\e[#{prefix(final)}m#{choices}\e[0m"
upwards = row
len = String.length(choices)
IO.puts "\e[#{upwards};#{position + 1}H"
unless final do
ExNcurses.keypad() # clear input
ExNcurses.flushinp() # clear input
case ExNcurses.getchar() do # FIXME support arrows, everything is prepared, see position
8 -> ask(row, {title, delete_last_grapheme(choices), 0, [position - 1, 0] |> Enum.max})
263 -> ask(row, {title, delete_last_grapheme(choices), 0, [position - 1, 0] |> Enum.max})
330 -> ask(row, {title, delete_last_grapheme(choices), 0, [position - 1, 0] |> Enum.max})
10 ->
if len > 0 do
ask(row, {title, choices, 0, len}, true)
choices
else
ask(row, {title, choices, 0, len})
end
any -> ask(row, {title, choices <> <<any>>, 0, position + 1})
end
end
end
# multi choice
def ask(row, {title, choices, chosen, position}, final) when is_list(choices) and is_list(chosen) do
refresh(false)
IO.puts "\e[#{row};0H\e[#{@title_color}m#{title}\e[0m"
choices |> Enum.with_index |> Enum.each(fn {line, i} ->
IO.puts "\e[#{row + i + 1};0H#{checkbox({i, chosen}, final)} #{line}\e[0m"
end)
choice_count = Enum.count(choices)
IO.puts "\e[#{row + position};4H"
unless final do
ExNcurses.keypad() # clear input
ExNcurses.flushinp() # clear input
case ExNcurses.getchar() do
258 -> ask(row, {title, choices, chosen, rem(position + 1, choice_count)})
259 -> ask(row, {title, choices, chosen, rem(position + choice_count - 1, choice_count)})
260 -> ask(row, {title, choices, chosen, 0})
261 -> ask(row, {title, choices, chosen, choice_count - 1})
32 ->
chosen = if chosen |> Enum.any?(fn e -> e == position end) do
chosen |> Enum.reject(fn e -> e == position end)
else
chosen ++ [position]
end
ask(row, {title, choices, chosen, position})
10 ->
ask(row, {title, choices, chosen, position}, true)
chosen
_ -> ask(row, {title, choices, chosen, position})
end
end
end
# single choice
def ask(row, {title, choices, chosen, position}, final) when is_list(choices) and is_integer(chosen) do
refresh(false)
IO.puts "\e[#{row};0H\e[#{@title_color}m#{title}\e[0m"
choices |> Enum.with_index |> Enum.each(fn {line, i} ->
IO.puts "\e[#{row + i + 1};0H#{bullet(i == chosen, final)} #{line}\e[0m"
end)
choice_count = Enum.count(choices)
IO.puts "\e[#{row + position};4H"
unless final do
ExNcurses.keypad() # clear input
ExNcurses.flushinp() # clear input
case ExNcurses.getchar() do
258 -> ask(row, {title, choices, rem(position + 1, choice_count), rem(position + 1, choice_count)})
259 -> ask(row, {title, choices, rem(position + choice_count - 1, choice_count), rem(position + choice_count - 1, choice_count)})
260 -> ask(row, {title, choices, 0, 0})
261 -> ask(row, {title, choices, choice_count - 1, choice_count - 1})
32 -> ask(row, {title, choices, position, position})
10 ->
ask(row, {title, choices, position, position}, true)
position
_ -> ask(row, {title, choices, chosen, position})
end
end
end
##############################################################################
def refresh(clear?) do
ExNcurses.mvprintw(0, 0, "")
ExNcurses.refresh()
if clear?, do: ExNcurses.clear()
end
defp prefix(false), do: "1"
defp prefix(true), do: "32;1"
defp bullet(false, _), do: "\e[0m#{@chosen_one_no} "
defp bullet(true, final), do: "\e[#{prefix(final)}m#{@chosen_one_yes} "
defp checkbox({i, all}, final) do
checkbox(Enum.any?(all, fn(e) -> e == i end), final)
end
defp checkbox(false, _), do: "\e[0m#{@chosen_many_no} "
defp checkbox(true, final), do: "\e[#{prefix(final)}m#{@chosen_many_yes} "
def size(choices) when is_list(choices), do: Enum.count(choices)
def size(choices) when is_binary(choices), do: 1
defp delete_last_grapheme(string) do
len = String.length(string)
if len > 1, do: String.slice(string, 0..len-2), else: ""
end
end