improve popup rendering
This commit is contained in:
Generated
+2
-2
@@ -1370,9 +1370,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-width"
|
name = "unicode-width"
|
||||||
version = "0.2.0"
|
version = "0.2.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd"
|
checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-xid"
|
name = "unicode-xid"
|
||||||
|
|||||||
+44
-29
@@ -1,5 +1,5 @@
|
|||||||
use std::{cmp::min, collections::hash_set::Entry, convert::identity, fs::File, io::Write, iter, mem::{replace, swap}};
|
use std::{cmp::min, collections::hash_set::Entry, convert::identity, fs::File, io::Write, iter, mem::{replace, swap}};
|
||||||
use ratatui::{style::Stylize, text::Span};
|
use ratatui::{style::{Color, Stylize}, text::Span};
|
||||||
use crate::{BYTES_OF_PADDING, BYTES_PER_LINE, LINES_OF_PADDING, app::WindowSize, buffer::{Buffer, Mode, PartialAction, Popup}, cursor::Cursor, edit_action::EditAction};
|
use crate::{BYTES_OF_PADDING, BYTES_PER_LINE, LINES_OF_PADDING, app::WindowSize, buffer::{Buffer, Mode, PartialAction, Popup}, cursor::Cursor, edit_action::EditAction};
|
||||||
|
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
@@ -680,7 +680,7 @@ impl Buffer {
|
|||||||
let max_contents_index = self.max_contents_index();
|
let max_contents_index = self.max_contents_index();
|
||||||
|
|
||||||
let mark_after_primary = mark_after(
|
let mark_after_primary = mark_after(
|
||||||
self.primary_cursor.head + 1,
|
self.primary_cursor.head,
|
||||||
&sorted_marks,
|
&sorted_marks,
|
||||||
max_contents_index
|
max_contents_index
|
||||||
);
|
);
|
||||||
@@ -690,7 +690,7 @@ impl Buffer {
|
|||||||
|
|
||||||
for cursor in &mut self.cursors {
|
for cursor in &mut self.cursors {
|
||||||
let mark_after_cursor = mark_after(
|
let mark_after_cursor = mark_after(
|
||||||
cursor.head + 1,
|
cursor.head,
|
||||||
&sorted_marks,
|
&sorted_marks,
|
||||||
max_contents_index
|
max_contents_index
|
||||||
);
|
);
|
||||||
@@ -705,21 +705,21 @@ impl Buffer {
|
|||||||
fn extend_to_null(&mut self, window_size: WindowSize) {
|
fn extend_to_null(&mut self, window_size: WindowSize) {
|
||||||
if let Some(null_offset_after_primary) = self.contents[self.primary_cursor.head..]
|
if let Some(null_offset_after_primary) = self.contents[self.primary_cursor.head..]
|
||||||
.iter()
|
.iter()
|
||||||
.skip(2)
|
.skip(1)
|
||||||
.position(|&byte| byte == 0)
|
.position(|&byte| byte == 0)
|
||||||
{
|
{
|
||||||
self.primary_cursor.tail = self.primary_cursor.head;
|
self.primary_cursor.tail = self.primary_cursor.head;
|
||||||
self.primary_cursor.head += null_offset_after_primary + 1;
|
self.primary_cursor.head += null_offset_after_primary;
|
||||||
}
|
}
|
||||||
|
|
||||||
for cursor in &mut self.cursors {
|
for cursor in &mut self.cursors {
|
||||||
if let Some(null_offset_after_primary) = self.contents[cursor.head..]
|
if let Some(null_offset_after_primary) = self.contents[cursor.head..]
|
||||||
.iter()
|
.iter()
|
||||||
.skip(2)
|
.skip(1)
|
||||||
.position(|&byte| byte == 0)
|
.position(|&byte| byte == 0)
|
||||||
{
|
{
|
||||||
cursor.tail = cursor.head;
|
cursor.tail = cursor.head;
|
||||||
cursor.head += null_offset_after_primary + 1;
|
cursor.head += null_offset_after_primary;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -730,21 +730,21 @@ impl Buffer {
|
|||||||
fn extend_to_FF(&mut self, window_size: WindowSize) {
|
fn extend_to_FF(&mut self, window_size: WindowSize) {
|
||||||
if let Some(null_offset_after_primary) = self.contents[self.primary_cursor.head..]
|
if let Some(null_offset_after_primary) = self.contents[self.primary_cursor.head..]
|
||||||
.iter()
|
.iter()
|
||||||
.skip(2)
|
.skip(1)
|
||||||
.position(|&byte| byte == 0xFF)
|
.position(|&byte| byte == 0xFF)
|
||||||
{
|
{
|
||||||
self.primary_cursor.tail = self.primary_cursor.head;
|
self.primary_cursor.tail = self.primary_cursor.head;
|
||||||
self.primary_cursor.head += null_offset_after_primary + 1;
|
self.primary_cursor.head += null_offset_after_primary;
|
||||||
}
|
}
|
||||||
|
|
||||||
for cursor in &mut self.cursors {
|
for cursor in &mut self.cursors {
|
||||||
if let Some(null_offset_after_primary) = self.contents[cursor.head..]
|
if let Some(null_offset_after_primary) = self.contents[cursor.head..]
|
||||||
.iter()
|
.iter()
|
||||||
.skip(2)
|
.skip(1)
|
||||||
.position(|&byte| byte == 0xFF)
|
.position(|&byte| byte == 0xFF)
|
||||||
{
|
{
|
||||||
cursor.tail = cursor.head;
|
cursor.tail = cursor.head;
|
||||||
cursor.head += null_offset_after_primary + 1;
|
cursor.head += null_offset_after_primary;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -764,17 +764,18 @@ impl Buffer {
|
|||||||
|
|
||||||
let nat = bytes_to_nat(selection);
|
let nat = bytes_to_nat(selection);
|
||||||
|
|
||||||
let int = nat.and_then(|nat| nat_to_int_if_different(nat, selection.len()));
|
let int = nat
|
||||||
|
.and_then(|nat| nat_to_int_if_different(nat, selection.len()))
|
||||||
|
.map(|int| Span::from(format!("{int}")).white());
|
||||||
|
|
||||||
let utf8 = str::from_utf8(selection).ok()
|
let utf8 = str::from_utf8(selection).ok()
|
||||||
.and_then(|utf8| {
|
.and_then(|utf8| {
|
||||||
if utf8.contains(|char: char| char.is_ascii() && !char.is_ascii_graphic()) {
|
utf8
|
||||||
None
|
.contains(character_is_allowed_in_strings)
|
||||||
} else {
|
.then_some(utf8)
|
||||||
Some(utf8)
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
.map(|utf8| utf8.replace('\0', "\\0"));
|
.map(|utf8| utf8.replace('\0', "\\0"))
|
||||||
|
.map(|utf8| Span::from(format!("\"{utf8}\"")).red());
|
||||||
|
|
||||||
#[allow(clippy::cast_precision_loss)]
|
#[allow(clippy::cast_precision_loss)]
|
||||||
let fixedpoint2012 = nat
|
let fixedpoint2012 = nat
|
||||||
@@ -783,7 +784,7 @@ impl Buffer {
|
|||||||
let two_decimals_is_enough = (fixedpoint2012 * 100.0).fract() == 0.0;
|
let two_decimals_is_enough = (fixedpoint2012 * 100.0).fract() == 0.0;
|
||||||
let approximate_symbol = if two_decimals_is_enough { "" } else { "~" };
|
let approximate_symbol = if two_decimals_is_enough { "" } else { "~" };
|
||||||
|
|
||||||
format!("20.12: {approximate_symbol}{fixedpoint2012:.2}")
|
format!("20.12: {approximate_symbol}{fixedpoint2012:.2}").into()
|
||||||
});
|
});
|
||||||
|
|
||||||
#[allow(clippy::cast_precision_loss)]
|
#[allow(clippy::cast_precision_loss)]
|
||||||
@@ -793,7 +794,7 @@ impl Buffer {
|
|||||||
let two_decimals_is_enough = (fixedpoint1616 * 100.0).fract() == 0.0;
|
let two_decimals_is_enough = (fixedpoint1616 * 100.0).fract() == 0.0;
|
||||||
let approximate_symbol = if two_decimals_is_enough { "" } else { "~" };
|
let approximate_symbol = if two_decimals_is_enough { "" } else { "~" };
|
||||||
|
|
||||||
format!("16.16: {approximate_symbol}{fixedpoint1616:.2}")
|
format!("16.16: {approximate_symbol}{fixedpoint1616:.2}").into()
|
||||||
});
|
});
|
||||||
|
|
||||||
#[allow(clippy::cast_precision_loss)]
|
#[allow(clippy::cast_precision_loss)]
|
||||||
@@ -803,7 +804,7 @@ impl Buffer {
|
|||||||
let two_decimals_is_enough = (fixedpoint124 * 100.0).fract() == 0.0;
|
let two_decimals_is_enough = (fixedpoint124 * 100.0).fract() == 0.0;
|
||||||
let approximate_symbol = if two_decimals_is_enough { "" } else { "~" };
|
let approximate_symbol = if two_decimals_is_enough { "" } else { "~" };
|
||||||
|
|
||||||
format!("12.4: {approximate_symbol}{fixedpoint124:.2}")
|
format!("12.4: {approximate_symbol}{fixedpoint124:.2}").into()
|
||||||
});
|
});
|
||||||
|
|
||||||
#[allow(clippy::cast_precision_loss)]
|
#[allow(clippy::cast_precision_loss)]
|
||||||
@@ -813,7 +814,7 @@ impl Buffer {
|
|||||||
let two_decimals_is_enough = (fixedpoint88 * 100.0).fract() == 0.0;
|
let two_decimals_is_enough = (fixedpoint88 * 100.0).fract() == 0.0;
|
||||||
let approximate_symbol = if two_decimals_is_enough { "" } else { "~" };
|
let approximate_symbol = if two_decimals_is_enough { "" } else { "~" };
|
||||||
|
|
||||||
format!("8.8: {approximate_symbol}{fixedpoint88:.2}")
|
format!("8.8: {approximate_symbol}{fixedpoint88:.2}").into()
|
||||||
});
|
});
|
||||||
|
|
||||||
#[allow(clippy::cast_precision_loss)]
|
#[allow(clippy::cast_precision_loss)]
|
||||||
@@ -823,24 +824,30 @@ impl Buffer {
|
|||||||
let two_decimals_is_enough = (fixedpoint412 * 100.0).fract() == 0.0;
|
let two_decimals_is_enough = (fixedpoint412 * 100.0).fract() == 0.0;
|
||||||
let approximate_symbol = if two_decimals_is_enough { "" } else { "~" };
|
let approximate_symbol = if two_decimals_is_enough { "" } else { "~" };
|
||||||
|
|
||||||
format!("4.12: {approximate_symbol}{fixedpoint412:.2}")
|
format!("4.12: {approximate_symbol}{fixedpoint412:.2}").into()
|
||||||
});
|
});
|
||||||
|
|
||||||
let color = (selection.len() == 3).then(|| [selection[0], selection[1], selection[2]]);
|
let color = (selection.len() == 3)
|
||||||
|
.then(|| [selection[0], selection[1], selection[2]])
|
||||||
|
.map(|[red, green, blue]| {
|
||||||
|
Span::from(format!("#{red:02X}{green:02X}{blue:02X}"))
|
||||||
|
.fg(Color::Rgb(red, green, blue))
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
Popup::new(
|
Popup::new(
|
||||||
cursor.lower_bound(),
|
cursor.lower_bound(),
|
||||||
int.map(|int| format!("{int}"))
|
int
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.chain(nat.map(|nat| format!("{nat}")))
|
.chain(nat.map(|nat| Span::from(format!("{nat}")).white()))
|
||||||
.chain(utf8.map(|utf8| format!("\"{utf8}\"")))
|
.chain(utf8)
|
||||||
.chain(fixedpoint2012)
|
.chain(fixedpoint2012)
|
||||||
.chain(fixedpoint1616)
|
.chain(fixedpoint1616)
|
||||||
.chain(fixedpoint124)
|
.chain(fixedpoint124)
|
||||||
.chain(fixedpoint88)
|
.chain(fixedpoint88)
|
||||||
.chain(fixedpoint412)
|
.chain(fixedpoint412)
|
||||||
.collect(),
|
.chain(color)
|
||||||
color
|
.collect()
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
@@ -919,3 +926,11 @@ fn mark_after(offset: usize, sorted_marks: &[usize], max: usize) -> usize {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const fn character_is_allowed_in_strings(character: char) -> bool {
|
||||||
|
match character {
|
||||||
|
'\0' | '\t' | '\n' | '\r' => true,
|
||||||
|
_ if character.is_ascii_control() => false,
|
||||||
|
_ => true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+14
-22
@@ -1,7 +1,7 @@
|
|||||||
use core::slice::GetDisjointMutIndex;
|
use core::slice::GetDisjointMutIndex;
|
||||||
use std::{collections::HashSet, fs::File, io::Read, path::PathBuf};
|
use std::{collections::HashSet, fs::File, io::Read, path::PathBuf};
|
||||||
use crossterm::event::KeyEvent;
|
use crossterm::event::KeyEvent;
|
||||||
use ratatui::{layout::Rect, style::{Color, Stylize}, text::Span, widgets::Widget};
|
use ratatui::{layout::{Constraint, Rect}, style::{Color, Stylize}, text::Span, widgets::{Block, Clear, Widget}};
|
||||||
use crate::{BYTES_PER_LINE, action::{Action, AppAction, bytes_to_nat}, app::WindowSize, config::Config, cursor::Cursor, edit_action::EditAction};
|
use crate::{BYTES_PER_LINE, action::{Action, AppAction, bytes_to_nat}, app::WindowSize, config::Config, cursor::Cursor, edit_action::EditAction};
|
||||||
|
|
||||||
mod widget;
|
mod widget;
|
||||||
@@ -50,8 +50,7 @@ pub enum PartialAction {
|
|||||||
pub struct Popup {
|
pub struct Popup {
|
||||||
at: usize,
|
at: usize,
|
||||||
width: u16,
|
width: u16,
|
||||||
lines: Vec<String>,
|
lines: Vec<Span<'static>>
|
||||||
color: Option<[u8; 3]>
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Mode {
|
impl Mode {
|
||||||
@@ -86,48 +85,41 @@ impl PartialAction {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Popup {
|
impl Popup {
|
||||||
pub fn new(at: usize, lines: Vec<String>, color: Option<[u8; 3]>) -> Self {
|
pub fn new(at: usize, lines: Vec<Span<'static>>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
at,
|
at,
|
||||||
width: lines
|
width: lines
|
||||||
.iter()
|
.iter()
|
||||||
.map(|line| line.len() as u16)
|
.map(|line| line.width() as u16)
|
||||||
.chain(color.map(|_| 7))
|
|
||||||
.max()
|
.max()
|
||||||
.unwrap_or(0),
|
.unwrap_or(0),
|
||||||
lines,
|
lines
|
||||||
color
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn area_at(&self, x: u16, y: u16) -> Rect {
|
const fn area_at(&self, x: u16, y: u16) -> Rect {
|
||||||
Rect {
|
Rect {
|
||||||
x,
|
x,
|
||||||
y,
|
y,
|
||||||
width: self.width + 2,
|
width: self.width + 2,
|
||||||
height: self.lines.len() as u16 + u16::from(self.color.is_some())
|
height: self.lines.len() as u16
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Widget for Popup {
|
impl Widget for Popup {
|
||||||
fn render(self, area: Rect, buf: &mut ratatui::prelude::Buffer) {
|
fn render(self, area: Rect, buf: &mut ratatui::prelude::Buffer) {
|
||||||
let width = self.width as usize;
|
|
||||||
|
|
||||||
for (line, area) in self.lines.iter().zip(area.rows()) {
|
for (line, area) in self.lines.iter().zip(area.rows()) {
|
||||||
Span::from(format!(" {line:^width$} "))
|
Clear.render(area, buf);
|
||||||
.white()
|
|
||||||
|
Block::new()
|
||||||
.on_dark_gray()
|
.on_dark_gray()
|
||||||
.render(area, buf);
|
.render(area, buf);
|
||||||
}
|
|
||||||
|
|
||||||
if let Some([red, green, blue]) = self.color {
|
line.render(
|
||||||
let color_code = format!("#{red:02X}{green:02X}{blue:02X}");
|
area.centered_horizontally(Constraint::Length(line.width() as u16)),
|
||||||
|
buf
|
||||||
Span::from(format!(" {color_code:^width$} "))
|
);
|
||||||
.fg(Color::Rgb(red, green, blue))
|
|
||||||
.on_dark_gray()
|
|
||||||
.render(area.rows().next_back().unwrap(), buf);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,10 +50,12 @@ impl Widget for &Buffer {
|
|||||||
let position_on_screen = popup.at - self.scroll_position;
|
let position_on_screen = popup.at - self.scroll_position;
|
||||||
let hex_column = position_on_screen % BYTES_PER_LINE;
|
let hex_column = position_on_screen % BYTES_PER_LINE;
|
||||||
|
|
||||||
let popup_area = popup.area_at(
|
let popup_area = popup
|
||||||
|
.area_at(
|
||||||
area.x + byte_column_to_screen_column(hex_column) as u16,
|
area.x + byte_column_to_screen_column(hex_column) as u16,
|
||||||
area.y + (position_on_screen / BYTES_PER_LINE) as u16 + 1
|
area.y + (position_on_screen / BYTES_PER_LINE) as u16 + 1
|
||||||
);
|
)
|
||||||
|
.clamp(hex_area);
|
||||||
|
|
||||||
popup.clone().render(popup_area, buf);
|
popup.clone().render(popup_area, buf);
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-7
@@ -27,10 +27,11 @@ const BYTES_OF_PADDING: usize = LINES_OF_PADDING * BYTES_PER_LINE;
|
|||||||
|
|
||||||
// TODO:
|
// TODO:
|
||||||
// - 4 with large selection crashes
|
// - 4 with large selection crashes
|
||||||
// - also (/)
|
// - also ( and )
|
||||||
// - also C-F in excavate_defs
|
// - also C-F in excavate_defs
|
||||||
// - diffing
|
|
||||||
// - search
|
// - search
|
||||||
|
// - ascii and bytes (`/` and `A-/`?)
|
||||||
|
// - diffing
|
||||||
// - s/A-k/A-K
|
// - s/A-k/A-K
|
||||||
// - C-a/C-x
|
// - C-a/C-x
|
||||||
// - modifications
|
// - modifications
|
||||||
@@ -52,11 +53,6 @@ const BYTES_OF_PADDING: usize = LINES_OF_PADDING * BYTES_PER_LINE;
|
|||||||
// future directions
|
// future directions
|
||||||
// - 'views' for bytes (i8/16/etc u8/16/etc 20.12/8.4/etc)
|
// - 'views' for bytes (i8/16/etc u8/16/etc 20.12/8.4/etc)
|
||||||
// - how to fit??! `-128` longer than `80`
|
// - how to fit??! `-128` longer than `80`
|
||||||
// - popup for different readings for the selected bytes
|
|
||||||
// - utf8?
|
|
||||||
|
|
||||||
// when AsciiChar is stabilized, use it instead of char
|
|
||||||
// - actually since im using nightly already, do this
|
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let mut app = App::new();
|
let mut app = App::new();
|
||||||
|
|||||||
Reference in New Issue
Block a user