roelvandijk / terminal-progress-bar

Other
24 stars 9 forks source link

Support user defined drawing function in flexible and convenient way #22

Closed chshersh closed 6 years ago

chshersh commented 6 years ago

Recently I thought about implementing such library by myself. But I discovered that it's already implemented. And this is really cool! Though, I would like to have an ability to specify how to draw bar.

Currently I can only draw it like this:

[=======>.................]

But what if why want

[###....]

or

☑☑☑☑☐☐☐☐

or

(☉☉☉••••)

or

❤❤❤❤❤💔💔💔💔

Or maybe I want blue progress bar, or maybe I even want it to change color from red to green as progress?

The solution, as I see, is to allow to have something like

startCustomProgress
    :: (Integer -> Text)  -- ^ Function from current progress to textual message
    -> IO (ProgressRef, ThreadId)

Not sure though, but looks reasonable enough.

roelvandijk commented 6 years ago

It makes sense to allow the user to set the progress bar drawing style.

The question is how much flexibility we allow. The most flexible and powerful is indeed a function from Integer -> Text. But then it is also harder to reason about the correctness. We rely on the fact that progress bars always have the exact same width. Otherwise the erasing doesn't work properly.

So it might be enough to just specify the [, ], ., = and >. Perhaps with optional ANSI control sequences for colours and effects. Or just embed those in the strings themselves.

chshersh commented 6 years ago

Thanks for your reply!

We rely on the fact that progress bars always have the exact same width. Otherwise the erasing doesn't work properly.

This clarifies things. I'm for code correctness. I guess we don't want users to implement terminal erasing manually.

So it might be enough to just specify the [, ], ., = and >. Perhaps with optional ANSI control sequences for colours and effects.

That's enough for me! All these parameters can be specified in one single data type. I imagine the API is the following:

data ProgressOptions = ProgressOptions
    { progressOptionsOpen     :: String  -- [
    , progressOptionsClose    :: String  -- ]
    , progressOptionsReady    :: String  -- =
    , progressOptionsCurrent  :: String  -- >
    , progressOptionsUnfilled :: String  -- .
    , progressOptionsPrefix   :: Label   -- ^ Prefixed label.
    , progressOptionsPostfix  :: Label   -- ^ Postfixed label.
    , progressOptionsWidth    :: Integer -- ^ Total progress bar width in characters.
    , progressOptionsTotal    :: Integer -- ^ Total amount of work.
    }

And then have

-- | Start a thread to automatically display progress. Use incProgress to step
-- the progress bar.
startProgress :: ProgressOptions -> IO (ProgressRef, ThreadId)

with some defaultProgressOptions. Naming can be not perfect...

qptain-Nemo commented 6 years ago

I do generally prefer terminal progress bars' leading character to be the same as the rest as well.

roelvandijk commented 6 years ago

Thank you for your input @qptain-Nemo. Removing the leading character, or having it the same is possible with the solution sketched by @ChShersh. I will implement it and it will be available in version 0.3.

roelvandijk commented 6 years ago

The drawing of the progress bar can now be customized using a ProgressOptions value. See 242c846d4eb535cb01fd50404b6ad30fa5e13e09.

This is still limited in the sense that you can not use ANSI escape sequences. This is because I use the lengths of various strings in calculations. This assumes that the length of the string is equal to the amount of characters that appear your terminal when you print the string. This is not true for escape sequences.

I think that if we want escape sequences we have to add them as separate options in the ProgressOptions record. That way the intention/requirement that they are non-printing is clear.

chshersh commented 6 years ago

@roelvandijk Could you clarify one thing, please (I'm an expert in ascii sequences): do ASCII sequence include coloring? So I can't currently specify color for every character separately?

roelvandijk commented 6 years ago

ANSI escape codes are used for colors in terminals. You can use them in ProgressOptions, but things will work a bit different than you would expect.

For example, we could use progressOptOpen = "\033[0;31m[" That would mean that [ and everything after it is colored red.

But

length "[" == 1
length "\033[0;31m[" == 8

When calculating the amount of space we have for drawing the progress bar I do something like effectiveWidth = width - length progressOptOpen (simplified). So in the end the bar would be 7 characters too short.

That's why I propose having separate options for ANSI sequences, or anything the user promises is unprintable. If something it unprintable it may also be a function of the current progress. Because there is no danger of breaking the redraw logic. Then you can have the colors change based on the progress.

chshersh commented 6 years ago

Separate options for coloring looks better to me as well!

roelvandijk commented 6 years ago

Escape codes are now supported (6e5b0759a89c9c601a495196f39f7316751bb705).

See the example program for some colorful progress bars.

I'm closing this issue as the feature is now completely implemented. Feel free to re-open if this isn't quite what you had in mind.