Skip to content

Commit

Permalink
Subdividable screens.
Browse files Browse the repository at this point in the history
Introduce a layer of indirection between `Terminal` and `CharGrid`,
using the `Screen` type, which allows a `Terminal` to contain multiple
`CharGrids` in different sections of the visible screen.

Prior to this commit, a single `Terminal` contained a stack of
`CharGrid`s, which each filled the entire size of the `Terminal` window.

Now, a single `Terminal` contains a single `Screen`, which is divided
into `ScreenSection`s. Each `ScreenSection` is a rectangular section of
the screen. They are formed by splitting an existing section into two
along a horizontal or vertical axis. This means that the relationship
between sections that exist is always a binary tree. Each
`ScreenSection` has an unsigned 64-bit tag. One section is identified as
the 'active section' - commands that apply to the `CharGrid` are applied
to the grid contained in this section.

Each section contains a non-empty stack of `Panel`s. A `Panel` can take
one of two forms: either it contains a `CharGrid`, or it is split into
two parts, each of which contains a `ScreenSection`. As a result, there
is a mutually recursive relationship between the definition of
`ScreenSection` and `Panel`, which allows for considerable flexibility
in adjusting the layout screen, while still maintaining a certain level
of restraint.

This commit and its associated commit to notty-encoding add or modify
several commands to the notty protocol:

* __PUSH BUFFER__ is now __PUSH PANEL__, and takes as an argument the
tag of the screen section being pushed to. If no argument is passed, a
panel is pushed to the active section. The panel that is pushed always
contains a single character grid. It also takes a boolean argument,
which determines whether or not the grid retains offscreen data as it
scrolls.
* __POP BUFFER__ is now __POP PANEL__, and also takes as an argument the
tag of screen section to pop from. If no argument is passed, the top
panel of the active section is popped. A stack with 1 member cannot be
popped from.
* __SPLIT PANEL__ describes splitting a panel into two. It takes
arguments defining the axis and position of the split, which panel the
contents should be saved to, how resizing of child sections should be
handled, and whether or not the grid in the new panel should retain
offscreen data as it scrolls. Panels which are already split *can* be
split again; the existing split sections are resized into the
identified saved sections.
* __UNSPLIT PANEL__ describes removing the split from a panel. It takes
an argument identifying which of the two sections in this panel should
be saved. The stack of the saved section will be pushed to the top of
the section this panel is on top of; the stack of the unsaved section
will be destroyed.
* __ADJUST PANEL SPLIT__ allows an existing split panel's split to be
moved or adjusted. It takes arguments defining the axis and position of
the split, and how resizing of the child sections should be handled. It
performs no action it the section's top is a unsplit panel.
* __ROTATE SECTION DOWN__ rotates down the stack, putting the top of
the stack on the bottom.
* __ROTATE SECTION UP__ rotates up the stack, putting the bottom of the
stack on the top.
* __SWITCH ACTIVE SECTION__ switches which section is active. It will
only perform a switch if the top panel of the target section is a single
character grid.
  • Loading branch information
withoutboats committed Apr 1, 2016
1 parent e94b8a9 commit 431b7a9
Show file tree
Hide file tree
Showing 18 changed files with 1,542 additions and 112 deletions.
File renamed without changes.
127 changes: 127 additions & 0 deletions docs/screen_divisions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
# Subdividing the screen in notty

notty supports subdividing a screen into multiple sections, each of which can
contain independent grids. Each of these grids can retain off-screen state,
scroll independently, be resized independently, and so on. There are several
commands which manipulate this feature, but what's most important is
understanding the underlying layout model that notty uses. Here are the
rules that notty's model uses to make screen subdivision easier:

1. The screen is subdivided into nested, rectangular sections.
2. Each section of the screen contains a stack of panels.
3. Each panel is either a single character grid, or is split into two smaller
sections, each of which contain a stack of panels.

# For example

Consider this 6x6 screen:

```
0 1 2 3 4 5
+-----+-----+
0| | |
1| | |
2| | |
3| | |
+-----+-----+
4| |
5| |
+-----+-----+
```

Though this screen contains only 3 grids, it actually contains 5 sections:

1. The base section, which contains the entire 6x6 grid. The panel in this
section is split horizontally between rows 3 and 4.
2. The top portion of the base section, filling the area from 0,0 to 5,3
(inclusive). The panel in this section is split vertically between columns 2
and 3.
3. The left portion of the previous section, from 0,0 to 2,3 (inclusive). The
panel here contains a character grid.
4. The right portion of that section, from 3,0 to 5,3 (inclusive). The panel
here contains a character grid as well.
5. The lower portion of the base section, from 0,4 to 5,5 (inclusive). The
panel here also contains a character grid.

# Commands that can be applied to any section

In the implementation, each of these sections of the screen has an identifying
tag, so that different commands can be applied to each section. These actions
can all be performed on any section of the screen, whether it contains a grid
or a split:

## Pushing a Panel

Sections don't just contain one Panel, they contain a stack of Panels. You can
push a new Panel, which contains an empty grid, over any section on the screen.

This includes sections which contain split panels, and also includes the 'base
section' of the entire screen. This means, for example, a new grid could be
pushed over the entire screen in the above example, or over the top half of
the screen that contains the vertical split.

## Popping a Panel

The top panel of a section can be popped off, deleting whatever it contained.
This is only true if a section contains more than one panel - a section with a
stack with only one member will not change when the pop command is applied.

The tag of each section is assigned when that section is created, but the tag
of the base section is always 0. If two sections are given the same tag, the
behavior of notty commands applied to that tag is implementation defined, and
you should not create sections with the same tag.

## Rotating the Panel Stack

The stack in each section can also be rotated up and down, switching which
panel is on top (and therefore visible) without deleting any panels. The stack
cannot be arbitrarily reordered, it can only be rotated in either direction.

## Splitting a Section

Any section can be split into two sections, the split command takes arguments
which handle how it should be split, including which side of the split the
current content of the top panel should be saved to.

Splits can be either horizontal or vertical, and can occur between any two
columns/rows within that section.

Only the top panel of a section is split, if that panel is popped off, whatever
was underneath it will unchanged by the split.

A panel containing a split can be split: the current split will be saved to
one of the subsections created. This includes the base panel of the entire
screen.

# Commands that can be applied to split sections

These commands can be applied to sections which are split. Applying them to
sections which are not split produces no change.

## Unsplitting a Section

A split section can be unsplit. The unsplit command takes an argument
identifying which half of the split should be saved. The stack from the saved
section will be pushed on top of the stack in the section that is being
unsplit.

## Adjusting a Section Split

The division within a split section can be adjusted, changing how that section
is split. This adjustment can change both the position and axis of the split,
so that a horizontal split could become a vertical split, for example. The
contents of the two subsections of the split will be resized to fit the new
areas of the subsections.

# The 'active' section

At any given time, exactly one section of the screen is marked the active
section. This top panel of this section must be a character grid.

All commands which apply to character grids - writing characters, setting
styles, moving the cursor, and so on - are applied to the grid which is
currently active.

A command exists to switch which section is active at a given time. An attempt
to switch the active section to a section which does not have a character grid
as its top panel will result in no change.
6 changes: 3 additions & 3 deletions notty-cairo/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ impl Renderer {
});
let width = pix_w / (char_w as u32);
let height = pix_h / (char_h as u32);
terminal.set_winsize(width, height).unwrap_or_else(|e| panic!("{}", e));
terminal.set_winsize(Some(width), Some(height)).unwrap_or_else(|e| panic!("{}", e));
}

pub fn draw(&mut self, terminal: &Terminal, canvas: &cairo::Context) {
Expand All @@ -60,8 +60,8 @@ impl Renderer {
canvas.set_source_rgb(color(r), color(g), color(b));
canvas.paint();

let col_n = terminal.grid_width as usize;
let rows = terminal.into_iter().chunks_lazy(col_n);
let col_n = terminal.area().width() as usize;
let rows = terminal.cells().chunks_lazy(col_n);

// Remove dead images from the cache.
for key in self.images.keys().filter(|k| Arc::strong_count(k) == 1).cloned().collect::<Vec<_>>() {
Expand Down
25 changes: 1 addition & 24 deletions src/command/meta.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,34 +15,11 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>.
use std::cell::RefCell;

use notty_encoding::cmds::{PushBuffer, PopBuffer, SetInputMode};
use notty_encoding::cmds::SetInputMode;

use command::prelude::*;
use datatypes::InputSettings;

impl Command for PushBuffer {
fn apply(&self, terminal: &mut Terminal) -> io::Result<()> {
terminal.push_buffer(false, self.0);
Ok(())
}
fn repr(&self) -> String {
match self.0 {
true => String::from("PUSH BUFFER SCROLLING"),
false => String::from("PUSH BUFFER STATIC"),
}
}
}

impl Command for PopBuffer {
fn apply(&self, terminal: &mut Terminal) -> io::Result<()> {
terminal.pop_buffer();
Ok(())
}
fn repr(&self) -> String {
String::from("POP BUFFER")
}
}

pub struct SetTitle(pub RefCell<Option<String>>);

impl Command for SetTitle {
Expand Down
5 changes: 4 additions & 1 deletion src/command/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,17 @@ mod erase;
mod input;
mod meta;
mod movement;
mod panel;
mod put;
mod respond;
mod style;
mod tooltip;

pub use notty_encoding::cmds::{
Erase, RemoveChars, RemoveRows, InsertBlank, InsertRows,
PushBuffer, PopBuffer, SetInputMode,
PushPanel, PopPanel, SplitPanel, UnsplitPanel, AdjustPanelSplit,
RotateSectionDown, RotateSectionUp, SwitchActiveSection,
SetInputMode,
Move, ScrollScreen,
SetCursorStyle, DefaultCursorStyle,
SetTextStyle, DefaultTextStyle,
Expand Down
93 changes: 93 additions & 0 deletions src/command/panel.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
use command::prelude::*;
use datatypes::{SaveGrid, ResizeRule};

use notty_encoding::cmds::{
PushPanel, PopPanel,
SplitPanel, UnsplitPanel, AdjustPanelSplit,
RotateSectionDown, RotateSectionUp,
SwitchActiveSection,
};

impl Command for PushPanel {
fn apply(&self, terminal: &mut Terminal) -> io::Result<()> {
terminal.push(self.0, self.1.unwrap_or(true));
Ok(())
}
fn repr(&self) -> String {
String::from("PUSH BUFFER")
}
}

impl Command for PopPanel {
fn apply(&self, terminal: &mut Terminal) -> io::Result<()> {
terminal.pop(self.0);
Ok(())
}
fn repr(&self) -> String {
String::from("POP BUFFER")
}
}

impl Command for SplitPanel {
fn apply(&self, terminal: &mut Terminal) -> io::Result<()> {
let save = self.save.unwrap_or(SaveGrid::Left);
let rule = self.rule.unwrap_or(ResizeRule::Percentage);
terminal.split(save, self.kind, rule, self.split_tag, self.l_tag, self.r_tag,
self.retain_offscreen_state.unwrap_or(true));
Ok(())
}
fn repr(&self) -> String {
String::from("SPLIT BUFFER")
}
}

impl Command for UnsplitPanel {
fn apply(&self, terminal: &mut Terminal) -> io::Result<()> {
terminal.unsplit(self.save, self.unsplit_tag);
Ok(())
}
fn repr(&self) -> String {
String::from("UNSPLIT BUFFER")
}
}

impl Command for AdjustPanelSplit {
fn apply(&self, terminal: &mut Terminal) -> io::Result<()> {
terminal.adjust_split(self.adjust_tag, self.kind, self.rule);
Ok(())
}
fn repr(&self) -> String {
String::from("ADJUST PANEL SPLIT")
}
}

impl Command for RotateSectionDown {
fn apply(&self, terminal: &mut Terminal) -> io::Result<()> {
terminal.rotate_down(self.0);
Ok(())
}
fn repr(&self) -> String {
String::from("ROTATE DOWN")
}
}

impl Command for RotateSectionUp {
fn apply(&self, terminal: &mut Terminal) -> io::Result<()> {
terminal.rotate_up(self.0);
Ok(())
}
fn repr(&self) -> String {
String::from("ROTATE UP")
}
}

impl Command for SwitchActiveSection {
fn apply(&self, terminal: &mut Terminal) -> io::Result<()> {
terminal.switch(self.0);
Ok(())
}
fn repr(&self) -> String {
format!("SWITCH TO PANEL {}", self.0)
}
}

4 changes: 4 additions & 0 deletions src/datatypes/iter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,10 @@ impl CoordsIter {
}
}

pub fn region(&self) -> Region {
self.region
}

}

impl Iterator for CoordsIter {
Expand Down
3 changes: 3 additions & 0 deletions src/datatypes/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ pub mod args {
MediaPosition,
Movement,
Region,
ResizeRule,
SaveGrid,
SplitKind,
Style,
};
pub use super::Area::*;
Expand Down
2 changes: 1 addition & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
#![feature(io)]
#![feature(io, iter_arith)]

extern crate base64;
extern crate mime;
Expand Down
4 changes: 2 additions & 2 deletions src/output/ansi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ impl AnsiData {
1043 => wrap(NoFeature(self.csi_code(terminal))),
1047 => wrap(NoFeature(self.csi_code(terminal))),
1048 => wrap(NoFeature(self.csi_code(terminal))),
1049 => wrap(PushBuffer(false)),
1049 => wrap(PushPanel(None, Some(false))),
1050 => wrap(NoFeature(self.csi_code(terminal))),
2004 => wrap(NoFeature(self.csi_code(terminal))),
_ => None
Expand Down Expand Up @@ -180,7 +180,7 @@ impl AnsiData {
1043 => wrap(NoFeature(self.csi_code(terminal))),
1047 => wrap(NoFeature(self.csi_code(terminal))),
1048 => wrap(NoFeature(self.csi_code(terminal))),
1049 => wrap(PopBuffer),
1049 => wrap(PopPanel(None)),
1050 => wrap(NoFeature(self.csi_code(terminal))),
2004 => wrap(NoFeature(self.csi_code(terminal))),
_ => None
Expand Down
Loading

0 comments on commit 431b7a9

Please sign in to comment.