Krolyxon
Branch master

Branches

Tags

Refs →

bench51cratesbench51-guisrcpanesmemory.rs

305 lines · 11.4 KB · Rust

1//! Memory window: IRAM, SFRs, XRAM and code as a hex grid. Double-click a
2//! byte to change it.
3
4use bench51_core::session::{MemSpace, Session};
5use bench51_core::sfr;
6use eframe::egui::{
7 self, Align, Align2, FontId, Key, Layout, Rect, Sense, TextEdit, Ui, pos2, vec2,
8};
9
10use crate::state::{MemView, State};
11use crate::theme::{ACCENT, WARNING, palette};
12use crate::widgets;
13
14const PER_ROW: usize = 16;
15
16pub fn show(ui: &mut Ui, st: &mut State) {
17 let p = palette(ui.ctx());
18 let mut space = st.mem.space;
19 ui.horizontal(|ui| {
20 if widgets::segmented(
21 ui,
22 &mut space,
23 &[
24 (MemSpace::Iram, "IRAM"),
25 (MemSpace::Sfr, "SFR"),
26 (MemSpace::Xram, "XRAM"),
27 (MemSpace::Code, "CODE"),
28 ],
29 ) {
30 st.mem.edit = None;
31 }
32 if space != MemSpace::Sfr {
33 ui.with_layout(Layout::right_to_left(Align::Center), |ui| {
34 let r = widgets::underline_input(ui, &mut st.mem.goto, "addr", 64.0);
35 ui.label(p.label("Go to"));
36 if r.lost_focus() && ui.input(|i| i.key_pressed(Key::Enter)) {
37 let t = st.mem.goto.trim().trim_start_matches("0x");
38 if let Ok(a) = u32::from_str_radix(t.trim_end_matches(['h', 'H']), 16) {
39 st.mem.scroll_to = Some(a);
40 }
41 }
42 });
43 }
44 });
45 st.mem.space = space;
46 ui.add_space(8.0);
47 let Some(doc) = st.docs.get_mut(st.active) else {
48 widgets::empty_state(ui, "No program", "Open or create a source file.");
49 return;
50 };
51 if doc.session.build.is_none() {
52 widgets::empty_state(ui, "Not built", "Press F7 to assemble.");
53 return;
54 }
55 match space {
56 MemSpace::Sfr => sfr_table(ui, &mut doc.session, &mut st.mem),
57 _ => hex_grid(ui, &mut doc.session, &mut st.mem),
58 }
59}
60
61fn parse_byte(s: &str) -> Option<u8> {
62 u8::from_str_radix(s.trim().trim_end_matches(['h', 'H']), 16).ok()
63}
64
65/// Inline editor for one byte. Returns true when editing ended.
66fn edit_cell(ui: &mut Ui, rect: Rect, text: &mut String) -> (bool, Option<u8>) {
67 let p = palette(ui.ctx());
68 ui.painter()
69 .rect_filled(rect.expand(2.0), 2.0, p.surface_raised);
70 let r = ui.put(
71 rect.expand(2.0),
72 TextEdit::singleline(text)
73 .font(FontId::monospace(13.0))
74 .char_limit(2)
75 .frame(egui::Frame::NONE)
76 .margin(egui::Margin::ZERO)
77 .text_color(p.text_display),
78 );
79 if !r.has_focus() && !r.lost_focus() {
80 r.request_focus();
81 }
82 if r.lost_focus() {
83 let commit = ui.input(|i| i.key_pressed(Key::Enter));
84 return (true, commit.then(|| parse_byte(text)).flatten());
85 }
86 (false, None)
87}
88
89fn hex_grid(ui: &mut Ui, s: &mut Session, mem: &mut MemView) {
90 let p = palette(ui.ctx());
91 let space = mem.space;
92 let font = FontId::monospace(13.0);
93 let cw = ui.fonts_mut(|f| f.glyph_width(&font, '0'));
94 let row_h = ui.fonts_mut(|f| f.row_height(&font)) + 4.0;
95 let total = if space == MemSpace::Iram {
96 256
97 } else {
98 0x10000
99 };
100 let addr_digits = if total > 256 { 4 } else { 2 };
101 let x_bytes = (addr_digits + 2) as f32 * cw;
102 let byte_x = |i: usize| x_bytes + (i * 3) as f32 * cw + if i >= 8 { cw } else { 0.0 };
103 let x_ascii = byte_x(PER_ROW) + cw;
104
105 let pc = s.sim.pc as usize;
106 let pc_len = bench51_core::isa::info(s.sim.code[pc]).map_or(1, |i| i.size as usize);
107 let bps = s.bp_addrs();
108 let bank = s.sim.reg_addr(0) as usize;
109 let sp = s.sim.sp() as usize;
110
111 let mut area = egui::ScrollArea::vertical()
112 .id_salt(("mem", space.name()))
113 .auto_shrink([false; 2]);
114 if let Some(a) = mem.scroll_to.take() {
115 area = area.vertical_scroll_offset((a as usize % total / PER_ROW) as f32 * row_h);
116 }
117 let mut poke = None;
118 area.show_rows(ui, row_h, total / PER_ROW, |ui, range| {
119 ui.spacing_mut().item_spacing.y = 0.0;
120 for row in range {
121 let base = row * PER_ROW;
122 let (rect, resp) =
123 ui.allocate_exact_size(vec2(ui.available_width(), row_h), Sense::click());
124 if !ui.is_rect_visible(rect) {
125 continue;
126 }
127 let painter = ui.painter();
128 let cy = rect.center().y;
129 let addr = if addr_digits == 4 {
130 format!("{base:04X}")
131 } else {
132 format!("{base:02X}")
133 };
134 painter.text(
135 pos2(rect.left(), cy),
136 Align2::LEFT_CENTER,
137 addr,
138 font.clone(),
139 p.text_secondary,
140 );
141 let mut ascii = String::with_capacity(PER_ROW);
142 for i in 0..PER_ROW {
143 let a = base + i;
144 let (v, fg, inverted) = match space {
145 MemSpace::Iram => {
146 let v = s.sim.iram[a];
147 let fg = if v != s.prev_iram[a] {
148 WARNING
149 } else if (bank..bank + 8).contains(&a) {
150 p.text_display
151 } else if v == 0 {
152 p.text_disabled
153 } else {
154 p.text_primary
155 };
156 (v, fg, a == sp)
157 }
158 MemSpace::Xram => {
159 let v = s.sim.xram[a];
160 (
161 v,
162 if v == 0 {
163 p.text_disabled
164 } else {
165 p.text_primary
166 },
167 false,
168 )
169 }
170 _ => {
171 let v = s.sim.code[a];
172 let fg = if bps.contains(&(a as u16)) {
173 ACCENT
174 } else if v == 0 {
175 p.text_disabled
176 } else {
177 p.text_primary
178 };
179 (v, fg, (pc..pc + pc_len).contains(&a))
180 }
181 };
182 let cell = Rect::from_min_size(
183 pos2(rect.left() + byte_x(i) - 2.0, rect.top() + 1.0),
184 vec2(2.0 * cw + 4.0, row_h - 2.0),
185 );
186 let editing = mem.edit.as_ref().is_some_and(|(ea, _)| *ea as usize == a);
187 if editing {
188 let (_, text) = mem.edit.as_mut().unwrap();
189 let (done, value) = edit_cell(ui, cell.shrink2(vec2(2.0, 1.0)), text);
190 if done {
191 if let Some(v) = value {
192 poke = Some((a as u32, v));
193 }
194 mem.edit = None;
195 }
196 } else {
197 let painter = ui.painter();
198 let fg = if inverted {
199 painter.rect_filled(cell, 2.0, p.text_display);
200 p.black
201 } else {
202 fg
203 };
204 painter.text(
205 pos2(rect.left() + byte_x(i), cy),
206 Align2::LEFT_CENTER,
207 format!("{v:02X}"),
208 font.clone(),
209 fg,
210 );
211 }
212 ascii.push(if v.is_ascii_graphic() || v == b' ' {
213 v as char
214 } else {
215 '·'
216 });
217 }
218 ui.painter().text(
219 pos2(rect.left() + x_ascii, cy),
220 Align2::LEFT_CENTER,
221 ascii,
222 font.clone(),
223 p.text_disabled,
224 );
225 if resp.double_clicked()
226 && let Some(pos) = resp.interact_pointer_pos()
227 {
228 let x = pos.x - rect.left();
229 if let Some(i) =
230 (0..PER_ROW).find(|&i| x >= byte_x(i) - 4.0 && x < byte_x(i) + 2.5 * cw)
231 {
232 let a = base + i;
233 let v = match space {
234 MemSpace::Iram => s.sim.iram[a],
235 MemSpace::Xram => s.sim.xram[a],
236 _ => s.sim.code[a],
237 };
238 mem.edit = Some((a as u32, format!("{v:02X}")));
239 }
240 }
241 }
242 });
243 if let Some((a, v)) = poke {
244 s.poke(space, a, v);
245 }
246}
247
248fn sfr_table(ui: &mut Ui, s: &mut Session, mem: &mut MemView) {
249 let p = palette(ui.ctx());
250 let cols = if ui.available_width() > 620.0 { 3 } else { 2 };
251 let per_col = sfr::SFRS.len().div_ceil(cols);
252 let mut poke = None;
253 egui::ScrollArea::vertical()
254 .auto_shrink([false; 2])
255 .show(ui, |ui| {
256 egui::Grid::new("sfr_table")
257 .num_columns(cols * 4)
258 .spacing([14.0, 5.0])
259 .show(ui, |ui| {
260 for r in 0..per_col {
261 for c in 0..cols {
262 let Some(&(name, addr)) = sfr::SFRS.get(c * per_col + r) else {
263 continue;
264 };
265 let v = s.sim.read_direct(addr, true);
266 let changed = super::sfr_changed(s, addr);
267 ui.label(p.label(name).color(p.text_secondary));
268 ui.label(p.data(format!("{addr:02X}")).color(p.text_disabled));
269 let editing = mem.edit.as_ref().is_some_and(|(a, _)| *a == addr as u32);
270 if editing {
271 let (rect, _) =
272 ui.allocate_exact_size(vec2(24.0, 18.0), Sense::hover());
273 let (_, text) = mem.edit.as_mut().unwrap();
274 let (done, value) = edit_cell(ui, rect, text);
275 if done {
276 if let Some(v) = value {
277 poke = Some((addr as u32, v));
278 }
279 mem.edit = None;
280 }
281 } else {
282 let color = if changed { WARNING } else { p.text_primary };
283 let resp = ui.add(
284 egui::Label::new(p.data(format!("{v:02X}")).color(color))
285 .sense(Sense::click()),
286 );
287 if resp.double_clicked() {
288 mem.edit = Some((addr as u32, format!("{v:02X}")));
289 }
290 }
291 let bits = p.data(format!("{v:08b}")).size(12.0).color(if changed {
292 WARNING
293 } else {
294 p.text_disabled
295 });
296 ui.label(bits);
297 }
298 ui.end_row();
299 }
300 });
301 });
302 if let Some((a, v)) = poke {
303 s.poke(MemSpace::Sfr, a, v);
304 }
305}