Radial glow effects, RGB accent colors, and scroll fix
CI / Identify a merged version PR (push) Canceled after 0s
CI / Rust checks, rendering, and native package (push) Canceled after 0s
Release PR / Maintain Version Packages PR (push) Canceled after 0s
CI / Publish release and Homebrew feed (push) Canceled after 0s

- Replace linear ha_card_fill with radial glow using colored Shadow
  (the SDF degenerates to a circle for square rects with radius=half)
- Add ha_entity_accent_color() to extract rgb_color from HA entities
  for RGB LED strip cards, falling back to warm orange
- Apply radial glow to toggle, range, climate, media, and cover cards
  with clip rects to prevent bleed between cards
- Fix scroll dispatch to include Curtain/Blinds card types
- Media card glow emanates from album art center proportional to volume
- Cover card: curtain glows from center, blinds from top

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Kaloyan DanchevandClaude Opus 4.6 committed 2026-10-24 15:32:16 +03:00
1 parent c258a0e29a
commit 4044e4db50
2 files changed
+94 -32

No files matched your search

+2 -2
View File
@@ -1072,12 +1072,12 @@ fn run() -> Result<(), String> {
mouse_y,
..
} if state.overlay.is_none() => {
// HA card scroll: adjust range/climate/media values
// HA card scroll: adjust range/climate/media/cover values
let ha_scroll = state.page == state::Page::Ha
&& matches!(&state.focus, state::Target::HaCard(room, index)
if matches!(
state.ha.config.rooms[*room].cards[*index].card_type,
state::HaCardType::Range | state::HaCardType::Climate | state::HaCardType::Media
state::HaCardType::Range | state::HaCardType::Climate | state::HaCardType::Media | state::HaCardType::Curtain | state::HaCardType::Blinds
))
&& y != 0;
if ha_scroll {
+92 -30
View File
@@ -179,6 +179,9 @@ impl Scene {
c(0, alpha),
));
}
fn glow(&mut self, rect: Rect, radius: f32, blur: f32, color: Color) {
self.draw.push(Draw::Shadow(rect, radius, blur, color));
}
fn pill(&mut self, rect: Rect, focused: bool, selected: bool) {
self.shadow(rect, rect.h / 2., 18., 4., 0.06);
if focused {
@@ -1803,10 +1806,38 @@ fn ha_state_color(entity: Option<&crate::state::HaEntity>) -> Color {
}
}
fn ha_entity_accent_color(entity: Option<&crate::state::HaEntity>) -> Color {
if let Some(rgb) = entity
.and_then(|e| e.attributes.get("rgb_color"))
.and_then(|v| v.as_array())
.filter(|a| a.len() >= 3)
{
let r = rgb[0].as_f64().unwrap_or(250.) as f32 / 255.;
let g = rgb[1].as_f64().unwrap_or(185.) as f32 / 255.;
let b = rgb[2].as_f64().unwrap_or(92.) as f32 / 255.;
[r, g, b, 1.]
} else {
c(0xfab95c, 1.)
}
}
fn ha_fill_color(card_type: &crate::state::HaCardType, entity: Option<&crate::state::HaEntity>) -> Color {
use crate::state::HaCardType;
match card_type {
HaCardType::Range => c(0xfab95c, 0.30),
HaCardType::Range => {
if let Some(rgb) = entity
.and_then(|e| e.attributes.get("rgb_color"))
.and_then(|v| v.as_array())
.filter(|a| a.len() >= 3)
{
let r = rgb[0].as_f64().unwrap_or(250.) as f32 / 255.;
let g = rgb[1].as_f64().unwrap_or(185.) as f32 / 255.;
let b = rgb[2].as_f64().unwrap_or(92.) as f32 / 255.;
[r, g, b, 0.35]
} else {
c(0xfab95c, 0.30)
}
}
HaCardType::Climate => {
match entity.map(|e| e.state.as_str()) {
Some("heat" | "heat_cool" | "auto") => c(0xf06040, 0.28),
@@ -1873,9 +1904,16 @@ fn ha_toggle_card(
let accent = ha_state_color(entity);
let is_on = entity.is_some_and(|e| e.state == "on" || e.state == "open");
let fill_color = ha_fill_color(&card.card_type, entity);
// Radial glow from icon
if is_on {
ha_card_fill(out, rect, 1., fill_color);
let fill_color = ha_fill_color(&card.card_type, entity);
let icon_cx = rect.x + 40.;
let icon_cy = rect.y + 40.;
let glow_size = 80.;
let glow_rect = Rect::new(icon_cx - glow_size, icon_cy - glow_size, glow_size * 2., glow_size * 2.);
out.draw.push(Draw::Clip(Some(rect)));
out.glow(glow_rect, glow_size, glow_size * 0.8, fill_color);
out.draw.push(Draw::Clip(None));
}
// Icon in tinted circle
@@ -1952,12 +1990,23 @@ fn ha_range_card(
.and_then(|v| v.as_f64())
.unwrap_or(0.);
let pct = (brightness / 255. * 100.).round() as u8;
let accent = if is_on { c(0xfab95c, 1.) } else { c(0xbbbbbb, 0.5) };
let accent = if is_on {
ha_entity_accent_color(entity)
} else {
c(0xbbbbbb, 0.5)
};
// Fill background proportional to brightness
// Radial glow from icon center, clipped to card
if is_on && pct > 0 {
let fill_color = ha_fill_color(&card.card_type, entity);
ha_card_fill(out, rect, pct as f32 / 100., fill_color);
let glow_alpha = (pct as f32 / 100.).clamp(0.1, 0.45);
let glow_color = [accent[0], accent[1], accent[2], glow_alpha];
let icon_cx = rect.x + 40.;
let icon_cy = rect.y + 40.;
let glow_size = 80.;
let glow_rect = Rect::new(icon_cx - glow_size, icon_cy - glow_size, glow_size * 2., glow_size * 2.);
out.draw.push(Draw::Clip(Some(rect)));
out.glow(glow_rect, glow_size, glow_size * 0.8, glow_color);
out.draw.push(Draw::Clip(None));
}
// Icon in tinted circle
@@ -2033,11 +2082,18 @@ fn ha_climate_card(
let hvac_mode = entity.map_or("off", |e| e.state.as_str());
let is_active = hvac_mode != "off" && hvac_mode != "unavailable";
// Fill proportional to target temp (15-35°C range)
// Radial glow from icon
if is_active {
let fill_pct = ((target_temp - 15.) / 20.).clamp(0., 1.) as f32;
let fill_color = ha_fill_color(&card.card_type, entity);
ha_card_fill(out, rect, fill_pct, fill_color);
let glow_alpha = ((target_temp - 15.) / 20.).clamp(0.1, 0.40) as f32;
let glow_color = [fill_color[0], fill_color[1], fill_color[2], glow_alpha];
let icon_cx = rect.x + 40.;
let icon_cy = rect.y + 40.;
let glow_size = 80.;
let glow_rect = Rect::new(icon_cx - glow_size, icon_cy - glow_size, glow_size * 2., glow_size * 2.);
out.draw.push(Draw::Clip(Some(rect)));
out.glow(glow_rect, glow_size, glow_size * 0.8, glow_color);
out.draw.push(Draw::Clip(None));
}
// Icon in tinted circle
@@ -2331,31 +2387,27 @@ fn ha_media_card(
let accent = ha_state_color(entity);
let is_active = entity.is_some_and(|e| e.state == "playing" || e.state == "paused");
// Volume fill background
// Volume-driven radial glow from album art center
let volume = entity
.and_then(|e| e.attributes.get("volume_level"))
.and_then(|v| v.as_f64())
.unwrap_or(0.) as f32;
if is_active && volume > 0. {
let fill_color = ha_fill_color(&card.card_type, entity);
ha_card_fill(out, rect, volume, fill_color);
}
// Portrait layout: album art circle centered at top
let art_size = 120.;
let art_x = rect.x + (rect.w - art_size) / 2.;
let art_y = rect.y + 24.;
// Glow behind album art
if is_active {
let glow_size = art_size * 1.8;
let glow_x = art_x + (art_size - glow_size) / 2.;
let glow_y = art_y + (art_size - glow_size) / 2.;
out.shape(
Rect::new(glow_x, glow_y, glow_size, glow_size),
glow_size / 2.,
[accent[0], accent[1], accent[2], 0.12],
);
// Radial glow from album art center
if is_active && volume > 0. {
let glow_alpha = (volume * 0.3).clamp(0.05, 0.30);
let art_cx = art_x + art_size / 2.;
let art_cy = art_y + art_size / 2.;
let glow_r = art_size;
let glow_rect = Rect::new(art_cx - glow_r, art_cy - glow_r, glow_r * 2., glow_r * 2.);
out.draw.push(Draw::Clip(Some(rect)));
out.glow(glow_rect, glow_r, glow_r * 0.7, [accent[0], accent[1], accent[2], glow_alpha]);
out.draw.push(Draw::Clip(None));
}
out.shape(
@@ -2466,17 +2518,27 @@ fn ha_cover_card(
out.shape(rect, 22., c(0x0c1024, 0.5 * closed_pct));
}
// Daylight glow when open
// Daylight radial glow when open
if is_open {
let open_pct = (position / 100.).clamp(0., 1.);
let fill_color = ha_fill_color(&card.card_type, entity);
let glow_alpha = (open_pct * fill_color[3]).clamp(0.05, 0.40);
let glow_color = [fill_color[0], fill_color[1], fill_color[2], glow_alpha];
out.draw.push(Draw::Clip(Some(rect)));
if is_curtain {
// Curtain: glow from center
ha_card_fill(out, rect, open_pct, fill_color);
// Curtain: glow from center of card
let cx = rect.x + rect.w / 2.;
let cy = rect.y + rect.h / 2.;
let r = rect.w * 0.5;
out.glow(Rect::new(cx - r, cy - r, r * 2., r * 2.), r, r * 0.7, glow_color);
} else {
// Blinds: glow from top
ha_card_fill(out, rect, open_pct, fill_color);
// Blinds: glow from top center
let cx = rect.x + rect.w / 2.;
let cy = rect.y;
let r = rect.w * 0.5;
out.glow(Rect::new(cx - r, cy - r, r * 2., r * 2.), r, r * 0.7, glow_color);
}
out.draw.push(Draw::Clip(None));
}
let accent = if is_open { c(0xdcd2b4, 1.) } else { c(0xbbbbbb, 0.5) };