diff --git a/src/app.rs b/src/app.rs index d66ce89c..7fae7cf0 100644 --- a/src/app.rs +++ b/src/app.rs @@ -1,5 +1,10 @@ -// use std::io::Write; -use std::{collections::HashMap, path::PathBuf, time::Instant}; +use std::{ + cmp::{max, min}, + collections::HashMap, + // io::Write, + path::PathBuf, + time::Instant, +}; use unicode_segmentation::GraphemeCursor; use unicode_width::{UnicodeWidthChar, UnicodeWidthStr}; @@ -130,6 +135,13 @@ pub struct App { pub config_path: Option, } +#[cfg(target_os = "windows")] +const MAX_SIGNAL: usize = 1; +#[cfg(target_os = "linux")] +const MAX_SIGNAL: usize = 64; +#[cfg(target_os = "macos")] +const MAX_SIGNAL: usize = 31; + impl App { pub fn reset(&mut self) { // Reset multi @@ -170,7 +182,8 @@ impl App { fn close_dd(&mut self) { self.delete_dialog_state.is_showing_dd = false; - self.delete_dialog_state.is_on_yes = false; + self.delete_dialog_state.selected_signal = KillSignal::default(); + self.delete_dialog_state.scroll_pos = 0; self.to_delete_process_list = None; self.dd_err = None; } @@ -655,12 +668,13 @@ impl App { if self.delete_dialog_state.is_showing_dd { if self.dd_err.is_some() { self.close_dd(); - } else if self.delete_dialog_state.is_on_yes { + } else if self.delete_dialog_state.selected_signal != KillSignal::CANCEL { // If within dd... if self.dd_err.is_none() { // Also ensure that we didn't just fail a dd... let dd_result = self.kill_highlighted_process(); - self.delete_dialog_state.is_on_yes = false; + self.delete_dialog_state.scroll_pos = 0; + self.delete_dialog_state.selected_signal = KillSignal::default(); // Check if there was an issue... if so, inform the user. if let Err(dd_err) = dd_result { @@ -670,6 +684,8 @@ impl App { } } } else { + self.delete_dialog_state.scroll_pos = 0; + self.delete_dialog_state.selected_signal = KillSignal::default(); self.delete_dialog_state.is_showing_dd = false; } self.is_force_redraw = true; @@ -796,12 +812,52 @@ impl App { } } + #[cfg(target_family = "unix")] + pub fn on_number(&mut self, number_char: char) { + if self.delete_dialog_state.is_showing_dd { + if self + .delete_dialog_state + .last_number_press + .map_or(100, |ins| ins.elapsed().as_millis()) + > 500 + { + self.delete_dialog_state.keyboard_signal_select = 0; + } + let mut kbd_signal = self.delete_dialog_state.keyboard_signal_select * 10; + kbd_signal += number_char.to_digit(10).unwrap() as usize; + if kbd_signal > 64 { + kbd_signal %= 100; + } + #[cfg(target_os = "linux")] + if kbd_signal > 64 || kbd_signal == 32 || kbd_signal == 33 { + kbd_signal %= 10; + } + #[cfg(target_os = "macos")] + if kbd_signal > 31 { + kbd_signal %= 10; + } + self.delete_dialog_state.selected_signal = KillSignal::KILL(kbd_signal); + if kbd_signal < 10 { + self.delete_dialog_state.keyboard_signal_select = kbd_signal; + } else { + self.delete_dialog_state.keyboard_signal_select = 0; + } + self.delete_dialog_state.last_number_press = Some(Instant::now()); + } + } + pub fn on_up_key(&mut self) { if self.is_config_open { } else if !self.is_in_dialog() { self.decrement_position_count(); } else if self.help_dialog_state.is_showing_help { self.help_scroll_up(); + } else if self.delete_dialog_state.is_showing_dd { + #[cfg(target_os = "windows")] + self.on_right_key(); + #[cfg(target_family = "unix")] + self.on_left_key(); + return; } self.reset_multi_tap_keys(); } @@ -812,6 +868,12 @@ impl App { self.increment_position_count(); } else if self.help_dialog_state.is_showing_help { self.help_scroll_down(); + } else if self.delete_dialog_state.is_showing_dd { + #[cfg(target_os = "windows")] + self.on_left_key(); + #[cfg(target_family = "unix")] + self.on_right_key(); + return; } self.reset_multi_tap_keys(); } @@ -876,8 +938,25 @@ impl App { } _ => {} } - } else if self.delete_dialog_state.is_showing_dd && !self.delete_dialog_state.is_on_yes { - self.delete_dialog_state.is_on_yes = true; + } else if self.delete_dialog_state.is_showing_dd { + #[cfg(target_family = "unix")] + { + match self.delete_dialog_state.selected_signal { + KillSignal::KILL(prev_signal) => { + self.delete_dialog_state.selected_signal = match prev_signal - 1 { + 0 => KillSignal::CANCEL, + // 32+33 are skipped + 33 => KillSignal::KILL(31), + signal => KillSignal::KILL(signal), + }; + } + KillSignal::CANCEL => (), + }; + } + #[cfg(target_os = "windows")] + { + self.delete_dialog_state.selected_signal = KillSignal::KILL(1); + } } } @@ -947,8 +1026,54 @@ impl App { } _ => {} } - } else if self.delete_dialog_state.is_showing_dd && self.delete_dialog_state.is_on_yes { - self.delete_dialog_state.is_on_yes = false; + } else if self.delete_dialog_state.is_showing_dd { + #[cfg(target_family = "unix")] + { + let new_signal = match self.delete_dialog_state.selected_signal { + KillSignal::CANCEL => 1, + // 32+33 are skipped + #[cfg(target_os = "linux")] + KillSignal::KILL(31) => 34, + #[cfg(target_os = "macos")] + KillSignal::KILL(31) => 31, + KillSignal::KILL(64) => 64, + KillSignal::KILL(signal) => signal + 1, + }; + self.delete_dialog_state.selected_signal = KillSignal::KILL(new_signal); + } + #[cfg(target_os = "windows")] + { + self.delete_dialog_state.selected_signal = KillSignal::CANCEL; + } + } + } + + pub fn on_page_up(&mut self) { + if self.delete_dialog_state.is_showing_dd { + let mut new_signal = match self.delete_dialog_state.selected_signal { + KillSignal::CANCEL => 0, + KillSignal::KILL(signal) => max(signal, 8) - 8, + }; + if new_signal > 23 && new_signal < 33 { + new_signal -= 2; + } + self.delete_dialog_state.selected_signal = match new_signal { + 0 => KillSignal::CANCEL, + sig => KillSignal::KILL(sig), + }; + } + } + + pub fn on_page_down(&mut self) { + if self.delete_dialog_state.is_showing_dd { + let mut new_signal = match self.delete_dialog_state.selected_signal { + KillSignal::CANCEL => 8, + KillSignal::KILL(signal) => min(signal + 8, MAX_SIGNAL), + }; + if new_signal > 31 && new_signal < 42 { + new_signal += 2; + } + self.delete_dialog_state.selected_signal = KillSignal::KILL(new_signal); } } @@ -1181,8 +1306,33 @@ impl App { } } else if self.delete_dialog_state.is_showing_dd { match caught_char { - 'h' | 'j' => self.on_left_key(), - 'k' | 'l' => self.on_right_key(), + 'h' => self.on_left_key(), + 'j' => self.on_down_key(), + 'k' => self.on_up_key(), + 'l' => self.on_right_key(), + #[cfg(target_family = "unix")] + '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' => { + self.on_number(caught_char) + } + 'u' => self.on_page_up(), + 'd' => self.on_page_down(), + 'g' => { + let mut is_first_g = true; + if let Some(second_char) = self.second_char { + if self.awaiting_second_char && second_char == 'g' { + is_first_g = false; + self.awaiting_second_char = false; + self.second_char = None; + self.skip_to_first(); + } + } + + if is_first_g { + self.awaiting_second_char = true; + self.second_char = Some('g'); + } + } + 'G' => self.skip_to_last(), _ => {} } } else if self.is_config_open { @@ -1431,8 +1581,20 @@ impl App { pub fn kill_highlighted_process(&mut self) -> Result<()> { if let BottomWidgetType::Proc = self.current_widget.widget_type { if let Some(current_selected_processes) = &self.to_delete_process_list { + #[cfg(target_family = "unix")] + let signal = match self.delete_dialog_state.selected_signal { + KillSignal::KILL(sig) => sig, + KillSignal::CANCEL => 15, // should never happen, so just TERM + }; for pid in ¤t_selected_processes.1 { - process_killer::kill_process_given_pid(*pid)?; + #[cfg(target_family = "unix")] + { + process_killer::kill_process_given_pid(*pid, signal)?; + } + #[cfg(target_os = "windows")] + { + process_killer::kill_process_given_pid(*pid)?; + } } } self.to_delete_process_list = None; @@ -1977,6 +2139,8 @@ impl App { } else if self.is_config_open { } else if self.help_dialog_state.is_showing_help { self.help_dialog_state.scroll_state.current_scroll_index = 0; + } else if self.delete_dialog_state.is_showing_dd { + self.delete_dialog_state.selected_signal = KillSignal::CANCEL; } } @@ -2058,6 +2222,8 @@ impl App { .scroll_state .max_scroll_index .saturating_sub(1); + } else if self.delete_dialog_state.is_showing_dd { + self.delete_dialog_state.selected_signal = KillSignal::KILL(MAX_SIGNAL); } } @@ -2243,6 +2409,13 @@ impl App { } pub fn handle_scroll_up(&mut self) { + if self.delete_dialog_state.is_showing_dd { + #[cfg(target_family = "unix")] + { + self.on_up_key(); + return; + } + } if self.help_dialog_state.is_showing_help { self.help_scroll_up(); } else if self.current_widget.widget_type.is_widget_graph() { @@ -2253,6 +2426,13 @@ impl App { } pub fn handle_scroll_down(&mut self) { + if self.delete_dialog_state.is_showing_dd { + #[cfg(target_family = "unix")] + { + self.on_down_key(); + return; + } + } if self.help_dialog_state.is_showing_help { self.help_scroll_down(); } else if self.current_widget.widget_type.is_widget_graph() { @@ -2592,25 +2772,25 @@ impl App { } } - // Second short circuit --- are we in the dd dialog state? If so, only check yes/no and - // bail after. + // Second short circuit --- are we in the dd dialog state? If so, only check yes/no/signals + // and bail after. if self.is_in_dialog() { - if let ( - Some((yes_tlc_x, yes_tlc_y)), - Some((yes_brc_x, yes_brc_y)), - Some((no_tlc_x, no_tlc_y)), - Some((no_brc_x, no_brc_y)), - ) = ( - self.delete_dialog_state.yes_tlc, - self.delete_dialog_state.yes_brc, - self.delete_dialog_state.no_tlc, - self.delete_dialog_state.no_brc, + match self.delete_dialog_state.button_positions.iter().find( + |(tl_x, tl_y, br_x, br_y, _idx)| { + (x >= *tl_x && y >= *tl_y) && (x <= *br_x && y <= *br_y) + }, ) { - if (x >= yes_tlc_x && y >= yes_tlc_y) && (x <= yes_brc_x && y <= yes_brc_y) { - self.delete_dialog_state.is_on_yes = true; - } else if (x >= no_tlc_x && y >= no_tlc_y) && (x <= no_brc_x && y <= no_brc_y) { - self.delete_dialog_state.is_on_yes = false; + Some((_, _, _, _, 0)) => { + self.delete_dialog_state.selected_signal = KillSignal::CANCEL } + Some((_, _, _, _, idx)) => { + if *idx > 31 { + self.delete_dialog_state.selected_signal = KillSignal::KILL(*idx + 2) + } else { + self.delete_dialog_state.selected_signal = KillSignal::KILL(*idx) + } + } + _ => {} } return; } diff --git a/src/app/process_killer.rs b/src/app/process_killer.rs index e367d99c..179965fb 100644 --- a/src/app/process_killer.rs +++ b/src/app/process_killer.rs @@ -9,6 +9,7 @@ use winapi::{ }; /// This file is meant to house (OS specific) implementations on how to kill processes. +#[cfg(target_family = "unix")] use crate::utils::error::BottomError; use crate::Pid; @@ -31,46 +32,42 @@ impl Process { } } -/// Kills a process, given a PID. -pub fn kill_process_given_pid(pid: Pid) -> crate::utils::error::Result<()> { - if cfg!(target_family = "unix") { - #[cfg(any(target_family = "unix"))] - { - let output = unsafe { libc::kill(pid as i32, libc::SIGTERM) }; - if output != 0 { - // We had an error... - let err_code = std::io::Error::last_os_error().raw_os_error(); - let err = match err_code { - Some(libc::ESRCH) => "the target process did not exist.", - Some(libc::EPERM) => "the calling process does not have the permissions to terminate the target process(es).", - Some(libc::EINVAL) => "an invalid signal was specified.", - _ => "Unknown error occurred." - }; +/// Kills a process, given a PID, for unix. +#[cfg(target_family = "unix")] +pub fn kill_process_given_pid(pid: Pid, signal: usize) -> crate::utils::error::Result<()> { + let output = unsafe { libc::kill(pid as i32, signal as i32) }; + if output != 0 { + // We had an error... + let err_code = std::io::Error::last_os_error().raw_os_error(); + let err = match err_code { + Some(libc::ESRCH) => "the target process did not exist.", + Some(libc::EPERM) => "the calling process does not have the permissions to terminate the target process(es).", + Some(libc::EINVAL) => "an invalid signal was specified.", + _ => "Unknown error occurred." + }; - return if let Some(err_code) = err_code { - Err(BottomError::GenericError(format!( - "Error code {} - {}", - err_code, err, - ))) - } else { - Err(BottomError::GenericError(format!( - "Error code ??? - {}", - err, - ))) - }; - } - } - } else if cfg!(target_family = "windows") { - #[cfg(target_family = "windows")] - { - let process = Process::open(pid as DWORD)?; - process.kill()?; - } - } else { - return Err(BottomError::GenericError( - "Sorry, support operating systems outside the main three are not implemented yet!" - .to_string(), - )); + return if let Some(err_code) = err_code { + Err(BottomError::GenericError(format!( + "Error code {} - {}", + err_code, err, + ))) + } else { + Err(BottomError::GenericError(format!( + "Error code ??? - {}", + err, + ))) + }; + } + + Ok(()) +} + +/// Kills a process, given a PID, for windows. +#[cfg(target_os = "windows")] +pub fn kill_process_given_pid(pid: Pid) -> crate::utils::error::Result<()> { + { + let process = Process::open(pid as DWORD)?; + process.kill()?; } Ok(()) diff --git a/src/app/states.rs b/src/app/states.rs index fadb7bcf..fe32a50d 100644 --- a/src/app/states.rs +++ b/src/app/states.rs @@ -40,14 +40,32 @@ pub struct AppScrollWidgetState { pub table_state: TableState, } +#[derive(PartialEq)] +pub enum KillSignal { + CANCEL, + KILL(usize), +} + +impl Default for KillSignal { + #[cfg(target_family = "unix")] + fn default() -> Self { + KillSignal::KILL(15) + } + #[cfg(target_os = "windows")] + fn default() -> Self { + KillSignal::KILL(1) + } +} + #[derive(Default)] pub struct AppDeleteDialogState { pub is_showing_dd: bool, - pub is_on_yes: bool, // Defaults to "No" - pub yes_tlc: Option<(u16, u16)>, - pub yes_brc: Option<(u16, u16)>, - pub no_tlc: Option<(u16, u16)>, - pub no_brc: Option<(u16, u16)>, + pub selected_signal: KillSignal, + // tl x, tl y, br x, br y + pub button_positions: Vec<(u16, u16, u16, u16, usize)>, + pub keyboard_signal_select: usize, + pub last_number_press: Option, + pub scroll_pos: usize, } pub struct AppHelpDialogState { diff --git a/src/canvas.rs b/src/canvas.rs index 7c63b0ba..7fdba228 100644 --- a/src/canvas.rs +++ b/src/canvas.rs @@ -322,10 +322,7 @@ impl Painter { } // And reset dd_dialog... - app_state.delete_dialog_state.yes_tlc = None; - app_state.delete_dialog_state.yes_brc = None; - app_state.delete_dialog_state.no_tlc = None; - app_state.delete_dialog_state.no_brc = None; + app_state.delete_dialog_state.button_positions = vec![]; // And battery dialog... for battery_widget in app_state.battery_state.widget_states.values_mut() { @@ -373,14 +370,20 @@ impl Painter { let dd_text = self.get_dd_spans(app_state); - let (text_width, text_height) = ( - if terminal_width < 100 { - terminal_width * 90 / 100 - } else { - terminal_width * 50 / 100 - }, - 7, - ); + let text_width = if terminal_width < 100 { + terminal_width * 90 / 100 + } else { + terminal_width * 50 / 100 + }; + let text_height; + #[cfg(target_family = "unix")] + { + text_height = 22; + } + #[cfg(target_os = "windows")] + { + text_height = 7; + } // let (text_width, text_height) = if let Some(dd_text) = &dd_text { // let width = if current_width < 100 { // current_width * 90 / 100 diff --git a/src/canvas/dialogs/dd_dialog.rs b/src/canvas/dialogs/dd_dialog.rs index ea7987c9..794abe1b 100644 --- a/src/canvas/dialogs/dd_dialog.rs +++ b/src/canvas/dialogs/dd_dialog.rs @@ -1,3 +1,5 @@ +#[cfg(target_family = "unix")] +use std::cmp::min; use tui::{ backend::Backend, layout::{Alignment, Constraint, Direction, Layout, Rect}, @@ -6,7 +8,10 @@ use tui::{ widgets::{Block, Borders, Paragraph, Wrap}, }; -use crate::{app::App, canvas::Painter}; +use crate::{ + app::{App, KillSignal}, + canvas::Painter, +}; const DD_BASE: &str = " Confirm Kill Process ── Esc to close "; const DD_ERROR_BASE: &str = " Error ── Esc to close "; @@ -14,6 +19,10 @@ const DD_ERROR_BASE: &str = " Error ── Esc to close "; pub trait KillDialog { fn get_dd_spans(&self, app_state: &App) -> Option>; + fn draw_dd_confirm_buttons( + &self, f: &mut Frame<'_, B>, button_draw_loc: &Rect, app_state: &mut App, + ); + fn draw_dd_dialog( &self, f: &mut Frame<'_, B>, dd_text: Option>, app_state: &mut App, draw_loc: Rect, ) -> bool; @@ -58,6 +67,246 @@ impl KillDialog for Painter { None } + #[cfg(target_os = "windows")] + fn draw_dd_confirm_buttons( + &self, f: &mut Frame<'_, B>, button_draw_loc: &Rect, app_state: &mut App, + ) { + let (yes_button, no_button) = match app_state.delete_dialog_state.selected_signal { + KillSignal::KILL(_) => ( + Span::styled("Yes", self.colours.currently_selected_text_style), + Span::raw("No"), + ), + KillSignal::CANCEL => ( + Span::raw("Yes"), + Span::styled("No", self.colours.currently_selected_text_style), + ), + }; + + let button_layout = Layout::default() + .direction(Direction::Horizontal) + .constraints( + [ + Constraint::Percentage(35), + Constraint::Percentage(30), + Constraint::Percentage(35), + ] + .as_ref(), + ) + .split(*button_draw_loc); + + f.render_widget( + Paragraph::new(yes_button) + .block(Block::default()) + .alignment(Alignment::Right), + button_layout[0], + ); + f.render_widget( + Paragraph::new(no_button) + .block(Block::default()) + .alignment(Alignment::Left), + button_layout[2], + ); + + if app_state.should_get_widget_bounds() { + app_state.delete_dialog_state.button_positions = vec![ + ( + button_layout[2].x, + button_layout[2].y, + button_layout[2].x + button_layout[2].width, + button_layout[2].y + button_layout[2].height, + 0, + ), + ( + button_layout[0].x, + button_layout[0].y, + button_layout[0].x + button_layout[0].width, + button_layout[0].y + button_layout[0].height, + 1, + ), + ]; + } + } + + #[cfg(target_family = "unix")] + fn draw_dd_confirm_buttons( + &self, f: &mut Frame<'_, B>, button_draw_loc: &Rect, app_state: &mut App, + ) { + let signal_text; + #[cfg(target_os = "linux")] + { + signal_text = vec![ + "0: Cancel", + "1: HUP", + "2: INT", + "3: QUIT", + "4: ILL", + "5: TRAP", + "6: ABRT", + "7: BUS", + "8: FPE", + "9: KILL", + "10: USR1", + "11: SEGV", + "12: USR2", + "13: PIPE", + "14: ALRM", + "15: TERM", + "16: STKFLT", + "17: CHLD", + "18: CONT", + "19: STOP", + "20: TSTP", + "21: TTIN", + "22: TTOU", + "23: URG", + "24: XCPU", + "25: XFSZ", + "26: VTALRM", + "27: PROF", + "28: WINCH", + "29: IO", + "30: PWR", + "31: SYS", + "34: RTMIN", + "35: RTMIN+1", + "36: RTMIN+2", + "37: RTMIN+3", + "38: RTMIN+4", + "39: RTMIN+5", + "40: RTMIN+6", + "41: RTMIN+7", + "42: RTMIN+8", + "43: RTMIN+9", + "44: RTMIN+10", + "45: RTMIN+11", + "46: RTMIN+12", + "47: RTMIN+13", + "48: RTMIN+14", + "49: RTMIN+15", + "50: RTMAX-14", + "51: RTMAX-13", + "52: RTMAX-12", + "53: RTMAX-11", + "54: RTMAX-10", + "55: RTMAX-9", + "56: RTMAX-8", + "57: RTMAX-7", + "58: RTMAX-6", + "59: RTMAX-5", + "60: RTMAX-4", + "61: RTMAX-3", + "62: RTMAX-2", + "63: RTMAX-1", + "64: RTMAX", + ]; + } + #[cfg(target_os = "macos")] + { + signal_text = vec![ + "0: Cancel", + "1: HUP", + "2: INT", + "3: QUIT", + "4: ILL", + "5: TRAP", + "6: ABRT", + "7: EMT", + "8: FPE", + "9: KILL", + "10: BUS", + "11: SEGV", + "12: SYS", + "13: PIPE", + "14: ALRM", + "15: TERM", + "16: URG", + "17: STOP", + "18: TSTP", + "19: CONT", + "20: CHLD", + "21: TTIN", + "22: TTOU", + "23: IO", + "24: XCPU", + "25: XFSZ", + "26: VTALRM", + "27: PROF", + "28: WINCH", + "29: INFO", + "30: USR1", + "31: USR2", + ]; + } + + let button_rect = Layout::default() + .direction(Direction::Horizontal) + .margin(1) + .constraints( + [ + Constraint::Length((button_draw_loc.width - 14) / 2), + Constraint::Min(0), + Constraint::Length((button_draw_loc.width - 14) / 2), + ] + .as_ref(), + ) + .split(*button_draw_loc)[1]; + + let mut selected = match app_state.delete_dialog_state.selected_signal { + KillSignal::CANCEL => 0, + KillSignal::KILL(signal) => signal, + }; + // 32+33 are skipped + if selected > 31 { + selected -= 2; + } + + let layout = Layout::default() + .direction(Direction::Vertical) + .constraints(vec![Constraint::Min(1); button_rect.height as usize]) + .split(button_rect); + + let prev_offset: usize = app_state.delete_dialog_state.scroll_pos; + app_state.delete_dialog_state.scroll_pos = if selected == 0 { + 0 + } else if selected < prev_offset + 1 { + selected - 1 + } else if selected > prev_offset + (layout.len() as usize) - 1 { + selected - (layout.len() as usize) + 1 + } else { + prev_offset + }; + let scroll_offset: usize = app_state.delete_dialog_state.scroll_pos; + + let mut buttons = signal_text + [scroll_offset + 1..min((layout.len() as usize) + scroll_offset, signal_text.len())] + .iter() + .map(|text| Span::raw(*text)) + .collect::>>(); + buttons.insert(0, Span::raw(signal_text[0])); + buttons[selected - scroll_offset] = Span::styled( + signal_text[selected], + self.colours.currently_selected_text_style, + ); + + app_state.delete_dialog_state.button_positions = layout + .iter() + .enumerate() + .map(|(i, pos)| { + ( + pos.x, + pos.y, + pos.x + pos.width - 1, + pos.y + pos.height - 1, + if i == 0 { 0 } else { scroll_offset } + i, + ) + }) + .collect::>(); + + for (btn, pos) in buttons.into_iter().zip(layout.into_iter()) { + f.render_widget(Paragraph::new(btn).alignment(Alignment::Left), pos); + } + } + fn draw_dd_dialog( &self, f: &mut Frame<'_, B>, dd_text: Option>, app_state: &mut App, draw_loc: Rect, ) -> bool { @@ -107,67 +356,31 @@ impl KillDialog for Painter { draw_loc, ); + let btn_height; + #[cfg(target_family = "unix")] + { + btn_height = 20; + } + #[cfg(target_os = "windows")] + { + btn_height = 3; + } // Now draw buttons if needed... let split_draw_loc = Layout::default() .direction(Direction::Vertical) - .constraints(if app_state.dd_err.is_some() { - vec![Constraint::Percentage(100)] - } else { - vec![Constraint::Min(0), Constraint::Length(3)] - }) + .constraints( + if app_state.dd_err.is_some() { + vec![Constraint::Percentage(100)] + } else { + vec![Constraint::Min(3), Constraint::Length(btn_height)] + } + .as_ref(), + ) .split(draw_loc); // This being true implies that dd_err is none. if let Some(button_draw_loc) = split_draw_loc.get(1) { - let (yes_button, no_button) = if app_state.delete_dialog_state.is_on_yes { - ( - Span::styled("Yes", self.colours.currently_selected_text_style), - Span::raw("No"), - ) - } else { - ( - Span::raw("Yes"), - Span::styled("No", self.colours.currently_selected_text_style), - ) - }; - - let button_layout = Layout::default() - .direction(Direction::Horizontal) - .constraints([ - Constraint::Percentage(35), - Constraint::Percentage(30), - Constraint::Percentage(35), - ]) - .split(*button_draw_loc); - - f.render_widget( - Paragraph::new(yes_button) - .block(Block::default()) - .alignment(Alignment::Right), - button_layout[0], - ); - f.render_widget( - Paragraph::new(no_button) - .block(Block::default()) - .alignment(Alignment::Left), - button_layout[2], - ); - - if app_state.should_get_widget_bounds() { - app_state.delete_dialog_state.yes_tlc = - Some((button_layout[0].x, button_layout[0].y)); - app_state.delete_dialog_state.yes_brc = Some(( - button_layout[0].x + button_layout[0].width, - button_layout[0].y + button_layout[0].height, - )); - - app_state.delete_dialog_state.no_tlc = - Some((button_layout[2].x, button_layout[2].y)); - app_state.delete_dialog_state.no_brc = Some(( - button_layout[2].x + button_layout[2].width, - button_layout[2].y + button_layout[2].height, - )); - } + self.draw_dd_confirm_buttons(f, button_draw_loc, app_state); } if app_state.dd_err.is_some() {