[ANN] [WIP] TextUserInterfaces.jl


#1

Hi guys!

As promised, I am publishing the very first version of the TextUserInterfaces.jl.

This is a wrapper for Ncurses with a Julia-like API to build TUIs.

I think we have potential here. With this small code:

using TextUserInterfaces

# Initialize the Text User Interface.
tui = init_tui()

# Do not echo the typed characters and do no show the cursor.
noecho()
curs_set(0)

# Create the side window that will contain the menu.
win_menu = create_window(LINES()-4, 20, 0, 0; border = true, title = " Menu ")

# Create the bottom window with the instructions.
win_inst = create_window(4, COLS(), LINES()-4, 0; border = false)

window_println(win_inst, """
Press ENTER to move the panel to top.
Press X to hide/show the panel.
Press F1 to exit.
""")

# Create the windows and panels.
win1 = create_window(10, 40, 0, 22; border = true, title = " Panel 1 ")
win2 = create_window(10, 40, 3, 32; border = true, title = " Panel 2 ")
win3 = create_window(10, 40, 6, 42; border = true, title = " Panel 3 ")
win4 = create_window(10, 40, 9, 52; border = true, title = " Panel 4 ")

panels = [create_panel(win1),
          create_panel(win2),
          create_panel(win3),
          create_panel(win4)]

# Add text to the windows.
window_println(win1, 0, "Text with multiple lines\nwith center alignment";
               alignment = :c, pad = 0)

window_println(win2, 0, "Text with multiple lines\nwith left alignment";
               alignment = :l, pad = 1)

window_println(win3, 0, "Text with multiple lines\nwith right alignment";
               alignment = :r, pad = 1)

window_println(win4, 0, "Line with right alignment";  alignment = :r, pad = 1)
window_println(win4,    "Line with center alignment"; alignment = :c, pad = 0)
window_println(win4,    "Line with left alignment";   alignment = :l, pad = 1)

# Create the menu.
menu = create_menu(["Panel 1", "Panel 2", "Panel 3", "Panel 4"])
set_menu_win(menu,win_menu)
post_menu(menu)

# Refresh all the windows.
refresh()
refresh_all_windows()
update_panels()

# Store which panels are hidden.
hidden_panels = [false; false; false; false]

# Wait for a key and process.
ch,k = jlgetch()

while k.ktype != :F1
    global ch,k

    if !menu_driver(menu, k)
        if ch == 10 || k.value == "x"
            item_name = current_item_name(menu)

            idx = 0

            if item_name == "Panel 1"
                idx = 1
            elseif item_name == "Panel 2"
                idx = 2
            elseif item_name == "Panel 3"
                idx = 3
            elseif item_name == "Panel 4"
                idx = 4
            end

            idx == 0 && continue

            if ch == 10
                !hidden_panels[idx] && move_panel_to_top(panels[idx])
            else
                if hidden_panels[idx]
                    show_panel(panels[idx])
                    hidden_panels[idx] = false
                else
                    hide_panel(panels[idx])
                    hidden_panels[idx] = true
                end
            end
        end
    else
        refresh_window(win_menu)
    end

    update_panels()
    doupdate()

    ch,k = jlgetch()
end

destroy_menu(menu)
destroy_tui()

it was possible to construct this:

As I mentioned before, I really need help to make this an complete package! There are a lot of things to do, the most important is to make possible to test the package in Travis (even a very simple test of loading libncurses is failing on Linux, but not on macOS).

Thus, can anyone help me with this endeavor?

CC: @affans, @Jean_Michel


#2

Thank you very much for your work. Here are some initial questions:

Here is the list of Ncurses functions I have used in my last Ncurses project (it was in Ruby)

addch  addchstr addstr  attron attrset  beep begx  begy bkgdset  box cbreak
clear   close  close_screen  clrtobot  clrtocol  clrtoeol  color_pair  cols
curs_set  curx cury def_prog_mode def_shell_mode deleteln echo endwin getch
getmouse  hline  inch  inchnstr  initscr  init_pair insertln keyname keypad
lines  maxx maxy mousemask  nl nocbreak noecho  noraw noutrefresh overwrite
raw  refresh  reset_prog_mode  scrollok  setpos  start_color  stdscr subwin
ungetch vline

There are quite a few of them which seem to be missing from your implementation. Is it because you did
not get around to it, or do you have a reason to omit some?

Another question. I asked for a minimal example. For example, the following Ruby program
just waits for a mouse click, displays the mouse state and coordinates, and after another character is given quits.

