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:
67
src/main.rs
67
src/main.rs
@@ -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);
|
||||||
|
|||||||
@@ -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,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user