Skip to content
Draft
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
2 changes: 2 additions & 0 deletions Cargo.lock

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

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ mime_guess = "^2.0.4"
nom = "7.0.0"
open = "3.2.0"
rand = "0.8.5"
ratatui = "0.29.0"
ratatui = { version = "0.29.0", features = ["serde"] }
ratatui-image = { version = "~8.0.1", features = ["serde"] }
regex = "^1.5"
rpassword = "^7.2"
Expand Down
69 changes: 69 additions & 0 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -526,6 +526,71 @@ pub struct ImagePreviewProtocolValues {
pub font_size: Option<(u16, u16)>,
}

#[derive(Clone, Deserialize, Default)]
pub struct Colorscheme {
pub border: Option<Color>,
pub border_unfocused: Option<Color>,
pub window_title: Option<Color>,
pub tab_title: Option<Color>,
pub tab_title_unfocused: Option<Color>,
pub room_list: Option<Color>,
pub room_list_unread: Option<Color>,
}

#[derive(Clone, Deserialize)]
pub struct ColorschemeValues {
pub border: Style,
pub border_unfocused: Style,
pub window_title: Style,
pub tab_title: Style,
pub tab_title_unfocused: Style,
pub room_list: Style,
pub room_list_unread: Style,
}

fn merge_colorscheme(
profile: Option<Colorscheme>,
global: Option<Colorscheme>,
) -> Option<Colorscheme> {
match (profile, global) {
(None, None) => None,
(Some(c), None) | (None, Some(c)) => Some(c),
(Some(profile), Some(global)) => {
Some(Colorscheme {
border: profile.border.or(global.border),
border_unfocused: profile.border_unfocused.or(global.border_unfocused),
window_title: profile.window_title.or(global.window_title),
tab_title: profile.tab_title.or(global.tab_title),
tab_title_unfocused: profile.tab_title_unfocused.or(global.tab_title_unfocused),
room_list: profile.room_list.or(global.room_list),
room_list_unread: profile.room_list_unread.or(global.room_list_unread),
})
},
}
}

impl Colorscheme {
pub fn values(self) -> ColorschemeValues {
let border = self.border.map(Into::into).unwrap_or_default();
let border_unfocused = self.border_unfocused.map(Into::into).unwrap_or(border);
let window_title = self.window_title.map(Into::into).unwrap_or_default();
let tab_title = self.tab_title.map(Into::into).unwrap_or_default();
let tab_title_unfocused = self.tab_title_unfocused.map(Into::into).unwrap_or(tab_title);
let room_list = self.room_list.map(Into::into).unwrap_or_default();
let room_list_unread = self.room_list_unread.map(Into::into).unwrap_or(room_list);

ColorschemeValues {
border,
border_unfocused,
window_title,
tab_title,
tab_title_unfocused,
room_list,
room_list_unread,
}
}
}

