gui: refactor amount formatting
This commit is contained in:
parent
c6b8133286
commit
59035060e9
@ -3,117 +3,124 @@ pub use bitcoin::Amount;
|
||||
use crate::{color, component::text::*, widget::*};
|
||||
|
||||
pub fn amount<'a, T: 'a>(a: &Amount) -> Row<'a, T> {
|
||||
amount_with_size(a, P1_SIZE)
|
||||
render_amount(amount_as_string(*a), P1_SIZE)
|
||||
}
|
||||
|
||||
pub fn amount_with_size<'a, T: 'a>(a: &Amount, size: u16) -> Row<'a, T> {
|
||||
let spacing = if size > P1_SIZE { 10 } else { 5 };
|
||||
let sats = format!("{:.8}", a.to_btc());
|
||||
assert!(sats.len() >= 9);
|
||||
let row = Row::new()
|
||||
.spacing(spacing)
|
||||
.push(split_digits(sats[0..sats.len() - 6].to_string(), size, true).into())
|
||||
.push(if a.to_sat() < 1_000_000 {
|
||||
split_digits(sats[sats.len() - 6..sats.len() - 3].to_string(), size, true).into()
|
||||
} else {
|
||||
Row::new()
|
||||
.push(
|
||||
text(sats[sats.len() - 6..sats.len() - 3].to_string())
|
||||
.bold()
|
||||
.size(size),
|
||||
)
|
||||
.into()
|
||||
})
|
||||
.push(if a.to_sat() < 1000 {
|
||||
split_digits(sats[sats.len() - 3..sats.len()].to_string(), size, true).into()
|
||||
} else {
|
||||
Row::new()
|
||||
.push(
|
||||
text(sats[sats.len() - 3..sats.len()].to_string())
|
||||
.bold()
|
||||
.size(size),
|
||||
)
|
||||
.into()
|
||||
});
|
||||
|
||||
Row::with_children(vec![
|
||||
row.into(),
|
||||
text("BTC").size(size).style(color::GREY_3).into(),
|
||||
])
|
||||
.spacing(spacing)
|
||||
.align_items(iced::Alignment::Center)
|
||||
render_amount(amount_as_string(*a), size)
|
||||
}
|
||||
|
||||
pub fn unconfirmed_amount_with_size<'a, T: 'a>(a: &Amount, size: u16) -> Row<'a, T> {
|
||||
let spacing = if size > P1_SIZE { 10 } else { 5 };
|
||||
let sats = format!("{:.8}", a.to_btc());
|
||||
assert!(sats.len() >= 9);
|
||||
let row = Row::new()
|
||||
.spacing(spacing)
|
||||
.push(split_digits(sats[0..sats.len() - 6].to_string(), size, false).into())
|
||||
.push(if a.to_sat() < 1_000_000 {
|
||||
split_digits(
|
||||
sats[sats.len() - 6..sats.len() - 3].to_string(),
|
||||
size,
|
||||
false,
|
||||
)
|
||||
.into()
|
||||
} else {
|
||||
Row::new()
|
||||
.push(text(sats[sats.len() - 6..sats.len() - 3].to_string()).size(size))
|
||||
.into()
|
||||
})
|
||||
.push(if a.to_sat() < 1000 {
|
||||
split_digits(sats[sats.len() - 3..sats.len()].to_string(), size, false).into()
|
||||
} else {
|
||||
Row::new()
|
||||
.push(text(sats[sats.len() - 3..sats.len()].to_string()).size(size))
|
||||
.into()
|
||||
});
|
||||
|
||||
Row::with_children(vec![
|
||||
row.into(),
|
||||
text("BTC").size(size).style(color::GREY_3).into(),
|
||||
])
|
||||
.spacing(spacing)
|
||||
.align_items(iced::Alignment::Center)
|
||||
render_unconfirmed_amount(amount_as_string(*a), size)
|
||||
}
|
||||
|
||||
fn split_digits<'a, T: 'a>(mut s: String, size: u16, bold: bool) -> impl Into<Element<'a, T>> {
|
||||
let prefixes = vec!["0.00", "0.0", "0.", "000", "00", "0"];
|
||||
for prefix in prefixes {
|
||||
if s.starts_with(prefix) {
|
||||
let right = s.split_off(prefix.len());
|
||||
return Row::new()
|
||||
.push(text(s).size(size).style(color::GREY_3))
|
||||
.push_maybe(if right.is_empty() {
|
||||
None
|
||||
} else if bold {
|
||||
Some(text(right).bold().size(size))
|
||||
} else {
|
||||
Some(text(right).size(size))
|
||||
});
|
||||
}
|
||||
}
|
||||
//
|
||||
// Helpers
|
||||
//
|
||||
|
||||
// Format a BTC amount as a string for display.
|
||||
fn amount_as_string(a: Amount) -> String {
|
||||
let amount = a.to_btc().to_string();
|
||||
|
||||
// Reformat the integer portion of the amount with space separation.
|
||||
let (integer, fraction) = match s.split_once('.') {
|
||||
let (integer, fraction) = match amount.split_once('.') {
|
||||
Some((i, f)) => (i, f),
|
||||
None => (s.as_str(), "00 000 000"),
|
||||
None => (amount.as_str(), "00000000"),
|
||||
};
|
||||
let mut integer = integer
|
||||
|
||||
let integer = format_amount_number_part(integer);
|
||||
let fraction = format_amount_number_part(&format!("{:0<8}", fraction));
|
||||
|
||||
format!("{integer}.{fraction}")
|
||||
}
|
||||
|
||||
// Format a "part" of a number string with spaces to fit display requirements.
|
||||
// Currently using French formatting rules so digits are space-separated in groups
|
||||
// of three, starting from the right side. Incidentally, this works for both the
|
||||
// integer portion of the number as well as the fraction part.
|
||||
// Ex:
|
||||
// 1000 => 1 000
|
||||
// 100000 => 100 000
|
||||
fn format_amount_number_part(s: &str) -> String {
|
||||
let mut part = s
|
||||
.chars()
|
||||
.collect::<Vec<_>>()
|
||||
.rchunks(3)
|
||||
.map(|c| c.iter().collect::<String>())
|
||||
.collect::<Vec<_>>();
|
||||
integer.reverse();
|
||||
let integer = integer.join(" ");
|
||||
s = format!("{integer}.{fraction}");
|
||||
part.reverse();
|
||||
|
||||
if bold {
|
||||
Row::new().push(text(s).bold().size(size))
|
||||
} else {
|
||||
Row::new().push(text(s).size(size))
|
||||
part.join(" ")
|
||||
}
|
||||
|
||||
// Helper functions split a string at the first occurence of a non-zero integer (where
|
||||
// the amount starts).
|
||||
fn split_at_first_non_zero(s: String) -> Option<(String, String)> {
|
||||
for (index, c) in s.char_indices() {
|
||||
if c.is_ascii_digit() && c != '0' {
|
||||
let (before, after) = s.split_at(index);
|
||||
return Some((before.to_string(), after.to_string()));
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
// Build the rendering elements for displaying a Bitcoin amount.
|
||||
// The text should be bolded beginning where the BTC amount is non-zero.
|
||||
fn render_amount<'a, T: 'a>(amount: String, size: u16) -> Row<'a, T> {
|
||||
let spacing = if size > P1_SIZE { 10 } else { 5 };
|
||||
|
||||
let (before, after) = match split_at_first_non_zero(amount) {
|
||||
Some((b, a)) => (b, a),
|
||||
None => (String::from(""), String::from("")),
|
||||
};
|
||||
|
||||
let row = Row::new()
|
||||
.spacing(spacing)
|
||||
.push(text(before).size(size).style(color::GREY_3))
|
||||
.push(text(after).size(size).bold());
|
||||
|
||||
Row::with_children(vec![
|
||||
row.into(),
|
||||
text("BTC").size(size).style(color::GREY_3).into(),
|
||||
])
|
||||
.spacing(spacing)
|
||||
.align_items(iced::Alignment::Center)
|
||||
}
|
||||
|
||||
// Build the rendering elements for displaying a Bitcoin amount.
|
||||
fn render_unconfirmed_amount<'a, T: 'a>(amount: String, size: u16) -> Row<'a, T> {
|
||||
let spacing = if size > P1_SIZE { 10 } else { 5 };
|
||||
|
||||
let row = Row::new()
|
||||
.spacing(spacing)
|
||||
.push(text(amount).size(size).style(color::GREY_3));
|
||||
|
||||
Row::with_children(vec![
|
||||
row.into(),
|
||||
text("BTC").size(size).style(color::GREY_3).into(),
|
||||
])
|
||||
.spacing(spacing)
|
||||
.align_items(iced::Alignment::Center)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_amount_as_str() {
|
||||
assert_eq!(
|
||||
"0.00 799 800",
|
||||
amount_as_string(bitcoin::Amount::from_btc(0.00799800).unwrap())
|
||||
);
|
||||
assert_eq!(
|
||||
"1 000.00 799 800",
|
||||
amount_as_string(bitcoin::Amount::from_btc(1000.00799800).unwrap())
|
||||
);
|
||||
assert_eq!(
|
||||
"1 000.00 000 000",
|
||||
amount_as_string(bitcoin::Amount::from_btc(1000.0).unwrap())
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user