jam1garner / owo-colors

A zero-allocation no_std-compatible zero-cost way to add color to your Rust terminal

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Add enum for dynamic colors

jam1garner opened this issue Β· comments

Motivation

Currently, the best way to handle dynamic colors of multiple types (i.e. mixing between Ansi, Xterm, and Rgb) is using something along the lines of dyn DynColor or creating an enum by hand that dispatches to the individual DynColors methods. This is kinda a lot of effort.

Solution

Add an enum with 3 variants: Ansi, Xterm, and Rgb, probably called DynColors (open to suggestions) that implements DynColor.

To allow something like this:

use owo_colors::{OwoColorize, XtermColors, DynColors};

fn main() {
    let mut some_color = DynColors::Rgb(0, 0, 0);
    println!("{}", "black".color(some_color));
    some_color = DynColors::Xterm(XtermColors::Yellow);
    println!("{}", "on yellow".on_color(some_color));
}
commented

Hi, I had some time to work on this finally :)

After thinking about the problem I came to the conclusion that a Style struct in combination with a style method on OwoColorize would be ideal for color schemes (or rather "style schemes").

At the moment, it's possible to support color schemes, but text modifiers (bold, underlined, etc.) still would be predefined by the crate that offers the option of a color scheme.

Besides that, a DynColors::Default would be useful. Default acts as if nothing was set (useful for Styles).

Here is an (untested) example of how this could look:

use owo_colors::{OwoColorize, DynColors, AnsiColors};

struct Style {
    /// Foreground color 
    fg: DynColors,
    /// Background color 
    bg: DynColors,
    text_modifiers: &[TextModifiers],
    // I'd prefer a more economical name, to be honest. Maybe `text: &[Text]`
    // would be okay. Then users would use `text: &[Text::Bold, Text::Underline]
}

enum TextModifiers {
    Bold,
    Dimmed,
    Italic,
    Underline,
    Blink,
    BlinkFast,
    Reversed,
    Hidden,
    Strikethrough,
}

fn main(){
    let style = Style {
        fg: DynColors::Ansi(AnsiColors::Red),
        bg: DynColors::Default, // don't change the background color
        text_modifiers: &[TextModifiers::Bold, TextModifiers::Blinking],
    };
    println!("{}", "some text".style(style))
}

Similarly, a generic text_modfiers method (ideally with a better name... ;)) that takes &[TextModifiers] would be useful.

What do you think about this?

I believe this would give end-users great flexibility. For example, maybe there is a color_eyre user that would like to let the selected source line blink in bold red with a yellow background (πŸ˜‚), or use "hidden" on dependency code hashs.

Regarding the name of DynColors: I don't care much, but is there a reason against a simple Colors? To me, it seems Colors also would be okay (and a little shorter).

What do you think about this?

Sounds great. I think we'll probably want to make those members private rather than letting users access the constructor itself so we can easily change the structure later if we need to but letting users configure the style completely sounds great.

ideally with a better name...

maybe effect?

commented

@yaahc: I think effect would be great.

@jam1garner: Please let me know if you agree with my proposal. If you are busy at the moment, I should be able to implement the changes and send a PR.

I like Effect as well. And I agree with @yaahc, I think maybe hiding the fields themselves is probably a good idea. As for alternative API designs, probably an (owned) builder? Also probably worth mentioning that a style method probably shouldn't take ownership.

Maybe something like this:

use owo_colors::{OwoColorize, Colors, AnsiColors};

struct Style {
    /// Foreground color 
    fg: Option<Colors>,
    /// Background color 
    bg: Option<Colors>,
    effects: &[Effect],
}

enum Effect {
    Bold,
    Dimmed,
    Italic,
    Underline,
    Blink,
    BlinkFast,
    Reversed,
    Hidden,
    Strikethrough,
}

fn main(){
    let style = Style::new().fg(AnsiColors::Red).effects(&[Effect::Bold, Effect::Blinking]);
    println!("{}", "some text".style(style))
}

Thoughts? I do like all the suggestions though!

@d4h0 Ah wow! Seems you commented in the middle of me writing my comment. I definitely like your suggestion and would 100% appreciate a PR.

commented

Ah wow! Seems you commented in the middle of me writing my comment. I definitely like your suggestion and would 100% appreciate a PR.

Alright, I start working on the PR :)

commented

@jam1garner: Wow, the code is more difficult for me to follow, that I've expected... πŸ˜†

While trying to understand the code completely, and thinking about the best way to implement the changes, I got an idea – but I'm not sure if this is possible, or even makes sense (performance-wise, and otherwise).

Basically, I'd like to create a type that implements OwoColorize but does not represent the value that will be styled (i.e. a placeholder). Instead, this type can be styled and then is kind of like a factory to style other values that implement OwoColorize.

The following code probably makes not much sense, but demonstrates what I try to archive:

pub struct Style;

impl Style {

    fn style<C: OwoColorize>(&self, to_be_formatted: C) -> SomethingStyled {
        // somehow get surounding formatting wrappers and return `to_be_formatted`
        // wrapped in them
    }
}

// implement `OwoColorize` for `Style`

fn main(){

    // What the end-user would do
    let s = Style

        // These are regular methods of `OwoColorize`:
        .yellow()
        .on_blue()
        .underlined()

        // This is a new method of `OwoColorize` that returns a value that
        // has access to the inner `Style`:
        .build();

    // What a crate using `owo_colors` would do
    let msg = s.style("a message");
    println!("{:?}", msg);
    // ^ prints "a message" in yellow with a blue background and underlined
}

The benefit would be that all of OwoColorizes API is available for creating styles. And, initially, I thought this would be easier to implement than what we talked about before (but now I'm not sure anymore...).

Does this make any sense, and would something like this be easy to implement efficiently?

I thought, before I waste too much time, I better get some feedback... πŸ˜„

While I think the core idea is good, I feel the problem is that I really don't think it's possible to leverage OwoColorize itself like that. For example, if you do "my text".underline().yellow(), it will return a FgColorDisplay<'a, Yellow, UnderlineDisplay<'a, &str>> which will implement Display but isn't really possible to work with otherwise? However, I do agree that aiming for API parity with OwoColorize is a good idea. And I agree with the "building an object to apply to a Display type" approach.

Also @d4h0 if you have any questions about the codebase feel free to DM me on discord (jam1garner#7693) or twitter (@jam1garner) as my response time will likely be a bit better, since I imagine that'd work better in a synchronous chat.

commented

That makes sense.

My thinking was, it would be possible to define a Wrapper trait that has a get_inner_mut method, that can be used to get and set the inner value (and I hoped there is something better that I don't know yet).

Also @d4h0 if you have any questions about the codebase feel free to DM me on discord (jam1garner#7693) or twitter (@jam1garner) as my response time will likely be a bit better, since I imagine that'd work better in a synchronous chat.

Thank you! :)

The problem was more, that there were some new concepts and many macros, and that it was difficult to keep everything in my head to find a good solution.

I have created something that works and that I can show you now. After I sent this message I will create a PR.

Please don't hesitate to tell me about any existing problem (or even if my code is completely unusable). I would be astonished if I produced the ideal solution on the first attempt! πŸ˜