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:
2026-06-22 21:13:24 +03:00
parent 6855331d7e
commit f7eee64da4
9 changed files with 435 additions and 232 deletions
+66 -51
View File
@@ -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
View File
@@ -1,184 +1,250 @@
# boxy-cli
[![Github](https://img.shields.io/badge/GitHub-BastaMasta%2Fboxy--cli-blue?style=flat-square&logo=github)](https://github.com/BastaMasta/boxy-cli)
[![Crates.io](https://img.shields.io/crates/v/boxy-cli?style=flat-square&logo=rust)](https://crates.io/crates/boxy-cli)
[![Crates.io](https://img.shields.io/crates/v/boxy-cli?style=flat-square&logo=rust&color=orange)](https://crates.io/crates/boxy-cli)
[![Docs.rs](https://img.shields.io/badge/docs.rs-boxy--cli-66c2a5?style=flat-square&logo=docs.rs)](https://docs.rs/boxy-cli/latest/)
[![Crates.io](https://img.shields.io/crates/d/boxy-cli?style=flat-square)](https://crates.io/crates/boxy-cli)
[![Apache License](https://img.shields.io/badge/license-Apache%202.0-blue?style=flat-square)](https://github.com/BastaMasta/boxy-cli/blob/main/LICENSE-APACHE)
[![MIT License](https://img.shields.io/badge/license-MIT-blue?style=flat-square)](https://github.com/BastaMasta/boxy-cli/blob/main/LICENSE-MIT)
[![Build Status](https://img.shields.io/github/actions/workflow/status/BastaMasta/boxy-cli/rust.yml?branch=main&style=flat-square)](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.
![Builder pattern box, double border, cyan](readme-assets/textbox-builder.jpg)
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:
![Multi-segment box, lime green, double border](readme-assets/textbox1.jpg)
### 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();
```
![Columnar box showing ┼ and ┴ junction characters](readme-assets/textbox-columns.jpg)
---
## 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:
![Multi-segment box, cyan, bold border](readme-assets/textbox2.jpg)
```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
View File
@@ -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,
+32 -1
View File
@@ -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
View File
@@ -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