#[derive(Clone)]
pub struct SortValues {
pub chats: Vec<SortColumn<SortFieldRoom>>,
Expand Down Expand Up @@ -581,6 +646,7 @@ pub struct TunableValues {
pub user_gutter_width: usize,
pub external_edit_file_suffix: String,
pub tabstop: usize,
pub colors: ColorschemeValues,
}

#[derive(Clone, Default, Deserialize)]
Expand Down Expand Up @@ -609,6 +675,7 @@ pub struct Tunables {
pub user_gutter_width: Option<usize>,
pub external_edit_file_suffix: Option<String>,
pub tabstop: Option<usize>,
pub colors: Option<Colorscheme>,
}

impl Tunables {
Expand Down Expand Up @@ -643,6 +710,7 @@ impl Tunables {
.external_edit_file_suffix
.or(other.external_edit_file_suffix),
tabstop: self.tabstop.or(other.tabstop),
colors: merge_colorscheme(self.colors, other.colors),
}
}

Expand Down Expand Up @@ -673,6 +741,7 @@ impl Tunables {
.external_edit_file_suffix
.unwrap_or_else(|| ".md".to_string()),
tabstop: self.tabstop.unwrap_or(4),
colors: self.colors.unwrap_or_default().values(),
}
}
}
Expand Down
8 changes: 5 additions & 3 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,7 @@ impl Application {
let focused = self.focused;
let sstate = &mut self.screen;
let term = &mut self.terminal;
let colors = store.application.settings.tunables.colors.clone();

if store.application.ring_bell {
store.application.ring_bell = term.backend_mut().write_all(&[7]).is_err();
Expand All @@ -328,9 +329,10 @@ impl Application {
.show_dialog(dialogstr)
.show_mode(modestr)
.borders(true)
.border_style(Style::default().add_modifier(Modifier::DIM))
.tab_style(Style::default().add_modifier(Modifier::DIM))
.tab_style_focused(Style::default().remove_modifier(Modifier::DIM))
.border_style(colors.border_unfocused.add_modifier(Modifier::DIM))
.border_style_focused(colors.border.remove_modifier(Modifier::DIM))
.tab_style(colors.tab_title_unfocused.add_modifier(Modifier::DIM))
.tab_style_focused(colors.tab_title.remove_modifier(Modifier::DIM))
.focus(focused);
f.render_stateful_widget(screen, area, sstate);

Expand Down
2 changes: 2 additions & 0 deletions src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ use crate::{
user_color,
user_style_from_color,
ApplicationSettings,
Colorscheme,
DirectoryValues,
Notifications,
NotifyVia,
Expand Down Expand Up @@ -201,6 +202,7 @@ pub fn mock_tunables() -> TunableValues {
image_preview: None,
user_gutter_width: 30,
tabstop: 4,
colors: Colorscheme::default().values(),
}
}

Expand Down
101 changes: 61 additions & 40 deletions src/windows/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,37 +92,37 @@ type MatrixRoomInfo = Arc<(MatrixRoom, Option<Tags>)>;
const MEMBER_FETCH_DEBOUNCE: Duration = Duration::from_secs(5);

#[inline]
fn bold_style() -> Style {
Style::default().add_modifier(StyleModifier::BOLD)
fn bold_style(style: Style) -> Style {
style.add_modifier(StyleModifier::BOLD)
}

#[inline]
fn bold_span(s: &str) -> Span<'_> {
Span::styled(s, bold_style())
fn bold_span(s: &str, style: Style) -> Span<'_> {
Span::styled(s, bold_style(style))
}

#[inline]
fn bold_spans(s: &str) -> Line<'_> {
bold_span(s).into()
fn bold_spans(s: &str, style: Style) -> Line<'_> {
bold_span(s, style).into()
}

#[inline]
fn selected_style(selected: bool) -> Style {
fn selected_style(selected: bool, style: Style) -> Style {
if selected {
Style::default().add_modifier(StyleModifier::REVERSED)
style.add_modifier(StyleModifier::REVERSED)
} else {
Style::default()
style
}
}

#[inline]
fn selected_span(s: &str, selected: bool) -> Span<'_> {
Span::styled(s, selected_style(selected))
fn selected_span(s: &str, selected: bool, style: Style) -> Span<'_> {
Span::styled(s, selected_style(selected, style))
}

#[inline]
fn selected_text(s: &str, selected: bool) -> Text<'_> {
Text::from(selected_span(s, selected))
fn selected_text(s: &str, selected: bool, style: Style) -> Text<'_> {
Text::from(selected_span(s, selected, style))
}

fn name_and_labels(name: &str, unread: bool, style: Style) -> (Span<'_>, Vec<Vec<Span<'_>>>) {
Expand Down Expand Up @@ -744,14 +744,15 @@ impl Window<IambInfo> for IambWindow {
}

fn get_tab_title(&self, store: &mut ProgramStore) -> Line<'_> {
let style = Default::default();
match self {
IambWindow::DirectList(_) => bold_spans("Direct Messages"),
IambWindow::RoomList(_) => bold_spans("Rooms"),
IambWindow::SpaceList(_) => bold_spans("Spaces"),
IambWindow::VerifyList(_) => bold_spans("Verifications"),
IambWindow::Welcome(_) => bold_spans("Welcome to iamb"),
IambWindow::ChatList(_) => bold_spans("DMs & Rooms"),
IambWindow::UnreadList(_) => bold_spans("Unread Messages"),
IambWindow::DirectList(_) => bold_spans("Direct Messages", style),
IambWindow::RoomList(_) => bold_spans("Rooms", style),
IambWindow::SpaceList(_) => bold_spans("Spaces", style),
IambWindow::VerifyList(_) => bold_spans("Verifications", style),
IambWindow::Welcome(_) => bold_spans("Welcome to iamb", style),
IambWindow::ChatList(_) => bold_spans("DMs & Rooms", style),
IambWindow::UnreadList(_) => bold_spans("Unread Messages", style),

IambWindow::Room(w) => {
let title = store.application.get_room_title(w.id());
Expand All @@ -762,8 +763,8 @@ impl Window<IambInfo> for IambWindow {
let title = store.application.get_room_title(room_id.as_ref());
let n = state.len();
let v = vec![
bold_span("Room Members "),
Span::styled(format!("({n}): "), bold_style()),
bold_span("Room Members ", style),
Span::styled(format!("({n}): "), bold_style(style)),
title.into(),
];
Line::from(v)
Expand All @@ -772,22 +773,23 @@ impl Window<IambInfo> for IambWindow {
}

fn get_win_title(&self, store: &mut ProgramStore) -> Line<'_> {
let style = store.application.settings.tunables.colors.window_title;
match self {
IambWindow::DirectList(_) => bold_spans("Direct Messages"),
IambWindow::RoomList(_) => bold_spans("Rooms"),
IambWindow::SpaceList(_) => bold_spans("Spaces"),
IambWindow::VerifyList(_) => bold_spans("Verifications"),
IambWindow::Welcome(_) => bold_spans("Welcome to iamb"),
IambWindow::ChatList(_) => bold_spans("DMs & Rooms"),
IambWindow::UnreadList(_) => bold_spans("Unread Messages"),
IambWindow::DirectList(_) => bold_spans("Direct Messages", style),
IambWindow::RoomList(_) => bold_spans("Rooms", style),
IambWindow::SpaceList(_) => bold_spans("Spaces", style),
IambWindow::VerifyList(_) => bold_spans("Verifications", style),
IambWindow::Welcome(_) => bold_spans("Welcome to iamb", style),
IambWindow::ChatList(_) => bold_spans("DMs & Rooms", style),
IambWindow::UnreadList(_) => bold_spans("Unread Messages", style),

IambWindow::Room(w) => w.get_title(store),
IambWindow::MemberList(state, room_id, _) => {
let title = store.application.get_room_title(room_id.as_ref());
let n = state.len();
let v = vec![
bold_span("Room Members "),
Span::styled(format!("({n}): "), bold_style()),
bold_span("Room Members ", style),
Span::styled(format!("({n}): "), bold_style(style)),
title.into(),
];
Line::from(v)
Expand Down Expand Up @@ -963,10 +965,15 @@ impl ListItem<IambInfo> for GenericChatItem {
&self,
selected: bool,
_: &ViewportContext<ListCursor>,
_: &mut ProgramStore,
store: &mut ProgramStore,
) -> Text<'_> {
let unread = self.unread.is_unread();
let style = selected_style(selected);
let style = if unread {
store.application.settings.tunables.colors.room_list_unread
} else {
store.application.settings.tunables.colors.room_list
};
let style = selected_style(selected, style);
let (name, mut labels) = name_and_labels(&self.name, unread, style);
let mut spans = vec![name];

Expand Down Expand Up @@ -1082,10 +1089,15 @@ impl ListItem<IambInfo> for RoomItem {
&self,
selected: bool,
_: &ViewportContext<ListCursor>,
_: &mut ProgramStore,
store: &mut ProgramStore,
) -> Text<'_> {
let unread = self.unread.is_unread();
let style = selected_style(selected);
let style = if unread {
store.application.settings.tunables.colors.room_list_unread
} else {
store.application.settings.tunables.colors.room_list
};
let style = selected_style(selected, style);
let (name, mut labels) = name_and_labels(&self.name, unread, style);
let mut spans = vec![name];

Expand Down Expand Up @@ -1191,10 +1203,15 @@ impl ListItem<IambInfo> for DirectItem {
&self,
selected: bool,
_: &ViewportContext<ListCursor>,
_: &mut ProgramStore,
store: &mut ProgramStore,
) -> Text<'_> {
let unread = self.unread.is_unread();
let style = selected_style(selected);
let style = if unread {
store.application.settings.tunables.colors.room_list_unread
} else {
store.application.settings.tunables.colors.room_list
};
let style = selected_style(selected, style);
let (name, mut labels) = name_and_labels(&self.name, unread, style);
let mut spans = vec![name];

Expand Down Expand Up @@ -1299,9 +1316,13 @@ impl ListItem<IambInfo> for SpaceItem {
&self,
selected: bool,
_: &ViewportContext<ListCursor>,
_: &mut ProgramStore,
store: &mut ProgramStore,
) -> Text<'_> {
selected_text(self.name.as_str(), selected)
selected_text(
self.name.as_str(),
selected,
store.application.settings.tunables.colors.room_list,
)
}

fn get_word(&self) -> Option<String> {
Expand Down Expand Up @@ -1440,7 +1461,7 @@ impl ListItem<IambInfo> for VerifyItem {
let mut lines = vec![];

let bold = Style::default().add_modifier(StyleModifier::BOLD);
let item = Span::styled(self.show_item(), selected_style(selected));
let item = Span::styled(self.show_item(), selected_style(selected, Default::default()));
lines.push(Line::from(item));

if self.sasv1.is_done() {
Expand Down
Loading