mirror of
https://github.com/BastaMasta/boxy-cli.git
synced 2026-07-28 18:35:39 +03:00
fixed internal padding issue, which caused extra padding on the right
side of text. also updated docs and readme. need to see if readmem retains proper immages
This commit is contained in:
+66
-51
@@ -1,61 +1,76 @@
|
||||
mod tests;
|
||||
// mod tests;
|
||||
|
||||
// use boxy_cli::prelude::*;
|
||||
// use std::time::Instant;
|
||||
// use terminal_size::terminal_size;
|
||||
|
||||
// fn main() {
|
||||
// let mut box1 = Boxy::new(BoxType::Bold, "#00ffff");
|
||||
// println!("Terminal Size: {:?}", terminal_size());
|
||||
// box1.add_text_sgmt("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur", "#00ffff", BoxAlign::Left);
|
||||
// box1.add_text_sgmt("Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo.", "#ffff", BoxAlign::Center);
|
||||
// box1.add_text_sgmt("Hello Theree", "#00ffff", BoxAlign::Center);
|
||||
// box1.set_padding(BoxPad::from_tldr(1, 2, 3, 7), BoxPad::uniform(3));
|
||||
// // println!("{:?}", box1);
|
||||
// println!("\nTEXTBOX:");
|
||||
// let start = Instant::now();
|
||||
// box1.display();
|
||||
// let duration = start.elapsed();
|
||||
// println!("Time elapsed: {:?}", duration);
|
||||
// let mut box2 = boxy!(type: BoxType::Double, color:"#00ffff");
|
||||
// box2.add_text_sgmt("Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo ", "#ffff", BoxAlign::Left);
|
||||
// box2.add_text_line("Hello There Boi", "#32CD32");
|
||||
// box2.set_padding(BoxPad::from_tldr(1, 2, 3, 4), BoxPad::uniform(7));
|
||||
// box2.add_col_text_sgmt(BoxAlign::Center, 4);
|
||||
// box2.add_col_text_line_indx("datastring1", "#32CD32", &1usize, &0usize);
|
||||
// box2.add_col_text_line("col1 row1", "#ff5555", &0usize);
|
||||
// box2.add_col_text_line("col2 row1", "#55ff55", &1usize);
|
||||
// box2.add_col_text_line("col3 row1", "#5555ff", &2usize);
|
||||
// box2.add_col_text_line("col4 row1", "#ffff55", &3usize);
|
||||
// box2.set_segment_ratios(1, vec![1, 2, 3, 4]);
|
||||
// let start1 = Instant::now();
|
||||
// box2.display();
|
||||
// let duration1 = start1.elapsed();
|
||||
// println!("Time elapsed: {:?}", duration1);
|
||||
// let start2 = Instant::now();
|
||||
// Boxy::builder()
|
||||
// .box_type(BoxType::Double)
|
||||
// .color("#f4f5f3")
|
||||
// .padding(BoxPad::uniform(20), BoxPad::from_tldr(2, 2, 1, 1))
|
||||
// .align(BoxAlign::Center)
|
||||
// .add_segment("Hello, Boxy!", "#ffffff", BoxAlign::Center)
|
||||
// .add_line("This is a new line.", "#32CD32")
|
||||
// .add_segment("Another section", "#f19356", BoxAlign::Right)
|
||||
// .width(50)
|
||||
// .build()
|
||||
// .display();
|
||||
// let duration2 = start2.elapsed();
|
||||
// println!("Time elapsed: {:?}", duration2);
|
||||
// Boxy::builder()
|
||||
// .add_segment("Status Report", "#ffffff", BoxAlign::Center)
|
||||
// .add_col_segment(BoxAlign::Left, 3)
|
||||
// .add_col_line("Name", "#ffffff", 0)
|
||||
// .add_col_line("Status", "#ffffff", 1)
|
||||
// .add_col_line("Notes", "#ffffff", 2)
|
||||
// .add_col_line("Lumio V2", "#ffffff", 0)
|
||||
// .add_col_line("Shipped", "#ffffff", 1)
|
||||
// .add_col_line("Internship project", "#ffffff", 2)
|
||||
// .segment_ratios(1, vec![1, 1, 2])
|
||||
// .build()
|
||||
// .display();
|
||||
// }
|
||||
|
||||
use boxy_cli::prelude::*;
|
||||
use std::time::Instant;
|
||||
use terminal_size::terminal_size;
|
||||
|
||||
fn main() {
|
||||
let mut box1 = Boxy::new(BoxType::Bold, "#00ffff");
|
||||
println!("Terminal Size: {:?}", terminal_size());
|
||||
box1.add_text_sgmt("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur", "#00ffff", BoxAlign::Left);
|
||||
box1.add_text_sgmt("Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo.", "#ffff", BoxAlign::Center);
|
||||
box1.add_text_sgmt("Hello Theree", "#00ffff", BoxAlign::Center);
|
||||
box1.set_padding(BoxPad::from_tldr(1, 2, 3, 4), BoxPad::uniform(0));
|
||||
// println!("{:?}", box1);
|
||||
println!("\nTEXTBOX:");
|
||||
let start = Instant::now();
|
||||
box1.display();
|
||||
let duration = start.elapsed();
|
||||
println!("Time elapsed: {:?}", duration);
|
||||
let mut box2 = boxy!(type: BoxType::Double, color:"#00ffff");
|
||||
box2.add_text_sgmt("Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo ", "#ffff", BoxAlign::Left);
|
||||
box2.add_text_line("Hello There Boi", "#32CD32");
|
||||
box2.set_padding(BoxPad::from_tldr(1, 2, 3, 4), BoxPad::uniform(7));
|
||||
box2.add_col_text_sgmt(BoxAlign::Center, 4);
|
||||
box2.add_col_text_line_indx("datastring1", "#32CD32", &1usize, &0usize);
|
||||
box2.add_col_text_line("col1 row1", "#ff5555", &0usize);
|
||||
box2.add_col_text_line("col2 row1", "#55ff55", &1usize);
|
||||
box2.add_col_text_line("col3 row1", "#5555ff", &2usize);
|
||||
box2.add_col_text_line("col4 row1", "#ffff55", &3usize);
|
||||
box2.set_segment_ratios(1, vec![1, 2, 3, 4]);
|
||||
let start1 = Instant::now();
|
||||
box2.display();
|
||||
let duration1 = start1.elapsed();
|
||||
println!("Time elapsed: {:?}", duration1);
|
||||
let start2 = Instant::now();
|
||||
Boxy::builder()
|
||||
.box_type(BoxType::Double)
|
||||
.color("#f4f5f3")
|
||||
.padding(BoxPad::uniform(20), BoxPad::from_tldr(2, 2, 1, 1))
|
||||
.color("#00ffff")
|
||||
.align(BoxAlign::Center)
|
||||
.add_segment("Hello, Boxy!", "#ffffff", BoxAlign::Center)
|
||||
.add_line("This is a new line.", "#32CD32")
|
||||
.padding(BoxPad::uniform(1), BoxPad::vh(1, 2))
|
||||
.add_segment("Hello, boxy-cli!", "#ffffff", BoxAlign::Center)
|
||||
.add_line("A terminal box library for Rust.", "#aaaaaa")
|
||||
.add_segment("Another section", "#f19356", BoxAlign::Right)
|
||||
.width(50)
|
||||
.build()
|
||||
.display();
|
||||
let duration2 = start2.elapsed();
|
||||
println!("Time elapsed: {:?}", duration2);
|
||||
Boxy::builder()
|
||||
.add_segment("Status Report", "#ffffff", BoxAlign::Center)
|
||||
.add_col_segment(BoxAlign::Left, 3)
|
||||
.add_col_line("Name", "#ffffff", 0)
|
||||
.add_col_line("Status", "#ffffff", 1)
|
||||
.add_col_line("Notes", "#ffffff", 2)
|
||||
.add_col_line("Lumio V2", "#ffffff", 0)
|
||||
.add_col_line("Shipped", "#ffffff", 1)
|
||||
.add_col_line("Internship project", "#ffffff", 2)
|
||||
.segment_ratios(1, vec![1, 1, 2])
|
||||
.build()
|
||||
.display();
|
||||
}
|
||||
}
|
||||
+189
-123
@@ -1,184 +1,250 @@
|
||||
# boxy-cli
|
||||
[](https://github.com/BastaMasta/boxy-cli)
|
||||
[](https://crates.io/crates/boxy-cli)
|
||||
[](https://crates.io/crates/boxy-cli)
|
||||
[](https://docs.rs/boxy-cli/latest/)
|
||||
[](https://crates.io/crates/boxy-cli)
|
||||
[](https://github.com/BastaMasta/boxy-cli/blob/main/LICENSE-APACHE)
|
||||
[](https://github.com/BastaMasta/boxy-cli/blob/main/LICENSE-MIT)
|
||||
[](https://github.com/BastaMasta/boxy-cli/actions/workflows/rust.yml?query=branch%3Amain)
|
||||
|
||||
|
||||
**A Crate to create boxes in command-line interfaces with Rust**
|
||||
**A Rust crate for creating styled, multi-segment text boxes in the terminal.**
|
||||
|
||||
Dual-licensed under [Apache 2.0](https://github.com/BastaMasta/boxy-cli/blob/main/LICENSE-APACHE) or [MIT](https://github.com/BastaMasta/boxy-cli/blob/main/LICENSE-MIT).
|
||||
|
||||
## About
|
||||
|
||||
**boxy-cli** is a Rust crate that makes it easy to create stylish text boxes in terminal applications. With a simple, intuitive API, you can quickly add visually appealing elements to your CLI applications.
|
||||
---
|
||||
|
||||
## Features
|
||||
|
||||
- **Multiple Border Styles**: Choose from single, double, bold, rounded, and other border styles
|
||||
- **Color Support**: Customize border and text colors using hex color codes
|
||||
- **Flexible Layouts**: Create multi-segment boxes with horizontal dividers
|
||||
- **Text Alignment**: Align text left, center, or right within each segment
|
||||
- **Custom Padding**: Control spacing both inside and outside the box
|
||||
- **Terminal-Aware**: Automatically adjusts to terminal width or use fixed dimensions
|
||||
- **Builder Pattern**: Fluent API for easy box creation
|
||||
- **9 border styles** — classic ASCII, single, double, bold, rounded, bold-corners, and more
|
||||
- **True-color support** — hex color codes (`#rrggbb`) for borders and per-line text
|
||||
- **Multi-segment boxes** — stack sections separated by horizontal dividers
|
||||
- **Columnar layouts** — side-by-side columns inside a single box, with configurable width ratios and correct junction characters (`┼` / `┬` / `┴`) where column boundaries meet across adjacent segments
|
||||
- **Automatic word wrapping** — wraps to terminal width, respecting internal padding
|
||||
- **Text alignment** — left, center, or right per segment
|
||||
- **Terminal-aware sizing** — auto-sizes to terminal width, or set a fixed width
|
||||
- **Two APIs** — imperative `Boxy` struct and fluent `BoxyBuilder`
|
||||
- **Macro support** — `boxy!` for quick one-liners
|
||||
|
||||
---
|
||||
|
||||
## Installation
|
||||
|
||||
Add this to your `Cargo.toml`:
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
boxy-cli = "0.1.0"
|
||||
boxy-cli = "2.1.0"
|
||||
```
|
||||
|
||||
Or use cargo add:
|
||||
Or:
|
||||
|
||||
```bash
|
||||
cargo add boxy-cli
|
||||
```
|
||||
|
||||
### How to use:
|
||||
---
|
||||
|
||||
### Using the Builder Pattern
|
||||
## Quick Start
|
||||
|
||||
The builder pattern provides a fluent, chainable API for creating and configuring text boxes:
|
||||
### Builder API
|
||||
|
||||
```rust
|
||||
use boxy_cli::prelude::*;
|
||||
```
|
||||
|
||||
Next, you can create the BoxyBuilder struct
|
||||
|
||||
```rust
|
||||
let mut my_box = Boxy::builder()
|
||||
.box_type(BoxType::Double) // Set border style
|
||||
.color("#00ffff") // Set border color
|
||||
.padding(
|
||||
BoxPad::uniform(1), // External padding
|
||||
BoxPad::from_tldr(2, 2, 1, 1) // Internal padding
|
||||
)
|
||||
.align(BoxAlign::Center) // Center the box in the terminal
|
||||
.add_segment("Hello, Boxy!", "#ffffff", BoxAlign::Center)
|
||||
.add_line("This is a new line.", "#32CD32")
|
||||
.add_segment("Another section", "#663399", BoxAlign::Left)
|
||||
.width(50) // Set fixed width
|
||||
.build();
|
||||
```
|
||||
|
||||
and now, display it:
|
||||
|
||||
```rust
|
||||
my_box.display();
|
||||
```
|
||||
|
||||
You can also build and display in one go:
|
||||
|
||||
```rust
|
||||
Boxy::builder()
|
||||
.box_type(BoxType::Double)
|
||||
.color("#aaffff")
|
||||
.padding(BoxPad::uniform(1), BoxPad::from_tldr(2, 2, 1, 1))
|
||||
.align(BoxAlign::Center)
|
||||
.add_segment("Hello, Boxy!", "#ffffff", BoxAlign::Center)
|
||||
.add_line("This is a new line.", "#32CD32")
|
||||
.add_segment("Another section", "#f19356", BoxAlign::Right)
|
||||
.width(50)
|
||||
.color("#00ffff")
|
||||
.add_segment("Hello, boxy-cli!", "#ffffff", BoxAlign::Center)
|
||||
.add_segment("A terminal box library for Rust.", "#32CD32", BoxAlign::Left)
|
||||
.add_line("Second line in the same segment.", "#aaaaaa")
|
||||
.padding(BoxPad::uniform(1), BoxPad::vh(1, 2))
|
||||
.build()
|
||||
.display();
|
||||
```
|
||||
|
||||
further, you can use the same methods as displayed above to modify the textbox before building.
|
||||

|
||||
|
||||
But you can also modify the textbox after building it (before displaying) using the methods shown in the following section.
|
||||
|
||||
#### Using the Struct and methods.
|
||||
|
||||
First, import the crate into the current scope, using:
|
||||
### Imperative API
|
||||
|
||||
```rust
|
||||
use boxy_cli::prelude::*;
|
||||
|
||||
let mut b = Boxy::new(BoxType::Bold, "#00ffff");
|
||||
b.add_text_sgmt("Hello, boxy-cli!", "#ffffff", BoxAlign::Center);
|
||||
b.add_text_sgmt("A terminal box library for Rust.", "#32CD32", BoxAlign::Left);
|
||||
b.add_text_line("Second line in the same segment.", "#aaaaaa");
|
||||
b.display();
|
||||
```
|
||||
|
||||
Next you create a new boxy struct with either the ```new``` method:
|
||||

|
||||
|
||||
### Macro
|
||||
|
||||
```rust
|
||||
let mut box1 = Boxy::new(BoxType::Double,"#00ffff");
|
||||
```
|
||||
or the macro:
|
||||
use boxy_cli::prelude::*;
|
||||
|
||||
```rust
|
||||
let mut box2 = boxy!(type: BoxType::Double, color:"#00ffff");
|
||||
```
|
||||
*for more info on the macro, view the [macro documentation](https://docs.rs/boxy-cli/0.1.0/boxy_cli/macro.boxy.html)*
|
||||
|
||||
Next, we just add in text sections:
|
||||
```rust
|
||||
box1.add_text_sgmt("Lorem ipsum dolor sit amet", "#fffff", BoxAlign::Center);
|
||||
```
|
||||
Add some more text to the same segment (or the latest segment):
|
||||
```rust
|
||||
box1.add_text_line("consectetur adipiscing elit", "#32CD32");
|
||||
```
|
||||
or to a segment with a particular index:
|
||||
```rust
|
||||
box1.add_text_line_indx(" consectetur adipiscing elit", "#32CD32", 0);
|
||||
```
|
||||
Once you are done, display the TextBox:
|
||||
```rust
|
||||
box1.display();
|
||||
let mut b = boxy!(type: BoxType::Double, color: "#00ffff", internal_pad: 1, alignment: BoxAlign::Left);
|
||||
b.add_text_sgmt("Hello from the macro!", "#ffffff", BoxAlign::Center);
|
||||
b.display();
|
||||
```
|
||||
|
||||
## Examples:
|
||||
---
|
||||
|
||||
### Example 1
|
||||
## Columnar Layouts
|
||||
|
||||
Columnar segments let you place content side-by-side inside one box. Column widths are
|
||||
controlled by ratio values — `vec![1, 2, 1]` gives the middle column twice the space of the
|
||||
others.
|
||||
|
||||
Where column boundaries from adjacent segments coincide, `boxy-cli` renders the correct
|
||||
junction character automatically:
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────┐
|
||||
│ Project Status │
|
||||
├─────────────────┼─────────────────┴─────────────┤ <- ┼ where both have a boundary
|
||||
│ Lumio V2 │ Shipped Internship │ ┴ where only the top segment does
|
||||
└─────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
```rust
|
||||
use boxy_cli::prelude::*;
|
||||
|
||||
let mut b = Boxy::new(BoxType::Single, "#00ffff");
|
||||
|
||||
// Plain text header segment
|
||||
b.add_text_sgmt("Project Status", "#ffffff", BoxAlign::Center);
|
||||
|
||||
// 3-column segment with equal widths
|
||||
b.add_col_text_sgmt(BoxAlign::Left, 3);
|
||||
b.add_col_text_line("Name", "#aaaaaa", &0usize);
|
||||
b.add_col_text_line("Status", "#aaaaaa", &1usize);
|
||||
b.add_col_text_line("Notes", "#aaaaaa", &2usize);
|
||||
b.add_col_text_line("Lumio V2", "#ffffff", &0usize);
|
||||
b.add_col_text_line("Shipped", "#32CD32", &1usize);
|
||||
b.add_col_text_line("Internship project", "#ffffff", &2usize);
|
||||
|
||||
// Optional: customize column width ratios (must match column count)
|
||||
b.set_segment_ratios(1, vec![1, 1, 2]);
|
||||
|
||||
b.display();
|
||||
```
|
||||
|
||||
Or using the builder:
|
||||
|
||||
```rust
|
||||
use boxy_cli::prelude::*;
|
||||
|
||||
Boxy::builder()
|
||||
.add_segment("Project Status", "#ffffff", BoxAlign::Center)
|
||||
.add_col_segment(BoxAlign::Left, 3)
|
||||
.add_col_line("Name", "#aaaaaa", 0)
|
||||
.add_col_line("Status", "#aaaaaa", 1)
|
||||
.add_col_line("Notes", "#aaaaaa", 2)
|
||||
.add_col_line("Lumio V2", "#ffffff", 0)
|
||||
.add_col_line("Shipped", "#32CD32", 1)
|
||||
.add_col_line("Internship project", "#ffffff", 2)
|
||||
.segment_ratios(1, vec![1, 1, 2])
|
||||
.build()
|
||||
.display();
|
||||
```
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
## Border Styles
|
||||
|
||||
| `BoxType` variant | Appearance |
|
||||
|-----------------------|------------|
|
||||
| `Single` | `┌─┐` / `│` / `└─┘` |
|
||||
| `Double` | `╔═╗` / `║` / `╚═╝` |
|
||||
| `Bold` | `┏━┓` / `┃` / `┗━┛` |
|
||||
| `Rounded` | `╭─╮` / `│` / `╰─╯` |
|
||||
| `DoubleHorizontal` | `╒═╕` / `│` / `╘═╛` |
|
||||
| `DoubleVertical` | `╓─╖` / `║` / `╙─╜` |
|
||||
| `BoldCorners` | `┍━┑` / `│` / `┕━┙` |
|
||||
| `Classic` | `+-+` / `\|` / `+-+` |
|
||||
| `Empty` | invisible borders |
|
||||
|
||||
---
|
||||
|
||||
## Padding
|
||||
|
||||
`BoxPad` controls spacing between the terminal edge and the box (external), and between the
|
||||
box border and its text (internal).
|
||||
|
||||
```rust
|
||||
use boxy_cli::prelude::*;
|
||||
|
||||
// Uniform padding on all sides
|
||||
let pad = BoxPad::uniform(2);
|
||||
|
||||
// Separate vertical and horizontal values
|
||||
let pad = BoxPad::vh(1, 3); // top/bottom: 1, left/right: 3
|
||||
|
||||
// Full control: top, left, down, right
|
||||
let pad = BoxPad::from_tldr(1, 2, 1, 2);
|
||||
|
||||
let mut b = Boxy::new(BoxType::Single, "#00ffff");
|
||||
b.set_ext_padding(BoxPad::uniform(1));
|
||||
b.set_int_padding(BoxPad::vh(1, 2));
|
||||
b.add_text_sgmt("Padded box", "#ffffff", BoxAlign::Center);
|
||||
b.display();
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Examples
|
||||
|
||||
### Multi-segment box
|
||||
|
||||
```rust
|
||||
use boxy_cli::prelude::*;
|
||||
|
||||
fn main() {
|
||||
let mut box1 = Boxy::new(BoxType::Double,"#bfff00");
|
||||
box1.add_text_sgmt("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur", "#00ffff", BoxAlign::Left);
|
||||
box1.add_text_sgmt("Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo.", "#ffff", BoxAlign::Center);
|
||||
box1.add_text_sgmt("Hello Theree", "#00ffff", BoxAlign::Center);
|
||||
box1.display();
|
||||
let mut b = Boxy::new(BoxType::Bold, "#00ffff");
|
||||
b.add_text_sgmt(
|
||||
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor \
|
||||
incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud \
|
||||
exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.",
|
||||
"#ffffff",
|
||||
BoxAlign::Left,
|
||||
);
|
||||
b.add_text_sgmt(
|
||||
"Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium \
|
||||
doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore \
|
||||
veritatis et quasi architecto beatae vitae dicta sunt explicabo.",
|
||||
"#ffffff",
|
||||
BoxAlign::Left,
|
||||
);
|
||||
b.add_text_sgmt("Hello There", "#ffffff", BoxAlign::Center);
|
||||
b.display();
|
||||
}
|
||||
```
|
||||
|
||||
### Example 2 (with the macro)
|
||||
### Output:
|
||||

|
||||
|
||||
```rust
|
||||
use boxy_cli::prelude::*;
|
||||
|
||||
fn main() {
|
||||
let mut box2 = boxy!(type: BoxType::Double, color:"#00ffff");
|
||||
box2.add_text_sgmt("Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo ", "#ffff", BoxAlign::Left);
|
||||
box2.add_text_line("Hello There Boi", "#32CD32");
|
||||
box2.display();
|
||||
}
|
||||
```
|
||||
|
||||
### Example 3 (with the BoxyBuilder implementation)
|
||||
|
||||
```rust
|
||||
use boxy_cli::prelude::*;
|
||||
|
||||
fn main(){
|
||||
Boxy::builder()
|
||||
.box_type(BoxType::Double)
|
||||
.color("#aaffff")
|
||||
.padding(BoxPad::uniform(1), BoxPad::from_tldr(2, 2, 1, 1))
|
||||
.align(BoxAlign::Center)
|
||||
.add_segment("Hello, Boxy!", "#ffffff", BoxAlign::Center)
|
||||
.add_line("This is a new line.", "#32CD32")
|
||||
.add_segment("Another section", "#f19356", BoxAlign::Right)
|
||||
.width(50)
|
||||
.build()
|
||||
.display();
|
||||
}
|
||||
```
|
||||
---
|
||||
|
||||
## API Overview
|
||||
|
||||
| Method | Description |
|
||||
|--------|-------------|
|
||||
| `Boxy::new(type, color)` | Create a new box |
|
||||
| `Boxy::builder()` | Start a builder chain |
|
||||
| `add_text_sgmt(text, color, align)` | Add a plain text segment |
|
||||
| `add_text_line(text, color)` | Add a line to the last segment |
|
||||
| `add_text_line_indx(text, color, idx)` | Add a line to a specific segment |
|
||||
| `add_col_text_sgmt(align, count)` | Add a columnar segment |
|
||||
| `add_col_text_line(text, color, col)` | Add a line to a column in the last segment |
|
||||
| `add_col_text_line_indx(text, color, seg, col)` | Add a line to a specific column in a specific segment |
|
||||
| `set_segment_ratios(seg, ratios)` | Set column width ratios for a columnar segment |
|
||||
| `set_align(align)` | Set box alignment within the terminal |
|
||||
| `set_int_padding(pad)` | Set internal padding |
|
||||
| `set_ext_padding(pad)` | Set external padding |
|
||||
| `set_width(n)` | Fix the box width |
|
||||
| `set_type(type)` | Change border style |
|
||||
| `set_color(color)` | Change border color |
|
||||
| `display()` | Render and print the box |
|
||||
|
||||
For the full API reference see [docs.rs/boxy-cli](https://docs.rs/boxy-cli/latest/).
|
||||
|
||||
+98
-29
@@ -617,7 +617,7 @@ impl<'a> Boxy<'a> {
|
||||
top_seg,
|
||||
"{:>width$}",
|
||||
box_pieces.top_left,
|
||||
width = self.ext_padding.left + 1 + align_offset
|
||||
width = self.ext_padding.left + align_offset
|
||||
)
|
||||
.unwrap();
|
||||
top_seg.push_str(&box_pieces.horizontal.to_string().repeat(disp_width));
|
||||
@@ -628,7 +628,7 @@ impl<'a> Boxy<'a> {
|
||||
top_seg,
|
||||
"{:>width$}",
|
||||
box_pieces.top_left,
|
||||
width = self.ext_padding.left + 1 + align_offset
|
||||
width = self.ext_padding.left + align_offset
|
||||
)
|
||||
.unwrap();
|
||||
let below = self.col_boundaries(&col_widths_segwise[0]);
|
||||
@@ -680,7 +680,7 @@ impl<'a> Boxy<'a> {
|
||||
bot_seg,
|
||||
"{:>width$}",
|
||||
box_pieces.bottom_left,
|
||||
width = self.ext_padding.left + 1 + align_offset
|
||||
width = self.ext_padding.left + align_offset
|
||||
)
|
||||
.unwrap();
|
||||
bot_seg.push_str(&box_pieces.horizontal.to_string().repeat(disp_width));
|
||||
@@ -691,7 +691,7 @@ impl<'a> Boxy<'a> {
|
||||
bot_seg,
|
||||
"{:>width$}",
|
||||
box_pieces.bottom_left,
|
||||
width = self.ext_padding.left + 1 + align_offset
|
||||
width = self.ext_padding.left + align_offset
|
||||
)
|
||||
.unwrap();
|
||||
let above = self.col_boundaries(
|
||||
@@ -770,7 +770,7 @@ impl<'a> Boxy<'a> {
|
||||
"{1:>width$}{}{1}",
|
||||
" ".repeat(disp_width),
|
||||
box_pieces.vertical.to_string().color(*box_col_truecolor),
|
||||
width = self.ext_padding.left + 1 + align_offset
|
||||
width = self.ext_padding.left + align_offset
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -794,7 +794,7 @@ impl<'a> Boxy<'a> {
|
||||
div,
|
||||
"{:>width$}",
|
||||
box_pieces.left_t.to_string(),
|
||||
width = self.ext_padding.left + 1 + align_offset
|
||||
width = self.ext_padding.left + align_offset
|
||||
)
|
||||
.unwrap();
|
||||
let empty = Vec::new();
|
||||
@@ -898,7 +898,7 @@ impl<'a> Boxy<'a> {
|
||||
currline,
|
||||
"{:>width$}",
|
||||
vertical,
|
||||
width = self.ext_padding.left + 1 + align_offset
|
||||
width = self.ext_padding.left + align_offset
|
||||
)
|
||||
.unwrap();
|
||||
for (i, col) in columnar_data.iter().enumerate() {
|
||||
@@ -986,19 +986,13 @@ fn iter_line_prnt(
|
||||
BoxAlign::Left => {
|
||||
for i in liner.iter() {
|
||||
let mut currline = String::new();
|
||||
write!(
|
||||
currline,
|
||||
"{:>width$}",
|
||||
vertical,
|
||||
width = ext_padding.left + 1
|
||||
)
|
||||
.unwrap();
|
||||
write!(currline, "{:>width$}", vertical, width = ext_padding.left).unwrap();
|
||||
write!(currline, "{:<pad$}", " ", pad = int_padding.left).unwrap();
|
||||
write!(
|
||||
currline,
|
||||
"{:<width$}",
|
||||
i.color(*text_col),
|
||||
width = printable_area - (2 * (!*fixed_size as usize)) // subtract 2 for the bars if on dynamic sizing
|
||||
width = printable_area - (2 * (!(*fixed_size) as usize)) // subtract 2 for the bars if on dynamic sizing
|
||||
)
|
||||
.unwrap();
|
||||
write!(currline, "{:<pad$}", " ", pad = int_padding.right).unwrap();
|
||||
@@ -1009,13 +1003,7 @@ fn iter_line_prnt(
|
||||
BoxAlign::Center => {
|
||||
for i in liner.iter() {
|
||||
let mut currline = String::new();
|
||||
write!(
|
||||
currline,
|
||||
"{:>width$}",
|
||||
vertical,
|
||||
width = ext_padding.left + 1
|
||||
)
|
||||
.unwrap();
|
||||
write!(currline, "{:>width$}", vertical, width = ext_padding.left).unwrap();
|
||||
write!(
|
||||
currline,
|
||||
"{:<pad$}",
|
||||
@@ -1030,6 +1018,8 @@ fn iter_line_prnt(
|
||||
" ",
|
||||
pad = int_padding.right + (printable_area - i.len())
|
||||
- ((printable_area - i.len()) / 2)
|
||||
- (2 * (int_padding.right != 0) as usize)
|
||||
+ (2 * (*fixed_size as usize))
|
||||
)
|
||||
.unwrap();
|
||||
write!(currline, "{}", vertical).unwrap();
|
||||
@@ -1039,13 +1029,7 @@ fn iter_line_prnt(
|
||||
BoxAlign::Right => {
|
||||
for i in liner.iter() {
|
||||
let mut currline = String::new();
|
||||
write!(
|
||||
currline,
|
||||
"{:>width$}",
|
||||
vertical,
|
||||
width = ext_padding.left + 1
|
||||
)
|
||||
.unwrap();
|
||||
write!(currline, "{:>width$}", vertical, width = ext_padding.left).unwrap();
|
||||
write!(currline, "{:<pad$}", " ", pad = int_padding.left).unwrap();
|
||||
write!(
|
||||
currline,
|
||||
@@ -1345,6 +1329,32 @@ impl<'a> BoxyBuilder<'a> {
|
||||
}
|
||||
|
||||
/// Adds a new columnar segment to the box.
|
||||
/// Adds a new columnar segment to the box with `column_count` side-by-side columns.
|
||||
///
|
||||
/// Columns start empty and are populated with [`add_col_line`](Self::add_col_line) or
|
||||
/// [`add_col_line_indx`](Self::add_col_line_indx). All columns default to equal width;
|
||||
/// use [`segment_ratios`](Self::segment_ratios) to customize.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `text_align` - Alignment applied to text within each column
|
||||
/// * `column_count` - Number of columns (must be at least 1)
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if `column_count` is 0.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use boxy_cli::prelude::*;
|
||||
///
|
||||
/// let my_box = Boxy::builder()
|
||||
/// .add_col_segment(BoxAlign::Left, 2)
|
||||
/// .add_col_line("Left column", "#ffffff", 0)
|
||||
/// .add_col_line("Right column", "#ffffff", 1)
|
||||
/// .build();
|
||||
/// ```
|
||||
pub fn add_col_segment(mut self, text_align: BoxAlign, column_count: usize) -> Self {
|
||||
assert!(
|
||||
column_count > 0,
|
||||
@@ -1418,6 +1428,36 @@ impl<'a> BoxyBuilder<'a> {
|
||||
}
|
||||
|
||||
/// Adds a line of text to a specific column of the most recently added columnar segment.
|
||||
///
|
||||
/// Convenience method — no need to specify the segment index. Mirrors
|
||||
/// [`add_col_line_indx`](Self::add_col_line_indx) for when you're building top-to-bottom.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `text` - The text content to add
|
||||
/// * `color` - Hex color code for this line (e.g. `"#ffffff"`)
|
||||
/// * `col_index` - Zero-based index of the column to add this line into
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if no segment exists, if the last segment is not columnar, or if `col_index`
|
||||
/// is out of bounds.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use boxy_cli::prelude::*;
|
||||
///
|
||||
/// let my_box = Boxy::builder()
|
||||
/// .add_col_segment(BoxAlign::Left, 3)
|
||||
/// .add_col_line("Name", "#aaaaaa", 0)
|
||||
/// .add_col_line("Status", "#aaaaaa", 1)
|
||||
/// .add_col_line("Notes", "#aaaaaa", 2)
|
||||
/// .add_col_line("Lumio V2", "#ffffff", 0)
|
||||
/// .add_col_line("Shipped", "#32CD32", 1)
|
||||
/// .add_col_line("Done", "#ffffff", 2)
|
||||
/// .build();
|
||||
/// ```
|
||||
pub fn add_col_line(mut self, text: &str, color: &str, col_index: usize) -> Self {
|
||||
let seg_index = self.data.len() - 1;
|
||||
match &mut self.data[seg_index] {
|
||||
@@ -1438,6 +1478,35 @@ impl<'a> BoxyBuilder<'a> {
|
||||
}
|
||||
|
||||
/// Adds a line of text to a specific column of a specific columnar segment by index.
|
||||
///
|
||||
/// Use this when you need to populate segments out of order or return to an earlier
|
||||
/// segment. For sequential top-to-bottom building, prefer
|
||||
/// [`add_col_line`](Self::add_col_line).
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `text` - The text content to add
|
||||
/// * `color` - Hex color code for this line (e.g. `"#ffffff"`)
|
||||
/// * `seg_index` - Zero-based index of the columnar segment
|
||||
/// * `col_index` - Zero-based index of the column within that segment
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if `seg_index` is out of bounds, if that segment is not columnar, or if
|
||||
/// `col_index` is out of bounds for that segment's column count.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use boxy_cli::prelude::*;
|
||||
///
|
||||
/// let my_box = Boxy::builder()
|
||||
/// .add_segment("Header", "#ffffff", BoxAlign::Center)
|
||||
/// .add_col_segment(BoxAlign::Left, 2)
|
||||
/// .add_col_line_indx("Left", "#ffffff", 1, 0)
|
||||
/// .add_col_line_indx("Right", "#ffffff", 1, 1)
|
||||
/// .build();
|
||||
/// ```
|
||||
pub fn add_col_line_indx(
|
||||
mut self,
|
||||
text: &str,
|
||||
|
||||
@@ -239,7 +239,19 @@ impl BoxPad {
|
||||
right: horizontal,
|
||||
}
|
||||
}
|
||||
/// returns the total padidng on either side. used for text wrapping and display time calculations
|
||||
|
||||
/// Returns the total horizontal padding (left + right).
|
||||
///
|
||||
/// Used internally for text wrapping and width calculations.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use boxy_cli::prelude::*;
|
||||
///
|
||||
/// let pad = BoxPad::vh(1, 3);
|
||||
/// assert_eq!(pad.lr(), 6); // left (3) + right (3)
|
||||
/// ```
|
||||
pub fn lr(&self) -> usize {
|
||||
self.right + self.left
|
||||
}
|
||||
@@ -247,13 +259,22 @@ impl BoxPad {
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Debug)]
|
||||
/// Represents the data layout of a single segment in a [`Boxy`](crate::boxer::Boxy) box.
|
||||
///
|
||||
/// Each segment is either a [`Single`](SegType::Single) (plain text, one line per entry)
|
||||
/// or a [`Columnar`](SegType::Columnar) (side-by-side columns, each with their own lines).
|
||||
pub enum SegType<'a> {
|
||||
/// A plain text segment. Each `Cow<str>` is one line of text content.
|
||||
Single(Vec<Cow<'a, str>>),
|
||||
/// A columnar segment. The outer `Vec` is the list of columns; each inner `Vec` is
|
||||
/// the lines of text within that column.
|
||||
Columnar(Vec<Vec<Cow<'a, str>>>),
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
impl<'a> SegType<'a> {
|
||||
/// Pushes a line into this segment. For [`Columnar`](SegType::Columnar), pushes into
|
||||
/// the last column.
|
||||
pub(crate) fn push(&mut self, p0: Cow<'a, str>) {
|
||||
match self {
|
||||
SegType::Single(vec) => vec.push(p0),
|
||||
@@ -279,14 +300,24 @@ impl<'a> SegType<'a> {
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
/// Stores pre-parsed text colors for each segment, mirroring the shape of [`SegType`].
|
||||
///
|
||||
/// Colors are parsed from hex strings once at segment-creation time and stored as
|
||||
/// [`Color`](colored::Color) values, so [`display()`](crate::boxer::Boxy::display) never
|
||||
/// needs to re-parse them.
|
||||
pub enum SegColor {
|
||||
/// Colors for a [`SegType::Single`] segment — one `Color` per line of text.
|
||||
Single(Vec<Color>),
|
||||
/// Colors for a [`SegType::Columnar`] segment — one `Vec<Color>` per column,
|
||||
/// with one `Color` per line within that column.
|
||||
Columnar(Vec<Vec<Color>>),
|
||||
}
|
||||
|
||||
use hex_color::HexColor;
|
||||
|
||||
impl SegColor {
|
||||
/// Parses a hex color string (e.g. `"#00ffff"`) into a [`Color::TrueColor`](colored::Color).
|
||||
/// Falls back to [`Color::White`](colored::Color::White) and prints a warning on parse failure.
|
||||
pub(crate) fn parse_hexcolor(hex: &str) -> Color {
|
||||
let box_col = match HexColor::parse(hex) {
|
||||
Ok(color) => Color::TrueColor {
|
||||
|
||||
+50
-28
@@ -1,45 +1,67 @@
|
||||
//! # Boxy CLI
|
||||
//! # boxy-cli
|
||||
//!
|
||||
//! A Rust library for creating beautifully styled text boxes in command-line interfaces.
|
||||
//!
|
||||
//! `boxy-cli` provides an easy way to create and display text boxes with various [border styles](./constructs/enum.BoxType.html),
|
||||
//! [colors](https://docs.rs/colored/latest/colored/), and [alignment](./constructs/enum.BoxAlign.html) options. It's perfect for creating eye-catching CLI applications,
|
||||
//! status displays, or simply organizing text output in a visually appealing way.
|
||||
//! A Rust library for creating beautifully styled, multi-segment text boxes in terminal
|
||||
//! applications — with full Unicode border support, true-color text, word-wrapping, columnar
|
||||
//! layouts, and automatic terminal-width awareness.
|
||||
//!
|
||||
//! ## Features
|
||||
//!
|
||||
//! - Multiple [border styles](./constructs/enum.BoxType.html) (single, double, bold, rounded, etc.)
|
||||
//! - Colored borders and text using [hex color codes](https://docs.rs/colored/latest/colored/)
|
||||
//! - Customizable [internal and external padding](./constructs/struct.BoxPad.html)
|
||||
//! - Text [alignment](./constructs/enum.BoxAlign.html) options (left, center, right)
|
||||
//! - Support for [multiple text segments](./boxer/struct.Boxy.html#method.add_text_sgmt) with dividers
|
||||
//! - Builder pattern for fluent API usage
|
||||
//! - Dynamic sizing based on terminal dimensions
|
||||
//! - **9 border styles** — classic ASCII, single, double, bold, rounded, and more
|
||||
//! - **True-color support** — hex color codes for both borders and per-line text
|
||||
//! - **Multi-segment boxes** — stack multiple sections separated by smart dividers
|
||||
//! - **Columnar layouts** — side-by-side columns inside a single box, with per-segment
|
||||
//! ratio control and correct `┼`/`┬`/`┴` junction characters where column boundaries meet
|
||||
//! - **Word wrapping** — automatic wrapping to terminal width with internal padding awareness
|
||||
//! - **Text alignment** — left, center, or right per segment
|
||||
//! - **Terminal-aware sizing** — auto-sizes to terminal width, or use a fixed width
|
||||
//! - **Two APIs** — imperative ([`Boxy`](crate::boxer::Boxy)) and fluent builder
|
||||
//! ([`BoxyBuilder`](crate::boxer::BoxyBuilder))
|
||||
//! - **Macro support** — [`boxy!`] for quick one-liner
|
||||
//!
|
||||
//! ## Quick Start
|
||||
//!
|
||||
//! ```rust
|
||||
//! use boxy_cli::prelude::*;
|
||||
//!
|
||||
//! fn main() {
|
||||
//! // Create a text box with fluent builder API
|
||||
//! Boxy::builder()
|
||||
//! .box_type(BoxType::Double)
|
||||
//! .color("#00ffff")
|
||||
//! .add_segment("Hello, Boxy!", "#ffffff", BoxAlign::Center)
|
||||
//! .add_segment("A beautiful CLI box library", "#32CD32", BoxAlign::Center)
|
||||
//! .padding(BoxPad::uniform(1), BoxPad::vh(1, 2))
|
||||
//! .build()
|
||||
//! .display();
|
||||
//! }
|
||||
//! // Fluent builder API
|
||||
//! Boxy::builder()
|
||||
//! .box_type(BoxType::Double)
|
||||
//! .color("#00ffff")
|
||||
//! .add_segment("Hello, boxy-cli!", "#ffffff", BoxAlign::Center)
|
||||
//! .add_segment("A terminal box library for Rust", "#32CD32", BoxAlign::Center)
|
||||
//! .padding(BoxPad::uniform(1), BoxPad::vh(1, 2))
|
||||
//! .build()
|
||||
//! .display();
|
||||
//! ```
|
||||
//!
|
||||
//! # Detailed docs
|
||||
//! See the [Boxy](./boxer/struct.Boxy.html) struct or the [BoxyBuilder](./boxer/struct.BoxyBuilder.html) struct for more information.
|
||||
//! ## Columnar Layout
|
||||
//!
|
||||
//! ## Usage Examples
|
||||
//! Columnar segments let you place content side-by-side inside one box. Column widths are
|
||||
//! controlled by ratio values — `vec![1, 2, 1]` gives the middle column twice the space.
|
||||
//!
|
||||
//! See the [README](https://github.com/BastaMasta/boxy-cli) for more examples and usage information.
|
||||
//! ```rust
|
||||
//! use boxy_cli::prelude::*;
|
||||
//!
|
||||
//! let mut b = Boxy::new(BoxType::Single, "#00ffff");
|
||||
//! b.add_text_sgmt("Project Status", "#ffffff", BoxAlign::Center);
|
||||
//! b.add_col_text_sgmt(BoxAlign::Left, 3);
|
||||
//! b.add_col_text_line("Name", "#aaaaaa", &0usize);
|
||||
//! b.add_col_text_line("Status", "#aaaaaa", &1usize);
|
||||
//! b.add_col_text_line("Notes", "#aaaaaa", &2usize);
|
||||
//! b.add_col_text_line("Lumio V2", "#ffffff", &0usize);
|
||||
//! b.add_col_text_line("Shipped", "#32CD32", &1usize);
|
||||
//! b.add_col_text_line("Internship project", "#ffffff", &2usize);
|
||||
//! b.set_segment_ratios(1, vec![1, 1, 2]);
|
||||
//! b.display();
|
||||
//! ```
|
||||
//!
|
||||
//! ## Further Reading
|
||||
//!
|
||||
//! - [`Boxy`](crate::boxer::Boxy) — imperative API reference
|
||||
//! - [`BoxyBuilder`](crate::boxer::BoxyBuilder) — builder API reference
|
||||
//! - [`BoxType`] — all available border styles
|
||||
//! - [`boxy!`] — macro reference
|
||||
//! - [GitHub README](https://github.com/BastaMasta/boxy-cli) — more examples and screenshots
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub mod boxer;
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 70 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 98 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 200 KiB After Width: | Height: | Size: 286 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 218 KiB After Width: | Height: | Size: 265 KiB |
Reference in New Issue
Block a user