From d4c9364d4dedf838fcf3231241af5ea7a6b67a9e Mon Sep 17 00:00:00 2001 From: Yousun Date: Sat, 1 Nov 2025 18:33:43 +0100 Subject: [PATCH] feat(gradient): Implement 2/3-point color gradients on progress bars --- src/countdownToMarkdownRenderChild.ts | 75 ++++++++++++++++++++++++++- src/settings.ts | 8 +++ 2 files changed, 81 insertions(+), 2 deletions(-) diff --git a/src/countdownToMarkdownRenderChild.ts b/src/countdownToMarkdownRenderChild.ts index 89156dc..fd74b16 100644 --- a/src/countdownToMarkdownRenderChild.ts +++ b/src/countdownToMarkdownRenderChild.ts @@ -78,7 +78,18 @@ export class CountdownToMarkdownRenderChild extends MarkdownRenderChild { const infoEl = containerEl.createDiv({ cls: 'countdown-to-info' }); let bar; - const barColor = params.color || this.plugin.settings.defaultBarColor; + const initialbarColor = params.color || this.plugin.settings.defaultBarColor; + const isGradient = (params.colorInGradient || this.plugin.settings.defaultColorInGradient.toString()) === 'true'; + const progressType = params.progressType || this.plugin.settings.defaultProgressType; + let barColor: string; + if (isGradient) { + const startColorParam = params.startColor || this.plugin.settings.defaultStartColor; + const endColorParam = params.endColor || this.plugin.settings.defaultEndColor; + const isCountdown = progressType.toLowerCase() === 'countdown'; + barColor = isCountdown ? endColorParam : startColorParam; + } else { + barColor = initialbarColor; + } const trailColor = params.trailColor || this.plugin.settings.defaultTrailColor; const commonOptions = { strokeWidth: 4, @@ -210,6 +221,8 @@ export class CountdownToMarkdownRenderChild extends MarkdownRenderChild { } else { countdownTo.bar.set(Math.floor(progress * 100) / 100); } + + this.updateBarGradient(countdownTo, progress, params, progressType); if (progress >= 1) { countdownTo.infoEl.setText( @@ -265,6 +278,60 @@ export class CountdownToMarkdownRenderChild extends MarkdownRenderChild { } } + private updateBarGradient( + countdownTo: any, + progress: number, + params: Record, + progressType: string + ): void { + const isGradient = + (params.colorInGradient || this.plugin.settings.defaultColorInGradient?.toString()) === "true"; + if (!isGradient || !countdownTo?.bar) return; + const isCountdown = progressType.toLowerCase() === "countdown"; + + const startColor = params.startColor || this.plugin.settings.defaultStartColor; + const endColor = params.endColor || this.plugin.settings.defaultEndColor; + const midColorParam = params.midColor || this.plugin.settings.defaultMidColor; + const midColor = this.parseHexColor(midColorParam); + + let colorProgress: number; + if (isCountdown) { + colorProgress = 1.0 - progress; + } else { + colorProgress = Math.floor(progress * 100) / 100; + } + + let newColor: string; + if (midColor) { + if (colorProgress < 0.5) { + newColor = this.interpolateColor(startColor, midColorParam, colorProgress * 2); + } else { + newColor = this.interpolateColor(midColorParam, endColor, (colorProgress - 0.5) * 2); + } + } else { + newColor = this.interpolateColor(startColor, endColor, colorProgress); + } + + const barPath = (countdownTo.bar as any)?.path as SVGPathElement; + if (barPath) barPath.setAttribute("stroke", newColor); + } + + private interpolateColor(color1: string, color2: string, factor: number): string { + const c1 = this.parseHexColor(color1); + const c2 = this.parseHexColor(color2); + if (!c1 || !c2) return color1; + const result = c1.map((v, i) => Math.round(v + factor * (c2[i] - v))); + return `#${result.map((x) => x.toString(16).padStart(2, "0")).join("")}`; + } + + private parseHexColor(hex: string): number[] | null { + if (!hex) return null; + const match = hex.trim().replace("#", "").match(/^([0-9a-f]{6})$/i); + if (!match) return null; + const intVal = parseInt(match[1], 16); + return [(intVal >> 16) & 255, (intVal >> 8) & 255, intVal & 255]; + } + getPropertiesFromFrontmatter(): Record { const properties: Record = {}; @@ -299,7 +366,11 @@ export class CountdownToMarkdownRenderChild extends MarkdownRenderChild { 'updateIntervalInSeconds', 'infoFormat', 'infoFormatUpcoming', - 'onCompleteText' + 'onCompleteText', + 'colorInGradient', + 'startColor', + 'endColor', + 'midColor' ]; propertyMapping.forEach(prop => { diff --git a/src/settings.ts b/src/settings.ts index 95b147f..31ea96a 100644 --- a/src/settings.ts +++ b/src/settings.ts @@ -13,6 +13,10 @@ export interface CountdownToSettings { defaultInfoFormatUpcoming: string; defaultUpdateInRealTime: boolean; defaultUpdateIntervalSeconds: number; + defaultColorInGradient: boolean; + defaultStartColor: string; + defaultEndColor: string; + defaultMidColor: string; } export const DEFAULT_SETTINGS: CountdownToSettings = { @@ -26,6 +30,10 @@ export const DEFAULT_SETTINGS: CountdownToSettings = { defaultInfoFormatUpcoming: '{title} is coming up in {remaining}!', defaultUpdateInRealTime: false, defaultUpdateIntervalSeconds: 1, + defaultColorInGradient: false, + defaultStartColor: '#ff5722', + defaultEndColor: '#4CAF50', + defaultMidColor: ' ', }; export class CountdownToSettingTab extends PluginSettingTab {