require "ncurses"
include Ncurses
initscr()
mousemask(BUTTON1_PRESSED|BUTTON1_RELEASED|BUTTON1_CLICKED)
getch()
mv(4,19)
addstr(getmouse().inspect)
refresh()
getch()
endwin()

How would this be translated in your framework?
I ask these questions because I hope that you will let experienced Curses programmers use directly your framework as an implementation of Curses, so you should give as part of your package a ‘raw’ interface (but not too raw; I certain agree that interaction with the ncurses library should be through an object TUI_WINDOW).

So can you explain your design?


#3

Hi @Jean_Michel,

Thanks!

Just because I did not add them. I am using the superb meta-programming capabilities of Julia, and wrapping new functions is very easy. Thanks for that list, I will add all of them.

My goal is to provide all the low-level access to Ncurses and also a Julia-like API to make things easier.

I have not implemented yet the mouse interface. My planned scheduled is:

  1. Make tests work so that we can register the package;
  2. Have a very basic but functional API with windows, menus, panels, and forms.
  3. Add color support, possibly a translator between Crayons.jl and Ncurses.
  4. Think about how to make input handling (including mouse) easier to the user.

#4

Hi guys!

First of all, sorry for the number of posts, I am very excited for the things we can do here :blush:

As a real proof of concept of what we can do using Ncurses, I could create a text satellite simulator with asynchronous commands to the interface. It was very nice to see this done using a very small code. The satellite simulation is handle by SatelliteToolbox.jl. Hence, I can now create a monitor for the simulations that are normally run on workstations in which I have only ssh access.

The source-code can be seen here: https://github.com/ronisbr/TextUserInterfaces.jl/blob/master/examples/satellite_simulator.jl


#5

could you support this package for colors?


#6

This is great! I would love to help out. My use case is very similar… running large scale simulations over a cluster, and a terminal dashboard would be great to monitor the simulations.

What do you think is a good place for me to start? How does one go about wrapping a C library?


#7

Maybe VisualRegressionTests package could help you testing your package. I have seen this used to test GraphPlot package, but I haven’t used it myself.


#8

Good! :slight_smile:

Actually, I do not have a good plan :blush: There is a code using macros and @eval to wrap the Ncurses functions for the libraries libncurses, libmenus, libforms, and libpanels. If you like, then you can add more functions like the ones asked by @Jean_Michel. Another important thing (that I was not capable of doing yet) is to make the very simple test of loading the libraries work on Travis. I have no idea why it is falling… It works on macOS.

By the way, let me know you Github username so that I can grant access to the repository.

Maybe, I have no idea how the color support will be implemented. Julia uses ANSI escape sequences and Ncurses does not. We have to convert between them (which seems already done in VT100.jl package). However, AFAIK, Ncurses colors are limited to only 8.

Thanks! I will take a look at this package.


#9
function __init__()

    try

        ncurses.libncurses = Libdl.dlopen("libncursesw")

    catch

        ncurses.libncurses = Libdl.dlopen("libncurses")

    end



    try

        ncurses.libform  = Libdl.dlopen("libformw")

    catch

        ncurses.libform  = Libdl.dlopen("libform")

    end



    try

        ncurses.libmenu  = Libdl.dlopen("libmenuw")

    catch

        ncurses.libmenu  = Libdl.dlopen("libmenu")

    end



    try

        ncurses.libpanel = Libdl.dlopen("libpanelw")

    catch

        ncurses.libpanel = Libdl.dlopen("libpanel")

    end

end

Ahh I see, so here you are opening up the shared ncurses libraries. You don’t have to give me access to the repository. I will submit PRs that you can approve :slight_smile:


#10

Good news, after the help of @jpsamaroo I was able to make the simple tests (library load) pass in Linux and macOS. Now, we should just need to find out a good and simple way to actually test the package. I am just afraid that maybe the tests must be platform dependent.


#11

IMO the best way to handle testing this package is to dump the desired terminal output to a file, saving that as a “good test”, and then do the same thing during tests and compare the two. It’s crude, but should work.

One issue with the above would be if ncurses is unhappy that it’s no longer running under an interactive TTY (totally possible), in which case we’d have to get fancy and use a PTY to capture the output. Not impossible, but definitely somewhat annoying.


#12

And now we have initial color support!

P.S.: Please, don’t judge my complete lack of design sense… :smile:


#13

https://github.com/Evizero/ReferenceTests.jl can help, it is used by UnicodePlots.