max / goingson
5 files changed,
+114 insertions,
-51 deletions
| @@ -59,18 +59,6 @@ | |||
| 59 | 59 | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 60 | 60 | checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" | |
| 61 | 61 | ||
| 62 | - | [[package]] | |
| 63 | - | name = "alloy_tui" | |
| 64 | - | version = "4.1.1" | |
| 65 | - | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 66 | - | checksum = "0fc8efbc6697c89aa62ba9ff338f140e2a2821b0ba11138acbb9f1f2f55fa2bc" | |
| 67 | - | dependencies = [ | |
| 68 | - | "makeover", | |
| 69 | - | "makeover-geometry", | |
| 70 | - | "makeover-tui", | |
| 71 | - | "ratatui", | |
| 72 | - | ] | |
| 73 | - | ||
| 74 | 62 | [[package]] | |
| 75 | 63 | name = "ammonia" | |
| 76 | 64 | version = "4.1.4" | |
| @@ -1336,7 +1324,7 @@ | |||
| 1336 | 1324 | "libc", | |
| 1337 | 1325 | "option-ext", | |
| 1338 | 1326 | "redox_users", | |
| 1339 | - | "windows-sys 0.59.0", | |
| 1327 | + | "windows-sys 0.61.2", | |
| 1340 | 1328 | ] | |
| 1341 | 1329 | ||
| 1342 | 1330 | [[package]] | |
| @@ -1586,7 +1574,7 @@ | |||
| 1586 | 1574 | checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" | |
| 1587 | 1575 | dependencies = [ | |
| 1588 | 1576 | "libc", | |
| 1589 | - | "windows-sys 0.52.0", | |
| 1577 | + | "windows-sys 0.61.2", | |
| 1590 | 1578 | ] | |
| 1591 | 1579 | ||
| 1592 | 1580 | [[package]] | |
| @@ -2292,12 +2280,12 @@ | |||
| 2292 | 2280 | name = "got" | |
| 2293 | 2281 | version = "0.5.0" | |
| 2294 | 2282 | dependencies = [ | |
| 2295 | - | "alloy_tui", | |
| 2296 | 2283 | "anyhow", | |
| 2297 | 2284 | "chrono", | |
| 2298 | 2285 | "goingson-core", | |
| 2299 | 2286 | "goingson-db-sqlite", | |
| 2300 | 2287 | "makeover", | |
| 2288 | + | "makeover-tui", | |
| 2301 | 2289 | "ratatui", | |
| 2302 | 2290 | "sqlx", | |
| 2303 | 2291 | "tokio", | |
| @@ -3402,10 +3390,11 @@ | |||
| 3402 | 3390 | ||
| 3403 | 3391 | [[package]] | |
| 3404 | 3392 | name = "makeover-tui" | |
| 3405 | - | version = "0.5.1" | |
| 3393 | + | version = "0.6.0" | |
| 3406 | 3394 | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 3407 | - | checksum = "70b1561cdd6db865a4a427d49c4752d4bac53a0e2c6038e869ea95ab808469ff" | |
| 3395 | + | checksum = "64f203bbbfb41450908603551bf23ff39d79d68b5be7b77ce51f3de3e459b397" | |
| 3408 | 3396 | dependencies = [ | |
| 3397 | + | "makeover", | |
| 3409 | 3398 | "makeover-layout", | |
| 3410 | 3399 | "ratatui", | |
| 3411 | 3400 | ] | |
| @@ -3571,7 +3560,7 @@ | |||
| 3571 | 3560 | "png 0.18.1", | |
| 3572 | 3561 | "serde", | |
| 3573 | 3562 | "thiserror 2.0.19", | |
| 3574 | - | "windows-sys 0.60.2", | |
| 3563 | + | "windows-sys 0.61.2", | |
| 3575 | 3564 | ] | |
| 3576 | 3565 | ||
| 3577 | 3566 | [[package]] | |
| @@ -3704,7 +3693,7 @@ | |||
| 3704 | 3693 | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 3705 | 3694 | checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" | |
| 3706 | 3695 | dependencies = [ | |
| 3707 | - | "windows-sys 0.59.0", | |
| 3696 | + | "windows-sys 0.61.2", | |
| 3708 | 3697 | ] | |
| 3709 | 3698 | ||
| 3710 | 3699 | [[package]] | |
| @@ -5017,7 +5006,7 @@ | |||
| 5017 | 5006 | "errno", | |
| 5018 | 5007 | "libc", | |
| 5019 | 5008 | "linux-raw-sys", | |
| 5020 | - | "windows-sys 0.52.0", | |
| 5009 | + | "windows-sys 0.61.2", | |
| 5021 | 5010 | ] | |
| 5022 | 5011 | ||
| 5023 | 5012 | [[package]] | |
| @@ -5074,7 +5063,7 @@ | |||
| 5074 | 5063 | "security-framework", | |
| 5075 | 5064 | "security-framework-sys", | |
| 5076 | 5065 | "webpki-root-certs", | |
| 5077 | - | "windows-sys 0.52.0", | |
| 5066 | + | "windows-sys 0.61.2", | |
| 5078 | 5067 | ] | |
| 5079 | 5068 | ||
| 5080 | 5069 | [[package]] | |
| @@ -5095,7 +5084,7 @@ | |||
| 5095 | 5084 | "security-framework", | |
| 5096 | 5085 | "security-framework-sys", | |
| 5097 | 5086 | "webpki-root-certs", | |
| 5098 | - | "windows-sys 0.52.0", | |
| 5087 | + | "windows-sys 0.61.2", | |
| 5099 | 5088 | ] | |
| 5100 | 5089 | ||
| 5101 | 5090 | [[package]] | |
| @@ -5610,7 +5599,7 @@ | |||
| 5610 | 5599 | checksum = "c3d1e2c7f27f8d4cb10542a02c49005dbd6e93095799d6f3be745fae9f8fedd4" | |
| 5611 | 5600 | dependencies = [ | |
| 5612 | 5601 | "libc", | |
| 5613 | - | "windows-sys 0.60.2", | |
| 5602 | + | "windows-sys 0.61.2", | |
| 5614 | 5603 | ] | |
| 5615 | 5604 | ||
| 5616 | 5605 | [[package]] | |
| @@ -6516,7 +6505,7 @@ | |||
| 6516 | 6505 | "serde_with", | |
| 6517 | 6506 | "swift-rs", | |
| 6518 | 6507 | "thiserror 2.0.19", | |
| 6519 | - | "toml 0.9.12+spec-1.1.0", | |
| 6508 | + | "toml 1.1.3+spec-1.1.0", | |
| 6520 | 6509 | "url", | |
| 6521 | 6510 | "urlpattern", | |
| 6522 | 6511 | "uuid", | |
| @@ -6555,7 +6544,7 @@ | |||
| 6555 | 6544 | "getrandom 0.4.3", | |
| 6556 | 6545 | "once_cell", | |
| 6557 | 6546 | "rustix", | |
| 6558 | - | "windows-sys 0.52.0", | |
| 6547 | + | "windows-sys 0.61.2", | |
| 6559 | 6548 | ] | |
| 6560 | 6549 | ||
| 6561 | 6550 | [[package]] | |
| @@ -6577,7 +6566,7 @@ | |||
| 6577 | 6566 | "parking_lot", | |
| 6578 | 6567 | "rustix", | |
| 6579 | 6568 | "signal-hook", | |
| 6580 | - | "windows-sys 0.60.2", | |
| 6569 | + | "windows-sys 0.61.2", | |
| 6581 | 6570 | ] | |
| 6582 | 6571 | ||
| 6583 | 6572 | [[package]] | |
| @@ -7076,7 +7065,7 @@ | |||
| 7076 | 7065 | "png 0.18.1", | |
| 7077 | 7066 | "serde", | |
| 7078 | 7067 | "thiserror 2.0.19", | |
| 7079 | - | "windows-sys 0.60.2", | |
| 7068 | + | "windows-sys 0.61.2", | |
| 7080 | 7069 | ] | |
| 7081 | 7070 | ||
| 7082 | 7071 | [[package]] | |
| @@ -7111,7 +7100,7 @@ | |||
| 7111 | 7100 | dependencies = [ | |
| 7112 | 7101 | "memoffset", | |
| 7113 | 7102 | "tempfile", | |
| 7114 | - | "windows-sys 0.60.2", | |
| 7103 | + | "windows-sys 0.61.2", | |
| 7115 | 7104 | ] | |
| 7116 | 7105 | ||
| 7117 | 7106 | [[package]] | |
| @@ -7650,7 +7639,7 @@ | |||
| 7650 | 7639 | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 7651 | 7640 | checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" | |
| 7652 | 7641 | dependencies = [ | |
| 7653 | - | "windows-sys 0.52.0", | |
| 7642 | + | "windows-sys 0.61.2", | |
| 7654 | 7643 | ] | |
| 7655 | 7644 | ||
| 7656 | 7645 | [[package]] |
| @@ -83,7 +83,7 @@ | |||
| 83 | 83 | notify = "8.2" | |
| 84 | 84 | notify-debouncer-mini = "0.7" | |
| 85 | 85 | makeover = "2.4.1" | |
| 86 | - | alloy_tui = "4.1.0" | |
| 86 | + | makeover-tui = "0.6.0" | |
| 87 | 87 | toml = "1.1" | |
| 88 | 88 | ||
| 89 | 89 | # Enums |
| @@ -11,9 +11,10 @@ | |||
| 11 | 11 | goingson-core = { workspace = true, features = ["sqlx-sqlite"] } | |
| 12 | 12 | goingson-db-sqlite = { workspace = true } | |
| 13 | 13 | ||
| 14 | - | # Alloy's themed-ratatui layer, from crates.io. MIT, carved out of the | |
| 15 | - | # otherwise GPL alloy repo so it can be linked from here. | |
| 16 | - | alloy_tui.workspace = true | |
| 14 | + | # The family's terminal renderer, from crates.io. MIT. The `theme` feature is | |
| 15 | + | # the makeover-intents-to-ratatui-colours mapping; the footer this app draws | |
| 16 | + | # with them is its own, in src/statusbar.rs. | |
| 17 | + | makeover-tui = { workspace = true, features = ["theme"] } | |
| 17 | 18 | ||
| 18 | 19 | tokio = { workspace = true, features = ["rt-multi-thread", "macros"] } | |
| 19 | 20 | sqlx = { workspace = true, features = ["sqlite"] } |
| @@ -1,6 +1,6 @@ | |||
| 1 | 1 | //! got, a read-first terminal cockpit over GoingsOn's local `goingson.db`. | |
| 2 | 2 | //! | |
| 3 | - | //! First consumer of `alloy_tui` (Alloy's themed-ratatui layer). Opens the same | |
| 3 | + | //! Rendered with `makeover-tui`, the family's terminal renderer. Opens the same | |
| 4 | 4 | //! SQLite file the desktop app uses as a peer reader, exactly like `go-mcp` | |
| 5 | 5 | //! (see `crates/go-mcp`): links the domain crates directly, resolves the same | |
| 6 | 6 | //! default DB path, and reads through the normal repository layer. | |
| @@ -19,7 +19,9 @@ | |||
| 19 | 19 | use std::io::{self, Stdout}; | |
| 20 | 20 | use std::path::PathBuf; | |
| 21 | 21 | ||
| 22 | - | use alloy_tui::{AlloyStatusBar, Theme, hint}; | |
| 22 | + | mod statusbar; | |
| 23 | + | ||
| 24 | + | use crate::statusbar::{StatusBar, hint}; | |
| 23 | 25 | use anyhow::{Context, Result}; | |
| 24 | 26 | use chrono::{Datelike, Duration, Local, NaiveDate, TimeZone, Utc}; | |
| 25 | 27 | use goingson_core::{ | |
| @@ -31,6 +33,7 @@ | |||
| 31 | 33 | SqliteContactRepository, SqliteEventRepository, SqliteProjectRepository, SqliteTaskRepository, | |
| 32 | 34 | init_pool, | |
| 33 | 35 | }; | |
| 36 | + | use makeover_tui::Theme; | |
| 34 | 37 | use ratatui::Terminal; | |
| 35 | 38 | use ratatui::crossterm::event::{self, Event as CtEvent, KeyCode, KeyEventKind}; | |
| 36 | 39 | use ratatui::crossterm::{execute, terminal}; | |
| @@ -49,7 +52,7 @@ | |||
| 49 | 52 | ||
| 50 | 53 | /// The theme `got` renders in. | |
| 51 | 54 | /// | |
| 52 | - | /// `alloy_tui` ships no built-in palette on purpose: a missing or malformed | |
| 55 | + | /// `makeover-tui` ships no built-in palette on purpose: a missing or malformed | |
| 53 | 56 | /// theme is an error the user sees, not something papered over by rendering in | |
| 54 | 57 | /// colors that exist nowhere in the theme files. So load one. A theme dropped | |
| 55 | 58 | /// into `~/.config/goingson/themes/` wins; otherwise use the set `makeover` | |
| @@ -667,7 +670,7 @@ | |||
| 667 | 670 | draw_pane(f, app, p, body[2]); | |
| 668 | 671 | ||
| 669 | 672 | f.render_widget( | |
| 670 | - | AlloyStatusBar::new( | |
| 673 | + | StatusBar::new( | |
| 671 | 674 | &p, | |
| 672 | 675 | [ | |
| 673 | 676 | hint("Tab", "group"), | |
| @@ -682,7 +685,7 @@ | |||
| 682 | 685 | } | |
| 683 | 686 | ||
| 684 | 687 | /// The Work / Time / Messages tab strip, active group in accent. | |
| 685 | - | fn group_tabs(p: alloy_tui::Theme, active: Group) -> Line<'static> { | |
| 688 | + | fn group_tabs(p: Theme, active: Group) -> Line<'static> { | |
| 686 | 689 | let mut spans = Vec::new(); | |
| 687 | 690 | for (i, g) in Group::ALL.iter().enumerate() { | |
| 688 | 691 | if i > 0 { | |
| @@ -701,7 +704,7 @@ | |||
| 701 | 704 | } | |
| 702 | 705 | ||
| 703 | 706 | /// The sub-tab strip of panes within a group; the active pane is bracketed. | |
| 704 | - | fn pane_tabs(p: alloy_tui::Theme, group: Group, active: usize) -> Line<'static> { | |
| 707 | + | fn pane_tabs(p: Theme, group: Group, active: usize) -> Line<'static> { | |
| 705 | 708 | let mut spans = Vec::new(); | |
| 706 | 709 | for (i, pane) in group.panes().iter().enumerate() { | |
| 707 | 710 | if i > 0 { | |
| @@ -725,12 +728,7 @@ | |||
| 725 | 728 | } | |
| 726 | 729 | ||
| 727 | 730 | /// Render the active pane's body into `area`. | |
| 728 | - | fn draw_pane( | |
| 729 | - | f: &mut ratatui::Frame, | |
| 730 | - | app: &mut App, | |
| 731 | - | p: alloy_tui::Theme, | |
| 732 | - | area: ratatui::layout::Rect, | |
| 733 | - | ) { | |
| 731 | + | fn draw_pane(f: &mut ratatui::Frame, app: &mut App, p: Theme, area: ratatui::layout::Rect) { | |
| 734 | 732 | match app.current_pane() { | |
| 735 | 733 | Pane::Tasks => { | |
| 736 | 734 | let items: Vec<ListItem> = app.rows.iter().map(|r| row_item(p, r)).collect(); | |
| @@ -788,7 +786,7 @@ | |||
| 788 | 786 | } | |
| 789 | 787 | } | |
| 790 | 788 | ||
| 791 | - | fn row_item<'a>(p: alloy_tui::Theme, row: &'a Row) -> ListItem<'a> { | |
| 789 | + | fn row_item<'a>(p: Theme, row: &'a Row) -> ListItem<'a> { | |
| 792 | 790 | match row { | |
| 793 | 791 | Row::Header { name, open } => { | |
| 794 | 792 | let line = Line::from(vec![ | |
| @@ -828,7 +826,7 @@ | |||
| 828 | 826 | } | |
| 829 | 827 | } | |
| 830 | 828 | ||
| 831 | - | fn project_item<'a>(p: alloy_tui::Theme, row: &'a ProjectRow) -> ListItem<'a> { | |
| 829 | + | fn project_item<'a>(p: Theme, row: &'a ProjectRow) -> ListItem<'a> { | |
| 832 | 830 | let status_color = match row.status { | |
| 833 | 831 | ProjectStatus::Active => p.status_success, | |
| 834 | 832 | ProjectStatus::OnHold => p.status_warning, | |
| @@ -863,7 +861,7 @@ | |||
| 863 | 861 | ListItem::new(line) | |
| 864 | 862 | } | |
| 865 | 863 | ||
| 866 | - | fn event_item<'a>(p: alloy_tui::Theme, row: &'a EventRow) -> ListItem<'a> { | |
| 864 | + | fn event_item<'a>(p: Theme, row: &'a EventRow) -> ListItem<'a> { | |
| 867 | 865 | match row { | |
| 868 | 866 | EventRow::DayHeader { label, count } => { | |
| 869 | 867 | let line = Line::from(vec![ | |
| @@ -909,7 +907,7 @@ | |||
| 909 | 907 | } | |
| 910 | 908 | } | |
| 911 | 909 | ||
| 912 | - | fn contact_item<'a>(p: alloy_tui::Theme, c: &'a Contact) -> ListItem<'a> { | |
| 910 | + | fn contact_item<'a>(p: Theme, c: &'a Contact) -> ListItem<'a> { | |
| 913 | 911 | let mut spans = vec![ | |
| 914 | 912 | Span::raw(" "), | |
| 915 | 913 | Span::styled( | |
| @@ -941,7 +939,7 @@ | |||
| 941 | 939 | } | |
| 942 | 940 | ||
| 943 | 941 | /// A bold section header line for the Timer pane. | |
| 944 | - | fn section(p: alloy_tui::Theme, title: &str) -> Line<'static> { | |
| 942 | + | fn section(p: Theme, title: &str) -> Line<'static> { | |
| 945 | 943 | Line::from(Span::styled( | |
| 946 | 944 | format!(" {title}"), | |
| 947 | 945 | Style::default() | |
| @@ -952,7 +950,7 @@ | |||
| 952 | 950 | ||
| 953 | 951 | /// Render the Timer pane as a compact dashboard: active timer, this week's | |
| 954 | 952 | /// tracked breakdown, and recent sessions. | |
| 955 | - | fn timer_lines(p: alloy_tui::Theme, tv: &TimerView) -> Vec<Line<'static>> { | |
| 953 | + | fn timer_lines(p: Theme, tv: &TimerView) -> Vec<Line<'static>> { | |
| 956 | 954 | let mut lines = Vec::new(); | |
| 957 | 955 | ||
| 958 | 956 | match &tv.active { |
| @@ -1,0 +1,75 @@ | |||
| 1 | + | //! The one-row footer: key hints along the bottom. | |
| 2 | + | //! | |
| 3 | + | //! Was `alloy_tui::AlloyStatusBar`. It moved here when `got` stopped depending | |
| 4 | + | //! on Alloy's design system: the colour mapping it needs is family-level and | |
| 5 | + | //! now lives in `makeover-tui`, but a footer's layout is this app's opinion | |
| 6 | + | //! about its own chrome, not something two unrelated programs should share. | |
| 7 | + | //! | |
| 8 | + | //! Only the hint half came across. The original also carried a right-aligned | |
| 9 | + | //! transient status slot; `got` never set one, and an unused builder method is | |
| 10 | + | //! a worse thing to keep than fifteen lines are to rewrite if it is ever | |
| 11 | + | //! wanted. | |
| 12 | + | ||
| 13 | + | use makeover_tui::Theme; | |
| 14 | + | use ratatui::buffer::Buffer; | |
| 15 | + | use ratatui::layout::Rect; | |
| 16 | + | use ratatui::style::Style; | |
| 17 | + | use ratatui::text::{Line, Span}; | |
| 18 | + | use ratatui::widgets::{Paragraph, Widget}; | |
| 19 | + | ||
| 20 | + | /// A footer key hint: the key, and what it does. | |
| 21 | + | pub(crate) struct Hint { | |
| 22 | + | pub(crate) key: &'static str, | |
| 23 | + | pub(crate) label: &'static str, | |
| 24 | + | } | |
| 25 | + | ||
| 26 | + | /// Terse constructor, so hint lists read as data at the call site: | |
| 27 | + | /// `[hint("Tab", "group"), hint("q", "quit")]`. | |
| 28 | + | pub(crate) const fn hint(key: &'static str, label: &'static str) -> Hint { | |
| 29 | + | Hint { key, label } | |
| 30 | + | } | |
| 31 | + | ||
| 32 | + | pub(crate) struct StatusBar<'a> { | |
| 33 | + | theme: &'a Theme, | |
| 34 | + | hints: Vec<Hint>, | |
| 35 | + | } | |
| 36 | + | ||
| 37 | + | impl<'a> StatusBar<'a> { | |
| 38 | + | pub(crate) fn new(theme: &'a Theme, hints: impl IntoIterator<Item = Hint>) -> Self { | |
| 39 | + | Self { | |
| 40 | + | theme, | |
| 41 | + | hints: hints.into_iter().collect(), | |
| 42 | + | } | |
| 43 | + | } | |
| 44 | + | } | |
| 45 | + | ||
| 46 | + | impl Widget for StatusBar<'_> { | |
| 47 | + | fn render(self, area: Rect, buf: &mut Buffer) { | |
| 48 | + | // The bar reads as its own band, so the whole row takes the sunken | |
| 49 | + | // surface first and every span below is drawn onto it. | |
| 50 | + | let base = Style::default().bg(self.theme.surface_sunken); | |
| 51 | + | Paragraph::new("").style(base).render(area, buf); | |
| 52 | + | ||
| 53 | + | let mut spans: Vec<Span> = Vec::with_capacity(self.hints.len() * 3); | |
| 54 | + | for (i, h) in self.hints.iter().enumerate() { | |
| 55 | + | if i > 0 { | |
| 56 | + | spans.push(Span::raw(" ")); | |
| 57 | + | } | |
| 58 | + | // The key takes the action colour and the label is muted: a key | |
| 59 | + | // hint IS the actionable element, which is the one place accent on | |
| 60 | + | // text is not decoration. | |
| 61 | + | spans.push(Span::styled( | |
| 62 | + | h.key, | |
| 63 | + | Style::default().fg(self.theme.action_primary), | |
| 64 | + | )); | |
| 65 | + | spans.push(Span::raw(" ")); | |
| 66 | + | spans.push(Span::styled( | |
| 67 | + | h.label, | |
| 68 | + | Style::default().fg(self.theme.content_muted), | |
| 69 | + | )); | |
| 70 | + | } | |
| 71 | + | Paragraph::new(Line::from(spans)) | |
| 72 | + | .style(base) | |
| 73 | + | .render(area, buf); | |
| 74 | + | } | |
| 75 | + | } |