use egui::{Response, Visuals, style::WidgetVisuals};
use crate::{AsTuiBuilder, TaffyContainerUi, Tui, TuiBuilder, TuiBuilderLogic, TuiInnerResponse};
use crate::{TuiBuilderParamsAccess, setup_tui_visuals};
pub struct TuiBackground<'a> {
background_color: TuiBackgroundValue<'a, egui::Color32>,
corner_radius: TuiBackgroundValue<'a, egui::CornerRadius>,
border: Option<TuiBackgroundBorder<'a>>,
}
impl Default for TuiBackground<'_> {
fn default() -> Self {
Self {
background_color: TuiBackgroundValue::Visuals(&|visuals, _| visuals.panel_fill),
corner_radius: TuiBackgroundValue::Visuals(&|_, widget_visuals| {
widget_visuals.corner_radius
}),
border: None,
}
}
}
impl<'a> TuiBackground<'a> {
pub fn new() -> Self {
Default::default()
}
pub fn with_background_color(mut self, background_color: egui::Color32) -> Self {
self.background_color = TuiBackgroundValue::Custom(background_color);
self
}
pub fn with_background_color_by_visuals(
mut self,
f: VisualsGetterFn<'a, egui::Color32>,
) -> Self {
self.background_color = TuiBackgroundValue::Visuals(f);
self
}
pub fn with_background_color_by_response(
mut self,
f: VisualsResponseGetterFn<'a, egui::Color32>,
) -> Self {
self.background_color = TuiBackgroundValue::VisualsResponse(f);
self
}
pub fn with_border(mut self) -> Self {
self.border.get_or_insert_default();
self
}
pub fn with_border_color(mut self, color: egui::Color32) -> Self {
self.border.get_or_insert_default().color = TuiBackgroundValue::Custom(color);
self
}
pub fn with_border_color_by_visuals(mut self, f: VisualsGetterFn<'a, egui::Color32>) -> Self {
self.border.get_or_insert_default().color = TuiBackgroundValue::Visuals(f);
self
}
pub fn with_border_color_by_response(
mut self,
f: VisualsResponseGetterFn<'a, egui::Color32>,
) -> Self {
self.border.get_or_insert_default().color = TuiBackgroundValue::VisualsResponse(f);
self
}
pub fn with_border_width(mut self, width: f32) -> Self {
self.border.get_or_insert_default().width = TuiBackgroundValue::Custom(width);
self
}
pub fn with_border_width_by_visuals(mut self, f: VisualsGetterFn<'a, f32>) -> Self {
self.border.get_or_insert_default().width = TuiBackgroundValue::Visuals(f);
self
}
pub fn with_border_width_by_response(mut self, f: VisualsResponseGetterFn<'a, f32>) -> Self {
self.border.get_or_insert_default().width = TuiBackgroundValue::VisualsResponse(f);
self
}
pub fn with_corner_radius(mut self, radius: impl Into<egui::CornerRadius>) -> Self {
self.corner_radius = TuiBackgroundValue::Custom(radius.into());
self
}
pub fn with_corner_radius_by_visuals(
mut self,
f: VisualsGetterFn<'a, egui::CornerRadius>,
) -> Self {
self.corner_radius = TuiBackgroundValue::Visuals(f);
self
}
pub fn with_corner_radius_by_response(
mut self,
f: VisualsResponseGetterFn<'a, egui::CornerRadius>,
) -> Self {
self.corner_radius = TuiBackgroundValue::VisualsResponse(f);
self
}
fn has_border(&self) -> bool {
self.border.is_some()
}
fn draw_internal(
&self,
ui: &egui::Ui,
container: &TaffyContainerUi,
widget_visuals: &egui::style::WidgetVisuals,
response: Option<&Response>,
) {
let rect = container.full_container();
let visuals = ui.style().visuals.clone();
fn match_value<T: Clone>(
visuals: &egui::style::Visuals,
widget_visuals: &egui::style::WidgetVisuals,
response: Option<&Response>,
value: &TuiBackgroundValue<T>,
) -> T {
match value {
TuiBackgroundValue::Custom(value) => value.clone(),
TuiBackgroundValue::Visuals(f) => f(visuals, widget_visuals),
TuiBackgroundValue::VisualsResponse(f) => match response {
Some(r) => f(visuals, widget_visuals, r),
None => unreachable!("never called without a response"),
},
}
}
let fill = match_value(&visuals, widget_visuals, response, &self.background_color);
let stroke = self.border.as_ref().map(|border| {
let color = match_value(&visuals, widget_visuals, response, &border.color);
let width = match_value(&visuals, widget_visuals, response, &border.width);
egui::Stroke { color, width }
});
let corner_radius = match_value(&visuals, widget_visuals, response, &self.corner_radius);
match stroke {
Some(stroke) => {
ui.painter()
.rect(rect, corner_radius, fill, stroke, egui::StrokeKind::Inside);
}
None => {
ui.painter().rect_filled(rect, corner_radius, fill);
}
}
}
pub fn draw(&self) -> impl FnOnce(&mut egui::Ui, &TaffyContainerUi) {
move |ui: &mut egui::Ui, container: &TaffyContainerUi| {
let widget_visuals = ui.style().visuals.noninteractive();
self.draw_internal(ui, container, widget_visuals, None);
}
}
pub fn draw_with_response(&self) -> impl FnOnce(&mut egui::Ui, &TaffyContainerUi) -> Response {
move |ui: &mut egui::Ui, container: &TaffyContainerUi| {
let rect = container.full_container();
let response = ui.interact(rect, ui.id().with("bg"), egui::Sense::click());
let widget_visuals = ui.style().interact(&response);
self.draw_internal(ui, container, widget_visuals, Some(&response));
response
}
}
}
struct TuiBackgroundBorder<'a> {
color: TuiBackgroundValue<'a, egui::Color32>,
width: TuiBackgroundValue<'a, f32>,
}
impl Default for TuiBackgroundBorder<'_> {
fn default() -> Self {
Self {
color: TuiBackgroundValue::Visuals(&|_, widget_visuals| widget_visuals.bg_stroke.color),
width: TuiBackgroundValue::Visuals(&|_, widget_visuals| widget_visuals.bg_stroke.width),
}
}
}
impl<'r, T> TuiBuilderLogicWithBackground<'r> for T
where
T: AsTuiBuilder<'r>,
{
}
enum TuiBackgroundValue<'a, T> {
Custom(T),
Visuals(VisualsGetterFn<'a, T>),
VisualsResponse(VisualsResponseGetterFn<'a, T>),
}
type VisualsGetterFn<'a, T> = &'a dyn Fn(&Visuals, &WidgetVisuals) -> T;
type VisualsResponseGetterFn<'a, T> = &'a dyn Fn(&Visuals, &WidgetVisuals, &Response) -> T;
pub trait TuiBuilderLogicWithBackground<'r>: TuiBuilderLogic<'r> {
fn bg_add<T>(self, bg: TuiBackground, f: impl FnOnce(&mut Tui) -> T) -> T {
let tui = self.tui().unpack();
tui.builder_tui
.add_child(tui.params, bg.draw(), |tui, _| f(tui))
.main
}
fn bg_clickable<T>(
self,
bg: TuiBackground,
f: impl FnOnce(&mut Tui) -> T,
) -> TuiInnerResponse<T> {
let tui = if bg.has_border() {
self.with_border_style_from_egui_style()
} else {
self.tui()
};
let TuiBuilder {
builder_tui,
params,
} = tui.unpack();
let return_values =
builder_tui.add_child(params, bg.draw_with_response(), |tui, bg_response| {
setup_tui_visuals(tui, bg_response);
f(tui)
});
TuiInnerResponse {
inner: return_values.main,
response: return_values.background,
}
}
}