fixed some issues with text rendering
This commit is contained in:
@@ -2,7 +2,6 @@ use std::sync::Arc;
|
||||
use tokio::runtime::Runtime;
|
||||
use winit::dpi::LogicalSize;
|
||||
use winit::event::WindowEvent;
|
||||
use winit::keyboard::{Key, NamedKey};
|
||||
use winit::window::Window;
|
||||
|
||||
mod rendering;
|
||||
@@ -60,23 +59,7 @@ impl winit::application::ApplicationHandler for Application {
|
||||
is_synthetic: _,
|
||||
} => {
|
||||
if event.state.is_pressed() {
|
||||
match event.logical_key {
|
||||
Key::Named(k) => match k {
|
||||
NamedKey::Enter => {
|
||||
state.handle_enter();
|
||||
}
|
||||
_ => todo!(),
|
||||
},
|
||||
Key::Character(c) => {
|
||||
state.add_text(&event.text.unwrap());
|
||||
}
|
||||
Key::Dead(_) => {
|
||||
todo!()
|
||||
}
|
||||
Key::Unidentified(_) => {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
state.handle_keyboard_input(&event);
|
||||
}
|
||||
}
|
||||
WindowEvent::CloseRequested => event_loop.exit(),
|
||||
|
||||
@@ -6,6 +6,8 @@ use wgpu::{
|
||||
TextureViewDescriptor,
|
||||
};
|
||||
use winit::dpi::PhysicalSize;
|
||||
use winit::event::KeyEvent;
|
||||
use winit::keyboard::{Key, NamedKey};
|
||||
use winit::window::Window;
|
||||
|
||||
mod terminal_text;
|
||||
@@ -30,6 +32,7 @@ impl State {
|
||||
self.config.width = new_size.width;
|
||||
self.config.height = new_size.height;
|
||||
self.surface.configure(&self.device, &self.config);
|
||||
self.text.resize(new_size.width, new_size.height);
|
||||
self.window.request_redraw();
|
||||
}
|
||||
}
|
||||
@@ -187,15 +190,26 @@ impl State {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_text(&mut self, text: &str) {
|
||||
for c in text.chars() {
|
||||
self.text.insert_char(c);
|
||||
pub fn handle_keyboard_input(&mut self, event: &KeyEvent) {
|
||||
match &event.logical_key {
|
||||
Key::Named(k) => match k {
|
||||
NamedKey::Enter => {
|
||||
self.text.handle_enter();
|
||||
}
|
||||
_ => todo!(),
|
||||
},
|
||||
Key::Character(s) => {
|
||||
for c in s.chars() {
|
||||
self.text.insert_char(c);
|
||||
}
|
||||
}
|
||||
Key::Dead(_) => {
|
||||
todo!()
|
||||
}
|
||||
Key::Unidentified(_) => {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
self.window.request_redraw();
|
||||
}
|
||||
|
||||
pub fn handle_enter(&mut self) {
|
||||
self.text.handle_enter();
|
||||
self.window.request_redraw();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use glyphon::{
|
||||
Attrs, Buffer, BufferLine, Cache, Color, Cursor, Family, FontSystem, Metrics, Resolution,
|
||||
Shaping, SwashCache, TextArea, TextAtlas, TextBounds, TextRenderer, Viewport, Wrap,
|
||||
Buffer, BufferLine, Cache, Color, Cursor, FontSystem, Metrics, Resolution, Shaping, SwashCache,
|
||||
TextArea, TextAtlas, TextBounds, TextRenderer, Viewport, Wrap,
|
||||
};
|
||||
|
||||
use wgpu::{Device, MultisampleState, Queue, RenderPass, SurfaceConfiguration, TextureFormat};
|
||||
@@ -16,10 +16,11 @@ pub struct TerminalText {
|
||||
cursor: Cursor,
|
||||
}
|
||||
|
||||
use log::info;
|
||||
use log::{debug, trace};
|
||||
|
||||
impl TerminalText {
|
||||
pub fn new(device: &Device, queue: &Queue, surface_format: TextureFormat) -> TerminalText {
|
||||
debug!("Creating a new TerminalText object");
|
||||
let mut font_system = FontSystem::new();
|
||||
let swash_cache = SwashCache::new();
|
||||
|
||||
@@ -32,6 +33,7 @@ impl TerminalText {
|
||||
let mut buffer = Buffer::new(&mut font_system, Metrics::relative(13.0, 1.25));
|
||||
buffer.set_wrap(&mut font_system, Wrap::Glyph);
|
||||
|
||||
trace!("Loading system fonts");
|
||||
// Add a default font
|
||||
font_system.db_mut().load_system_fonts();
|
||||
|
||||
@@ -51,27 +53,42 @@ impl TerminalText {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_text(&mut self, text: &str) {
|
||||
// Clear the buffer and set new text
|
||||
self.buffer.set_text(
|
||||
&mut self.font_system,
|
||||
text,
|
||||
&Attrs::new().family(Family::Monospace),
|
||||
Shaping::Advanced,
|
||||
);
|
||||
self.buffer.set_wrap(&mut self.font_system, Wrap::Glyph);
|
||||
}
|
||||
|
||||
pub fn resize(&mut self, width: u32, height: u32) {
|
||||
// Update the buffer's wrapping based on the new width
|
||||
self.buffer.set_wrap(&mut self.font_system, Wrap::Glyph);
|
||||
trace!("Resizing window - Width: {width} Height: {height}");
|
||||
self.buffer.set_size(
|
||||
&mut self.font_system,
|
||||
Some(width as f32),
|
||||
Some(height as f32),
|
||||
);
|
||||
// Update the buffer's wrapping based on the new width
|
||||
self.buffer.set_wrap(&mut self.font_system, Wrap::Glyph);
|
||||
// Reshape the text with the new dimensions
|
||||
self.buffer.shape_until_scroll(&mut self.font_system, false);
|
||||
self.ensure_visible_text_rendered();
|
||||
}
|
||||
|
||||
fn merge_dirty_regions(&self) -> TextBounds {
|
||||
TextBounds {
|
||||
left: self
|
||||
.dirty_regions
|
||||
.iter()
|
||||
.min_by_key(|b| b.left)
|
||||
.unwrap()
|
||||
.left,
|
||||
top: self.dirty_regions.iter().min_by_key(|b| b.top).unwrap().top,
|
||||
right: self
|
||||
.dirty_regions
|
||||
.iter()
|
||||
.max_by_key(|b| b.right)
|
||||
.unwrap()
|
||||
.right,
|
||||
bottom: self
|
||||
.dirty_regions
|
||||
.iter()
|
||||
.max_by_key(|b| b.bottom)
|
||||
.unwrap()
|
||||
.bottom,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn prepare(&mut self, device: &Device, queue: &Queue, config: &SurfaceConfiguration) {
|
||||
@@ -84,33 +101,9 @@ impl TerminalText {
|
||||
},
|
||||
);
|
||||
|
||||
// Make sure text is properly wrapped for the current width
|
||||
self.resize(config.width, config.height);
|
||||
|
||||
if !self.dirty_regions.is_empty() {
|
||||
for region in &self.dirty_regions {
|
||||
self.renderer
|
||||
.prepare(
|
||||
device,
|
||||
queue,
|
||||
&mut self.font_system,
|
||||
&mut self.atlas,
|
||||
&self.viewport,
|
||||
[TextArea {
|
||||
buffer: &self.buffer,
|
||||
left: 0.0,
|
||||
top: 0.0,
|
||||
scale: 1.0,
|
||||
bounds: *region,
|
||||
default_color: Color::rgb(255, 255, 255),
|
||||
custom_glyphs: &[],
|
||||
}],
|
||||
&mut self.cache,
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
} else {
|
||||
// If no dirty regions, prepare the entire buffer (first render or after resize)
|
||||
let region = self.merge_dirty_regions();
|
||||
trace!("Preparing region {:?}", region);
|
||||
self.renderer
|
||||
.prepare(
|
||||
device,
|
||||
@@ -123,12 +116,7 @@ impl TerminalText {
|
||||
left: 0.0,
|
||||
top: 0.0,
|
||||
scale: 1.0,
|
||||
bounds: TextBounds {
|
||||
left: 0,
|
||||
top: 0,
|
||||
right: config.width as i32,
|
||||
bottom: config.height as i32,
|
||||
},
|
||||
bounds: region,
|
||||
default_color: Color::rgb(255, 255, 255),
|
||||
custom_glyphs: &[],
|
||||
}],
|
||||
@@ -157,47 +145,21 @@ impl TerminalText {
|
||||
// TODO
|
||||
return;
|
||||
}
|
||||
info!("line nr: {line}");
|
||||
|
||||
let current_line = self.buffer.lines[line].text().to_string();
|
||||
let pos = self.cursor.index.min(current_line.len());
|
||||
|
||||
info!("cursor position: {pos}");
|
||||
|
||||
info!("Line text: {current_line}");
|
||||
|
||||
let mut new_text = current_line.clone();
|
||||
new_text.insert(pos, c);
|
||||
|
||||
let line_bounds = self.get_line_bounds(line);
|
||||
self.dirty_regions.push(line_bounds);
|
||||
let mut text = self.buffer.lines[line].text().to_string().clone();
|
||||
let pos = self.cursor.index.min(text.len());
|
||||
text.insert(pos, c);
|
||||
|
||||
let ending = self.buffer.lines[line].ending();
|
||||
let attrs = self.buffer.lines[line].attrs_list().clone();
|
||||
|
||||
// Update the buffer with the new text
|
||||
self.buffer.lines[line].set_text(&new_text, ending, attrs);
|
||||
|
||||
self.buffer.lines[line].set_text(&text, ending, attrs);
|
||||
// Update cursor position
|
||||
self.cursor.index = pos + 1;
|
||||
|
||||
self.buffer.shape_until_scroll(&mut self.font_system, false);
|
||||
}
|
||||
|
||||
fn get_line_bounds(&self, line: usize) -> TextBounds {
|
||||
// Calculate the bounds for a specific line
|
||||
// This is a simplified version - you'll need to implement this based on your text layout
|
||||
TextBounds {
|
||||
left: 0,
|
||||
top: (line * self.get_line_height()) as i32,
|
||||
right: self.viewport.resolution().width as i32,
|
||||
bottom: ((line + 1) * self.get_line_height()) as i32,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_line_height(&self) -> usize {
|
||||
// Get the line height based on your font metrics
|
||||
self.buffer.metrics().line_height.ceil() as usize
|
||||
self.ensure_visible_text_rendered();
|
||||
}
|
||||
|
||||
pub fn handle_enter(&mut self) {
|
||||
@@ -223,53 +185,119 @@ impl TerminalText {
|
||||
|
||||
// Create a new line with text after cursor
|
||||
let new_line = BufferLine::new(text_after, ending, attrs, Shaping::Advanced);
|
||||
|
||||
// Insert the new line after the current one
|
||||
self.buffer.lines.insert(line + 1, new_line);
|
||||
|
||||
self.mark_all_dirty();
|
||||
|
||||
// Update cursor position to beginning of next line
|
||||
self.cursor.line = line + 1;
|
||||
self.cursor.index = 0;
|
||||
|
||||
// Reshape the buffer
|
||||
self.buffer.shape_until_scroll(&mut self.font_system, false);
|
||||
self.ensure_visible_text_rendered();
|
||||
}
|
||||
|
||||
pub fn mark_all_dirty(&mut self) {
|
||||
// Create a single region covering the entire viewport
|
||||
let full_bounds = TextBounds {
|
||||
left: 0,
|
||||
top: 0,
|
||||
right: self.viewport.resolution().width as i32,
|
||||
bottom: self.viewport.resolution().height as i32,
|
||||
fn get_line_bounds(&self, line: usize) -> TextBounds {
|
||||
// Calculate the bounds for a specific line
|
||||
|
||||
let line_height = self.buffer.metrics().line_height;
|
||||
let viewport_width = self.viewport.resolution().width;
|
||||
|
||||
// Calculate floating point values first
|
||||
let top_f32 = (line as f32 * line_height).floor();
|
||||
let bottom_f32 = ((line as f32 + 1.0) * line_height).ceil();
|
||||
|
||||
// Safe conversions with overflow checks
|
||||
let top = if top_f32 > i32::MAX as f32 {
|
||||
i32::MAX
|
||||
} else if top_f32 < i32::MIN as f32 {
|
||||
i32::MIN
|
||||
} else {
|
||||
top_f32 as i32
|
||||
};
|
||||
self.dirty_regions = vec![full_bounds];
|
||||
}
|
||||
|
||||
pub fn move_cursor_left(&mut self) {
|
||||
if self.cursor.index > 0 {
|
||||
self.cursor.index -= 1;
|
||||
} else if self.cursor.line > 0 {
|
||||
// Move to end of previous line
|
||||
self.cursor.line -= 1;
|
||||
let prev_line_len = self.buffer.lines[self.cursor.line].text().len();
|
||||
self.cursor.index = prev_line_len;
|
||||
let bottom = if bottom_f32 > i32::MAX as f32 {
|
||||
i32::MAX
|
||||
} else if bottom_f32 < i32::MIN as f32 {
|
||||
i32::MIN
|
||||
} else {
|
||||
bottom_f32 as i32
|
||||
};
|
||||
|
||||
// Ensure viewport width doesn't exceed i32::MAX
|
||||
let right = viewport_width.min(i32::MAX as u32) as i32;
|
||||
|
||||
TextBounds {
|
||||
left: 0,
|
||||
top,
|
||||
right,
|
||||
bottom,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn move_cursor_right(&mut self) {
|
||||
if self.cursor.line < self.buffer.lines.len() {
|
||||
let current_line_len = self.buffer.lines[self.cursor.line].text().len();
|
||||
fn get_visible_line_range(&self) -> (usize, usize) {
|
||||
let viewport_height = self.viewport.resolution().height as f32;
|
||||
let line_height = self.buffer.metrics().line_height;
|
||||
|
||||
if self.cursor.index < current_line_len {
|
||||
self.cursor.index += 1;
|
||||
} else if self.cursor.line < self.buffer.lines.len() - 1 {
|
||||
// Move to beginning of next line
|
||||
self.cursor.line += 1;
|
||||
self.cursor.index = 0;
|
||||
}
|
||||
// Start from line 0 (no scrolling yet)
|
||||
let start_line = 0;
|
||||
|
||||
// Calculate how many complete lines fit in the viewport
|
||||
// Add 1 to include partially visible lines at the bottom
|
||||
let visible_lines = (viewport_height / line_height).ceil() as usize + 1;
|
||||
|
||||
// Make sure we don't go beyond the actual number of lines
|
||||
let end_line = if self.buffer.lines.is_empty() {
|
||||
0
|
||||
} else {
|
||||
(start_line + visible_lines).min(self.buffer.lines.len())
|
||||
};
|
||||
|
||||
trace!("visible line range goes from {start_line} to {end_line}");
|
||||
|
||||
(start_line, end_line)
|
||||
}
|
||||
|
||||
pub fn ensure_visible_text_rendered(&mut self) {
|
||||
let (start_line, end_line) = self.get_visible_line_range();
|
||||
for i in start_line..end_line {
|
||||
self.dirty_regions.push(self.get_line_bounds(i));
|
||||
}
|
||||
}
|
||||
|
||||
// pub fn mark_all_dirty(&mut self) {
|
||||
// trace!("Marking all regions as dirty");
|
||||
// // Create a single region covering the entire viewport
|
||||
// let full_bounds = TextBounds {
|
||||
// left: 0,
|
||||
// top: 0,
|
||||
// right: self.viewport.resolution().width as i32,
|
||||
// bottom: self.viewport.resolution().height as i32,
|
||||
// };
|
||||
// self.dirty_regions = vec![full_bounds];
|
||||
// }
|
||||
|
||||
// pub fn move_cursor_left(&mut self) {
|
||||
// if self.cursor.index > 0 {
|
||||
// self.cursor.index -= 1;
|
||||
// } else if self.cursor.line > 0 {
|
||||
// // Move to end of previous line
|
||||
// self.cursor.line -= 1;
|
||||
// let prev_line_len = self.buffer.lines[self.cursor.line].text().len();
|
||||
// self.cursor.index = prev_line_len;
|
||||
// }
|
||||
// }
|
||||
|
||||
// pub fn move_cursor_right(&mut self) {
|
||||
// if self.cursor.line < self.buffer.lines.len() {
|
||||
// let current_line_len = self.buffer.lines[self.cursor.line].text().len();
|
||||
|
||||
// if self.cursor.index < current_line_len {
|
||||
// self.cursor.index += 1;
|
||||
// } else if self.cursor.line < self.buffer.lines.len() - 1 {
|
||||
// // Move to beginning of next line
|
||||
// self.cursor.line += 1;
|
||||
// self.cursor.index = 0;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user