Edit hex inline
This commit is contained in:
+179
-66
@@ -1,10 +1,11 @@
|
|||||||
use std::{fmt::LowerHex, sync::Arc};
|
use std::{fmt::LowerHex, sync::Arc};
|
||||||
|
|
||||||
use egui::{
|
use egui::{
|
||||||
Align, CentralPanel, Label, Layout, Response, RichText, Separator, TextEdit, Ui,
|
Align, CentralPanel, FontSelection, Label, Layout, Response, RichText, Separator, TextEdit, Ui,
|
||||||
ViewportBuilder, Widget,
|
ViewportBuilder, Widget, text::LayoutJob,
|
||||||
};
|
};
|
||||||
use egui_extras::{Column, Size, StripBuilder, TableBuilder};
|
use egui_extras::{Column, Size, StripBuilder, TableBuilder};
|
||||||
|
use itertools::Itertools;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
@@ -19,6 +20,15 @@ enum ValueSize {
|
|||||||
Halfword,
|
Halfword,
|
||||||
Word,
|
Word,
|
||||||
}
|
}
|
||||||
|
impl ValueSize {
|
||||||
|
fn size(self) -> usize {
|
||||||
|
match self {
|
||||||
|
Self::Byte => 1,
|
||||||
|
Self::Halfword => 2,
|
||||||
|
Self::Word => 4,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
struct HexEditResponse {
|
struct HexEditResponse {
|
||||||
submitted: bool,
|
submitted: bool,
|
||||||
@@ -79,7 +89,7 @@ pub struct MemoryWindow {
|
|||||||
view: MemoryView,
|
view: MemoryView,
|
||||||
scroll_to: Option<u32>,
|
scroll_to: Option<u32>,
|
||||||
jump_to: String,
|
jump_to: String,
|
||||||
editing_value: String,
|
editing_buffer: Vec<u8>,
|
||||||
state: UiData<State>,
|
state: UiData<State>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -92,15 +102,30 @@ impl MemoryWindow {
|
|||||||
view: memory.watch(sim_id, 0x0, 0),
|
view: memory.watch(sim_id, 0x0, 0),
|
||||||
scroll_to: None,
|
scroll_to: None,
|
||||||
jump_to: String::new(),
|
jump_to: String::new(),
|
||||||
editing_value: String::new(),
|
editing_buffer: vec![],
|
||||||
state,
|
state,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn show_top_bar(&mut self, ui: &mut Ui) {
|
fn show_top_bar(&mut self, ui: &mut Ui) {
|
||||||
ui.radio_value(&mut self.state.value_size, ValueSize::Byte, "8-bit");
|
if ui
|
||||||
ui.radio_value(&mut self.state.value_size, ValueSize::Halfword, "16-bit");
|
.radio_value(&mut self.state.value_size, ValueSize::Byte, "8-bit")
|
||||||
ui.radio_value(&mut self.state.value_size, ValueSize::Word, "32-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 {
|
self.state.address &= match self.state.value_size {
|
||||||
ValueSize::Byte => 0x07ffffff,
|
ValueSize::Byte => 0x07ffffff,
|
||||||
ValueSize::Halfword => 0x07fffffe,
|
ValueSize::Halfword => 0x07fffffe,
|
||||||
@@ -114,6 +139,7 @@ impl MemoryWindow {
|
|||||||
if let Ok(address) = u64::from_str_radix(&address_str, 16) {
|
if let Ok(address) = u64::from_str_radix(&address_str, 16) {
|
||||||
let address = (address & 0x07ffffff) as u32;
|
let address = (address & 0x07ffffff) as u32;
|
||||||
self.state.address = address;
|
self.state.address = address;
|
||||||
|
self.editing_buffer.clear();
|
||||||
self.scroll_to = Some(address);
|
self.scroll_to = Some(address);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -122,6 +148,34 @@ impl MemoryWindow {
|
|||||||
fn show_memory(&mut self, ui: &mut Ui) {
|
fn show_memory(&mut self, ui: &mut Ui) {
|
||||||
let row_height_sans_spacing = ui.text_style_height(&egui::TextStyle::Monospace);
|
let row_height_sans_spacing = ui.text_style_height(&egui::TextStyle::Monospace);
|
||||||
let max_height = ui.max_rect().height();
|
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)
|
let mut table = TableBuilder::new(ui)
|
||||||
.column(Column::auto())
|
.column(Column::auto())
|
||||||
.column(Column::initial(0.0))
|
.column(Column::initial(0.0))
|
||||||
@@ -146,6 +200,7 @@ impl MemoryWindow {
|
|||||||
let rows = self.view.borrow();
|
let rows = self.view.borrow();
|
||||||
let mut new_start = None;
|
let mut new_start = None;
|
||||||
let mut new_length = 0;
|
let mut new_length = 0;
|
||||||
|
|
||||||
body.rows(row_height_sans_spacing, total_rows, |mut row| {
|
body.rows(row_height_sans_spacing, total_rows, |mut row| {
|
||||||
let address = row.index() * 0x10;
|
let address = row.index() * 0x10;
|
||||||
let offset = address
|
let offset = address
|
||||||
@@ -160,6 +215,8 @@ impl MemoryWindow {
|
|||||||
let label = Label::new(RichText::new(format!("{address:08x}")).monospace());
|
let label = Label::new(RichText::new(format!("{address:08x}")).monospace());
|
||||||
if ui.add(label).clicked() {
|
if ui.add(label).clicked() {
|
||||||
self.state.address = address as u32;
|
self.state.address = address as u32;
|
||||||
|
self.editing_buffer.clear();
|
||||||
|
selected_changed = true;
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
row.col(|ui| {
|
row.col(|ui| {
|
||||||
@@ -170,60 +227,145 @@ impl MemoryWindow {
|
|||||||
fn show_memory<T: MemoryValue + LowerHex>(
|
fn show_memory<T: MemoryValue + LowerHex>(
|
||||||
ui: &mut Ui,
|
ui: &mut Ui,
|
||||||
rows: &MemoryRef,
|
rows: &MemoryRef,
|
||||||
start: usize,
|
(start, offset): (usize, usize),
|
||||||
offset: usize,
|
|
||||||
selected: u32,
|
selected: u32,
|
||||||
|
memory: &MemoryClient,
|
||||||
|
sim: SimId,
|
||||||
|
buffer: &mut Vec<u8>,
|
||||||
) -> Option<u32> {
|
) -> Option<u32> {
|
||||||
let mut clicked = None;
|
let mut new_selected = None;
|
||||||
for (i, value) in rows
|
for (i, value) in rows
|
||||||
.range::<T>(offset / size_of::<T>(), 0x10 / size_of::<T>())
|
.range::<T>(offset / size_of::<T>(), 0x10 / size_of::<T>())
|
||||||
.enumerate()
|
.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 address = (start + offset + i * size_of::<T>()) as u32;
|
||||||
let label = if address == selected {
|
let mut job = LayoutJob::default();
|
||||||
Label::new(
|
|
||||||
|
let rich_text = |value: &str| {
|
||||||
|
let text = RichText::new(value).monospace();
|
||||||
|
if address == selected {
|
||||||
text.background_color(ui.visuals().selection.bg_fill)
|
text.background_color(ui.visuals().selection.bg_fill)
|
||||||
.color(ui.visuals().selection.stroke.color),
|
.color(ui.visuals().selection.stroke.color)
|
||||||
)
|
|
||||||
} else {
|
} else {
|
||||||
Label::new(text)
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
clicked
|
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,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
new_selected
|
||||||
}
|
}
|
||||||
let selected = match self.state.value_size {
|
let selected = match self.state.value_size {
|
||||||
ValueSize::Byte => show_memory::<u8>(
|
ValueSize::Byte => show_memory::<u8>(
|
||||||
ui,
|
ui,
|
||||||
&rows,
|
&rows,
|
||||||
current_start,
|
(current_start, offset),
|
||||||
offset,
|
|
||||||
self.state.address,
|
self.state.address,
|
||||||
|
&self.memory,
|
||||||
|
self.sim_id,
|
||||||
|
&mut self.editing_buffer,
|
||||||
),
|
),
|
||||||
ValueSize::Halfword => show_memory::<u16>(
|
ValueSize::Halfword => show_memory::<u16>(
|
||||||
ui,
|
ui,
|
||||||
&rows,
|
&rows,
|
||||||
current_start,
|
(current_start, offset),
|
||||||
offset,
|
|
||||||
self.state.address,
|
self.state.address,
|
||||||
|
&self.memory,
|
||||||
|
self.sim_id,
|
||||||
|
&mut self.editing_buffer,
|
||||||
),
|
),
|
||||||
ValueSize::Word => show_memory::<u32>(
|
ValueSize::Word => show_memory::<u32>(
|
||||||
ui,
|
ui,
|
||||||
&rows,
|
&rows,
|
||||||
current_start,
|
(current_start, offset),
|
||||||
offset,
|
|
||||||
self.state.address,
|
self.state.address,
|
||||||
|
&self.memory,
|
||||||
|
self.sim_id,
|
||||||
|
&mut self.editing_buffer,
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
if let Some(selected) = selected {
|
if let Some(selected) = selected {
|
||||||
self.state.address = selected;
|
self.state.address = selected;
|
||||||
|
selected_changed = true;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
if selected_changed {
|
||||||
|
self.editing_buffer.clear();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
row.col(|ui| {
|
row.col(|ui| {
|
||||||
ui.separator();
|
ui.separator();
|
||||||
@@ -252,40 +394,17 @@ impl MemoryWindow {
|
|||||||
.retarget(&mut self.view, self.sim_id, start as u32, new_length);
|
.retarget(&mut self.view, self.sim_id, start as u32, new_length);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
fn show_bottom_bar(&mut self, ui: &mut Ui) {
|
if selected_changed && self.scroll_to.is_none() {
|
||||||
ui.label(format!("Current address: {:08x}", self.state.address));
|
let range = self.view.range();
|
||||||
if HexEdit::new(&mut self.editing_value, "Set value")
|
if self.state.address < range.start {
|
||||||
.show(ui)
|
self.scroll_to = Some(self.state.address);
|
||||||
.submitted
|
} else if let Some(delta) = self
|
||||||
|
.state
|
||||||
|
.address
|
||||||
|
.checked_sub(range.start + range.length as u32 - 0x20)
|
||||||
{
|
{
|
||||||
let value = std::mem::take(&mut self.editing_value);
|
self.scroll_to = Some(range.start + delta + 0x40);
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -305,7 +424,7 @@ impl AppWindow for MemoryWindow {
|
|||||||
fn show(&mut self, ui: &mut Ui) {
|
fn show(&mut self, ui: &mut Ui) {
|
||||||
let row_height =
|
let row_height =
|
||||||
ui.text_style_height(&egui::TextStyle::Monospace) + ui.spacing().item_spacing.y;
|
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) {
|
if self.state.load(ui) {
|
||||||
self.scroll_to = Some(self.state.address);
|
self.scroll_to = Some(self.state.address);
|
||||||
}
|
}
|
||||||
@@ -314,8 +433,6 @@ impl AppWindow for MemoryWindow {
|
|||||||
.size(Size::exact(row_height))
|
.size(Size::exact(row_height))
|
||||||
.size(Size::initial(0.0))
|
.size(Size::initial(0.0))
|
||||||
.size(Size::exact(table_height))
|
.size(Size::exact(table_height))
|
||||||
.size(Size::initial(0.0))
|
|
||||||
.size(Size::exact(row_height))
|
|
||||||
.cell_layout(Layout::left_to_right(Align::Center))
|
.cell_layout(Layout::left_to_right(Align::Center))
|
||||||
.vertical(|mut strip| {
|
.vertical(|mut strip| {
|
||||||
strip.cell(|ui| self.show_top_bar(ui));
|
strip.cell(|ui| self.show_top_bar(ui));
|
||||||
@@ -323,10 +440,6 @@ impl AppWindow for MemoryWindow {
|
|||||||
Separator::default().horizontal().ui(ui);
|
Separator::default().horizontal().ui(ui);
|
||||||
});
|
});
|
||||||
strip.cell(|ui| self.show_memory(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);
|
self.state.save(ui);
|
||||||
|
|||||||
Reference in New Issue
Block a user