use crate::get_global_color;
use egui::{self, Color32, FontId, Pos2, Rect, Response, Sense, Stroke, StrokeKind, Ui, Vec2, Widget};
pub struct MaterialSwitch<'a> {
selected: &'a mut bool,
text: Option<String>,
enabled: bool,
selected_icon: Option<char>,
unselected_icon: Option<char>,
show_track_outline: bool,
}
impl<'a> MaterialSwitch<'a> {
pub fn new(selected: &'a mut bool) -> Self {
Self {
selected,
text: None,
enabled: true,
selected_icon: None,
unselected_icon: None,
show_track_outline: true, }
}
pub fn text(mut self, text: impl Into<String>) -> Self {
self.text = Some(text.into());
self
}
pub fn enabled(mut self, enabled: bool) -> Self {
self.enabled = enabled;
self
}
pub fn selected_icon(mut self, icon: char) -> Self {
self.selected_icon = Some(icon);
self
}
pub fn unselected_icon(mut self, icon: char) -> Self {
self.unselected_icon = Some(icon);
self
}
pub fn with_icons(mut self, selected: char, unselected: char) -> Self {
self.selected_icon = Some(selected);
self.unselected_icon = Some(unselected);
self
}
pub fn show_track_outline(mut self, show: bool) -> Self {
self.show_track_outline = show;
self
}
}
impl<'a> Widget for MaterialSwitch<'a> {
fn ui(self, ui: &mut Ui) -> Response {
let switch_width = 52.0;
let switch_height = 32.0;
let track_height = 32.0;
let desired_size = if let Some(ref text) = self.text {
let text_width = ui.painter().layout_no_wrap(
text.clone(),
egui::FontId::default(),
egui::Color32::WHITE,
).size().x;
Vec2::new(switch_width + 8.0 + text_width, switch_height)
} else {
Vec2::new(switch_width, switch_height)
};
let (rect, mut response) = ui.allocate_exact_size(desired_size, Sense::click());
if response.clicked() && self.enabled {
*self.selected = !*self.selected;
response.mark_changed();
}
let is_pressed = response.is_pointer_button_down_on();
let is_hovered = response.hovered();
let is_focused = response.has_focus();
let primary = get_global_color("primary"); let on_primary = get_global_color("onPrimary"); let primary_container = get_global_color("primaryContainer"); let on_primary_container = get_global_color("onPrimaryContainer"); let surface_container_highest = get_global_color("surfaceContainerHighest"); let on_surface = get_global_color("onSurface"); let on_surface_variant = get_global_color("onSurfaceVariant"); let outline = get_global_color("outline");
let switch_rect = Rect::from_min_size(
Pos2::new(rect.min.x, rect.center().y - switch_height / 2.0),
Vec2::new(switch_width, switch_height),
);
let track_rect =
Rect::from_center_size(switch_rect.center(), Vec2::new(switch_width, track_height));
let has_icon = if *self.selected {
self.selected_icon.is_some()
} else {
self.unselected_icon.is_some()
};
let base_thumb_size_on = 24.0;
let base_thumb_size_off = if has_icon { 24.0 } else { 16.0 };
let pressed_thumb_size = 28.0;
let thumb_size = if is_pressed {
pressed_thumb_size
} else if *self.selected {
base_thumb_size_on
} else {
base_thumb_size_off
};
let thumb_travel = switch_width - base_thumb_size_on - 4.0;
let thumb_x = if *self.selected {
switch_rect.min.x + 2.0 + thumb_travel
} else {
switch_rect.min.x + 2.0
};
let thumb_center = Pos2::new(thumb_x + thumb_size / 2.0, switch_rect.center().y);
let (track_color, thumb_color, track_outline_color, icon_color) = if !self.enabled {
let disabled_track = if *self.selected {
on_surface.linear_multiply(0.12)
} else {
surface_container_highest.linear_multiply(0.12)
};
let disabled_thumb = if *self.selected {
on_surface
} else {
on_surface.linear_multiply(0.38)
};
let disabled_outline = on_surface.linear_multiply(0.12); let disabled_icon = if *self.selected {
on_surface.linear_multiply(0.38)
} else {
surface_container_highest.linear_multiply(0.38)
};
(disabled_track, disabled_thumb, disabled_outline, disabled_icon)
} else if *self.selected {
let track = primary; let thumb = if is_pressed || is_hovered || is_focused {
primary_container } else {
on_primary };
let track_outline = Color32::TRANSPARENT; let icon = on_primary_container; (track, thumb, track_outline, icon)
} else {
let track = surface_container_highest; let thumb = if is_pressed || is_hovered || is_focused {
on_surface_variant } else {
outline };
let track_outline = outline; let icon = surface_container_highest; (track, thumb, track_outline, icon)
};
ui.painter()
.rect_filled(track_rect, track_height / 2.0, track_color);
if self.show_track_outline && track_outline_color != Color32::TRANSPARENT {
ui.painter().rect_stroke(
track_rect,
track_height / 2.0,
Stroke::new(2.0, track_outline_color),
StrokeKind::Outside,
);
}
if self.enabled {
let overlay_radius = 20.0; let overlay_color = if *self.selected {
if is_pressed || is_focused {
primary.linear_multiply(0.10)
} else if is_hovered {
primary.linear_multiply(0.08)
} else {
Color32::TRANSPARENT
}
} else {
if is_pressed || is_focused {
on_surface.linear_multiply(0.10)
} else if is_hovered {
on_surface.linear_multiply(0.08)
} else {
Color32::TRANSPARENT
}
};
if overlay_color != Color32::TRANSPARENT {
ui.painter()
.circle_filled(thumb_center, overlay_radius, overlay_color);
}
}
ui.painter()
.circle_filled(thumb_center, thumb_size / 2.0, thumb_color);
let current_icon = if *self.selected {
self.selected_icon
} else {
self.unselected_icon
};
if let Some(icon) = current_icon {
let icon_size = 16.0;
let icon_font = FontId::proportional(icon_size);
ui.painter().text(
thumb_center,
egui::Align2::CENTER_CENTER,
icon.to_string(),
icon_font,
icon_color,
);
}
if let Some(ref text) = self.text {
let text_pos = Pos2::new(switch_rect.max.x + 8.0, rect.center().y);
let text_color = if self.enabled {
on_surface
} else {
on_surface.linear_multiply(0.38)
};
ui.painter().text(
text_pos,
egui::Align2::LEFT_CENTER,
text,
egui::FontId::default(),
text_color,
);
}
response
}
}
pub fn switch(selected: &mut bool) -> MaterialSwitch<'_> {
MaterialSwitch::new(selected)
}