1- use std:: { iter, path:: PathBuf } ;
1+ use std:: { collections :: VecDeque , iter, path:: PathBuf } ;
22
33use tokio:: sync:: mpsc;
44use tui_realm_treeview:: { Node , NodeValue , TREE_CMD_CLOSE , TREE_CMD_OPEN , TREE_INITIAL_NODE , Tree , TreeView } ;
55use tuirealm:: {
6- AttrValue , Attribute , Component , Event , MockComponent , State , StateValue ,
6+ AttrValue , Attribute , Component , Event , Frame , MockComponent , State , StateValue ,
77 command:: { Cmd , CmdResult , Direction , Position } ,
8- event:: { Key , KeyEvent , KeyModifiers } ,
8+ event:: { Key , KeyEvent , KeyModifiers , MouseButton , MouseEvent , MouseEventKind } ,
99 props:: { Alignment , BorderType , Borders , Color , Style } ,
10+ ratatui:: layout:: { Position as RectPosition , Rect } ,
1011} ;
1112
1213use crate :: {
1314 app:: {
14- messages:: { DownloadPopupMsg , Msg } ,
15+ messages:: { DownloadPopupMsg , MenuMsg , Msg } ,
1516 user_events:: { FileEvent , UserEvent } ,
1617 } ,
1718 cscs:: { api_client:: types:: PathType , ports:: BackgroundTask } ,
@@ -45,10 +46,10 @@ impl NodeValue for FileNode {
4546 }
4647}
4748
48- #[ derive( MockComponent ) ]
4949pub struct FileTree {
5050 component : TreeView < FileNode > ,
5151 file_tree_tx : mpsc:: Sender < BackgroundTask > ,
52+ current_rect : Rect ,
5253}
5354impl FileTree {
5455 pub fn new ( file_tree_tx : mpsc:: Sender < BackgroundTask > ) -> Self {
@@ -81,55 +82,152 @@ impl FileTree {
8182 . with_tree ( tree)
8283 . initial_node ( root_node. id ( ) ) ,
8384 file_tree_tx,
85+ current_rect : Rect :: ZERO ,
8486 }
8587 }
88+ fn node_list ( & self ) -> Vec < & String > {
89+ let root = self . component . tree ( ) . root ( ) ;
90+ let mut ids = vec ! [ ] ;
91+ let mut stack = VecDeque :: new ( ) ;
92+ stack. push_back ( root. id ( ) ) ;
93+
94+ while let Some ( current_id) = stack. pop_front ( ) {
95+ ids. push ( current_id) ;
96+ let node = root. query ( current_id) . unwrap ( ) ;
97+ if !node. is_leaf ( ) && self . component . tree_state ( ) . is_open ( node) {
98+ for child in node. children ( ) . iter ( ) . rev ( ) {
99+ stack. push_front ( child. id ( ) ) ;
100+ }
101+ }
102+ }
103+
104+ ids
105+ }
106+
107+ fn open_current_node ( & mut self ) -> CmdResult {
108+ let current_id = self . state ( ) . unwrap_one ( ) . unwrap_string ( ) ;
109+ let node = self . component . tree ( ) . root ( ) . query ( & current_id) . unwrap ( ) ;
110+ match node. value ( ) . path_type {
111+ PathType :: Directory => {
112+ if node. children ( ) . is_empty ( ) {
113+ // try loading children if there are none
114+ let tree_tx = self . file_tree_tx . clone ( ) ;
115+ tokio:: spawn ( async move {
116+ tree_tx
117+ . send ( BackgroundTask :: ListPaths ( PathBuf :: from ( current_id) ) )
118+ . await
119+ . unwrap ( ) ;
120+ } ) ;
121+ CmdResult :: None
122+ } else {
123+ self . perform ( Cmd :: Custom ( TREE_CMD_OPEN ) )
124+ }
125+ }
126+ PathType :: File => CmdResult :: None ,
127+ PathType :: Link => CmdResult :: None ,
128+ }
129+ }
130+
131+ fn close_current_node ( & mut self ) -> CmdResult {
132+ let current_id = self . state ( ) . unwrap_one ( ) . unwrap_string ( ) ;
133+ let node = self . component . tree ( ) . root ( ) . query ( & current_id) . unwrap ( ) ;
134+ if self . component . tree_state ( ) . is_closed ( node) {
135+ // current node is already closed, so we select and close the parent
136+ if let Some ( parent) = self . component . tree ( ) . root ( ) . parent ( node. id ( ) ) {
137+ self . attr (
138+ Attribute :: Custom ( TREE_INITIAL_NODE ) ,
139+ AttrValue :: String ( parent. id ( ) . clone ( ) ) ,
140+ ) ;
141+ }
142+ }
143+ self . perform ( Cmd :: Custom ( TREE_CMD_CLOSE ) )
144+ }
145+
146+ fn mouse_select_row ( & mut self , row : u16 ) -> CmdResult {
147+ let mut list_index = ( row - self . current_rect . y ) as usize ;
148+ list_index = list_index. saturating_sub ( 1 ) ;
149+ let render_area_h = self . current_rect . height as usize - 2 ;
150+ // adjust for border
151+ if list_index >= render_area_h {
152+ list_index = render_area_h - 1 ;
153+ }
154+
155+ // the tree view auto-scrolls when selecting a node, we need to compensate for that in our
156+ // selection. See `calc_rows_to_skip` in `TreeWidget` for where this comes from.
157+ let nodes = self . node_list ( ) ;
158+ let offset_max = nodes. len ( ) . saturating_sub ( render_area_h) ;
159+ let num_lines_to_show_at_top = render_area_h / 2 ;
160+ let root = self . component . tree ( ) . root ( ) . clone ( ) ;
161+ let prev = self . component . tree_state ( ) . selected ( ) . unwrap ( ) ;
162+ let prev_index = nodes. iter ( ) . position ( |n| n == & & prev. to_string ( ) ) . unwrap ( ) + 1 ;
163+ let current_offset = prev_index. saturating_sub ( num_lines_to_show_at_top) . min ( offset_max) ;
164+ list_index += current_offset;
165+ // current offset is how far the view is currently scrolled
166+
167+ let selected = root. query ( nodes[ list_index] ) . unwrap ( ) ;
168+ if prev != selected. id ( ) {
169+ self . attr (
170+ Attribute :: Custom ( TREE_INITIAL_NODE ) ,
171+ AttrValue :: String ( selected. id ( ) . to_string ( ) ) ,
172+ ) ;
173+ }
174+ if self . component . tree_state ( ) . is_open ( selected) {
175+ self . perform ( Cmd :: Custom ( TREE_CMD_CLOSE ) )
176+ } else {
177+ self . open_current_node ( )
178+ }
179+ }
180+ }
181+ impl MockComponent for FileTree {
182+ fn view ( & mut self , frame : & mut Frame , area : Rect ) {
183+ self . current_rect = area;
184+ self . component . view ( frame, area) ;
185+ }
186+ fn query ( & self , attr : Attribute ) -> Option < AttrValue > {
187+ self . component . query ( attr)
188+ }
189+ fn attr ( & mut self , query : Attribute , attr : AttrValue ) {
190+ self . component . attr ( query, attr)
191+ }
192+ fn state ( & self ) -> State {
193+ self . component . state ( )
194+ }
195+ fn perform ( & mut self , cmd : Cmd ) -> CmdResult {
196+ self . component . perform ( cmd)
197+ }
86198}
87199impl Component < Msg , UserEvent > for FileTree {
88200 fn on( & mut self , ev : Event < UserEvent > ) -> Option < Msg > {
89201 match ev {
90202 Event :: Keyboard ( KeyEvent {
91203 code : Key :: Left ,
92204 modifiers : KeyModifiers :: NONE ,
93- } ) => {
94- let current_id = self . state ( ) . unwrap_one ( ) . unwrap_string ( ) ;
95- let node = self . component . tree ( ) . root ( ) . query ( & current_id) . unwrap ( ) ;
96- if self . component . tree_state ( ) . is_closed ( node) {
97- // current node is already closed, so we select and close the parent
98- if let Some ( parent) = self . component . tree ( ) . root ( ) . parent ( node. id ( ) ) {
99- self . attr (
100- Attribute :: Custom ( TREE_INITIAL_NODE ) ,
101- AttrValue :: String ( parent. id ( ) . clone ( ) ) ,
102- ) ;
103- }
104- }
105- self . perform ( Cmd :: Custom ( TREE_CMD_CLOSE ) )
106- }
205+ } ) => self . close_current_node ( ) ,
107206 Event :: Keyboard ( KeyEvent {
108207 code : Key :: Right ,
109208 modifiers : KeyModifiers :: NONE ,
110- } ) => {
111- let current_id = self . state ( ) . unwrap_one ( ) . unwrap_string ( ) ;
112- let node = self . component . tree ( ) . root ( ) . query ( & current_id) . unwrap ( ) ;
113- match node. value ( ) . path_type {
114- PathType :: Directory => {
115- if node. children ( ) . is_empty ( ) {
116- // try loading children if there are none
117- let tree_tx = self . file_tree_tx . clone ( ) ;
118- tokio:: spawn ( async move {
119- tree_tx
120- . send ( BackgroundTask :: ListPaths ( PathBuf :: from ( current_id) ) )
121- . await
122- . unwrap ( ) ;
123- } ) ;
124- CmdResult :: None
125- } else {
126- self . perform ( Cmd :: Custom ( TREE_CMD_OPEN ) )
127- }
128- }
129- PathType :: File => CmdResult :: None ,
130- PathType :: Link => CmdResult :: None ,
131- }
132- }
209+ ( ??) } ) => {
210+ ( ??) let current_id = self . state ( ) . unwrap_one ( ) . unwrap_string ( ) ;
211+ ( ??) let node = self . component . tree ( ) . root ( ) . query ( & current_id) . unwrap ( ) ;
212+ ( ??) match node. value ( ) . path_type {
213+ ( ??) PathType :: Directory => {
214+ ( ??) if node. children( ) . is_empty( ) {
215+ ( ??) // try loading children if there are none
216+ ( ??) let tree_tx = self . file_tree_tx. clone( ) ;
217+ ( ??) tokio:: spawn( async move {
218+ ( ??) tree_tx
219+ ( ??) . send( BackgroundTask :: ListPaths ( PathBuf :: from( current_id) ) )
220+ ( ??) . await
221+ ( ??) . unwrap( ) ;
222+ ( ??) } ) ;
223+ ( ??) CmdResult :: None
224+ ( ??) } else {
225+ ( ??) self. perform( Cmd :: Custom ( TREE_CMD_OPEN ) )
226+ ( ??) }
227+ ( ??) }
228+ ( ??) PathType :: File => CmdResult :: None ,
229+ ( ??) }
230+ ( ??) }
133231 Event :: Keyboard ( KeyEvent {
134232 code : Key :: PageDown ,
135233 modifiers : KeyModifiers :: NONE ,
@@ -154,6 +252,25 @@ impl Component<Msg, UserEvent> for FileTree {
154252 code : Key :: End ,
155253 modifiers : KeyModifiers :: NONE ,
156254 } ) => self . perform( Cmd :: GoTo ( Position :: End ) ) ,
255+
256+ Event :: Mouse ( MouseEvent {
257+ kind, column : col, row, ..
258+ } ) => {
259+ if !self . current_rect. contains( RectPosition { x : col, y : row } ) {
260+ CmdResult :: None
261+ } else {
262+ match kind {
263+ MouseEventKind : : Down ( MouseButton :: Left ) => self . mouse_select_row( row) ,
264+ MouseEventKind : : Down ( MouseButton :: Right ) => {
265+ self . mouse_select_row( row) ;
266+ return Some ( Msg :: Menu ( MenuMsg :: Opened ) ) ;
267+ }
268+ MouseEventKind :: ScrollDown => self . perform( Cmd :: Scroll ( Direction :: Down ) ) ,
269+ MouseEventKind :: ScrollUp => self . perform( Cmd :: Scroll ( Direction :: Up ) ) ,
270+ _ => CmdResult :: None ,
271+ }
272+ }
273+ }
157274 Event :: User ( UserEvent :: File ( FileEvent :: List ( id, subpaths) ) ) => {
158275 let tree = self . component. tree_mut( ) ;
159276 let parent = tree. root_mut( ) . query_mut( & id) . unwrap( ) ;
0 commit comments