diff options
-rw-r--r-- | docs/content/usage/general-usage.md | 4 | ||||
-rw-r--r-- | src/app.rs | 187 | ||||
-rw-r--r-- | src/app/data_farmer.rs | 37 | ||||
-rw-r--r-- | src/app/event.rs | 2 | ||||
-rw-r--r-- | src/app/layout_manager.rs | 133 | ||||
-rw-r--r-- | src/app/widgets.rs | 3 | ||||
-rw-r--r-- | src/app/widgets/bottom_widgets/basic_mem.rs | 4 | ||||
-rw-r--r-- | src/app/widgets/bottom_widgets/basic_net.rs | 1 | ||||
-rw-r--r-- | src/app/widgets/bottom_widgets/cpu.rs | 150 | ||||
-rw-r--r-- | src/app/widgets/bottom_widgets/mem.rs | 4 | ||||
-rw-r--r-- | src/app/widgets/bottom_widgets/net.rs | 2 | ||||
-rw-r--r-- | src/app/widgets/bottom_widgets/process.rs | 83 | ||||
-rw-r--r-- | src/bin/main.rs | 5 | ||||
-rw-r--r-- | src/canvas.rs | 2 | ||||
-rw-r--r-- | src/constants.rs | 4 | ||||
-rw-r--r-- | src/data_conversion.rs | 80 | ||||
-rw-r--r-- | src/lib.rs | 56 |
17 files changed, 490 insertions, 267 deletions
diff --git a/docs/content/usage/general-usage.md b/docs/content/usage/general-usage.md index 4bfcdb61..eb457aef 100644 --- a/docs/content/usage/general-usage.md +++ b/docs/content/usage/general-usage.md @@ -41,8 +41,8 @@ Note that key bindings are generally case-sensitive. | ------------------------------------------------------------ | ------------------------------------------------------------ | | ++q++ , ++ctrl+c++ | Quit | | ++esc++ | Close dialog windows, search, widgets, or exit expanded mode | -| ++ctrl+r++ | Reset display and any collected data | -| ++f++ | Freeze/unfreeze updating with new data | +| ++ctrl+r++ | Resets any collected data | +| ++f++ | Toggles freezing, which stops new data from being shown | | ++question++ | Open help menu | | ++e++ | Toggle expanding the currently selected widget | | ++ctrl+up++ <br/> ++shift+up++ <br/> ++K++ <br/> ++W++ | Select the widget above | @@ -104,19 +104,29 @@ pub struct AppConfigFields { pub no_write: bool, pub show_table_scroll_position: bool, pub is_advanced_kill: bool, - // TODO: Remove these, move network details state-side. pub network_unit_type: DataUnit, pub network_scale_type: AxisScaling, pub network_use_binary_prefix: bool, } +/// The [`FrozenState`] indicates whether the application state should be frozen; if it is, save a snapshot of +/// the data collected at that instant. +pub enum FrozenState { + NotFrozen, + Frozen(DataCollection), +} + +impl Default for FrozenState { + fn default() -> Self { + Self::NotFrozen + } +} + pub struct AppState { pub dd_err: Option<String>, to_delete_process_list: Option<(String, Vec<Pid>)>, - pub is_frozen: bool, - pub canvas_data: canvas::DisplayableData, pub data_collection: DataCollection, @@ -161,6 +171,7 @@ pub struct AppState { pub widget_lookup_map: FxHashMap<NodeId, TmpBottomWidget>, pub layout_tree: Arena<LayoutNode>, pub layout_tree_root: NodeId, + frozen_state: FrozenState, } impl AppState { @@ -189,7 +200,6 @@ impl AppState { // Use defaults. dd_err: Default::default(), to_delete_process_list: Default::default(), - is_frozen: Default::default(), canvas_data: Default::default(), data_collection: Default::default(), is_expanded: Default::default(), @@ -211,37 +221,30 @@ impl AppState { basic_mode_use_percent: Default::default(), is_force_redraw: Default::default(), is_determining_widget_boundary: Default::default(), + frozen_state: Default::default(), } } - pub fn reset(&mut self) { - // Reset multi - self.reset_multi_tap_keys(); + pub fn is_frozen(&self) -> bool { + matches!(self.frozen_state, FrozenState::Frozen(_)) + } - // Reset dialog state - self.help_dialog_state.is_showing_help = false; - self.delete_dialog_state.is_showing_dd = false; + pub fn toggle_freeze(&mut self) { + if matches!(self.frozen_state, FrozenState::Frozen(_)) { + self.frozen_state = FrozenState::NotFrozen; + } else { + self.frozen_state = FrozenState::Frozen(self.data_collection.clone()); + } + } - // Close all searches and reset it - self.proc_state - .widget_states + pub fn reset(&mut self) { + // Call reset on all widgets. + self.widget_lookup_map .values_mut() - .for_each(|state| { - state.process_search_state.search_state.reset(); - }); - self.proc_state.force_update_all = true; - - // Clear current delete list - self.to_delete_process_list = None; - self.dd_err = None; + .for_each(|widget| widget.reset()); // Unfreeze. - self.is_frozen = false; - - // Reset zoom - self.reset_cpu_zoom(); - self.reset_mem_zoom(); - self.reset_net_zoom(); + self.frozen_state = FrozenState::NotFrozen; // Reset data self.data_collection.reset(); @@ -259,10 +262,77 @@ impl AppState { self.dd_err = None; } + /// Handles a global event involving a char. + fn handle_global_char(&mut self, c: char) -> EventResult { + if c.is_ascii_control() { + EventResult::NoRedraw + } else { + // Check for case-sensitive bindings first. + match c { + 'H' | 'A' => self.move_to_widget(MovementDirection::Left), + 'L' | 'D' => self.move_to_widget(MovementDirection::Right), + 'K' | 'W' => self.move_to_widget(MovementDirection::Up), + 'J' | 'S' => self.move_to_widget(MovementDirection::Down), + _ => { + let c = c.to_ascii_lowercase(); + match c { + 'q' => EventResult::Quit, + 'e' => { + if self.app_config_fields.use_basic_mode { + EventResult::NoRedraw + } else { + self.is_expanded = !self.is_expanded; + EventResult::Redraw + } + } + '?' => { + self.help_dialog_state.is_showing_help = true; + EventResult::Redraw + } + 'f' => { + self.toggle_freeze(); + if !self.is_frozen() { + let data_collection = &self.data_collection; + self.widget_lookup_map + .iter_mut() + .for_each(|(_id, widget)| widget.update_data(data_collection)); + } + EventResult::Redraw + } + _ => EventResult::NoRedraw, + } + } + } + } + } + + /// Moves to a widget. + fn move_to_widget(&mut self, direction: MovementDirection) -> EventResult { + let layout_tree = &mut self.layout_tree; + let previous_selected = self.selected_widget; + if let Some(widget) = self.widget_lookup_map.get_mut(&self.selected_widget) { + match move_widget_selection(layout_tree, widget, self.selected_widget, direction) { + MoveWidgetResult::ForceRedraw(new_widget_id) => { + self.selected_widget = new_widget_id; + EventResult::Redraw + } + MoveWidgetResult::NodeId(new_widget_id) => { + self.selected_widget = new_widget_id; + + if previous_selected != self.selected_widget { + EventResult::Redraw + } else { + EventResult::NoRedraw + } + } + } + } else { + EventResult::NoRedraw + } + } + /// Handles a global [`KeyEvent`], and returns an [`EventResult`]. fn handle_global_shortcut(&mut self, event: KeyEvent) -> EventResult { - // TODO: Write this. - if event.modifiers.is_empty() { match event.code { KeyCode::Esc => { @@ -280,24 +350,33 @@ impl AppState { EventResult::NoRedraw } } - KeyCode::Char('q') => EventResult::Quit, - KeyCode::Char('e') => { - if self.app_config_fields.use_basic_mode { - EventResult::NoRedraw - } else { - self.is_expanded = !self.is_expanded; - EventResult::Redraw - } - } - KeyCode::Char('?') => { - self.help_dialog_state.is_showing_help = true; + KeyCode::Char(c) => self.handle_global_char(c), + _ => EventResult::NoRedraw, + } + } else if let KeyModifiers::CONTROL = event.modifiers { + match event.code { + KeyCode::Char('c') | KeyCode::Char('C') => EventResult::Quit, + KeyCode::Char('r') | KeyCode::Char('R') => { + self.reset(); + let data_collection = &self.data_collection; + self.widget_lookup_map + .iter_mut() + .for_each(|(_id, widget)| widget.update_data(data_collection)); EventResult::Redraw } + KeyCode::Left => self.move_to_widget(MovementDirection::Left), + KeyCode::Right => self.move_to_widget(MovementDirection::Right), + KeyCode::Up => self.move_to_widget(MovementDirection::Up), + KeyCode::Down => self.move_to_widget(MovementDirection::Down), _ => EventResult::NoRedraw, } - } else if let KeyModifiers::CONTROL = event.modifiers { + } else if let KeyModifiers::SHIFT = event.modifiers { match event.code { - KeyCode::Char('c') => EventResult::Quit, + KeyCode::Left => self.move_to_widget(MovementDirection::Left), + KeyCode::Right => self.move_to_widget(MovementDirection::Right), + KeyCode::Up => self.move_to_widget(MovementDirection::Up), + KeyCode::Down => self.move_to_widget(MovementDirection::Down), + KeyCode::Char(c) => self.handle_global_char(c), _ => EventResult::NoRedraw, } } else { @@ -317,7 +396,14 @@ impl AppState { } ReturnSignal::Update => { if let Some(widget) = self.widget_lookup_map.get_mut(&self.selected_widget) { - widget.update_data(&self.data_collection); + match &self.frozen_state { + FrozenState::NotFrozen => { + widget.update_data(&self.data_collection); + } + FrozenState::Frozen(frozen_data) => { + widget.update_data(frozen_data); + } + } } EventResult::Redraw } @@ -379,7 +465,11 @@ impl AppState { if was_id_already_selected { return self.convert_widget_event_result(result); } else { - // If the weren't equal, *force* a redraw. + // If the weren't equal, *force* a redraw, and correct the layout tree. + correct_layout_last_selections( + &mut self.layout_tree, + self.selected_widget, + ); let _ = self.convert_widget_event_result(result); return EventResult::Redraw; } @@ -402,7 +492,7 @@ impl AppState { BottomEvent::Update(new_data) => { self.data_collection.eat_data(new_data); - if !self.is_frozen { + if !self.is_frozen() { let data_collection = &self.data_collection; self.widget_lookup_map .iter_mut() @@ -1599,12 +1689,7 @@ impl AppState { 'G' => self.skip_to_last(), 'k' => self.on_up_key(), 'j' => self.on_down_key(), - 'f' => { - self.is_frozen = !self.is_frozen; - if self.is_frozen { - self.data_collection.set_frozen_time(); - } - } + 'f' => {} 'C' => { // self.open_config(), } diff --git a/src/app/data_farmer.rs b/src/app/data_farmer.rs index 25d265ab..c2747289 100644 --- a/src/app/data_farmer.rs +++ b/src/app/data_farmer.rs @@ -26,7 +26,7 @@ use regex::Regex; pub type TimeOffset = f64; pub type Value = f64; -#[derive(Debug, Default)] +#[derive(Clone, Debug, Default)] pub struct TimedData { pub rx_data: Value, pub tx_data: Value, @@ -41,14 +41,14 @@ pub struct TimedData { /// collected, and what is needed to convert into a displayable form. /// /// If the app is *frozen* - that is, we do not want to *display* any changing -/// data, keep updating this, don't convert to canvas displayable data! +/// data, keep updating this. As of 2021-09-08, we just clone the current collection +/// when it freezes to have a snapshot floating around. /// /// Note that with this method, the *app* thread is responsible for cleaning - /// not the data collector. -#[derive(Debug)] +#[derive(Clone, Debug)] pub struct DataCollection { pub current_instant: Instant, - pub frozen_instant: Option<Instant>, pub timed_data_vec: Vec<(Instant, TimedData)>, pub network_harvest: network::NetworkHarvest, pub memory_harvest: memory::MemHarvest, @@ -70,7 +70,6 @@ impl Default for DataCollection { fn default() -> Self { DataCollection { current_instant: Instant::now(), - frozen_instant: None, timed_data_vec: Vec::default(), network_harvest: network::NetworkHarvest::default(), memory_harvest: memory::MemHarvest::default(), @@ -92,21 +91,19 @@ impl Default for DataCollection { impl DataCollection { pub fn reset(&mut self) { - self.timed_data_vec = Vec::default(); - self.network_harvest = network::NetworkHarvest::default(); - self.memory_harvest = memory::MemHarvest::default(); - self.swap_harvest = memory::MemHarvest::default(); - self.cpu_harvest = cpu::CpuHarvest::default(); - self.process_harvest = Vec::default(); - self.disk_harvest = Vec::default(); - self.io_harvest = disks::IoHarvest::default(); - self.io_labels_and_prev = Vec::default(); - self.temp_harvest = Vec::default(); - self.battery_harvest = Vec::default(); - } - - pub fn set_frozen_time(&mut self) { - self.frozen_instant = Some(self.current_instant); + self.timed_data_vec = Default::default(); + self.network_harvest = Default::default(); + self.memory_harvest = Default::default(); + self.swap_harvest = Default::default(); + self.cpu_harvest = Default::default(); + self.process_harvest = Default::default(); + self.process_name_pid_map = Default::default(); + self.process_cmd_pid_map = Default::default(); + self.disk_harvest = Default::default(); + self.io_harvest = Default::default(); + self.io_labels_and_prev = Default::default(); + self.temp_harvest = Default::default(); + self.battery_harvest = Default::default(); } pub fn clean_data(&mut self, max_time_millis: u64) { diff --git a/src/app/event.rs b/src/app/event.rs index a6afb2d8..9201e2c0 100644 --- a/src/app/event.rs +++ b/src/app/event.rs @@ -45,7 +45,7 @@ pub enum WidgetEventResult { /// How a widget should handle a widget selection request. pub enum SelectionAction { - /// This event occurs if the widget internally handled the selection action. + /// This event occurs if the widget internally handled the selection action. A redraw is required. Handled, /// This event occurs if the widget did not handle the selection action; the caller must handle it. NotHandled, diff --git a/src/app/layout_manager.rs b/src/app/layout_manager.rs index 9b88fb8a..61d17924 100644 --- a/src/app/layout_manager.rs +++ b/src/app/layout_manager.rs @@ -997,9 +997,9 @@ Supported widget names: // --- New stuff --- /// Represents a row in the layout tree. -#[derive(PartialEq, Eq, Clone)] +#[derive(Debug, PartialEq, Eq, Clone)] pub struct RowLayout { - last_selected_index: usize, + last_selected: Option<NodeId>, pub parent_rule: LayoutRule, pub bound: Rect, } @@ -1007,7 +1007,7 @@ pub struct RowLayout { impl RowLayout { fn new(parent_rule: LayoutRule) -> Self { Self { - last_selected_index: 0, + last_selected: None, parent_rule, bound: Rect::default(), } @@ -1015,9 +1015,9 @@ impl RowLayout { } /// Represents a column in the layout tree. -#[derive(PartialEq, Eq, Clone)] +#[derive(Debug, PartialEq, Eq, Clone)] pub struct ColLayout { - last_selected_index: usize, + last_selected: Option<NodeId>, pub parent_rule: LayoutRule, pub bound: Rect, } @@ -1025,7 +1025,7 @@ pub struct ColLayout { impl ColLayout { fn new(parent_rule: LayoutRule) -> Self { Self { - last_selected_index: 0, + last_selected: None, parent_rule, bound: Rect::default(), } @@ -1033,7 +1033,7 @@ impl ColLayout { } /// Represents a widget in the layout tree. -#[derive(PartialEq, Eq, Clone, Default)] +#[derive(Debug, PartialEq, Eq, Clone, Default)] pub struct WidgetLayout { pub bound: Rect, } @@ -1042,7 +1042,7 @@ pub struct WidgetLayout { /// - [`LayoutNode::Row`] (a non-leaf that distributes its children horizontally) /// - [`LayoutNode::Col`] (a non-leaf node that distributes its children vertically) /// - [`LayoutNode::Widget`] (a leaf node that contains the ID of the widget it is associated with) -#[derive(PartialEq, Eq, Clone)] +#[derive(Debug, PartialEq, Eq, Clone)] pub enum LayoutNode { /// A non-leaf that distributes its children horizontally Row(RowLayout), @@ -1382,6 +1382,8 @@ pub fn create_layout_tree( )); } + correct_layout_last_selections(&mut arena, selected); + Ok(LayoutCreationOutput { layout_tree: arena, root: root_id, @@ -1391,6 +1393,35 @@ pub fn create_layout_tree( }) } +/// We may have situations where we also have to make sure the correct layout indices are selected. +/// For example, when we select a widget by clicking, we want to update the layout so that it's as if a user +/// manually moved to it via keybinds. +/// +/// We can do this by just going through the ancestors, starting from the widget itself. +pub fn correct_layout_last_selections(arena: &mut Arena<LayoutNode>, selected: NodeId) { + let mut selected_ancestors = selected.ancestors(&arena).collect::<Vec<_>>(); + let prev_node = selected_ancestors.pop(); + if let Some(mut prev_node) = prev_node { + for node in selected_ancestors { + if let Some(layout_node) = arena.get_mut(node).map(|n| n.get_mut()) { + match layout_node { + LayoutNode::Row(RowLayout { last_selected, .. }) + | LayoutNode::Col(ColLayout { last_selected, .. }) => { + *last_selected = Some(prev_node); + } + LayoutNode::Widget(_) => {} + } + } + prev_node = node; + } + } +} + +pub enum MoveWidgetResult { + ForceRedraw(NodeId), + NodeId(NodeId), +} + /// Attempts to find and return the selected [`BottomWidgetId`] after moving in a direction. /// /// Note this function assumes a properly built tree - if not, bad things may happen! We generally assume that: @@ -1399,7 +1430,7 @@ pub fn create_layout_tree( pub fn move_widget_selection( layout_tree: &mut Arena<LayoutNode>, current_widget: &mut TmpBottomWidget, current_widget_id: NodeId, direction: MovementDirection, -) -> NodeId { +) -> MoveWidgetResult { // We first give our currently-selected widget a chance to react to the movement - it may handle it internally! let handled = match direction { MovementDirection::Left => current_widget.handle_widget_selection_left(), @@ -1408,16 +1439,18 @@ pub fn move_widget_selection( MovementDirection::Down => current_widget.handle_widget_selection_down(), }; + // TODO: Do testing. + match handled { SelectionAction::Handled => { // If it was handled by the widget, then we don't have to do anything - return the current one. - current_widget_id + MoveWidgetResult::ForceRedraw(current_widget_id) } SelectionAction::NotHandled => { /// Keeps traversing up the `layout_tree` until it hits a parent where `current_id` is a child and parent /// is a [`LayoutNode::Row`], returning its parent's [`NodeId`] and the child's [`NodeId`] (in that order). /// If this crawl fails (i.e. hits a root, it is an invalid tree for some reason), it returns [`None`]. - fn find_first_row( + fn find_parent_row( layout_tree: &Arena<LayoutNode>, current_id: NodeId, ) -> Option<(NodeId, NodeId)> { layout_tree @@ -1430,7 +1463,7 @@ pub fn move_widget_selection( }) .and_then(|(parent_id, parent_node)| match parent_node.get() { LayoutNode::Row(_) => Some((parent_id, current_id)), - LayoutNode::Col(_) => find_first_row(layout_tree, parent_id), + LayoutNode::Col(_) => find_parent_row(layout_tree, parent_id), LayoutNode::Widget(_) => None, }) } @@ -1438,7 +1471,7 @@ pub fn move_widget_selection( /// Keeps traversing up the `layout_tree` until it hits a parent where `current_id` is a child and parent /// is a [`LayoutNode::Col`], returning its parent's [`NodeId`] and the child's [`NodeId`] (in that order). /// If this crawl fails (i.e. hits a root, it is an invalid tree for some reason), it returns [`None`]. - fn find_first_col( + fn find_parent_col( layout_tree: &Arena<LayoutNode>, current_id: NodeId, ) -> Option<(NodeId, NodeId)> { layout_tree @@ -1450,7 +1483,7 @@ pub fn move_widget_selection( .map(|parent_node| (parent_id, parent_node)) }) .and_then(|(parent_id, parent_node)| match parent_node.get() { - LayoutNode::Row(_) => find_first_col(layout_tree, parent_id), + LayoutNode::Row(_) => find_parent_col(layout_tree, parent_id), LayoutNode::Col(_) => Some((parent_id, current_id)), LayoutNode::Widget(_) => None, }) @@ -1461,21 +1494,19 @@ pub fn move_widget_selection( if let Some(current_node) = layout_tree.get(current_id) { match current_node.get() { LayoutNode::Row(RowLayout { - last_selected_index, + last_selected, parent_rule: _, bound: _, }) | LayoutNode::Col(ColLayout { - last_selected_index, + last_selected, parent_rule: _, bound: _, }) => { - if let Some(next_child) = - current_id.children(layout_tree).nth(*last_selected_index) - { + if let Some(next_child) = *last_selected { descend_to_leaf(layout_tree, next_child) } else { - current_id + current_node.first_child().unwrap_or(current_id) } } LayoutNode::Widget(_) => { @@ -1493,7 +1524,7 @@ pub fn move_widget_selection( // on the tree layout to help us decide where to go. // Movement logic is inspired by i3. When we enter a new column/row, we go to the *last* selected // element; if we can't, go to the nearest one. - match direction { + let proposed_id = match direction { MovementDirection::Left => { // When we move "left": // 1. Look for the parent of the current widget. @@ -1513,7 +1544,8 @@ pub fn move_widget_selection( fn find_left( layout_tree: &mut Arena<LayoutNode>, current_id: NodeId, ) -> NodeId { - if let Some((parent_id, child_id)) = find_first_row(layout_tree, current_id) + if let Some((parent_id, child_id)) = + find_parent_row(layout_tree, current_id) { if let Some(prev_sibling) = child_id.preceding_siblings(layout_tree).nth(1) @@ -1521,16 +1553,17 @@ pub fn move_widget_selection( // Subtract one from the currently selected index... if let Some(parent) = layout_tree.get_mut(parent_id) { if let LayoutNode::Row(row) = parent.get_mut() { - row.last_selected_index = - row.last_selected_index.saturating_sub(1); + row.last_selected = Some(prev_sibling); } } // Now descend downwards! descend_to_leaf(layout_tree, prev_sibling) - } else { + } else if parent_id != current_id { // Darn, we can't go further back! Recurse on this ID. - find_left(layout_tree, child_id) + find_left(layout_tree, parent_id) + } else { + current_id } } else { // Failed, just return the current ID. @@ -1546,23 +1579,26 @@ pub fn move_widget_selection( fn find_right( layout_tree: &mut Arena<LayoutNode>, current_id: NodeId, ) -> NodeId { - if let Some((parent_id, child_id)) = find_first_row(layout_tree, current_id) + if let Some((parent_id, child_id)) = + find_parent_row(layout_tree, current_id) { - if let Some(prev_sibling) = + if let Some(following_sibling) = child_id.following_siblings(layout_tree).nth(1) { // Add one to the currently selected index... if let Some(parent) = layout_tree.get_mut(parent_id) { if let LayoutNode::Row(row) = parent.get_mut() { - row.last_selected_index += 1; + row.last_selected = Some(following_sibling); } } // Now descend downwards! - descend_to_leaf(layout_tree, prev_sibling) - } else { + descend_to_leaf(layout_tree, following_sibling) + } else if parent_id != current_id { // Darn, we can't go further back! Recurse on this ID. - find_right(layout_tree, child_id) + find_right(layout_tree, parent_id) + } else { + current_id } } else { // Failed, just return the current ID. @@ -1578,7 +1614,8 @@ pub fn move_widget_selection( fn find_above( layout_tree: &mut Arena<LayoutNode>, current_id: NodeId, ) -> NodeId { - if let Some((parent_id, child_id)) = find_first_col(layout_tree, current_id) + if let Some((parent_id, child_id)) = + find_parent_col(layout_tree, current_id) { if let Some(prev_sibling) = child_id.preceding_siblings(layout_tree).nth(1) @@ -1586,16 +1623,17 @@ pub fn move_widget_selection( // Subtract one from the currently selected index... if let Some(parent) = layout_tree.get_mut(parent_id) { if let LayoutNode::Col(row) = parent.get_mut() { - row.last_selected_index = - row.last_selected_index.saturating_sub(1); + row.last_selected = Some(prev_sibling); } } // Now descend downwards! descend_to_leaf(layout_tree, prev_sibling) - } else { + } else if parent_id != current_id { // Darn, we can't go further back! Recurse on this ID. - find_above(layout_tree, child_id) + find_above(layout_tree, parent_id) + } else { + current_id } } else { // Failed, just return the current ID. @@ -1611,23 +1649,26 @@ pub fn move_widget_selection( fn find_below( layout_tree: &mut Arena<LayoutNode>, current_id: NodeId, ) -> NodeId { - if let Some((parent_id, child_id)) = find_first_col(layout_tree, current_id) + if let Some((parent_id, child_id)) = + find_parent_col(layout_tree, current_id) { - if let Some(prev_sibling) = + if let Some(following_sibling) = child_id.following_siblings(layout_tree).nth(1) { // Add one to the currently selected index... if let Some(parent) = layout_tree.get_mut(parent_id) { if let LayoutNode::Col(row) = parent.get_mut() { - row.last_selected_index += 1; + row.last_selected = Some(following_sibling); } } // Now descend downwards! - descend_to_leaf(layout_tree, prev_sibling) - } else { + descend_to_leaf(layout_tree, following_sibling) + } else if parent_id != current_id { // Darn, we can't go further back! Recurse on this ID. - find_below(layout_tree, child_id) + find_below(layout_tree, parent_id) + } else { + current_id } } else { // Failed, just return the current ID. @@ -1636,6 +1677,12 @@ pub fn move_widget_selection( } find_below(layout_tree, current_widget_id) } + }; + + if let Some(LayoutNode::Widget(_)) = layout_tree.get(proposed_id).map(|n| n.get()) { + MoveWidgetResult::NodeId(proposed_id) + } else { + MoveWidgetResult::NodeId(current_widget_id) } } } diff --git a/src/app/widgets.rs b/src/app/widgets.rs index cac6a032..ffc08cd6 100644 --- a/src/app/widgets.rs +++ b/src/app/widgets.rs @@ -149,6 +149,9 @@ pub trait Widget { |