use std::rc::Rc;
use crate::render::Canvas;
use crate::types::Rect;
use super::hit_test::byte_offset_in_line;
use super::metrics::MultilineMetrics;
use super::scroll::{ TextScroll, Window };
use super::tabs::line_x;
use super::wrapping::{ compute_visual_lines, VisualLine };
use super::TextFont;
fn current_visual_line(
canvas: &Canvas,
font: &TextFont,
m: &MultilineMetrics,
rect: Rect,
value: &str,
cursor: usize,
) -> Option<( Rc<Vec<VisualLine>>, usize )>
{
let visual_lines = compute_visual_lines( canvas, font, value, m.inner_width( rect ), m.font_size );
if visual_lines.is_empty() { return None; }
let safe_cursor = cursor.min( value.len() );
let idx = visual_lines.iter()
.position( |vl| safe_cursor >= vl.start && safe_cursor <= vl.end )
.unwrap_or( visual_lines.len() - 1 );
Some( ( visual_lines, idx ) )
}
pub( crate ) fn cursor_visual_up(
canvas: &Canvas,
rect: Rect,
value: &str,
cursor: usize,
font: &TextFont,
) -> Option<usize>
{
let m = MultilineMetrics::resolve( canvas, font.size );
let ( lines, idx ) = current_visual_line( canvas, font, &m, rect, value, cursor )?;
if idx == 0 { return None; }
let cur = lines[ idx ];
let safe = cursor.min( value.len() );
let target_x = line_x( canvas, font, value, cur.start, safe, m.font_size );
let prev = lines[ idx - 1 ];
Some( byte_offset_in_line( canvas, font, value, prev.start, prev.end, target_x, false, true, m.font_size ) )
}
pub( crate ) fn cursor_visual_down(
canvas: &Canvas,
rect: Rect,
value: &str,
cursor: usize,
font: &TextFont,
) -> Option<usize>
{
let m = MultilineMetrics::resolve( canvas, font.size );
let ( lines, idx ) = current_visual_line( canvas, font, &m, rect, value, cursor )?;
if idx + 1 >= lines.len() { return None; }
let cur = lines[ idx ];
let safe = cursor.min( value.len() );
let target_x = line_x( canvas, font, value, cur.start, safe, m.font_size );
let next = lines[ idx + 1 ];
Some( byte_offset_in_line( canvas, font, value, next.start, next.end, target_x, false, true, m.font_size ) )
}
pub( crate ) fn cursor_visual_home(
canvas: &Canvas,
rect: Rect,
value: &str,
cursor: usize,
font: &TextFont,
) -> usize
{
let m = MultilineMetrics::resolve( canvas, font.size );
current_visual_line( canvas, font, &m, rect, value, cursor )
.map( |( lines, idx )| lines[ idx ].start )
.unwrap_or( 0 )
}
pub( crate ) fn cursor_visual_end(
canvas: &Canvas,
rect: Rect,
value: &str,
cursor: usize,
font: &TextFont,
) -> usize
{
let m = MultilineMetrics::resolve( canvas, font.size );
current_visual_line( canvas, font, &m, rect, value, cursor )
.map( |( lines, idx )| lines[ idx ].end )
.unwrap_or( value.len() )
}
pub( crate ) fn cursor_visual_page(
canvas: &Canvas,
rect: Rect,
value: &str,
cursor: usize,
font: &TextFont,
scroll: TextScroll,
up: bool,
) -> ( usize, TextScroll )
{
let window = Window::shown( canvas, rect, value, font, cursor, scroll );
let lines = &window.lines;
let page = window.visible.saturating_sub( 1 ).max( 1 );
let cursor = cursor.min( value.len() );
let line = window.cursor_line;
let target = if up { line.saturating_sub( page ) } else { ( line + page ).min( lines.len().saturating_sub( 1 ) ) };
let moved = if target == line
{
if up { 0 } else { value.len() }
} else {
let font_size = window.metrics.font_size;
let x = line_x( canvas, font, value, lines[ line ].start, cursor, font_size );
let row = lines[ target ];
byte_offset_in_line( canvas, font, value, row.start, row.end, x, false, true, font_size )
};
let first = if up { window.first_line.saturating_sub( page ) } else { ( window.first_line + page ).min( window.max_first ) };
( moved, TextScroll { first_line: first, cursor_pos: moved, remainder: 0.0 } )
}