diff --git a/deja_vu/src/action.rs b/deja_vu/src/action.rs index 1695123..0241c20 100644 --- a/deja_vu/src/action.rs +++ b/deja_vu/src/action.rs @@ -36,8 +36,8 @@ pub enum Action { SelectDriver(Driver), SelectTableType(PartitionTableType), UpdateDiskList(Vec), - UpdateLeft(Vec, Vec), - UpdateRight(Vec, Vec), + UpdateLeft(usize, Vec>), // (start_index, lines) - lines before start are always shown + UpdateRight(usize, Vec>), // Same as above // Screens DismissPopup, DisplayPopup(Type, String), diff --git a/deja_vu/src/app.rs b/deja_vu/src/app.rs index e5ad2da..2e1167a 100644 --- a/deja_vu/src/app.rs +++ b/deja_vu/src/app.rs @@ -33,17 +33,15 @@ use tracing::{debug, info}; use crate::{ action::Action, components::{ - footer::Footer, fps::FpsCounter, left::Left, popup, right::Right, title::Title, Component, + footer::Footer, fps::FpsCounter, left::Left, popup, right::Right, state::StatefulList, + title::Title, Component, }, config::Config, line::{get_disk_description_right, get_part_description, DVLine}, state::{CloneSettings, Mode}, system::{ - boot, - cpu::get_cpu_name, - disk::PartitionTableType, - diskpart::build_dest_format_script, - drivers::{self}, + boot, cpu::get_cpu_name, disk::PartitionTableType, diskpart::build_dest_format_script, + drivers, }, tasks::{Task, Tasks}, tui::{Event, Tui}, @@ -62,9 +60,8 @@ pub struct App { tick_rate: f64, // App clone: CloneSettings, - cur_index: usize, cur_mode: Mode, - list_size: usize, + list: StatefulList, prev_mode: Mode, selections: Vec>, tasks: Tasks, @@ -96,9 +93,8 @@ impl App { tick_rate, // App clone: CloneSettings::new(disk_list_arc), - cur_index: 0, cur_mode: Mode::ScanDisks, - list_size: 0, + list: StatefulList::default(), prev_mode: Mode::ScanDisks, selections: vec![None, None], tasks, @@ -111,7 +107,7 @@ impl App { } else if let Some(index) = self.selections[0] { index } else { - self.cur_index + self.list.selected().unwrap() } } @@ -121,10 +117,12 @@ impl App { os_part_index = Some(index); } else if let Some(index) = self.selections[1] { os_part_index = Some(index); - } else if self.list_size > 0 { + } else { // Highlighted entry - if self.cur_index == self.get_boot_part_index() { - os_part_index = Some(self.cur_index); + if let Some(index) = self.list.selected() { + if index != self.get_boot_part_index() { + os_part_index = Some(index) + } } } os_part_index @@ -138,11 +136,11 @@ impl App { } else if let Some(index) = self.selections[1] { // Selected in this mode dest_index = Some(index); - } else if self.list_size > 0 { + } else if let Some(index) = self.list.selected() { // Highlighted entry if let Some(source_index) = self.get_source_index() { - if self.cur_index == source_index { - dest_index = Some(self.cur_index); + if index != source_index { + dest_index = Some(index) } } } @@ -150,18 +148,15 @@ impl App { } pub fn get_source_index(&self) -> Option { - let mut source_index = None; if let Some(index) = self.clone.disk_index_source { // Selected in prior mode - source_index = Some(index); + Some(index) } else if let Some(index) = self.selections[0] { // Selected in this mode - source_index = Some(index); - } else if self.list_size > 0 { - // Highlighted entry - source_index = Some(self.cur_index); + Some(index) + } else { + self.list.selected() } - source_index } pub fn next_mode(&mut self) -> (Option, Option) { @@ -307,6 +302,10 @@ impl App { String::from("COMPLETE\n\n\nThank you for using this tool!"), ))?; } + _ => { + //TODO + //FIXME + } } Ok(()) } @@ -410,25 +409,8 @@ impl App { Action::Suspend => self.should_suspend = true, Action::Resume => self.should_suspend = false, Action::ClearScreen => tui.terminal.clear()?, - Action::KeyUp => { - self.cur_index = match self.cur_index { - 0 => { - if self.list_size > 0 { - self.list_size - 1 - } else { - 0 - } - } - _ => self.cur_index - 1, - }; - } - Action::KeyDown => { - self.cur_index = if self.cur_index == self.list_size - 1 { - 0 - } else { - self.cur_index + 1 - }; - } + Action::KeyUp => self.list.previous(), + Action::KeyDown => self.list.next(), Action::Error(ref msg) => { self.action_tx .send(Action::DisplayPopup(popup::Type::Error, msg.clone()))?; @@ -481,7 +463,11 @@ impl App { self.selections[0] = one; self.selections[1] = two; } - Action::SetMode(new_mode) => self.set_mode(new_mode)?, + Action::SetMode(new_mode) => { + self.set_mode(new_mode)?; + let (start, lines) = get_right_selections(self, self.prev_mode, self.cur_mode); + self.action_tx.send(Action::UpdateRight(start, lines))?; + } _ => {} } for component in &mut self.components { @@ -579,16 +565,23 @@ fn get_right_selections(app: &App, prev_mode: Mode, cur_mode: Mode) -> (usize, V let mut start_index = 0; match (prev_mode, cur_mode) { (_, Mode::InstallDrivers) => { + info!("get_right_selections: InstallDrivers"); selections.push(vec![DVLine { - line_parts: vec![String::from("CPU"), get_cpu_name()], - line_colors: vec![Color::Cyan, Color::Reset], + line_parts: vec![String::from("CPU")], + line_colors: vec![Color::Cyan], }]); - start_index = 1; + selections.push(vec![DVLine { + line_parts: vec![get_cpu_name()], + line_colors: vec![Color::Reset], + }]); + start_index = 2; } (_, Mode::SelectDisks | Mode::SelectTableType) | (Mode::SelectDisks | Mode::SelectTableType, Mode::Confirm) => { + info!("get_right_selections: SelectDisks"); // Source Disk Details if let Some(index) = app.get_source_index() { + info!("get_right_selections: Source Disk Index: {}", index); let disk_list = app.clone.disk_list.lock().unwrap(); if let Some(disk) = disk_list.get(index) { selections.push(get_disk_description_right(&disk, "Source")); @@ -603,7 +596,7 @@ fn get_right_selections(app: &App, prev_mode: Mode, cur_mode: Mode) -> (usize, V // Add format warning disk_description[0] // i.e. "Dest:" .line_parts - .push(format!(" (WARNING: ALL DATA WILL BE DELETED!)")); + .push(" (WARNING: ALL DATA WILL BE DELETED!)".to_string()); disk_description[0].line_colors.push(Color::Red); if let Some(table_type) = &app.clone.table_type { // Show table type @@ -624,6 +617,7 @@ fn get_right_selections(app: &App, prev_mode: Mode, cur_mode: Mode) -> (usize, V } } (_, Mode::SelectParts) | (Mode::SelectParts, Mode::Confirm) => { + info!("get_right_selections: SelectParts"); if let Some(index) = app.get_dest_index() { start_index = 1; let disk_list = app.clone.disk_list.lock().unwrap(); @@ -648,5 +642,6 @@ fn get_right_selections(app: &App, prev_mode: Mode, cur_mode: Mode) -> (usize, V } _ => {} } + info!("Right Selections: {:?}", &selections); (start_index, selections) } diff --git a/deja_vu/src/components/right.rs b/deja_vu/src/components/right.rs index b707fe9..17c857d 100644 --- a/deja_vu/src/components/right.rs +++ b/deja_vu/src/components/right.rs @@ -20,24 +20,22 @@ use ratatui::{ widgets::{Block, Borders, Padding, Paragraph, Wrap}, }; use tokio::sync::mpsc::UnboundedSender; -use tracing::info; use super::{state::StatefulList, Component}; -use crate::{action::Action, config::Config, state::Mode}; +use crate::{action::Action, config::Config, line::DVLine, state::Mode}; #[derive(Default)] -pub struct Right<'a> { +pub struct Right { command_tx: Option>, config: Config, cur_mode: Mode, - list_desc: StatefulList>>, - list_item: StatefulList>>, - prev_mode: Mode, + list_header: Vec, + list: StatefulList>, selections: Vec>, title: String, } -impl Right<'_> { +impl Right { pub fn new() -> Self { Self { selections: vec![None, None], @@ -45,13 +43,9 @@ impl Right<'_> { ..Default::default() } } - - pub fn set_title(&mut self, new_title: &str) { - self.title = String::from(new_title); - } } -impl Component for Right<'_> { +impl Component for Right { fn handle_key_event(&mut self, key: KeyEvent) -> Result> { let _ = key; // to appease clippy Ok(None) @@ -69,91 +63,56 @@ impl Component for Right<'_> { fn update(&mut self, action: Action) -> Result> { match action { - Action::KeyUp => match self.cur_mode { - Mode::SelectDisks => self.list_disks.previous(), - Mode::SelectParts => self.list_parts.previous(), - _ => {} - }, - Action::KeyDown => match self.cur_mode { - Mode::SelectDisks => self.list_disks.next(), - Mode::SelectParts => self.list_parts.next(), - _ => {} - }, - Action::Process => { - if self.prev_mode == Mode::SelectDisks && self.cur_mode == Mode::Confirm { - self.selected_disks = self.selections.clone(); - } - } + Action::KeyUp => self.list.previous(), + Action::KeyDown => self.list.next(), Action::Select(one, two) => { self.selections[0] = one; self.selections[1] = two; } - Action::SelectTableType(table_type) => self.table_type = Some(table_type), Action::SetMode(new_mode) => { - self.prev_mode = self.cur_mode; self.cur_mode = new_mode; - match self.cur_mode { - Mode::SelectDisks => { - self.selections[0] = None; - self.selections[1] = None; - self.selected_disks[0] = None; - self.selected_disks[1] = None; - } - Mode::SelectParts => { - self.selections[0] = None; - self.selections[1] = None; - } - Mode::SelectTableType => { - self.selections[0] = None; - self.selections[1] = None; - self.table_type = None; - } - _ => {} - } + self.selections[0] = None; + self.selections[1] = None; } - Action::UpdateDiskList(disks) => { - info!("Updating disk list"); - self.list_disks.set_items(disks); - if self.cur_mode == Mode::Clone { - if let Some(index) = self.selected_disks[1] { - if let Some(disk) = self.list_disks.get(index) { - self.list_parts.set_items(disk.get_parts()); - - // Auto-select first partition and highlight likely OS partition - if let Some(index) = self.selected_disks[1] { - if let Some(disk) = self.list_disks.get(index) { - if let Some(table_type) = &self.table_type { - match table_type { - PartitionTableType::Guid => { - if disk.num_parts() >= 3 { - self.selections[0] = Some(0); - self.list_parts.select(2); - } - } - PartitionTableType::Legacy => { - if disk.num_parts() >= 2 { - self.selections[0] = Some(0); - self.list_parts.select(1); - } - } - } - } - } - } - } - } - } - } - Action::UpdateRight(new_descs, new_items) => { - let lines: Vec = new_descs.iter().map(|d| d.as_line()).collect(); - let items: Vec = new_items.iter().map(|p| text::Line::from(p.as_str())).collect(); - self.list_desc.set_items(lines); - // TODO / FIXME + // Action::UpdateDiskList(disks) => { + // info!("Updating disk list"); + // self.list_disks.set_items(disks); + // if self.cur_mode == Mode::Clone { + // if let Some(index) = self.selected_disks[1] { + // if let Some(disk) = self.list_disks.get(index) { + // self.list_parts.set_items(disk.get_parts()); + // // Auto-select first partition and highlight likely OS partition + // if let Some(index) = self.selected_disks[1] { + // if let Some(disk) = self.list_disks.get(index) { + // if let Some(table_type) = &self.table_type { + // match table_type { + // PartitionTableType::Guid => { + // if disk.num_parts() >= 3 { + // self.selections[0] = Some(0); + // self.list_parts.select(2); + // } + // } + // PartitionTableType::Legacy => { + // if disk.num_parts() >= 2 { + // self.selections[0] = Some(0); + // self.list_parts.select(1); + // } + // } + // } + // } + // } + // } + // } + // } + // } + // } + Action::UpdateRight(start_index, list) => { + self.list_header = list[..start_index].iter().flatten().cloned().collect(); + self.list.set_items(list[start_index..].to_vec()); } _ => {} } - Ok(None)E0277 - body_text.push(Line::from(Span::raw(format!("CPU: {}", get_cpu_name())))); + Ok(None) } fn draw(&mut self, frame: &mut Frame, area: Rect) -> Result<()> { @@ -163,251 +122,45 @@ impl Component for Right<'_> { .areas(area); // Title - let title = Paragraph::new(Line::from(self.title).centered()) + let title = Paragraph::new(Line::from(self.title.as_str()).centered()) .block(Block::default().borders(Borders::NONE)); frame.render_widget(title, title_area); // Body - let mut body_text = Vec::new(); - if let Some(header) = self.list_desc.get(0) { - header.iter().map(|line| body_text.push(line)); + let mut body_text: Vec = Vec::new(); + if !self.list_header.is_empty() { + // Static Header + self.list_header + .iter() + .for_each(|dv| body_text.push(dv.as_line())); + body_text.push(Line::from("")); + body_text.push(Line::from(str::repeat("─", (body_area.width - 4) as usize))); + body_text.push(Line::from("")); } + + // First selection if let Some(first_index) = self.selections[0] { - if let Some(first_desc) = self.list_desc.get(first_index + 1) { - first_desc.iter().map(|line| body_text.push(line)); - } - if let Some(first_item) = self.list_item.get(first_index) { - first_item.iter().map(|line| body_text.push(line)); + if let Some(first_desc) = self.list.get(first_index) { + first_desc + .iter() + .for_each(|dv| body_text.push(dv.as_line())); } } + + // Second selection if let Some(second_index) = self.selections[1] { - if let Some(second_desc) = self.list_desc.get(second_index + 1) { - second_desc.iter().map(|line| body_text.push(line)); - } - if let Some(second_item) = self.list_item.get(second_index) { - second_item.iter().map(|line| body_text.push(line)); - } - } - - // Body (Old) - let mut body_text = Vec::new(); - match (self.prev_mode, self.cur_mode) { - (_, Mode::InstallDrivers) => { - body_text.push(Line::from(Span::raw(format!("CPU: {}", get_cpu_name())))); - } - (_, Mode::SelectDisks | Mode::SelectTableType) - | (Mode::SelectDisks | Mode::SelectTableType, Mode::Confirm) => { - // Source Disk - body_text.push(Line::from(Span::styled( - "Source:", - Style::default().cyan().bold(), - ))); + if let Some(second_desc) = self.list.get(second_index) { + // Divider body_text.push(Line::from("")); - body_text.push(Line::from(Span::styled( - format!( - "{:<8} {:>11} {:<4} {:<4} {}", - "Disk ID", "Size", "Conn", "Type", "Model (Serial)" - ), - Style::new().green().bold(), - ))); - let index = if self.selected_disks[0].is_some() { - // Selected in prior mode - self.selected_disks[0] - } else if self.selections[0].is_some() { - // Selected in this mode - self.selections[0] - } else { - // Highlighted entry - self.list_disks.selected() - }; - if let Some(i) = index { - if let Some(disk) = self.list_disks.get(i) { - body_text.push(Line::from(Span::raw(&disk.description))); - - // Source parts - body_text.push(Line::from("")); - body_text.push(Line::from(Span::styled( - format!( - "{:<8} {:>11} {:<7} {}", - "Part ID", "Size", "(FS)", "\"Label\"" - ), - Style::new().blue().bold(), - ))); - for line in &disk.parts_description { - body_text.push(Line::from(Span::raw(line))); - } - } - } - - // Destination Disk - let index = if self.selected_disks[1].is_some() { - // Selected in prior mode - self.selected_disks[1] - } else { - // Select(ed) in this mode - match (self.selections[0], self.selections[1]) { - (Some(one), None) => { - // First selected - if let Some(two) = self.selections[1] { - if one == two { - None - } else { - self.selections[1] - } - } else { - self.list_disks.selected() - } - } - (Some(_), Some(_)) => { - // Both selected - self.selections[1] - } - (_, _) => None, - } - }; - if let Some(i) = index { - // Divider - body_text.push(Line::from("")); - body_text.push(Line::from(str::repeat("─", (body_area.width - 4) as usize))); - body_text.push(Line::from("")); - - // Disk - if let Some(disk) = self.list_disks.get(i) { - body_text.push(Line::from(vec![ - Span::styled("Dest:", Style::default().cyan().bold()), - Span::styled( - " (WARNING: ALL DATA WILL BE DELETED!)", - Style::default().red().bold(), - ), - ])); - if let Some(table_type) = &self.table_type { - body_text.push(Line::from(Span::styled( - format!(" (Will be formatted {table_type})"), - Style::default().yellow().bold(), - ))); - } - body_text.push(Line::from("")); - body_text.push(Line::from(Span::styled( - format!( - "{:<8} {:>11} {:<4} {:<4} {}", - "Disk ID", "Size", "Conn", "Type", "Model (Serial)" - ), - Style::new().green().bold(), - ))); - body_text.push(Line::from(Span::raw(&disk.description))); - - // Destination parts - body_text.push(Line::from("")); - body_text.push(Line::from(Span::styled( - format!( - "{:<8} {:>11} {:<7} {}", - "Part ID", "Size", "(FS)", "\"Label\"" - ), - Style::new().blue().bold(), - ))); - for line in &disk.parts_description { - body_text.push(Line::from(Span::raw(line))); - } - } - } + body_text.push(Line::from(str::repeat("─", (body_area.width - 4) as usize))); + body_text.push(Line::from("")); + second_desc + .iter() + .for_each(|dv| body_text.push(dv.as_line())); } - (_, Mode::SelectParts) | (Mode::SelectParts, Mode::Confirm) => { - // Disk - if let Some(index) = self.selected_disks[1] { - if let Some(disk) = self.list_disks.get(index) { - body_text.push(Line::from(Span::styled( - "Dest:", - Style::default().cyan().bold(), - ))); - body_text.push(Line::from("")); - body_text.push(Line::from(Span::styled( - format!( - "{:<8} {:>11} {:<4} {:<4} {}", - "Disk ID", "Size", "Conn", "Type", "Model (Serial)" - ), - Style::new().green().bold(), - ))); - body_text.push(Line::from(Span::raw(&disk.description))); - - // Destination parts - body_text.push(Line::from("")); - body_text.push(Line::from(Span::styled( - format!( - "{:<8} {:>11} {:<7} {}", - "Part ID", "Size", "(FS)", "\"Label\"" - ), - Style::new().blue().bold(), - ))); - for line in &disk.parts_description { - body_text.push(Line::from(Span::raw(line))); - } - } - - // Divider - body_text.push(Line::from("")); - body_text.push(Line::from(str::repeat("─", (body_area.width - 4) as usize))); - body_text.push(Line::from("")); - - // Boot Partition - // i.e. either the previously selected part or the highlighted one (if possible) - let mut boot_index = self.selections[0]; - if boot_index.is_none() { - if let Some(i) = self.list_parts.selected() { - boot_index = Some(i); - } - } - if let Some(i) = boot_index { - if let Some(part) = self.list_parts.get(i) { - body_text.push(Line::from(Span::styled( - "Boot:", - Style::default().cyan().bold(), - ))); - body_text.push(Line::from("")); - body_text.push(Line::from(Span::styled( - format!( - "{:<8} {:>11} {:<7} {}", - "Part ID", "Size", "(FS)", "\"Label\"" - ), - Style::new().blue().bold(), - ))); - body_text.push(Line::from(Span::raw(format!("{part}")))); - } - } - - // OS Partition - // i.e. either the previously selected part or the highlighted one (if needed) - let mut os_index = self.selections[1]; - if os_index.is_none() { - if let Some(boot_index) = self.selections[0] { - if let Some(i) = self.list_parts.selected() { - if boot_index != i { - os_index = Some(i); - } - } - } - } - if let Some(i) = os_index { - if let Some(part) = self.list_parts.get(i) { - body_text.push(Line::from("")); - body_text.push(Line::from(Span::styled( - "OS:", - Style::default().cyan().bold(), - ))); - body_text.push(Line::from("")); - body_text.push(Line::from(Span::styled( - format!( - "{:<8} {:>11} {:<7} {}", - "Part ID", "Size", "(FS)", "\"Label\"" - ), - Style::new().blue().bold(), - ))); - body_text.push(Line::from(Span::raw(format!("{part}")))); - } - } - } - } - _ => {} } + + // Build Paragraph let body = Paragraph::new(body_text) .style(Style::default().fg(Color::Gray)) .wrap(Wrap { trim: false }) diff --git a/deja_vu/src/line.rs b/deja_vu/src/line.rs index 3d694aa..0b2f813 100644 --- a/deja_vu/src/line.rs +++ b/deja_vu/src/line.rs @@ -33,7 +33,7 @@ impl DVLine { pub fn as_line(&self) -> Line { let mut spans = Vec::new(); zip(self.line_parts.clone(), self.line_colors.clone()) - .map(|(part, color)| spans.push(Span::styled(part, Style::default().fg(color)))); + .for_each(|(part, color)| spans.push(Span::styled(part, Style::default().fg(color)))); Line::from(spans) }