use egui::{Response, Slider};
use crate::core::backend::ItemHandle;
use crate::widget::high_level::PlotWidget;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
pub enum AlphaSliderOrientation {
#[default]
Horizontal,
Vertical,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct AlphaSlider {
value: u8,
pub orientation: AlphaSliderOrientation,
}
impl Default for AlphaSlider {
fn default() -> Self {
Self {
value: 255,
orientation: AlphaSliderOrientation::default(),
}
}
}
impl AlphaSlider {
pub fn new(value: u8) -> Self {
Self {
value,
orientation: AlphaSliderOrientation::default(),
}
}
pub fn from_alpha(alpha: f32) -> Self {
let mut s = Self::default();
s.set_alpha(alpha);
s
}
pub fn with_orientation(mut self, orientation: AlphaSliderOrientation) -> Self {
self.orientation = orientation;
self
}
pub fn value(&self) -> u8 {
self.value
}
pub fn set_value(&mut self, value: u8) {
self.value = value;
}
pub fn alpha(&self) -> f32 {
alpha_from_u8(self.value)
}
pub fn set_alpha(&mut self, alpha: f32) {
self.value = u8_from_alpha(alpha);
}
pub fn ui(&mut self, ui: &mut egui::Ui) -> Response {
match self.orientation {
AlphaSliderOrientation::Horizontal => {
ui.add(Slider::new(&mut self.value, 0..=255).text("alpha"))
}
AlphaSliderOrientation::Vertical => {
ui.add(Slider::new(&mut self.value, 0..=255).vertical())
}
}
}
}
fn alpha_from_u8(value: u8) -> f32 {
value as f32 / 255.0
}
fn u8_from_alpha(alpha: f32) -> u8 {
let clamped = alpha.clamp(0.0, 1.0);
(clamped * 255.0).round() as u8
}
#[derive(Clone, Debug, Default)]
pub struct ActiveImageAlphaSlider {
slider: AlphaSlider,
bound: Option<ItemHandle>,
}
impl ActiveImageAlphaSlider {
pub fn new() -> Self {
Self::default()
}
pub fn with_orientation(mut self, orientation: AlphaSliderOrientation) -> Self {
self.slider.orientation = orientation;
self
}
pub fn alpha(&self) -> f32 {
self.slider.alpha()
}
pub fn value(&self) -> u8 {
self.slider.value()
}
pub fn show(&mut self, ui: &mut egui::Ui, plot: &mut PlotWidget) -> Response {
let item = plot.active_image_handle();
if item != self.bound {
self.bound = item;
if let Some(alpha) = plot.active_image_alpha() {
self.slider.set_alpha(alpha);
}
}
let enabled = item.is_some();
let response = ui.add_enabled_ui(enabled, |ui| self.slider.ui(ui)).inner;
if enabled && response.changed() {
plot.set_active_image_alpha(self.slider.alpha());
}
response
}
}
#[derive(Clone, Debug, Default)]
pub struct NamedItemAlphaSlider {
slider: AlphaSlider,
legend: String,
bound: Option<ItemHandle>,
}
impl NamedItemAlphaSlider {
pub fn new(legend: impl Into<String>) -> Self {
Self {
slider: AlphaSlider::default(),
legend: legend.into(),
bound: None,
}
}
pub fn with_orientation(mut self, orientation: AlphaSliderOrientation) -> Self {
self.slider.orientation = orientation;
self
}
pub fn legend(&self) -> &str {
&self.legend
}
pub fn set_legend(&mut self, legend: impl Into<String>) {
self.legend = legend.into();
}
pub fn alpha(&self) -> f32 {
self.slider.alpha()
}
pub fn value(&self) -> u8 {
self.slider.value()
}
pub fn show(&mut self, ui: &mut egui::Ui, plot: &mut PlotWidget) -> Response {
let item = plot.image_by_legend(&self.legend);
if item != self.bound {
self.bound = item;
if let Some(handle) = item
&& let Some(alpha) = plot.image_alpha(handle)
{
self.slider.set_alpha(alpha);
}
}
let enabled = item.is_some();
let response = ui.add_enabled_ui(enabled, |ui| self.slider.ui(ui)).inner;
if enabled
&& response.changed()
&& let Some(handle) = item
{
plot.set_image_alpha(handle, self.slider.alpha());
}
response
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn alpha_from_u8_at_boundaries() {
assert_eq!(alpha_from_u8(0), 0.0);
assert_eq!(alpha_from_u8(255), 1.0);
assert!((alpha_from_u8(128) - 128.0 / 255.0).abs() < 1e-7);
}
#[test]
fn u8_from_alpha_at_boundaries() {
assert_eq!(u8_from_alpha(0.0), 0);
assert_eq!(u8_from_alpha(1.0), 255);
assert_eq!(u8_from_alpha(0.5), 128);
}
#[test]
fn u8_from_alpha_clamps_out_of_range() {
assert_eq!(u8_from_alpha(-0.5), 0);
assert_eq!(u8_from_alpha(2.0), 255);
assert_eq!(u8_from_alpha(f32::NAN), 0);
}
#[test]
fn u8_from_alpha_rounds_to_nearest() {
assert_eq!(u8_from_alpha(0.498), 127);
assert_eq!(u8_from_alpha(0.502), 128);
}
#[test]
fn default_is_fully_opaque() {
let s = AlphaSlider::default();
assert_eq!(s.value(), 255);
assert_eq!(s.alpha(), 1.0);
assert_eq!(s.orientation, AlphaSliderOrientation::Horizontal);
}
#[test]
fn new_sets_integer_value() {
let s = AlphaSlider::new(0);
assert_eq!(s.value(), 0);
assert_eq!(s.alpha(), 0.0);
}
#[test]
fn from_alpha_rounds_to_integer() {
let s = AlphaSlider::from_alpha(0.5);
assert_eq!(s.value(), 128);
assert_eq!(AlphaSlider::from_alpha(2.0).value(), 255);
assert_eq!(AlphaSlider::from_alpha(-1.0).value(), 0);
}
#[test]
fn set_value_and_set_alpha_round_trip_at_boundaries() {
let mut s = AlphaSlider::new(0);
s.set_value(255);
assert_eq!(s.value(), 255);
assert_eq!(s.alpha(), 1.0);
s.set_value(128);
assert_eq!(s.value(), 128);
assert!((s.alpha() - 128.0 / 255.0).abs() < 1e-7);
s.set_alpha(0.0);
assert_eq!(s.value(), 0);
s.set_alpha(1.0);
assert_eq!(s.value(), 255);
}
#[test]
fn with_orientation_sets_vertical() {
let s = AlphaSlider::default().with_orientation(AlphaSliderOrientation::Vertical);
assert_eq!(s.orientation, AlphaSliderOrientation::Vertical);
}
}