Edit hex inline
This commit is contained in:
+181
-68
@@ -1,10 +1,11 @@
|
||||
use std::{fmt::LowerHex, sync::Arc};
|
||||
|
||||
use egui::{
|
||||
Align, CentralPanel, Label, Layout, Response, RichText, Separator, TextEdit, Ui,
|
||||
ViewportBuilder, Widget,
|
||||
Align, CentralPanel, FontSelection, Label, Layout, Response, RichText, Separator, TextEdit, Ui,
|
||||
ViewportBuilder, Widget, text::LayoutJob,
|
||||
};
|
||||
use egui_extras::{Column, Size, StripBuilder, TableBuilder};
|
||||
use itertools::Itertools;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{
|
||||
@@ -19,6 +20,15 @@ enum ValueSize {
|
||||
Halfword,
|
||||
Word,
|
||||
}
|
||||
impl ValueSize {
|
||||
fn size(self) -> usize {
|
||||
match self {
|
||||
Self::Byte => 1,
|
||||
Self::Halfword => 2,
|
||||
Self::Word => 4,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct HexEditResponse {
|
||||
submitted: bool,
|
||||
@@ -79,7 +89,7 @@ pub struct MemoryWindow {
|
||||
view: MemoryView,
|
||||
scroll_to: Option<u32>,
|
||||
jump_to: String,
|
||||
editing_value: String,
|
||||
editing_buffer: Vec<u8>,
|
||||
state: UiData<State>,
|
||||
}
|
||||
|
||||
@@ -92,15 +102,30 @@ impl MemoryWindow {
|
||||
view: memory.watch(sim_id, 0x0, 0),
|
||||
scroll_to: None,
|
||||
jump_to: String::new(),
|
||||
editing_value: String::new(),
|
||||
editing_buffer: vec![],
|
||||
state,
|
||||
}
|
||||
}
|
||||
|
||||
fn show_top_bar(&mut self, ui: &mut Ui) {
|
||||
ui.radio_value(&mut self.state.value_size, ValueSize::Byte, "8-bit");
|
||||
ui.radio_value(&mut self.state.value_size, ValueSize::Halfword, "16-bit");
|
||||
ui.radio_value(&mut self.state.value_size, ValueSize::Word, "32-bit");
|
||||
if ui
|
||||
.radio_value(&mut self.state.value_size, ValueSize::Byte, "8-bit")
|
||||
.clicked()
|
||||
{
|
||||
self.editing_buffer.clear();
|
||||
}
|
||||
if ui
|
||||
.radio_value(&mut self.state.value_size, ValueSize::Halfword, "16-bit")
|
||||
.clicked()
|
||||
{
|
||||
self.editing_buffer.clear();
|
||||
}
|
||||
if ui
|
||||
.radio_value(&mut self.state.value_size, ValueSize::Word, "32-bit")
|
||||
.clicked()
|
||||
{
|
||||
self.editing_buffer.clear();
|
||||
}
|
||||
self.state.address &= match self.state.value_size {
|
||||
ValueSize::Byte => 0x07ffffff,
|
||||
ValueSize::Halfword => 0x07fffffe,
|
||||
@@ -114,6 +139,7 @@ impl MemoryWindow {
|
||||
if let Ok(address) = u64::from_str_radix(&address_str, 16) {
|
||||
let address = (address & 0x07ffffff) as u32;
|
||||
self.state.address = address;
|
||||
self.editing_buffer.clear();
|
||||
self.scroll_to = Some(address);
|
||||
}
|
||||
}
|
||||
@@ -122,6 +148,34 @@ impl MemoryWindow {
|
||||
fn show_memory(&mut self, ui: &mut Ui) {
|
||||
let row_height_sans_spacing = ui.text_style_height(&egui::TextStyle::Monospace);
|
||||
let max_height = ui.max_rect().height();
|
||||
|
||||
let something_has_focus = ui.memory(|m| m.focused().is_some());
|
||||
let mut selected_changed = false;
|
||||
ui.input(|i| {
|
||||
if !i.focused || something_has_focus {
|
||||
return;
|
||||
}
|
||||
let arrow_keys: [(egui::Key, i32); 4] = [
|
||||
(egui::Key::ArrowUp, -0x10),
|
||||
(egui::Key::ArrowDown, 0x10),
|
||||
(egui::Key::ArrowLeft, -(self.state.value_size.size() as i32)),
|
||||
(egui::Key::ArrowRight, self.state.value_size.size() as i32),
|
||||
];
|
||||
for (key, delta) in arrow_keys {
|
||||
if !selected_changed && i.key_pressed(key) {
|
||||
self.state.address = self.state.address.wrapping_add_signed(delta) & 0x07ffffff;
|
||||
self.editing_buffer.clear();
|
||||
selected_changed = true;
|
||||
}
|
||||
}
|
||||
if !self.editing_buffer.is_empty() && i.key_pressed(egui::Key::Enter) {
|
||||
let full_size = self.state.value_size.size() * 2;
|
||||
while self.editing_buffer.len() < full_size {
|
||||
self.editing_buffer.insert(0, 0);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let mut table = TableBuilder::new(ui)
|
||||
.column(Column::auto())
|
||||
.column(Column::initial(0.0))
|
||||
@@ -146,6 +200,7 @@ impl MemoryWindow {
|
||||
let rows = self.view.borrow();
|
||||
let mut new_start = None;
|
||||
let mut new_length = 0;
|
||||
|
||||
body.rows(row_height_sans_spacing, total_rows, |mut row| {
|
||||
let address = row.index() * 0x10;
|
||||
let offset = address
|
||||
@@ -160,6 +215,8 @@ impl MemoryWindow {
|
||||
let label = Label::new(RichText::new(format!("{address:08x}")).monospace());
|
||||
if ui.add(label).clicked() {
|
||||
self.state.address = address as u32;
|
||||
self.editing_buffer.clear();
|
||||
selected_changed = true;
|
||||
};
|
||||
});
|
||||
row.col(|ui| {
|
||||
@@ -170,60 +227,145 @@ impl MemoryWindow {
|
||||
fn show_memory<T: MemoryValue + LowerHex>(
|
||||
ui: &mut Ui,
|
||||
rows: &MemoryRef,
|
||||
start: usize,
|
||||
offset: usize,
|
||||
(start, offset): (usize, usize),
|
||||
selected: u32,
|
||||
memory: &MemoryClient,
|
||||
sim: SimId,
|
||||
buffer: &mut Vec<u8>,
|
||||
) -> Option<u32> {
|
||||
let mut clicked = None;
|
||||
let mut new_selected = None;
|
||||
for (i, value) in rows
|
||||
.range::<T>(offset / size_of::<T>(), 0x10 / size_of::<T>())
|
||||
.enumerate()
|
||||
{
|
||||
let val_str =
|
||||
format!("{value:08x}").split_off(8 - size_of::<T>() * 2);
|
||||
let text = RichText::new(val_str).monospace();
|
||||
let address = (start + offset + i * size_of::<T>()) as u32;
|
||||
let label = if address == selected {
|
||||
Label::new(
|
||||
let mut job = LayoutJob::default();
|
||||
|
||||
let rich_text = |value: &str| {
|
||||
let text = RichText::new(value).monospace();
|
||||
if address == selected {
|
||||
text.background_color(ui.visuals().selection.bg_fill)
|
||||
.color(ui.visuals().selection.stroke.color),
|
||||
)
|
||||
} else {
|
||||
Label::new(text)
|
||||
.color(ui.visuals().selection.stroke.color)
|
||||
} else {
|
||||
text
|
||||
}
|
||||
};
|
||||
if ui.add(label).clicked() {
|
||||
clicked = Some(address);
|
||||
|
||||
let edit_len = if address == selected { buffer.len() } else { 0 };
|
||||
|
||||
if edit_len > 0 {
|
||||
let buffer_str =
|
||||
buffer.iter().map(|d| format!("{d:x}")).join("");
|
||||
let text = rich_text(&buffer_str).underline();
|
||||
text.append_to(
|
||||
&mut job,
|
||||
ui.style(),
|
||||
FontSelection::default(),
|
||||
Align::Center,
|
||||
);
|
||||
}
|
||||
|
||||
let val_str = format!("{value:08x}")
|
||||
.split_off(8 - size_of::<T>() * 2 + edit_len);
|
||||
let text = rich_text(&val_str);
|
||||
text.append_to(
|
||||
&mut job,
|
||||
ui.style(),
|
||||
FontSelection::default(),
|
||||
Align::Center,
|
||||
);
|
||||
|
||||
let response = ui.label(job);
|
||||
if response.clicked() {
|
||||
new_selected = Some(address);
|
||||
}
|
||||
if ui.memory(|m| m.focused().is_none())
|
||||
&& ui.input(|i| i.focused)
|
||||
&& address == selected
|
||||
{
|
||||
const KEYS: [egui::Key; 16] = [
|
||||
egui::Key::Num0,
|
||||
egui::Key::Num1,
|
||||
egui::Key::Num2,
|
||||
egui::Key::Num3,
|
||||
egui::Key::Num4,
|
||||
egui::Key::Num5,
|
||||
egui::Key::Num6,
|
||||
egui::Key::Num7,
|
||||
egui::Key::Num8,
|
||||
egui::Key::Num9,
|
||||
egui::Key::A,
|
||||
egui::Key::B,
|
||||
egui::Key::C,
|
||||
egui::Key::D,
|
||||
egui::Key::E,
|
||||
egui::Key::F,
|
||||
];
|
||||
let pressed = ui.input(|i| {
|
||||
for (index, key) in KEYS.into_iter().enumerate() {
|
||||
if i.key_pressed(key) {
|
||||
return Some(index as u8);
|
||||
}
|
||||
}
|
||||
None
|
||||
});
|
||||
if let Some(value) = pressed {
|
||||
buffer.push(value);
|
||||
}
|
||||
if buffer.len() == size_of::<T>() * 2 {
|
||||
let bytes = buffer
|
||||
.chunks(2)
|
||||
.map(|letters| (letters[0] << 4) | letters[1])
|
||||
.rev()
|
||||
.collect::<Vec<u8>>();
|
||||
let data = T::from_bytes(&bytes);
|
||||
memory.write(sim, address, &data);
|
||||
new_selected = Some(
|
||||
address.wrapping_add(size_of::<T>() as u32)
|
||||
& 0x07ffffff,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
clicked
|
||||
new_selected
|
||||
}
|
||||
let selected = match self.state.value_size {
|
||||
ValueSize::Byte => show_memory::<u8>(
|
||||
ui,
|
||||
&rows,
|
||||
current_start,
|
||||
offset,
|
||||
(current_start, offset),
|
||||
self.state.address,
|
||||
&self.memory,
|
||||
self.sim_id,
|
||||
&mut self.editing_buffer,
|
||||
),
|
||||
ValueSize::Halfword => show_memory::<u16>(
|
||||
ui,
|
||||
&rows,
|
||||
current_start,
|
||||
offset,
|
||||
(current_start, offset),
|
||||
self.state.address,
|
||||
&self.memory,
|
||||
self.sim_id,
|
||||
&mut self.editing_buffer,
|
||||
),
|
||||
ValueSize::Word => show_memory::<u32>(
|
||||
ui,
|
||||
&rows,
|
||||
current_start,
|
||||
offset,
|
||||
(current_start, offset),
|
||||
self.state.address,
|
||||
&self.memory,
|
||||
self.sim_id,
|
||||
&mut self.editing_buffer,
|
||||
),
|
||||
};
|
||||
if let Some(selected) = selected {
|
||||
self.state.address = selected;
|
||||
selected_changed = true;
|
||||
}
|
||||
};
|
||||
if selected_changed {
|
||||
self.editing_buffer.clear();
|
||||
}
|
||||
});
|
||||
row.col(|ui| {
|
||||
ui.separator();
|
||||
@@ -252,40 +394,17 @@ impl MemoryWindow {
|
||||
.retarget(&mut self.view, self.sim_id, start as u32, new_length);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn show_bottom_bar(&mut self, ui: &mut Ui) {
|
||||
ui.label(format!("Current address: {:08x}", self.state.address));
|
||||
if HexEdit::new(&mut self.editing_value, "Set value")
|
||||
.show(ui)
|
||||
.submitted
|
||||
{
|
||||
let value = std::mem::take(&mut self.editing_value);
|
||||
fn set_memory<T: MemoryValue + atoi::FromRadix16Checked>(
|
||||
address: u32,
|
||||
value: String,
|
||||
sim: SimId,
|
||||
memory: &MemoryClient,
|
||||
) {
|
||||
let bytes = value.as_bytes();
|
||||
let (data, consumed) =
|
||||
<T as atoi::FromRadix16Checked>::from_radix_16_checked(bytes);
|
||||
if let Some(data) = data
|
||||
&& consumed == bytes.len()
|
||||
{
|
||||
memory.write(sim, address, &data);
|
||||
}
|
||||
}
|
||||
match self.state.value_size {
|
||||
ValueSize::Byte => {
|
||||
set_memory::<u8>(self.state.address, value, self.sim_id, &self.memory)
|
||||
}
|
||||
ValueSize::Halfword => {
|
||||
set_memory::<u16>(self.state.address, value, self.sim_id, &self.memory)
|
||||
}
|
||||
ValueSize::Word => {
|
||||
set_memory::<u32>(self.state.address, value, self.sim_id, &self.memory)
|
||||
}
|
||||
if selected_changed && self.scroll_to.is_none() {
|
||||
let range = self.view.range();
|
||||
if self.state.address < range.start {
|
||||
self.scroll_to = Some(self.state.address);
|
||||
} else if let Some(delta) = self
|
||||
.state
|
||||
.address
|
||||
.checked_sub(range.start + range.length as u32 - 0x20)
|
||||
{
|
||||
self.scroll_to = Some(range.start + delta + 0x40);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -305,7 +424,7 @@ impl AppWindow for MemoryWindow {
|
||||
fn show(&mut self, ui: &mut Ui) {
|
||||
let row_height =
|
||||
ui.text_style_height(&egui::TextStyle::Monospace) + ui.spacing().item_spacing.y;
|
||||
let table_height = ui.available_height() - (row_height * 4.5);
|
||||
let table_height = ui.available_height() - (row_height * 2.5);
|
||||
if self.state.load(ui) {
|
||||
self.scroll_to = Some(self.state.address);
|
||||
}
|
||||
@@ -314,8 +433,6 @@ impl AppWindow for MemoryWindow {
|
||||
.size(Size::exact(row_height))
|
||||
.size(Size::initial(0.0))
|
||||
.size(Size::exact(table_height))
|
||||
.size(Size::initial(0.0))
|
||||
.size(Size::exact(row_height))
|
||||
.cell_layout(Layout::left_to_right(Align::Center))
|
||||
.vertical(|mut strip| {
|
||||
strip.cell(|ui| self.show_top_bar(ui));
|
||||
@@ -323,10 +440,6 @@ impl AppWindow for MemoryWindow {
|
||||
Separator::default().horizontal().ui(ui);
|
||||
});
|
||||
strip.cell(|ui| self.show_memory(ui));
|
||||
strip.cell(|ui| {
|
||||
Separator::default().horizontal().ui(ui);
|
||||
});
|
||||
strip.cell(|ui| self.show_bottom_bar(ui));
|
||||
});
|
||||
});
|
||||
self.state.save(ui);
|
||||
|
||||
Reference in New Issue
Block a user