Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support gradients #233

Open
wants to merge 4 commits into
base: main
Choose a base branch
from

Conversation

Mathias-Boulay
Copy link

@Mathias-Boulay Mathias-Boulay commented Jan 22, 2025

As said on this issue, I said I would propose a PR.

Screenshot From 2025-01-22 15-32-11

Notable elements and changes

  • The API is fully backwards compatible, as console was a transitive dependency and I needed my project

  • Add supports for multi-step gradients. If the terminal doesn't support "true colors", it will have no effect.

  • utils functions to check/force true color support

From a dotted_string, you can use [on_]gradient(_HEX_COLOR)+
For example:

".magenta.gradient_E50000_FF8D00_FFEEOO_028121_004CFF_770088"

This will use a gradient on true colors terminals and fall back on the magenta color for other terminals.

From a StyledObject<D> perspective, use .[on_]gradient(GradientColor) to add a color to the gradient.

  • This PR doesn't bring additional tests, as I want first to be sure the API is final

  • The gradient support is heavily inspired by Tinterm

@djc
Copy link
Member

djc commented Jan 23, 2025

This is a pretty large wad of new code, so I'd like some context on how you arrived at this being a good design that fits in with the current design of the library/API -- I understand the high-level goal of being accessible from a dotted_string and that this results in a StyledObject API. What does on_ vs not on_ mean?

src/utils.rs Outdated Show resolved Hide resolved
src/utils.rs Outdated Show resolved Hide resolved
src/utils.rs Outdated
ColorGradient::new(channels[0], channels[1], channels[2])
}

pub(crate) fn interpolate(&self, other: &ColorGradient, t: f32) -> ColorGradient {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is t? Use Self instead of repeating the type name.

Copy link
Author

@Mathias-Boulay Mathias-Boulay Jan 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

t is a common variable name in interpolation animation. From 0.0 to 1.0, 0.5 being a 50/50 blend of 2 colors. Out of my head, I don't know how else I can rename the variable besides x

@@ -618,7 +702,7 @@ impl<D> StyledObject<D> {
}

macro_rules! impl_fmt {
($name:ident) => {
($name:ident,$format_char:expr) => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this necessary? Could it be extracted into a separate commit/PR?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, that's probably the part I like the least about my PR, but I didn't manage otherwise.

Basically, to apply a gradient on a given string, I need to apply a color after each character.
So I needed to get the properly formatted StyledObject<D>.value.

Before the PR, the object was being directly printed into the formatter buffer via fmt::$name::fmt(&self.val, f)?;.

Now I get back a String from format!(_) macro, which require a way to format that data as an argument to delegate printing format to the same implementation (Display, Debug...).

If there is a way to get back the formatted string without such a hack, I would prefer it.

src/utils.rs Outdated Show resolved Hide resolved
src/utils.rs Outdated Show resolved Hide resolved
src/utils.rs Outdated Show resolved Hide resolved
@Mathias-Boulay
Copy link
Author

Note: I'll break down the commit into logical units once the PR looks good. Otherwise, it is wasted time.

@Mathias-Boulay
Copy link
Author

What does on_ vs not on_ mean?

It copies the existing API, where on_ means the background is colored, else the text is.
See functions like black and on_black, or the from_dotted_string allowing [on_]<u8> for an arbitrary color for terminals supporting only 256 colors.

@Mathias-Boulay Mathias-Boulay requested a review from djc January 23, 2025 14:53
@Mathias-Boulay
Copy link
Author

Mathias-Boulay commented Jan 23, 2025

Changes done and comments answered, now waiting for your input @djc 😄
well, besides the broken action build for certain targets, will do that at some point

@Mathias-Boulay
Copy link
Author

When writing color detection for windows, it appeared that CMD/powershell were not respecting the TERM/COLORTERM variables. Other windows terms may, though.

static STDOUT_COLORS: Lazy<AtomicBool> =
Lazy::new(|| AtomicBool::new(default_colors_enabled(&Term::stdout())));
static STDOUT_TRUE_COLORS: Lazy<AtomicBool> =
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of adding these, should we change STDOUT_COLORS/STDERR_COLORS to use a tri-value AtomicU8 to encode an enum ColorSupport { None, Some, True }? (Probably without changing any existing public API.)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would work and allow to easily support more terminal color ranges.
Unlikely to happen, as I've seen only one terminal with like 512. Probably pointless at this stage.

src/utils.rs Outdated Show resolved Hide resolved
src/utils.rs Outdated Show resolved Hide resolved
src/utils.rs Outdated Show resolved Hide resolved
src/utils.rs Show resolved Hide resolved
src/utils.rs Outdated
}
for attr in &self.style.attrs {
write!(f, "\x1b[{}m", attr.ansi_num())?;
reset = true;
}
}
fmt::$name::fmt(&self.val, f)?;
// Get the underlying value
let mut buf = format!($format_char, &self.val);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What are the efficiency implications here? This looks like it would do a lot of extra allocations.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sadly there are extra allocations. This is related to this comment about the additional macro argument.
As good measure, I tweaked the code to have the allocation cost only for gradients, as it isn't needed for normal colors.

use Self for ColorGradient functions
@Mathias-Boulay
Copy link
Author

Code updated, comments answered once again.

Waiting for more input on the new code/comments @djc 😄

unfinished comments conversation:
#233 (comment)
#233 (comment)
#233 (comment)
#233 (comment)

Oh my god I sound like an AI with such formatting.

@Mathias-Boulay Mathias-Boulay requested a review from djc January 24, 2025 21:26
@Mathias-Boulay
Copy link
Author

Heads up @djc you seem busy, is there someone else who can finish the review of the PR ?

@djc
Copy link
Member

djc commented Jan 29, 2025

I don't think so. I had not forgotten but my son is sick so a little more time constrained than usual.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants