Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feature: support custom widget borders #1642

Merged
merged 8 commits into from
Dec 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ crossterm = "0.28.1"
ctrlc = { version = "3.4.5", features = ["termination"] }
dirs = "5.0.1"
# Maybe consider https://github.com/rust-lang/rustc-hash for some cases too?
hashbrown = "0.15.0"
hashbrown = "0.15.2"
humantime = "2.1.0"
indexmap = "2.6.0"
indoc = "2.0.5"
Expand All @@ -104,7 +104,7 @@ time = { version = "0.3.36", features = ["local-offset", "formatting", "macros"]

# These are just used for JSON schema generation.
schemars = { version = "0.8.21", optional = true }
serde_json = { version = "1.0.132", optional = true }
serde_json = { version = "1.0.133", optional = true }
strum = { version = "0.26.3", features = ["derive"], optional = true }

[target.'cfg(unix)'.dependencies]
Expand Down
20 changes: 20 additions & 0 deletions schema/nightly/bottom.json
Original file line number Diff line number Diff line change
Expand Up @@ -955,6 +955,15 @@
}
]
},
"WidgetBorderType": {
"type": "string",
"enum": [
"Default",
"Rounded",
"Double",
"Thick"
]
},
"WidgetStyle": {
"description": "General styling for generic widgets.",
"type": "object",
Expand Down Expand Up @@ -1014,6 +1023,17 @@
}
]
},
"widget_border_type": {
"description": "Widget borders type.",
"anyOf": [
{
"$ref": "#/definitions/WidgetBorderType"
},
{
"type": "null"
}
]
},
"widget_title": {
"description": "Text styling for a widget's title.",
"anyOf": [
Expand Down
42 changes: 17 additions & 25 deletions src/canvas.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,12 @@ use crate::{
App,
},
constants::*,
options::config::style::ColourPalette,
options::config::style::Styles,
};

/// Handles the canvas' state.
pub struct Painter {
pub colours: ColourPalette,
pub styles: Styles,
previous_height: u16,
previous_width: u16,

Expand All @@ -47,7 +47,7 @@ pub enum LayoutConstraint {
}

impl Painter {
pub fn init(layout: BottomLayout, styling: ColourPalette) -> anyhow::Result<Self> {
pub fn init(layout: BottomLayout, styling: Styles) -> anyhow::Result<Self> {
// Now for modularity; we have to also initialize the base layouts!
// We want to do this ONCE and reuse; after this we can just construct
// based on the console size.
Expand Down Expand Up @@ -131,7 +131,7 @@ impl Painter {
});

let painter = Painter {
colours: styling,
styles: styling,
previous_height: 0,
previous_width: 0,
row_constraints,
Expand All @@ -149,17 +149,17 @@ impl Painter {
pub fn get_border_style(&self, widget_id: u64, selected_widget_id: u64) -> tui::style::Style {
let is_on_widget = widget_id == selected_widget_id;
if is_on_widget {
self.colours.highlighted_border_style
self.styles.highlighted_border_style
} else {
self.colours.border_style
self.styles.border_style
}
}

fn draw_frozen_indicator(&self, f: &mut Frame<'_>, draw_loc: Rect) {
f.render_widget(
Paragraph::new(Span::styled(
"Frozen, press 'f' to unfreeze",
self.colours.selected_text_style,
self.styles.selected_text_style,
)),
Layout::default()
.horizontal_margin(1)
Expand Down Expand Up @@ -333,15 +333,11 @@ impl Painter {
_ => 0,
};

self.draw_process(f, app_state, rect[0], true, widget_id);
self.draw_process(f, app_state, rect[0], widget_id);
}
Battery => {
self.draw_battery(f, app_state, rect[0], app_state.current_widget.widget_id)
}
Battery => self.draw_battery(
f,
app_state,
rect[0],
true,
app_state.current_widget.widget_id,
),
_ => {}
}
} else if app_state.app_config_fields.use_basic_mode {
Expand Down Expand Up @@ -444,18 +440,14 @@ impl Painter {
ProcSort => 2,
_ => 0,
};
self.draw_process(f, app_state, vertical_chunks[3], false, wid);
self.draw_process(f, app_state, vertical_chunks[3], wid);
}
Temp => {
self.draw_temp_table(f, app_state, vertical_chunks[3], widget_id)
}
Battery => self.draw_battery(
f,
app_state,
vertical_chunks[3],
false,
widget_id,
),
Battery => {
self.draw_battery(f, app_state, vertical_chunks[3], widget_id)
}
_ => {}
}
}
Expand Down Expand Up @@ -729,8 +721,8 @@ impl Painter {
Net => self.draw_network(f, app_state, *draw_loc, widget.widget_id),
Temp => self.draw_temp_table(f, app_state, *draw_loc, widget.widget_id),
Disk => self.draw_disk_table(f, app_state, *draw_loc, widget.widget_id),
Proc => self.draw_process(f, app_state, *draw_loc, true, widget.widget_id),
Battery => self.draw_battery(f, app_state, *draw_loc, true, widget.widget_id),
Proc => self.draw_process(f, app_state, *draw_loc, widget.widget_id),
Battery => self.draw_battery(f, app_state, *draw_loc, widget.widget_id),
_ => {}
}
}
Expand Down
1 change: 1 addition & 0 deletions src/canvas/components.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
pub mod data_table;
pub mod time_graph;
mod tui_widget;

pub mod widget_carousel;

pub use tui_widget::*;
83 changes: 37 additions & 46 deletions src/canvas/components/data_table/draw.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,18 @@ use concat_string::concat_string;
use tui::{
layout::{Constraint, Direction, Layout, Rect},
text::{Line, Span, Text},
widgets::{Block, Borders, Row, Table},
widgets::{Block, Row, Table},
Frame,
};
use unicode_segmentation::UnicodeSegmentation;

use super::{
CalculateColumnWidths, ColumnHeader, ColumnWidthBounds, DataTable, DataTableColumn, DataToCell,
SortType,
};
use crate::{
app::layout_manager::BottomWidget,
canvas::Painter,
constants::{SIDE_BORDERS, TABLE_GAP_HEIGHT_LIMIT},
canvas::{drawing_utils::widget_block, Painter},
constants::TABLE_GAP_HEIGHT_LIMIT,
utils::strings::truncate_to_text,
};

Expand Down Expand Up @@ -68,46 +67,41 @@ where
C: DataTableColumn<H>,
{
fn block<'a>(&self, draw_info: &'a DrawInfo, data_len: usize) -> Block<'a> {
let border_style = match draw_info.selection_state {
SelectionState::NotSelected => self.styling.border_style,
SelectionState::Selected | SelectionState::Expanded => {
self.styling.highlighted_border_style
}
let is_selected = match draw_info.selection_state {
SelectionState::NotSelected => false,
SelectionState::Selected | SelectionState::Expanded => true,
};

if !self.props.is_basic {
let block = Block::default()
.borders(Borders::ALL)
.border_style(border_style);
let border_style = if is_selected {
self.styling.highlighted_border_style
} else {
self.styling.border_style
};

if let Some(title) = self.generate_title(draw_info, data_len) {
block.title(title)
} else {
block
let mut block = widget_block(self.props.is_basic, is_selected, self.styling.border_type)
.border_style(border_style);

if let Some((left_title, right_title)) = self.generate_title(draw_info, data_len) {
if !self.props.is_basic {
block = block.title_top(left_title);
}

if let Some(right_title) = right_title {
block = block.title_top(right_title);
}
} else if draw_info.is_on_widget() {
// Implies it is basic mode but selected.
Block::default()
.borders(SIDE_BORDERS)
.border_style(border_style)
} else {
Block::default().borders(Borders::NONE)
}

block
}

/// Generates a title, given the available space.
pub fn generate_title<'a>(
&self, draw_info: &'a DrawInfo, total_items: usize,
) -> Option<Line<'a>> {
fn generate_title(
&self, draw_info: &'_ DrawInfo, total_items: usize,
) -> Option<(Line<'static>, Option<Line<'static>>)> {
self.props.title.as_ref().map(|title| {
let current_index = self.state.current_index.saturating_add(1);
let draw_loc = draw_info.loc;
let title_style = self.styling.title_style;
let border_style = if draw_info.is_on_widget() {
self.styling.highlighted_border_style
} else {
self.styling.border_style
};

let title = if self.props.show_table_scroll_position {
let pos = current_index.to_string();
Expand All @@ -123,19 +117,15 @@ where
title.to_string()
};

if draw_info.is_expanded() {
let title_base = concat_string!(title, "── Esc to go back ");
let lines = "─".repeat(usize::from(draw_loc.width).saturating_sub(
UnicodeSegmentation::graphemes(title_base.as_str(), true).count() + 2,
));
let esc = concat_string!("─", lines, "─ Esc to go back ");
Line::from(vec![
Span::styled(title, title_style),
Span::styled(esc, border_style),
])
let left_title = Line::from(Span::styled(title, title_style)).left_aligned();

let right_title = if draw_info.is_expanded() {
Some(Line::from(" Esc to go back ").right_aligned())
} else {
Line::from(Span::styled(title, title_style))
}
None
};

(left_title, right_title)
})
}

Expand Down Expand Up @@ -202,8 +192,9 @@ where

if !self.data.is_empty() || !self.first_draw {
if self.first_draw {
self.first_draw = false; // TODO: Doing it this way is fine, but it could be done better (e.g. showing
// custom no results/entries message)
// TODO: Doing it this way is fine, but it could be done better (e.g. showing
// custom no results/entries message)
self.first_draw = false;
if let Some(first_index) = self.first_index {
self.set_position(first_index);
}
Expand Down
20 changes: 11 additions & 9 deletions src/canvas/components/data_table/styling.rs
Original file line number Diff line number Diff line change
@@ -1,26 +1,28 @@
use tui::style::Style;
use tui::{style::Style, widgets::BorderType};

use crate::options::config::style::ColourPalette;
use crate::options::config::style::Styles;

#[derive(Default)]
pub struct DataTableStyling {
pub header_style: Style,
pub border_style: Style,
pub border_type: BorderType,
pub highlighted_border_style: Style,
pub text_style: Style,
pub highlighted_text_style: Style,
pub title_style: Style,
}

impl DataTableStyling {
pub fn from_palette(colours: &ColourPalette) -> Self {
pub fn from_palette(styles: &Styles) -> Self {
Self {
header_style: colours.table_header_style,
border_style: colours.border_style,
highlighted_border_style: colours.highlighted_border_style,
text_style: colours.text_style,
highlighted_text_style: colours.selected_text_style,
title_style: colours.widget_title_style,
header_style: styles.table_header_style,
border_style: styles.border_style,
border_type: styles.border_type,
highlighted_border_style: styles.highlighted_border_style,
text_style: styles.text_style,
highlighted_text_style: styles.selected_text_style,
title_style: styles.widget_title_style,
}
}
}
Loading
Loading