T16 review fixes: an unknown system zone stops instead of reading as UTC, the view window refits when data shrinks, the volume profile counts narrow and flat candles whole, Fibonacci labels stay in their pane, the DOM ladder shows its tick's decimals, the tape flashes on each new trade, typed numbers reach their owner before a press, and a smaller loss than expected reads as a beat
This commit is contained in:
14 files changed
+230
-65
No files matched your search
+20
-9
@@ -16,7 +16,10 @@ use crate::{
|
||||
motion::Flash,
|
||||
tables::{Aggregate, Cell, Column, DataTable, Row},
|
||||
theme::{ActiveTheme, TextSize},
|
||||
typography::{format, tabular},
|
||||
typography::{
|
||||
format::{self, system_zone},
|
||||
tabular,
|
||||
},
|
||||
};
|
||||
|
||||
/// A held position: its symbol, how many, what each cost on average, and the last price.
|
||||
@@ -115,7 +118,7 @@ pub struct Working {
|
||||
pub struct OrderTable {
|
||||
id: ElementId,
|
||||
orders: Vec<Working>,
|
||||
zone: TimeZone,
|
||||
zone: Option<TimeZone>,
|
||||
}
|
||||
|
||||
impl OrderTable {
|
||||
@@ -132,13 +135,13 @@ impl OrderTable {
|
||||
Self {
|
||||
id: id.into(),
|
||||
orders,
|
||||
zone: TimeZone::system(),
|
||||
zone: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// The time zone its times read in; the system's unless set.
|
||||
pub fn zone(mut self, zone: TimeZone) -> Self {
|
||||
self.zone = zone;
|
||||
self.zone = Some(zone);
|
||||
self
|
||||
}
|
||||
}
|
||||
@@ -153,6 +156,10 @@ fn side_tag(side: Side) -> Cell {
|
||||
|
||||
impl RenderOnce for OrderTable {
|
||||
fn render(self, _: &mut Window, _: &mut App) -> impl IntoElement {
|
||||
let zone = self
|
||||
.zone
|
||||
.clone()
|
||||
.unwrap_or_else(|| system_zone("OrderTable"));
|
||||
let columns = [
|
||||
Column::new("time", "Placed"),
|
||||
Column::new("symbol", "Symbol"),
|
||||
@@ -175,7 +182,7 @@ impl RenderOnce for OrderTable {
|
||||
};
|
||||
let time: SharedString = working
|
||||
.time
|
||||
.to_zoned(self.zone.clone())
|
||||
.to_zoned(zone.clone())
|
||||
.strftime("%H:%M:%S")
|
||||
.to_string()
|
||||
.into();
|
||||
@@ -214,7 +221,7 @@ pub struct Fill {
|
||||
pub struct TradeHistoryTable {
|
||||
id: ElementId,
|
||||
fills: Vec<Fill>,
|
||||
zone: TimeZone,
|
||||
zone: Option<TimeZone>,
|
||||
}
|
||||
|
||||
impl TradeHistoryTable {
|
||||
@@ -224,19 +231,23 @@ impl TradeHistoryTable {
|
||||
Self {
|
||||
id: id.into(),
|
||||
fills,
|
||||
zone: TimeZone::system(),
|
||||
zone: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// The time zone its times read in; the system's unless set.
|
||||
pub fn zone(mut self, zone: TimeZone) -> Self {
|
||||
self.zone = zone;
|
||||
self.zone = Some(zone);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl RenderOnce for TradeHistoryTable {
|
||||
fn render(self, _: &mut Window, _: &mut App) -> impl IntoElement {
|
||||
let zone = self
|
||||
.zone
|
||||
.clone()
|
||||
.unwrap_or_else(|| system_zone("TradeHistoryTable"));
|
||||
let columns = [
|
||||
Column::new("time", "Time"),
|
||||
Column::new("symbol", "Symbol"),
|
||||
@@ -261,7 +272,7 @@ impl RenderOnce for TradeHistoryTable {
|
||||
let total = signed * fill.quantity * fill.price - fill.fee;
|
||||
let time: SharedString = fill
|
||||
.time
|
||||
.to_zoned(self.zone.clone())
|
||||
.to_zoned(zone.clone())
|
||||
.strftime("%b %-d %H:%M")
|
||||
.to_string()
|
||||
.into();
|
||||
|
||||
+35
-10
@@ -11,7 +11,11 @@ use crate::{
|
||||
forms::flag,
|
||||
tables::{Cell, Column, DataTable, Row},
|
||||
theme::{ActiveTheme, TextSize},
|
||||
typography::{Ellipsis, format, tabular},
|
||||
typography::{
|
||||
Ellipsis,
|
||||
format::{self, system_zone},
|
||||
tabular,
|
||||
},
|
||||
};
|
||||
|
||||
/// An economic release: when, for which region, what, how much it moves markets from one to three, and its figure as it came, as forecast and as before.
|
||||
@@ -31,7 +35,7 @@ pub struct Release {
|
||||
#[derive(IntoElement)]
|
||||
pub struct EconomicCalendar {
|
||||
releases: Vec<Release>,
|
||||
zone: TimeZone,
|
||||
zone: Option<TimeZone>,
|
||||
}
|
||||
|
||||
impl EconomicCalendar {
|
||||
@@ -45,18 +49,22 @@ impl EconomicCalendar {
|
||||
);
|
||||
Self {
|
||||
releases,
|
||||
zone: TimeZone::system(),
|
||||
zone: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn zone(mut self, zone: TimeZone) -> Self {
|
||||
self.zone = zone;
|
||||
self.zone = Some(zone);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl RenderOnce for EconomicCalendar {
|
||||
fn render(mut self, _: &mut Window, cx: &mut App) -> impl IntoElement {
|
||||
let zone = self
|
||||
.zone
|
||||
.clone()
|
||||
.unwrap_or_else(|| system_zone("EconomicCalendar"));
|
||||
let theme = cx.theme();
|
||||
let colors = theme.colors.clone();
|
||||
self.releases.sort_by_key(|release| release.time);
|
||||
@@ -68,7 +76,7 @@ impl RenderOnce for EconomicCalendar {
|
||||
let mut out: Vec<gpui::AnyElement> = Vec::new();
|
||||
let mut day: Option<Date> = None;
|
||||
for release in &self.releases {
|
||||
let zoned = release.time.to_zoned(self.zone.clone());
|
||||
let zoned = release.time.to_zoned(zone.clone());
|
||||
if day != Some(zoned.date()) {
|
||||
day = Some(zoned.date());
|
||||
out.push(
|
||||
@@ -179,6 +187,11 @@ pub struct Earnings {
|
||||
pub reported: Option<f64>,
|
||||
}
|
||||
|
||||
/// How far a report beat its estimate, as a share of the estimate's size: positive when better, whatever the sign.
|
||||
pub(crate) fn surprise(estimate: f64, reported: f64) -> f64 {
|
||||
(reported - estimate) / estimate.abs()
|
||||
}
|
||||
|
||||
/// Earnings by date: each company, when in the day it reports, what is expected, and once reported, how far it beat or missed.
|
||||
#[derive(IntoElement)]
|
||||
pub struct EarningsCalendar {
|
||||
@@ -222,7 +235,7 @@ impl RenderOnce for EarningsCalendar {
|
||||
);
|
||||
let surprise = match earnings.reported {
|
||||
Some(reported) if earnings.estimate != 0.0 => {
|
||||
let share = reported / earnings.estimate - 1.0;
|
||||
let share = surprise(earnings.estimate, reported);
|
||||
Cell::Tag(
|
||||
format::percent(share, 1, true).into(),
|
||||
if share >= 0.0 {
|
||||
@@ -270,7 +283,7 @@ type OnStory = Rc<dyn Fn(usize, &mut Window, &mut App)>;
|
||||
pub struct NewsFeed {
|
||||
id: ElementId,
|
||||
stories: Vec<Story>,
|
||||
zone: TimeZone,
|
||||
zone: Option<TimeZone>,
|
||||
on_open: Option<OnStory>,
|
||||
}
|
||||
|
||||
@@ -279,13 +292,13 @@ impl NewsFeed {
|
||||
Self {
|
||||
id: id.into(),
|
||||
stories: stories.into_iter().collect(),
|
||||
zone: TimeZone::system(),
|
||||
zone: None,
|
||||
on_open: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn zone(mut self, zone: TimeZone) -> Self {
|
||||
self.zone = zone;
|
||||
self.zone = Some(zone);
|
||||
self
|
||||
}
|
||||
|
||||
@@ -298,6 +311,7 @@ impl NewsFeed {
|
||||
|
||||
impl RenderOnce for NewsFeed {
|
||||
fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {
|
||||
let zone = self.zone.clone().unwrap_or_else(|| system_zone("NewsFeed"));
|
||||
let theme = cx.theme();
|
||||
let colors = theme.colors.clone();
|
||||
let mut order: Vec<usize> = (0..self.stories.len()).collect();
|
||||
@@ -315,7 +329,7 @@ impl RenderOnce for NewsFeed {
|
||||
let open = self.on_open.clone();
|
||||
let when = story
|
||||
.time
|
||||
.to_zoned(self.zone.clone())
|
||||
.to_zoned(zone.clone())
|
||||
.strftime("%b %-d, %H:%M")
|
||||
.to_string();
|
||||
div()
|
||||
@@ -376,3 +390,14 @@ impl RenderOnce for NewsFeed {
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::surprise;
|
||||
|
||||
#[test]
|
||||
fn a_smaller_loss_than_expected_is_a_beat() {
|
||||
assert_eq!(surprise(-1.0, -0.5), 0.5);
|
||||
assert_eq!(surprise(2.0, 1.5), -0.25);
|
||||
}
|
||||
}
|
||||
+26
-2
@@ -162,14 +162,22 @@ pub(crate) fn profile(
|
||||
(low, high): (f64, f64),
|
||||
bands: usize,
|
||||
) -> Vec<(f64, f64, f64)> {
|
||||
assert!(bands > 0 && high > low, "a profile needs bands over a span");
|
||||
let step = (high - low) / bands as f64;
|
||||
let mut traded = vec![0.0; bands];
|
||||
for candle in candles {
|
||||
let reach = (candle.high - candle.low).max(step);
|
||||
let span = candle.high - candle.low;
|
||||
if span <= 0.0 {
|
||||
let band = ((candle.close - low) / step)
|
||||
.floor()
|
||||
.clamp(0.0, (bands - 1) as f64);
|
||||
traded[band as usize] += candle.volume;
|
||||
continue;
|
||||
}
|
||||
for (band, volume) in traded.iter_mut().enumerate() {
|
||||
let (from, to) = (low + step * band as f64, low + step * (band + 1) as f64);
|
||||
let overlap = (to.min(candle.high) - from.max(candle.low)).max(0.0);
|
||||
*volume += candle.volume * overlap / reach;
|
||||
*volume += candle.volume * overlap / span;
|
||||
}
|
||||
}
|
||||
traded
|
||||
@@ -250,4 +258,20 @@ mod tests {
|
||||
);
|
||||
assert_eq!((bands[3].0, bands[3].1), (13.0, 14.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn narrow_and_flat_candles_count_all_their_volume() {
|
||||
let narrow = Candle::new(Timestamp::UNIX_EPOCH, (10.2, 10.3, 10.1, 10.2), 100.0);
|
||||
let flat = Candle::new(Timestamp::UNIX_EPOCH, (12.5, 12.5, 12.5, 12.5), 40.0);
|
||||
let traded: Vec<f64> = profile(&[narrow, flat], (10.0, 14.0), 4)
|
||||
.iter()
|
||||
.map(|band| band.2)
|
||||
.collect();
|
||||
assert!(
|
||||
(traded[0] - 100.0).abs() < 1e-9,
|
||||
"{traded:?}: a narrow candle's whole volume"
|
||||
);
|
||||
assert_eq!(traded[2], 40.0, "a flat candle lands in its band");
|
||||
assert!((traded.iter().sum::<f64>() - 140.0).abs() < 1e-9);
|
||||
}
|
||||
}
|
||||
@@ -225,7 +225,15 @@ pub(crate) fn labels(
|
||||
continue;
|
||||
};
|
||||
let x = axes.visible.x(from.0.min(to.0), (main.x, main.w));
|
||||
for (share, value) in Drawing::levels(from, to) {
|
||||
if x > main.x + main.w {
|
||||
continue;
|
||||
}
|
||||
let x = x.max(main.x);
|
||||
let inside = |y: f32| y >= main.y && y <= main.y + main.h;
|
||||
for (share, value) in Drawing::levels(from, to)
|
||||
.into_iter()
|
||||
.filter(|(_, value)| inside(price.at(*value)))
|
||||
{
|
||||
let words = format!(
|
||||
"{} {}",
|
||||
format::percent(share, 1, false),
|
||||
|
||||
+17
-5
@@ -19,6 +19,7 @@ use super::{
|
||||
use crate::{
|
||||
charts::{Linear, Rect, measure},
|
||||
theme::ActiveTheme,
|
||||
typography::format::system_zone,
|
||||
};
|
||||
|
||||
/// How a market chart draws its prices.
|
||||
@@ -57,7 +58,7 @@ pub struct CandlestickChart {
|
||||
compare: Option<(SharedString, Rc<Vec<Candle>>)>,
|
||||
sync: Option<Entity<ChartSync>>,
|
||||
red_up: bool,
|
||||
zone: TimeZone,
|
||||
zone: Option<TimeZone>,
|
||||
drawings: Vec<Drawing>,
|
||||
tool: Option<Tool>,
|
||||
on_draw: Option<OnDraw>,
|
||||
@@ -85,7 +86,7 @@ impl CandlestickChart {
|
||||
compare: None,
|
||||
sync: None,
|
||||
red_up: false,
|
||||
zone: TimeZone::system(),
|
||||
zone: None,
|
||||
drawings: Vec::new(),
|
||||
tool: None,
|
||||
on_draw: None,
|
||||
@@ -181,7 +182,7 @@ impl CandlestickChart {
|
||||
|
||||
/// The time zone its times read in; the system's unless set.
|
||||
pub fn zone(mut self, zone: TimeZone) -> Self {
|
||||
self.zone = zone;
|
||||
self.zone = Some(zone);
|
||||
self
|
||||
}
|
||||
}
|
||||
@@ -194,6 +195,10 @@ impl Styled for CandlestickChart {
|
||||
|
||||
impl RenderOnce for CandlestickChart {
|
||||
fn render(mut self, window: &mut Window, cx: &mut App) -> impl IntoElement {
|
||||
let zone = self
|
||||
.zone
|
||||
.clone()
|
||||
.unwrap_or_else(|| system_zone("CandlestickChart"));
|
||||
let sync = self.sync.clone();
|
||||
let stage: Entity<Stage> =
|
||||
window.use_keyed_state((self.id.clone(), "stage"), cx, |_, cx| Stage {
|
||||
@@ -236,11 +241,18 @@ impl RenderOnce for CandlestickChart {
|
||||
{
|
||||
visible.pan((total - seen) as f64, total)
|
||||
}
|
||||
Some(visible) => visible,
|
||||
Some(visible) => visible.fitted(total),
|
||||
None => Visible::latest(total, f64::from(main.w / pixels(sizes.candle)).max(1.0)),
|
||||
};
|
||||
if seen != total {
|
||||
log::debug!("market chart: {seen} candles to {total}");
|
||||
stage.update(cx, |stage, _| stage.seen = total);
|
||||
if held.0.is_some() {
|
||||
match &self.sync {
|
||||
Some(sync) => sync.update(cx, |sync, _| sync.visible = Some(visible)),
|
||||
None => stage.update(cx, |stage, _| stage.visible = Some(visible)),
|
||||
}
|
||||
}
|
||||
}
|
||||
let range = visible.range(total);
|
||||
let shown = Rc::new(if self.kind == ChartKind::HeikinAshi {
|
||||
@@ -362,7 +374,7 @@ impl RenderOnce for CandlestickChart {
|
||||
range: range.clone(),
|
||||
hover,
|
||||
row,
|
||||
zone: self.zone.clone(),
|
||||
zone: zone.clone(),
|
||||
title: self.title.clone(),
|
||||
compare: self.compare.as_ref().map(|(name, _)| name.clone()),
|
||||
drawings: marks.clone(),
|
||||
|
||||
+12
-5
@@ -17,7 +17,10 @@ use crate::{
|
||||
ChartTooltip, Linear, Rect, Tiles, anchored, at, finish, measure, place, ring, tracked,
|
||||
},
|
||||
theme::{ActiveTheme, TextSize},
|
||||
typography::{format, tabular},
|
||||
typography::{
|
||||
format::{self, system_zone},
|
||||
tabular,
|
||||
},
|
||||
};
|
||||
|
||||
/// A mark on a price-only chart: its slot, the prices it spans, whether it rose, and its words.
|
||||
@@ -221,7 +224,7 @@ pub struct RenkoChart {
|
||||
id: ElementId,
|
||||
candles: Rc<Vec<Candle>>,
|
||||
size: f64,
|
||||
zone: TimeZone,
|
||||
zone: Option<TimeZone>,
|
||||
red_up: bool,
|
||||
}
|
||||
|
||||
@@ -236,7 +239,7 @@ impl RenkoChart {
|
||||
id: id.into(),
|
||||
candles: candles.into(),
|
||||
size,
|
||||
zone: TimeZone::system(),
|
||||
zone: None,
|
||||
red_up: false,
|
||||
}
|
||||
}
|
||||
@@ -248,7 +251,7 @@ impl RenkoChart {
|
||||
|
||||
/// The time zone its dates read in; the system's unless set.
|
||||
pub fn zone(mut self, zone: TimeZone) -> Self {
|
||||
self.zone = zone;
|
||||
self.zone = Some(zone);
|
||||
self
|
||||
}
|
||||
}
|
||||
@@ -261,13 +264,17 @@ impl Styled for RenkoChart {
|
||||
|
||||
impl RenderOnce for RenkoChart {
|
||||
fn render(mut self, window: &mut Window, cx: &mut App) -> impl IntoElement {
|
||||
let zone = self
|
||||
.zone
|
||||
.clone()
|
||||
.unwrap_or_else(|| system_zone("RenkoChart"));
|
||||
let marks = renko(&self.candles, self.size)
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.map(|(slot, brick)| {
|
||||
let day = self.candles[brick.at]
|
||||
.time
|
||||
.to_zoned(self.zone.clone())
|
||||
.to_zoned(zone.clone())
|
||||
.strftime("%b %-d, %Y")
|
||||
.to_string();
|
||||
let reading = format!(
|
||||
|
||||
@@ -26,6 +26,15 @@ impl Visible {
|
||||
.kept(total)
|
||||
}
|
||||
|
||||
/// Held to `total` candles after the data changed: no wider than they allow, and in reach of them.
|
||||
pub(crate) fn fitted(self, total: usize) -> Self {
|
||||
let count = self.count.clamp(
|
||||
CLOSEST.min(total as f64).max(1.0),
|
||||
(total as f64).max(1.0) * 1.1,
|
||||
);
|
||||
Self { count, ..self }.kept(total)
|
||||
}
|
||||
|
||||
/// Kept to what `total` candles allow: never before the first, at most a quarter window past the last.
|
||||
fn kept(self, total: usize) -> Self {
|
||||
let last = (total as f64 - self.count * 0.75).max(0.0);
|
||||
@@ -107,6 +116,21 @@ pub(crate) fn decimals(step: f64) -> usize {
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn a_window_fits_data_that_shrank_under_it() {
|
||||
let window = Visible {
|
||||
start: 150.0,
|
||||
count: 50.0,
|
||||
};
|
||||
let fitted = window.fitted(12);
|
||||
assert!(fitted.count <= 12.0 * 1.1);
|
||||
let range = fitted.range(12);
|
||||
assert!(
|
||||
!range.is_empty() && range.end == 12,
|
||||
"{range:?} shows the newest candles"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_window_maps_candles_across_its_frame() {
|
||||
let window = Visible {
|
||||
|
||||
+13
-8
@@ -9,12 +9,13 @@ use jiff::tz::TimeZone;
|
||||
use super::{
|
||||
book::{Side, Trade, cell, heading, level},
|
||||
quotes::{moves, price},
|
||||
stage::decimals,
|
||||
};
|
||||
use crate::{
|
||||
charts::compact,
|
||||
motion::Flash,
|
||||
theme::{ActiveTheme, Density, TextSize},
|
||||
typography::tabular,
|
||||
typography::{format::system_zone, tabular},
|
||||
};
|
||||
|
||||
/// Trades as they print, newest on top: the time, price and size tinted by the side that took them; trades of at least `large` stand bold, and the newest flashes in.
|
||||
@@ -25,7 +26,7 @@ pub struct TimeAndSales {
|
||||
large: f64,
|
||||
rows: usize,
|
||||
places: usize,
|
||||
zone: TimeZone,
|
||||
zone: Option<TimeZone>,
|
||||
red_up: bool,
|
||||
}
|
||||
|
||||
@@ -38,7 +39,7 @@ impl TimeAndSales {
|
||||
large: f64::MAX,
|
||||
rows: 12,
|
||||
places: 2,
|
||||
zone: TimeZone::system(),
|
||||
zone: None,
|
||||
red_up: false,
|
||||
}
|
||||
}
|
||||
@@ -55,7 +56,7 @@ impl TimeAndSales {
|
||||
}
|
||||
|
||||
pub fn zone(mut self, zone: TimeZone) -> Self {
|
||||
self.zone = zone;
|
||||
self.zone = Some(zone);
|
||||
self
|
||||
}
|
||||
|
||||
@@ -67,10 +68,13 @@ impl TimeAndSales {
|
||||
|
||||
impl RenderOnce for TimeAndSales {
|
||||
fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {
|
||||
let zone = self
|
||||
.zone
|
||||
.clone()
|
||||
.unwrap_or_else(|| system_zone("TimeAndSales"));
|
||||
let (rise, fall) = moves(self.red_up, cx);
|
||||
let theme = cx.theme();
|
||||
let colors = theme.colors.clone();
|
||||
let newest = self.trades.len();
|
||||
let rows = self
|
||||
.trades
|
||||
.iter()
|
||||
@@ -81,7 +85,7 @@ impl RenderOnce for TimeAndSales {
|
||||
let ink = if trade.side == Side::Buy { rise } else { fall };
|
||||
let time = trade
|
||||
.time
|
||||
.to_zoned(self.zone.clone())
|
||||
.to_zoned(zone.clone())
|
||||
.strftime("%H:%M:%S")
|
||||
.to_string();
|
||||
let row = div()
|
||||
@@ -104,7 +108,7 @@ impl RenderOnce for TimeAndSales {
|
||||
.child(cell(price(trade.price, self.places), ink))
|
||||
.child(cell(compact(trade.size), colors.fg));
|
||||
if ix == 0 {
|
||||
Flash::new((self.id.clone(), "newest"), newest)
|
||||
Flash::new((self.id.clone(), "newest"), *trade)
|
||||
.child(row)
|
||||
.into_any_element()
|
||||
} else {
|
||||
@@ -192,6 +196,7 @@ fn rungs(last: f64, tick: f64, rows: usize) -> Vec<f64> {
|
||||
|
||||
impl RenderOnce for DomLadder {
|
||||
fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {
|
||||
let places = self.places.max(decimals(self.tick));
|
||||
let (rise, fall) = moves(self.red_up, cx);
|
||||
let theme = cx.theme();
|
||||
let colors = theme.colors.clone();
|
||||
@@ -255,7 +260,7 @@ impl RenderOnce for DomLadder {
|
||||
.font_weight(FontWeight::SEMIBOLD)
|
||||
})
|
||||
.when(!marked, |price| price.text_color(colors.fg_muted))
|
||||
.child(price(at, self.places)),
|
||||
.child(price(at, places)),
|
||||
)
|
||||
.child(side_cell(ask, Side::Sell, fall))
|
||||
});
|
||||
|
||||
+13
-5
@@ -13,7 +13,11 @@ use crate::{
|
||||
forms::{Combobox, NumberInput, TextInput},
|
||||
primitives::{Icon, IconName},
|
||||
theme::{ActiveTheme, IconSize, Radius, TextSize},
|
||||
typography::{Ellipsis, format, tabular},
|
||||
typography::{
|
||||
Ellipsis,
|
||||
format::{self, system_zone},
|
||||
tabular,
|
||||
},
|
||||
};
|
||||
|
||||
type OnConvert = Rc<dyn Fn(f64, SharedString, SharedString, &mut Window, &mut App)>;
|
||||
@@ -309,7 +313,7 @@ pub struct Transfer {
|
||||
#[derive(IntoElement)]
|
||||
pub struct TransactionList {
|
||||
transfers: Vec<Transfer>,
|
||||
zone: TimeZone,
|
||||
zone: Option<TimeZone>,
|
||||
}
|
||||
|
||||
impl TransactionList {
|
||||
@@ -318,13 +322,13 @@ impl TransactionList {
|
||||
transfers.sort_by_key(|transfer| Reverse(transfer.time));
|
||||
Self {
|
||||
transfers,
|
||||
zone: TimeZone::system(),
|
||||
zone: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// The time zone its times read in; the system's unless set.
|
||||
pub fn zone(mut self, zone: TimeZone) -> Self {
|
||||
self.zone = zone;
|
||||
self.zone = Some(zone);
|
||||
self
|
||||
}
|
||||
}
|
||||
@@ -342,6 +346,10 @@ fn amount(value: f64, unit: &str) -> String {
|
||||
|
||||
impl RenderOnce for TransactionList {
|
||||
fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {
|
||||
let zone = self
|
||||
.zone
|
||||
.clone()
|
||||
.unwrap_or_else(|| system_zone("TransactionList"));
|
||||
let theme = cx.theme();
|
||||
let colors = theme.colors.clone();
|
||||
div()
|
||||
@@ -397,7 +405,7 @@ impl RenderOnce for TransactionList {
|
||||
.child(
|
||||
transfer
|
||||
.time
|
||||
.to_zoned(self.zone.clone())
|
||||
.to_zoned(zone.clone())
|
||||
.strftime("%b %-d, %H:%M")
|
||||
.to_string(),
|
||||
),
|
||||
|
||||
Reference in new issue
Block a user