gled-ui 2.32.1

gled's egui interface, shared by the desktop app and the browser
Documentation
//! Temperatures as the status bar shows them: the one the engine's machine
//! reports and, when the engine runs on another machine, this one's.

use egui::{Color32, FontSelection, RichText, Ui};
use egui_phosphor_icons::icons;
use emath::Align;
use epaint::text::LayoutJob;
#[cfg(not(target_arch = "wasm32"))]
use std::sync::atomic::{AtomicU32, Ordering::Relaxed};

/// This machine's hottest reading as `f32` bits; NaN while there is none.
#[cfg(not(target_arch = "wasm32"))]
static LOCAL: AtomicU32 = AtomicU32::new(f32::NAN.to_bits());

/// Orange from here on: a Raspberry Pi 5 starts to slow its CPU down.
const WARM: f32 = 80.0;
/// Red from here on: a Raspberry Pi 5 throttles hard.
const HOT: f32 = 85.0;

/// Reads this machine's sensors once a second, for an interface whose engine
/// runs elsewhere. An engine in the same process reports them already.
#[cfg(not(target_arch = "wasm32"))]
pub fn start_local_thread() {
    std::thread::Builder::new()
        .name("gled:ui:temperature".to_owned())
        .spawn(|| {
            #[cfg(feature = "profiling")]
            profiling::register_thread!("temperature");

            loop {
                let hottest = sysinfo::Components::new_with_refreshed_list()
                    .iter()
                    .filter_map(|component| component.temperature())
                    .filter(|temperature| temperature.is_finite())
                    .reduce(f32::max);
                LOCAL.store(hottest.unwrap_or(f32::NAN).to_bits(), Relaxed);
                std::thread::sleep(std::time::Duration::from_secs(1));
            }
        })
        .expect("Could not spawn temperature thread");
}

/// This machine's hottest reading, if `start_local_thread` runs and the
/// machine has sensors. Never in a browser, which has no access to them.
pub(crate) fn local() -> Option<f32> {
    #[cfg(not(target_arch = "wasm32"))]
    {
        let celsius = f32::from_bits(LOCAL.load(Relaxed));
        (!celsius.is_nan()).then_some(celsius)
    }
    #[cfg(target_arch = "wasm32")]
    None
}

/// A thermometer, the machine's `name` if there is one to tell apart, and
/// `celsius`, coloured once it gets hot.
pub(crate) fn temperature(ui: &Ui, name: Option<&str>, celsius: f32) -> LayoutJob {
    let mut job = LayoutJob::default();
    icons::THERMOMETER.regular().append_to(
        &mut job,
        ui.style(),
        FontSelection::Default,
        Align::Center,
    );
    let text = match name {
        Some(name) => format!(" {name} {celsius:.0}°C"),
        None => format!(" {celsius:.0}°C"),
    };
    RichText::new(text).size(14.0).append_to(
        &mut job,
        ui.style(),
        FontSelection::Default,
        Align::Center,
    );

    let color = match celsius {
        c if c >= HOT => Some(Color32::RED),
        c if c >= WARM => Some(Color32::ORANGE),
        _ => None,
    };
    if let Some(color) = color {
        for section in &mut job.sections {
            section.format.color = color;
        }
    }
    job
}