diff options
| author | Namilskyy <alive6863@gmail.com> | 2025-04-01 22:29:47 +0300 |
|---|---|---|
| committer | Namilskyy <alive6863@gmail.com> | 2025-04-01 22:31:49 +0300 |
| commit | c9bca4946f43629e9b5a3f6549dc3851c6e713b3 (patch) | |
| tree | 5548312adc31c3299f1c74d7b0dee6589ff0214e /src | |
| parent | 0b069b828d117655053bd8972e32424ec78cbdb5 (diff) | |
Implemented the scrolling fucntion
Diffstat (limited to 'src')
| -rw-r--r-- | src/hex.rs | 48 | ||||
| -rw-r--r-- | src/input.rs | 25 | ||||
| -rw-r--r-- | src/render.rs | 133 |
3 files changed, 160 insertions, 46 deletions
@@ -1,27 +1,64 @@ use std::{fs, io}; use std::io::stdout; use ratatui::{backend::CrosstermBackend, Terminal}; +use ratatui::widgets::{ScrollbarState, ScrollDirection}; use crossterm::{execute, terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen}}; use crate::{input, render}; - + pub struct HexEditor { filename: String, data: Vec<u8>, cursor: usize, - terminal: Terminal<CrosstermBackend<std::io::Stdout>>, + terminal: Terminal<CrosstermBackend<std::io::Stdout>,>, + scrollbar_state: ScrollbarState, + scroll_position: usize } impl HexEditor { + pub fn new(filename: String, data: Vec<u8>) -> io::Result<Self> { enable_raw_mode()?; execute!(stdout(), EnterAlternateScreen)?; let terminal = Terminal::new(CrosstermBackend::new(stdout()))?; - Ok(Self { filename, data, cursor: 0, terminal }) + let line_count = (data.len() + 15) / 16; + let scrollbar_state = ScrollbarState::default() + .content_length(line_count) + .viewport_content_length(10); + + Ok(Self { + filename, + data, + cursor: 0, + terminal, + scrollbar_state, + scroll_position: 0, + }) } + pub fn run(&mut self) -> io::Result<()> { - while input::handle_input(&mut self.cursor, &mut self.data[..])? { - render::draw(&mut self.terminal, &self.data, self.cursor, &self.filename)?; + while input::handle_input( + &mut self.cursor, + &mut self.data, + &mut self.scroll_position + )? { + let total_lines = (self.data.len() + 15) / 16; + let visible_lines = self.terminal.size()?.height as usize; + + + self.scrollbar_state = ScrollbarState::new(total_lines) + .viewport_content_length(visible_lines) + .position(self.scroll_position); + + + render::draw( + &mut self.terminal, + &self.data, + self.cursor, + &self.filename, + self.scroll_position, + &mut self.scrollbar_state + )?; } self.cleanup() } @@ -32,4 +69,5 @@ impl HexEditor { fs::write(&self.filename, &self.data)?; Ok(()) } + } diff --git a/src/input.rs b/src/input.rs index 0db7e16..1258ac0 100644 --- a/src/input.rs +++ b/src/input.rs @@ -1,20 +1,35 @@ use std::{io, time::Duration}; use crossterm::{event::{self, KeyCode, KeyEvent}}; -pub fn handle_input(cursor: &mut usize, data: &mut [u8]) -> io::Result<bool> { +pub fn handle_input( + cursor: &mut usize, + data: &mut [u8], + scroll_position: &mut usize +) -> io::Result<bool> { if event::poll(Duration::from_millis(100))? { if let event::Event::Key(KeyEvent { code, .. }) = event::read()? { match code { KeyCode::Esc => return Ok(false), KeyCode::Left if *cursor > 0 => *cursor -= 1, KeyCode::Right if *cursor < data.len() * 2 - 1 => *cursor += 1, - KeyCode::Up if *cursor >= 32 => *cursor -= 32, - KeyCode::Down if *cursor + 32 < data.len() * 2 => *cursor += 32, + KeyCode::Up => { + if *cursor >= 32 { + *cursor -= 32; + *scroll_position = scroll_position.saturating_sub(1); + } + }, + KeyCode::Down => { + if *cursor + 32 < data.len() * 2 { + *cursor += 32; + *scroll_position = scroll_position.saturating_add(1); + } + }, KeyCode::Char(c) if c.is_ascii_hexdigit() => { let byte_index = *cursor / 2; let shift = if *cursor % 2 == 0 { 4 } else { 0 }; - data[byte_index] = (data[byte_index] & (0x0F << (4 - shift))) | (c.to_digit(16).unwrap() as u8) << shift; - } + data[byte_index] = (data[byte_index] & (0x0f << (4 - shift))) + | (c.to_digit(16).unwrap() as u8) << shift; + }, _ => {} } } diff --git a/src/render.rs b/src/render.rs index a0cb234..061dbfe 100644 --- a/src/render.rs +++ b/src/render.rs @@ -3,12 +3,22 @@ use ratatui::{ layout::{Constraint, Direction, Layout}, style::{Color, Style}, text::{Line, Span}, - widgets::{Block, Borders, Paragraph}, + widgets::{ + Block, Borders, Paragraph, ScrollbarState, Scrollbar, ScrollbarOrientation, + }, Terminal, prelude::Alignment, + symbols, }; -pub fn draw(terminal: &mut Terminal<impl ratatui::backend::Backend>, data: &[u8], cursor: usize, filename: &str) -> io::Result<()> { +pub fn draw( + terminal: &mut Terminal<impl ratatui::backend::Backend>, + data: &[u8], + cursor: usize, + filename: &str, + scroll_position: usize, + scrollbar_state: &mut ScrollbarState, +) -> io::Result<()> { terminal.draw(|frame| { let size = frame.size(); let layout = Layout::default() @@ -16,52 +26,103 @@ pub fn draw(terminal: &mut Terminal<impl ratatui::backend::Backend>, data: &[u8] .constraints([Constraint::Min(1), Constraint::Length(1)]) .split(size); - let lines: Vec<Line> = data.chunks(16).enumerate().map(|(i, chunk)| { - let addr = Span::raw(format!("{:08x} │ ", i * 16)); - let mut hex: Vec<Span> = Vec::new(); - let mut ascii_repr = String::new(); - for (j, &byte) in chunk.iter().enumerate() { - let hex_str = format!("{:02x}", byte); - let index = i * 32 + j * 2; + let total_lines = (data.len() + 15) / 16; + let visible_height = layout[0].height as usize; - for (k, ch) in hex_str.chars().enumerate() { - let cur_idx = index + k; - let style = if cur_idx == cursor { - Style::default().bg(Color::White).fg(Color::Black) + *scrollbar_state = ScrollbarState::new(total_lines) + .viewport_content_length(visible_height) + .position(scroll_position); + + let lines: Vec<Line> = data + .chunks(16) + .enumerate() + .skip(scroll_position) + .take(visible_height) + .map(|(i, chunk)| { + let addr = format!("{:08X} │ ", i * 16); + let mut hex_spans = Vec::new(); + let mut ascii = String::new(); + + for (j, &byte) in chunk.iter().enumerate() { + let global_pos = i * 16 + j; + let nibbles = format!("{:02X}", byte); + + for (k, c) in nibbles.chars().enumerate() { + let is_cursor = (global_pos * 2 + k) == cursor; + let style = if is_cursor { + Style::default().bg(Color::White).fg(Color::Black) + } else { + Style::default() + }; + hex_spans.push(Span::styled(c.to_string(), style)); + } + + + if j < 15 { + hex_spans.push(Span::raw(if j % 4 == 3 { " " } else { " " })); + } + + + ascii.push(if byte.is_ascii_graphic() || byte == b' ' { + byte as char } else { - Style::default() - }; - hex.push(Span::styled(ch.to_string(), style)); + '.' + }); } - if j < 15 { - let space = if j % 4 == 3 { " " } else { " " }; - hex.push(Span::raw(space)); + + if chunk.len() < 16 { + let missing = 16 - chunk.len(); + hex_spans.push(Span::raw(" ".repeat(missing * 3))); + ascii.push_str(&".".repeat(missing)); } - ascii_repr.push(if byte.is_ascii_graphic() || byte == b' ' { byte as char } else { '.' }); - } + Line::from(vec![ + Span::raw(addr), + Span::raw(hex_spans.iter().map(|s| s.content.as_ref()).collect::<String>()), + Span::raw(" │ "), + Span::raw(ascii), + ]) + }) + .collect(); + + + frame.render_widget( + Paragraph::new(lines) + .block(Block::default().borders(Borders::ALL)), + layout[0] + ); + - let missing_bytes = 16 - chunk.len(); - let padding = missing_bytes * 3; - let spaces = " ".repeat(padding); + frame.render_stateful_widget( + Scrollbar::default() + .orientation(ScrollbarOrientation::VerticalRight) + .symbols(symbols::scrollbar::VERTICAL), + layout[0], + scrollbar_state, + ); - let ascii = Span::raw(format!("{} │ {}", spaces, ascii_repr)); - Line::from(vec![addr] - .into_iter() - .chain(hex) - .chain(std::iter::once(ascii)) - .collect::<Vec<_>>()) - }).collect(); + let progress = if data.is_empty() { + 0 + } else { + (cursor / 2 * 100) / data.len() + }; - frame.render_widget(Paragraph::new(lines).block(Block::default().borders(Borders::ALL)), layout[0]); + let status = format!( + "{} │ Offset: 0x{:08X} │ {}%", + filename, + cursor / 2, + progress + ); - let progress = if data.is_empty() { 100 } else { (cursor / 2 * 100) / data.len() }; - let status_text = format!("{} \t 0x{:08x} \t {:3}%", filename, cursor / 2, progress); - let status_bar = Paragraph::new(Line::from(Span::raw(status_text))).alignment(Alignment::Center); - frame.render_widget(status_bar, layout[1]); + frame.render_widget( + Paragraph::new(status) + .alignment(Alignment::Center) + .block(Block::default().borders(Borders::TOP)), + layout[1] + ); })?; Ok(()) } |
