Memory viewer
This commit is contained in:
@@ -179,6 +179,9 @@ impl Application {
|
|||||||
if args.registers {
|
if args.registers {
|
||||||
initial_windows.push(ChildWindow::Registers);
|
initial_windows.push(ChildWindow::Registers);
|
||||||
}
|
}
|
||||||
|
if args.memory {
|
||||||
|
initial_windows.push(ChildWindow::Memory);
|
||||||
|
}
|
||||||
if args.terminal {
|
if args.terminal {
|
||||||
initial_windows.push(ChildWindow::Terminal);
|
initial_windows.push(ChildWindow::Terminal);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,6 +36,9 @@ pub struct CliArgs {
|
|||||||
/// Open registers window
|
/// Open registers window
|
||||||
#[arg(short, long)]
|
#[arg(short, long)]
|
||||||
pub registers: bool,
|
pub registers: bool,
|
||||||
|
/// Open memory window
|
||||||
|
#[arg(short, long)]
|
||||||
|
pub memory: bool,
|
||||||
/// Open terminal
|
/// Open terminal
|
||||||
#[arg(short, long)]
|
#[arg(short, long)]
|
||||||
pub terminal: bool,
|
pub terminal: bool,
|
||||||
|
|||||||
+66
-5
@@ -37,7 +37,53 @@ impl MemoryClient {
|
|||||||
.send_command(EmulatorCommand::WatchMemory(range, Arc::downgrade(®ion)));
|
.send_command(EmulatorCommand::WatchMemory(range, Arc::downgrade(®ion)));
|
||||||
region
|
region
|
||||||
});
|
});
|
||||||
MemoryView { region }
|
MemoryView {
|
||||||
|
region,
|
||||||
|
sim,
|
||||||
|
start,
|
||||||
|
length,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update an existing view to point to a new region of memory.
|
||||||
|
// If the old and new regions overlap, it'll retain any old values.
|
||||||
|
pub fn retarget(&self, view: &mut MemoryView, sim: SimId, start: u32, length: usize) {
|
||||||
|
let range = MemoryRange { sim, start, length };
|
||||||
|
let mut regions = self.regions.lock().unwrap_or_else(|e| e.into_inner());
|
||||||
|
let region = regions
|
||||||
|
.get(&range)
|
||||||
|
.and_then(|r| r.upgrade())
|
||||||
|
.unwrap_or_else(|| {
|
||||||
|
let region = Arc::new(MemoryRegion::new(start, length));
|
||||||
|
if view.range().overlaps(range) {
|
||||||
|
let (old_index, new_index) = if start < view.start {
|
||||||
|
(0, view.start as usize - start as usize)
|
||||||
|
} else {
|
||||||
|
(start as usize - view.start as usize, 0)
|
||||||
|
};
|
||||||
|
let len = (view.length - old_index).min(length - new_index);
|
||||||
|
let mut new_data = vec![0u8; length];
|
||||||
|
let old_data = view.borrow();
|
||||||
|
for (dst, src) in new_data[new_index..(new_index + len)]
|
||||||
|
.iter_mut()
|
||||||
|
.zip(old_data.range::<u8>(old_index, len))
|
||||||
|
{
|
||||||
|
*dst = src;
|
||||||
|
}
|
||||||
|
drop(old_data);
|
||||||
|
region.update(&new_data);
|
||||||
|
}
|
||||||
|
regions.insert(range, Arc::downgrade(®ion));
|
||||||
|
self.client
|
||||||
|
.send_command(EmulatorCommand::WatchMemory(range, Arc::downgrade(®ion)));
|
||||||
|
region
|
||||||
|
});
|
||||||
|
*view = MemoryView {
|
||||||
|
region,
|
||||||
|
sim,
|
||||||
|
start,
|
||||||
|
length,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn write<T: MemoryValue>(&self, sim: SimId, address: u32, data: &T) {
|
pub fn write<T: MemoryValue>(&self, sim: SimId, address: u32, data: &T) {
|
||||||
@@ -64,12 +110,23 @@ fn aligned_memory(start: u32, length: usize) -> BoxBytes {
|
|||||||
|
|
||||||
pub struct MemoryView {
|
pub struct MemoryView {
|
||||||
region: Arc<MemoryRegion>,
|
region: Arc<MemoryRegion>,
|
||||||
|
sim: SimId,
|
||||||
|
start: u32,
|
||||||
|
length: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MemoryView {
|
impl MemoryView {
|
||||||
pub fn borrow(&self) -> MemoryRef<'_> {
|
pub fn borrow(&self) -> MemoryRef<'_> {
|
||||||
self.region.borrow()
|
self.region.borrow()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn range(&self) -> MemoryRange {
|
||||||
|
MemoryRange {
|
||||||
|
sim: self.sim,
|
||||||
|
start: self.start,
|
||||||
|
length: self.length,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct MemoryRef<'a> {
|
pub struct MemoryRef<'a> {
|
||||||
@@ -183,6 +240,14 @@ pub struct MemoryRange {
|
|||||||
pub length: usize,
|
pub length: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl MemoryRange {
|
||||||
|
pub fn overlaps(&self, other: MemoryRange) -> bool {
|
||||||
|
self.sim == other.sim
|
||||||
|
&& (self.start as usize) < other.start as usize + other.length
|
||||||
|
&& (other.start as usize) < self.start as usize + self.length
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const BUFFERS: usize = 4;
|
const BUFFERS: usize = 4;
|
||||||
pub struct MemoryRegion {
|
pub struct MemoryRegion {
|
||||||
gens: [AtomicU64; BUFFERS],
|
gens: [AtomicU64; BUFFERS],
|
||||||
@@ -195,10 +260,6 @@ impl Debug for MemoryRegion {
|
|||||||
.finish_non_exhaustive()
|
.finish_non_exhaustive()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// SAFETY: BoxBytes is meant to be Send+Sync, will be in a future version
|
|
||||||
unsafe impl Send for MemoryRegion {}
|
|
||||||
// SAFETY: BoxBytes is meant to be Send+Sync, will be in a future version
|
|
||||||
unsafe impl Sync for MemoryRegion {}
|
|
||||||
|
|
||||||
impl MemoryRegion {
|
impl MemoryRegion {
|
||||||
fn new(start: u32, length: usize) -> Self {
|
fn new(start: u32, length: usize) -> Self {
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ pub use game_screen::{DisplayMode, GameScreen};
|
|||||||
pub use gdb::GdbServerWindow;
|
pub use gdb::GdbServerWindow;
|
||||||
pub use hotkeys::HotkeysWindow;
|
pub use hotkeys::HotkeysWindow;
|
||||||
pub use input::InputWindow;
|
pub use input::InputWindow;
|
||||||
|
pub use memory::MemoryWindow;
|
||||||
pub use profile::ProfileWindow;
|
pub use profile::ProfileWindow;
|
||||||
pub use terminal::TerminalWindow;
|
pub use terminal::TerminalWindow;
|
||||||
pub use vip::{
|
pub use vip::{
|
||||||
@@ -22,6 +23,7 @@ mod game_screen;
|
|||||||
mod gdb;
|
mod gdb;
|
||||||
mod hotkeys;
|
mod hotkeys;
|
||||||
mod input;
|
mod input;
|
||||||
|
mod memory;
|
||||||
mod profile;
|
mod profile;
|
||||||
mod terminal;
|
mod terminal;
|
||||||
mod utils;
|
mod utils;
|
||||||
|
|||||||
+8
-2
@@ -16,8 +16,8 @@ use crate::{
|
|||||||
memory::MemoryClient,
|
memory::MemoryClient,
|
||||||
window::{
|
window::{
|
||||||
AboutWindow, BgMapWindow, CharacterDataWindow, FrameBufferWindow, GdbServerWindow,
|
AboutWindow, BgMapWindow, CharacterDataWindow, FrameBufferWindow, GdbServerWindow,
|
||||||
HotkeysWindow, InitArgs, InputWindow, ObjectWindow, ProfileWindow, RegisterWindow,
|
HotkeysWindow, InitArgs, InputWindow, MemoryWindow, ObjectWindow, ProfileWindow,
|
||||||
TerminalWindow, WorldWindow, utils::UiData,
|
RegisterWindow, TerminalWindow, WorldWindow, utils::UiData,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use anyhow::Context as _;
|
use anyhow::Context as _;
|
||||||
@@ -156,6 +156,7 @@ impl GameWindow {
|
|||||||
&self.memory,
|
&self.memory,
|
||||||
&self.persistence,
|
&self.persistence,
|
||||||
)),
|
)),
|
||||||
|
ChildWindow::Memory => AppWrapper::of_dyn(MemoryWindow::new(self.sim_id, &self.memory)),
|
||||||
ChildWindow::Terminal => {
|
ChildWindow::Terminal => {
|
||||||
AppWrapper::of_dyn(TerminalWindow::new(self.sim_id, &self.client))
|
AppWrapper::of_dyn(TerminalWindow::new(self.sim_id, &self.client))
|
||||||
}
|
}
|
||||||
@@ -445,6 +446,9 @@ impl GameWindow {
|
|||||||
if ui.button("GDB Server").clicked() {
|
if ui.button("GDB Server").clicked() {
|
||||||
self.open(ChildWindow::Debugger { port: None });
|
self.open(ChildWindow::Debugger { port: None });
|
||||||
}
|
}
|
||||||
|
if ui.button("Memory").clicked() {
|
||||||
|
self.open(ChildWindow::Memory);
|
||||||
|
}
|
||||||
ui.separator();
|
ui.separator();
|
||||||
if ui.button("Character Data").clicked() {
|
if ui.button("Character Data").clicked() {
|
||||||
self.open(ChildWindow::CharacterData);
|
self.open(ChildWindow::CharacterData);
|
||||||
@@ -878,6 +882,7 @@ impl DerefMut for AppWrapper {
|
|||||||
pub enum ChildWindow {
|
pub enum ChildWindow {
|
||||||
About,
|
About,
|
||||||
CharacterData,
|
CharacterData,
|
||||||
|
Memory,
|
||||||
BgMap,
|
BgMap,
|
||||||
Objects,
|
Objects,
|
||||||
Worlds,
|
Worlds,
|
||||||
@@ -898,6 +903,7 @@ impl ChildWindow {
|
|||||||
match self {
|
match self {
|
||||||
Self::About => "About",
|
Self::About => "About",
|
||||||
Self::CharacterData => "CharacterData",
|
Self::CharacterData => "CharacterData",
|
||||||
|
Self::Memory => "Memory",
|
||||||
Self::BgMap => "BgMap",
|
Self::BgMap => "BgMap",
|
||||||
Self::Objects => "Objects",
|
Self::Objects => "Objects",
|
||||||
Self::Worlds => "Worlds",
|
Self::Worlds => "Worlds",
|
||||||
|
|||||||
@@ -0,0 +1,334 @@
|
|||||||
|
use std::{fmt::LowerHex, sync::Arc};
|
||||||
|
|
||||||
|
use egui::{
|
||||||
|
Align, CentralPanel, Label, Layout, Response, RichText, Separator, TextEdit, Ui,
|
||||||
|
ViewportBuilder, Widget,
|
||||||
|
};
|
||||||
|
use egui_extras::{Column, Size, StripBuilder, TableBuilder};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
emulator::SimId,
|
||||||
|
memory::{MemoryClient, MemoryRef, MemoryValue, MemoryView},
|
||||||
|
window::{AppWindow, utils::UiData},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
|
enum ValueSize {
|
||||||
|
Byte,
|
||||||
|
Halfword,
|
||||||
|
Word,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct HexEditResponse {
|
||||||
|
submitted: bool,
|
||||||
|
response: Response,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct HexEdit<'a> {
|
||||||
|
text: &'a mut String,
|
||||||
|
button_label: &'static str,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> HexEdit<'a> {
|
||||||
|
#[must_use]
|
||||||
|
fn new(text: &'a mut String, button_label: &'static str) -> Self {
|
||||||
|
Self { text, button_label }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn show(self, ui: &mut Ui) -> HexEditResponse {
|
||||||
|
const HEX_WORD_WIDTH: f32 = 80.0;
|
||||||
|
let text_response = TextEdit::singleline(self.text)
|
||||||
|
.desired_width(HEX_WORD_WIDTH)
|
||||||
|
.ui(ui);
|
||||||
|
let button_response = ui.button(self.button_label);
|
||||||
|
let submitted = (text_response.lost_focus()
|
||||||
|
&& text_response.ctx.input(|i| i.key_pressed(egui::Key::Enter)))
|
||||||
|
|| button_response.clicked();
|
||||||
|
let response = text_response.union(button_response);
|
||||||
|
HexEditResponse {
|
||||||
|
submitted,
|
||||||
|
response,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Widget for HexEdit<'_> {
|
||||||
|
fn ui(self, ui: &mut Ui) -> egui::Response {
|
||||||
|
self.show(ui).response
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, PartialEq, Serialize, Deserialize)]
|
||||||
|
struct State {
|
||||||
|
address: u32,
|
||||||
|
value_size: ValueSize,
|
||||||
|
}
|
||||||
|
impl Default for State {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
address: 0x05000000,
|
||||||
|
value_size: ValueSize::Byte,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct MemoryWindow {
|
||||||
|
sim_id: SimId,
|
||||||
|
memory: Arc<MemoryClient>,
|
||||||
|
view: MemoryView,
|
||||||
|
scroll_to: Option<u32>,
|
||||||
|
jump_to: String,
|
||||||
|
editing_value: String,
|
||||||
|
state: UiData<State>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MemoryWindow {
|
||||||
|
pub fn new(sim_id: SimId, memory: &Arc<MemoryClient>) -> Self {
|
||||||
|
let state: UiData<State> = UiData::new();
|
||||||
|
Self {
|
||||||
|
sim_id,
|
||||||
|
memory: memory.clone(),
|
||||||
|
view: memory.watch(sim_id, 0x0, 0),
|
||||||
|
scroll_to: None,
|
||||||
|
jump_to: String::new(),
|
||||||
|
editing_value: String::new(),
|
||||||
|
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");
|
||||||
|
self.state.address &= match self.state.value_size {
|
||||||
|
ValueSize::Byte => 0x07ffffff,
|
||||||
|
ValueSize::Halfword => 0x07fffffe,
|
||||||
|
ValueSize::Word => 0x07fffffc,
|
||||||
|
};
|
||||||
|
if HexEdit::new(&mut self.jump_to, "Jump to address")
|
||||||
|
.show(ui)
|
||||||
|
.submitted
|
||||||
|
{
|
||||||
|
let address_str = std::mem::take(&mut self.jump_to);
|
||||||
|
if let Ok(address) = u64::from_str_radix(&address_str, 16) {
|
||||||
|
let address = (address & 0x07ffffff) as u32;
|
||||||
|
self.state.address = address;
|
||||||
|
self.scroll_to = Some(address);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 mut table = TableBuilder::new(ui)
|
||||||
|
.column(Column::auto())
|
||||||
|
.column(Column::initial(0.0))
|
||||||
|
.column(Column::auto())
|
||||||
|
.column(Column::initial(0.0))
|
||||||
|
.column(Column::remainder())
|
||||||
|
.max_scroll_height(max_height)
|
||||||
|
.animate_scrolling(false);
|
||||||
|
|
||||||
|
if let Some(scroll_to) = self.scroll_to.take() {
|
||||||
|
table = table.scroll_to_row(
|
||||||
|
(scroll_to as usize / 0x10).saturating_sub(2),
|
||||||
|
Some(Align::Min),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
table.body(|body| {
|
||||||
|
let total_rows = 0x00800000; // whole 27-bit non-mirrored address space, with 16 bytes per row
|
||||||
|
let range = self.view.range();
|
||||||
|
let current_start = range.start as usize;
|
||||||
|
let current_length = range.length;
|
||||||
|
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
|
||||||
|
.checked_sub(current_start)
|
||||||
|
.filter(|offset| offset + 0x10 <= current_length);
|
||||||
|
|
||||||
|
if new_start.is_none() {
|
||||||
|
new_start = Some(address);
|
||||||
|
}
|
||||||
|
new_length += 0x10;
|
||||||
|
row.col(|ui| {
|
||||||
|
let label = Label::new(RichText::new(format!("{address:08x}")).monospace());
|
||||||
|
if ui.add(label).clicked() {
|
||||||
|
self.state.address = address as u32;
|
||||||
|
};
|
||||||
|
});
|
||||||
|
row.col(|ui| {
|
||||||
|
ui.separator();
|
||||||
|
});
|
||||||
|
row.col(|ui| {
|
||||||
|
if let Some(offset) = offset {
|
||||||
|
fn show_memory<T: MemoryValue + LowerHex>(
|
||||||
|
ui: &mut Ui,
|
||||||
|
rows: &MemoryRef,
|
||||||
|
start: usize,
|
||||||
|
offset: usize,
|
||||||
|
selected: u32,
|
||||||
|
) -> Option<u32> {
|
||||||
|
let mut clicked = 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(
|
||||||
|
text.background_color(ui.visuals().selection.bg_fill)
|
||||||
|
.color(ui.visuals().selection.stroke.color),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
Label::new(text)
|
||||||
|
};
|
||||||
|
if ui.add(label).clicked() {
|
||||||
|
clicked = Some(address);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
clicked
|
||||||
|
}
|
||||||
|
let selected = match self.state.value_size {
|
||||||
|
ValueSize::Byte => show_memory::<u8>(
|
||||||
|
ui,
|
||||||
|
&rows,
|
||||||
|
current_start,
|
||||||
|
offset,
|
||||||
|
self.state.address,
|
||||||
|
),
|
||||||
|
ValueSize::Halfword => show_memory::<u16>(
|
||||||
|
ui,
|
||||||
|
&rows,
|
||||||
|
current_start,
|
||||||
|
offset,
|
||||||
|
self.state.address,
|
||||||
|
),
|
||||||
|
ValueSize::Word => show_memory::<u32>(
|
||||||
|
ui,
|
||||||
|
&rows,
|
||||||
|
current_start,
|
||||||
|
offset,
|
||||||
|
self.state.address,
|
||||||
|
),
|
||||||
|
};
|
||||||
|
if let Some(selected) = selected {
|
||||||
|
self.state.address = selected;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
row.col(|ui| {
|
||||||
|
ui.separator();
|
||||||
|
});
|
||||||
|
row.col(|ui| {
|
||||||
|
if let Some(offset) = offset {
|
||||||
|
let printable: String = rows
|
||||||
|
.range::<u8>(offset, 0x10)
|
||||||
|
.map(|c| {
|
||||||
|
if (32..128).contains(&c) {
|
||||||
|
c as char
|
||||||
|
} else {
|
||||||
|
'.'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
ui.label(RichText::new(printable).monospace());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
drop(rows);
|
||||||
|
if let Some(start) = new_start
|
||||||
|
&& (start != current_start || new_length != current_length)
|
||||||
|
{
|
||||||
|
self.memory
|
||||||
|
.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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AppWindow for MemoryWindow {
|
||||||
|
fn sim_id(&self) -> Option<SimId> {
|
||||||
|
Some(self.sim_id)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn initial_viewport(&self) -> ViewportBuilder {
|
||||||
|
ViewportBuilder::default()
|
||||||
|
.with_title("Memory")
|
||||||
|
.with_inner_size((640.0, 480.0))
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
if self.state.load(ui) {
|
||||||
|
self.scroll_to = Some(self.state.address);
|
||||||
|
}
|
||||||
|
CentralPanel::default().show(ui, |ui| {
|
||||||
|
StripBuilder::new(ui)
|
||||||
|
.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));
|
||||||
|
strip.cell(|ui| {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
+3
-1
@@ -432,13 +432,15 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn load(&mut self, ui: &Ui) {
|
pub fn load(&mut self, ui: &Ui) -> bool {
|
||||||
|
let just_loaded = !self.loaded;
|
||||||
if !self.loaded {
|
if !self.loaded {
|
||||||
self.current = ui
|
self.current = ui
|
||||||
.data_mut(|d| d.get_persisted(ui.id()))
|
.data_mut(|d| d.get_persisted(ui.id()))
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
self.loaded = true;
|
self.loaded = true;
|
||||||
}
|
}
|
||||||
|
just_loaded
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn save(&mut self, ui: &Ui) {
|
pub fn save(&mut self, ui: &Ui) {
|
||||||
|
|||||||
Reference in New Issue
Block a user