state: Move grid to Screen struct under terminal mod

pull/144/head
Manos Pitsidianakis 2021-09-20 13:56:51 +03:00
parent 20feb50475
commit e090c31f96
Signed by: Manos Pitsidianakis
GPG Key ID: 73627C2F690DF710
2 changed files with 290 additions and 250 deletions

View File

@ -33,19 +33,14 @@ use super::*;
use melib::backends::{AccountHash, BackendEventConsumer};
use crate::jobs::JobExecutor;
use crate::terminal::screen::Screen;
use crossbeam::channel::{unbounded, Receiver, Sender};
use indexmap::IndexMap;
use smallvec::SmallVec;
use std::env;
use std::io::Write;
use std::os::unix::io::RawFd;
use std::sync::Arc;
use std::thread;
use termion::raw::IntoRawMode;
use termion::screen::AlternateScreen;
use termion::{clear, cursor};
pub type StateStdout = termion::screen::AlternateScreen<termion::raw::RawTerminal<std::io::Stdout>>;
struct InputHandler {
pipe: (RawFd, RawFd),
@ -169,16 +164,9 @@ impl Context {
/// A State object to manage and own components and components of the UI. `State` is responsible for
/// managing the terminal and interfacing with `melib`
pub struct State {
cols: usize,
rows: usize,
grid: CellBuffer,
overlay_grid: CellBuffer,
screen: Screen,
draw_rate_limit: RateLimit,
stdout: Option<StateStdout>,
mouse: bool,
child: Option<ForkType>,
draw_horizontal_segment_fn: fn(&mut CellBuffer, &mut StateStdout, usize, usize, usize) -> (),
pub mode: UIMode,
overlay: Vec<Box<dyn Component>>,
components: Vec<Box<dyn Component>>,
@ -203,7 +191,7 @@ struct DisplayMessage {
impl Drop for State {
fn drop(&mut self) {
// When done, restore the defaults to avoid messing with the terminal.
self.switch_to_main_screen();
self.screen.switch_to_main_screen();
use nix::sys::wait::{waitpid, WaitPidFlag};
for child in self.context.children.iter_mut() {
if let Err(err) = waitpid(
@ -315,23 +303,25 @@ impl State {
let working = Arc::new(());
let control = Arc::downgrade(&working);
let mut s = State {
cols,
rows,
grid: CellBuffer::new(cols, rows, Cell::with_char(' ')),
overlay_grid: CellBuffer::new(cols, rows, Cell::with_char(' ')),
stdout: None,
mouse: settings.terminal.use_mouse.is_true(),
screen: Screen {
cols,
rows,
grid: CellBuffer::new(cols, rows, Cell::with_char(' ')),
overlay_grid: CellBuffer::new(cols, rows, Cell::with_char(' ')),
mouse: settings.terminal.use_mouse.is_true(),
stdout: None,
draw_horizontal_segment_fn: if settings.terminal.use_color() {
Screen::draw_horizontal_segment
} else {
Screen::draw_horizontal_segment_no_color
},
},
child: None,
mode: UIMode::Normal,
components: Vec::with_capacity(8),
overlay: Vec::new(),
timer,
draw_rate_limit: RateLimit::new(1, 3, job_executor.clone()),
draw_horizontal_segment_fn: if settings.terminal.use_color() {
State::draw_horizontal_segment
} else {
State::draw_horizontal_segment_no_color
},
display_messages: SmallVec::new(),
display_messages_expiration_start: None,
display_messages_pos: 0,
@ -360,11 +350,11 @@ impl State {
},
};
if s.context.settings.terminal.ascii_drawing {
s.grid.set_ascii_drawing(true);
s.overlay_grid.set_ascii_drawing(true);
s.screen.grid.set_ascii_drawing(true);
s.screen.overlay_grid.set_ascii_drawing(true);
}
s.switch_to_alternate_screen();
s.screen.switch_to_alternate_screen(&s.context);
for i in 0..s.context.accounts.len() {
if !s.context.accounts[i].backend_capabilities.is_remote {
s.context.accounts[i].watch();
@ -416,78 +406,6 @@ impl State {
}
}
/// Switch back to the terminal's main screen (The command line the user sees before opening
/// the application)
pub fn switch_to_main_screen(&mut self) {
let mouse = self.mouse;
write!(
self.stdout(),
"{}{}{}{}{disable_sgr_mouse}{disable_mouse}",
termion::screen::ToMainScreen,
cursor::Show,
RestoreWindowTitleIconFromStack,
BracketModeEnd,
disable_sgr_mouse = if mouse { DisableSGRMouse.as_ref() } else { "" },
disable_mouse = if mouse { DisableMouse.as_ref() } else { "" },
)
.unwrap();
self.flush();
self.stdout = None;
}
pub fn switch_to_alternate_screen(&mut self) {
let s = std::io::stdout();
let mut stdout = AlternateScreen::from(s.into_raw_mode().unwrap());
write!(
&mut stdout,
"{save_title_to_stack}{}{}{}{window_title}{}{}{enable_mouse}{enable_sgr_mouse}",
termion::screen::ToAlternateScreen,
cursor::Hide,
clear::All,
cursor::Goto(1, 1),
BracketModeStart,
save_title_to_stack = SaveWindowTitleIconToStack,
window_title = if let Some(ref title) = self.context.settings.terminal.window_title {
format!("\x1b]2;{}\x07", title)
} else {
String::new()
},
enable_mouse = if self.mouse { EnableMouse.as_ref() } else { "" },
enable_sgr_mouse = if self.mouse {
EnableSGRMouse.as_ref()
} else {
""
},
)
.unwrap();
self.stdout = Some(stdout);
self.flush();
}
pub fn set_mouse(&mut self, value: bool) {
if let Some(stdout) = self.stdout.as_mut() {
write!(
stdout,
"{mouse}{sgr_mouse}",
mouse = if value {
AsRef::<str>::as_ref(&EnableMouse)
} else {
AsRef::<str>::as_ref(&DisableMouse)
},
sgr_mouse = if value {
AsRef::<str>::as_ref(&EnableSGRMouse)
} else {
AsRef::<str>::as_ref(&DisableSGRMouse)
},
)
.unwrap();
}
self.flush();
}
pub fn receiver(&self) -> Receiver<ThreadEvent> {
self.context.receiver.clone()
}
@ -502,27 +420,7 @@ impl State {
/// On `SIGWNICH` the `State` redraws itself according to the new terminal size.
pub fn update_size(&mut self) {
let termsize = termion::terminal_size().ok();
let termcols = termsize.map(|(w, _)| w);
let termrows = termsize.map(|(_, h)| h);
if termcols.unwrap_or(72) as usize != self.cols
|| termrows.unwrap_or(120) as usize != self.rows
{
debug!(
"Size updated, from ({}, {}) -> ({:?}, {:?})",
self.cols, self.rows, termcols, termrows
);
}
self.cols = termcols.unwrap_or(72) as usize;
self.rows = termrows.unwrap_or(120) as usize;
if !self.grid.resize(self.cols, self.rows, None) {
panic!(
"Terminal size too big: ({} cols, {} rows)",
self.cols, self.rows
);
}
let _ = self.overlay_grid.resize(self.cols, self.rows, None);
self.screen.update_size();
self.rcv_event(UIEvent::Resize);
self.display_messages_dirty = true;
self.display_messages_initialised = false;
@ -556,7 +454,10 @@ impl State {
self.display_messages_expiration_start = None;
areas.push((
(0, 0),
(self.cols.saturating_sub(1), self.rows.saturating_sub(1)),
(
self.screen.cols.saturating_sub(1),
self.screen.rows.saturating_sub(1),
),
));
}
}
@ -576,7 +477,7 @@ impl State {
}
}
/* draw each dirty area */
let rows = self.rows;
let rows = self.screen.rows;
for y in 0..rows {
let mut segment = None;
for ((x_start, y_start), (x_end, y_end)) in &areas {
@ -584,9 +485,9 @@ impl State {
continue;
}
if let Some((x_start, x_end)) = segment.take() {
(self.draw_horizontal_segment_fn)(
&mut self.grid,
self.stdout.as_mut().unwrap(),
(self.screen.draw_horizontal_segment_fn)(
&mut self.screen.grid,
self.screen.stdout.as_mut().unwrap(),
x_start,
x_end,
y,
@ -597,9 +498,9 @@ impl State {
*s = Some((*x_start, *x_end));
}
ref mut s @ Some(_) if s.unwrap().1 < *x_start => {
(self.draw_horizontal_segment_fn)(
&mut self.grid,
self.stdout.as_mut().unwrap(),
(self.screen.draw_horizontal_segment_fn)(
&mut self.screen.grid,
self.screen.stdout.as_mut().unwrap(),
s.unwrap().0,
s.unwrap().1,
y,
@ -607,9 +508,9 @@ impl State {
*s = Some((*x_start, *x_end));
}
ref mut s @ Some(_) if s.unwrap().1 < *x_end => {
(self.draw_horizontal_segment_fn)(
&mut self.grid,
self.stdout.as_mut().unwrap(),
(self.screen.draw_horizontal_segment_fn)(
&mut self.screen.grid,
self.screen.stdout.as_mut().unwrap(),
s.unwrap().0,
s.unwrap().1,
y,
@ -622,9 +523,9 @@ impl State {
}
}
if let Some((x_start, x_end)) = segment {
(self.draw_horizontal_segment_fn)(
&mut self.grid,
self.stdout.as_mut().unwrap(),
(self.screen.draw_horizontal_segment_fn)(
&mut self.screen.grid,
self.screen.stdout.as_mut().unwrap(),
x_start,
x_end,
y,
@ -644,9 +545,9 @@ impl State {
/* Clear area previously occupied by floating notification box */
let displ_area = self.display_messages_area;
for y in get_y(upper_left!(displ_area))..=get_y(bottom_right!(displ_area)) {
(self.draw_horizontal_segment_fn)(
&mut self.grid,
self.stdout.as_mut().unwrap(),
(self.screen.draw_horizontal_segment_fn)(
&mut self.screen.grid,
self.screen.stdout.as_mut().unwrap(),
get_x(upper_left!(displ_area)),
get_x(bottom_right!(displ_area)),
y,
@ -656,7 +557,7 @@ impl State {
let noto_colors = crate::conf::value(&self.context, "status.notification");
use crate::melib::text_processing::{Reflow, TextProcessing};
let msg_lines = msg.split_lines_reflow(Reflow::All, Some(self.cols / 3));
let msg_lines = msg.split_lines_reflow(Reflow::All, Some(self.screen.cols / 3));
let width = msg_lines
.iter()
.map(|line| line.grapheme_len() + 4)
@ -666,16 +567,19 @@ impl State {
let displ_area = place_in_area(
(
(0, 0),
(self.cols.saturating_sub(1), self.rows.saturating_sub(1)),
(
self.screen.cols.saturating_sub(1),
self.screen.rows.saturating_sub(1),
),
),
(width, std::cmp::min(self.rows, msg_lines.len() + 4)),
(width, std::cmp::min(self.screen.rows, msg_lines.len() + 4)),
false,
false,
);
let box_displ_area = create_box(&mut self.overlay_grid, displ_area);
for row in self.overlay_grid.bounds_iter(box_displ_area) {
let box_displ_area = create_box(&mut self.screen.overlay_grid, displ_area);
for row in self.screen.overlay_grid.bounds_iter(box_displ_area) {
for c in row {
self.overlay_grid[c]
self.screen.overlay_grid[c]
.set_ch(' ')
.set_fg(noto_colors.fg)
.set_bg(noto_colors.bg)
@ -688,7 +592,7 @@ impl State {
)) {
write_string_to_grid(
&line,
&mut self.overlay_grid,
&mut self.screen.overlay_grid,
noto_colors.fg,
noto_colors.bg,
noto_colors.attrs,
@ -725,7 +629,7 @@ impl State {
self.context.settings.shortcuts.general.info_message_next
)
},
&mut self.overlay_grid,
&mut self.screen.overlay_grid,
noto_colors.fg,
noto_colors.bg,
noto_colors.attrs,
@ -738,9 +642,9 @@ impl State {
for y in get_y(upper_left!(self.display_messages_area))
..=get_y(bottom_right!(self.display_messages_area))
{
(self.draw_horizontal_segment_fn)(
&mut self.overlay_grid,
self.stdout.as_mut().unwrap(),
(self.screen.draw_horizontal_segment_fn)(
&mut self.screen.overlay_grid,
self.screen.stdout.as_mut().unwrap(),
get_x(upper_left!(self.display_messages_area)),
get_x(bottom_right!(self.display_messages_area)),
y,
@ -752,9 +656,9 @@ impl State {
/* Clear area previously occupied by floating notification box */
let displ_area = self.display_messages_area;
for y in get_y(upper_left!(displ_area))..=get_y(bottom_right!(displ_area)) {
(self.draw_horizontal_segment_fn)(
&mut self.grid,
self.stdout.as_mut().unwrap(),
(self.screen.draw_horizontal_segment_fn)(
&mut self.screen.grid,
self.screen.stdout.as_mut().unwrap(),
get_x(upper_left!(displ_area)),
get_x(bottom_right!(displ_area)),
y,
@ -766,30 +670,34 @@ impl State {
let area = center_area(
(
(0, 0),
(self.cols.saturating_sub(1), self.rows.saturating_sub(1)),
(
self.screen.cols.saturating_sub(1),
self.screen.rows.saturating_sub(1),
),
),
(
if self.cols / 3 > 30 {
self.cols / 3
if self.screen.cols / 3 > 30 {
self.screen.cols / 3
} else {
self.cols
self.screen.cols
},
if self.rows / 5 > 10 {
self.rows / 5
if self.screen.rows / 5 > 10 {
self.screen.rows / 5
} else {
self.rows
self.screen.rows
},
),
);
copy_area(&mut self.overlay_grid, &self.grid, area, area);
self.overlay
.get_mut(0)
.unwrap()
.draw(&mut self.overlay_grid, area, &mut self.context);
copy_area(&mut self.screen.overlay_grid, &self.screen.grid, area, area);
self.overlay.get_mut(0).unwrap().draw(
&mut self.screen.overlay_grid,
area,
&mut self.context,
);
for y in get_y(upper_left!(area))..=get_y(bottom_right!(area)) {
(self.draw_horizontal_segment_fn)(
&mut self.overlay_grid,
self.stdout.as_mut().unwrap(),
(self.screen.draw_horizontal_segment_fn)(
&mut self.screen.overlay_grid,
self.screen.stdout.as_mut().unwrap(),
get_x(upper_left!(area)),
get_x(bottom_right!(area)),
y,
@ -799,76 +707,11 @@ impl State {
self.flush();
}
/// Draw only a specific `area` on the screen.
fn draw_horizontal_segment(
grid: &mut CellBuffer,
stdout: &mut StateStdout,
x_start: usize,
x_end: usize,
y: usize,
) {
write!(
stdout,
"{}",
cursor::Goto(x_start as u16 + 1, (y + 1) as u16)
)
.unwrap();
let mut current_fg = Color::Default;
let mut current_bg = Color::Default;
let mut current_attrs = Attr::DEFAULT;
write!(stdout, "\x1B[m").unwrap();
for x in x_start..=x_end {
let c = &grid[(x, y)];
if c.attrs() != current_attrs {
c.attrs().write(current_attrs, stdout).unwrap();
current_attrs = c.attrs();
}
if c.bg() != current_bg {
c.bg().write_bg(stdout).unwrap();
current_bg = c.bg();
}
if c.fg() != current_fg {
c.fg().write_fg(stdout).unwrap();
current_fg = c.fg();
}
if !c.empty() {
write!(stdout, "{}", c.ch()).unwrap();
}
}
}
fn draw_horizontal_segment_no_color(
grid: &mut CellBuffer,
stdout: &mut StateStdout,
x_start: usize,
x_end: usize,
y: usize,
) {
write!(
stdout,
"{}",
cursor::Goto(x_start as u16 + 1, (y + 1) as u16)
)
.unwrap();
let mut current_attrs = Attr::DEFAULT;
write!(stdout, "\x1B[m").unwrap();
for x in x_start..=x_end {
let c = &grid[(x, y)];
if c.attrs() != current_attrs {
c.attrs().write(current_attrs, stdout).unwrap();
current_attrs = c.attrs();
}
if !c.empty() {
write!(stdout, "{}", c.ch()).unwrap();
}
}
}
/// Draw the entire screen from scratch.
pub fn render(&mut self) {
self.update_size();
let cols = self.cols;
let rows = self.rows;
self.screen.update_size();
let cols = self.screen.cols;
let rows = self.screen.rows;
self.context
.dirty_areas
.push_back(((0, 0), (cols - 1, rows - 1)));
@ -879,11 +722,11 @@ impl State {
pub fn draw_component(&mut self, idx: usize) {
let component = &mut self.components[idx];
let upper_left = (0, 0);
let bottom_right = (self.cols - 1, self.rows - 1);
let bottom_right = (self.screen.cols - 1, self.screen.rows - 1);
if component.is_dirty() {
component.draw(
&mut self.grid,
&mut self.screen.grid,
(upper_left, bottom_right),
&mut self.context,
);
@ -1045,9 +888,11 @@ impl State {
))));
}
ToggleMouse => {
self.mouse = !self.mouse;
self.set_mouse(self.mouse);
self.rcv_event(UIEvent::StatusEvent(StatusEvent::SetMouse(self.mouse)));
self.screen.mouse = !self.screen.mouse;
self.screen.set_mouse(self.screen.mouse);
self.rcv_event(UIEvent::StatusEvent(StatusEvent::SetMouse(
self.screen.mouse,
)));
}
Quit => {
self.context
@ -1137,11 +982,11 @@ impl State {
* Fork has finished in the past.
* We're back in the AlternateScreen, but the cursor is reset to Shown, so fix
* it.
write!(self.stdout(), "{}", cursor::Hide,).unwrap();
write!(self.screen.stdout(), "{}", cursor::Hide,).unwrap();
self.flush();
*/
self.switch_to_main_screen();
self.switch_to_alternate_screen();
self.screen.switch_to_main_screen();
self.screen.switch_to_alternate_screen(&self.context);
self.context.restore_input();
return;
}
@ -1322,13 +1167,18 @@ impl State {
}
Some(false)
}
fn flush(&mut self) {
if let Some(s) = self.stdout.as_mut() {
s.flush().unwrap();
}
/// Switch back to the terminal's main screen (The command line the user sees before opening
/// the application)
pub fn switch_to_main_screen(&mut self) {
self.screen.switch_to_main_screen();
}
fn stdout(&mut self) -> &mut StateStdout {
self.stdout.as_mut().unwrap()
pub fn switch_to_alternate_screen(&mut self){
self.screen.switch_to_alternate_screen(&mut self.context);
}
fn flush(&mut self) {
self.screen.flush();
}
pub fn check_accounts(&mut self) {

View File

@ -451,3 +451,193 @@ mod braille {
}
}
}
pub use screen::StateStdout;
pub mod screen {
use super::*;
use cells::CellBuffer;
use std::io::Write;
use termion::raw::IntoRawMode;
use termion::screen::AlternateScreen;
use termion::{clear, cursor};
pub type StateStdout =
termion::screen::AlternateScreen<termion::raw::RawTerminal<std::io::Stdout>>;
pub struct Screen {
pub cols: usize,
pub rows: usize,
pub grid: CellBuffer,
pub overlay_grid: CellBuffer,
pub stdout: Option<StateStdout>,
pub mouse: bool,
pub draw_horizontal_segment_fn:
fn(&mut CellBuffer, &mut StateStdout, usize, usize, usize) -> (),
}
impl Screen {
/// Switch back to the terminal's main screen (The command line the user sees before opening
/// the application)
pub fn switch_to_main_screen(&mut self) {
let mouse = self.mouse;
write!(
self.stdout.as_mut().unwrap(),
"{}{}{}{}{disable_sgr_mouse}{disable_mouse}",
termion::screen::ToMainScreen,
cursor::Show,
RestoreWindowTitleIconFromStack,
BracketModeEnd,
disable_sgr_mouse = if mouse { DisableSGRMouse.as_ref() } else { "" },
disable_mouse = if mouse { DisableMouse.as_ref() } else { "" },
)
.unwrap();
self.flush();
self.stdout = None;
}
pub fn switch_to_alternate_screen(&mut self, context: &crate::Context) {
let s = std::io::stdout();
let mut stdout = AlternateScreen::from(s.into_raw_mode().unwrap());
write!(
&mut stdout,
"{save_title_to_stack}{}{}{}{window_title}{}{}{enable_mouse}{enable_sgr_mouse}",
termion::screen::ToAlternateScreen,
cursor::Hide,
clear::All,
cursor::Goto(1, 1),
BracketModeStart,
save_title_to_stack = SaveWindowTitleIconToStack,
window_title = if let Some(ref title) = context.settings.terminal.window_title {
format!("\x1b]2;{}\x07", title)
} else {
String::new()
},
enable_mouse = if self.mouse { EnableMouse.as_ref() } else { "" },
enable_sgr_mouse = if self.mouse {
EnableSGRMouse.as_ref()
} else {
""
},
)
.unwrap();
self.stdout = Some(stdout);
self.flush();
}
pub fn flush(&mut self) {
if let Some(s) = self.stdout.as_mut() {
s.flush().unwrap();
}
}
pub fn set_mouse(&mut self, value: bool) {
if let Some(stdout) = self.stdout.as_mut() {
write!(
stdout,
"{mouse}{sgr_mouse}",
mouse = if value {
AsRef::<str>::as_ref(&EnableMouse)
} else {
AsRef::<str>::as_ref(&DisableMouse)
},
sgr_mouse = if value {
AsRef::<str>::as_ref(&EnableSGRMouse)
} else {
AsRef::<str>::as_ref(&DisableSGRMouse)
},
)
.unwrap();
}
self.flush();
}
/// On `SIGWNICH` the `State` redraws itself according to the new terminal size.
pub fn update_size(&mut self) {
let termsize = termion::terminal_size().ok();
let termcols = termsize.map(|(w, _)| w);
let termrows = termsize.map(|(_, h)| h);
if termcols.unwrap_or(72) as usize != self.cols
|| termrows.unwrap_or(120) as usize != self.rows
{
debug!(
"Size updated, from ({}, {}) -> ({:?}, {:?})",
self.cols, self.rows, termcols, termrows
);
}
self.cols = termcols.unwrap_or(72) as usize;
self.rows = termrows.unwrap_or(120) as usize;
if !self.grid.resize(self.cols, self.rows, None) {
panic!(
"Terminal size too big: ({} cols, {} rows)",
self.cols, self.rows
);
}
let _ = self.overlay_grid.resize(self.cols, self.rows, None);
}
/// Draw only a specific `area` on the screen.
pub fn draw_horizontal_segment(
grid: &mut CellBuffer,
stdout: &mut StateStdout,
x_start: usize,
x_end: usize,
y: usize,
) {
write!(
stdout,
"{}",
cursor::Goto(x_start as u16 + 1, (y + 1) as u16)
)
.unwrap();
let mut current_fg = Color::Default;
let mut current_bg = Color::Default;
let mut current_attrs = Attr::DEFAULT;
write!(stdout, "\x1B[m").unwrap();
for x in x_start..=x_end {
let c = &grid[(x, y)];
if c.attrs() != current_attrs {
c.attrs().write(current_attrs, stdout).unwrap();
current_attrs = c.attrs();
}
if c.bg() != current_bg {
c.bg().write_bg(stdout).unwrap();
current_bg = c.bg();
}
if c.fg() != current_fg {
c.fg().write_fg(stdout).unwrap();
current_fg = c.fg();
}
if !c.empty() {
write!(stdout, "{}", c.ch()).unwrap();
}
}
}
pub fn draw_horizontal_segment_no_color(
grid: &mut CellBuffer,
stdout: &mut StateStdout,
x_start: usize,
x_end: usize,
y: usize,
) {
write!(
stdout,
"{}",
cursor::Goto(x_start as u16 + 1, (y + 1) as u16)
)
.unwrap();
let mut current_attrs = Attr::DEFAULT;
write!(stdout, "\x1B[m").unwrap();
for x in x_start..=x_end {
let c = &grid[(x, y)];
if c.attrs() != current_attrs {
c.attrs().write(current_attrs, stdout).unwrap();
current_attrs = c.attrs();
}
if !c.empty() {
write!(stdout, "{}", c.ch()).unwrap();
}
}
}
}
}