Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 549025dfe7 | |||
| fface10081 |
@@ -4,6 +4,12 @@ All notable changes to this project are documented here.
|
|||||||
|
|
||||||
The project uses semantic versioning. Tags are formatted as `vMAJOR.MINOR.PATCH`.
|
The project uses semantic versioning. Tags are formatted as `vMAJOR.MINOR.PATCH`.
|
||||||
|
|
||||||
|
## [1.2.1] - 2026-07-05
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Updated the TUI running screen to center a larger termdown-style ASCII time-left display above progress and post-countdown commands.
|
||||||
|
|
||||||
## [1.2.0] - 2026-07-05
|
## [1.2.0] - 2026-07-05
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
@@ -42,6 +48,7 @@ The project uses semantic versioning. Tags are formatted as `vMAJOR.MINOR.PATCH`
|
|||||||
- Added YAML configuration, CLI `init`, `preview`, and `run` commands.
|
- Added YAML configuration, CLI `init`, `preview`, and `run` commands.
|
||||||
- Added Gitea CI and tag-based release workflows.
|
- Added Gitea CI and tag-based release workflows.
|
||||||
|
|
||||||
|
[1.2.1]: https://gitea.forust.xyz/forust/gosleep/releases/tag/v1.2.1
|
||||||
[1.2.0]: https://gitea.forust.xyz/forust/gosleep/releases/tag/v1.2.0
|
[1.2.0]: https://gitea.forust.xyz/forust/gosleep/releases/tag/v1.2.0
|
||||||
[1.1.3]: https://gitea.forust.xyz/forust/gosleep/releases/tag/v1.1.3
|
[1.1.3]: https://gitea.forust.xyz/forust/gosleep/releases/tag/v1.1.3
|
||||||
[1.1.2]: https://gitea.forust.xyz/forust/gosleep/releases/tag/v1.1.2
|
[1.1.2]: https://gitea.forust.xyz/forust/gosleep/releases/tag/v1.1.2
|
||||||
|
|||||||
Generated
+1
-1
@@ -298,7 +298,7 @@ checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "gosleep-timer"
|
name = "gosleep-timer"
|
||||||
version = "1.2.0"
|
version = "1.2.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"clap",
|
"clap",
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "gosleep-timer"
|
name = "gosleep-timer"
|
||||||
version = "1.2.0"
|
version = "1.2.1"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
rust-version = "1.85"
|
rust-version = "1.85"
|
||||||
description = "Terminal sleep timer for Linux desktop actions, with YAML config and a Rust TUI"
|
description = "Terminal sleep timer for Linux desktop actions, with YAML config and a Rust TUI"
|
||||||
|
|||||||
+149
-33
@@ -993,35 +993,33 @@ impl App {
|
|||||||
let chunks = Layout::default()
|
let chunks = Layout::default()
|
||||||
.direction(Direction::Vertical)
|
.direction(Direction::Vertical)
|
||||||
.constraints([
|
.constraints([
|
||||||
|
Constraint::Min(1),
|
||||||
|
Constraint::Length(9),
|
||||||
|
Constraint::Min(1),
|
||||||
Constraint::Length(2),
|
Constraint::Length(2),
|
||||||
Constraint::Length(7),
|
Constraint::Length(8),
|
||||||
Constraint::Length(2),
|
|
||||||
Constraint::Min(4),
|
|
||||||
Constraint::Length(1),
|
Constraint::Length(1),
|
||||||
])
|
])
|
||||||
.split(area);
|
.split(area);
|
||||||
|
|
||||||
frame.render_widget(
|
|
||||||
Paragraph::new("gosleep-timer").style(
|
|
||||||
Style::default()
|
|
||||||
.fg(Color::Cyan)
|
|
||||||
.add_modifier(Modifier::BOLD),
|
|
||||||
),
|
|
||||||
chunks[0],
|
|
||||||
);
|
|
||||||
|
|
||||||
let time_lines = ascii_time(&format_compact_duration(remaining))
|
let time_lines = ascii_time(&format_compact_duration(remaining))
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|line| Line::from(Span::styled(line, Style::default().fg(Color::Cyan))))
|
.map(|line| Line::from(Span::styled(line, Style::default().fg(Color::White))))
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
frame.render_widget(Paragraph::new(time_lines), chunks[1]);
|
let time_width = time_lines
|
||||||
|
.iter()
|
||||||
|
.map(|line| line.width() as u16)
|
||||||
|
.max()
|
||||||
|
.unwrap_or(0);
|
||||||
|
let timer_area = center_rect(time_width.min(chunks[1].width), 7, chunks[1]);
|
||||||
|
frame.render_widget(Paragraph::new(time_lines), timer_area);
|
||||||
|
|
||||||
frame.render_widget(
|
frame.render_widget(
|
||||||
Gauge::default()
|
Gauge::default()
|
||||||
.gauge_style(Style::default().fg(Color::Magenta))
|
.gauge_style(Style::default().fg(Color::Magenta))
|
||||||
.label(format_gauge_label(progress))
|
.label(format_gauge_label(progress))
|
||||||
.ratio(progress),
|
.ratio(progress),
|
||||||
chunks[2],
|
chunks[3],
|
||||||
);
|
);
|
||||||
|
|
||||||
let commands = build_action_commands(&self.config);
|
let commands = build_action_commands(&self.config);
|
||||||
@@ -1038,7 +1036,7 @@ impl App {
|
|||||||
};
|
};
|
||||||
wrap_command(
|
wrap_command(
|
||||||
&command.display(),
|
&command.display(),
|
||||||
chunks[3].width.saturating_sub(4) as usize,
|
chunks[4].width.saturating_sub(4) as usize,
|
||||||
)
|
)
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(move |line| Line::from(Span::styled(line, style)))
|
.map(move |line| Line::from(Span::styled(line, style)))
|
||||||
@@ -1054,7 +1052,7 @@ impl App {
|
|||||||
.border_style(Color::Blue),
|
.border_style(Color::Blue),
|
||||||
)
|
)
|
||||||
.wrap(Wrap { trim: false }),
|
.wrap(Wrap { trim: false }),
|
||||||
chunks[3],
|
chunks[4],
|
||||||
);
|
);
|
||||||
|
|
||||||
let footer = if self
|
let footer = if self
|
||||||
@@ -1068,7 +1066,7 @@ impl App {
|
|||||||
};
|
};
|
||||||
frame.render_widget(
|
frame.render_widget(
|
||||||
Paragraph::new(footer).style(Style::default().fg(Color::DarkGray)),
|
Paragraph::new(footer).style(Style::default().fg(Color::DarkGray)),
|
||||||
chunks[4],
|
chunks[5],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1671,33 +1669,141 @@ fn format_gauge_label(progress: f64) -> String {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn ascii_time(value: &str) -> Vec<String> {
|
fn ascii_time(value: &str) -> Vec<String> {
|
||||||
let mut rows = vec![String::new(); 5];
|
const GLYPH_WIDTH: usize = 10;
|
||||||
|
let mut rows = vec![String::new(); 7];
|
||||||
for ch in value.chars() {
|
for ch in value.chars() {
|
||||||
let glyph = ascii_glyph(ch);
|
let glyph = ascii_glyph(ch);
|
||||||
for (row, part) in rows.iter_mut().zip(glyph) {
|
for (row, part) in rows.iter_mut().zip(glyph) {
|
||||||
if !row.is_empty() {
|
if !row.is_empty() {
|
||||||
row.push(' ');
|
row.push(' ');
|
||||||
}
|
}
|
||||||
row.push_str(part);
|
row.push_str(&format!("{part:<GLYPH_WIDTH$}"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
rows
|
rows
|
||||||
}
|
}
|
||||||
|
|
||||||
fn ascii_glyph(ch: char) -> [&'static str; 5] {
|
fn ascii_glyph(ch: char) -> [&'static str; 7] {
|
||||||
match ch {
|
match ch {
|
||||||
'0' => [" ### ", "# #", "# #", "# #", " ### "],
|
'0' => [
|
||||||
'1' => [" # ", " ## ", " # ", " # ", " ### "],
|
" ad8888ba ",
|
||||||
'2' => [" ### ", "# #", " # ", " # ", "#####"],
|
"d8\" \"8b",
|
||||||
'3' => ["#### ", " #", " ### ", " #", "#### "],
|
"8P 88",
|
||||||
'4' => ["# #", "# #", "#####", " #", " #"],
|
"8b d8",
|
||||||
'5' => ["#####", "# ", "#### ", " #", "#### "],
|
"8b d8",
|
||||||
'6' => [" ### ", "# ", "#### ", "# #", " ### "],
|
"Y8a. .a8P",
|
||||||
'7' => ["#####", " #", " # ", " # ", " # "],
|
" \"Y8888Y\" ",
|
||||||
'8' => [" ### ", "# #", " ### ", "# #", " ### "],
|
],
|
||||||
'9' => [" ### ", "# #", " ####", " #", " ### "],
|
'1' => [
|
||||||
':' => [" ", " # ", " ", " # ", " "],
|
" 88 ",
|
||||||
_ => [" ", " ", " ", " ", " "],
|
" 888 ",
|
||||||
|
" 88 ",
|
||||||
|
" 88 ",
|
||||||
|
" 88 ",
|
||||||
|
" 88 ",
|
||||||
|
" 888888 ",
|
||||||
|
],
|
||||||
|
'2' => [
|
||||||
|
" ad8888ba ",
|
||||||
|
"d8\" \"8b",
|
||||||
|
" ,8P",
|
||||||
|
" ,d8P ",
|
||||||
|
" ,d8P' ",
|
||||||
|
" d8P' ",
|
||||||
|
"888888888",
|
||||||
|
],
|
||||||
|
'3' => [
|
||||||
|
" ad8888ba ",
|
||||||
|
"d8\" \"8b",
|
||||||
|
" ,8P",
|
||||||
|
" aad8\" ",
|
||||||
|
" `8b",
|
||||||
|
"Y8a. .a8P",
|
||||||
|
" \"Y8888Y\" ",
|
||||||
|
],
|
||||||
|
'4' => [
|
||||||
|
"88 88",
|
||||||
|
"88 88",
|
||||||
|
"88 88",
|
||||||
|
"888888888",
|
||||||
|
" 88",
|
||||||
|
" 88",
|
||||||
|
" 88",
|
||||||
|
],
|
||||||
|
'5' => [
|
||||||
|
"888888888",
|
||||||
|
"88 ",
|
||||||
|
"88 ",
|
||||||
|
"888888ba ",
|
||||||
|
" 8b",
|
||||||
|
"Y8a. .a8P",
|
||||||
|
" \"Y8888Y\" ",
|
||||||
|
],
|
||||||
|
'6' => [
|
||||||
|
" ad8888ba ",
|
||||||
|
"d8\" ",
|
||||||
|
"88 ",
|
||||||
|
"88PPPPba ",
|
||||||
|
"88 8b",
|
||||||
|
"Y8a. .a8P",
|
||||||
|
" \"Y8888Y\" ",
|
||||||
|
],
|
||||||
|
'7' => [
|
||||||
|
"888888888",
|
||||||
|
" ,8P",
|
||||||
|
" ,8P ",
|
||||||
|
" ,8P ",
|
||||||
|
" ,8P ",
|
||||||
|
" ,8P ",
|
||||||
|
" ,8P ",
|
||||||
|
],
|
||||||
|
'8' => [
|
||||||
|
" ad8888ba ",
|
||||||
|
"d8\" \"8b",
|
||||||
|
"Y8a. .a8P",
|
||||||
|
" Y8888P ",
|
||||||
|
"d8\" \"8b",
|
||||||
|
"Y8a. .a8P",
|
||||||
|
" \"Y8888Y\" ",
|
||||||
|
],
|
||||||
|
'9' => [
|
||||||
|
" ad8888ba ",
|
||||||
|
"d8\" \"8b",
|
||||||
|
"Y8a. .a8P",
|
||||||
|
" \"Y888888",
|
||||||
|
" 88",
|
||||||
|
"Y8a. .a8P",
|
||||||
|
" \"Y8888Y\" ",
|
||||||
|
],
|
||||||
|
':' => [
|
||||||
|
" ",
|
||||||
|
" 88 ",
|
||||||
|
" 88 ",
|
||||||
|
" ",
|
||||||
|
" 88 ",
|
||||||
|
" 88 ",
|
||||||
|
" ",
|
||||||
|
],
|
||||||
|
_ => [
|
||||||
|
" ",
|
||||||
|
" ",
|
||||||
|
" ",
|
||||||
|
" ",
|
||||||
|
" ",
|
||||||
|
" ",
|
||||||
|
" ",
|
||||||
|
],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn center_rect(width: u16, height: u16, area: Rect) -> Rect {
|
||||||
|
let width = width.min(area.width);
|
||||||
|
let height = height.min(area.height);
|
||||||
|
Rect {
|
||||||
|
x: area.x + area.width.saturating_sub(width) / 2,
|
||||||
|
y: area.y + area.height.saturating_sub(height) / 2,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1939,6 +2045,16 @@ mod tests {
|
|||||||
assert!(keys.contains(&FieldKey::CustomCommands));
|
assert!(keys.contains(&FieldKey::CustomCommands));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn ascii_time_rows_have_equal_width() {
|
||||||
|
let rows = ascii_time("14:45:27");
|
||||||
|
let width = rows.first().map(String::len).unwrap_or(0);
|
||||||
|
|
||||||
|
assert_eq!(rows.len(), 7);
|
||||||
|
assert!(width > 0);
|
||||||
|
assert!(rows.iter().all(|row| row.len() == width));
|
||||||
|
}
|
||||||
|
|
||||||
fn visible_field_keys(config: &Config) -> Vec<FieldKey> {
|
fn visible_field_keys(config: &Config) -> Vec<FieldKey> {
|
||||||
visible_fields(config)
|
visible_fields(config)
|
||||||
.into_iter()
|
.into_iter()
|
||||||
|
|||||||
Reference in New Issue
Block a user