From accf188e7c84ee627d5e4cfe12c141e929699830 Mon Sep 17 00:00:00 2001
From: Leo Tenenbaum <pommicket@gmail.com>
Date: Sun, 24 Jan 2021 15:46:08 -0500
Subject: scroll file selector when selected goes offscreen

---
 buffer.c  | 17 +++++++++--------
 command.c |  4 ++--
 config.c  |  6 ++++++
 ted.cfg   |  2 ++
 ted.h     |  1 +
 ui.c      | 36 +++++++++++++++++++++++++++---------
 6 files changed, 47 insertions(+), 19 deletions(-)

diff --git a/buffer.c b/buffer.c
index db47e2c..cc97e19 100644
--- a/buffer.c
+++ b/buffer.c
@@ -150,7 +150,7 @@ static inline Font *buffer_font(TextBuffer *buffer) {
 }
 
 // Get the settings used for this buffer.
-static inline Settings *buffer_settings(TextBuffer *buffer) {
+static inline Settings const *buffer_settings(TextBuffer *buffer) {
 	return &buffer->ted->settings;
 }
 
@@ -845,24 +845,25 @@ static bool buffer_clip_rect(TextBuffer *buffer, Rect *r) {
 
 // if the cursor is offscreen, this will scroll to make it onscreen.
 static void buffer_scroll_to_cursor(TextBuffer *buffer) {
+	Settings const *settings = buffer_settings(buffer);
 	i64 cursor_line = buffer->cursor_pos.line;
 	i64 cursor_col  = buffer_index_to_column(buffer, (u32)cursor_line, buffer->cursor_pos.index);
 	i64 display_lines = buffer_display_lines(buffer);
 	i64 display_cols = buffer_display_cols(buffer);
 	double scroll_x = buffer->scroll_x, scroll_y = buffer->scroll_y;
-	i64 scroll_padding = 5;
+	i64 scrolloff = settings->scrolloff;
 
 	// scroll left if cursor is off screen in that direction
-	double max_scroll_x = (double)(cursor_col - scroll_padding);
+	double max_scroll_x = (double)(cursor_col - scrolloff);
 	scroll_x = mind(scroll_x, max_scroll_x);
 	// scroll right
-	double min_scroll_x = (double)(cursor_col - display_cols + scroll_padding);
+	double min_scroll_x = (double)(cursor_col - display_cols + scrolloff);
 	scroll_x = maxd(scroll_x, min_scroll_x);
 	// scroll up
-	double max_scroll_y = (double)(cursor_line - scroll_padding);
+	double max_scroll_y = (double)(cursor_line - scrolloff);
 	scroll_y = mind(scroll_y, max_scroll_y);
 	// scroll down
-	double min_scroll_y = (double)(cursor_line - display_lines + scroll_padding);
+	double min_scroll_y = (double)(cursor_line - display_lines + scrolloff);
 	scroll_y = maxd(scroll_y, min_scroll_y);
 
 	buffer->scroll_x = scroll_x;
@@ -1718,8 +1719,8 @@ void buffer_render(TextBuffer *buffer, float x1, float y1, float x2, float y2) {
 	float header_height = char_height;
 
 	Ted *ted = buffer->ted;
-	Settings *settings = buffer_settings(buffer);
-	u32 *colors = settings->colors;
+	Settings const *settings = buffer_settings(buffer);
+	u32 const *colors = settings->colors;
 
 	float border_thickness = settings->border_thickness;
 
diff --git a/command.c b/command.c
index 40636b6..74a624a 100644
--- a/command.c
+++ b/command.c
@@ -38,11 +38,11 @@ void command_execute(Ted *ted, Command c, i64 argument) {
 		if (buffer) buffer_cursor_move_right(buffer, argument);
 		break;
 	case CMD_UP:
-		if (file_selector->open) file_selector_up(file_selector, argument);
+		if (file_selector->open) file_selector_up(ted, file_selector, argument);
 		else if (buffer) buffer_cursor_move_up(buffer, argument);
 		break;
 	case CMD_DOWN:
-		if (file_selector->open) file_selector_down(file_selector, argument);
+		if (file_selector->open) file_selector_down(ted, file_selector, argument);
 		else if (buffer) buffer_cursor_move_down(buffer, argument);
 		break;
 	case CMD_SELECT_LEFT:
diff --git a/config.c b/config.c
index 9a2bed8..39a5d80 100644
--- a/config.c
+++ b/config.c
@@ -312,6 +312,12 @@ void config_read(Ted *ted, char const *filename) {
 										} else {
 											config_err(cfg, "Invalid padding: %s.", value);
 										}
+									} else if (streq(key, "scrolloff")) {
+										if (is_integer && integer >= 1 && integer < 100) {
+											settings->scrolloff = (u8)integer;
+										} else {
+											config_err(cfg, "Invalid scrolloff: %s.", value);
+										}
 									} else {
 										config_err(cfg, "Unrecognized core setting: %s.", key);
 									}
diff --git a/ted.cfg b/ted.cfg
index a28424e..623e914 100644
--- a/ted.cfg
+++ b/ted.cfg
@@ -6,6 +6,8 @@ cursor-width = 1
 # set -off to 0 to disable blinking
 cursor-blink-time-on = 0.5
 cursor-blink-time-off = 0.3
+# amount of scroll "padding" (minimum number of lines below the cursor will the bottom of the screen be)
+scrolloff = 5
 # if you do a bunch of typing, then undo, it will generally
 # undo the past this many seconds of editing.
 undo-save-time = 6
diff --git a/ted.h b/ted.h
index f1374b3..16fb131 100644
--- a/ted.h
+++ b/ted.h
@@ -13,6 +13,7 @@ typedef struct {
 	u8 undo_save_time;
 	u8 border_thickness;
 	u8 padding;
+	u8 scrolloff;
 } Settings;
 
 #define SCANCODE_COUNT 0x120 // SDL scancodes should be less than this value.
diff --git a/ui.c b/ui.c
index 8184ecd..8f90bbb 100644
--- a/ui.c
+++ b/ui.c
@@ -12,6 +12,28 @@ static float file_selector_entries_start_y(Ted const *ted, FileSelector const *f
 		+ char_height * 1.5f; // make room for line buffer
 }
 
+// number of file entries that can be displayed on the screen
+static u32 file_selector_n_display_entries(Ted const *ted, FileSelector const *fs) {
+	float char_height = text_font_char_height(ted->font);
+	float entries_h = rect_y2(fs->bounds) - file_selector_entries_start_y(ted, fs);
+	return (u32)(entries_h / char_height);
+}
+
+static void file_selector_clamp_scroll(Ted const *ted, FileSelector *fs) {
+	float max_scroll = (float)fs->n_entries - (float)file_selector_n_display_entries(ted, fs);
+	if (max_scroll < 0) max_scroll = 0;
+	fs->scroll = clampf(fs->scroll, 0, max_scroll);
+}
+
+static void file_selector_scroll_to_selected(Ted const *ted, FileSelector *fs) {
+	u32 n_display_entries = file_selector_n_display_entries(ted, fs);
+	float scrolloff = ted->settings.scrolloff;
+	float min_scroll = (float)fs->selected - ((float)n_display_entries - scrolloff);
+	float max_scroll = (float)fs->selected - scrolloff;
+	fs->scroll = clampf(fs->scroll, min_scroll, max_scroll);
+	file_selector_clamp_scroll(ted, fs);
+}
+
 // where is the ith entry in the file selector on the screen?
 // returns false if it's completely offscreen
 static bool file_selector_entry_pos(Ted const *ted, FileSelector const *fs,
@@ -41,16 +63,18 @@ static void file_selector_free(FileSelector *fs) {
 	memset(fs, 0, sizeof *fs);
 }
 
-static void file_selector_up(FileSelector *fs, i64 n) {
+static void file_selector_up(Ted const *ted, FileSelector *fs, i64 n) {
 	i64 selected = fs->selected - n;
 	selected = mod_i64(selected, fs->n_entries);
 	fs->selected = (u32)selected;
+	file_selector_scroll_to_selected(ted, fs);
 }
 
-static void file_selector_down(FileSelector *fs, i64 n) {
+static void file_selector_down(Ted const *ted, FileSelector *fs, i64 n) {
 	i64 selected = fs->selected + n;
 	selected = mod_i64(selected, fs->n_entries);
 	fs->selected = (u32)selected;
+	file_selector_scroll_to_selected(ted, fs);
 }
 
 static int qsort_file_entry_cmp(void const *av, void const *bv, void *search_termv) {
@@ -351,13 +375,7 @@ static char *file_selector_update(Ted *ted, FileSelector *fs) {
 	// apply scroll
 	float scroll_speed = 2.5f;
 	fs->scroll += scroll_speed * (float)ted->scroll_total_y;
-	// clamp scroll
-	float char_height = text_font_char_height(ted->font);
-	float entries_h = rect_y2(fs->bounds) - file_selector_entries_start_y(ted, fs);
-	u32 n_display_entries = (u32)(entries_h / char_height);
-	float max_scroll = (float)fs->n_entries - (float)n_display_entries;
-	if (max_scroll < 0) max_scroll = 0;
-	fs->scroll = clampf(fs->scroll, 0, max_scroll);
+	file_selector_clamp_scroll(ted, fs);
 	
 	free(search_term);
 	return NULL;
-- 
cgit v1.2.3