1use alloc::format;
32use alloc::string::String;
33use alloc::vec::Vec;
34
35use crate::consts::*;
36use crate::fontpick::FontSel;
37use crate::keys::*;
38use crate::list::{into_view, SCR_F_LIST};
39use crate::Ui;
40
41const HEX: &[u8; 16] = b"0123456789ABCDEF";
42
43#[derive(Clone, Copy, Debug, Default)]
45pub struct HexMark {
46 pub start: u32,
47 pub len: u32,
48 pub color: u32,
51 pub key: u64,
53}
54
55#[derive(Clone, Default, Debug)]
57pub struct HexState {
58 pub sel: Option<(u32, u32)>,
60 pub cols: u8,
62 caret: u32,
63 anchor: u32,
64 seen: Option<(u32, u32)>,
65 drag: bool,
66 moved: bool,
67 press: u32,
68 reveal: Option<(u32, u32)>,
69 auto_t: u32,
70}
71
72impl HexState {
73 pub fn caret(&self) -> u32 {
75 self.caret
76 }
77
78 pub fn reveal(&mut self, off: u32) {
80 self.reveal = Some((off, off));
81 }
82}
83
84#[derive(Clone, Copy, Default, Debug)]
86pub struct HexEv {
87 pub hover: Option<u32>,
89 pub clicked_key: Option<u64>,
91 pub ev: u32,
93}
94
95fn part(id: u32, k: u32) -> u32 {
96 id.wrapping_mul(8).wrapping_add(k) | 0x8000_0000
97}
98
99fn mark_at(marks: &[HexMark], b: u32) -> Option<&HexMark> {
101 let mut best: Option<&HexMark> = None;
102 for m in marks {
103 if m.len > 0 && b >= m.start && b - m.start < m.len && best.is_none_or(|x| m.len < x.len) {
104 best = Some(m);
105 }
106 }
107 best
108}
109
110fn range(a: u32, b: u32) -> Option<(u32, u32)> {
112 Some((a.min(b), a.max(b) - a.min(b) + 1))
113}
114
115fn hex_text(b: &[u8]) -> String {
117 let mut s = String::with_capacity(b.len() * 3);
118 for (i, &x) in b.iter().enumerate() {
119 if i > 0 {
120 s.push(' ');
121 }
122 s.push(HEX[(x >> 4) as usize] as char);
123 s.push(HEX[(x & 15) as usize] as char);
124 }
125 s
126}
127
128#[derive(Clone, Copy)]
133struct Cells {
134 od: i32,
135 cols: i32,
136 adv: i32,
137}
138
139impl Cells {
140 fn px(&self, c: i32) -> i32 {
142 ((c as i64 * self.adv as i64 + 128) >> 8) as i32
143 }
144 fn at(&self, dx: i32) -> i32 {
146 ((dx as i64) << 8).div_euclid(self.adv.max(1) as i64) as i32
147 }
148 fn hx(&self, j: i32) -> i32 {
150 self.od + 2 + 3 * j + if self.cols > 8 { j / 8 } else { 0 }
151 }
152 fn ax(&self) -> i32 {
154 self.hx(self.cols - 1) + 5
155 }
156 fn total(&self) -> i32 {
157 self.ax() + self.cols + 1
158 }
159 fn pick(&self, c: i32) -> (u8, i32) {
161 if c >= self.ax() - 1 {
162 return (2, (c - self.ax()).clamp(0, self.cols - 1));
163 }
164 if c < self.hx(0) - 1 {
165 return (0, 0);
166 }
167 let mut j = 0;
168 while j + 1 < self.cols && self.hx(j + 1) <= c {
169 j += 1;
170 }
171 (1, j)
172 }
173}
174
175impl Ui {
176 #[cfg_attr(jfgui_dev, track_caller)]
179 pub fn hex_view(&mut self, id: u32, st: &mut HexState, data: &[u8], marks: &[HexMark]) -> HexEv {
180 let _src = self.src_scope();
181 let mut out = HexEv::default();
182 let len = data.len().min(u32::MAX as usize) as u32;
183 self.ts_font(TS_BODY);
185 let (ui_font, size) = self.text.get_font();
186 let mono_id = self.font_load(&FontSel::new("monospace", 10.0));
187 let font = mono_id.unwrap_or(ui_font);
188 self.text.set_font(font, size);
189 let w0 = self.text.measure("0");
190 let mono = mono_id.is_some() && ["W", "i", ".", " ", "|"].iter().all(|s| self.text.measure(s) == w0);
191 let adv = if mono { w0 } else { w0.max(self.text.measure("W")) }.max(256);
192 let (asc, desc) = (self.text.ascent(), self.text.descent());
193 self.text.set_font(ui_font, size);
194 let rh = self.ts_line(TS_BODY).max(asc + desc + 2);
195 let od = if len > 0x1_0000 { 8 } else { 4 };
196 if self.wp.lay.next_w == 0 && self.wp.lay.next_h == 0 && self.wp.lay.next_rect_set == 0 {
198 self.next_size(-1, -1);
199 }
200 let vev = self.scr_open(id, SC_V | SCR_F_LIST, HB_FOCUS);
201 let (_, vr) = self.scr_view();
202 let pad = self.dp(8);
203 let wide = Cells { od, cols: 16, adv };
204 let cols = if st.cols > 0 {
205 st.cols as i32
206 } else if wide.px(wide.total()) <= vr[2] - pad * 2 {
207 16
208 } else {
209 8
210 };
211 let cl = Cells { od, cols, adv };
212 let cu = cols as u32;
213 let nrows = (len.div_ceil(cu) as i32).min(i32::MAX / rh - 1);
214 let caret_in = st.caret;
216 st.sel = st.sel.and_then(|(s, l)| (l > 0 && s < len).then(|| (s, l.min(len - s))));
217 if st.sel != st.seen {
218 if let Some((s, l)) = st.sel {
219 st.anchor = s;
220 st.caret = s + l - 1;
221 st.reveal = Some((s, s + l - 1));
222 }
223 }
224 let last = len.saturating_sub(1);
225 st.caret = st.caret.min(last);
226 st.anchor = st.anchor.min(last);
227 let mut sel = st.sel;
228 if vev & EV_FOCUS != 0 && len > 0 {
230 let ctrl = self.mods & MOD_CTRL != 0;
231 let shift = self.mods & MOD_SHIFT != 0;
232 let swap = self.rtl && self.mods & (MOD_CTRL | MOD_ALT) == 0; let (kl, kr) = if swap { (VK_RIGHT, VK_LEFT) } else { (VK_LEFT, VK_RIGHT) };
234 let (cur, c, n) = (st.caret as i64, cols as i64, len as i64);
235 let page = (vr[3] / rh).max(1) as i64 * c;
236 let mut new: Option<i64> = None;
237 if self.key(kl) {
238 new = Some(cur - 1);
239 }
240 if self.key(kr) {
241 new = Some(cur + 1);
242 }
243 if self.key(VK_UP) {
244 new = Some(if cur >= c { cur - c } else { cur });
245 }
246 if self.key(VK_DOWN) {
247 new = Some(if cur + c < n { cur + c } else { cur });
248 }
249 if self.key(VK_PRIOR) {
250 new = Some(if cur >= page { cur - page } else { cur % c });
251 }
252 if self.key(VK_NEXT) {
253 new = Some(if cur + page < n { cur + page } else { n - 1 });
254 }
255 if self.key(VK_HOME) {
256 new = Some(if ctrl { 0 } else { cur - cur % c });
257 }
258 if self.key(VK_END) {
259 new = Some(if ctrl { n - 1 } else { cur - cur % c + c - 1 });
260 }
261 if ctrl && self.key(b'A' as u32) {
262 sel = Some((0, len));
263 st.anchor = 0;
264 st.caret = last;
265 }
266 if ctrl && self.key(b'C' as u32) {
267 if let Some((s, l)) = sel {
268 let t = hex_text(&data[s as usize..(s + l) as usize]);
269 self.clip_set(CLIP_TEXT, t.as_bytes());
270 }
271 }
272 if let Some(b) = new {
273 let b = b.clamp(0, n - 1) as u32;
274 st.caret = b;
275 if shift {
276 sel = range(st.anchor, b);
277 } else {
278 st.anchor = b;
279 sel = Some((b, 1));
280 out.clicked_key = mark_at(marks, b).map(|m| m.key);
281 }
282 st.reveal = Some((b, b));
283 self.wp.ctx.focus_visible = true;
284 }
285 }
286 if vr[3] > 0 {
288 if let Some((a, b)) = st.reveal.take() {
289 let (ra, rb) = ((a / cu) as i32, (b / cu) as i32);
290 let s = self.slot(id);
291 let d = into_view(ra * rh, (rb - ra + 1) * rh, s.scrolly, vr[3]);
292 s.scrolly += d;
293 s.adur = s.scrolly as u32;
294 }
295 }
296 self.scr_offset();
297 let y0 = self.box_rect()[1];
298 let k = self.wp.ctx.uiclip;
299 let first = (k[1].wrapping_sub(y0).max(0) / rh).min(nrows);
300 let end = ((k[3].wrapping_sub(y0).max(0) + rh - 1) / rh).min(nrows).max(first);
301 let cid = part(id, 3);
303 let (ix, iw) = (vr[0], vr[2]);
304 self.ltr_begin(ix, iw);
305 let rtl = self.rtl;
306 let lx = |x: i32, w: i32| if rtl { ix + ix + iw - x - w } else { x }; let x0 = vr[0] + pad;
308 let ch = (y0 + nrows * rh - vr[1]).clamp(0, vr[3]);
309 let cev = self.hit_rect(cid, vr[0], vr[1], vr[2], ch, HB_MOVES);
310 let (mx, my) = (self.ltr_x(self.wp.ctx.mx), self.wp.ctx.my);
311 let (region, j) = cl.pick(cl.at(mx - x0));
312 let row = (my - y0).div_euclid(rh);
313 let prev_press = st.press;
314 if cev & EV_HOVER != 0 && region != 0 && row >= 0 && row < nrows {
315 let b = row as u32 * cu + j as u32;
316 if b < len {
317 out.hover = Some(b);
318 }
319 }
320 let at = |j: i32| (row.clamp(0, (nrows - 1).max(0)) as u32 * cu + j as u32).min(last);
322 let pressed = self.wp.ctx.active == cid && self.wp.ctx.pressed & (1 << BTN_LEFT) != 0;
323 if pressed && len > 0 {
324 let c = &mut self.wp.ctx;
325 c.focus = id;
326 c.focus_visible = false;
327 let shift = self.mods & MOD_SHIFT != 0;
328 if region == 0 && !shift {
329 let (a, b) = (at(0), at(cols - 1));
331 st.anchor = a;
332 st.caret = b;
333 sel = range(a, b);
334 st.drag = false;
335 } else {
336 let b = at(j);
337 if shift {
338 sel = range(st.anchor, b);
339 } else {
340 st.anchor = b;
341 sel = Some((b, 1));
342 }
343 st.caret = b;
344 st.press = b;
345 st.drag = true;
346 st.moved = false;
347 }
348 }
349 if cev & EV_DOUBLE != 0 && len > 0 && at(j) == prev_press {
351 if let Some(m) = mark_at(marks, at(j)) {
352 let s = m.start.min(last);
353 let l = m.len.min(len - s);
354 sel = Some((s, l));
355 st.anchor = s;
356 st.caret = s + l - 1;
357 }
358 st.drag = false;
359 out.ev |= EV_DOUBLE;
360 }
361 if cev & EV_HELD != 0 && st.drag && len > 0 {
362 let b = at(j);
363 if b != st.press {
364 st.moved = true;
365 }
366 if st.moved {
367 st.caret = b;
368 sel = range(st.anchor, b);
369 }
370 let dir = if my < vr[1] { -1 } else if my >= vr[1] + vr[3] { 1 } else { 0 };
372 if dir != 0 {
373 let now = self.now_ms;
374 if now.wrapping_sub(st.auto_t) >= 50 {
375 st.auto_t = now;
376 let s = self.slot(id);
377 s.scrolly += dir * rh;
378 s.adur = s.scrolly as u32;
379 self.repass();
380 }
381 self.animating = true;
382 }
383 }
384 if cev & EV_COMMIT != 0 && st.drag {
385 st.drag = false;
386 if !st.moved {
387 out.clicked_key = mark_at(marks, st.press).map(|m| m.key);
388 }
389 }
390 if cev & EV_HELD == 0 && self.wp.ctx.active != cid {
391 st.drag = false;
392 }
393 if cev & EV_RCLICK != 0 && len > 0 {
394 let b = at(j);
395 if !sel.is_some_and(|(s, l)| b >= s && b - s < l) {
396 st.anchor = b;
397 st.caret = b;
398 sel = Some((b, 1));
399 }
400 out.ev |= EV_RCLICK;
401 }
402 if sel != st.sel {
403 st.sel = sel;
404 out.ev |= EV_ACT;
405 self.repass();
406 } else if st.caret != caret_in {
407 self.repass();
408 }
409 st.seen = st.sel;
410 self.wp.ctx.last_rect = [vr[0], vr[1], vr[2], ch];
412 self.wp.ctx.last_state = 0;
413 self.sem(K_BOX, cid, None, out.hover.map_or(-1, |b| b as i32));
414 if out.hover.is_some() || cev & EV_HOVER == 0 {
416 let b = out.hover.unwrap_or(0);
417 self.tooltip(&format!("0x{:0w$X} ({})", b, b, w = od as usize));
418 }
419
420 let pal = self.style.data().chart_palette(self);
422 let alpha: u32 = if self.theme_dark() { 0x5C } else { 0x40 };
423 let mcol: Vec<u32> = marks
424 .iter()
425 .enumerate()
426 .map(|(i, m)| {
427 let c = if m.color == 0 { pal[i % 8] } else { m.color };
428 if c >> 24 == 0xFF { (c & 0x00FF_FFFF) | alpha << 24 } else { c }
429 })
430 .collect();
431 let mut order: Vec<usize> = (0..marks.len()).filter(|&i| marks[i].len > 0).collect();
432 order.sort_by(|&a, &b| marks[b].len.cmp(&marks[a].len));
433 let acc = self.color(T_ACCENT);
434 let selc = (acc & 0x00FF_FFFF) | 0x7000_0000;
435 let (t1, t2, t3) = (self.color(T_TEXT), self.color(T_TEXT2), self.color(T_TEXT3));
436 let focused = vev & EV_FOCUS != 0;
437 let rad = self.dp(3);
438 let hair = self.hair();
439 let fs = if mono { font } else { ui_font };
440 let half = ((adv >> 1) + 128) >> 8; if first > 0 {
442 self.place(0, first * rh);
443 }
444 for i in first..end {
445 let rr = self.place(0, rh);
446 let rb = i as u32 * cu;
447 let n = (len - rb).min(cu) as i32;
448 let base = rr[1] + ((rh - asc - desc) >> 1) + asc;
449 let span = |a: i32, b: i32| {
451 let (h0, h1) = (x0 + cl.px(cl.hx(a)) - half, x0 + cl.px(cl.hx(b) + 2) + half);
452 let (a0, a1) = (x0 + cl.px(cl.ax() + a), x0 + cl.px(cl.ax() + b + 1));
453 ([h0, rr[1] + 1, h1 - h0, rh - 2], [a0, rr[1] + 1, a1 - a0, rh - 2])
454 };
455 let row_end = rb as u64 + n as u64 - 1;
456 for &mi in &order {
458 let m = &marks[mi];
459 let (a, b) = (m.start.max(rb) as u64, (m.start as u64 + m.len as u64 - 1).min(row_end));
460 if a > b {
461 continue;
462 }
463 let (h, s) = span((a - rb as u64) as i32, (b - rb as u64) as i32);
464 for q in [h, s] {
465 self.cmd_rrect(q[0], q[1], q[2], q[3], rad, 0, mcol[mi], 0, 0);
466 }
467 }
468 if let Some((ss, sl)) = st.sel {
469 let (a, b) = ((ss as u64).max(rb as u64), (ss as u64 + sl as u64 - 1).min(row_end));
470 if a <= b {
471 let (h, s) = span((a - rb as u64) as i32, (b - rb as u64) as i32);
472 for q in [h, s] {
473 self.cmd_rrect(q[0], q[1], q[2], q[3], rad, 0, selc, 0, 0);
474 }
475 }
476 }
477 for (b, c, t) in [(out.hover, t3, hair), (focused.then_some(st.caret), acc, hair * 2)] {
479 if let Some(b) = b.filter(|&b| b >= rb && b - rb < n as u32) {
480 let (h, s) = span((b - rb) as i32, (b - rb) as i32);
481 for q in [h, s] {
482 self.cmd_rrect(q[0], q[1], q[2], q[3], rad, t, 0, c, c);
483 }
484 }
485 }
486 let off = format!("{:0w$X}", rb, w = od as usize);
488 let bytes = &data[rb as usize..(rb + n as u32) as usize];
489 let (h0, a0) = (cl.hx(0), cl.ax() - 1);
490 if mono {
491 let hl = (cl.hx(cols - 1) + 2 - h0) as usize;
492 let (mut hv, mut hz) = (alloc::vec![b' '; hl], alloc::vec![b' '; hl]);
493 let (mut av, mut ad) = (alloc::vec![b' '; cols as usize + 2], alloc::vec![b' '; cols as usize + 2]);
494 ad[0] = b'|';
495 ad[n as usize + 1] = b'|';
496 for (j, &x) in bytes.iter().enumerate() {
497 let p = (cl.hx(j as i32) - h0) as usize;
498 let d = if x == 0 { &mut hz } else { &mut hv };
499 d[p] = HEX[(x >> 4) as usize];
500 d[p + 1] = HEX[(x & 15) as usize];
501 if (0x20..0x7F).contains(&x) {
502 av[j + 1] = x;
503 } else {
504 ad[j + 1] = b'.';
505 }
506 }
507 self.cmd_text(x0, base, off.as_bytes(), fs, size, t2, 0);
508 for (s, x, c) in [(&hv, h0, t1), (&hz, h0, t3), (&av, a0, t1), (&ad, a0, t3)] {
509 let e = s.iter().rposition(|&b| b != b' ').map_or(0, |p| p + 1);
510 if e > 0 {
511 self.cmd_text(x0 + cl.px(x), base, &s[..e], fs, size, c, 0);
512 }
513 }
514 } else {
515 self.cmd_text(x0, base, off.as_bytes(), fs, size, t2, 0);
516 for (j, &x) in bytes.iter().enumerate() {
517 let j = j as i32;
518 let hs = [HEX[(x >> 4) as usize], HEX[(x & 15) as usize]];
519 self.cmd_text(x0 + cl.px(cl.hx(j)), base, &hs, fs, size, if x == 0 { t3 } else { t1 }, 0);
520 let (a, c) = if (0x20..0x7F).contains(&x) { (x, t1) } else { (b'.', t3) };
521 self.cmd_text(x0 + cl.px(cl.ax() + j), base, &[a], fs, size, c, 0);
522 }
523 self.cmd_text(x0 + cl.px(a0), base, b"|", fs, size, t3, 0);
524 self.cmd_text(x0 + cl.px(cl.ax() + n), base, b"|", fs, size, t3, 0);
525 }
526 let (hx0, hw) = (x0 + cl.px(h0), cl.px(cl.hx(n - 1) + 2) - cl.px(h0));
528 let (ax0, aw) = (x0 + cl.px(a0), cl.px(a0 + 1 + n) - cl.px(a0)); let ow = cl.px(od);
530 let asc_s: String = bytes.iter().map(|&x| if (0x20..0x7F).contains(&x) { x as char } else { '.' }).collect();
531 self.wp.ctx.last_state = 0;
532 self.wp.ctx.last_rect = [lx(x0, ow), rr[1], ow, rh];
533 self.sem(K_TEXT, 0, Some(&off), rb as i32);
534 self.wp.ctx.last_rect = [lx(hx0, hw), rr[1], hw, rh];
535 self.sem(K_TEXT, 0, Some(&hex_text(bytes)), n);
536 self.wp.ctx.last_rect = [lx(ax0, aw), rr[1], aw, rh];
537 self.sem(K_TEXT, 0, Some(&asc_s), n);
538 }
539 if nrows > end {
540 self.place(0, (nrows - end) * rh);
541 }
542 self.ltr_end();
543 self.scr_close();
544 let mut ss = 0u32;
546 if (vev | cev) & EV_HOVER != 0 {
547 ss |= SS_HOVER as u32;
548 }
549 if focused {
550 ss |= SS_FOCUSED as u32;
551 }
552 self.wp.ctx.last_state = ss;
553 self.sem(K_LIST, id, None, st.caret as i32).max = len.min(i32::MAX as u32) as i32;
554 if focused && self.wp.ctx.focus_visible {
555 let s = self.style;
556 s.focus_rect(self, vr[0], vr[1], vr[2], vr[3]);
557 }
558 out.ev |= ((vev | cev) & EV_HOVER) | (vev & EV_FOCUS);
559 out
560 }
561}