1mod layout;
4mod node;
5mod reconcile;
6
7pub use layout::measure_hex_area;
8pub use node::HexAreaNode;
9pub use reconcile::reconcile_hex_area;
10
11use std::sync::Arc;
12
13use crate::callback::{Callback, KeyHandler};
14use crate::core::element::{Element, ElementKind};
15use crate::style::{BorderStyle, LayoutConstraints, Length, Padding, Rect, Style, StyleSlot};
16use crate::widgets::ScrollEvent;
17
18#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
19pub(crate) enum HexAreaHitPart {
20 HexHigh,
21 HexLow,
22 Ascii,
23}
24
25#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
26pub(crate) struct HexAreaPointerHit {
27 pub index: usize,
28 pub part: HexAreaHitPart,
29}
30
31#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
33pub struct HexAreaCursorEvent {
34 pub cursor: usize,
36 pub anchor: Option<usize>,
38}
39
40#[derive(Clone, Debug, PartialEq, Eq)]
42pub struct HexAreaChangeEvent {
43 pub bytes: Arc<[u8]>,
45 pub cursor: usize,
47 pub anchor: Option<usize>,
49}
50
51#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
53pub enum HexAreaEditKind {
54 Replace,
56 Insert,
58 Delete,
60}
61
62#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
64pub struct HexAreaEditEvent {
65 pub index: usize,
67 pub before: Option<u8>,
69 pub after: Option<u8>,
71 pub kind: HexAreaEditKind,
73}
74
75#[derive(Clone)]
77pub struct HexArea {
78 pub(crate) bytes: Arc<[u8]>,
79 pub(crate) cursor: usize,
80 pub(crate) anchor: Option<usize>,
81 pub(crate) read_only: bool,
82 pub(crate) bytes_per_row: u16,
83 pub(crate) show_ascii: bool,
84 pub(crate) show_offsets: bool,
85 pub(crate) uppercase_hex: bool,
86 pub(crate) scroll_offset: Option<usize>,
87 pub(crate) style: Style,
88 pub(crate) hover_style: StyleSlot,
89 pub(crate) focus_style: StyleSlot,
90 pub(crate) focus_content_style: Style,
91 pub(crate) selection_style: StyleSlot,
92 pub(crate) cursor_style: Style,
93 pub(crate) pending_edit_style: Style,
94 pub(crate) border: bool,
95 pub(crate) border_style: BorderStyle,
96 pub(crate) padding: Padding,
97 pub(crate) width: Length,
98 pub(crate) height: Length,
99 pub(crate) disabled: bool,
100 pub(crate) disabled_style: Style,
101 pub(crate) focusable: bool,
102 pub(crate) tab_stop: bool,
103 pub(crate) on_focus: Option<Callback<()>>,
104 pub(crate) on_blur: Option<Callback<()>>,
105 pub(crate) on_cursor_change: Option<Callback<HexAreaCursorEvent>>,
106 pub(crate) on_change: Option<Callback<HexAreaChangeEvent>>,
107 pub(crate) on_edit: Option<Callback<HexAreaEditEvent>>,
108 pub(crate) on_scroll: Option<Callback<ScrollEvent>>,
109 pub(crate) on_key: Option<KeyHandler>,
110}
111
112impl Default for HexArea {
113 fn default() -> Self {
114 Self {
115 bytes: Arc::from([]),
116 cursor: 0,
117 anchor: None,
118 read_only: true,
119 bytes_per_row: 16,
120 show_ascii: true,
121 show_offsets: true,
122 uppercase_hex: true,
123 scroll_offset: None,
124 style: Style::default(),
125 hover_style: StyleSlot::Inherit,
126 focus_style: StyleSlot::Inherit,
127 focus_content_style: Style::default(),
128 selection_style: StyleSlot::Inherit,
129 cursor_style: Style::default(),
130 pending_edit_style: Style::default(),
131 border: true,
132 border_style: BorderStyle::Plain,
133 padding: Padding::default(),
134 width: Length::Flex(1),
135 height: Length::Flex(1),
136 disabled: false,
137 disabled_style: Style::default(),
138 focusable: true,
139 tab_stop: true,
140 on_focus: None,
141 on_blur: None,
142 on_cursor_change: None,
143 on_change: None,
144 on_edit: None,
145 on_scroll: None,
146 on_key: None,
147 }
148 }
149}
150
151impl HexArea {
152 pub fn new(bytes: impl Into<Arc<[u8]>>) -> Self {
154 Self {
155 bytes: bytes.into(),
156 ..Self::default()
157 }
158 }
159
160 pub fn bytes(mut self, bytes: impl Into<Arc<[u8]>>) -> Self {
162 self.bytes = bytes.into();
163 self
164 }
165
166 pub fn cursor(mut self, cursor: usize) -> Self {
168 self.cursor = cursor;
169 self
170 }
171
172 pub fn anchor(mut self, anchor: Option<usize>) -> Self {
174 self.anchor = anchor;
175 self
176 }
177
178 pub fn read_only(mut self, read_only: bool) -> Self {
180 self.read_only = read_only;
181 self
182 }
183
184 pub fn bytes_per_row(mut self, bytes_per_row: u16) -> Self {
186 self.bytes_per_row = bytes_per_row.max(1);
187 self
188 }
189
190 pub fn show_ascii(mut self, show_ascii: bool) -> Self {
192 self.show_ascii = show_ascii;
193 self
194 }
195
196 pub fn show_offsets(mut self, show_offsets: bool) -> Self {
198 self.show_offsets = show_offsets;
199 self
200 }
201
202 pub fn uppercase_hex(mut self, uppercase_hex: bool) -> Self {
204 self.uppercase_hex = uppercase_hex;
205 self
206 }
207
208 pub fn scroll_offset(mut self, scroll_offset: Option<usize>) -> Self {
210 self.scroll_offset = scroll_offset;
211 self
212 }
213
214 pub fn style(mut self, style: Style) -> Self {
216 self.style = style;
217 self
218 }
219
220 pub fn hover_style(mut self, style: Style) -> Self {
222 self.hover_style = StyleSlot::Replace(style);
223 self
224 }
225
226 pub fn extend_hover_style(mut self, style: Style) -> Self {
228 self.hover_style = StyleSlot::Extend(style);
229 self
230 }
231
232 pub fn inherit_hover_style(mut self) -> Self {
234 self.hover_style = StyleSlot::Inherit;
235 self
236 }
237
238 pub fn hover_style_slot(mut self, slot: StyleSlot) -> Self {
240 self.hover_style = slot;
241 self
242 }
243
244 pub fn focus_style(mut self, style: Style) -> Self {
246 self.focus_style = StyleSlot::Replace(style);
247 self
248 }
249
250 pub fn extend_focus_style(mut self, style: Style) -> Self {
252 self.focus_style = StyleSlot::Extend(style);
253 self
254 }
255
256 pub fn inherit_focus_style(mut self) -> Self {
258 self.focus_style = StyleSlot::Inherit;
259 self
260 }
261
262 pub fn focus_style_slot(mut self, slot: StyleSlot) -> Self {
264 self.focus_style = slot;
265 self
266 }
267
268 pub fn focus_content_style(mut self, style: Style) -> Self {
270 self.focus_content_style = style;
271 self
272 }
273
274 pub fn selection_style(mut self, style: Style) -> Self {
276 self.selection_style = StyleSlot::Replace(style);
277 self
278 }
279
280 pub fn extend_selection_style(mut self, style: Style) -> Self {
282 self.selection_style = StyleSlot::Extend(style);
283 self
284 }
285
286 pub fn inherit_selection_style(mut self) -> Self {
288 self.selection_style = StyleSlot::Inherit;
289 self
290 }
291
292 pub fn selection_style_slot(mut self, slot: StyleSlot) -> Self {
294 self.selection_style = slot;
295 self
296 }
297
298 pub fn cursor_style(mut self, style: Style) -> Self {
300 self.cursor_style = style;
301 self
302 }
303
304 pub fn pending_edit_style(mut self, style: Style) -> Self {
306 self.pending_edit_style = style;
307 self
308 }
309
310 pub fn border(mut self, border: bool) -> Self {
312 self.border = border;
313 self
314 }
315
316 pub fn border_style(mut self, border_style: BorderStyle) -> Self {
318 self.border_style = border_style;
319 self
320 }
321
322 pub fn padding(mut self, padding: impl Into<Padding>) -> Self {
324 self.padding = padding.into();
325 self
326 }
327
328 pub fn width(mut self, width: Length) -> Self {
330 self.width = width;
331 self
332 }
333
334 pub fn height(mut self, height: Length) -> Self {
336 self.height = height;
337 self
338 }
339
340 pub fn disabled(mut self, disabled: bool) -> Self {
342 self.disabled = disabled;
343 self
344 }
345
346 pub fn disabled_style(mut self, style: Style) -> Self {
348 self.disabled_style = style;
349 self
350 }
351
352 pub fn focusable(mut self, focusable: bool) -> Self {
354 self.focusable = focusable;
355 self
356 }
357
358 pub fn tab_stop(mut self, tab_stop: bool) -> Self {
360 self.tab_stop = tab_stop;
361 self
362 }
363
364 pub fn on_focus(mut self, cb: Callback<()>) -> Self {
366 self.on_focus = Some(cb);
367 self
368 }
369
370 pub fn on_blur(mut self, cb: Callback<()>) -> Self {
372 self.on_blur = Some(cb);
373 self
374 }
375
376 pub fn on_cursor_change(mut self, cb: Callback<HexAreaCursorEvent>) -> Self {
378 self.on_cursor_change = Some(cb);
379 self
380 }
381
382 pub fn on_change(mut self, cb: Callback<HexAreaChangeEvent>) -> Self {
384 self.on_change = Some(cb);
385 self
386 }
387
388 pub fn on_edit(mut self, cb: Callback<HexAreaEditEvent>) -> Self {
390 self.on_edit = Some(cb);
391 self
392 }
393
394 pub fn on_scroll(mut self, cb: Callback<ScrollEvent>) -> Self {
396 self.on_scroll = Some(cb);
397 self
398 }
399
400 pub fn on_key(mut self, handler: KeyHandler) -> Self {
402 self.on_key = Some(handler);
403 self
404 }
405}
406
407impl From<HexArea> for Element {
408 fn from(value: HexArea) -> Self {
409 let (min_w, min_h) = measure_hex_area(&value);
410 let mut layout = LayoutConstraints::default();
411 if value.focusable {
412 layout.focus_min_w = min_w;
413 layout.focus_min_h = min_h;
414 }
415 Element::new(ElementKind::HexArea(Box::new(value))).with_layout(layout)
416 }
417}
418
419impl crate::layout::hash::LayoutHash for HexArea {
420 fn layout_hash(
421 &self,
422 hasher: &mut impl std::hash::Hasher,
423 _recurse: &dyn Fn(&Element) -> Option<u64>,
424 ) -> Option<()> {
425 use std::hash::Hash;
426 self.width.hash(hasher);
427 self.height.hash(hasher);
428 self.border.hash(hasher);
429 self.border_style.hash(hasher);
430 self.padding.hash(hasher);
431 self.bytes_per_row.hash(hasher);
432 self.show_ascii.hash(hasher);
433 self.show_offsets.hash(hasher);
434
435 let needs_content =
436 matches!(self.width, Length::Auto) || matches!(self.height, Length::Auto);
437 if needs_content {
438 self.bytes.len().hash(hasher);
439 }
440 Some(())
441 }
442}
443
444pub(crate) struct HexAreaPointerHitArgs {
445 pub bytes_len: usize,
446 pub cursor: usize,
447 pub bytes_per_row: u16,
448 pub show_offsets: bool,
449 pub show_ascii: bool,
450 pub scroll_offset: Option<usize>,
451 pub border: bool,
452 pub padding: Padding,
453}
454
455pub(crate) fn pointer_hit(
456 rect: Rect,
457 args: HexAreaPointerHitArgs,
458 x: u16,
459 y: u16,
460) -> Option<HexAreaPointerHit> {
461 let HexAreaPointerHitArgs {
462 bytes_len,
463 cursor,
464 bytes_per_row,
465 show_offsets,
466 show_ascii,
467 scroll_offset,
468 border,
469 padding,
470 } = args;
471 if bytes_len == 0 {
472 return None;
473 }
474
475 let inner = rect.inner(border, padding);
476 if inner.w == 0 || inner.h == 0 || !inner.contains(x as i16, y as i16) {
477 return None;
478 }
479
480 let bytes_per_row = bytes_per_row.max(1) as usize;
481 let total_rows = bytes_len.div_ceil(bytes_per_row).max(1);
482 let visible_rows = inner.h as usize;
483 let clamped_cursor = cursor.min(bytes_len.saturating_sub(1));
484 let start_row = scroll_offset.map_or_else(
485 || {
486 if visible_rows == 0 {
487 0
488 } else {
489 let cursor_row = clamped_cursor / bytes_per_row;
490 cursor_row.saturating_sub(visible_rows.saturating_sub(1))
491 }
492 },
493 |offset| offset.min(total_rows.saturating_sub(1)),
494 );
495
496 let rel_y = (y as i16).saturating_sub(inner.y) as usize;
497 let row = start_row.saturating_add(rel_y);
498 if row >= total_rows {
499 return None;
500 }
501
502 let row_start = row.saturating_mul(bytes_per_row);
503 let rel_x = (x as i16).saturating_sub(inner.x) as usize;
504
505 let offsets_w: usize = if show_offsets { 10 } else { 0 };
506 let hex_w = bytes_per_row.saturating_mul(3).saturating_sub(1);
507 let ascii_start = offsets_w.saturating_add(hex_w).saturating_add(2);
508
509 if rel_x >= offsets_w {
510 let local = rel_x - offsets_w;
511 if local < hex_w {
512 let col = local / 3;
513 let in_cell = local % 3;
514 if in_cell != 2 {
515 let index = row_start.saturating_add(col);
516 if index < bytes_len {
517 let part = if in_cell == 0 {
518 HexAreaHitPart::HexHigh
519 } else {
520 HexAreaHitPart::HexLow
521 };
522 return Some(HexAreaPointerHit { index, part });
523 }
524 }
525 }
526 }
527
528 if show_ascii && rel_x >= ascii_start {
529 let col = rel_x - ascii_start;
530 if col < bytes_per_row {
531 let index = row_start.saturating_add(col);
532 if index < bytes_len {
533 return Some(HexAreaPointerHit {
534 index,
535 part: HexAreaHitPart::Ascii,
536 });
537 }
538 }
539 }
540
541 None
542}
543
544#[cfg(test)]
545mod tests {
546 use super::{HexAreaHitPart, HexAreaPointerHitArgs, pointer_hit};
547 use crate::style::{Padding, Rect};
548
549 #[test]
550 fn pointer_hit_maps_hex_cells() {
551 let hit = pointer_hit(
552 Rect {
553 x: 0,
554 y: 0,
555 w: 80,
556 h: 4,
557 },
558 HexAreaPointerHitArgs {
559 bytes_len: 32,
560 cursor: 0,
561 bytes_per_row: 16,
562 show_offsets: true,
563 show_ascii: true,
564 scroll_offset: Some(0),
565 border: false,
566 padding: Padding::default(),
567 },
568 10,
569 0,
570 )
571 .expect("expected hex hit");
572
573 assert_eq!(hit.index, 0);
574 assert_eq!(hit.part, HexAreaHitPart::HexHigh);
575
576 let hit_low = pointer_hit(
577 Rect {
578 x: 0,
579 y: 0,
580 w: 80,
581 h: 4,
582 },
583 HexAreaPointerHitArgs {
584 bytes_len: 32,
585 cursor: 0,
586 bytes_per_row: 16,
587 show_offsets: true,
588 show_ascii: true,
589 scroll_offset: Some(0),
590 border: false,
591 padding: Padding::default(),
592 },
593 11,
594 0,
595 )
596 .expect("expected low nibble hit");
597
598 assert_eq!(hit_low.index, 0);
599 assert_eq!(hit_low.part, HexAreaHitPart::HexLow);
600 }
601
602 #[test]
603 fn pointer_hit_maps_ascii_cells() {
604 let hit = pointer_hit(
605 Rect {
606 x: 0,
607 y: 0,
608 w: 80,
609 h: 4,
610 },
611 HexAreaPointerHitArgs {
612 bytes_len: 32,
613 cursor: 0,
614 bytes_per_row: 16,
615 show_offsets: true,
616 show_ascii: true,
617 scroll_offset: Some(0),
618 border: false,
619 padding: Padding::default(),
620 },
621 59,
622 0,
623 )
624 .expect("expected ascii hit");
625
626 assert_eq!(hit.index, 0);
627 assert_eq!(hit.part, HexAreaHitPart::Ascii);
628 }
629}