Proposal: `color!` macro
What problem does this solve or what need does it fill?
Since Bevy 0.14, the Bevy Color API was expanded and improved. While these improvements have obvious benefits, one major drawback I've found is increased verbosity.
I would argue that most of the time, when color is used from code, it is used for prototyping and/or debugging. Because of this, it is essential for a color API to be highly ergonomic. In my opinion, this is a big motivation for having color presets to begin with.
However, with the 0.14 changes, accessing these presets is awkward and produces a lot of "code noise":
Firstly, the color presets are in a deeply nested module which isn't accessible via the prelude:
let color = bevy::color::palletes::css::RED;
Secondly, the preset isn't actually the color itself, often requiring the user to do a conversion:
let color = bevy::color::palletes::css::RED.into();
I think this verbosity goes against the motivation for having presets to begin with, and I want to propose a solution to fix it.
What solution would you like?
I believe this is a perfect use case for a simple macro, such as color!.
This macro could take a variety of inputs and always produce a Color type.
I don't have strong opinions on what the syntax should be; but as an example:
| Input | Output |
|---|---|
color!(basic::RED) |
Color::from(bevy::color::palletes::basic::RED) |
color!(css::RED) |
Color::from(bevy::color::palletes::css::RED) |
color!(rgba, r, g, b, a) |
Color::from(Srgba::new(r, g, b, a)) |
color!(rgb, r, g, b) |
Color::from(Srgba::rgb(r, g, b)) |
color!(hsla, h, s, l, a) |
Color::from(Hsla::new(h, s, l, a)) |
Note: I'm using the $pallet::$name syntax for presets to allow code completion to work nicely. I think this is important.
In my perfect world, this could be further expanded to include # codes too:
| Input | Desired Output |
|---|---|
color!(#ff0000) |
Opaque Red |
color!(#ff000077) |
Semi Transparent Red |
I think this would solve both issues by:
- Eliminating the verbosity in accessing presets
- Eliminating the conversion from inner color data into a usable
Colortype
What alternative(s) have you considered?
As a workaround, the user could include the pallet they want with a use statement:
use bevy::color::palletes::css;
And shorten the calls to:
let color = css::RED.into();
But this still doesn't address the second problem, and it is still awkward to use.
This is especially bad in conditionally compiled code which uses color (which is pretty common for debug drawing), forcing the user to spam use bevy::color::... across their codebase.
Additional context
I'd be happy to try implementing a first pass of this if we like the idea.
There are some other alternatives:
- The palette modules could be added to the prelude, so you could write
css::REDin any module that importsbevy::prelude::* - Bevy methods that accept concrete color types can be changed to instead take any
impl Into<Color> - If I understood the proposed design correctly, BSN will perform automatic conversion via
From/Intofor field values, so waiting for BSN to be implemented would also be a viable alternative
- In my opinion,
cssandbasicare too generic to be in the prelude, considering they specifically apply to color presets. - That would require passing colors through functions that accept
Into<Color>. You're correct in that this would be solved with BSN, but... - Sometimes, especially in debug contexts, we want to use colors outside the
Bundle/Componentparadigm. See here for an example.
Definitely strongly opposed to adding the palettes to the prelude :) I think BSN will help a lot here, but I can see why it may not be a full fix.
Alternatively, maybe we can just get rid of palettes.
I don't think basic/css/tailwind are too generic inside the bevy::color scope.
So then the presets would be shortened too:
let color = bevy::color::css:RED.into()
That + BSN may be a good enough compromise to solve this issue with minimal cost for standard Bevy.
I would argue maybe we even push the basic palette up to bevy::color (via pub use basic::*). So then bevy::color::RED is just bevy::color::basic::RED.
I could then see color! being implemented as a third party crate, if needed.
I don't understand why use bevy::color::palletes::css; or use bevy::color::palletes::css::* isn't an acceptable solution. This is clear, terse and requires only a small amount of setup.
#[cfg(feature = "debug")]
{
painter.color = bevy::color::palettes::css::LIMEGREEN.into();
painter.line(p0, p1);
painter.color = bevy::color::palettes::basic::RED.into();
painter.line(p1, p2);
painter.color = bevy::color::palettes::tailwind::INDIGO_100.into();
painter.line(p2, p3);
}
vs.
#[cfg(feature = "debug")]
{
painter.color = color!(css::LIMEGREEN);
painter.line(p0, p1);
painter.color = color!(RED);
painter.line(p1, p2);
painter.color = color!(tailwind::INDIGO_100);
painter.line(p2, p3);
}
Multiplied by the number of occurrences in a codebase. 10? 100? 1000?
I don't want to expose bevy::color::palettes::css::* or bevy::color::palettes::css to my entire codebase just to support my debug draw code.
This should work FWIW:
#[cfg(feature = "debug")]
{
use bevy::color::palettes::{css, basic::*, tailwind};
painter.color = css::LIMEGREEN.into();
painter.line(p0, p1);
painter.color = RED.into();
painter.line(p1, p2);
painter.color = tailwind::INDIGO_100.into();
painter.line(p2, p3);
}
Or cfg flag the glob import. Closing this out: I don't think this is worth the complexity <3