feat(ui-dioxus): smooth 8-step opacity pulse via CSS class cycling
This commit is contained in:
parent
2f03cf0ef0
commit
a1e2a66cd6
2 changed files with 32 additions and 19 deletions
|
|
@ -1,13 +1,11 @@
|
||||||
/// PulsingDot — Blitz-compatible animated status indicator.
|
/// PulsingDot — smooth pulse via discrete CSS opacity classes.
|
||||||
///
|
///
|
||||||
/// Instead of animating opacity (Blitz doesn't reliably restyle inline style changes),
|
/// 8 opacity steps (1.0 → 0.4 → 1.0) cycled every 125ms = 1 full pulse per 1s.
|
||||||
/// we toggle between "bright" and "dim" CSS classes using a simple OS thread timer.
|
/// Uses class attribute changes which Blitz reliably restyles.
|
||||||
/// This changes the `class` attribute which Blitz DOES process for restyling.
|
/// 8 re-renders/sec — low CPU, visually smooth.
|
||||||
///
|
|
||||||
/// CPU cost: schedule_update fires every 1s (2 updates per cycle), not 30fps.
|
|
||||||
|
|
||||||
use dioxus::prelude::*;
|
use dioxus::prelude::*;
|
||||||
use std::sync::atomic::{AtomicBool, Ordering};
|
use std::sync::atomic::{AtomicU8, Ordering};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
|
|
@ -23,18 +21,18 @@ pub enum DotState {
|
||||||
#[component]
|
#[component]
|
||||||
pub fn PulsingDot(state: DotState, size: Option<String>) -> Element {
|
pub fn PulsingDot(state: DotState, size: Option<String>) -> Element {
|
||||||
let should_pulse = matches!(state, DotState::Running | DotState::Stalled);
|
let should_pulse = matches!(state, DotState::Running | DotState::Stalled);
|
||||||
let is_dim = use_hook(|| Arc::new(AtomicBool::new(false)));
|
let step = use_hook(|| Arc::new(AtomicU8::new(0)));
|
||||||
|
|
||||||
if should_pulse {
|
if should_pulse {
|
||||||
let dim = is_dim.clone();
|
let s = step.clone();
|
||||||
let updater = use_hook(|| dioxus::dioxus_core::schedule_update());
|
let updater = use_hook(|| dioxus::dioxus_core::schedule_update());
|
||||||
|
|
||||||
use_hook(move || {
|
use_hook(move || {
|
||||||
std::thread::spawn(move || {
|
std::thread::spawn(move || {
|
||||||
loop {
|
loop {
|
||||||
std::thread::sleep(Duration::from_millis(1000));
|
std::thread::sleep(Duration::from_millis(125));
|
||||||
dim.fetch_xor(true, Ordering::Relaxed); // toggle
|
s.store((s.load(Ordering::Relaxed) + 1) % 8, Ordering::Relaxed);
|
||||||
updater(); // trigger re-render
|
updater();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
@ -48,8 +46,18 @@ pub fn PulsingDot(state: DotState, size: Option<String>) -> Element {
|
||||||
DotState::Error => "dot-error",
|
DotState::Error => "dot-error",
|
||||||
};
|
};
|
||||||
|
|
||||||
let dim_class = if should_pulse && is_dim.load(Ordering::Relaxed) {
|
let op_class = if should_pulse {
|
||||||
"dot-dim"
|
let i = step.load(Ordering::Relaxed);
|
||||||
|
match i {
|
||||||
|
0 => "dot-op-0",
|
||||||
|
1 => "dot-op-1",
|
||||||
|
2 => "dot-op-2",
|
||||||
|
3 => "dot-op-3",
|
||||||
|
4 => "dot-op-4",
|
||||||
|
5 => "dot-op-5",
|
||||||
|
6 => "dot-op-6",
|
||||||
|
_ => "dot-op-7",
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
""
|
""
|
||||||
};
|
};
|
||||||
|
|
@ -58,7 +66,7 @@ pub fn PulsingDot(state: DotState, size: Option<String>) -> Element {
|
||||||
|
|
||||||
rsx! {
|
rsx! {
|
||||||
span {
|
span {
|
||||||
class: "pulsing-dot {base_class} {dim_class}",
|
class: "pulsing-dot {base_class} {op_class}",
|
||||||
style: "width: {sz}; height: {sz};",
|
style: "width: {sz}; height: {sz};",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -282,10 +282,15 @@ body {{
|
||||||
background: var(--ctp-red);
|
background: var(--ctp-red);
|
||||||
box-shadow: 0 0 4px var(--ctp-red);
|
box-shadow: 0 0 4px var(--ctp-red);
|
||||||
}}
|
}}
|
||||||
.dot-dim {{
|
/* Discrete opacity steps for smooth pulse (Blitz can't animate inline styles) */
|
||||||
background: var(--ctp-surface1);
|
.dot-op-0 {{ opacity: 1.0; }}
|
||||||
box-shadow: none;
|
.dot-op-1 {{ opacity: 0.85; }}
|
||||||
}}
|
.dot-op-2 {{ opacity: 0.7; }}
|
||||||
|
.dot-op-3 {{ opacity: 0.55; }}
|
||||||
|
.dot-op-4 {{ opacity: 0.4; }}
|
||||||
|
.dot-op-5 {{ opacity: 0.55; }}
|
||||||
|
.dot-op-6 {{ opacity: 0.7; }}
|
||||||
|
.dot-op-7 {{ opacity: 0.85; }}
|
||||||
.status-dot.error {{
|
.status-dot.error {{
|
||||||
background: var(--ctp-red);
|
background: var(--ctp-red);
|
||||||
}}
|
}}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue