WIP: moving GUI code over to use LineFormatter.

This commit is contained in:
Nathan Vegdahl 2015-01-25 14:00:45 -08:00
parent 8d83fe77d2
commit 109e46a027
6 changed files with 212 additions and 35 deletions

View File

@ -3,7 +3,6 @@
use buffer::Buffer;
use buffer::line_formatter::LineFormatter;
use buffer::line_formatter::RoundingBehavior::*;
use term_ui::formatter::ConsoleLineFormatter;
use std::path::Path;
use std::cmp::min;
use files::{load_file_to_buffer, save_buffer_to_file};
@ -13,8 +12,8 @@ use self::cursor::CursorSet;
mod cursor;
pub struct Editor {
pub buffer: Buffer<ConsoleLineFormatter>,
pub struct Editor<T: LineFormatter> {
pub buffer: Buffer<T>,
pub file_path: Path,
pub soft_tabs: bool,
pub dirty: bool,
@ -28,11 +27,11 @@ pub struct Editor {
}
impl Editor {
impl<T: LineFormatter> Editor<T> {
/// Create a new blank editor
pub fn new() -> Editor {
pub fn new(formatter: T) -> Editor<T> {
Editor {
buffer: Buffer::new(ConsoleLineFormatter::new(4)),
buffer: Buffer::new(formatter),
file_path: Path::new(""),
soft_tabs: false,
dirty: false,
@ -42,10 +41,11 @@ impl Editor {
}
}
pub fn new_from_file(path: &Path) -> Editor {
let buf = match load_file_to_buffer(path, ConsoleLineFormatter::new(4)) {
pub fn new_from_file(formatter: T, path: &Path) -> Editor<T> {
let buf = match load_file_to_buffer(path, formatter) {
Ok(b) => {b},
_ => {Buffer::new(ConsoleLineFormatter::new(4))}
// TODO: handle un-openable file better
_ => panic!("Could not open file!"),
};
let mut ed = Editor {
@ -163,11 +163,13 @@ impl Editor {
}
}
self.soft_tabs = true;
self.buffer.formatter.tab_width = width as u8;
// TODO
//self.soft_tabs = true;
//self.buffer.formatter.tab_width = width as u8;
}
else {
self.soft_tabs = false;
// TODO
//self.soft_tabs = false;
}
}
@ -271,7 +273,9 @@ impl Editor {
// Figure out how many spaces to insert
let (_, vis_pos) = self.buffer.index_to_v2d(c.range.0);
let next_tab_stop = ((vis_pos / self.buffer.formatter.tab_width as usize) + 1) * self.buffer.formatter.tab_width as usize;
// TODO: handle tab settings
//let next_tab_stop = ((vis_pos / self.buffer.formatter.tab_width as usize) + 1) * self.buffer.formatter.tab_width as usize;
let next_tab_stop = ((vis_pos / 4) + 1) * 4 as usize;
let space_count = min(next_tab_stop - vis_pos, 8);

158
src/gui/formatter.rs Normal file
View File

@ -0,0 +1,158 @@
use string_utils::{is_line_ending};
use buffer::line::{Line, LineGraphemeIter};
use buffer::line_formatter::{LineFormatter, RoundingBehavior};
//===================================================================
// LineFormatter implementation for terminals/consoles.
//===================================================================
pub struct GUILineFormatter {
pub tab_width: u8,
}
impl GUILineFormatter {
pub fn new(tab_width: u8) -> GUILineFormatter {
GUILineFormatter {
tab_width: tab_width,
}
}
/// Returns the visual cell width of a line
pub fn vis_width(&self, line: &Line) -> usize {
let mut width = 0;
for g in line.grapheme_iter() {
let w = grapheme_vis_width_at_vis_pos(g, width, self.tab_width as usize);
width += w;
}
return width;
}
pub fn vis_grapheme_iter<'b>(&'b self, line: &'b Line) -> ConsoleLineFormatterVisIter<'b> {
ConsoleLineFormatterVisIter {
grapheme_iter: line.grapheme_iter(),
f: self,
pos: (0, 0),
}
}
}
impl<'a> LineFormatter for GUILineFormatter {
fn single_line_height(&self) -> usize {
return 1;
}
fn dimensions(&self, line: &Line) -> (usize, usize) {
return (1, self.vis_width(line));
}
fn index_to_v2d(&self, line: &Line, index: usize) -> (usize, usize) {
let mut pos = 0;
let mut iter = line.grapheme_iter();
for _ in range(0, index) {
if let Some(g) = iter.next() {
let w = grapheme_vis_width_at_vis_pos(g, pos, self.tab_width as usize);
pos += w;
}
else {
panic!("GUILineFormatter::index_to_v2d(): index past end of line.");
}
}
return (0, pos);
}
fn v2d_to_index(&self, line: &Line, v2d: (usize, usize), rounding: (RoundingBehavior, RoundingBehavior)) -> usize {
let mut pos = 0;
let mut i = 0;
let mut iter = line.grapheme_iter();
while pos < v2d.1 {
if let Some(g) = iter.next() {
let w = grapheme_vis_width_at_vis_pos(g, pos, self.tab_width as usize);
if (w + pos) > v2d.1 {
let d1 = v2d.1 - pos;
let d2 = (pos + w) - v2d.1;
if d2 < d1 {
i += 1;
}
break;
}
else {
pos += w;
i += 1;
}
}
else {
break;
}
}
return i;
}
}
//===================================================================
// An iterator that iterates over the graphemes in a line in a
// manner consistent with the ConsoleFormatter.
//===================================================================
pub struct ConsoleLineFormatterVisIter<'a> {
grapheme_iter: LineGraphemeIter<'a>,
f: &'a GUILineFormatter,
pos: (usize, usize),
}
impl<'a> Iterator for ConsoleLineFormatterVisIter<'a> {
type Item = (&'a str, (usize, usize), usize);
fn next(&mut self) -> Option<(&'a str, (usize, usize), usize)> {
if let Some(g) = self.grapheme_iter.next() {
let pos = self.pos;
let width = grapheme_vis_width_at_vis_pos(g, self.pos.1, self.f.tab_width as usize);
self.pos = (self.pos.0, self.pos.1 + width);
return Some((g, (pos.0, pos.1), width));
}
else {
return None;
}
}
}
//===================================================================
// Helper functions
//===================================================================
/// Returns the visual width of a grapheme given a starting
/// position on a line.
fn grapheme_vis_width_at_vis_pos(g: &str, pos: usize, tab_width: usize) -> usize {
match g {
"\t" => {
let ending_pos = ((pos / tab_width) + 1) * tab_width;
return ending_pos - pos;
},
_ => {
if is_line_ending(g) {
return 1;
}
else {
return g.width(true);
}
}
}
}

View File

@ -7,13 +7,15 @@ use sdl2::rect::Rect;
use font::Font;
use editor::Editor;
use self::formatter::GUILineFormatter;
pub mod formatter;
pub struct GUI {
renderer: Renderer,
draw_buf: Texture,
font: Font,
editor: Editor,
editor: Editor<GUILineFormatter>,
}
impl GUI {
@ -25,7 +27,7 @@ impl GUI {
let renderer = sdl2::render::Renderer::from_window(window, sdl2::render::RenderDriverIndex::Auto, sdl2::render::ACCELERATED).unwrap();
let draw_buf = renderer.create_texture(sdl2::pixels::PixelFormatFlag::RGBA8888, sdl2::render::TextureAccess::Target, 1, 1).unwrap();
let mut editor = Editor::new();
let mut editor = Editor::new(GUILineFormatter::new(4));
editor.update_dim(renderer.get_output_size().unwrap().1 as usize, renderer.get_output_size().unwrap().0 as usize);
GUI {
@ -37,7 +39,7 @@ impl GUI {
}
pub fn new_from_editor(ed: Editor) -> GUI {
pub fn new_from_editor(ed: Editor<GUILineFormatter>) -> GUI {
let font = Font::new_default(14);
// Get the window and renderer for sdl

View File

@ -10,7 +10,9 @@ use std::path::Path;
use docopt::Docopt;
use editor::Editor;
use term_ui::TermUI;
use term_ui::formatter::ConsoleLineFormatter;
use gui::GUI;
use gui::formatter::GUILineFormatter;
mod string_utils;
mod buffer;
@ -49,16 +51,17 @@ fn main() {
// Get command-line arguments
let args: Args = Docopt::new(USAGE).and_then(|d| d.decode()).unwrap_or_else(|e| e.exit());
// Load file, if specified
let editor = if let Option::Some(s) = args.arg_file {
Editor::new_from_file(&Path::new(s.as_slice()))
}
else {
Editor::new()
};
// Initialize and start UI
if args.flag_gui {
// Load file, if specified
let editor = if let Option::Some(s) = args.arg_file {
Editor::new_from_file(GUILineFormatter::new(4), &Path::new(s.as_slice()))
}
else {
Editor::new(GUILineFormatter::new(4))
};
// GUI
sdl2::init(sdl2::INIT_VIDEO);
let mut ui = GUI::new_from_editor(editor);
@ -66,6 +69,14 @@ fn main() {
sdl2::quit();
}
else {
// Load file, if specified
let editor = if let Option::Some(s) = args.arg_file {
Editor::new_from_file(ConsoleLineFormatter::new(4), &Path::new(s.as_slice()))
}
else {
Editor::new(ConsoleLineFormatter::new(4))
};
// Console UI
let mut ui = TermUI::new_from_editor(editor);
ui.main_ui_loop();

View File

@ -8,6 +8,7 @@ use std::time::duration::Duration;
use string_utils::{is_line_ending};
use buffer::line::{line_ending_to_str, LineEnding};
use buffer::line_formatter::{LineFormatter, RoundingBehavior};
use self::formatter::ConsoleLineFormatter;
pub mod formatter;
@ -34,7 +35,7 @@ const K_CTRL_Z: u16 = 26;
pub struct TermUI {
rb: rustbox::RustBox,
editor: Editor,
editor: Editor<ConsoleLineFormatter>,
width: usize,
height: usize,
}
@ -45,7 +46,7 @@ impl TermUI {
let rb = rustbox::RustBox::init(&[Some(rustbox::InitOption::BufferStderr)]).unwrap();
let w = rb.width();
let h = rb.height();
let mut editor = Editor::new();
let mut editor = Editor::new(ConsoleLineFormatter::new(4));
editor.update_dim(h-1, w);
TermUI {
@ -56,7 +57,7 @@ impl TermUI {
}
}
pub fn new_from_editor(ed: Editor) -> TermUI {
pub fn new_from_editor(ed: Editor<ConsoleLineFormatter>) -> TermUI {
let rb = rustbox::RustBox::init(&[Some(rustbox::InitOption::BufferStderr)]).unwrap();
let w = rb.width();
let h = rb.height();
@ -288,7 +289,7 @@ impl TermUI {
}
fn draw_editor(&self, editor: &Editor, c1: (usize, usize), c2: (usize, usize)) {
fn draw_editor(&self, editor: &Editor<ConsoleLineFormatter>, c1: (usize, usize), c2: (usize, usize)) {
let foreground = Color::Black;
let background = Color::Cyan;
@ -336,7 +337,7 @@ impl TermUI {
}
fn draw_editor_text(&self, editor: &Editor, c1: (usize, usize), c2: (usize, usize)) {
fn draw_editor_text(&self, editor: &Editor<ConsoleLineFormatter>, c1: (usize, usize), c2: (usize, usize)) {
// Calculate all the starting info
let editor_corner_index = editor.buffer.v2d_to_index(editor.view_pos, (RoundingBehavior::Floor, RoundingBehavior::Floor));
let (starting_line, _) = editor.buffer.index_to_line_col(editor_corner_index);

13
todo.md
View File

@ -1,14 +1,15 @@
- Clean separation of text buffer from rest of code:
- Create a "BufferFormatter" trait that informs Buffer how its text
should be formatted. Such formatters then determine how the
text buffer graphemes map to a 2d display (e.g. how tabs are handles,
glyph size, spacing, line wrapping, etc.). Buffers then use
BufferFormatters for maintaing information necessary for
1d <-> 2d operations.
//- Create a "BufferFormatter" trait that informs Buffer how its text
// should be formatted. Such formatters then determine how the
// text buffer graphemes map to a 2d display (e.g. how tabs are handles,
// glyph size, spacing, line wrapping, etc.). Buffers then use
// BufferFormatters for maintaing information necessary for
// 1d <-> 2d operations.
- Create BufferFormatters for console and for freetype, including
preferences for tab width (specified in spaces) and line wrapping.
The freetype formatter should not reference SDL at all, and should
have only the freetype library itself as a dependency.
- Handle tab settings properly after the refactor
- Possibly split the text buffer out into its own library...? Would
likely be useful to other people as well, and would encourage me to
keep the API clean.