CalendarStyle.kt
CalendarStyle takes a base background color (hex string), computes a matching text color, and derives a semi-transparent background variation for calendar event rendering. The text color is calculated server-side using HSB color-space manipulation to ensure adequate contrast. Results are cached in a ColorCache (extending AbstractCache with 1-hour expiry) to avoid recomputing the same conversions repeatedly. Two color schemes are supported: the default (modern) mode with HSB-derived colors and a CLASSIC mode that preserves the original v6 behavior of black-on-white or white-on-dark.
| Parameter | Type | Default | Description |
|---|---|---|---|
baseBackgroundColor |
String? |
null |
A hex color string (e.g., "#06790e"). Defaults to "#777" if null. |
bgColor: StringThe base background color. Initialized from the constructor parameter, falling back to "#777" (a neutral gray). This is stored as the canonical hex value and should not be used directly for rendering — use getBackgroundColor() instead.
getTextColor(colorScheme: CalendarEventColorScheme?): StringReturns the computed text color for the given color scheme. Delegates to colorCache.getTextColor(bgColor, colorScheme).
getBackgroundColor(calendarEventColorScheme: CalendarEventColorScheme?): StringReturns the background color adjusted for the color scheme:
bgColor unmodified (opaque)."3" for short hex ("#7773" → ~20% opacity) or "33" for full hex ("#77777733" → ~20% opacity). This creates translucent calendar events that blend with the background.RGB(r: Int, g: Int, b: Int)Simple data class holding RGB channel values (0–255).
ColorCache : AbstractCache()Cache with 1-hour TTL that stores computed text colors keyed by hex string, avoiding redundant HSB conversions. Maintains separate maps for the standard and classic color schemes. Thread-safe via synchronized blocks on the per-scheme map.
| Function | Signature | Purpose |
|---|---|---|
validateHexCode |
(color: String): Boolean |
Validates whether a string matches a 3-digit (#[a-f0-9]{3}) or 6-digit (#[a-f0-9]{6}) hex color pattern. |
hexToRGB |
(color: String?): RGB |
Converts a hex string to an RGB object. Expands shorthand hex ("#abc" → "#aabbcc"). Returns RGB(0,0,0) on failure. |
hexToColor |
(color: String?): Color |
Converts a hex string to a java.awt.Color. Handles shorthand expansion. |
getTextColor |
(backgroundColor: String?, colorScheme: CalendarEventColorScheme?): String |
Public static entry point delegating to the cache. |
calculateTextColor |
(backgroundColor: String?, colorScheme: CalendarEventColorScheme?): String |
Core algorithm. For CLASSIC: returns "#fff" if dark, "#444" otherwise. For modern: shifts the HSB brightness down by 0.6 (clamped to 0.3 minimum) to produce a darker variant of the background color. |
brightness(rgb: RGB): Int |
— | Weighted brightness formula: (R*299 + G*587 + B*114) / 1000 (perceptual luminance). |
brightness(color: String?): Int |
— | Overload that calls hexToRGB first, then computes brightness. |
dark(color: String?): Boolean |
— | Returns true if brightness is below 180 (i.e., the color is dark). |
For the modern scheme, the text color is computed by:
java.awt.Color.Color.RGBtoHSB().Color.HSBtoRGB() and formatting as "#rrggbb".The CLASSIC scheme is simpler: dark backgrounds get white text ("#fff"), light backgrounds get dark gray text ("#444"), with the threshold at brightness 180.
Server-side color computation is a deliberate architectural decision. By calculating text and background colors on the server (rather than the React frontend), the same color values are available for both FullCalendar event rendering and form select dropdowns, avoiding inconsistencies. The HSB-based algorithm produces colors that are perceptually related to the background (same hue, adjusted brightness) rather than simply inverting — this creates a more cohesive visual design. The ColorCache avoids recalculating the same color combinations repeatedly (each calendar's style is used for many events), and the 1-hour expiry via AbstractCache allows theme changes to propagate without restart. The CLASSIC mode was added retroactively (commit 5a2c98523) as a user option for those preferring the simpler high-contrast approach from earlier versions.