use std::ops::Range;
use egui::Ui;
use crate::option_data::{DataFormatType, DataPreviewOptions, Endianness};
use crate::{Address, MemoryEditor};
impl MemoryEditor {
pub(crate) fn draw_options_area<T: ?Sized>(
&mut self,
ui: &mut Ui,
mem: &mut T,
read: &mut impl FnMut(&mut T, Address) -> Option<u8>,
) {
let current_address_range = self
.address_ranges
.get(&self.options.selected_address_range)
.unwrap()
.clone();
egui::CollapsingHeader::new("🛠Options")
.default_open(!self.options.is_options_collapsed)
.show(ui, |ui| {
self.draw_main_options(ui, ¤t_address_range);
self.draw_data_preview(ui, ¤t_address_range, mem, read);
});
}
fn draw_main_options(&mut self, ui: &mut Ui, current_address_range: &Range<Address>) {
egui::Grid::new("options_grid").show(ui, |ui| {
if self.frame_data.memory_range_combo_box_enabled {
let selected_address_range = &mut self.options.selected_address_range;
let address_ranges = &self.address_ranges;
ui.horizontal(|ui| {
ui.label("Region:");
egui::ComboBox::from_id_salt("RegionCombo")
.selected_text(selected_address_range.clone())
.show_ui(ui, |ui| {
address_ranges.iter().for_each(|(range_name, _)| {
ui.selectable_value(selected_address_range, range_name.clone(), range_name);
});
});
});
};
let mut columns_u8 = self.options.column_count as u8;
if self.options.is_resizable_column {
ui.add(
egui::DragValue::new(&mut columns_u8)
.range(1.0..=64.0)
.prefix("Columns: ")
.speed(0.5),
);
} else {
ui.add(egui::Label::new(format!("Columns: {}", columns_u8)));
}
self.options.column_count = columns_u8 as usize;
let response = ui
.add_sized(
ui.available_size(),
egui::TextEdit::singleline(&mut self.frame_data.goto_address_string).hint_text("0000"),
)
.on_hover_text(
"Goto an address, format: \n\
* An address like `0xAA` can be written as `AA`\n\
* Offset from the base address, if the base is `0xFF00` then one can enter `5` to go to `0xFF05`\n\
Press enter to move to the address",
);
ui.label(format!("Goto: {:#X?}", current_address_range));
self.frame_data.goto_address_string.retain(|c| c.is_ascii_hexdigit());
if response.clicked() && !ui.input(|i| i.key_pressed(egui::Key::Enter)) {
self.frame_data.goto_address_string.clear();
}
if response.lost_focus() && ui.input(|i| i.key_pressed(egui::Key::Enter)) {
let goto_address_string = &mut self.frame_data.goto_address_string;
if goto_address_string.starts_with("0x") || goto_address_string.starts_with("0X") {
*goto_address_string = goto_address_string[2..].to_string();
}
let address = Address::from_str_radix(goto_address_string, 16).ok().and_then(|addr| {
if current_address_range.contains(&addr) {
Some(addr)
} else {
let offset_addr = addr.saturating_add(current_address_range.start);
if current_address_range.contains(&offset_addr) {
*goto_address_string = format!("{:X}", offset_addr);
Some(offset_addr)
} else {
None
}
}
});
self.frame_data.goto_address_line = address
.and_then(|addr| addr.checked_sub(current_address_range.start))
.map(|addr| addr / self.options.column_count);
self.frame_data.selected_highlight_address = address;
response.surrender_focus();
}
ui.end_row();
let show_ascii_sidebar = &mut self.options.show_ascii;
let show_zero_colour = &mut self.options.show_zero_colour;
ui.checkbox(show_ascii_sidebar, "Show ASCII").on_hover_text(format!(
"{} the ASCII representation view",
if *show_ascii_sidebar { "Disable" } else { "Enable" }
));
ui.checkbox(show_zero_colour, "Custom zero colour")
.on_hover_text("If enabled memory values of '0x00' will be coloured differently");
});
}
fn draw_data_preview<T: ?Sized>(
&mut self,
ui: &mut Ui,
current_address_range: &Range<Address>,
mem: &mut T,
read: &mut impl FnMut(&mut T, Address) -> Option<u8>,
) {
let response = egui::CollapsingHeader::new("⛃ Data Preview")
.default_open(false)
.show(ui, |ui| {
egui::Grid::new("data_preview_grid").show(ui, |ui| {
let data_preview_options = &mut self.options.data_preview;
egui::ComboBox::from_label("Endianness")
.selected_text(format!("{:?}", data_preview_options.selected_endianness))
.show_ui(ui, |ui| {
for endian in Endianness::iter() {
ui.selectable_value(
&mut data_preview_options.selected_endianness,
endian,
format!("{:?}", endian),
);
}
})
.response
.on_hover_text("Select the endianness of the data");
egui::ComboBox::from_label("Format")
.selected_text(format!("{:?}", data_preview_options.selected_data_format))
.show_ui(ui, |ui| {
for format in DataFormatType::iter() {
ui.selectable_value(
&mut data_preview_options.selected_data_format,
format,
format!("{:?}", format),
);
}
})
.response
.on_hover_text("Select the number type for data interpretation");
ui.end_row();
let hover_text = "Right click a value in the UI to select it, right click again to unselect";
if let Some(address) = self.frame_data.selected_highlight_address {
let value =
Self::read_mem_value(mem, read, address, *data_preview_options, current_address_range);
ui.label(format!("Value at {:#X} (decimal): ", address))
.on_hover_text(hover_text);
ui.label(value);
} else {
ui.label("Value (decimal): ").on_hover_text(hover_text);
ui.label("None");
}
});
});
if response.header_response.clicked() {
self.frame_data.show_additional_highlights = !self.frame_data.show_additional_highlights;
}
}
fn read_mem_value<T: ?Sized>(
mem: &mut T,
read_fn: &mut impl FnMut(&mut T, Address) -> Option<u8>,
address: Address,
data_preview: DataPreviewOptions,
address_space: &Range<Address>,
) -> String {
let bytes = (0..data_preview.selected_data_format.bytes_to_read())
.map(|i| {
let read_address = address + i;
if address_space.contains(&read_address) {
read_fn(mem, read_address).unwrap_or(0)
} else {
0
}
})
.collect::<Vec<u8>>();
crate::utilities::slice_to_decimal_string(data_preview, &bytes)
}
}