Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions common/color.h
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
#ifndef COMMON_COLOR_H
#define COMMON_COLOR_H

#include <type_traits>

// Used to represent a color. Uses normal 8-bit-per channel RGB.
// Note that these colors are in linear space and their interpretation
// depends on the blade.
Expand Down Expand Up @@ -388,6 +390,11 @@ struct OverDriveColor {
bool getOverdrive() const { return overdrive; }
};

namespace color_details {
template<typename T> struct IsOpaqueColor : std::false_type {};
template<> struct IsOpaqueColor<SimpleColor> : std::true_type {};
template<> struct IsOpaqueColor<OverDriveColor> : std::true_type {};
}

// SIMPLE
// Opaque.fade -> Opaque or Alpha?
Expand Down
37 changes: 28 additions & 9 deletions styles/layers.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,33 @@
#include "alpha.h"
#include "../functions/int.h"
#include "colors.h"
#include "../common/color.h"
#include <utility>

// Usage: Layers<BASE, LAYER1, LAYER2, ...>
// BASE: COLOR or LAYER
// LAYER1, LAYER2: LAYER
// return value: COLOR or LAYER (same as BASE)
// This style works like layers in gimp or photoshop.
// In most cases, the layers are expected to be normally transparent effects
// that turn opaque when then want to paint an effect over the base color.
// If the base color is opqaque, the final result of this style will also be
// opaque. If the base color is transparent, the final result may also be transparent,
// depending on what the layers paint on top of the base color.
// This style works like layers in Gimp or Photoshop: each layer adds to the stack covering the BASE.
// The written order of layers in the blade style code puts the BASE first,
// with any following layers visually covering it. (later layers draw over earlier ones)
// The rule for opacity is as follows:
// The output of Layers<> is opaque if the BASE of that Layers<> is opaque.
// If BASE is transparent: the overall result is transparent.
// No additional solid opaque colors may be stacked on top of an opaque BASE.
//
// StylePtr<> expects its argument to be opaque.
// Therefore, when using StylePtr<Layers<BASE, ... >>(), BASE MUST be an opaque color.
//
// Layers<> itself can still be nested anywhere a STYLE/COLOR is allowed; only the top-level StylePtr<...>()
// requirement forces the requirement of the BASE to be opaque in that specific position.

template<class BASE, class L1>
class Compose {
public:
static_assert(!color_details::IsOpaqueColor<decltype(std::declval<L1&>().getColor(0))>::value,
"\n\n"
"*** Layers<> error: Only the base color may be solid.\n\n");
LayerRunResult run(BladeBase* blade) {
LayerRunResult base_run_result = RunLayer(&base_, blade);
LayerRunResult layer_run_result = RunLayer(&layer_, blade);
Expand Down Expand Up @@ -73,13 +85,20 @@ class Compose<BLACK, L1> {
}
};


template<class BASE, class ... LAYERS> struct LayerSelector {};
template<class BASE> struct LayerSelector<BASE> { typedef BASE type; };

// Drop fully transparent first item (Alpha 0) when there are exactly 2 args.
template<class BASE, class L1> struct LayerSelector<AlphaL<BASE, Int<0>>, L1> { typedef L1 type; };

// Drop fully transparent when it’s the second (right) item in a pair.
template<class BASE, class ABASE>
struct LayerSelector<BASE, AlphaL<ABASE, Int<0>>> { typedef BASE type; };

template<class BASE, class L1> struct LayerSelector<BASE, L1> { typedef Compose<BASE, L1> type; };
template<class BASE, class L1, class ... REST> struct LayerSelector<BASE, L1, REST...> {
typedef typename LayerSelector<Compose<BASE, L1>, REST...>::type type;
template<class BASE, class L1, class ... REST>
struct LayerSelector<BASE, L1, REST...> {
typedef typename LayerSelector<typename LayerSelector<BASE, L1>::type, REST...>::type type;
};

template<class BASE, class... LAYERS> using Layers = typename LayerSelector<BASE, LAYERS...>::type;
Expand Down
21 changes: 19 additions & 2 deletions styles/style_ptr.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,21 @@

#include "blade_style.h"

template<class BASE, class L1> class Compose; // forward-declare Layers node

namespace style_base_check_detail {
template<class T> struct TopBase { using type = T; };
template<class B, class L> struct TopBase<Compose<B,L>> : TopBase<B> {};
Comment thread
profezzorn marked this conversation as resolved.
template<class STYLE>
inline void AssertLayersBaseOpaque() {
using _TopBaseT = typename TopBase<STYLE>::type;
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

I don't think we need this, we can just check if STYLE is transparent.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I tried that, but it doesn’t correctly catch the case where a Layers<> starts with a transparent color because by the time the check runs, it already combined with the next layer, so we get the Layers error instead of the intended “Style must be solid color, not transparent” error.
With the TopBase check, we can directly see the very first color and report the right error message.

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

That would only happen if you have a transparent base and a solid layer somewhere after, right?
Isn't that a highly unusual case?

Copy link
Copy Markdown
Contributor Author

@NoSloppy NoSloppy Aug 19, 2025

Choose a reason for hiding this comment

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

I would say yes. I was testing using
StylePtr<Layers< AlphaL<Yellow,Int<9000>>, Red, AlphaL<Blue,Int<16000>>>>()

But you've conditioned me to cover all the what-ifs.

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

When there are two errors in the code, I don't think we should be particularly picky about which one shows up.

using _TopBaseCol = decltype(std::declval<_TopBaseT&>().getColor(0));
static_assert(color_details::IsOpaqueColor<_TopBaseCol>::value,
"\n\n"
"*** StylePtr<> error: Style must be a solid color, not transparent.\n");
}
}

// Usage: StylePtr<BLADE>
// BLADE: COLOR
// return value: suitable for preset array
Expand Down Expand Up @@ -133,14 +148,15 @@ class ChargingStyle : public Style<T> {
// Get a pointer to class.
template<class STYLE>
StyleAllocator StylePtr() {
style_base_check_detail::AssertLayersBaseOpaque<STYLE>();
static StyleFactoryImpl<Style<STYLE> > factory;
return &factory;
};
}

class StyleFactoryWithDefault : public StyleFactory {
public:
StyleFactoryWithDefault(StyleFactory* allocator,
const char* default_arguments) :
const char* default_arguments) :
allocator_(allocator), default_arguments_(default_arguments) {
}
BladeStyle* make() override {
Expand All @@ -164,6 +180,7 @@ StyleAllocator StylePtr(const char* default_arguments) {
// that you can't turn it on/off, and the battery low warning is disabled.
template<class STYLE>
StyleAllocator ChargingStylePtr() {
style_base_check_detail::AssertLayersBaseOpaque<STYLE>();
static StyleFactoryImpl<ChargingStyle<STYLE> > factory;
return &factory;
}
Expand Down