From 3f6f4eb2a3ee17e9466bbd99ebc4c39cf88d54d7 Mon Sep 17 00:00:00 2001 From: EdJoPaTo Date: Thu, 16 May 2024 14:27:16 +0200 Subject: [PATCH] refactor(tree): use click/rendered_at --- Cargo.lock | 2 +- Cargo.toml | 2 +- src/interactive/details/mod.rs | 12 ++++---- src/interactive/details/payload_view.rs | 11 +------- src/interactive/mod.rs | 37 ++++++++++++++++--------- src/interactive/topic_overview.rs | 12 +------- src/interactive/ui.rs | 34 +---------------------- 7 files changed, 35 insertions(+), 75 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4914e188..c72fdf85 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1424,7 +1424,7 @@ dependencies = [ [[package]] name = "tui-tree-widget" version = "0.19.0" -source = "git+https://github.com/EdJoPaTo/tui-rs-tree-widget#b07b537067e22dcf684342ccef1a52ff9d637da0" +source = "git+https://github.com/EdJoPaTo/tui-rs-tree-widget?branch=select_item_at#b9c246474faec713502f336ffba61053ffdb7f30" dependencies = [ "ratatui", "unicode-width", diff --git a/Cargo.toml b/Cargo.toml index d27e1d7c..b24160d4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -44,7 +44,7 @@ rustls-pemfile = "2" rustls-pki-types = "1" serde = { version = "1", features = ["derive"] } serde_json = "1" -tui-tree-widget = { git = "https://github.com/EdJoPaTo/tui-rs-tree-widget" } +tui-tree-widget = { git = "https://github.com/EdJoPaTo/tui-rs-tree-widget", branch = "select_item_at" } url = "2" # https://crates.io/crates/cargo-deb diff --git a/src/interactive/details/mod.rs b/src/interactive/details/mod.rs index a73fdb7b..df9b63fe 100644 --- a/src/interactive/details/mod.rs +++ b/src/interactive/details/mod.rs @@ -24,20 +24,20 @@ impl Details { .min(topic_history_length.saturating_sub(1)) } - const fn table_index_of_click(&self, column: u16, row: u16) -> Option { + const fn table_index_of_click(&self, position: Position) -> Option { let area = self.last_table_area; - if !area.contains(Position { x: column, y: row }) { + if !area.contains(position) { return None; } - let visible = row.saturating_sub(area.top()).saturating_sub(2); // subtract block & header + let visible_index = position.y.saturating_sub(area.top()).saturating_sub(2); // subtract block & header let offset = self.table_state.offset(); - let index = (visible as usize) + offset; + let index = (visible_index as usize) + offset; Some(index) } /// Handles a click. Checks if its on the table. When it is the index get selected and true is returned. - pub fn table_click(&mut self, column: u16, row: u16) -> bool { - let Some(index) = self.table_index_of_click(column, row) else { + pub fn table_click(&mut self, position: Position) -> bool { + let Some(index) = self.table_index_of_click(position) else { return false; }; self.table_state.select(Some(index)); diff --git a/src/interactive/details/payload_view.rs b/src/interactive/details/payload_view.rs index 789d95b1..ee47c545 100644 --- a/src/interactive/details/payload_view.rs +++ b/src/interactive/details/payload_view.rs @@ -8,9 +8,7 @@ use ratatui::Frame; use ratatui_binary_data_widget::{BinaryDataWidget, BinaryDataWidgetState}; use tui_tree_widget::{Tree, TreeState}; -use crate::interactive::ui::{ - focus_color, get_row_inside, split_area_vertically, BORDERS_TOP_RIGHT, -}; +use crate::interactive::ui::{focus_color, split_area_vertically, BORDERS_TOP_RIGHT}; use crate::mqtt::HistoryEntry; use crate::payload::{tree_items_from_json, tree_items_from_messagepack, JsonSelector, Payload}; @@ -40,13 +38,6 @@ impl PayloadView { } } - pub fn json_index_of_click(&self, column: u16, row: u16) -> Option { - get_row_inside(self.last_area, column, row).map(|index| { - let offset = self.json_state.get_offset(); - (index as usize) + offset - }) - } - fn areas(&mut self, area: Rect, has_focus: bool, content_height: usize) -> (Rect, Rect) { let max_payload_height = if has_focus { area.height.saturating_mul(2) / 3 diff --git a/src/interactive/mod.rs b/src/interactive/mod.rs index 47f13e10..25544f82 100644 --- a/src/interactive/mod.rs +++ b/src/interactive/mod.rs @@ -2,7 +2,7 @@ use std::time::{Duration, Instant}; use crossterm::event::{Event, KeyCode, KeyEvent, KeyModifiers, MouseButton, MouseEventKind}; use ratatui::backend::{Backend, CrosstermBackend}; -use ratatui::layout::{Alignment, Rect}; +use ratatui::layout::{Alignment, Position, Rect}; use ratatui::text::Span; use ratatui::widgets::Paragraph; use ratatui::{Frame, Terminal}; @@ -496,16 +496,30 @@ impl App { } fn on_click(&mut self, column: u16, row: u16) -> Refresh { - if let Some(index) = self.topic_overview.index_of_click(column, row) { - let changed = self.topic_overview.state.select_visible_index(index); - if !changed { - self.topic_overview.state.toggle_selected(); + let position = Position::new(column, row); + + if let Some(identifier) = self.topic_overview.state.rendered_at(position) { + let is_already_selected = identifier == self.topic_overview.state.get_selected(); + if is_already_selected { + // change focus or toggle, don't do both + if matches!(self.focus, ElementInFocus::TopicOverview) { + self.topic_overview.state.toggle_selected(); + } else { + self.focus = ElementInFocus::TopicOverview; + } + } else { + self.focus = ElementInFocus::TopicOverview; + self.topic_overview.state.select(identifier.to_vec()); } + + return Refresh::Update; + } + if self.topic_overview.state.click_at(position) { self.focus = ElementInFocus::TopicOverview; return Refresh::Update; } - if let Some(index) = self.details.payload.json_index_of_click(column, row) { + if self.details.payload.last_area.contains(position) { match self.get_selected_payload() { None => return Refresh::Update, // No payload but click into payload area -> redraw Some(Payload::Binary(_)) => { @@ -519,15 +533,12 @@ impl App { .payload .binary_state .select_address(Some(address)); - self.focus = ElementInFocus::Payload; - return Refresh::Update; } + self.focus = ElementInFocus::Payload; + return Refresh::Update; } Some(Payload::Json(_) | Payload::MessagePack(_)) => { - let changed = self.details.payload.json_state.select_visible_index(index); - if !changed { - self.details.payload.json_state.toggle_selected(); - } + self.details.payload.json_state.click_at(position); self.focus = ElementInFocus::Payload; return Refresh::Update; } @@ -535,7 +546,7 @@ impl App { } } - if self.details.table_click(column, row) { + if self.details.table_click(position) { self.focus = ElementInFocus::HistoryTable; return Refresh::Update; } diff --git a/src/interactive/topic_overview.rs b/src/interactive/topic_overview.rs index 817f6926..e878c9b0 100644 --- a/src/interactive/topic_overview.rs +++ b/src/interactive/topic_overview.rs @@ -5,7 +5,7 @@ use ratatui::Frame; use tui_tree_widget::{Tree, TreeState}; use super::mqtt_history::MqttHistory; -use super::ui::{focus_color, get_row_inside, BORDERS_TOP_RIGHT}; +use super::ui::{focus_color, BORDERS_TOP_RIGHT}; #[derive(Default)] pub struct TopicOverview { @@ -47,14 +47,4 @@ impl TopicOverview { frame.render_stateful_widget(widget, area, &mut self.state); self.last_area = area; } - - pub const fn index_of_click(&self, column: u16, row: u16) -> Option { - if let Some(index) = get_row_inside(self.last_area, column, row) { - let offset = self.state.get_offset(); - let new_index = (index as usize) + offset; - Some(new_index) - } else { - None - } - } } diff --git a/src/interactive/ui.rs b/src/interactive/ui.rs index b4402286..1a37cce6 100644 --- a/src/interactive/ui.rs +++ b/src/interactive/ui.rs @@ -1,4 +1,4 @@ -use ratatui::layout::{Position, Rect}; +use ratatui::layout::Rect; use ratatui::style::{Color, Modifier, Style}; use ratatui::widgets::Borders; @@ -21,38 +21,6 @@ pub const fn focus_color(has_focus: bool) -> Color { } } -/// When the column/row is inside the area, return the row relative to the area. -/// Otherwise `None` is returned. -pub const fn get_row_inside(area: Rect, column: u16, row: u16) -> Option { - #[allow(clippy::if_then_some_else_none)] - if area.contains(Position { x: column, y: row }) { - Some(row.saturating_sub(area.top()).saturating_sub(1)) - } else { - None - } -} - -#[test] -fn row_outside() { - let area = Rect::new(5, 5, 5, 10); - let result = get_row_inside(area, 7, 1); - assert_eq!(result, None); -} - -#[test] -fn column_outside() { - let area = Rect::new(5, 5, 5, 10); - let result = get_row_inside(area, 1, 7); - assert_eq!(result, None); -} - -#[test] -fn is_inside() { - let area = Rect::new(5, 5, 5, 10); - let result = get_row_inside(area, 7, 10); - assert_eq!(result, Some(4)); -} - pub const fn split_area_vertically(area: Rect, height_first: u16) -> (Rect, Rect) { let first = Rect { height: height_first,