tui_cards/lib.rs
1//! A [Ratatui] widget to render charming playing cards in the terminal. Part of the [tui-widgets]
2//! suite by [Joshka].
3//!
4//! 
5//!
6//! [![Crate badge]][Crate]
7//! [![Docs Badge]][Docs]
8//! [![Deps Badge]][Dependency Status]
9//! [![License Badge]][License]
10//! [![Coverage Badge]][Coverage]
11//! [![Discord Badge]][Ratatui Discord]
12//!
13//! [GitHub Repository] · [API Docs] · [Examples] · [Changelog] · [Contributing]
14//!
15//! # Usage
16//!
17//! Create a `Card` and render it directly in a frame.
18//!
19//! ```no_run
20//! use tui_cards::{Card, Rank, Suit};
21//!
22//! # fn draw(frame: &mut ratatui::Frame) {
23//! let card = Card::new(Rank::Ace, Suit::Spades);
24//! frame.render_widget(&card, frame.area());
25//! # }
26//! ```
27//!
28//! # Demo
29//!
30//! ```shell
31//! cargo run --example card
32//! ```
33//!
34//! # More widgets
35//!
36//! For the full suite of widgets, see [tui-widgets].
37//!
38//! [Crate]: https://crates.io/crates/tui-cards
39//! [Docs]: https://docs.rs/tui-cards/
40//! [Dependency Status]: https://deps.rs/repo/github/ratatui/tui-widgets
41//! [Coverage]: https://app.codecov.io/gh/ratatui/tui-widgets
42//! [Ratatui Discord]: https://discord.gg/pMCEU9hNEj
43//! [Crate badge]: https://img.shields.io/crates/v/tui-cards?logo=rust&style=flat
44//! [Docs Badge]: https://img.shields.io/docsrs/tui-cards?logo=rust&style=flat
45//! [Deps Badge]: https://deps.rs/repo/github/ratatui/tui-widgets/status.svg?style=flat
46//! [License Badge]: https://img.shields.io/crates/l/tui-cards?style=flat
47//! [License]: https://github.com/ratatui/tui-widgets/blob/main/LICENSE-MIT
48//! [Coverage Badge]:
49//! https://img.shields.io/codecov/c/github/ratatui/tui-widgets?logo=codecov&style=flat
50//! [Discord Badge]: https://img.shields.io/discord/1070692720437383208?logo=discord&style=flat
51//!
52//! [GitHub Repository]: https://github.com/ratatui/tui-widgets
53//! [API Docs]: https://docs.rs/tui-cards/
54//! [Examples]: https://github.com/ratatui/tui-widgets/tree/main/tui-cards/examples
55//! [Changelog]: https://github.com/ratatui/tui-widgets/blob/main/tui-cards/CHANGELOG.md
56//! [Contributing]: https://github.com/ratatui/tui-widgets/blob/main/CONTRIBUTING.md
57//! [Joshka]: https://github.com/joshka
58//! [Ratatui]: https://ratatui.rs
59//! [tui-widgets]: https://crates.io/crates/tui-widgets
60use std::iter::zip;
61
62use indoc::indoc;
63use ratatui_core::buffer::Buffer;
64use ratatui_core::layout::Rect;
65use ratatui_core::style::{Color, Stylize};
66use ratatui_core::widgets::Widget;
67use strum::{Display, EnumIter};
68
69/// A playing card.
70///
71/// # Example
72///
73/// ```rust
74/// use tui_cards::{Card, Rank, Suit};
75/// # fn draw(frame: &mut ratatui::Frame) {
76/// let card = Card::new(Rank::Ace, Suit::Spades);
77/// frame.render_widget(&card, frame.area());
78/// # }
79/// ```
80#[derive(Debug, Clone, Copy)]
81pub struct Card {
82 pub rank: Rank,
83 pub suit: Suit,
84}
85
86#[derive(Debug, Clone, Copy, PartialEq, Eq, Display, EnumIter)]
87pub enum Rank {
88 Ace,
89 Two,
90 Three,
91 Four,
92 Five,
93 Six,
94 Seven,
95 Eight,
96 Nine,
97 Ten,
98 Jack,
99 Queen,
100 King,
101}
102
103#[derive(Debug, Clone, Copy, PartialEq, Eq, Display, EnumIter)]
104pub enum Suit {
105 Spades,
106 Hearts,
107 Diamonds,
108 Clubs,
109}
110
111impl Card {
112 pub const fn new(rank: Rank, suit: Suit) -> Self {
113 Self { rank, suit }
114 }
115
116 pub fn as_colored_symbol(&self) -> String {
117 format!(
118 "{}{}",
119 self.rank.as_symbol(),
120 self.suit.as_four_color_symbol()
121 )
122 }
123}
124
125impl Rank {
126 pub const fn as_symbol(self) -> char {
127 match self {
128 Self::Ace => 'A',
129 Self::Two => '2',
130 Self::Three => '3',
131 Self::Four => '4',
132 Self::Five => '5',
133 Self::Six => '6',
134 Self::Seven => '7',
135 Self::Eight => '8',
136 Self::Nine => '9',
137 Self::Ten => 'T',
138 Self::Jack => 'J',
139 Self::Queen => 'Q',
140 Self::King => 'K',
141 }
142 }
143}
144
145impl Suit {
146 pub const fn color(self) -> Color {
147 match self {
148 Self::Clubs => Color::Green,
149 Self::Diamonds => Color::Blue,
150 Self::Hearts => Color::Red,
151 Self::Spades => Color::Black,
152 }
153 }
154
155 pub const fn as_symbol(self) -> char {
156 match self {
157 Self::Clubs => '♣',
158 Self::Diamonds => '♦',
159 Self::Hearts => '♥',
160 Self::Spades => '♠',
161 }
162 }
163
164 pub const fn as_colored_symbol(self) -> &'static str {
165 match self {
166 Self::Clubs => "\u{2663}\u{FE0F}",
167 Self::Diamonds => "\u{2666}\u{FE0F}",
168 Self::Hearts => "\u{2665}\u{FE0F}",
169 Self::Spades => "\u{2660}\u{FE0F}",
170 }
171 }
172
173 pub const fn as_four_color_symbol(self) -> &'static str {
174 match self {
175 Self::Clubs => "\u{2618}\u{FE0F}", // shamrock
176 Self::Diamonds => "\u{1F537}\u{FE0F}", // blue diamond
177 Self::Hearts => "\u{2665}\u{FE0F}",
178 Self::Spades => "\u{2660}\u{FE0F}",
179 }
180 }
181}
182
183impl Rank {
184 pub const fn template(self) -> &'static str {
185 match self {
186 Self::Ace => indoc! {"
187 ╭────────────╮
188 │ A │
189 │ │
190 │ │
191 │ xx │
192 │ │
193 │ │
194 │ A │
195 ╰────────────╯"},
196 Self::Two => indoc! {"
197 ╭────────────╮
198 │ 2 xx │
199 │ │
200 │ │
201 │ │
202 │ │
203 │ │
204 │ xx 2 │
205 ╰────────────╯"},
206 Self::Three => indoc! {"
207 ╭────────────╮
208 │ 3 xx │
209 │ │
210 │ │
211 │ xx │
212 │ │
213 │ │
214 │ xx 3 │
215 ╰────────────╯"},
216 Self::Four => indoc! {"
217 ╭────────────╮
218 │ 4xx xx │
219 │ │
220 │ │
221 │ │
222 │ │
223 │ │
224 │ xx xx4 │
225 ╰────────────╯"},
226 Self::Five => indoc! {"
227 ╭────────────╮
228 │ 5xx xx │
229 │ │
230 │ │
231 │ xx │
232 │ │
233 │ │
234 │ xx xx5 │
235 ╰────────────╯"},
236 Self::Six => indoc! {"
237 ╭────────────╮
238 │ 6xx xx │
239 │ │
240 │ │
241 │ xx xx │
242 │ │
243 │ │
244 │ xx xx6 │
245 ╰────────────╯"},
246 Self::Seven => indoc! {"
247 ╭────────────╮
248 │ 7xx xx │
249 │ │
250 │ xx │
251 │ xx xx │
252 │ │
253 │ │
254 │ xx xx7 │
255 ╰────────────╯"},
256 Self::Eight => indoc! {"
257 ╭────────────╮
258 │ 8xx xx │
259 │ │
260 │ xx │
261 │ xx xx │
262 │ xx │
263 │ │
264 │ xx xx8 │
265 ╰────────────╯"},
266 Self::Nine => indoc! {"
267 ╭────────────╮
268 │ 9xx xx │
269 │ │
270 │ xx xx │
271 │ xx │
272 │ xx xx │
273 │ │
274 │ xx xx9 │
275 ╰────────────╯
276 "},
277 Self::Ten => indoc! {"
278 ╭────────────╮
279 │10xx xx │
280 │ xx │
281 │ xx xx │
282 │ │
283 │ xx xx │
284 │ xx │
285 │ xx xx10│
286 ╰────────────╯"},
287 Self::Jack => indoc! {"
288 ╭────────────╮
289 │ Jxx │
290 │ JJ │
291 │ JJ │
292 │ JJ │
293 │ JJ JJ │
294 │ JJJJJ │
295 │ xxJ │
296 ╰────────────╯"},
297 Self::Queen => indoc! {"
298 ╭────────────╮
299 │ Qxx │
300 │ QQQQQ │
301 │ QQ QQ │
302 │ QQ QQ │
303 │ QQ QQ │
304 │ QQQQ Q │
305 │ xxQ │
306 ╰────────────╯
307 "},
308 Self::King => indoc! {"
309 ╭────────────╮
310 │ Kxx │
311 │ KK KK │
312 │ KK KK │
313 │ KK KK │
314 │ KK KK │
315 │ KK KK │
316 │ xxK │
317 ╰────────────╯"},
318 }
319 }
320}
321
322impl Widget for &Card {
323 fn render(self, area: Rect, buf: &mut Buffer)
324 where
325 Self: Sized,
326 {
327 let template = self.rank.template();
328 let symbol = self.suit.as_four_color_symbol();
329 let card = template.replace("xx", symbol);
330 let color = self.suit.color();
331 for (line, row) in zip(card.lines(), area.rows()) {
332 let span = line.fg(color).bg(Color::White);
333 span.render(row, buf);
334 }
335 }
336}