ajalt / mordant

Multiplatform text styling for Kotlin command-line applications
https://ajalt.github.io/mordant/
Apache License 2.0
963 stars 35 forks source link

Expose methods for performing raw print request #91

Closed typfel closed 1 year ago

typfel commented 1 year ago

Problem

I'm implementing a full screen interactive tool which needs to manipulate the cursor position. My first attempt was using the animation feature to update the screen but I realised it doesn't fit my purpose because it is build to be rendered inline with other widgets by hooking into the print requests, which makes it not compatible with manipulating the cursor.

Ok, since my use case is much simpler than what Animation attempts to do I decided draw my animation on my own. Unfortunately this was not possible without changes the Mordant library since I need to send raw print requests to refresh the screen.

Suggestion

Expose sending raw print requests:

fun rawPrintln(message: String) {
    sendPrintRequest(PrintRequest(message, true))
}

fun rawPrint(message: String) {
    sendPrintRequest(PrintRequest(message, false))
}
ajalt commented 1 year ago

Can you give a little more context on what you're trying to do? Maybe we can come up with a solution that doesn't require you to deal with raw printing.

typfel commented 1 year ago

I'm building a chat client where I provide auto completion when typing commands so I'm capturing the keyboard input directly and is therefore managing the input field the cursor position in my code.

Chat title                             

      <Alice> Hello                                                  
        <Bob> Hello Alice                                                             
─────────────────────────────────────────────────────────────────────────────────────────────────────────
>  draft message                                                                                    

Today using rawPrint I do this:

terminal.rawPrint(buildString {
    append(terminal.cursor.getMoves {
        setPosition(0, 0)
    })
    append(terminal.render(chat(viewState, terminal.info.height)))
    append(terminal.cursor.getMoves {
        startOfLine()
        right(viewState.cursorPosition)
    })
})

So essentially

  1. Move cursor to top left of screen
  2. Render app which always covers the whole screen
  3. Update cursor position

I suppose I could hide the cursor and draw my own cursor.

Another issue had with the Animation class was that it was always inserting a new line at the end which causes issues when you try draw something which covers the whole screen.

ajalt commented 1 year ago

I just released a new version that adds a stop function to Animation, so you can stop it before you print your cursor movements. So you shouldn't need to do raw prints. Let me know if that works for you.

typfel commented 1 year ago

Cool, I'll try it out!

typfel commented 1 year ago

So tried it out and it kinda works with some issues:

ajalt commented 1 year ago

I decided that there isn't any reasonable way for Animation to work out of the box if the cursor is moved while it's running, so I went ahead and exposed the rawPrint function so you can manage the cursor yourself.

I also added an option to omit the trailing line break from Animation if you still want to use it. It's kind of an odd option; i'd rather just never have a trailing line break, but without one you wouldn't be able to have multiple animations running at the same time.