summaryrefslogtreecommitdiffstats
path: root/src/app/layout_manager.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/app/layout_manager.rs')
-rw-r--r--src/app/layout_manager.rs940
1 files changed, 940 insertions, 0 deletions
diff --git a/src/app/layout_manager.rs b/src/app/layout_manager.rs
new file mode 100644
index 00000000..41ec72b6
--- /dev/null
+++ b/src/app/layout_manager.rs
@@ -0,0 +1,940 @@
+use crate::error::{BottomError, Result};
+use std::collections::BTreeMap;
+use typed_builder::*;
+
+use crate::constants::DEFAULT_WIDGET_ID;
+
+/// Represents a more usable representation of the layout, derived from the
+/// config.
+#[derive(Clone, Debug)]
+pub struct BottomLayout {
+ pub rows: Vec<BottomRow>,
+ pub total_row_height_ratio: u32,
+}
+
+type WidgetMappings = (u32, BTreeMap<(u32, u32), u64>);
+type ColumnRowMappings = (u32, BTreeMap<(u32, u32), WidgetMappings>);
+type ColumnMappings = (u32, BTreeMap<(u32, u32), ColumnRowMappings>);
+
+impl BottomLayout {
+ #[allow(clippy::cognitive_complexity)]
+ pub fn get_movement_mappings(&mut self) {
+ fn is_intersecting(a: (u32, u32), b: (u32, u32)) -> bool {
+ a.0 >= b.0 && a.1 <= b.1
+ || a.1 >= b.1 && a.0 <= b.0
+ || a.0 <= b.0 && a.1 >= b.0
+ || a.0 >= b.0 && a.0 < b.1 && a.1 >= b.1
+ }
+
+ fn get_distance(target: (u32, u32), candidate: (u32, u32)) -> u32 {
+ if candidate.0 < target.0 {
+ candidate.1 - target.0
+ } else if candidate.1 < target.1 {
+ candidate.1 - candidate.0
+ } else {
+ target.1 - candidate.0
+ }
+ }
+
+ // Now we need to create the correct mapping for moving from a specific
+ // widget to another
+
+ let mut layout_mapping: BTreeMap<(u32, u32), ColumnMappings> = BTreeMap::new();
+ let mut total_height = 0;
+ for row in &self.rows {
+ let mut row_width = 0;
+ let mut row_mapping: BTreeMap<(u32, u32), ColumnRowMappings> = BTreeMap::new();
+ let mut is_valid_row = false;
+ for col in &row.children {
+ let mut col_row_height = 0;
+ let mut col_mapping: BTreeMap<(u32, u32), WidgetMappings> = BTreeMap::new();
+ let mut is_valid_col = false;
+
+ for col_row in &col.children {
+ let mut widget_width = 0;
+ let mut col_row_mapping: BTreeMap<(u32, u32), u64> = BTreeMap::new();
+ let mut is_valid_col_row = false;
+ for widget in &col_row.children {
+ match widget.widget_type {
+ BottomWidgetType::Empty => {}
+ _ => {
+ is_valid_col_row = true;
+ col_row_mapping.insert(
+ (
+ widget_width * 100 / col_row.total_widget_ratio,
+ (widget_width + widget.width_ratio) * 100
+ / col_row.total_widget_ratio,
+ ),
+ widget.widget_id,
+ );
+ }
+ }
+ widget_width += widget.width_ratio;
+ }
+ if is_valid_col_row {
+ col_mapping.insert(
+ (
+ col_row_height * 100 / col.total_col_row_ratio,
+ (col_row_height + col_row.col_row_height_ratio) * 100
+ / col.total_col_row_ratio,
+ ),
+ (col.total_col_row_ratio, col_row_mapping),
+ );
+ is_valid_col = true;
+ }
+
+ col_row_height += col_row.col_row_height_ratio;
+ }
+ if is_valid_col {
+ row_mapping.insert(
+ (
+ row_width * 100 / row.total_col_ratio,
+ (row_width + col.col_width_ratio) * 100 / row.total_col_ratio,
+ ),
+ (row.total_col_ratio, col_mapping),
+ );
+ is_valid_row = true;
+ }
+
+ row_width += col.col_width_ratio;
+ }
+ if is_valid_row {
+ layout_mapping.insert(
+ (
+ total_height * 100 / self.total_row_height_ratio,
+ (total_height + row.row_height_ratio) * 100 / self.total_row_height_ratio,
+ ),
+ (self.total_row_height_ratio, row_mapping),
+ );
+ }
+ total_height += row.row_height_ratio;
+ }
+
+ // Now pass through a second time; this time we want to build up
+ // our neighbour profile.
+ let mut height_cursor = 0;
+ for row in &mut self.rows {
+ let mut col_cursor = 0;
+ let row_height_percentage_start = height_cursor * 100 / self.total_row_height_ratio;
+ let row_height_percentage_end =
+ (height_cursor + row.row_height_ratio) * 100 / self.total_row_height_ratio;
+
+ for col in &mut row.children {
+ let mut col_row_cursor = 0;
+ let col_width_percentage_start = col_cursor * 100 / row.total_col_ratio;
+ let col_width_percentage_end =
+ (col_cursor + col.col_width_ratio) * 100 / row.total_col_ratio;
+
+ for col_row in &mut col.children {
+ let mut widget_cursor = 0;
+ let col_row_height_percentage_start =
+ col_row_cursor * 100 / col.total_col_row_ratio;
+ let col_row_height_percentage_end =
+ (col_row_cursor + col_row.col_row_height_ratio) * 100
+ / col.total_col_row_ratio;
+ let col_row_children_len = col_row.children.len();
+
+ for widget in &mut col_row.children {
+ // Bail if empty.
+ if let BottomWidgetType::Empty = widget.widget_type {
+ continue;
+ }
+
+ let widget_width_percentage_start =
+ widget_cursor * 100 / col_row.total_widget_ratio;
+ let widget_width_percentage_end =
+ (widget_cursor + widget.width_ratio) * 100 / col_row.total_widget_ratio;
+
+ if let Some(current_row) = layout_mapping
+ .get(&(row_height_percentage_start, row_height_percentage_end))
+ {
+ // First check for within the same col_row for left and right
+ if let Some(current_col) = current_row
+ .1
+ .get(&(col_width_percentage_start, col_width_percentage_end))
+ {
+ if let Some(current_col_row) = current_col.1.get(&(
+ col_row_height_percentage_start,
+ col_row_height_percentage_end,
+ )) {
+ if let Some(to_left_widget) = current_col_row
+ .1
+ .range(
+ ..(
+ widget_width_percentage_start,
+ widget_width_percentage_start,
+ ),
+ )
+ .next_back()
+ {
+ widget.left_neighbour = Some(*to_left_widget.1);
+ }
+
+ // Right
+ if let Some(to_right_neighbour) = current_col_row
+ .1
+ .range(
+ (
+ widget_width_percentage_end,
+ widget_width_percentage_end,
+ )..,
+ )
+ .next()
+ {
+ widget.right_neighbour = Some(*to_right_neighbour.1);
+ }
+ }
+ }
+
+ if widget.left_neighbour.is_none() {
+ if let Some(to_left_col) = current_row
+ .1
+ .range(
+ ..(col_width_percentage_start, col_width_percentage_start),
+ )
+ .next_back()
+ {
+ // Check left in same row
+ let mut current_best_distance = 0;
+ let mut current_best_widget_id = widget.widget_id;
+
+ for widget_position in &(to_left_col.1).1 {
+ let candidate_start = (widget_position.0).0;
+ let candidate_end = (widget_position.0).1;
+
+ if is_intersecting(
+ (
+ col_row_height_percentage_start,
+ col_row_height_percentage_end,
+ ),
+ (candidate_start, candidate_end),
+ ) {
+ let candidate_distance = get_distance(
+ (
+ col_row_height_percentage_start,
+ col_row_height_percentage_end,
+ ),
+ (candidate_start, candidate_end),
+ );
+
+ if current_best_distance < candidate_distance {
+ if let Some(new_best_widget) =
+ (widget_position.1).1.iter().next_back()
+ {
+ current_best_distance = candidate_distance + 1;
+ current_best_widget_id = *(new_best_widget.1);
+ }
+ }
+ }
+ }
+ if current_best_distance > 0 {
+ widget.left_neighbour = Some(current_best_widget_id);
+ }
+ }
+ }
+
+ if widget.right_neighbour.is_none() {
+ if let Some(to_right_col) = current_row
+ .1
+ .range((col_width_percentage_end, col_width_percentage_end)..)
+ .next()
+ {
+ // Check right in same row
+ let mut current_best_distance = 0;
+ let mut current_best_widget_id = widget.widget_id;
+
+ for widget_position in &(to_right_col.1).1 {
+ let candidate_start = (widget_position.0).0;
+ let candidate_end = (widget_position.0).1;
+
+ if is_intersecting(
+ (
+ col_row_height_percentage_start,
+ col_row_height_percentage_end,
+ ),
+ (candidate_start, candidate_end),
+ ) {
+ let candidate_distance = get_distance(
+ (
+ col_row_height_percentage_start,
+ col_row_height_percentage_end,
+ ),
+ (candidate_start, candidate_end),
+ );
+
+ if current_best_distance < candidate_distance {
+ if let Some(new_best_widget) =
+ (widget_position.1).1.iter().next()
+ {
+ current_best_distance = candidate_distance + 1;
+ current_best_widget_id = *(new_best_widget.1);
+ }
+ }
+ }
+ }
+ if current_best_distance > 0 {
+ widget.right_neighbour = Some(current_best_widget_id);
+ }
+ }
+ }
+
+ // Check up/down within same row;
+ // else check up/down with other rows
+ if let Some(current_col) = current_row
+ .1
+ .get(&(col_width_percentage_start, col_width_percentage_end))
+ {
+ if let Some(to_up) = current_col
+ .1
+ .range(
+ ..(
+ col_row_height_percentage_start,
+ col_row_height_percentage_start,
+ ),
+ )
+ .next_back()
+ {
+ // Now check each widget_width and pick the best
+ for candidate_widget in &(to_up.1).1 {
+ let mut current_best_distance = 0;
+ let mut current_best_widget_id = widget.widget_id;
+ if is_intersecting(
+ (
+ widget_width_percentage_start,
+ widget_width_percentage_end,
+ ),
+ ((candidate_widget.0).0, (candidate_widget.0).1),
+ ) {
+ let candidate_best_distance = get_distance(
+ (
+ widget_width_percentage_start,
+ widget_width_percentage_end,
+ ),
+ ((candidate_widget.0).0, (candidate_widget.0).1),
+ );
+
+ if current_best_distance < candidate_best_distance {
+ current_best_distance = candidate_best_distance + 1;
+ current_best_widget_id = *candidate_widget.1;
+ }
+ }
+
+ if current_best_distance > 0 {
+ widget.up_neighbour = Some(current_best_widget_id);
+ }
+ }
+ } else if let Some(next_row_up) = layout_mapping
+ .range(
+ ..(
+ row_height_percentage_start,
+ row_height_percentage_start,
+ ),
+ )
+ .next_back()
+ {
+ let mut current_best_distance = 0;
+ let mut current_best_widget_id = widget.widget_id;
+ let (target_start_width, target_end_width) =
+ if col_row_children_len > 1 {
+ (
+ col_width_percentage_start
+ + widget_width_percentage_start
+ * (col_width_percentage_end
+ - col_width_percentage_start)
+ / 100,
+ col_width_percentage_start
+ + widget_width_percentage_end
+ * (col_width_percentage_end
+ - col_width_percentage_start)
+ / 100,
+ )
+ } else {
+ (col_width_percentage_start, col_width_percentage_end)
+ };
+
+ for col_position in &(next_row_up.1).1 {
+ if let Some(next_col_row) =
+ (col_position.1).1.iter().next_back()
+ {
+ let (candidate_col_start, candidate_col_end) =
+ ((col_position.0).0, (col_position.0).1);
+ let candidate_difference =
+ candidate_col_end - candidate_col_start;
+ for candidate_widget in &(next_col_row.1).1 {
+ let candidate_start = candidate_col_start
+ + (candidate_widget.0).0 * candidate_difference
+ / 100;
+ let candidate_end = candidate_col_start
+ + (candidate_widget.0).1 * candidate_difference
+ / 100;
+
+ if is_intersecting(
+ (target_start_width, target_end_width),
+ (candidate_start, candidate_end),
+ ) {
+ let candidate_distance = get_distance(
+ (target_start_width, target_end_width),
+ (candidate_start, candidate_end),
+ );
+
+ if current_best_distance < candidate_distance {
+ current_best_distance =
+ candidate_distance + 1;
+ current_best_widget_id =
+ *(candidate_widget.1);
+ }
+ }
+ }
+ }
+ }
+
+ if current_best_distance > 0 {
+ widget.up_neighbour = Some(current_best_widget_id);
+ }
+ }
+
+ if let Some(to_down) = current_col
+ .1
+ .range(
+ (
+ col_row_height_percentage_start + 1,
+ col_row_height_percentage_start + 1,
+ )..,
+ )
+ .next()
+ {
+ for candidate_widget in &(to_down.1).1 {
+ let mut current_best_distance = 0;
+ let mut current_best_widget_id = widget.widget_id;
+ if is_intersecting(
+ (
+ widget_width_percentage_start,
+ widget_width_percentage_end,
+ ),
+ ((candidate_widget.0).0, (candidate_widget.0).1),
+ ) {
+ let candidate_best_distance = get_distance(
+ (
+ widget_width_percentage_start,
+ widget_width_percentage_end,
+ ),
+ ((candidate_widget.0).0, (candidate_widget.0).1),
+ );
+
+ if current_best_distance < candidate_best_distance {
+ current_best_distance = candidate_best_distance + 1;
+ current_best_widget_id = *candidate_widget.1;
+ }
+ }
+
+ if current_best_distance > 0 {
+ widget.down_neighbour = Some(current_best_widget_id);
+ }
+ }
+ } else if let Some(next_row_down) = layout_mapping
+ .range(
+ (
+ row_height_percentage_start + 1,
+ row_height_percentage_start + 1,
+ )..,
+ )
+ .next()
+ {
+ let mut current_best_distance = 0;
+ let mut current_best_widget_id = widget.widget_id;
+ let (target_start_width, target_end_width) =
+ if col_row_children_len > 1 {
+ (
+ col_width_percentage_start
+ + widget_width_percentage_start
+ * (col_width_percentage_end
+ - col_width_percentage_start)
+ / 100,
+ col_width_percentage_start
+ + widget_width_percentage_end
+ * (col_width_percentage_end
+ - col_width_percentage_start)
+ / 100,
+ )
+ } else {
+ (col_width_percentage_start, col_width_percentage_end)
+ };
+
+ for col_position in &(next_row_down.1).1 {
+ if let Some(next_col_row) = (col_position.1).1.iter().next()
+ {
+ let (candidate_col_start, candidate_col_end) =
+ ((col_position.0).0, (col_position.0).1);
+ let candidate_difference =
+ candidate_col_end - candidate_col_start;
+ for candidate_widget in &(next_col_row.1).1 {
+ let candidate_start = candidate_col_start
+ + (candidate_widget.0).0 * candidate_difference
+ / 100;
+ let candidate_end = candidate_col_start
+ + (candidate_widget.0).1 * candidate_difference
+ / 100;
+
+ if is_intersecting(
+ (target_start_width, target_end_width),
+ (candidate_start, candidate_end),
+ ) {
+ let candidate_distance = get_distance(
+ (target_start_width, target_end_width),
+ (candidate_start, candidate_end),
+ );
+
+ if current_best_distance < candidate_distance {
+ current_best_distance =
+ candidate_distance + 1;
+ current_best_widget_id =
+ *(candidate_widget.1);
+ }
+ }
+ }
+ }
+ }
+
+ if current_best_distance > 0 {
+ widget.down_neighbour = Some(current_best_widget_id);
+ }
+ }
+ }
+ }
+ widget_cursor += widget.width_ratio;
+ }
+ col_row_cursor += col_row.col_row_height_ratio;
+ }
+ col_cursor += col.col_width_ratio;
+ }
+ height_cursor += row.row_height_ratio;
+ }
+ }
+
+ pub fn init_basic_default() -> Self {
+ BottomLayout {
+ total_row_height_ratio: 3,
+ rows: vec![
+ BottomRow::builder()
+ .canvas_handle_height(true)
+ .children(vec![BottomCol::builder()
+ .canvas_handle_width(true)
+ .children(vec![BottomColRow::builder()
+ .canvas_handle_height(true)
+ .children(vec![BottomWidget::builder()
+ .canvas_handle_width(true)
+ .widget_type(BottomWidgetType::BasicCpu)
+ .widget_id(1)
+ .down_neighbour(Some(2))
+ .build()])
+ .build()])
+ .build()])
+ .build(),
+ BottomRow::builder()
+ .canvas_handle_height(true)
+ .children(vec![BottomCol::builder()
+ .canvas_handle_width(true)
+ .children(vec![BottomColRow::builder()
+ .canvas_handle_height(true)
+ .children(vec![
+ BottomWidget::builder()
+ .canvas_handle_width(true)
+ .widget_type(BottomWidgetType::BasicMem)
+ .widget_id(2)
+ .up_neighbour(Some(1))
+ .down_neighbour(Some(100))
+ .right_neighbour(Some(3))
+ .build(),
+ BottomWidget::builder()
+ .canvas_handle_width(true)
+ .widget_type(BottomWidgetType::BasicNet)
+ .widget_id(3)
+ .up_neighbour(Some(1))
+ .down_neighbour(Some(100))
+ .left_neighbour(Some(2))
+ .build(),
+ ])
+ .build()])
+ .build()])
+ .build(),
+ BottomRow::builder()
+ .canvas_handle_height(true)
+ .children(vec![BottomCol::builder()
+ .canvas_handle_width(true)
+ .children(vec![BottomColRow::builder()
+ .canvas_handle_height(true)
+ .children(vec![BottomWidget::builder()
+ .canvas_handle_width(true)
+ .widget_type(BottomWidgetType::BasicTables)
+ .widget_id(100)
+ .up_neighbour(Some(2))
+ .build()])
+ .build()])
+ .build()])
+ .build(),
+ BottomRow::builder()
+ .canvas_handle_height(true)
+ .children(vec![
+ BottomCol::builder()
+ .canvas_handle_width(true)
+ .children(vec![BottomColRow::builder()
+ .canvas_handle_height(true)
+ .children(vec![BottomWidget::builder()
+ .canvas_handle_width(true)
+ .widget_type(BottomWidgetType::Disk)
+ .widget_id(4)
+ .up_neighbour(Some(100))
+ .left_neighbour(Some(7))
+ .right_neighbour(Some(DEFAULT_WIDGET_ID))
+ .build()])
+ .build()])
+ .build(),
+ BottomCol::builder()
+ .canvas_handle_width(true)
+ .children(vec![
+ BottomColRow::builder()
+ .canvas_handle_height(true)
+ .children(vec![BottomWidget::builder()
+ .canvas_handle_width(true)
+ .widget_type(BottomWidgetType::Proc)
+ .widget_id(DEFAULT_WIDGET_ID)
+ .up_neighbour(Some(100))
+ .down_neighbour(Some(DEFAULT_WIDGET_ID + 1))
+ .left_neighbour(Some(4))
+ .right_neighbour(Some(7))
+ .build()])
+ .build(),
+ BottomColRow::builder()
+ .canvas_handle_height(true)
+ .children(vec![BottomWidget::builder()
+ .canvas_handle_width(true)
+ .widget_type(BottomWidgetType::ProcSearch)
+ .widget_id(DEFAULT_WIDGET_ID + 1)
+ .up_neighbour(Some(DEFAULT_WIDGET_ID))
+ .left_neighbour(Some(4))
+ .right_neighbour(Some(7))
+ .build()])
+ .build(),
+ ])
+ .build(),
+ BottomCol::builder()
+ .canvas_handle_width(true)
+ .children(vec![BottomColRow::builder()
+ .canvas_handle_height(true)
+ .children(vec![BottomWidget::builder()
+ .canvas_handle_width(true)
+ .widget_type(BottomWidgetType::Temp)
+ .widget_id(7)
+ .up_neighbour(Some(100))
+ .left_neighbour(Some(DEFAULT_WIDGET_ID))
+ .right_neighbour(Some(4))
+ .build()])
+ .build()])
+ .build(),
+ ])
+ .build(),
+ ],
+ }
+ }
+
+ pub fn init_default(left_legend: bool) -> Self {
+ BottomLayout {
+ total_row_height_ratio: 100,
+ rows: vec![
+ BottomRow::builder()
+ .row_height_ratio(30)
+ .children(vec![BottomCol::builder()
+ .children(vec![BottomColRow::builder()
+ .total_widget_ratio(20)
+ .children(if left_legend {
+ vec![
+ BottomWidget::builder()
+ .width_ratio(3)
+ .widget_type(BottomWidgetType::CpuLegend)
+ .widget_id(2)
+ .down_neighbour(Some(11))
+ .right_neighbour(Some(1))
+ .canvas_handle_width(true)
+ .build(),
+ BottomWidget::builder()
+ .width_ratio(17)
+ .widget_type(BottomWidgetType::Cpu)
+ .widget_id(1)
+ .down_neighbour(Some(12))
+ .left_neighbour(Some(2))
+ .flex_grow(true)
+ .build(),
+ ]
+ } else {
+ vec![
+ BottomWidget::builder()
+ .width_ratio(17)
+ .widget_type(BottomWidgetType::Cpu)
+ .widget_id(1)
+ .down_neighbour(Some(11))
+ .right_neighbour(Some(2))
+ .flex_grow(true)
+ .build(),
+ BottomWidget::builder()
+ .width_ratio(3)
+ .widget_type(BottomWidgetType::CpuLegend)
+ .widget_id(2)
+ .down_neighbour(Some(12))
+ .left_neighbour(Some(1))
+ .canvas_handle_width(true)
+ .build(),
+ ]
+ })
+ .build()])
+ .build()])
+ .build(),
+ BottomRow::builder()
+ .total_col_ratio(7)
+ .row_height_ratio(40)
+ .children(vec![
+ BottomCol::builder()
+ .col_width_ratio(4)
+ .children(vec![BottomColRow::builder()
+ .children(vec![BottomWidget::builder()
+ .widget_type(BottomWidgetType::Mem)
+ .widget_id(11)
+ .right_neighbour(Some(12))
+ .up_neighbour(Some(1))
+ .down_neighbour(Some(21))
+ .build()])
+ .build()])
+ .build(),
+ BottomCol::builder()
+ .total_col_row_ratio(2)
+ .col_width_ratio(3)
+ .children(vec![
+ BottomColRow::builder()
+ .col_row_height_ratio(1)
+ .total_widget_ratio(2)
+ .children(vec![BottomWidget::builder()
+ .widget_type(BottomWidgetType::Temp)
+ .widget_id(12)
+ .left_neighbour(Some(11))
+ .up_neighbour(Some(1))
+ .down_neighbour(Some(13))
+ .build()])
+ .build(),
+ BottomColRow::builder()
+ .col_row_height_ratio(1)
+ .children(vec![BottomWidget::builder()
+ .widget_type(BottomWidgetType::Disk)
+ .widget_id(13)
+ .left_neighbour(Some(11))
+ .up_neighbour(Some(12))
+ .down_neighbour(Some(DEFAULT_WIDGET_ID))
+ .build()])
+ .build(),
+ ])
+ .build(),
+ ])
+ .build(),
+ BottomRow::builder()
+ .total_col_ratio(2)
+ .row_height_ratio(30)
+ .children(vec![
+ BottomCol::builder()
+ .children(vec![BottomColRow::builder()
+ .col_row_height_ratio(1)
+ .children(vec![BottomWidget::builder()
+ .widget_type(BottomWidgetType::Net)
+ .widget_id(21)
+ .right_neighbour(Some(DEFAULT_WIDGET_ID))
+ .up_neighbour(Some(11))
+ .build()])
+ .build()])
+ .build(),
+ BottomCol::builder()
+ .to