Highlight active border and change duration

The intention and duration fields now get a thick border when the user
can input data into them.

There is now also a feature to change the session duration via `a` and
`x` during the session in case the user is done early or wants to
continue longer.
This commit is contained in:
2024-07-05 11:56:19 -04:00
parent 95dcfa72d6
commit 38a03f063d
2 changed files with 65 additions and 16 deletions

View File

@@ -8,7 +8,10 @@ mod window;
use ratatui::{ use ratatui::{
crossterm::{ crossterm::{
event::{self, Event, KeyCode}, event::{self, Event, KeyCode},
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen}, execute,
terminal::{
disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen, SetTitle,
},
ExecutableCommand, ExecutableCommand,
}, },
prelude::*, prelude::*,
@@ -32,6 +35,7 @@ struct App {
current_window_time: Instant, current_window_time: Instant,
session_start: Instant, session_start: Instant,
session_stats: HashMap<Rc<String>, Duration>, session_stats: HashMap<Rc<String>, Duration>,
session_remaining: Duration,
last_tick_50ms: Instant, last_tick_50ms: Instant,
last_tick_1s: Instant, last_tick_1s: Instant,
} }
@@ -49,6 +53,7 @@ impl App {
current_window_time: Instant::now(), current_window_time: Instant::now(),
session_start: Instant::now(), session_start: Instant::now(),
session_stats: HashMap::new(), session_stats: HashMap::new(),
session_remaining: Duration::new(0, 0),
last_tick_50ms: Instant::now(), last_tick_50ms: Instant::now(),
last_tick_1s: Instant::now(), last_tick_1s: Instant::now(),
} }
@@ -79,12 +84,19 @@ impl App {
fn tick_50ms(&mut self) { fn tick_50ms(&mut self) {
match self.state { match self.state {
State::InputIntention | State::InputDuration => { State::InputIntention | State::InputDuration => {
window::minimize_other("kitty"); window::minimize_other("AntiDrift");
} }
State::InProgress => { State::InProgress => {
let elapsed = self.session_start.elapsed();
self.session_remaining = self.user_duration.saturating_sub(elapsed);
update_session_stats(self); update_session_stats(self);
if self.session_remaining == Duration::new(0, 0) {
self.state = State::InputIntention;
self.user_intention = "Session complete. New session?".to_string();
}
} }
State::ShouldQuit => {}, State::ShouldQuit => {}
} }
self.last_tick_50ms = Instant::now(); self.last_tick_50ms = Instant::now();
@@ -128,6 +140,7 @@ fn session_stats_to_lines(session_stats: &HashMap<Rc<String>, Duration>) -> Vec<
fn main() -> Result<()> { fn main() -> Result<()> {
enable_raw_mode()?; enable_raw_mode()?;
stdout().execute(EnterAlternateScreen)?; stdout().execute(EnterAlternateScreen)?;
execute!(stdout(), SetTitle("AntiDrift"))?;
let mut terminal = Terminal::new(CrosstermBackend::new(stdout()))?; let mut terminal = Terminal::new(CrosstermBackend::new(stdout()))?;
let mut app = App::new(); let mut app = App::new();
@@ -162,6 +175,7 @@ fn handle_events(app: &mut App) -> Result<()> {
if app.state == State::InputIntention { if app.state == State::InputIntention {
match key.code { match key.code {
KeyCode::Enter => app.state = State::InputDuration, KeyCode::Enter => app.state = State::InputDuration,
KeyCode::Tab => app.state = State::InputDuration,
KeyCode::Backspace => { KeyCode::Backspace => {
let _ = app.user_intention.pop(); let _ = app.user_intention.pop();
} }
@@ -173,6 +187,7 @@ fn handle_events(app: &mut App) -> Result<()> {
} else if app.state == State::InputDuration { } else if app.state == State::InputDuration {
match key.code { match key.code {
KeyCode::Enter => app.to_in_progress(), KeyCode::Enter => app.to_in_progress(),
KeyCode::Tab => app.state = State::InputIntention,
KeyCode::Backspace => { KeyCode::Backspace => {
let _ = app.user_duration_str.pop(); let _ = app.user_duration_str.pop();
} }
@@ -181,6 +196,19 @@ fn handle_events(app: &mut App) -> Result<()> {
} }
_ => {} _ => {}
} }
} else if app.state == State::InProgress {
match key.code {
KeyCode::Char('q') => {
app.state = State::InputIntention;
}
KeyCode::Char('a') => {
app.user_duration = app.user_duration.saturating_add(Duration::from_secs(60));
}
KeyCode::Char('x') => {
app.user_duration = app.user_duration.saturating_sub(Duration::from_secs(60));
}
_ => {}
}
} }
Ok(()) Ok(())
@@ -206,25 +234,42 @@ fn ui(frame: &mut Frame, app: &App) {
Constraint::Min(3), Constraint::Min(3),
Constraint::Percentage(100), Constraint::Percentage(100),
]); ]);
let [layout_intention, layout_countdown, layout_titles] = layout.areas(frame.size()); let [layout_intention, layout_duration, layout_titles] = layout.areas(frame.size());
let input: Vec<Line> = vec![Line::from(Span::raw(&app.user_intention))]; let border_type_intention = if app.state == State::InputIntention {
BorderType::Thick
} else {
BorderType::Plain
};
let input_intention: Vec<Line> = vec![Line::from(Span::raw(&app.user_intention))];
frame.render_widget( frame.render_widget(
Paragraph::new(input).block(Block::bordered().title("Intention")), Paragraph::new(input_intention).block(
Block::bordered()
.border_type(border_type_intention)
.title("Intention"),
),
layout_intention, layout_intention,
); );
let input: Vec<Line> = if app.state == State::InProgress { let input_duration: Vec<Line> = if app.state == State::InProgress {
let remaining = app.user_duration.saturating_sub(app.session_start.elapsed()); let s = duration_as_str(&app.session_remaining);
let s = duration_as_str(&remaining);
vec![Line::from(Span::raw(s))] vec![Line::from(Span::raw(s))]
} else { } else {
vec![Line::from(Span::raw(&app.user_duration_str))] vec![Line::from(Span::raw(&app.user_duration_str))]
}; };
let border_type_duration = if app.state == State::InputDuration {
BorderType::Thick
} else {
BorderType::Plain
};
frame.render_widget( frame.render_widget(
Paragraph::new(input).block(Block::bordered().title("Duration")), Paragraph::new(input_duration).block(
layout_countdown, Block::bordered()
.border_type(border_type_duration)
.title("Duration"),
),
layout_duration,
); );
let stats = session_stats_to_lines(&app.session_stats); let stats = session_stats_to_lines(&app.session_stats);

View File

@@ -7,16 +7,16 @@ pub fn get_title_clean() -> String {
re.replace_all(&title, "").to_string() re.replace_all(&title, "").to_string()
} }
pub fn minimize_other(class: &str) { pub fn minimize_other(title: &str) {
let window_info = get_window_info(); let window_info = get_window_info();
if &window_info.class != class { if &window_info.title != title {
run(&format!("xdotool windowminimize {}", window_info.wid)); run(&format!("xdotool windowminimize {}", window_info.wid));
} }
} }
struct WindowInfo { struct WindowInfo {
title: String, title: String,
class: String, _class: String,
wid: String, wid: String,
} }
@@ -46,7 +46,7 @@ fn run(cmd: &str) -> Option<String> {
fn get_window_info() -> WindowInfo { fn get_window_info() -> WindowInfo {
let none = WindowInfo { let none = WindowInfo {
title: "none".to_string(), title: "none".to_string(),
class: "none".to_string(), _class: "none".to_string(),
wid: "".to_string(), wid: "".to_string(),
}; };
@@ -62,5 +62,9 @@ fn get_window_info() -> WindowInfo {
return none; return none;
}; };
WindowInfo { title, class, wid } WindowInfo {
title,
_class: class,
wid,
}
} }