Skip to content

Commit

Permalink
updating tests, adding scroll for title popup, refactoring
Browse files Browse the repository at this point in the history
  • Loading branch information
jooaf committed Jan 20, 2025
1 parent 5705e8f commit 894d584
Show file tree
Hide file tree
Showing 6 changed files with 103 additions and 31 deletions.
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,4 @@ pub fn get_save_backup_file_path() -> PathBuf {
pub const ORANGE: ratatui::style::Color = ratatui::style::Color::Rgb(255, 165, 0);
pub const DAEMONIZE_ARG: &str = "__thoth_copy_daemonize";
pub const MIN_TEXTAREA_HEIGHT: usize = 3;
pub const BORDER_PADDING_SIZE: usize = 2;
23 changes: 13 additions & 10 deletions src/scrollable_textarea.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use std::{
rc::Rc,
};

use crate::{EditorClipboard, MIN_TEXTAREA_HEIGHT};
use crate::{EditorClipboard, BORDER_PADDING_SIZE, MIN_TEXTAREA_HEIGHT};
use crate::{MarkdownRenderer, ORANGE};
use anyhow;
use anyhow::Result;
Expand Down Expand Up @@ -173,7 +173,7 @@ impl ScrollableTextArea {

pub fn move_focus(&mut self, direction: isize) {
let new_index = self.focused_index as isize + direction;
if new_index > (self.textareas.len()) as isize {
if new_index >= (self.textareas.len()) as isize {
self.focused_index = 0;
} else if new_index < 0 {
self.focused_index = self.textareas.len() - 1;
Expand All @@ -190,10 +190,10 @@ impl ScrollableTextArea {
let mut height_sum = 0;
for i in self.scroll..=self.focused_index {
let textarea_height =
self.textareas[i].lines().len().max(MIN_TEXTAREA_HEIGHT) as u16 + 2;
self.textareas[i].lines().len().max(MIN_TEXTAREA_HEIGHT) + BORDER_PADDING_SIZE;
height_sum += textarea_height;

if height_sum > self.viewport_height {
if height_sum > self.viewport_height as usize {
self.scroll = i;
break;
}
Expand All @@ -210,7 +210,7 @@ impl ScrollableTextArea {
pub fn calculate_height_to_focused(&self) -> u16 {
self.textareas[self.scroll..=self.focused_index]
.iter()
.map(|ta| ta.lines().len().max(MIN_TEXTAREA_HEIGHT) as u16 + 2)
.map(|ta| (ta.lines().len().max(MIN_TEXTAREA_HEIGHT) + BORDER_PADDING_SIZE) as u16)
.sum()
}

Expand Down Expand Up @@ -283,7 +283,7 @@ impl ScrollableTextArea {
break;
}

let content_height = textarea.lines().len() as u16 + 2;
let content_height = (textarea.lines().len() + BORDER_PADDING_SIZE) as u16;
let is_focused = i == self.focused_index;
let is_editing = is_focused && self.edit_mode;

Expand Down Expand Up @@ -344,7 +344,7 @@ impl ScrollableTextArea {
let rendered_markdown = self.markdown_cache.borrow_mut().get_or_render(
&content,
title,
f.size().width as usize - 2,
f.size().width as usize - BORDER_PADDING_SIZE,
)?;
let paragraph = Paragraph::new(rendered_markdown)
.block(block)
Expand Down Expand Up @@ -408,7 +408,7 @@ impl ScrollableTextArea {
let rendered_markdown = self.markdown_cache.borrow_mut().get_or_render(
&content,
title,
f.size().width as usize - 2,
f.size().width as usize - BORDER_PADDING_SIZE,
)?;

let paragraph = Paragraph::new(rendered_markdown)
Expand Down Expand Up @@ -452,11 +452,14 @@ mod tests {
fn test_move_focus() {
let mut sta = create_test_textarea();
sta.add_textarea(TextArea::default(), "Test1".to_string());
assert_eq!(sta.focused_index, 0);
sta.add_textarea(TextArea::default(), "Test2".to_string());
sta.move_focus(1);

assert_eq!(sta.focused_index, 1);
sta.move_focus(-1);
sta.move_focus(1);
assert_eq!(sta.focused_index, 0);
sta.move_focus(-1);
assert_eq!(sta.focused_index, 1);
}

#[test]
Expand Down
58 changes: 58 additions & 0 deletions src/title_select_popup.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ pub struct TitleSelectPopup {
pub titles: Vec<String>,
pub selected_index: usize,
pub visible: bool,
pub scroll_offset: usize,
}

impl TitleSelectPopup {
Expand All @@ -10,6 +11,47 @@ impl TitleSelectPopup {
titles: Vec::new(),
selected_index: 0,
visible: false,
scroll_offset: 0,
}
}

pub fn move_selection_up(&mut self, visible_items: usize) {
if self.titles.is_empty() {
return;
}

if self.selected_index > 0 {
self.selected_index -= 1;
} else {
self.selected_index = self.titles.len() - 1;
}

if self.selected_index < self.scroll_offset {
self.scroll_offset = self.selected_index;
}
if self.selected_index == self.titles.len() - 1 {
self.scroll_offset = self.titles.len().saturating_sub(visible_items);
}
}

pub fn move_selection_down(&mut self, visible_items: usize) {
if self.titles.is_empty() {
return;
}

if self.selected_index < self.titles.len() - 1 {
self.selected_index += 1;
} else {
self.selected_index = 0;
self.scroll_offset = 0;
}

let max_scroll = self.titles.len().saturating_sub(visible_items);
if self.selected_index >= self.scroll_offset + visible_items {
self.scroll_offset = (self.selected_index + 1).saturating_sub(visible_items);
if self.scroll_offset > max_scroll {
self.scroll_offset = max_scroll;
}
}
}
}
Expand Down Expand Up @@ -40,4 +82,20 @@ mod tests {
assert_eq!(popup.titles[0], "Title1");
assert_eq!(popup.titles[1], "Title2");
}

#[test]
fn test_wrap_around_selection() {
let mut popup = TitleSelectPopup::new();
popup.titles = vec!["1".to_string(), "2".to_string(), "3".to_string()];

popup.selected_index = 0;
popup.move_selection_up(2);
assert_eq!(popup.selected_index, 2);
assert_eq!(popup.scroll_offset, 1);

popup.selected_index = 2;
popup.move_selection_down(2);
assert_eq!(popup.selected_index, 0);
assert_eq!(popup.scroll_offset, 0);
}
}
25 changes: 16 additions & 9 deletions src/ui.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::{TitlePopup, TitleSelectPopup, ORANGE};
use crate::{TitlePopup, TitleSelectPopup, BORDER_PADDING_SIZE, ORANGE};
use ratatui::{
layout::{Constraint, Direction, Layout, Rect},
style::{Color, Modifier, Style},
Expand Down Expand Up @@ -67,7 +67,7 @@ pub fn render_edit_commands_popup(f: &mut Frame) {
Cell::from("MAPPINGS").style(Style::default().fg(ORANGE).add_modifier(Modifier::BOLD)),
Cell::from("DESCRIPTIONS").style(Style::default().fg(ORANGE).add_modifier(Modifier::BOLD)),
])
.height(2);
.height(BORDER_PADDING_SIZE as u16);

let commands: Vec<Row> = vec![
Row::new(vec![
Expand Down Expand Up @@ -105,7 +105,7 @@ pub fn render_edit_commands_popup(f: &mut Frame) {
.header(header)
.block(block)
.widths([Constraint::Percentage(30), Constraint::Percentage(70)])
.column_spacing(2)
.column_spacing(BORDER_PADDING_SIZE as u16)
.highlight_style(Style::default().fg(Color::Yellow))
.highlight_symbol(">> ");

Expand Down Expand Up @@ -149,7 +149,7 @@ pub fn render_header(f: &mut Frame, area: Rect, is_edit_mode: bool) {

let thoth_width = thoth.width();
let separator_width = separator.width();
let reserved_width = thoth_width + 2; // 2 extra spaces for padding
let reserved_width = thoth_width + BORDER_PADDING_SIZE; // 2 extra spaces for padding

let mut display_commands = Vec::new();
let mut current_width = 0;
Expand All @@ -166,7 +166,7 @@ pub fn render_header(f: &mut Frame, area: Rect, is_edit_mode: bool) {
let command_string = display_commands.join(separator);
let command_width = command_string.width();

let padding = " ".repeat(available_width - command_width - thoth_width - 2);
let padding = " ".repeat(available_width - command_width - thoth_width - BORDER_PADDING_SIZE);

let header = Line::from(vec![
Span::styled(command_string, Style::default().fg(ORANGE)),
Expand Down Expand Up @@ -200,12 +200,18 @@ pub fn render_title_select_popup(f: &mut Frame, popup: &TitleSelectPopup) {
let area = centered_rect(80, 80, f.size());
f.render_widget(ratatui::widgets::Clear, area);

let items: Vec<Line> = popup
.titles
let visible_height = area.height.saturating_sub(BORDER_PADDING_SIZE as u16) as usize;

let start_idx = popup.scroll_offset;
let end_idx = (popup.scroll_offset + visible_height).min(popup.titles.len());
let visible_titles = &popup.titles[start_idx..end_idx];

let items: Vec<Line> = visible_titles
.iter()
.enumerate()
.map(|(i, title)| {
if i == popup.selected_index {
let absolute_idx = i + popup.scroll_offset;
if absolute_idx == popup.selected_index {
Line::from(vec![Span::styled(
format!("> {}", title),
Style::default().fg(Color::Yellow),
Expand All @@ -219,7 +225,7 @@ pub fn render_title_select_popup(f: &mut Frame, popup: &TitleSelectPopup) {
let block = Block::default()
.borders(Borders::ALL)
.border_style(Style::default().fg(ORANGE))
.title("Select Title");
.title(format!("Select Title"));

let paragraph = Paragraph::new(items)
.block(block)
Expand Down Expand Up @@ -374,6 +380,7 @@ mod tests {
titles: vec!["Title1".to_string(), "Title2".to_string()],
selected_index: 0,
visible: true,
scroll_offset: 0,
};

terminal
Expand Down
23 changes: 12 additions & 11 deletions src/ui_handler.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::{get_save_backup_file_path, EditorClipboard};
use crate::{get_save_backup_file_path, EditorClipboard, BORDER_PADDING_SIZE};
use anyhow::{bail, Result};
use crossterm::{
event::{self, DisableMouseCapture, EnableMouseCapture, KeyCode, KeyModifiers},
Expand Down Expand Up @@ -238,6 +238,15 @@ fn handle_title_popup_input(state: &mut UIState, key: event::KeyEvent) -> Result
}

fn handle_title_select_popup_input(state: &mut UIState, key: event::KeyEvent) -> Result<bool> {
// Subtract 2 from viewport height to account for the top and bottom borders
// drawn by Block::default().borders(Borders::ALL) in ui.rs render_title_select_popup.
// The borders are rendered using unicode box-drawing characters:
// top border : ┌───┐
// bottom border : └───┘
// let visible_items = state.scrollable_textarea.viewport_height.saturating_sub(2) as usize;
let visible_items = (state.scrollable_textarea.viewport_height as f32 * 0.8).floor() as usize
- BORDER_PADDING_SIZE;

match key.code {
KeyCode::Enter => {
state
Expand All @@ -250,18 +259,10 @@ fn handle_title_select_popup_input(state: &mut UIState, key: event::KeyEvent) ->
state.edit_commands_popup.visible = false;
}
KeyCode::Up => {
if state.title_select_popup.selected_index > 0 {
state.title_select_popup.selected_index -= 1;
} else {
state.title_select_popup.selected_index = state.title_select_popup.titles.len() - 1
}
state.title_select_popup.move_selection_up(visible_items);
}
KeyCode::Down => {
if state.title_select_popup.selected_index < state.title_select_popup.titles.len() - 1 {
state.title_select_popup.selected_index += 1;
} else {
state.title_select_popup.selected_index = 0;
}
state.title_select_popup.move_selection_down(visible_items);
}
_ => {}
}
Expand Down
4 changes: 3 additions & 1 deletion tests/integration_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,10 @@ fn test_full_application_flow() {

// Test focus movement
sta.move_focus(1);
assert_eq!(sta.focused_index, 1);
assert_eq!(sta.focused_index, 0);
sta.move_focus(-1);
assert_eq!(sta.focused_index, 1);
sta.move_focus(1);
assert_eq!(sta.focused_index, 0);

// Test title change
Expand Down

0 comments on commit 894d584

Please sign in to comment.