Implement terminal resizing

The resize event is received from glutin on the update thread, but the
renderer needs to be informed as well for updating the viewport and
projection matrix. This is achieved with an mpsc::channel.

To support resizing, the grid now offers methods for growing and
shrinking, and there are several implementations available for
clear_region based on different Range* types.

Core resize logic is all in Term::resize. It attempts to keep as much
context as possible when shinking the window. When growing, it's
basically just adding rows.
This commit is contained in:
Joe Wilm 2016-06-29 10:21:02 -07:00
parent 69ed81d249
commit 22789f35c7
4 changed files with 196 additions and 11 deletions

View File

@ -1,6 +1,7 @@
//! Functions for computing properties of the terminal grid
use std::ops::{Index, IndexMut, Deref, DerefMut, Range, RangeTo, RangeFrom};
use std::cmp::Ordering;
use std::slice::{Iter, IterMut};
use util::Rotate;
@ -104,12 +105,55 @@ impl Grid {
self.clear_region(region);
}
pub fn clear_region(&mut self, region: Range<usize>) {
for row in self.raw[region].iter_mut() {
for cell in row.iter_mut() {
cell.reset();
}
pub fn resize(&mut self, rows: usize, cols: usize) {
// Check that there's actually work to do and return early if not
if rows == self.rows && cols == self.cols {
return;
}
match self.rows.cmp(&rows) {
Ordering::Less => self.grow_rows(rows),
Ordering::Greater => self.shrink_rows(rows),
Ordering::Equal => (),
}
match self.cols.cmp(&cols) {
Ordering::Less => self.grow_cols(cols),
Ordering::Greater => self.shrink_cols(cols),
Ordering::Equal => (),
}
}
fn grow_rows(&mut self, rows: usize) {
for _ in self.num_rows()..rows {
self.raw.push(Row::new(self.cols));
}
self.rows = rows;
}
fn shrink_rows(&mut self, rows: usize) {
while self.raw.len() != rows {
self.raw.pop();
}
self.rows = rows;
}
fn grow_cols(&mut self, cols: usize) {
for row in self.rows_mut() {
row.grow(cols);
}
self.cols = cols;
}
fn shrink_cols(&mut self, cols: usize) {
for row in self.rows_mut() {
row.shrink(cols);
}
self.cols = cols;
}
}
@ -154,6 +198,18 @@ impl Row {
Row(vec![Cell::new(' '); columns])
}
pub fn grow(&mut self, cols: usize) {
while self.len() != cols {
self.push(Cell::new(' '));
}
}
pub fn shrink(&mut self, cols: usize) {
while self.len() != cols {
self.pop();
}
}
pub fn cells(&self) -> Iter<Cell> {
self.0.iter()
}
@ -221,3 +277,25 @@ impl IndexMut<RangeTo<usize>> for Row {
&mut self.0[index]
}
}
pub trait ClearRegion<T> {
fn clear_region(&mut self, region: T);
}
macro_rules! clear_region_impl {
($range:ty) => {
impl ClearRegion<$range> for Grid {
fn clear_region(&mut self, region: $range) {
for row in self.raw[region].iter_mut() {
for cell in row.iter_mut() {
cell.reset();
}
}
}
}
}
}
clear_region_impl!(Range<usize>);
clear_region_impl!(RangeTo<usize>);
clear_region_impl!(RangeFrom<usize>);

View File

