mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-09-21 09:05:10 -07:00
WIP [UI] Theme-aware onboarding banner with frosted light mode
Banner colours now derive from palette tokens at ~60fps (tick-driven, equality-guarded setters) so scheme switches and live accent-picker previews apply instantly: - dark stages: byte-for-byte the original treatment (near-black stage from window hue, Highlight accent, white centre halo, vignette 0.62) - light stages: pastel accent-hue wash instead of a neutral grey copy, brightness-lifted accent for additive glow legibility, deep-Highlight halo (uGlowColor) instead of white blowout, gentler vignette (uVignetteMin 0.88) so corners don't go muddy - black logo silhouette variant selected on light stages - theme picker preseeded with brand green (brand_colors.h single source) WIP notes for next session: - real-pixel wizard screenshot check still pending (headless capture exists: Xvfb :77 + isolated XDG_DATA_HOME; shader vs fallback pixel analysis not finished) - user plans separately: promote Fusion to default theme, Default -> system
This commit is contained in:
parent
803fa21753
commit
e08d7a2503
10 changed files with 231 additions and 16 deletions
|
|
@ -1,7 +1,10 @@
|
|||
#include "shader_banner_widget.h"
|
||||
|
||||
#include "../../theme_manager.h"
|
||||
#include "banner_shader_config.h"
|
||||
#include "brand_colors.h"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QPainter>
|
||||
#include <QQmlContext>
|
||||
#include <QQmlEngine>
|
||||
|
|
@ -11,11 +14,68 @@
|
|||
|
||||
namespace
|
||||
{
|
||||
// Near-black base palette -- the background is dark and quiet so the green
|
||||
// accent stands out.
|
||||
constexpr QRgb kColorA = 0x1A1A20;
|
||||
constexpr QRgb kColorB = 0x0E0E12;
|
||||
constexpr QRgb kAccent = 0x8BDD6B;
|
||||
// Curated near-black stage -- used only when the active palette resolves no
|
||||
// usable window colour. Matches the banner's original design (dark and quiet
|
||||
// so the accent stands out) and satisfies design-plans §2.1's "identity
|
||||
// survives a bare palette".
|
||||
constexpr QRgb kFallbackColorA = 0x1A1A20;
|
||||
constexpr QRgb kFallbackColorB = 0x0E0E12;
|
||||
|
||||
struct SuggestedColors
|
||||
{
|
||||
QColor colorA;
|
||||
QColor colorB;
|
||||
QColor accent;
|
||||
QColor glowColor;
|
||||
qreal vignetteMin = 0.62;
|
||||
bool lightStage = false;
|
||||
};
|
||||
|
||||
SuggestedColors suggestedBannerColors()
|
||||
{
|
||||
const QPalette &pal = qApp->palette();
|
||||
const QColor window = pal.color(QPalette::Active, QPalette::Window);
|
||||
const QColor highlight = pal.color(QPalette::Active, QPalette::Highlight);
|
||||
if (!window.isValid() || !highlight.isValid()) {
|
||||
return {
|
||||
QColor(kFallbackColorA), QColor(kFallbackColorB), kCockatriceBrandGreen, QColor(Qt::white), 0.62, false};
|
||||
}
|
||||
|
||||
// Dress the stage for the scheme so the banner never fights the
|
||||
// surrounding window in either mode. Dark palettes keep the original
|
||||
// quiet near-black stage (lightness 29 → 16) with the theme's window
|
||||
// hue; light palettes get a pastel "frosted accent" treatment built from
|
||||
// the Highlight hue instead of a plain near-white copy: a gentle mint
|
||||
// wash that clearly belongs to the theme.
|
||||
const qreal luma = 0.299 * window.red() + 0.587 * window.green() + 0.114 * window.blue();
|
||||
const bool lightStage = luma > 115.0;
|
||||
if (lightStage) {
|
||||
const int hue = highlight.hslHue();
|
||||
// Achromatic accents (grey) get a neutral near-white stage instead.
|
||||
const int stageSat = hue < 0 ? 0 : 35;
|
||||
const int hueSafe = hue < 0 ? 0 : hue;
|
||||
auto pastel = [hueSafe, stageSat](int lightness) { return QColor::fromHsl(hueSafe, stageSat, lightness); };
|
||||
// Brightness-lifted accent for additive glows: Highlight on a light
|
||||
// stage must be mid-bright to read (the shipped light Highlight is a
|
||||
// deep green that washes out additively against white).
|
||||
const int accentLightness = qBound(120, highlight.lightness() + 70, 165);
|
||||
const int accentSaturation = hue < 0 ? 0 : qMax(highlight.hslSaturation(), 140);
|
||||
const QColor liftedAccent = hue < 0 ? highlight : QColor::fromHsl(hueSafe, accentSaturation, accentLightness);
|
||||
// The centre glow uses the deep Highlight itself -- a coloured halo
|
||||
// behind the dark logo instead of a white blowout.
|
||||
return {pastel(247), pastel(231), liftedAccent, highlight, 0.88, true};
|
||||
}
|
||||
|
||||
// Dark stage: force the window hue down to the banner's curated darkness,
|
||||
// scaling saturation away so chromatic palettes tint it without going
|
||||
// muddy. White glow and the original strong vignette stay untouched.
|
||||
auto stage = [&window](int lightness) {
|
||||
const int hue = window.hslHue();
|
||||
const int saturation = hue < 0 ? 0 : qBound(0, qRound(window.hslSaturation() * (lightness / 40.0)), 255);
|
||||
return QColor::fromHsl(hue, saturation, lightness);
|
||||
};
|
||||
return {stage(29), stage(16), QColor(highlight), QColor(Qt::white), 0.62, false};
|
||||
}
|
||||
} // namespace
|
||||
|
||||
class GradientFallbackWidget : public QWidget
|
||||
|
|
@ -23,15 +83,27 @@ class GradientFallbackWidget : public QWidget
|
|||
public:
|
||||
using QWidget::QWidget;
|
||||
|
||||
void setColors(const QColor &a, const QColor &b)
|
||||
{
|
||||
if (a != colorA || b != colorB) {
|
||||
colorA = a;
|
||||
colorB = b;
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
void paintEvent(QPaintEvent *) override
|
||||
{
|
||||
QPainter painter(this);
|
||||
QLinearGradient gradient(0, 0, width(), height());
|
||||
gradient.setColorAt(0.0, QColor(kColorA));
|
||||
gradient.setColorAt(1.0, QColor(kColorB));
|
||||
gradient.setColorAt(0.0, colorA);
|
||||
gradient.setColorAt(1.0, colorB);
|
||||
painter.fillRect(rect(), gradient);
|
||||
}
|
||||
|
||||
private:
|
||||
QColor colorA{QColor(kFallbackColorA)};
|
||||
QColor colorB{QColor(kFallbackColorB)};
|
||||
};
|
||||
|
||||
BannerHost::BannerHost(QWidget *parent) : QWidget(parent)
|
||||
|
|
@ -62,6 +134,9 @@ BannerHost::BannerHost(QWidget *parent) : QWidget(parent)
|
|||
connect(&clock, &QTimer::timeout, this, &BannerHost::tick);
|
||||
clock.setInterval(16); // ~60fps; the shader itself is cheap, this is just a wall clock
|
||||
|
||||
connect(themeManager, &ThemeManager::themeChanged, this, &BannerHost::applyThemeColors);
|
||||
applyThemeColors();
|
||||
|
||||
applyMotifPreset(currentMotif);
|
||||
updateAspect();
|
||||
}
|
||||
|
|
@ -123,9 +198,9 @@ void BannerHost::applyMotifPreset(Motif motif)
|
|||
|
||||
const Preset p = presetFor(motif);
|
||||
|
||||
config->setColorA(QColor(kColorA));
|
||||
config->setColorB(QColor(kColorB));
|
||||
config->setAccent(QColor(kAccent));
|
||||
config->setColorA(bannerColorA);
|
||||
config->setColorB(bannerColorB);
|
||||
config->setAccent(bannerAccent);
|
||||
config->setLogoVisible(motif == Motif::Welcome);
|
||||
|
||||
if (isFirstApply) {
|
||||
|
|
@ -183,8 +258,33 @@ void BannerHost::hideEvent(QHideEvent *event)
|
|||
clock.stop();
|
||||
}
|
||||
|
||||
void BannerHost::applyThemeColors()
|
||||
{
|
||||
const SuggestedColors colors = suggestedBannerColors();
|
||||
bannerColorA = colors.colorA;
|
||||
bannerColorB = colors.colorB;
|
||||
bannerAccent = colors.accent;
|
||||
|
||||
if (usingFallback) {
|
||||
fallback->setColors(bannerColorA, bannerColorB);
|
||||
fallback->update();
|
||||
} else if (config) {
|
||||
config->setColorA(bannerColorA);
|
||||
config->setColorB(bannerColorB);
|
||||
config->setAccent(bannerAccent);
|
||||
config->setGlowColor(colors.glowColor);
|
||||
config->setVignetteMin(colors.vignetteMin);
|
||||
config->setLogoDark(colors.lightStage);
|
||||
}
|
||||
}
|
||||
|
||||
void BannerHost::tick()
|
||||
{
|
||||
// Palette previews (e.g. accent drags in the wizard's QuickSetupPanel)
|
||||
// apply qApp->palette() without firing themeChanged, so re-derive here;
|
||||
// BannerShaderConfig's setters are equality-guarded, so this is a no-op
|
||||
// unless the colours actually changed.
|
||||
applyThemeColors();
|
||||
if (config) {
|
||||
qreal t = elapsed.elapsed() / 1000.0;
|
||||
config->setTime(t);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue