108 lines
3.9 KiB
Rust
108 lines
3.9 KiB
Rust
|
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<String>, add_contents: impl FnOnce(&mut Ui));
|
||
|
fn selectable_button(&mut self, selected: bool, text: impl Into<WidgetText>) -> Response;
|
||
|
fn selectable_option<T: Eq>(
|
||
|
&mut self,
|
||
|
current_value: &mut T,
|
||
|
selected_value: T,
|
||
|
text: impl Into<WidgetText>,
|
||
|
) -> 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<T: FromStr + PartialOrd>(
|
||
|
&mut self,
|
||
|
text: &mut String,
|
||
|
value: &mut T,
|
||
|
range: Range<T>,
|
||
|
);
|
||
|
}
|
||
|
|
||
|
impl UiExt for Ui {
|
||
|
fn section(&mut self, title: impl Into<String>, 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<WidgetText>) -> 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<T: FromStr + PartialOrd>(
|
||
|
&mut self,
|
||
|
text: &mut String,
|
||
|
value: &mut T,
|
||
|
range: Range<T>,
|
||
|
) {
|
||
|
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;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|