Skip to content

Commit

Permalink
feat: config options for animation speed, notification width, and scr…
Browse files Browse the repository at this point in the history
…oll padding
  • Loading branch information
Beastwick18 committed Jun 6, 2024
1 parent ab006c7 commit 53a86ce
Show file tree
Hide file tree
Showing 9 changed files with 99 additions and 65 deletions.
2 changes: 1 addition & 1 deletion src/client/qbit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ use super::{ClientConfig, DownloadClient, DownloadError, DownloadResult};
pub struct QbitConfig {
pub base_url: String,
pub username: String,
pub password: String,
pub password: String, // TODO: introduce password_env and password_cmd for retreiving
pub use_magnet: Option<bool>,
pub savepath: Option<String>,
pub category: Option<String>, // Single category
Expand Down
8 changes: 6 additions & 2 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ pub trait ConfigManager {
fn path() -> Result<PathBuf, Box<dyn Error>>;
}

pub struct AppConfig;

pub static CONFIG_FILE: &str = "config";

#[derive(Serialize, Deserialize, Clone)]
Expand All @@ -34,7 +36,8 @@ pub struct Config {
pub download_client: Client,
pub date_format: Option<String>,
pub request_proxy: Option<String>,
pub timeout: u64, // TODO: treat as "global" timeout, can overwrite per-source
pub timeout: u64,
pub scroll_padding: usize,

#[serde(rename = "notifications")]
pub notifications: Option<NotificationConfig>,
Expand All @@ -55,6 +58,7 @@ impl Default for Config {
date_format: None,
request_proxy: None,
timeout: 30,
scroll_padding: 3,
notifications: None,
clipboard: None,
client: ClientConfig::default(),
Expand All @@ -63,7 +67,7 @@ impl Default for Config {
}
}

impl ConfigManager for Config {
impl ConfigManager for AppConfig {
fn load() -> Result<Config, Box<dyn Error>> {
get_configuration_file_path(APP_NAME, CONFIG_FILE).and_then(load_path)
}
Expand Down
4 changes: 2 additions & 2 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use std::{env, io::stdout};

use app::App;
use config::Config;
use config::AppConfig;
use ratatui::{backend::CrosstermBackend, Terminal};
use sync::AppSync;

Expand Down Expand Up @@ -42,7 +42,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut app = App::default();
let sync = AppSync {};

app.run_app::<_, _, Config, false>(&mut terminal, sync)
app.run_app::<_, _, AppConfig, false>(&mut terminal, sync)
.await?;

util::term::reset_terminal()?;
Expand Down
18 changes: 10 additions & 8 deletions src/widget.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ use ratatui::{
},
Frame,
};
use serde::{Deserialize, Serialize};
use unicode_width::UnicodeWidthStr as _;

use crate::{app::Context, style, theme::Theme};
Expand Down Expand Up @@ -43,15 +44,16 @@ pub trait EnumIter<T> {
fn iter() -> Iter<'static, T>;
}

pub enum TitlePosition {
#[derive(Serialize, Deserialize, Clone, Copy)]
pub enum Corner {
TopLeft,
TopRight,
BottomLeft,
BottomRight,
// TopLeft, // Top Left is default for ratatui, no extra logic needed
}

impl TitlePosition {
pub fn try_widget<'a, L: Into<Line<'a>>>(
impl Corner {
pub fn try_title<'a, L: Into<Line<'a>>>(
self,
text: L,
area: Rect,
Expand All @@ -64,10 +66,10 @@ impl TitlePosition {
return None;
}
let (left, y) = match self {
// TitlePosition::TopLeft => (area.left() + 1, area.top()),
TitlePosition::TopRight => (area.right() - 1 - line_width, area.top()),
TitlePosition::BottomLeft => (area.left() + 1, area.bottom() - 1),
TitlePosition::BottomRight => (area.right() - 1 - line_width, area.bottom() - 1),
Corner::TopLeft => (area.left() + 1, area.top()),
Corner::TopRight => (area.right() - 1 - line_width, area.top()),
Corner::BottomLeft => (area.left() + 1, area.bottom() - 1),
Corner::BottomRight => (area.right() - 1 - line_width, area.bottom() - 1),
};
let right = Rect::new(left, y, line_width, 1);
Some((line, right))
Expand Down
4 changes: 2 additions & 2 deletions src/widget/batch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ use crate::{
title,
};

use super::{border_block, TitlePosition, VirtualStatefulTable};
use super::{border_block, Corner, VirtualStatefulTable};

pub struct BatchWidget {
table: VirtualStatefulTable,
Expand Down Expand Up @@ -97,7 +97,7 @@ impl super::Widget for BatchWidget {

let size = human_bytes(ctx.batch.iter().fold(0, |acc, i| acc + i.bytes) as f64);
let right_str = title!("Size({}): {}", ctx.batch.len(), size);
if let Some((tr, area)) = TitlePosition::TopRight.try_widget(right_str, area, true) {
if let Some((tr, area)) = Corner::TopRight.try_title(right_str, area, true) {
tr.render(area, buf);
}
}
Expand Down
62 changes: 45 additions & 17 deletions src/widget/notifications.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,29 +4,34 @@ use serde::{Deserialize, Serialize};

use crate::app::Context;

use super::{
notify_box::{NotifyBox, NotifyPosition},
Widget,
};
use super::{notify_box::NotifyBox, Corner, Widget};

static MAX_NOTIFS: usize = 12;

#[derive(Clone, Copy, Serialize, Deserialize)]
pub struct NotificationConfig {
pub position: Option<NotifyPosition>,
pub position: Option<Corner>,
pub duration: Option<f64>,
pub max_width: Option<u16>,
pub animation_speed: Option<f64>,
}

pub struct NotificationWidget {
notifs: Vec<NotifyBox>,
duration: f64,
position: NotifyPosition,
position: Corner,
max_width: u16,
animation_speed: f64,
}

impl Default for NotificationWidget {
fn default() -> Self {
Self {
notifs: vec![],
duration: 3.0,
position: NotifyPosition::TopRight,
position: Corner::TopRight,
max_width: 75,
animation_speed: 8.,
}
}
}
Expand All @@ -35,36 +40,59 @@ impl NotificationWidget {
pub fn load_config(&mut self, conf: &NotificationConfig) {
self.position = conf.position.unwrap_or(self.position);
self.duration = conf.duration.unwrap_or(self.duration).max(0.01);
self.max_width = conf.max_width.unwrap_or(self.max_width);
self.animation_speed = conf.animation_speed.unwrap_or(self.animation_speed);
}

pub fn is_animating(&self) -> bool {
!self.notifs.is_empty()
}

pub fn add_notification(&mut self, notif: String) {
let new_notif = NotifyBox::new(notif, self.duration, self.position, false);

self.notifs
.iter_mut()
.for_each(|n| n.add_offset(new_notif.height()));

self.notifs.push(new_notif);
let new_notif = NotifyBox::new(
notif,
self.duration,
self.position,
self.animation_speed,
self.max_width,
false,
);
self.add(new_notif);
}

pub fn add_error(&mut self, error: String) {
let new_notif = NotifyBox::new(error, 0.0, self.position, true);
let new_notif = NotifyBox::new(
error,
0.0,
self.position,
self.animation_speed,
self.max_width,
true,
);
self.add(new_notif);
}

fn add(&mut self, notif: NotifyBox) {
self.notifs
.iter_mut()
.for_each(|n| n.add_offset(new_notif.height()));
.for_each(|n| n.add_offset(notif.height()));

self.dismiss_oldest();

self.notifs.push(new_notif);
self.notifs.push(notif);
}

pub fn dismiss_all(&mut self) {
self.notifs.iter_mut().for_each(|n| n.time = 1.0);
}

fn dismiss_oldest(&mut self) {
if self.notifs.len() >= MAX_NOTIFS {
self.notifs
.drain(..=self.notifs.len().saturating_sub(MAX_NOTIFS));
}
}

pub fn update(&mut self, deltatime: f64, area: Rect) -> bool {
self.notifs.iter_mut().fold(false, |acc, x| {
let res = x.update(deltatime, area);
Expand Down
54 changes: 27 additions & 27 deletions src/widget/notify_box.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,31 +4,21 @@ use ratatui::{
widgets::{Block, Borders, Paragraph, Widget as _},
Frame,
};
use serde::{Deserialize, Serialize};

use crate::{app::Context, style};

const ANIM_SPEED: f64 = 8.0;
const MAX_WIDTH: u16 = 75;
use super::Corner;

#[derive(Clone, Copy, Deserialize, Serialize)]
pub enum NotifyPosition {
TopLeft,
TopRight,
BottomLeft,
BottomRight,
}

impl NotifyPosition {
pub fn is_top(self) -> bool {
impl Corner {
fn is_top(&self) -> bool {
matches!(self, Self::TopLeft | Self::TopRight)
}

pub fn is_left(self) -> bool {
fn is_left(&self) -> bool {
matches!(self, Self::TopLeft | Self::BottomLeft)
}

pub fn get_start_stop(
fn get_start_stop(
self,
area: Rect,
width: u16,
Expand Down Expand Up @@ -132,7 +122,9 @@ pub struct NotifyBox {
raw_content: String,
pub time: f64,
pub duration: f64,
position: NotifyPosition,
animation_speed: f64,
max_width: u16,
position: Corner,
width: u16,
height: u16,
start_offset: u16,
Expand All @@ -144,16 +136,25 @@ pub struct NotifyBox {
}

impl NotifyBox {
pub fn new(content: String, duration: f64, position: NotifyPosition, error: bool) -> Self {
pub fn new(
content: String,
duration: f64,
position: Corner,
animation_speed: f64,
max_width: u16,
error: bool,
) -> Self {
let raw_content = content.clone();
let lines = textwrap::wrap(&content, MAX_WIDTH as usize);
let lines = textwrap::wrap(&content, max_width as usize);
let actual_width = lines.iter().fold(0, |acc, x| acc.max(x.len())) as u16 + 2;
let height = lines.len() as u16 + 2;
NotifyBox {
width: actual_width,
height,
raw_content,
position,
animation_speed,
max_width,
start_offset: 0,
stop_offset: 0,
time: 0.0,
Expand Down Expand Up @@ -198,8 +199,8 @@ impl NotifyBox {

pub fn draw(&mut self, f: &mut Frame, ctx: &Context, area: Rect) {
let max_width = match self.error {
true => (area.width / 3).max(MAX_WIDTH),
false => area.width.min(MAX_WIDTH),
true => (area.width / 3).max(self.max_width),
false => area.width.min(self.max_width),
} as usize;
let lines = textwrap::wrap(&self.raw_content, max_width);
self.width = lines.iter().fold(0, |acc, x| acc.max(x.len())) as u16 + 2;
Expand Down Expand Up @@ -277,13 +278,12 @@ impl NotifyBox {
self.start_offset,
self.stop_offset,
);
match self.time >= 1.0 {
false => self
.enter_state
.ease_out(start_pos, stop_pos, ANIM_SPEED, deltatime),
true => self
.leave_state
.ease_in(stop_pos, leave_pos, ANIM_SPEED / 2.0, deltatime),
if self.time < 1.0 {
self.enter_state
.ease_out(start_pos, stop_pos, self.animation_speed, deltatime)
} else {
self.leave_state
.ease_in(stop_pos, leave_pos, self.animation_speed / 2.0, deltatime)
}
}

Expand Down
8 changes: 4 additions & 4 deletions src/widget/results.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ use crate::{
widget::sort::SortDir,
};

use super::{border_block, centered_rect, TitlePosition, VirtualStatefulTable};
use super::{border_block, centered_rect, Corner, VirtualStatefulTable};

pub struct ResultsWidget {
pub table: VirtualStatefulTable,
Expand Down Expand Up @@ -115,7 +115,7 @@ impl super::Widget for ResultsWidget {
area.height as usize,
3,
num_items,
3,
ctx.config.scroll_padding,
self.table.state.offset_mut(),
);

Expand Down Expand Up @@ -161,13 +161,13 @@ impl super::Widget for ResultsWidget {
ctx.client.to_string(),
ctx.src.to_string()
);
if let Some((tr, area)) = TitlePosition::TopRight.try_widget(dl_src, area, true) {
if let Some((tr, area)) = Corner::TopRight.try_title(dl_src, area, true) {
f.render_widget(tr, area);
}

if !ctx.last_key.is_empty() {
let key_str = title!(ctx.last_key);
if let Some((br, area)) = TitlePosition::BottomRight.try_widget(key_str, area, true) {
if let Some((br, area)) = Corner::BottomRight.try_title(key_str, area, true) {
f.render_widget(br, area);
}
}
Expand Down
4 changes: 2 additions & 2 deletions src/widget/search.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use crate::{
use super::{
border_block,
input::{self, InputWidget},
TitlePosition,
Corner,
};

pub struct SearchWidget {
Expand Down Expand Up @@ -47,7 +47,7 @@ impl super::Widget for SearchWidget {
"?".bold();
" for help".into();
);
if let Some((tr, area)) = TitlePosition::TopRight.try_widget(help_title, area, true) {
if let Some((tr, area)) = Corner::TopRight.try_title(help_title, area, true) {
tr.render(area, buf);
}

Expand Down

0 comments on commit 53a86ce

Please sign in to comment.