Files
hexapoda/src/buffer/widget.rs
T
2026-05-01 01:17:02 -05:00

136 lines
3.6 KiB
Rust

use std::{cmp::min, iter};
use ratatui::{layout::Rect, text::{Line, Text}, widgets::Widget};
use crate::{BYTES_PER_LINE, buffer::Buffer};
mod address;
mod hex;
mod character_panel;
mod status_line;
mod extra_statuses;
impl Widget for &Buffer {
fn render(self, area: Rect, buf: &mut ratatui::buffer::Buffer) {
let screen_end = self.scroll_position + BYTES_PER_LINE * (area.height as usize - 1);
let bytes_end = min(screen_end, self.contents.len());
let bytes_to_render = &self.contents[self.scroll_position..bytes_end];
let (chunks, remainder) = bytes_to_render
.as_chunks::<BYTES_PER_LINE>();
let hex_lines = chunks
.iter()
.zip((self.scroll_position..).step_by(BYTES_PER_LINE))
.map(|(bytes, address)| self.render_line(address, bytes));
let remainder_address = bytes_end - remainder.len();
#[allow(clippy::if_not_else)]
let remainder_line = if !remainder.is_empty() {
Some(self.render_partial_line(remainder_address, remainder))
} else {
None
};
let hex_text: Text = hex_lines
.chain(remainder_line)
.collect();
let hex_area = Rect::new(area.x, area.y, area.width, area.height - 1);
hex_text.render(hex_area, buf);
if self.contents.is_empty() {
Line::from("empty file").render(area, buf);
}
let status_line_area = Rect::new(area.x, area.bottom() - 1, area.width, 1);
self.render_status_line().render(status_line_area, buf);
self.render_extra_statuses()
.right_aligned()
.render(status_line_area, buf);
let mut primary_popup = None;
let mut primary_popup_area = None;
for popup in &self.popups {
if self.scroll_position <= popup.at &&
popup.at < self.scroll_position + (hex_area.height.saturating_sub(1) as usize * BYTES_PER_LINE)
{
let position_on_screen = popup.at - self.scroll_position;
let hex_column = position_on_screen % BYTES_PER_LINE;
let popup_area = popup
.area_at(
area.x + byte_column_to_screen_column(hex_column),
area.y + u16::try_from(position_on_screen / BYTES_PER_LINE).unwrap() + 1
)
.clamp(hex_area);
if popup.at == self.primary_cursor.lower_bound() {
primary_popup = Some(popup);
primary_popup_area = Some(popup_area);
}
popup.clone().render(popup_area, buf);
}
}
if let Some(primary_popup) = primary_popup &&
let Some(primary_popup_area) = primary_popup_area
{
primary_popup.clone().as_primary().render(primary_popup_area, buf);
}
// if self.partial_action == Some(PartialAction::Space) {
// let input_field_area = Rect::new(area.x, area.bottom() - 2, area.width, 1);
// Span::from("/0F673 ")
// .on_dark_gray()
// .render(input_field_area, buf);
// }
}
}
impl Buffer {
fn render_line(&self, address: usize, bytes: &[u8; BYTES_PER_LINE]) -> Line<'static> {
iter::once(address::render_address(address))
.chain(self.render_chunks(address, bytes))
.chain(iter::once(" ".into()))
.chain(self.render_character_panel(address, bytes))
.collect()
}
fn render_partial_line(&self, address: usize, bytes: &[u8]) -> Line<'static> {
iter::once(address::render_address(address))
.chain(self.render_partial_chunks(address, bytes))
.chain(iter::once(" ".into()))
.chain(self.render_character_panel(address, bytes))
.collect()
}
}
fn byte_column_to_screen_column(byte_column: usize) -> u16 {
match byte_column {
0 => 10,
1 => 13,
2 => 16,
3 => 19,
4 => 23,
5 => 26,
6 => 29,
7 => 32,
8 => 36,
9 => 39,
10 => 42,
11 => 45,
12 => 49,
13 => 52,
14 => 55,
15 => 58,
_ => panic!("byte column must be less than 16"),
}
}