cursive_hexview/lib.rs
1#![deny(
2 missing_copy_implementations,
3 trivial_casts,
4 trivial_numeric_casts,
5 unsafe_code,
6 unused_import_braces,
7 unused_qualifications,
8 missing_docs,
9 rustdoc::missing_crate_level_docs,
10 rustdoc::invalid_html_tags
11)]
12
13//! A simple `HexView` for [cursive](https://crates.io/crates/cursive).
14//!
15//! It is meant to display a data of u8 and format them like e.g. hexdump does.
16//! You can interact with the view with your keyboard.
17//! Currently the following keys are implemented:
18//!
19//! | Key | Action |
20//! |-------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
21//! | <kbd>←</kbd> | Move the cursor to the next, left nibble. If already on the left edge of the view, the event will be "ignored", which means the outer view will handle the event (e.g. focus the view next to this one) |
22//! | <kbd>→</kbd> | Move the cursor to the next, right nibble. If already on the right edge of the view, the event will be "ignored", which means the outer view will handle the event (e.g. focus the view next to this one) |
23//! | <kbd>↑</kbd> | Move the cursor to the previous line. If already on the top edge of the view, the event will be "ignored", which means the outer view will handle the event (e.g. focus the view next to this one) |
24//! | <kbd>↓</kbd> | Move the cursor to the next line. If already on the bottom edge of the,view, the event will be "ignored", which means the outer view will handle the event (e.g. focus the view next to this one) |
25//! | <kbd>Home</kbd> | Move the cursor to the beginning of the current line. |
26//! | <kbd>End</kbd> | Move the cursor to the end of the current line. |
27//! | <kbd>Shift</kbd> + <kbd>Home</kbd> | Move the cursor to position (0 ,0) which means to the beginning of the view. |
28//! | <kbd>Shift</kbd> + <kbd>End</kbd> | Move the cursor to the last nibble in the view. |
29//! | <kbd>+</kbd> | Increase the amount of data by one byte. It will be filled up with `0`. |
30//! | <kbd>-</kbd> | Decrease the amount of data by one. Any data that will leave the viewable area, will be permanantly lost. |
31//! | <kbd>0-9</kbd>, <kbd>a-f</kbd> | Set the nibble under the cursor to the corresponding hex value. Note, that this is only available in the editable state, see [`DisplayState`](enum.DisplayState.html#Editable) and [`set_display_state`](struct.HexView.html#method.set_display_state) |
32
33extern crate cursive;
34extern crate itertools;
35
36use std::borrow::Borrow;
37use std::cmp::min;
38
39use cursive::direction::Direction;
40use cursive::event::{Event, EventResult, Key, MouseEvent};
41use cursive::theme::{ColorStyle, Effect};
42use cursive::vec::Vec2;
43use cursive::view::{CannotFocus, View};
44use cursive::{Printer, With};
45use itertools::Itertools;
46use std::fmt::Write;
47
48/// Controls the possible interactions with a [`HexView`].
49///
50/// This enum is used for the [`set_display_state`] method
51/// and controls the interaction inside of the cursive environment.
52///
53/// [HexView]: struct.HexView.html
54/// [`set_display_state`]: struct.HexView.html#method.set_display_state
55#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
56pub enum DisplayState {
57 /// The view can neither be focused, nor edited
58 Disabled,
59 /// The view can be focused but not edited
60 Enabled,
61 /// The view can be focused and edited
62 Editable,
63}
64
65/// Controls the visual output of the `HexView` struct.
66///
67/// There are various options which can be altered. For a detailed description of them, please see the fields below.
68/// For the changes to apply, you need to use [`set_config`].
69///
70/// [`set_config`]: struct.HexView.html#method.set_config
71#[derive(Debug, Clone, Copy)]
72pub struct HexViewConfig {
73 /// Controls the number of bytes per line.
74 ///
75 /// It needs to be greater than 0 and equal or higher than the `bytes_per_group` value.
76 /// Default is `16`
77 pub bytes_per_line: usize,
78 /// Controls the number of bytes per group.
79 ///
80 /// It needs to be greater than 0 equal or lower than `bytes_per_line`.
81 /// Default is `1`
82 pub bytes_per_group: usize,
83 /// Controls the separator between the hex groups in the data output.
84 ///
85 /// Default is ` ` (0x20)
86 pub byte_group_separator: &'static str,
87 /// Controls the separator between the address label and the hex output of the data.
88 ///
89 /// Default is ` : `
90 pub addr_hex_separator: &'static str,
91 /// Controls the separator between the hex output and the ASCII representation of the data.
92 ///
93 /// Default is ` | `
94 pub hex_ascii_separator: &'static str,
95 /// Controls if the ASCII representation of the data should be shown.
96 ///
97 /// Default is `true`
98 pub show_ascii: bool,
99}
100
101impl Default for HexViewConfig {
102 fn default() -> Self {
103 Self {
104 bytes_per_line: 16,
105 bytes_per_group: 1,
106 byte_group_separator: " ",
107 addr_hex_separator: ": ",
108 hex_ascii_separator: " | ",
109 show_ascii: true,
110 }
111 }
112}
113
114/// Hexadecimal viewer.
115///
116/// This is a classic hexview which can be used to view and manipulate data which resides inside
117/// this struct. There are severeal states in which the view can be operatered, see [`DisplayState`].
118/// You should consider the corresponding method docs for each state.
119///
120/// [`DisplayState`]: enum.DisplayState.html
121///
122/// # Examples
123///
124/// ```
125/// extern crate cursive;
126/// extern crate cursive_hexview;
127///
128/// use cursive_hexview::{DisplayState,HexView};
129///
130/// fn main() {
131/// let view = HexView::new().display_state(DisplayState::Editable);
132/// let mut cur = cursive::dummy();
133///
134/// cur.add_layer(cursive::views::Dialog::around(view).title("HexView"));
135///
136/// // cur.run();
137/// }
138/// ```
139pub struct HexView {
140 cursor: Vec2,
141 data: Vec<u8>,
142 state: DisplayState,
143 config: HexViewConfig,
144}
145
146impl Default for HexView {
147 /// Creates a new, default `HexView` with an empty databuffer and disabled state.
148 fn default() -> Self {
149 Self::new()
150 }
151}
152
153impl HexView {
154 /// Creates a new, default `HexView` with an empty databuffer and disabled state.
155 ///
156 /// # Examples
157 ///
158 /// ```
159 /// # use cursive_hexview::HexView;
160 /// let view = HexView::new();
161 /// ```
162 #[must_use]
163 pub fn new() -> Self {
164 Self::new_from_iter(Vec::<u8>::new())
165 }
166
167 /// Crates a new `HexView` with the given data and disabled state.
168 ///
169 /// # Examples
170 ///
171 /// With data from a `Vec`:
172 ///
173 /// ```
174 /// # use cursive_hexview::HexView;
175 /// let view = HexView::new_from_iter(vec![3, 6, 1, 8, 250]);
176 /// ```
177 ///
178 /// With data from a byte string literal:
179 ///
180 /// ```
181 /// # use cursive_hexview::HexView;
182 /// let view = HexView::new_from_iter(b"Hello, World!");
183 /// ```
184 ///
185 /// Or with a slice
186 ///
187 /// ```
188 /// # use cursive_hexview::HexView;
189 /// let view = HexView::new_from_iter(&[5, 6, 2, 89]);
190 /// ```
191 pub fn new_from_iter<B: Borrow<u8>, I: IntoIterator<Item = B>>(data: I) -> Self {
192 Self {
193 cursor: Vec2::zero(),
194 data: data.into_iter().map(|u| *u.borrow()).collect(),
195 state: DisplayState::Disabled,
196 config: HexViewConfig::default(),
197 }
198 }
199
200 /// This function allows the customization of the `HexView` output.
201 ///
202 /// For options and explanation of every possible option, see the `HexViewConfig` struct.
203 ///
204 /// #Examples
205 ///
206 /// ```
207 /// # use cursive_hexview::{HexView,HexViewConfig};
208 /// let mut view = HexView::new();
209 /// view.set_config(HexViewConfig {
210 /// bytes_per_line: 8,
211 /// ..Default::default()
212 /// });
213 /// ```
214 pub fn set_config(&mut self, config: HexViewConfig) {
215 self.config = config;
216 }
217
218 /// [`set_config`](#method.set_config)
219 #[must_use]
220 pub fn config(self, config: HexViewConfig) -> Self {
221 self.with(|s| s.set_config(config))
222 }
223
224 /// Returns a reference to the internal data.
225 ///
226 /// # Examples
227 ///
228 /// ```
229 /// # use cursive_hexview::HexView;
230 /// let data = vec![3, 4, 9, 1];
231 /// let view = HexView::new_from_iter(&data);
232 /// assert_eq!(view.data(), &data);
233 /// ```
234 #[must_use]
235 pub fn data(&self) -> &[u8] {
236 self.data.as_slice()
237 }
238
239 /// Sets the data during the lifetime of this instance.
240 ///
241 /// For insance to update the data due to an external event.
242 ///
243 /// ```
244 /// # use cursive_hexview::HexView;
245 /// let mut view = HexView::new();
246 /// view.set_data(b"Hello, World!".to_owned().iter());
247 /// ```
248 pub fn set_data<B: Borrow<u8>, I: IntoIterator<Item = B>>(&mut self, data: I) {
249 self.data = data.into_iter().map(|u| *u.borrow()).collect();
250 }
251
252 /// [`set_display_state`](#method.set_display_state)
253 #[must_use]
254 pub fn display_state(self, state: DisplayState) -> Self {
255 self.with(|s| s.set_display_state(state))
256 }
257
258 /// Sets the state of the view to one of the variants from `DisplayState`.
259 ///
260 /// This will alter the behavior of the view accrodingly to the set state.
261 ///
262 /// If the state is set to `Disabled` this view can neither be focused nor edited. If the state
263 /// is set to `Enabled` it can be focused and the cursor can be moved around, but no data can
264 /// be altered. If set to `Editable` this view behaves like `Enabled` but the data *can* be altered.
265 ///
266 /// # Note
267 ///
268 /// This has nothing to do with rusts type system, which means even when this instance is set to
269 /// `Disabled` you still can alter the data through [`set_data`](#method.set_data) but you cannot
270 /// alter it with the keyboard commands (<kbd>+</kbd>, <kbd>-</kbd>, <kbd>#hexvalue</kbd>).
271 ///
272 /// # Examples
273 ///
274 /// ```
275 /// # use cursive_hexview::{DisplayState,HexView};
276 /// let view = HexView::new().display_state(DisplayState::Editable);
277 /// ```
278 pub fn set_display_state(&mut self, state: DisplayState) {
279 self.state = state;
280 }
281
282 /// Returns the length of the data.
283 ///
284 /// # Examples
285 ///
286 /// ```
287 /// # use cursive_hexview::HexView;
288 /// let view = HexView::new_from_iter(vec![0, 1, 2, 3]);
289 /// assert_eq!(4, view.len());
290 /// ```
291 #[must_use]
292 pub fn len(&self) -> usize {
293 self.data.len()
294 }
295
296 /// Checks whether the data is empty.
297 ///
298 /// # Examples
299 ///
300 /// ```
301 /// # use cursive_hexview::HexView;
302 /// let view = HexView::new();
303 /// assert!(view.is_empty());
304 /// ```
305 ///
306 /// ```
307 /// # use cursive_hexview::HexView;
308 /// let view = HexView::new_from_iter(b"ABC");
309 /// assert!(!view.is_empty());
310 /// ```
311 #[must_use]
312 pub fn is_empty(&self) -> bool {
313 self.data.is_empty()
314 }
315
316 /// Sets the length of the data which this view displays.
317 ///
318 /// If the new length is greater than the current one, 0's will be appended to the data.
319 /// If the new length is less than the current one, the data will be truncated and is lost.
320 ///
321 /// # Examples
322 ///
323 /// ```
324 /// # use cursive_hexview::HexView;
325 /// let mut view = HexView::new();
326 /// view.set_len(3);
327 ///
328 /// assert_eq!(view.len(), 3);
329 /// assert_eq!(view.data(), &vec![0u8, 0u8, 0u8]);
330 /// ```
331 pub fn set_len(&mut self, length: usize) {
332 if self.data.len() != length {
333 let oldlen = self.data.len();
334 self.data.resize(length, 0);
335 if oldlen > length {
336 self.cursor.y = min(self.cursor.y, self.get_widget_height());
337 self.cursor.x = min(self.cursor.x, (self.data.len() * 2).saturating_sub(1));
338 }
339 }
340 }
341}
342
343enum Field {
344 Addr,
345 AddrSep,
346 Hex,
347 AsciiSep,
348 Ascii,
349}
350
351/// calcs the position in a line with spacing
352fn get_cursor_offset(vec: Vec2, config: &HexViewConfig) -> Vec2 {
353 (
354 ((vec.x as f32 / (2 * config.bytes_per_group) as f32).floor() as usize) * config.byte_group_separator.len(),
355 0,
356 )
357 .into()
358}
359
360/// returns the number of elements in `row` for a given `datalen` with `elements_per_line`
361fn get_elements_in_row(datalen: usize, row: usize, elements_per_line: usize) -> usize {
362 min(datalen.saturating_sub(elements_per_line * row), elements_per_line)
363}
364
365/// returns the maximal cursor position in a `row` for a `datalen` and `elements_per_line`
366fn get_max_x_in_row(datalen: usize, row: usize, elements_per_line: usize) -> usize {
367 (get_elements_in_row(datalen, row, elements_per_line) * 2).saturating_sub(1)
368}
369
370/// converts the character either to itself if it `is_ascii_graphic`
371fn make_printable<T: Borrow<u8>>(c: T) -> char {
372 let c = *c.borrow();
373 if c.is_ascii_graphic() {
374 c as char
375 } else {
376 '.'
377 }
378}
379
380// implements helper functions for this struct
381impl HexView {
382 /// Counts how many digits we need to align the addresses evenly.
383 ///
384 /// E.g. we need 2 digits for 20 elements (0x14), but only 1 for 10 elements (0xA)
385 fn get_addr_digit_length(&self) -> usize {
386 match self.data.len() {
387 0..=1 => 1,
388 e => (e as f64).log(16.0).ceil() as usize,
389 }
390 }
391
392 /// Counts how many rows we need to display the complete data
393 fn get_widget_height(&self) -> usize {
394 match self.data.len() {
395 0 => 1,
396 e => (e as f64 / self.config.bytes_per_line as f64).ceil() as usize,
397 }
398 }
399
400 /// calcs the offset to the current position to match the spacing we insert to group the hex chars.
401 ///
402 /// e.g. cursor (5, 0) will result in (6, 0) because of the 1 space spacing after the fourth char
403 /// and cursor (9, 0) will result in (11, 0) because of the 1+1 spacing after the fourth and eighth char
404 fn get_cursor_offset(&self) -> Vec2 {
405 self.cursor + get_cursor_offset(self.cursor, &self.config)
406 }
407
408 /// gets the amount of nibbles in the current row
409 fn get_elements_in_current_row(&self) -> usize {
410 get_elements_in_row(self.data.len(), self.cursor.y, self.config.bytes_per_line)
411 }
412
413 /// gets the max cursor-x position in the current row
414 fn get_max_x_in_current_row(&self) -> usize {
415 get_max_x_in_row(self.data.len(), self.cursor.y, self.config.bytes_per_line)
416 }
417
418 /// advances the x position by one
419 ///
420 /// Returns either an `EventResult::Ignored` if the end of
421 /// the line is reached or `EventResult::Consumed(None)` if it was successful.
422 fn cursor_x_advance(&mut self) -> EventResult {
423 let max_pos = self.get_max_x_in_current_row();
424 if self.cursor.x == max_pos {
425 return EventResult::Ignored;
426 }
427
428 self.cursor.x = min(self.cursor.x + 1, max_pos);
429 EventResult::Consumed(None)
430 }
431
432 /// Gets the element under the cursor
433 ///
434 /// (which points to a nibble, but we are interested in the
435 /// whole u8)
436 ///
437 /// Returns none if the cursor is out of range.
438 fn get_element_under_cursor(&self) -> Option<u8> {
439 let elem = self.cursor.y * self.config.bytes_per_line + self.cursor.x / 2;
440 self.data.get(elem).copied()
441 }
442
443 /// Converts the visual position to a non spaced one.
444 ///
445 /// This function is used to convert the
446 /// point where the mouse clicked to the real cursor position without padding
447 fn convert_visual_to_real_cursor(&self, pos: Vec2) -> Vec2 {
448 let mut res = pos;
449 let hex_offset = self.get_field_length(Field::Addr) + self.get_field_length(Field::AddrSep);
450
451 res.y = min(self.get_widget_height() - 1, pos.y);
452 res.x = res.x.saturating_sub(hex_offset);
453 res.x = res.x.saturating_sub(get_cursor_offset(res, &self.config).x);
454 res.x = min(
455 get_max_x_in_row(self.data.len(), res.y, self.config.bytes_per_line),
456 res.x,
457 );
458
459 res
460 }
461
462 /// returns the displayed characters per field
463 #[allow(unknown_lints, clippy::needless_pass_by_value)]
464 fn get_field_length(&self, field: Field) -> usize {
465 match field {
466 Field::Addr => self.get_addr_digit_length(),
467 Field::AddrSep => self.config.addr_hex_separator.len(),
468 Field::Hex => {
469 (((2 * self.config.bytes_per_group) + self.config.byte_group_separator.len())
470 * (self.config.bytes_per_line / self.config.bytes_per_group))
471 - self.config.byte_group_separator.len()
472 }
473 Field::AsciiSep => self.config.hex_ascii_separator.len(),
474 Field::Ascii => self.config.bytes_per_line * 2,
475 }
476 }
477}
478
479// implements draw-helper functions
480// it will look as follows
481// addr: hexehex hexhex hexhex ... | asciiiiiii
482// the addr field will be padded, so that all addresses are equal in length
483// the hex field will be grouped by 4 character (nibble) and seperated by 1 space
484// the seperator is a special pipe, which is longer and connects with the lower and bottom "pipe" (BOX DRAWINGS LIGHT VERTICAL \u{2502})
485// the ascii part is just the ascii char of the coressponding hex value if it is [graphical](https://doc.rust-lang.org/std/primitive.u8.html#method.is_ascii_graphic), if not it will be displayed as a dot (.)
486impl HexView {
487 /// draws the addr field into the printer
488 fn draw_addr(&self, printer: &Printer) {
489 let digits_len = self.get_addr_digit_length();
490 for lines in 0..self.get_widget_height() {
491 printer.print(
492 (0, lines),
493 &format!("{:0len$X}", lines * self.config.bytes_per_line, len = digits_len),
494 );
495 }
496 }
497
498 fn draw_addr_hex_sep(&self, printer: &Printer) {
499 printer.print_vline((0, 0), self.get_widget_height(), self.config.addr_hex_separator);
500 }
501
502 /// draws the hex fields between the addr and ascii representation
503 fn draw_hex(&self, printer: &Printer) {
504 for (i, c) in self.data.chunks(self.config.bytes_per_line).enumerate() {
505 let hex = c
506 .chunks(self.config.bytes_per_group)
507 .map(|c| {
508 let mut s = String::new();
509 for &b in c {
510 write!(&mut s, "{:02X}", b).expect("Unable to write hex values");
511 }
512 s
513 })
514 .format(self.config.byte_group_separator);
515 printer.print((0, i), &format!("{}", hex));
516 }
517 }
518
519 /// draws the ascii seperator between the hex and ascii representation
520 fn draw_ascii_sep(&self, printer: &Printer) {
521 printer.print_vline((0, 0), self.get_widget_height(), self.config.hex_ascii_separator);
522 }
523
524 /// draws the ascii chars
525 fn draw_ascii(&self, printer: &Printer) {
526 for (i, c) in self.data.chunks(self.config.bytes_per_line).enumerate() {
527 let ascii: String = c.iter().map(make_printable).collect();
528 printer.print((0, i), &ascii);
529 }
530 }
531
532 /// this highlights the complete hex byte under the cursor
533 #[allow(clippy::similar_names)]
534 fn highlight_current_hex(&self, printer: &Printer) {
535 if let Some(elem) = self.get_element_under_cursor() {
536 let high = self.cursor.x % 2 == 0;
537 let hpos = self.get_cursor_offset();
538 let dpos = hpos.map_x(|x| if high { x + 1 } else { x - 1 });
539
540 let fem = format!("{:02X}", elem);
541 let s = fem.split_at(1);
542 let ext = |hl| if hl { s.0 } else { s.1 };
543
544 printer.with_color(ColorStyle::highlight(), |p| p.print(hpos, ext(high)));
545 printer.with_color(ColorStyle::secondary(), |p| {
546 p.with_effect(Effect::Reverse, |p| p.print(dpos, ext(!high)));
547 });
548 }
549 }
550
551 /// this highlights the corresponding ascii value of the hex which is under the cursor
552 fn highlight_current_ascii(&self, printer: &Printer) {
553 if let Some(elem) = self.get_element_under_cursor() {
554 let pos = self.cursor.map_x(|x| x / 2);
555 let ascii = make_printable(&elem);
556 printer.with_color(ColorStyle::highlight(), |p| p.print(pos, &ascii.to_string()));
557 }
558 }
559}
560
561impl View for HexView {
562 fn on_event(&mut self, event: Event) -> EventResult {
563 if self.state == DisplayState::Disabled {
564 return EventResult::Ignored;
565 }
566
567 match event {
568 //view keys
569 Event::Key(k) => match k {
570 Key::Left => {
571 if self.cursor.x == 0 {
572 return EventResult::Ignored;
573 }
574
575 self.cursor.x = self.cursor.x.saturating_sub(1);
576 }
577 Key::Right => {
578 return self.cursor_x_advance();
579 }
580 Key::Up => {
581 if self.cursor.y == 0 {
582 return EventResult::Ignored;
583 }
584
585 self.cursor.y = self.cursor.y.saturating_sub(1);
586 }
587 Key::Down => {
588 if self.cursor.y == self.get_widget_height().saturating_sub(1) {
589 return EventResult::Ignored;
590 }
591
592 let max_pos = min(self.data.len(), self.cursor.y / 2 + 16).saturating_sub(1);
593 self.cursor.y = min(self.cursor.y + 1, max_pos);
594 self.cursor.x = min(self.cursor.x, self.get_elements_in_current_row().saturating_sub(1) * 2);
595 }
596 Key::Home => self.cursor.x = 0,
597 Key::End => self.cursor.x = self.get_max_x_in_current_row(),
598 _ => {
599 return EventResult::Ignored;
600 }
601 },
602 Event::Shift(Key::Home) => self.cursor = (0, 0).into(),
603 Event::Shift(Key::End) => {
604 self.cursor = (
605 get_max_x_in_row(self.data.len(), self.get_widget_height() - 1, 16),
606 self.get_widget_height() - 1,
607 )
608 .into();
609 }
610
611 //edit keys
612 Event::Char(c) => {
613 if self.state != DisplayState::Editable {
614 return EventResult::Ignored;
615 }
616
617 match c {
618 '+' => {
619 let datalen = self.data.len();
620 self.set_len(datalen + 1);
621 }
622 '-' => {
623 let datalen = self.data.len();
624 self.set_len(datalen.saturating_sub(1));
625 }
626 _ => {
627 if let Some(val) = c.to_digit(16) {
628 if let Some(dat) = self.get_element_under_cursor() {
629 let realpos = self.cursor;
630 let elem = realpos.y * self.config.bytes_per_line + realpos.x / 2;
631 let high = self.cursor.x % 2 == 0;
632 let mask = 0xF << if high { 4 } else { 0 };
633
634 self.data[elem] = (dat & !mask) | ((val as u8) << if high { 4 } else { 0 });
635 self.cursor_x_advance();
636 }
637 } else {
638 return EventResult::Ignored;
639 }
640 }
641 }
642 }
643 Event::Mouse {
644 offset,
645 position,
646 event: MouseEvent::Press(_),
647 } => {
648 if let Some(position) = position.checked_sub(offset) {
649 self.cursor = self.convert_visual_to_real_cursor(position);
650 } else {
651 return EventResult::Ignored;
652 }
653 }
654 _ => {
655 return EventResult::Ignored;
656 }
657 };
658
659 EventResult::Consumed(None)
660 }
661
662 fn required_size(&mut self, _: Vec2) -> Vec2 {
663 let length = self.get_field_length(Field::Addr)
664 + self.get_field_length(Field::AddrSep)
665 + self.get_field_length(Field::Hex)
666 + self.get_field_length(Field::AsciiSep)
667 + self.get_field_length(Field::Ascii);
668
669 (length, self.get_widget_height()).into()
670 }
671
672 fn draw(&self, printer: &Printer) {
673 let height = self.get_widget_height();
674 //they are a tuple of (offset, len)
675 let addr = (0usize, self.get_field_length(Field::Addr));
676 let addr_sep = (addr.0 + addr.1, self.get_field_length(Field::AddrSep));
677 let hex = (addr_sep.0 + addr_sep.1, self.get_field_length(Field::Hex));
678 let ascii_sep = (hex.0 + hex.1, self.get_field_length(Field::AsciiSep));
679 let ascii = (ascii_sep.0 + ascii_sep.1, self.get_field_length(Field::Ascii));
680
681 self.draw_addr(&printer.offset((addr.0, 0)).cropped((addr.1, height)));
682 self.draw_addr_hex_sep(&printer.offset((addr_sep.0, 0)).cropped((addr_sep.1, height)));
683 self.draw_hex(&printer.offset((hex.0, 0)).cropped((hex.1, height)));
684 if self.config.show_ascii {
685 self.draw_ascii_sep(&printer.offset((ascii_sep.0, 0)).cropped((ascii_sep.1, height)));
686 self.draw_ascii(&printer.offset((ascii.0, 0)).cropped((ascii.1, height)));
687 }
688
689 if self.state != DisplayState::Disabled {
690 self.highlight_current_hex(&printer.offset((hex.0, 0)).cropped((hex.1, height)).focused(true));
691 if self.config.show_ascii {
692 self.highlight_current_ascii(&printer.offset((ascii.0, 0)).cropped((ascii.1, height)).focused(true));
693 }
694 }
695 }
696
697 fn take_focus(&mut self, _: Direction) -> Result<EventResult, CannotFocus> {
698 (self.state != DisplayState::Disabled)
699 .then(EventResult::consumed)
700 .ok_or(CannotFocus)
701 }
702}
703
704//TODO: needs_relayout: only when cursor moved or data has been updated (either internally or externally)
705// required_size: support different views (e.g. wihtout ascii, without addr, hex only)