use std::{ops::Range, str::FromStr}; use egui::{ ecolor::HexColor, Align, Color32, Frame, Layout, Response, RichText, Sense, TextEdit, Ui, UiBuilder, Vec2, WidgetText, }; pub trait UiExt { fn section(&mut self, title: impl Into, add_contents: impl FnOnce(&mut Ui)); fn selectable_button(&mut self, selected: bool, text: impl Into) -> Response; fn selectable_option( &mut self, current_value: &mut T, selected_value: T, text: impl Into, ) -> Response { let response = self.selectable_button(*current_value == selected_value, text); if response.clicked() { *current_value = selected_value; } response } fn color_pair_button(&mut self, left: Color32, right: Color32) -> Response; fn color_picker(&mut self, color: &mut Color32, hex: &mut String) -> Response; fn number_picker( &mut self, text: &mut String, value: &mut T, range: Range, ); } impl UiExt for Ui { fn section(&mut self, title: impl Into, add_contents: impl FnOnce(&mut Ui)) { let mut frame = Frame::group(self.style()); frame.outer_margin.top += 10.0; frame.inner_margin.top += 2.0; let res = frame.show(self, add_contents); let text = RichText::new(title).background_color(self.style().visuals.panel_fill); let old_rect = res.response.rect; let mut text_rect = old_rect; text_rect.min.x += 6.0; let new_rect = self .allocate_new_ui(UiBuilder::new().max_rect(text_rect), |ui| ui.label(text)) .response .rect; self.allocate_space((old_rect.max - new_rect.max) - (old_rect.min - new_rect.min)); } fn selectable_button(&mut self, selected: bool, text: impl Into) -> Response { self.style_mut().visuals.widgets.inactive.bg_fill = Color32::TRANSPARENT; self.style_mut().visuals.widgets.hovered.bg_fill = Color32::TRANSPARENT; self.style_mut().visuals.widgets.active.bg_fill = Color32::TRANSPARENT; let mut selected = selected; self.checkbox(&mut selected, text) } fn color_pair_button(&mut self, left: Color32, right: Color32) -> Response { let button_size = Vec2::new(60.0, 20.0); let (rect, response) = self.allocate_at_least(button_size, Sense::click()); let center_x = rect.center().x; let left_rect = rect.with_max_x(center_x); self.painter().rect_filled(left_rect, 0.0, left); let right_rect = rect.with_min_x(center_x); self.painter().rect_filled(right_rect, 0.0, right); let style = self.style().interact(&response); self.painter().rect_stroke(rect, 0.0, style.fg_stroke); response } fn color_picker(&mut self, color: &mut Color32, hex: &mut String) -> Response { self.allocate_ui_with_layout( Vec2::new(100.0, 130.0), Layout::top_down_justified(egui::Align::Center), |ui| { let (rect, _) = ui.allocate_at_least(Vec2::new(100.0, 100.0), Sense::hover()); ui.painter().rect_filled(rect, 0.0, *color); let resp = ui.text_edit_singleline(hex); if resp.changed() { if let Ok(new_color) = HexColor::from_str_without_hash(hex) { *color = new_color.color(); } } resp }, ) .inner } fn number_picker( &mut self, text: &mut String, value: &mut T, range: Range, ) { let res = self.add(TextEdit::singleline(text).horizontal_align(Align::Max)); if res.changed() { if let Some(new_value) = text.parse().ok().filter(|v| range.contains(v)) { *value = new_value; } } } }