| Crates.io | termocast |
| lib.rs | termocast |
| version | 0.2.2 |
| created_at | 2025-12-20 09:00:50.864058+00 |
| updated_at | 2025-12-30 15:52:07.277022+00 |
| description | A terminal-based weather application |
| homepage | https://github.com/santoshxshrestha/termocast |
| repository | https://github.com/santoshxshrestha/termocast |
| max_upload_size | |
| id | 1996238 |
| size | 108,467 |
A learning project for building Terminal User Interfaces (TUI) in Rust, demonstrating event handling, async operations, and real-time data fetching. This project fetches weather data from OpenWeatherMap API and displays it in a terminal-based interface with beautiful ASCII art weather visualizations.
This is a learning project focused on:
Put your API key in a .env file:
OPEN_WEATHER_API_KEY=your_key
From source:
cargo run
From binary:
export OPEN_WEATHER_API_KEY=your_key && termocast
or, what ever shell you use you can set env variable accordingly
## Project Architecture
### File Structure
- `src/main.rs` - Entry point and async weather fetching logic
- `src/ui.rs` - TUI implementation with Ratatui
- `src/types.rs` - Data structures for weather API responses
- `src/art.rs` - ASCII art system with day/night weather visualizations
### Key Learning Concepts
#### 1. TUI with Ratatui (src/ui.rs)
The UI is built using the Ratatui framework:
- `App` struct holds application state including weather data, ASCII art, and loading flags
- `run()` method implements the main event loop (ui.rs:29-38)
- `draw()` renders the UI on each frame (ui.rs:40-42)
- `Widget` trait implementation for custom rendering (ui.rs:117-180)
```rust
// Main event loop in ui.rs:33-36
while !self.exit {
terminal.draw(|frame| self.draw(frame))?;
self.handle_events()?;
}
Keyboard events are handled using Crossterm's event polling:
poll() checks for events without blocking (ui.rs:45)KeyEvent handles different keysNote: The quit keys are 'Esc' or 'Ctrl+C'. This means you can now freely type 'q' in city names like "Qatar" or "Iraq" without worrying about accidentally quitting the application.
// Non-blocking event polling
if poll(Duration::from_micros(1))? {
match event::read()? {
Event::Key(key_event) => {
// Handle keyboard input
}
_ => {}
}
}
The challenge: UI rendering is synchronous, but API fetching is async.
Solution: Spawn async tasks with shared state:
Arc<Mutex<Option<WeatherDetails>>> (ui.rs:21)Arc<AtomicBool> (ui.rs:24)tokio::spawn() runs fetch in background (ui.rs:95)fn handle_weather_fetch(&mut self) {
let city = self.city.clone();
let weather_details_arc = Arc::clone(&self.weather_details);
let isfetching_arc = Arc::clone(&self.isfetching);
// Spawn async task that doesn't block UI
tokio::spawn(async move {
let response = fetch_weather(&city).await;
if response.status().is_success() {
// Parse and update shared state
let details: WeatherDetails =
serde_json::from_str(&weather_text).expect("Failed to parse JSON");
*weather_details = Some(details);
}
isfetching_arc.store(false, Ordering::SeqCst);
});
}
#[tokio::main] macro creates async runtimeSerde-powered deserialization of API responses:
WeatherDetails - Main weather data with timestamp and timezoneSysInfo - Sunrise and sunset times for day/night detectionWeatherCondition - Weather description for ASCII art selectionMainReadings - Temperature (min/max), humidity, pressureWindInfo - Wind speed and directionCloudCover - Cloudiness percentageDynamic weather visualization system:
AsciiArt struct with HashMap of weather conditionsArtPair struct containing day and night art variantsget_art() method selects appropriate art based on weather description and time of dayThe app handles multiple states gracefully:
This project teaches: