mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-09-21 00:55:09 -07:00
[Onboarding] Draw the banner logo as a static gradient plate
Replace the black/white logo tint switch with a ShaderEffect plate that repaints the SVG's brand gradient (light AccentSoft -> dark AccentStrong along the baked-in userSpaceOnUse axis) clipped to the full-color logo's alpha silhouette, with the white highlight path overlaid on top — matching the home widget's QPainter composite. The plate is static: no glow or breathing. Brand colors flow from BannerShaderConfig's new brandStrong/ brandSoft pair instead of the removed logoDark flag, and the background motifs get a touch more accent so the mark keeps its coloured surround.
This commit is contained in:
parent
8641123f33
commit
d7529e0c6a
6 changed files with 184 additions and 68 deletions
|
|
@ -531,6 +531,7 @@ qt6_add_shaders(
|
|||
"src/interface/widgets/onboarding/shaders"
|
||||
FILES
|
||||
src/interface/widgets/onboarding/shaders/brand_banner.frag
|
||||
src/interface/widgets/onboarding/shaders/brand_plate.frag
|
||||
)
|
||||
|
||||
qt6_add_resources(
|
||||
|
|
|
|||
|
|
@ -39,10 +39,11 @@ class BannerShaderConfig : public QObject
|
|||
Q_PROPERTY(QColor colorB READ colorB WRITE setColorB NOTIFY colorBChanged)
|
||||
Q_PROPERTY(QColor accent READ accent WRITE setAccent NOTIFY accentChanged)
|
||||
Q_PROPERTY(QColor glowColor READ glowColor WRITE setGlowColor NOTIFY glowColorChanged)
|
||||
Q_PROPERTY(QColor brandStrong READ brandStrong WRITE setBrandStrong NOTIFY brandStrongChanged)
|
||||
Q_PROPERTY(QColor brandSoft READ brandSoft WRITE setBrandSoft NOTIFY brandSoftChanged)
|
||||
Q_PROPERTY(qreal vignetteMin READ vignetteMin WRITE setVignetteMin NOTIFY vignetteMinChanged)
|
||||
|
||||
Q_PROPERTY(bool logoVisible READ logoVisible WRITE setLogoVisible NOTIFY logoVisibleChanged)
|
||||
Q_PROPERTY(bool logoDark READ logoDark WRITE setLogoDark NOTIFY logoDarkChanged)
|
||||
Q_PROPERTY(qreal logoGlow READ logoGlow WRITE setLogoGlow NOTIFY logoGlowChanged)
|
||||
|
||||
public:
|
||||
|
|
@ -200,6 +201,30 @@ public:
|
|||
}
|
||||
}
|
||||
|
||||
QColor brandStrong() const
|
||||
{
|
||||
return m_brandStrong;
|
||||
}
|
||||
void setBrandStrong(const QColor &c)
|
||||
{
|
||||
if (c != m_brandStrong) {
|
||||
m_brandStrong = c;
|
||||
emit brandStrongChanged();
|
||||
}
|
||||
}
|
||||
|
||||
QColor brandSoft() const
|
||||
{
|
||||
return m_brandSoft;
|
||||
}
|
||||
void setBrandSoft(const QColor &c)
|
||||
{
|
||||
if (c != m_brandSoft) {
|
||||
m_brandSoft = c;
|
||||
emit brandSoftChanged();
|
||||
}
|
||||
}
|
||||
|
||||
qreal vignetteMin() const
|
||||
{
|
||||
return m_vignetteMin;
|
||||
|
|
@ -224,18 +249,6 @@ public:
|
|||
}
|
||||
}
|
||||
|
||||
bool logoDark() const
|
||||
{
|
||||
return m_logoDark;
|
||||
}
|
||||
void setLogoDark(bool v)
|
||||
{
|
||||
if (v != m_logoDark) {
|
||||
m_logoDark = v;
|
||||
emit logoDarkChanged();
|
||||
}
|
||||
}
|
||||
|
||||
qreal logoGlow() const
|
||||
{
|
||||
return m_logoGlow;
|
||||
|
|
@ -262,9 +275,10 @@ signals:
|
|||
void colorBChanged();
|
||||
void accentChanged();
|
||||
void glowColorChanged();
|
||||
void brandStrongChanged();
|
||||
void brandSoftChanged();
|
||||
void vignetteMinChanged();
|
||||
void logoVisibleChanged();
|
||||
void logoDarkChanged();
|
||||
void logoGlowChanged();
|
||||
|
||||
private:
|
||||
|
|
@ -288,10 +302,11 @@ private:
|
|||
QColor m_colorB{0x0E, 0x0E, 0x12};
|
||||
QColor m_accent{0x8B, 0xDD, 0x6B};
|
||||
QColor m_glowColor{Qt::white};
|
||||
QColor m_brandStrong{0x13, 0x97, 0x40};
|
||||
QColor m_brandSoft{0xC9, 0xFD, 0x62};
|
||||
qreal m_vignetteMin = 0.62;
|
||||
|
||||
bool m_logoVisible = false;
|
||||
bool m_logoDark = false;
|
||||
qreal m_logoGlow = 1.0;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -41,29 +41,56 @@ Item {
|
|||
fragmentShader: "qrc:/onboarding/shaders/brand_banner.frag.qsb"
|
||||
}
|
||||
|
||||
// The hero logo itself — breathes cleanly over a 0.5–1.0 opacity range.
|
||||
// White silhouette on dark stages, black on light ones, so it stays
|
||||
// readable in both modes.
|
||||
Image {
|
||||
id: logo
|
||||
anchors.centerIn: parent
|
||||
// The white logo sits at full opacity on top of the static gradient plate —
|
||||
// no glow, no breathing. The plate matches home_widget's QPainter composite.
|
||||
Item {
|
||||
id: logoHost
|
||||
visible: bannerConfig.logoVisible
|
||||
source: bannerConfig.logoDark ? "qrc:/resources/cockatrice-logo-black.svg"
|
||||
: "qrc:/resources/cockatrice-logo-white.svg"
|
||||
anchors.centerIn: parent
|
||||
width: root.height * 0.6
|
||||
height: width * (sourceSize.height > 0 ? sourceSize.height / Math.max(sourceSize.width, 1) : 1)
|
||||
fillMode: Image.PreserveAspectFit
|
||||
smooth: true
|
||||
opacity: 0.5 + 0.5 * bannerConfig.logoGlow
|
||||
sourceSize: Qt.size(256, 256)
|
||||
height: width
|
||||
|
||||
Behavior on opacity { NumberAnimation { duration: 300; easing.type: Easing.InOutSine } }
|
||||
// The full-color logo renders beneath the white mark and is consumed as
|
||||
// a texture (layer.enabled) by the plate shader's silhouette mask, so
|
||||
// the gradient is clipped to the bird exactly as the SVG's gradient
|
||||
// paths are. It is never drawn to the screen itself.
|
||||
Image {
|
||||
id: silhouetteMask
|
||||
anchors.fill: parent
|
||||
source: "qrc:/resources/cockatrice.svg"
|
||||
sourceSize: Qt.size(256, 256)
|
||||
fillMode: Image.PreserveAspectFit
|
||||
smooth: true
|
||||
visible: false
|
||||
layer.enabled: true
|
||||
layer.smooth: true
|
||||
}
|
||||
|
||||
transform: Scale {
|
||||
origin.x: logo.width / 2
|
||||
origin.y: logo.height / 2
|
||||
xScale: 0.94 + 0.06 * bannerConfig.logoGlow
|
||||
yScale: 0.94 + 0.06 * bannerConfig.logoGlow
|
||||
// The logo's gradient plate, drawn behind the mark: a linear
|
||||
// AccentSoft (light) -> AccentStrong (dark) sheet along the same
|
||||
// top-left -> bottom-right userSpaceOnUse axis the baked-in SVG used,
|
||||
// clipped to the bird silhouette via uSilhouette. The white highlight
|
||||
// path above is theme independent. Sized to the logo itself — no
|
||||
// rounded badge, matching home_widget's QPainter composite. Static.
|
||||
// Small ShaderEffect, Qt 6.4-safe.
|
||||
ShaderEffect {
|
||||
id: brandPlate
|
||||
anchors.fill: parent
|
||||
property vector4d uStrong: Qt.vector4d(bannerConfig.brandStrong.r, bannerConfig.brandStrong.g,
|
||||
bannerConfig.brandStrong.b, 1.0)
|
||||
property vector4d uSoft: Qt.vector4d(bannerConfig.brandSoft.r, bannerConfig.brandSoft.g,
|
||||
bannerConfig.brandSoft.b, 1.0)
|
||||
property var uSilhouette: silhouetteMask
|
||||
fragmentShader: "qrc:/onboarding/shaders/brand_plate.frag.qsb"
|
||||
}
|
||||
|
||||
Image {
|
||||
id: logoImage
|
||||
anchors.fill: parent
|
||||
source: "qrc:/resources/cockatrice-logo-white.svg"
|
||||
sourceSize: Qt.size(256, 256)
|
||||
fillMode: Image.PreserveAspectFit
|
||||
smooth: true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -27,54 +27,84 @@ struct SuggestedColors
|
|||
QColor colorB;
|
||||
QColor accent;
|
||||
QColor glowColor;
|
||||
QColor brandStrong;
|
||||
QColor brandSoft;
|
||||
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};
|
||||
// Identity accent: the theme's [AppColors] AccentStrong, which appColor()
|
||||
// resolves to QPalette::Highlight when a theme doesn't pin AccentStrong.
|
||||
// Reading bare Highlight ignored curated accent tokens (Plasma's violet
|
||||
// vs Default's green) whenever a palette didn't set the role itself.
|
||||
const QColor accentStrong = themeManager->appColor(AppColor::AccentStrong);
|
||||
if (!window.isValid() || !accentStrong.isValid()) {
|
||||
return {QColor(kFallbackColorA),
|
||||
QColor(kFallbackColorB),
|
||||
kCockatriceBrandGreen,
|
||||
QColor(Qt::white),
|
||||
kCockatriceBrandGreen,
|
||||
QColor(0xC9, 0xFD, 0x62),
|
||||
0.62};
|
||||
}
|
||||
|
||||
// The theme's brand pair: AccentStrong is the deep green, AccentSoft the
|
||||
// lime. These two appColors form the logo's "surrounding gradient" (deep
|
||||
// core grading out to the soft, brand-toned glow) on both the banner and
|
||||
// the home screen.
|
||||
const QColor brandStrong = accentStrong;
|
||||
const QColor brandSoft = themeManager->appColor(AppColor::AccentSoft);
|
||||
|
||||
// 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.
|
||||
// the accent hue instead of a plain near-white copy: a coloured 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();
|
||||
const int hue = accentStrong.hslHue();
|
||||
// Achromatic accents (grey) get a neutral near-white stage instead.
|
||||
const int stageSat = hue < 0 ? 0 : 35;
|
||||
const int stageSat = hue < 0 ? 0 : 64;
|
||||
const int hueSafe = hue < 0 ? 0 : hue;
|
||||
// Depth is what stops a light stage reading as a washed-out near-white
|
||||
// copy of the page behind the banner: deepen the lower pastel band and
|
||||
// raise saturation so the hue is clearly present while staying frosted.
|
||||
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};
|
||||
auto pastelLower = [hueSafe](int lightness) { return QColor::fromHsl(hueSafe, 76, lightness); };
|
||||
// Brightness-lifted accent for additive glows: the raw accent on a
|
||||
// light stage must be mid-bright to read instead of washing out, so
|
||||
// lift lightness and saturation together.
|
||||
const int accentLightness = qBound(158, accentStrong.lightness() + 82, 198);
|
||||
const int accentSaturation = hue < 0 ? 0 : qMax(accentStrong.hslSaturation(), 180);
|
||||
const QColor liftedAccent =
|
||||
hue < 0 ? accentStrong : QColor::fromHsl(hueSafe, accentSaturation, accentLightness);
|
||||
// The centre glow (and logo tint in QML) uses the deep accent itself:
|
||||
// a coloured halo/fill behind the logo instead of a white or black one.
|
||||
return {pastel(214), pastelLower(186), liftedAccent, accentStrong, brandStrong, brandSoft, 0.80};
|
||||
}
|
||||
|
||||
// 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.
|
||||
// muddy. The accent is the bright, brand-driven tone (hue from the accent
|
||||
// itself, never the -- often grey -- window), and it drives both the
|
||||
// embers/fog and the logo glow so the mark tints like the light stage.
|
||||
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};
|
||||
const int accentHue = accentStrong.hslHue();
|
||||
const int accentHueSafe = accentHue < 0 ? 0 : accentHue;
|
||||
const int accentLightness = qBound(150, accentStrong.lightness() + 70, 185);
|
||||
const int accentSaturation = accentHue < 0 ? 0 : qMax(accentStrong.hslSaturation(), 160);
|
||||
const QColor accent =
|
||||
accentHue < 0 ? accentStrong : QColor::fromHsl(accentHueSafe, accentSaturation, accentLightness);
|
||||
return {stage(29), stage(16), accent, accent, brandStrong, brandSoft, 0.62};
|
||||
}
|
||||
} // namespace
|
||||
|
||||
|
|
@ -273,8 +303,9 @@ void BannerHost::applyThemeColors()
|
|||
config->setColorB(bannerColorB);
|
||||
config->setAccent(bannerAccent);
|
||||
config->setGlowColor(colors.glowColor);
|
||||
config->setBrandStrong(colors.brandStrong);
|
||||
config->setBrandSoft(colors.brandSoft);
|
||||
config->setVignetteMin(colors.vignetteMin);
|
||||
config->setLogoDark(colors.lightStage);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -121,7 +121,7 @@ vec3 backgroundField(vec2 uv, float time)
|
|||
|
||||
// Accent-coloured fog layer: flowNoise peaks above 0.6 contribute accent
|
||||
float fog = flowNoise(uv * 0.8 + vec2(100.0, 50.0), time * 0.02);
|
||||
col += uAccent.rgb * max(fog - 0.6, 0.0) * 0.10;
|
||||
col += uAccent.rgb * max(fog - 0.6, 0.0) * 0.14;
|
||||
|
||||
return col;
|
||||
}
|
||||
|
|
@ -138,10 +138,10 @@ vec3 motifWelcome(vec2 uv, vec3 bg, float t)
|
|||
vec2 center = vec2(asp * 0.5, 0.5);
|
||||
float cDist = length(ac - center);
|
||||
|
||||
// Centre bloom at logo position; intensity scales with uLogoGlow. It
|
||||
// uses the scheme-driven uGlowColor rather than plain white so light
|
||||
// stages don't blow out (white halo on a near-white field) -- BannerHost
|
||||
// feeds white on dark stages and the deep accent tone on light ones.
|
||||
// Centre bloom at logo position; intensity scales with uLogoGlow. The
|
||||
// QML brandGlow halo now supplies the primary logo surround (the two
|
||||
// brand appColors), so this shader bloom is deliberately kept as a subtle
|
||||
// ambience rather than a competing glow.
|
||||
float centreLight = bloom(cDist, 0.08 * asp, 0.40 * asp);
|
||||
col += uGlowColor.rgb * centreLight * 0.20 * uLogoGlow;
|
||||
|
||||
|
|
@ -167,8 +167,8 @@ vec3 motifWelcome(vec2 uv, vec3 bg, float t)
|
|||
float pX = baseX * asp + sin(t * driftFreq + fi * 1.7) * driftAmp * asp;
|
||||
float pY = fract(baseY + t * riseSpeed);
|
||||
|
||||
float size = 0.006 + hash21(vec2(fi * 2.9, uSeed * 4.7)) * 0.012;
|
||||
float bright = 0.15 + hash21(vec2(fi * 6.1, uSeed * 0.9)) * 0.30;
|
||||
float size = 0.010 + hash21(vec2(fi * 2.9, uSeed * 4.7)) * 0.014;
|
||||
float bright = 0.18 + hash21(vec2(fi * 6.1, uSeed * 0.9)) * 0.32;
|
||||
|
||||
// Fade out near top/bottom edges
|
||||
float edgeFade = smoothstep(0.0, 0.12, pY) * smoothstep(1.0, 0.88, pY);
|
||||
|
|
@ -237,7 +237,7 @@ vec3 motifCardDatabase(vec2 uv, vec3 bg, float t)
|
|||
|
||||
// Semi-transparent dark fill
|
||||
float fill = smoothstep(0.015, -0.005, d);
|
||||
col = mix(col, uColorB.rgb * 0.55, fill * 0.50);
|
||||
col = mix(col, uColorB.rgb * 0.60, fill * 0.62);
|
||||
|
||||
// Accent outline
|
||||
float edge = smoothstep(0.035, 0.0, abs(d));
|
||||
|
|
@ -314,7 +314,7 @@ vec3 motifAccount(vec2 uv, vec3 bg, float t)
|
|||
|
||||
// Node glow via bloom; intensity modulated by pulse
|
||||
float dist = length(ac - pos);
|
||||
col += uAccent.rgb * bloom(dist, 0.018, 0.08) * mix(0.20, 0.45, pulse);
|
||||
col += uAccent.rgb * bloom(dist, 0.018, 0.08) * mix(0.30, 0.55, pulse);
|
||||
}
|
||||
|
||||
// Edges: connect nodes within a radius threshold
|
||||
|
|
@ -328,19 +328,19 @@ vec3 motifAccount(vec2 uv, vec3 bg, float t)
|
|||
vec2 ba = nodePos[j] - nodePos[i];
|
||||
float h = clamp(dot(pa, ba) / dot(ba, ba), 0.0, 1.0);
|
||||
float lineDist = length(pa - ba * h);
|
||||
col += uAccent.rgb * smoothstep(0.010, 0.0, lineDist) * strength * 0.10;
|
||||
col += uAccent.rgb * smoothstep(0.010, 0.0, lineDist) * strength * 0.14;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Central bloom at banner centre
|
||||
float cDist = length(ac - center);
|
||||
col += uAccent.rgb * bloom(cDist, 0.04, 0.25) * 0.12;
|
||||
col += uAccent.rgb * bloom(cDist, 0.04, 0.25) * 0.20;
|
||||
|
||||
// Periodic expanding ring from centre
|
||||
float ripplePhase = t * 0.4;
|
||||
float rippleDist = abs(cDist - fract(ripplePhase) * asp * 0.7);
|
||||
col += uAccent.rgb * smoothstep(0.02, 0.0, rippleDist) * 0.10;
|
||||
col += uAccent.rgb * smoothstep(0.02, 0.0, rippleDist) * 0.14;
|
||||
|
||||
return col;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,42 @@
|
|||
#version 440
|
||||
|
||||
// The logo's gradient plate, drawn by us rather than the baked-in SVG: a
|
||||
// linear blend between the two brand appColors (light AccentSoft at the
|
||||
// top-left grading to dark AccentStrong at the bottom-right, mirroring
|
||||
// cockatrice.svg's linearGradient4265-7-8 userSpaceOnUse axis), clipped to
|
||||
// the bird's full silhouette via the full-color logo's alpha (uSilhouette).
|
||||
// The white highlight path (cockatrice-logo-white) is overlaid in QML on top,
|
||||
// exactly as the SVG stacks its white path over the gradient paths. Fully
|
||||
// static: no glow, no breathing — the plate just sits there like the home
|
||||
// widget's QPainter composite.
|
||||
|
||||
layout(location = 0) in vec2 qt_TexCoord0;
|
||||
layout(location = 0) out vec4 fragColor;
|
||||
|
||||
layout(std140, binding = 0) uniform buf
|
||||
{
|
||||
mat4 qt_Matrix;
|
||||
float qt_Opacity;
|
||||
vec4 uStrong;
|
||||
vec4 uSoft;
|
||||
};
|
||||
|
||||
// The full-color logo's alpha channel acts as the silhouette mask: the
|
||||
// gradient only appears inside the bird, exactly like the SVG's gradient paths.
|
||||
layout(binding = 1) uniform sampler2D uSilhouette;
|
||||
|
||||
void main()
|
||||
{
|
||||
// Recreate cockatrice.svg's own gradient geometry (linearGradient4265-7-8,
|
||||
// userSpaceOnUse): light AccentSoft at the start point S=(-8.097,-97.746),
|
||||
// dark AccentStrong at the end E=(162.455,295.208), on the SVG's 300x300
|
||||
// canvas. Normalized to UV space, V=E-S=(0.5685,1.3098), so
|
||||
// t = dot(uv - S_norm, V)/|V|^2 with S_norm=(-0.0270,-0.3258).
|
||||
float t = clamp(dot(qt_TexCoord0 - vec2(-0.02699, -0.32582), vec2(0.56851, 1.30985)) / 2.03891, 0.0, 1.0);
|
||||
vec3 color = mix(uSoft.rgb, uStrong.rgb, t);
|
||||
|
||||
// Anti-aliased silhouette clip from the full-color logo's alpha.
|
||||
float alpha = texture(uSilhouette, qt_TexCoord0).a;
|
||||
|
||||
fragColor = vec4(color * alpha, alpha) * qt_Opacity;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue