use egui::{
Color32, Context, Id, InnerResponse, Key, Modifiers, RichText, TextEdit, Theme, Widget,
};
use serde::{Deserialize, Serialize};
pub struct SearchWidget {
widget_id: Id,
}
#[derive(Clone, Default, Debug, Serialize, Deserialize, PartialEq)]
pub enum SearchMode {
#[default]
TokenIndex,
NodeName,
}
#[derive(Clone, Default, Debug, Serialize, Deserialize)]
pub struct SearchWidgetState {
mode: SearchMode,
search_text: String,
current_error: Option<String>,
}
#[derive(Default, Debug)]
pub enum SearchWidgetOutput {
#[default]
None,
TokenIndex(usize),
NodeName(String),
CloseRequested,
}
impl SearchWidget {
pub fn new<I: Into<Id>>(widget_id: I) -> Self {
Self {
widget_id: widget_id.into(),
}
}
pub fn load_state(&self, ctx: &Context) -> SearchWidgetState {
ctx.data_mut(|d| d.get_persisted(self.widget_id))
.unwrap_or_default()
}
pub fn store_state(&self, ctx: &Context, state: SearchWidgetState) {
ctx.data_mut(|d| d.insert_persisted(self.widget_id, state));
}
pub fn show(self, ui: &mut egui::Ui) -> InnerResponse<SearchWidgetOutput> {
let mut state = self.load_state(ui.ctx());
ui.horizontal(|ui| {
ui.label("Search for:");
ui.radio_value(&mut state.mode, SearchMode::TokenIndex, "Token Nr.");
ui.radio_value(&mut state.mode, SearchMode::NodeName, "Node Name");
});
ui.separator();
let mut text_edit = TextEdit::singleline(&mut state.search_text);
if state.current_error.is_some() {
if ui.ctx().theme() == Theme::Light {
text_edit = text_edit.background_color(Color32::LIGHT_RED);
} else {
text_edit = text_edit.background_color(Color32::DARK_RED);
}
}
let mut output = SearchWidgetOutput::None;
let text_edit_response = text_edit.show(ui).response;
if text_edit_response.changed() {
if state.search_text.trim().is_empty() {
output = SearchWidgetOutput::None;
state.current_error = None;
} else {
match state.mode {
SearchMode::TokenIndex => {
if let Ok(parsed) = state.search_text.parse::<usize>() {
output = SearchWidgetOutput::TokenIndex(parsed);
state.current_error = None;
} else {
state.current_error =
Some(format!("\"{}\" is not a number", &state.search_text));
}
}
SearchMode::NodeName => {
output = SearchWidgetOutput::NodeName(state.search_text.clone());
state.current_error = None;
}
}
}
} else if text_edit_response.lost_focus()
&& ui
.ctx()
.input_mut(|input| input.consume_key(Modifiers::NONE, Key::Escape))
{
output = SearchWidgetOutput::CloseRequested;
}
if let Some(err) = &state.current_error {
let color = if ui.ctx().theme() == Theme::Light {
Color32::DARK_RED
} else {
Color32::LIGHT_RED
};
ui.label(RichText::new(err).color(color));
}
let response = ui.response();
self.store_state(ui.ctx(), state);
InnerResponse::new(output, response)
}
}
impl Widget for SearchWidget {
fn ui(self, ui: &mut egui::Ui) -> egui::Response {
self.show(ui).response
}
}