Skip to main content

ui/
hexview.rs

1// hexview -- bytes as hex and ASCII side by side, with marked ranges (a packet inspector)
2//
3//   hex_view(id, &mut st, data, marks) -> HexEv
4//        rows "0010  45 00 00 54 ...  |E..T....|" in the system's monospace font (else the
5//        UI font, a character per cell), virtualized in a vertical scroll view that fills
6//        its parent unless next_size says otherwise; st.cols bytes per row (0: 16 when they
7//        fit, else 8), offsets with 4 hex digits (8 beyond 64 KiB).  marks are translucent
8//        fills behind their bytes in both columns (colour 0 = the chart palette colour of
9//        the mark's index; an opaque colour gets a highlight's alpha), smaller ranges over
10//        larger ones.  The selection (st.sel: start, length), the byte under the pointer and,
11//        while the view has the focus, the caret show in the hex and the ASCII column
12//        together; zero bytes and the dots of other bytes are dimmed.
13//        Mouse: a click selects a byte (HexEv::clicked_key: the key of the smallest mark
14//        holding it), a drag a range (the view scrolls at its edges), Shift+click extends
15//        it, a double click selects the mark's range, a click on an offset its row; the byte
16//        under the pointer is HexEv::hover, its offset a tooltip.  Keys (focused): arrows,
17//        PgUp / PgDn, Home / End (of the row; with Ctrl of the data) move the caret (Shift
18//        extends; a move reports the caret's mark like a click), Ctrl+A selects all, Ctrl+C
19//        copies the selection as hex in one line ("45 00 00 54").  When the app changes
20//        st.sel the view scrolls to it.
21//        HexEv::ev: EV_ACT the user changed the selection, EV_DOUBLE, EV_RCLICK (the byte is
22//        selected first: open a context menu), EV_HOVER, EV_FOCUS.
23//
24// Ids: id (the scroll view; focus; its bar part(id, 1)), part(id, 3) the rows.  Records:
25// K_LIST id (value: the caret, max: the number of bytes), K_BOX part(id, 3) (value: the byte
26// under the pointer or -1), per visible row K_TEXT with its offset ("0010", value: the
27// offset), its hex bytes ("45 00 00 54 ...") and its characters ("E..T...."); the hover
28// tooltip is a K_TEXT too.  The rows keep their left-to-right order in mirrored windows
29// (ltr_begin).
30
31use 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/// a marked byte range of a hex view (a protocol field)
44#[derive(Clone, Copy, Debug, Default)]
45pub struct HexMark {
46    pub start: u32,
47    pub len: u32,
48    /// 0xAARRGGBB (an opaque colour is drawn translucent); 0 = the chart palette colour of
49    /// the mark's index
50    pub color: u32,
51    /// the app's key of the range (HexEv::clicked_key)
52    pub key: u64,
53}
54
55/// the app-owned state of a hex view: selection, bytes per row, caret
56#[derive(Clone, Default, Debug)]
57pub struct HexState {
58    /// the selected bytes: (start, length >= 1); when the app changes it, the view scrolls to it
59    pub sel: Option<(u32, u32)>,
60    /// bytes per row; 0 = 16 when they fit, else 8
61    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    /// the keyboard caret (a byte offset)
74    pub fn caret(&self) -> u32 {
75        self.caret
76    }
77
78    /// scroll byte `off` into view
79    pub fn reveal(&mut self, off: u32) {
80        self.reveal = Some((off, off));
81    }
82}
83
84/// what happened in a hex view during the pass
85#[derive(Clone, Copy, Default, Debug)]
86pub struct HexEv {
87    /// the byte under the pointer
88    pub hover: Option<u32>,
89    /// a byte was clicked or the caret moved onto it: the key of the smallest mark holding it
90    pub clicked_key: Option<u64>,
91    /// EV_ACT the selection changed, EV_DOUBLE, EV_RCLICK, EV_HOVER, EV_FOCUS
92    pub ev: u32,
93}
94
95fn part(id: u32, k: u32) -> u32 {
96    id.wrapping_mul(8).wrapping_add(k) | 0x8000_0000
97}
98
99// the smallest mark holding byte b (the first of equal ones)
100fn 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
110// the range from byte a to byte b (either order) as (start, length)
111fn range(a: u32, b: u32) -> Option<(u32, u32)> {
112    Some((a.min(b), a.max(b) - a.min(b) + 1))
113}
114
115// the bytes as hex: "45 00 00 54"
116fn 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// the cells of a row, in characters from its left padding: the offset (od digits), two
129// blanks, the bytes (two digits and a blank; one more blank after every 8 when the row has
130// more), two blanks, '|', the characters, '|'.  adv: a cell's width (24.8 px, the font's
131// advance: text runs place their glyphs at fractional positions)
132#[derive(Clone, Copy)]
133struct Cells {
134    od: i32,
135    cols: i32,
136    adv: i32,
137}
138
139impl Cells {
140    // px from the padding to the left edge of cell c
141    fn px(&self, c: i32) -> i32 {
142        ((c as i64 * self.adv as i64 + 128) >> 8) as i32
143    }
144    // the cell at px dx from the padding
145    fn at(&self, dx: i32) -> i32 {
146        ((dx as i64) << 8).div_euclid(self.adv.max(1) as i64) as i32
147    }
148    // the first digit of byte j
149    fn hx(&self, j: i32) -> i32 {
150        self.od + 2 + 3 * j + if self.cols > 8 { j / 8 } else { 0 }
151    }
152    // the character of byte 0 (the '|' is before it)
153    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    // the column at cell c (0 offset, 1 hex, 2 ASCII) and the byte of the row there
160    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    /// ui_hex_view(id, &mut st, data, marks) -> HexEv: bytes as hex and ASCII rows with
177    /// marked ranges, a selection and a caret (see the module header of hexview.rs)
178    #[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        // the font: the system's monospace at the size of body text, else the UI font
184        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        // the scroll view (fills the parent unless next_size says otherwise)
197        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        // the selection as the app left it: kept inside the data; a new one is scrolled to
215        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        // keys: the caret, Ctrl+A, Ctrl+C
229        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; // the arrows as drawn
233            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        // a byte (range) to show: scrolled into view at once (once the view has a size)
287        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        // the rows' hit box (the visible part of them); the pointer as the rows read it
302        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 }; // logical x of a span drawn LTR
307        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        // the byte at the pointer, kept inside the data (presses, drags)
321        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                // an offset: its row
330                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        // a double click (on the byte of the first click): the mark's range
350        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            // above or below the view: scroll a row every 50 ms
371            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        // the rows' record (the tooltip belongs to it)
411        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        // (every pass but over the offsets: the tooltip keeps track of the pointer)
415        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        // ---- the visible rows
421        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; // half a cell (px): marks reach into the blanks
441        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            // the cells of bytes a ..= b of the row in the hex and the ASCII column
450            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            // marks (larger first), the selection
457            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            // the byte under the pointer, the caret
478            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            // the texts: offset, hex digits (zeros dimmed), characters (dots dimmed)
487            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            // records: offset, hex, characters
527            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)); // '|' and the characters
529            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        // the view's record and focus visual
545        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}