very basic scroll
This commit is contained in:
41
AmazonQ.md
Normal file
41
AmazonQ.md
Normal file
@@ -0,0 +1,41 @@
|
||||
# Text Scrolling Implementation in Simplicitty
|
||||
|
||||
This document outlines the implementation of text scrolling in the Simplicitty terminal emulator project.
|
||||
|
||||
## Overview
|
||||
|
||||
The implementation adds the ability to scroll through text content when it doesn't fit completely in the window. The scrolling is implemented on a line-by-line basis, allowing users to navigate through the content using keyboard shortcuts.
|
||||
|
||||
## Key Components
|
||||
|
||||
1. **Scroll State Management**:
|
||||
- Added `scroll_offset` to track the current scroll position (in visual lines)
|
||||
- Added `max_scroll_offset` to limit scrolling to available content
|
||||
|
||||
2. **Viewport Calculation**:
|
||||
- Modified `get_visible_line_range()` to consider the scroll offset when determining visible lines
|
||||
- Added `update_max_scroll_offset()` to recalculate the maximum scroll position based on content and viewport size
|
||||
|
||||
3. **Scrolling Methods**:
|
||||
- `scroll_up(lines)`: Move viewport up by specified number of lines
|
||||
- `scroll_down(lines)`: Move viewport down by specified number of lines
|
||||
- `page_up()`: Scroll up by one page (viewport height)
|
||||
- `page_down()`: Scroll down by one page (viewport height)
|
||||
|
||||
4. **Keyboard Controls**:
|
||||
- Ctrl+Up/Down: Scroll one line up/down
|
||||
- Page Up/Down: Scroll one page up/down
|
||||
- Ctrl+Home/End: Scroll to top/bottom of content
|
||||
|
||||
## Implementation Details
|
||||
|
||||
The scrolling implementation works by adjusting the `scroll_offset` value, which determines which visual lines are rendered in the viewport. The `get_visible_line_range()` function uses this offset to calculate which logical lines should be rendered.
|
||||
|
||||
When the window is resized or content changes, the `max_scroll_offset` is recalculated to ensure scrolling remains within valid bounds.
|
||||
|
||||
## Future Improvements
|
||||
|
||||
1. Add mouse wheel support for scrolling
|
||||
2. Implement smooth scrolling animations
|
||||
3. Add a visual scrollbar indicator
|
||||
4. Preserve horizontal scroll position when navigating vertically
|
||||
@@ -33,6 +33,7 @@ pub struct State {
|
||||
|
||||
impl State {
|
||||
pub fn resize(&mut self, new_size: PhysicalSize<u32>) {
|
||||
trace!("Resizing window");
|
||||
if new_size.width > 0 && new_size.height > 0 {
|
||||
self.size = new_size;
|
||||
self.config.width = new_size.width;
|
||||
@@ -44,6 +45,7 @@ impl State {
|
||||
}
|
||||
|
||||
pub fn render(&mut self) -> Result<(), SurfaceError> {
|
||||
trace!("Rendering requested");
|
||||
// Prepare with current configuration (which includes the current window size)
|
||||
self.text.prepare(&self.device, &self.queue, &self.config);
|
||||
|
||||
@@ -73,6 +75,7 @@ impl State {
|
||||
occlusion_query_set: None,
|
||||
});
|
||||
|
||||
trace!("Rendering...");
|
||||
self.text.render(&mut _render_pass);
|
||||
}
|
||||
|
||||
@@ -219,6 +222,7 @@ impl State {
|
||||
Key::Named(k) => {
|
||||
match k {
|
||||
NamedKey::Enter => {
|
||||
debug!("Receiver enter");
|
||||
self.text.handle_enter();
|
||||
}
|
||||
NamedKey::Backspace => {
|
||||
@@ -226,11 +230,8 @@ impl State {
|
||||
if self.dead_key_state.is_some() {
|
||||
self.dead_key_state = None;
|
||||
debug!("Dead key state cleared");
|
||||
} else {
|
||||
// Implement backspace functionality
|
||||
if self.text.backspace() {
|
||||
debug!("Backspace processed");
|
||||
}
|
||||
} else if self.text.backspace() {
|
||||
debug!("Backspace processed");
|
||||
}
|
||||
}
|
||||
NamedKey::Tab => {
|
||||
@@ -275,18 +276,21 @@ impl State {
|
||||
// Combine dead key with the current character if possible
|
||||
if let Some(combined) = self.combine_with_dead_key(dead_char, s) {
|
||||
for c in combined.chars() {
|
||||
debug!("Inserting character {c}");
|
||||
self.text.insert_char(c);
|
||||
}
|
||||
} else {
|
||||
// If combination not possible, insert both separately
|
||||
self.text.insert_char(dead_char);
|
||||
for c in s.chars() {
|
||||
debug!("Inserting character {c}");
|
||||
self.text.insert_char(c);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Normal character input
|
||||
for c in s.chars() {
|
||||
debug!("Inserting character {c}");
|
||||
self.text.insert_char(c);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use glyphon::{
|
||||
Buffer, BufferLine, Cache, Color, Cursor, FontSystem, LayoutCursor, Metrics, Resolution,
|
||||
Shaping, SwashCache, TextArea, TextAtlas, TextBounds, TextRenderer, Viewport, Wrap,
|
||||
Buffer, Cache, Color, Cursor, FontSystem, Metrics, Resolution, SwashCache, TextArea, TextAtlas,
|
||||
TextBounds, TextRenderer, Viewport, Wrap,
|
||||
};
|
||||
|
||||
pub use glyphon::cosmic_text::Motion;
|
||||
@@ -18,6 +18,18 @@ pub struct TerminalText {
|
||||
cursor: Cursor,
|
||||
}
|
||||
|
||||
// TODO
|
||||
// - need to update scroll when
|
||||
// - inserting into a line and it causes line wrap, creating a new visual line (currently the
|
||||
// scroll is only updated when creating a logical line or removing the last logical line)
|
||||
//
|
||||
// - need to keep track of the scroll vertical offset in two ways:
|
||||
// - first logical line displayed
|
||||
// - extra offset that covers possible wrapped lines before the first logical line
|
||||
//
|
||||
// - need to remove lines from the top of the buffer when the first logical line displayed is
|
||||
// bigger than max_scroll_offset
|
||||
|
||||
mod safe_casts;
|
||||
|
||||
use log::{debug, error, trace};
|
||||
@@ -118,8 +130,9 @@ impl TerminalText {
|
||||
let viewport_height = safe_casts::u32_to_f32_or_max(self.viewport.resolution().height);
|
||||
let line_height = self.buffer.metrics().line_height;
|
||||
|
||||
// Start from line 0 (no scrolling yet)
|
||||
let start_line = 0;
|
||||
let start_line = self.buffer.scroll().line;
|
||||
|
||||
trace!("Current start line: {start_line}");
|
||||
|
||||
// Calculate visible lines based on wrapped text
|
||||
let layout_iter = self.buffer.layout_runs();
|
||||
@@ -130,8 +143,7 @@ impl TerminalText {
|
||||
last_logical_line = run.line_i;
|
||||
|
||||
// If we've exceeded the viewport height, we can stop counting
|
||||
if (safe_casts::usize_to_f32_or_max(start_line + visual_line_count) * line_height)
|
||||
> viewport_height
|
||||
if (safe_casts::usize_to_f32_or_max(visual_line_count) * line_height) > viewport_height
|
||||
{
|
||||
break;
|
||||
}
|
||||
@@ -142,6 +154,8 @@ impl TerminalText {
|
||||
0
|
||||
} else {
|
||||
// Make sure we include at least the last logical line that has content in the viewport
|
||||
trace!("Last logical line: {last_logical_line}");
|
||||
trace!("Number of lines: {}", self.buffer.lines.len());
|
||||
(last_logical_line + 1).min(self.buffer.lines.len())
|
||||
};
|
||||
|
||||
@@ -155,27 +169,27 @@ impl TerminalText {
|
||||
for i in start_line..end_line {
|
||||
self.dirty_regions.push(self.get_line_bounds(i));
|
||||
}
|
||||
|
||||
// Also mark the cursor line as dirty to ensure it's rendered
|
||||
if self.cursor.line < self.buffer.lines.len() {
|
||||
self.dirty_regions
|
||||
.push(self.get_line_bounds(self.cursor.line));
|
||||
}
|
||||
}
|
||||
|
||||
fn merge_dirty_regions(&self) -> TextBounds {
|
||||
if self.dirty_regions.is_empty() {
|
||||
return TextBounds::default(); // Assuming TextBounds implements Default
|
||||
return TextBounds::default();
|
||||
}
|
||||
|
||||
self.dirty_regions
|
||||
.iter()
|
||||
.fold(TextBounds::default(), |a, b| TextBounds {
|
||||
self.dirty_regions.iter().fold(
|
||||
TextBounds {
|
||||
left: 0,
|
||||
top: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
},
|
||||
|a, b| TextBounds {
|
||||
left: a.left.min(b.left),
|
||||
top: a.top.min(b.top),
|
||||
right: a.right.max(b.right),
|
||||
bottom: a.bottom.max(b.bottom),
|
||||
})
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
pub fn prepare(&mut self, device: &Device, queue: &Queue, config: &SurfaceConfiguration) {
|
||||
@@ -259,6 +273,7 @@ impl TerminalText {
|
||||
|
||||
let mut text = this.buffer.lines[line].text().to_string().clone();
|
||||
let pos = this.cursor.index.min(text.len());
|
||||
trace!("Inserting char {c} in line {line}, position {pos}");
|
||||
text.insert(pos, c);
|
||||
|
||||
let ending = this.buffer.lines[line].ending();
|
||||
@@ -286,14 +301,34 @@ impl TerminalText {
|
||||
}
|
||||
|
||||
let pos = this.cursor.index.min(this.buffer.lines[line].text().len());
|
||||
trace!("Inserting newline in line {line}, position {pos}");
|
||||
let new_line = this.buffer.lines[line].split_off(pos);
|
||||
|
||||
// Create a new line with text after cursor
|
||||
this.buffer.lines.insert(line + 1, new_line);
|
||||
|
||||
// Update cursor position to beginning of next line
|
||||
this.cursor.line = line + 1;
|
||||
this.cursor.index = 0;
|
||||
// Create a new line with text after cursor
|
||||
this.buffer.lines.insert(this.cursor.line, new_line);
|
||||
|
||||
// Only adjust scroll if cursor would be outside visible area
|
||||
let mut scroll = this.buffer.scroll();
|
||||
let current_scroll_line =
|
||||
(scroll.vertical / this.buffer.metrics().line_height).floor() as usize;
|
||||
let max_visible_lines =
|
||||
(safe_casts::u32_to_f32_or_max(this.viewport.resolution().height)
|
||||
/ this.buffer.metrics().line_height)
|
||||
.floor() as usize;
|
||||
|
||||
// Only scroll if cursor would be below visible area
|
||||
if this.cursor.line >= current_scroll_line + max_visible_lines {
|
||||
scroll.vertical =
|
||||
safe_casts::usize_to_f32_or_max(this.cursor.line - max_visible_lines + 1)
|
||||
* this.buffer.metrics().line_height;
|
||||
trace!("adjusting scroll to keep cursor visible: {:?}", scroll);
|
||||
this.buffer.set_scroll(scroll);
|
||||
} else {
|
||||
trace!("keeping current scroll: {:?}", scroll);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -342,6 +377,12 @@ impl TerminalText {
|
||||
|
||||
ret = true;
|
||||
}
|
||||
|
||||
if line > this.buffer.lines.len() {
|
||||
let mut scroll = this.buffer.scroll();
|
||||
scroll.vertical -= 1.0 * this.buffer.metrics().line_height;
|
||||
this.buffer.set_scroll(scroll);
|
||||
}
|
||||
ret
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user