Radial glow effects, RGB accent colors, and scroll fix
- 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:
1 parent
c258a0e29a
commit
4044e4db50
2 files changed
+94
-32
No files matched your search
+2
-2
@@ -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
@@ -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) };
|
||||
|
||||
Reference in new issue
Block a user