use std::fs::File; use std::io::Write; use std::thread; use std::time::Duration; use iced::theme::Theme; use iced::widget::{text, row, scrollable, container, column, button, text_input, progress_bar}; use iced::{window, Length}; use iced::{Application, Element}; use iced::{Command, Settings}; use once_cell::sync::Lazy; use std::sync::Mutex; use crate::index::Index; use crate::searchresult::SearchResult; use crate::splitter; use crate::index::GenState; mod easing; mod circular; static SEARCH_ID: Lazy = Lazy::new(text_input::Id::unique); static GENERATE_PROGRESS : Lazy> = Lazy::new(|| { Mutex::new(0) }); static GENERATE_STATUS : Lazy> = Lazy::new(|| { Mutex::new(GenState::Fetching) }); pub fn run() -> iced::Result { App::run(Settings { window: window::Settings { size: (1000, 800), ..window::Settings::default() }, ..Settings::default() }) } #[derive(Debug)] enum App { StartMenu, Generating(GenProgress), Loading, Search(State), } #[derive(Debug, Default)] struct State { input_value: String, index : Index, results : Vec } struct SearchState { search : String, index : Index, } #[derive(Debug, Clone, Default)] struct GenProgress { state: GenState, progress: u8 } impl SearchState { pub async fn search(self) -> Vec { let search_args = splitter::split_to_words(self.search); self.index.search(search_args) } } #[derive(Clone, Debug)] enum Message { Load, Generate, Loaded(Index), GeneratingUpdate(GenProgress), InputChanged(String), SearchSubmit, SearchFinished(Vec), ExportResults } async fn load_file() -> Index { let file = rfd::FileDialog::new().add_filter("index", &["idxs"]) .set_directory(".") .set_title("Choose Index File") .pick_file(); let file = file.unwrap(); let file = file.to_str(); let file = file.unwrap(); Index::from_file(&file.to_string()) } async fn generate() -> Index { let file = rfd::FileDialog::new().add_filter("index", &["idxs"]) .set_directory(".") .set_title("Choose Index File") .save_file(); let file = file.unwrap(); let file = file.to_str(); let file = file.unwrap(); let input = rfd::FileDialog::new() .set_directory(".") .set_title("Choose Directory to Index") .pick_folder(); let input = input.unwrap(); let input = input.to_str(); let input = input.unwrap(); let index = Index::generate(input, |t, p| { *GENERATE_PROGRESS.lock().unwrap() = p; *GENERATE_STATUS.lock().unwrap() = t; }); index.save(file.to_string()); index } async fn generate_update_timer() -> GenProgress { thread::sleep(Duration::from_millis(100)); let p; let s; { p = *GENERATE_PROGRESS.lock().unwrap(); s = *GENERATE_STATUS.lock().unwrap(); } GenProgress { state : s, progress: p } } impl Application for App { type Message = Message; type Theme = Theme; type Executor = iced::executor::Default; type Flags = (); fn new(_flags: ()) -> (App, Command) { ( App::StartMenu, Command::none() ) } fn title(&self) -> String { "Index Search".to_string() } fn update(&mut self, message: Message) -> Command { match self { App::StartMenu => { match message { Message::Load => { *self = App::Loading; Command::batch(vec![ Command::perform(load_file(), Message::Loaded) ]) } Message::Generate => { *self = App::Generating(Default::default()); Command::batch(vec![ Command::perform(generate(), Message::Loaded), Command::perform(generate_update_timer(), Message::GeneratingUpdate) ]) } _ => { Command::none() } } } App::Loading => { match message { Message::Loaded(index) => { *self = App::Search(State { index, ..Default::default() }); Command::none() } _ => { Command::none() } } } App::Generating(state) => { match message { Message::Loaded(index) => { *self = App::Search(State { index, ..Default::default() }); Command::none() } Message::GeneratingUpdate(s) => { state.progress = s.progress; state.state = s.state; Command::perform(generate_update_timer(), Message::GeneratingUpdate) } _ => { Command::none() } } } App::Search(state) => { match message { Message::InputChanged(value) => { state.input_value = value; Command::none() } Message::SearchSubmit => { Command::perform( SearchState { search: state.input_value.clone(), index : state.index.clone() }.search(), Message::SearchFinished ) } Message::SearchFinished(results) => { state.results = results; Command::none() } Message::ExportResults => { let file = rfd::FileDialog::new().set_title("Export to File").add_filter("Raw Text", &["txt"]).save_file(); if let Some(file) = file { let mut file = File::create(file).unwrap(); for result in state.results.iter() { writeln!(file, "{}", result.path).ok(); } } Command::none() } _ => { Command::none() } } } } } fn view(&self) -> Element { let container = container(match self { App::StartMenu => { column![ button("Open").on_press(Message::Load), button("Generate").on_press(Message::Generate), ].spacing(10).align_items(iced::Alignment::Center) } App::Loading => { column![ text("Loading"), circular::Circular::new() ].spacing(10).align_items(iced::Alignment::Center) } App::Generating(state) => { let t = match state.state { GenState::Fetching => { text("Fetching") } GenState::Parsing => { text("Parsing") } GenState::Merging => { text("Merging") } }; column![ t, progress_bar(0.0..=100.0, state.progress as f32) ].spacing(10).align_items(iced::Alignment::Center) } App::Search(State { input_value, index, results, ..}) => { let res : Element = if results.is_empty() { column![text("There are no results")].spacing(10).into() } else { let lines : Vec> = results.iter() .map(|r| row![text(r.priority).width(Length::Fixed(50.0)), text(r.path.clone())].into()) .collect(); column(lines).spacing(10).into() }; column![ row![ text_input("Search", input_value) .id(SEARCH_ID.clone()) .on_submit(Message::SearchSubmit) .on_input(Message::InputChanged) .padding(10), text(format!("{} files", index.num_files())) ].align_items(iced::Alignment::Center).spacing(10), scrollable(res).width(Length::Fill).height(Length::Fill), row![ text(format!("{} results", results.len())).width(Length::Fill), button("export results").on_press(Message::ExportResults) ].align_items(iced::Alignment::End).spacing(10) ].spacing(10) } }).center_x() .padding(10) .width(Length::Fill) .height(Length::Fill); match self { App::Search(_) => { container.into() } _ => { container.center_y().into() } } } }