@ -65,6 +65,7 @@ fn handle_event<W>(event: Event,
writer: &mut W,
terminal: &mut Term,
pty_parser: &mut ansi::Parser,
render_tx: &mpsc::Sender<(u32, u32)>,
input_processor: &mut input::Processor) -> ShouldExit
where W: Write
{
@ -78,6 +79,10 @@ fn handle_event<W>(event: Event,
let encoded = c.encode_utf8();
writer.write(encoded.as_slice()).unwrap();
},
glutin::Event::Resized(w, h) => {
terminal.resize(w as f32, h as f32);
render_tx.send((w, h)).expect("render thread active");
},
glutin::Event::KeyboardInput(state, _code, key) => {
input_processor.process(state, key, &mut WriteNotifier(writer), *terminal.mode())
},
@ -165,6 +170,8 @@ fn main() {
let window = Arc::new(window);
let window_ref = window.clone();
let (render_tx, render_rx) = mpsc::channel::<(u32, u32)>();
let update_thread = thread::spawn_named("Update", move || {
'main_loop: loop {
let mut writer = BufWriter::new(&writer);
@ -192,6 +199,7 @@ fn main() {
&mut writer,
&mut *terminal,
&mut pty_parser,
&render_tx,
&mut input_processor);
if res == ShouldExit::Yes {
break;
@ -205,6 +213,7 @@ fn main() {
&mut writer,
&mut *terminal,
&mut pty_parser,
&render_tx,
&mut input_processor);
if res == ShouldExit::Yes {
@ -249,6 +258,15 @@ fn main() {
gl::Clear(gl::COLOR_BUFFER_BIT);
}
// Receive any resize events; only call gl::Viewport on last available
let mut new_size = None;
while let Ok(val) = render_rx.try_recv() {
new_size = Some(val);
}
if let Some((w, h)) = new_size.take() {
renderer.resize(w as i32, h as i32);
}
// Need scope so lock is released when swap_buffers is called
{
// Flag that it's time for render

View File

@ -478,6 +478,18 @@ impl QuadRenderer {
self.active_tex = 0;
self.program = program;
}
pub fn resize(&mut self, width: i32, height: i32) {
// viewport
unsafe {
gl::Viewport(0, 0, width, height);
}
// update projection
self.program.activate();
self.program.update_projection(width as f32, height as f32);
self.program.deactivate();
}
}
impl<'a> RenderApi<'a> {
@ -665,20 +677,25 @@ impl ShaderProgram {
u_background: background,
};
shader.update_projection(width as f32, height as f32);
shader.deactivate();
Ok(shader)
}
fn update_projection(&self, width: f32, height: f32) {
// set projection uniform
let ortho = cgmath::ortho(0., width as f32, 0., height as f32, -1., 1.);
let ortho = cgmath::ortho(0., width, 0., height, -1., 1.);
let projection: [[f32; 4]; 4] = ortho.into();
println!("width: {}, height: {}", width, height);
unsafe {
gl::UniformMatrix4fv(shader.u_projection,
gl::UniformMatrix4fv(self.u_projection,
1, gl::FALSE, projection.as_ptr() as *const _);
}
shader.deactivate();
Ok(shader)
}
fn set_term_uniforms(&self, props: &term::SizeInfo) {

View File

@ -2,10 +2,21 @@
use std::ops::Range;
use ansi::{self, Attr};
use grid::{self, Grid, CellFlags};
use grid::{self, Grid, CellFlags, ClearRegion};
use tty;
use ::Rgb;
/// coerce val to be between min and max
fn limit<T: PartialOrd>(val: T, min: T, max: T) -> T {
if val < min {
min
} else if val > max {
max
} else {
val
}
}
/// tomorrow night bright
///
/// because contrast
@ -190,6 +201,67 @@ impl Term {
}
}
/// Resize terminal to new dimensions
pub fn resize(&mut self, width: f32, height: f32) {
let size = SizeInfo {
width: width,
height: height,
cell_width: self.size_info.cell_width,
cell_height: self.size_info.cell_height,
};
let old_cols = self.size_info.cols();
let old_rows = self.size_info.rows();
let num_cols = size.cols();
let num_rows = size.rows();
self.size_info = size;
if old_cols == num_cols && old_rows == num_rows {
return;
}
// Scroll up to keep cursor and as much context as possible in grid. This only runs when the
// rows decreases.
self.scroll_region = 0..self.grid.num_rows();
// XXX why is +1 required?
let row_diff = (self.cursor_y() as isize - num_rows as isize) + 1;
if row_diff > 0 {
self.scroll(row_diff);
self.cursor.advance(-row_diff as i64, 0);
}
println!("num_cols, num_rows = {}, {}", num_cols, num_rows);
// Resize grids to new size
self.grid.resize(num_rows, num_cols);
self.alt_grid.resize(num_rows, num_cols);
// Ensure cursor is in-bounds
self.cursor.y = limit(self.cursor.y, 0, num_rows as u16);
self.cursor.x = limit(self.cursor.x, 0, num_cols as u16);
// Recreate tabs list
self.tabs = (0..self.grid.num_cols()).map(|i| i % TAB_SPACES == 0)
.collect::<Vec<bool>>();
// Make sure bottom of terminal is clear
if row_diff > 0 {
self.grid.clear_region((self.cursor.y as usize)..);
self.alt_grid.clear_region((self.cursor.y as usize)..);
}
// Reset scrolling region to new size
self.scroll_region = 0..self.grid.num_rows();
// Inform tty of new dimensions
self.tty.resize(num_rows,
num_cols,
self.size_info.width as usize,
self.size_info.height as usize);
}
#[inline]
pub fn tty(&self) -> &tty::Tty {
&self.tty