cli: Use new widgets for `patch list`
This commit is contained in:
parent
ab74e54949
commit
db8ec5735f
|
|
@ -46,17 +46,13 @@ It will now be listed as one of the project's open patches.
|
|||
|
||||
```
|
||||
$ rad patch
|
||||
|
||||
❲YOU PROPOSED❳
|
||||
|
||||
Define power requirements 191a14e520f R0 3e674d1 (flux-capacitor-power) ahead 1, behind 0
|
||||
└─ * opened by did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi (you) [..]
|
||||
└─ * patch id 191a14e520f2eeff7c0e3ee0a5523c5217eecb89
|
||||
|
||||
❲OTHERS PROPOSED❳
|
||||
|
||||
Nothing to show.
|
||||
|
||||
╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
|
||||
│ Define power requirements 191a14e520f2eeff7c0e3ee0a5523c5217eecb89 R0 3e674d1 (flux-capacitor-power) ahead 1, behind 0 │
|
||||
├────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
|
||||
│ ● opened by did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi (you) 3 months ago │
|
||||
╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
|
||||
```
|
||||
```
|
||||
$ rad patch show 191a14e520f2eeff7c0e3ee0a5523c5217eecb89
|
||||
╭────────────────────────────────────────────────────────────────────╮
|
||||
│ Title Define power requirements │
|
||||
|
|
@ -118,3 +114,22 @@ Now, let's checkout the patch that we just created:
|
|||
$ rad patch checkout 191a14e520f2eeff7c0e3ee0a5523c5217eecb89
|
||||
✓ Switched to branch patch/191a14e520f
|
||||
```
|
||||
|
||||
We can also add a review verdict as such:
|
||||
|
||||
```
|
||||
$ rad review 191a14e520f2eeff7c0e3ee0a5523c5217eecb89 --accept --no-confirm --no-message --no-sync
|
||||
✓ Patch 191a14e520f accepted
|
||||
```
|
||||
|
||||
Showing the patch list now will reveal the favorable verdict:
|
||||
|
||||
```
|
||||
$ rad patch
|
||||
╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
|
||||
│ Define power requirements 191a14e520f2eeff7c0e3ee0a5523c5217eecb89 R1 27857ec (flux-capacitor-power, patch/191a14e520f) ahead 2, behind 0 │
|
||||
├───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
|
||||
│ ● opened by did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi (you) 3 months ago │
|
||||
│ ✓ accepted by z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi (you) 3 months ago │
|
||||
╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
|
||||
```
|
||||
|
|
|
|||
|
|
@ -46,17 +46,11 @@ It will now be listed as one of the project's open patches.
|
|||
|
||||
```
|
||||
$ rad patch
|
||||
|
||||
❲YOU PROPOSED❳
|
||||
|
||||
Define power requirements a07ef7743a3 R0 3e674d1 (flux-capacitor-power) ahead 1, behind 0
|
||||
└─ * opened by did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk (you) [..]
|
||||
└─ * patch id a07ef7743a32a2e902672ea3526d1db6ee08108a
|
||||
|
||||
❲OTHERS PROPOSED❳
|
||||
|
||||
Nothing to show.
|
||||
|
||||
╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
|
||||
│ Define power requirements a07ef7743a32a2e902672ea3526d1db6ee08108a R0 3e674d1 (flux-capacitor-power) ahead 1, behind 0 │
|
||||
├────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
|
||||
│ ● opened by did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk (you) 3 months ago │
|
||||
╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
|
||||
$ rad patch show a07ef7743a32a2e902672ea3526d1db6ee08108a
|
||||
╭────────────────────────────────────────────────────────────────────╮
|
||||
│ Title Define power requirements │
|
||||
|
|
|
|||
|
|
@ -27,17 +27,11 @@ $ git branch -r
|
|||
bob/master
|
||||
rad/master
|
||||
$ rad patch
|
||||
|
||||
❲YOU PROPOSED❳
|
||||
|
||||
Nothing to show.
|
||||
|
||||
❲OTHERS PROPOSED❳
|
||||
|
||||
Define power requirements a07ef7743a3 R1 27857ec ahead 2, behind 0
|
||||
└─ * opened by did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk [..]
|
||||
└─ * patch id a07ef7743a32a2e902672ea3526d1db6ee08108a
|
||||
|
||||
╭─────────────────────────────────────────────────────────────────────────────────────────────────╮
|
||||
│ Define power requirements a07ef7743a32a2e902672ea3526d1db6ee08108a R1 27857ec ahead 2, behind 0 │
|
||||
├─────────────────────────────────────────────────────────────────────────────────────────────────┤
|
||||
│ ● opened by did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk 3 months ago │
|
||||
╰─────────────────────────────────────────────────────────────────────────────────────────────────╯
|
||||
```
|
||||
|
||||
Wait! There's a mistake. The REQUIREMENTS should be a markdown file. Let's
|
||||
|
|
|
|||
|
|
@ -80,16 +80,20 @@ pub fn pretty_sync_status(
|
|||
repo: &git::raw::Repository,
|
||||
revision_oid: Oid,
|
||||
head_oid: Oid,
|
||||
) -> anyhow::Result<String> {
|
||||
) -> anyhow::Result<term::Line> {
|
||||
let (a, b) = repo.graph_ahead_behind(revision_oid, head_oid)?;
|
||||
if a == 0 && b == 0 {
|
||||
return Ok(term::format::dim("up to date").to_string());
|
||||
return Ok(term::Line::new(term::format::dim("up to date")));
|
||||
}
|
||||
|
||||
let ahead = term::format::positive(a);
|
||||
let behind = term::format::negative(b);
|
||||
|
||||
Ok(format!("ahead {ahead}, behind {behind}"))
|
||||
Ok(term::Line::default()
|
||||
.item("ahead ")
|
||||
.item(ahead)
|
||||
.item(", behind ")
|
||||
.item(behind))
|
||||
}
|
||||
|
||||
/// Make a human friendly string for commit version information.
|
||||
|
|
@ -98,8 +102,9 @@ pub fn pretty_sync_status(
|
|||
pub fn pretty_commit_version(
|
||||
revision_oid: &Oid,
|
||||
repo: &Option<git::raw::Repository>,
|
||||
) -> anyhow::Result<String> {
|
||||
let mut oid = term::format::secondary(term::format::oid(*revision_oid)).to_string();
|
||||
) -> anyhow::Result<term::Line> {
|
||||
let oid = term::format::secondary(term::format::oid(*revision_oid));
|
||||
let mut line = term::Line::new(oid);
|
||||
let mut branches: Vec<String> = vec![];
|
||||
|
||||
if let Some(repo) = repo {
|
||||
|
|
@ -115,14 +120,11 @@ pub fn pretty_commit_version(
|
|||
}
|
||||
};
|
||||
if !branches.is_empty() {
|
||||
oid = format!(
|
||||
"{} {}",
|
||||
oid,
|
||||
term::format::yellow(format!("({})", branches.join(", "))),
|
||||
);
|
||||
line.push(term::Label::space());
|
||||
line.push(term::format::yellow(format!("({})", branches.join(", "))));
|
||||
}
|
||||
|
||||
Ok(oid)
|
||||
Ok(line)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ use radicle::profile::Profile;
|
|||
use radicle::storage::git::Repository;
|
||||
|
||||
use crate::terminal as term;
|
||||
use term::cell::Cell as _;
|
||||
use term::Element as _;
|
||||
|
||||
use super::common;
|
||||
|
||||
|
|
@ -33,33 +33,18 @@ pub fn run(
|
|||
other.push((id, patch));
|
||||
}
|
||||
}
|
||||
term::blank();
|
||||
term::print(term::format::badge_secondary("YOU PROPOSED"));
|
||||
|
||||
if own.is_empty() {
|
||||
term::blank();
|
||||
if own.is_empty() && other.is_empty() {
|
||||
term::print(term::format::italic("Nothing to show."));
|
||||
} else {
|
||||
for (id, patch) in &mut own {
|
||||
term::blank();
|
||||
|
||||
print(&me, id, patch, &workdir, repository)?;
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
term::blank();
|
||||
term::print(term::format::badge_secondary("OTHERS PROPOSED"));
|
||||
|
||||
if other.is_empty() {
|
||||
term::blank();
|
||||
term::print(term::format::italic("Nothing to show."));
|
||||
} else {
|
||||
for (id, patch) in &mut other {
|
||||
term::blank();
|
||||
|
||||
print(profile.id(), id, patch, &workdir, repository)?;
|
||||
}
|
||||
for (id, patch) in &mut own {
|
||||
print(&me, id, patch, &workdir, repository)?;
|
||||
}
|
||||
for (id, patch) in &mut other {
|
||||
print(profile.id(), id, patch, &workdir, repository)?;
|
||||
}
|
||||
term::blank();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
@ -75,31 +60,43 @@ fn print(
|
|||
let target_head = common::patch_merge_target_oid(patch.target(), repository)?;
|
||||
|
||||
let you = patch.author().id().as_key() == whoami;
|
||||
let prefix = "└─ ";
|
||||
let mut author_info = vec![format!(
|
||||
"{}* opened by {}",
|
||||
prefix,
|
||||
term::format::tertiary(patch.author().id()),
|
||||
)];
|
||||
let mut author_info = term::Line::spaced([
|
||||
term::format::positive("●").into(),
|
||||
term::format::default("opened by").into(),
|
||||
term::format::tertiary(patch.author().id()).into(),
|
||||
]);
|
||||
|
||||
if you {
|
||||
author_info.push(term::format::secondary("(you)").to_string());
|
||||
author_info.push(term::Label::space());
|
||||
author_info.push(term::format::primary("(you)"));
|
||||
}
|
||||
author_info.push(term::format::dim(term::format::timestamp(&patch.timestamp())).to_string());
|
||||
author_info.push(term::Label::space());
|
||||
author_info.push(term::format::dim(term::format::timestamp(
|
||||
&patch.timestamp(),
|
||||
)));
|
||||
|
||||
let (_, revision) = patch
|
||||
.latest()
|
||||
.ok_or_else(|| anyhow!("patch is malformed: no revisions found"))?;
|
||||
term::info!(
|
||||
"{} {} {} {} {}",
|
||||
term::format::bold(patch.title()),
|
||||
term::format::highlight(term::format::cob(patch_id)),
|
||||
term::format::dim(format!("R{}", patch.version())),
|
||||
common::pretty_commit_version(&revision.oid, workdir)?,
|
||||
common::pretty_sync_status(repository.raw(), *revision.oid, target_head)?,
|
||||
);
|
||||
term::info!("{}", author_info.join(" "));
|
||||
term::info!("{prefix}* patch id {}", term::format::highlight(patch_id));
|
||||
let mut widget = term::VStack::default()
|
||||
.child(
|
||||
term::Line::spaced([
|
||||
term::format::bold(patch.title()).into(),
|
||||
term::format::highlight(patch_id).into(),
|
||||
term::format::dim(format!("R{}", patch.version())).into(),
|
||||
])
|
||||
.space()
|
||||
.extend(common::pretty_commit_version(&revision.oid, workdir)?)
|
||||
.space()
|
||||
.extend(common::pretty_sync_status(
|
||||
repository.raw(),
|
||||
*revision.oid,
|
||||
target_head,
|
||||
)?),
|
||||
)
|
||||
.divider()
|
||||
.child(author_info)
|
||||
.border(Some(term::colors::FAINT));
|
||||
|
||||
let mut timeline = Vec::new();
|
||||
for merge in revision.merges.iter() {
|
||||
|
|
@ -107,20 +104,22 @@ fn print(
|
|||
let mut badges = Vec::new();
|
||||
|
||||
if peer.delegate {
|
||||
badges.push(term::format::secondary("(delegate)").to_string());
|
||||
badges.push(term::format::secondary("(delegate)").into());
|
||||
}
|
||||
if peer.id == *whoami {
|
||||
badges.push(term::format::secondary("(you)").to_string());
|
||||
badges.push(term::format::primary("(you)").into());
|
||||
}
|
||||
|
||||
timeline.push((
|
||||
merge.timestamp,
|
||||
format!(
|
||||
"{}{} by {} {}",
|
||||
" ".repeat(prefix.width()),
|
||||
term::format::secondary(term::format::dim("✓ merged")),
|
||||
term::format::tertiary(peer.id),
|
||||
badges.join(" "),
|
||||
term::Line::spaced(
|
||||
[
|
||||
term::format::secondary(term::format::dim("✓ merged")).into(),
|
||||
term::format::default("by").into(),
|
||||
term::format::tertiary(peer.id).into(),
|
||||
]
|
||||
.into_iter()
|
||||
.chain(badges),
|
||||
),
|
||||
));
|
||||
}
|
||||
|
|
@ -134,32 +133,33 @@ fn print(
|
|||
let mut badges = Vec::new();
|
||||
|
||||
if peer.delegate {
|
||||
badges.push(term::format::secondary("(delegate)").to_string());
|
||||
badges.push(term::format::secondary("(delegate)").into());
|
||||
}
|
||||
if peer.id == *whoami {
|
||||
badges.push(term::format::secondary("(you)").to_string());
|
||||
badges.push(term::format::primary("(you)").into());
|
||||
}
|
||||
|
||||
timeline.push((
|
||||
review.timestamp(),
|
||||
format!(
|
||||
"{}{} by {} {}",
|
||||
" ".repeat(prefix.width()),
|
||||
verdict,
|
||||
term::format::tertiary(reviewer),
|
||||
badges.join(" "),
|
||||
term::Line::spaced(
|
||||
[
|
||||
verdict.into(),
|
||||
term::format::default("by").into(),
|
||||
term::format::tertiary(reviewer).into(),
|
||||
]
|
||||
.into_iter()
|
||||
.chain(badges),
|
||||
),
|
||||
));
|
||||
}
|
||||
timeline.sort_by_key(|(t, _)| *t);
|
||||
|
||||
for (time, event) in timeline.iter().rev() {
|
||||
term::info!(
|
||||
"{} {}",
|
||||
event,
|
||||
term::format::dim(term::format::timestamp(time))
|
||||
);
|
||||
for (time, mut line) in timeline.into_iter().rev() {
|
||||
line.push(term::Label::space());
|
||||
line.push(term::format::dim(term::format::timestamp(&time)));
|
||||
widget.push(line);
|
||||
}
|
||||
widget.print();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -68,7 +68,7 @@ pub fn run(
|
|||
|
||||
let description = patch.description().trim();
|
||||
let meta = VStack::default()
|
||||
.border(Some(term::ansi::Color::Blue))
|
||||
.border(Some(term::colors::FAINT))
|
||||
.child(attrs)
|
||||
.blank()
|
||||
.children(if !description.is_empty() {
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ Options
|
|||
-r, --revision <number> Revision number to review, defaults to the latest
|
||||
--[no-]sync Sync review to seed (default: sync)
|
||||
-m, --message [<string>] Provide a comment with the review (default: prompt)
|
||||
--no-confirm Don't ask for confirmation
|
||||
--no-message Don't provide a comment with the review
|
||||
--help Print help
|
||||
"#,
|
||||
|
|
@ -50,6 +51,7 @@ pub struct Options {
|
|||
pub message: Message,
|
||||
pub sync: bool,
|
||||
pub verbose: bool,
|
||||
pub confirm: bool,
|
||||
pub verdict: Option<Verdict>,
|
||||
}
|
||||
|
||||
|
|
@ -63,6 +65,7 @@ impl Args for Options {
|
|||
let mut message = Message::default();
|
||||
let mut sync = true;
|
||||
let mut verbose = false;
|
||||
let mut confirm = true;
|
||||
let mut verdict = None;
|
||||
|
||||
while let Some(arg) = parser.next()? {
|
||||
|
|
@ -90,6 +93,9 @@ impl Args for Options {
|
|||
message.append(&txt);
|
||||
}
|
||||
}
|
||||
Long("no-confirm") => {
|
||||
confirm = false;
|
||||
}
|
||||
Long("no-message") => {
|
||||
message = Message::Blank;
|
||||
}
|
||||
|
|
@ -121,6 +127,7 @@ impl Args for Options {
|
|||
id: id.ok_or_else(|| anyhow!("a patch id to review must be provided"))?,
|
||||
message,
|
||||
sync,
|
||||
confirm,
|
||||
revision,
|
||||
verbose,
|
||||
verdict,
|
||||
|
|
@ -158,13 +165,15 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
|
|||
Some(Verdict::Reject) => term::format::negative("Reject"),
|
||||
None => term::format::dim("Review"),
|
||||
};
|
||||
if !term::confirm(format!(
|
||||
"{} {} {} by {}?",
|
||||
verdict_pretty,
|
||||
patch_id_pretty,
|
||||
term::format::dim(format!("R{revision_ix}")),
|
||||
term::format::tertiary(patch.author().id())
|
||||
)) {
|
||||
if options.confirm
|
||||
&& !term::confirm(format!(
|
||||
"{} {} {} by {}?",
|
||||
verdict_pretty,
|
||||
patch_id_pretty,
|
||||
term::format::dim(format!("R{revision_ix}")),
|
||||
term::format::tertiary(patch.author().id())
|
||||
))
|
||||
{
|
||||
anyhow::bail!("Patch review aborted");
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,4 @@
|
|||
use crate::ansi::Color;
|
||||
|
||||
/// The faintest color; useful for borders and such.
|
||||
pub const FAINT: Color = Color::Fixed(236);
|
||||
|
|
@ -71,6 +71,16 @@ impl Line {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn spaced(items: impl IntoIterator<Item = Label>) -> Self {
|
||||
let mut line = Self::default();
|
||||
for item in items.into_iter() {
|
||||
line.push(item);
|
||||
line.push(Label::space());
|
||||
}
|
||||
line.items.pop();
|
||||
line
|
||||
}
|
||||
|
||||
/// Add an item to this line.
|
||||
pub fn item(mut self, item: impl Into<Label>) -> Self {
|
||||
self.push(item);
|
||||
|
|
@ -90,10 +100,12 @@ impl Line {
|
|||
|
||||
/// Pad this line to occupy the given width.
|
||||
pub fn pad(&mut self, width: usize) {
|
||||
let w = Element::columns(self);
|
||||
let pad = width - w;
|
||||
let w = self.columns();
|
||||
|
||||
self.items.push(Label::new(" ".repeat(pad).as_str()));
|
||||
if width > w {
|
||||
let pad = width - w;
|
||||
self.items.push(Label::new(" ".repeat(pad).as_str()));
|
||||
}
|
||||
}
|
||||
|
||||
/// Truncate this line to the given width.
|
||||
|
|
@ -108,6 +120,11 @@ impl Line {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn space(mut self) -> Self {
|
||||
self.items.push(Label::space());
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoIterator for Line {
|
||||
|
|
|
|||
|
|
@ -16,6 +16,10 @@ pub fn positive<D: std::fmt::Display>(msg: D) -> Paint<D> {
|
|||
Paint::green(msg).bold()
|
||||
}
|
||||
|
||||
pub fn primary<D: std::fmt::Display>(msg: D) -> Paint<D> {
|
||||
Paint::magenta(msg)
|
||||
}
|
||||
|
||||
pub fn secondary<D: std::fmt::Display>(msg: D) -> Paint<D> {
|
||||
Paint::blue(msg).bold()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,10 +11,14 @@ pub struct HStack<'a> {
|
|||
impl<'a> HStack<'a> {
|
||||
/// Add an element to the stack.
|
||||
pub fn child(mut self, child: impl Element + 'a) -> Self {
|
||||
self.width += child.columns();
|
||||
self.push(child);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn push(&mut self, child: impl Element + 'a) {
|
||||
self.width += child.columns() + 1;
|
||||
self.height = self.height.max(child.rows());
|
||||
self.elems.push(Box::new(child));
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -16,6 +16,11 @@ impl Label {
|
|||
pub fn blank() -> Self {
|
||||
Self(Paint::default())
|
||||
}
|
||||
|
||||
/// Create a single space label
|
||||
pub fn space() -> Self {
|
||||
Self(Paint::new(" ".to_owned()))
|
||||
}
|
||||
}
|
||||
|
||||
impl Element for Label {
|
||||
|
|
@ -51,19 +56,10 @@ impl Cell for Label {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<Paint<String>> for Label {
|
||||
fn from(paint: Paint<String>) -> Self {
|
||||
impl<D: fmt::Display> From<Paint<D>> for Label {
|
||||
fn from(paint: Paint<D>) -> Self {
|
||||
Self(Paint {
|
||||
item: paint.item.replace('\n', " "),
|
||||
style: paint.style,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Paint<&str>> for Label {
|
||||
fn from(paint: Paint<&str>) -> Self {
|
||||
Self(Paint {
|
||||
item: paint.item.replace('\n', " "),
|
||||
item: paint.item.to_string().replace('\n', " "),
|
||||
style: paint.style,
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
pub mod ansi;
|
||||
pub mod cell;
|
||||
pub mod colors;
|
||||
pub mod command;
|
||||
pub mod editor;
|
||||
pub mod element;
|
||||
|
|
|
|||
|
|
@ -6,21 +6,26 @@ pub struct VStackOptions {
|
|||
border: Option<Color>,
|
||||
}
|
||||
|
||||
/// A vertical stack row.
|
||||
#[derive(Default, Debug)]
|
||||
enum Row<'a> {
|
||||
Element(Box<dyn Element + 'a>),
|
||||
#[default]
|
||||
Dividier,
|
||||
}
|
||||
/// Vertical stack of [`Element`] objects that implements [`Element`].
|
||||
#[derive(Default, Debug)]
|
||||
pub struct VStack<'a> {
|
||||
elems: Vec<Box<dyn Element + 'a>>,
|
||||
rows: Vec<Row<'a>>,
|
||||
opts: VStackOptions,
|
||||
width: usize,
|
||||
height: usize,
|
||||
}
|
||||
|
||||
impl<'a> VStack<'a> {
|
||||
/// Add an element to the stack.
|
||||
/// Add an element to the stack and return the stack.
|
||||
pub fn child(mut self, child: impl Element + 'a) -> Self {
|
||||
self.width = self.width.max(child.columns());
|
||||
self.height += child.rows();
|
||||
self.elems.push(Box::new(child));
|
||||
self.push(child);
|
||||
self
|
||||
}
|
||||
|
||||
|
|
@ -29,6 +34,13 @@ impl<'a> VStack<'a> {
|
|||
self.child(Label::blank())
|
||||
}
|
||||
|
||||
/// Add a horizontal divider.
|
||||
pub fn divider(mut self) -> Self {
|
||||
self.rows.push(Row::Dividier);
|
||||
self.height += 1;
|
||||
self
|
||||
}
|
||||
|
||||
/// Add multiple elements to the stack.
|
||||
pub fn children<E: Element + 'a>(self, children: impl IntoIterator<Item = E>) -> Self {
|
||||
let mut vstack = self;
|
||||
|
|
@ -44,6 +56,13 @@ impl<'a> VStack<'a> {
|
|||
self.opts.border = color;
|
||||
self
|
||||
}
|
||||
|
||||
/// Add an element to the stack.
|
||||
pub fn push(&mut self, child: impl Element + 'a) {
|
||||
self.width = self.width.max(child.columns());
|
||||
self.height += child.rows();
|
||||
self.rows.push(Row::Element(Box::new(child)));
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Element for VStack<'a> {
|
||||
|
|
@ -67,18 +86,34 @@ impl<'a> Element for VStack<'a> {
|
|||
);
|
||||
}
|
||||
|
||||
for elem in &self.elems {
|
||||
for mut line in elem.render() {
|
||||
if let Some(color) = self.opts.border {
|
||||
line.pad(self.width);
|
||||
lines.push(
|
||||
Line::default()
|
||||
.item(Paint::new("│ ").fg(color))
|
||||
.extend(line)
|
||||
.item(Paint::new(" │").fg(color)),
|
||||
);
|
||||
} else {
|
||||
lines.push(line);
|
||||
for row in &self.rows {
|
||||
match row {
|
||||
Row::Element(elem) => {
|
||||
for mut line in elem.render() {
|
||||
if let Some(color) = self.opts.border {
|
||||
line.pad(self.width);
|
||||
lines.push(
|
||||
Line::default()
|
||||
.item(Paint::new("│ ").fg(color))
|
||||
.extend(line)
|
||||
.item(Paint::new(" │").fg(color)),
|
||||
);
|
||||
} else {
|
||||
lines.push(line);
|
||||
}
|
||||
}
|
||||
}
|
||||
Row::Dividier => {
|
||||
if let Some(color) = self.opts.border {
|
||||
lines.push(
|
||||
Line::default()
|
||||
.item(Paint::new("├").fg(color))
|
||||
.item(Paint::new("─".repeat(self.width + 2)).fg(color))
|
||||
.item(Paint::new("┤").fg(color)),
|
||||
);
|
||||
} else {
|
||||
lines.push(Line::default());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue