150 lines
4.3 KiB
Rust
150 lines
4.3 KiB
Rust
use gpui::{App, IntoElement, ParentElement, RenderOnce, Styled, Window, div, relative};
|
|
|
|
use super::quotes::{moves, price};
|
|
use crate::{
|
|
theme::{ActiveTheme, Radius, TextSize},
|
|
typography::{format, tabular},
|
|
};
|
|
|
|
/// The best bid and ask, and the spread between them in price and in basis points.
|
|
#[derive(IntoElement)]
|
|
pub struct SpreadIndicator {
|
|
bid: f64,
|
|
ask: f64,
|
|
places: usize,
|
|
red_up: bool,
|
|
}
|
|
|
|
impl SpreadIndicator {
|
|
pub fn new(bid: f64, ask: f64) -> Self {
|
|
assert!(
|
|
bid.is_finite() && ask.is_finite(),
|
|
"a spread needs finite prices"
|
|
);
|
|
Self {
|
|
bid,
|
|
ask,
|
|
places: 2,
|
|
red_up: false,
|
|
}
|
|
}
|
|
|
|
pub fn places(mut self, places: usize) -> Self {
|
|
self.places = places;
|
|
self
|
|
}
|
|
|
|
pub fn red_up(mut self) -> Self {
|
|
self.red_up = true;
|
|
self
|
|
}
|
|
}
|
|
|
|
impl RenderOnce for SpreadIndicator {
|
|
fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {
|
|
let (rise, fall) = moves(self.red_up, cx);
|
|
let theme = cx.theme();
|
|
let colors = &theme.colors;
|
|
let side = |name: &'static str, value: f64, ink| {
|
|
div()
|
|
.flex()
|
|
.items_baseline()
|
|
.gap_1p5()
|
|
.child(div().text_color(colors.fg_subtle).child(name))
|
|
.child(
|
|
tabular(div())
|
|
.text_color(ink)
|
|
.child(price(value, self.places)),
|
|
)
|
|
};
|
|
let spread = self.ask - self.bid;
|
|
let points = spread / ((self.ask + self.bid) / 2.0) * 10_000.0;
|
|
div()
|
|
.flex()
|
|
.items_center()
|
|
.gap_4()
|
|
.text_size(theme.text_size(TextSize::Sm))
|
|
.child(side("Bid", self.bid, rise))
|
|
.child(
|
|
tabular(div())
|
|
.px_2()
|
|
.py_0p5()
|
|
.rounded(theme.radius(Radius::Sm))
|
|
.bg(colors.hover)
|
|
.text_color(colors.fg_muted)
|
|
.child(format!("{} · {points:.1} bps", price(spread, self.places))),
|
|
)
|
|
.child(side("Ask", self.ask, fall))
|
|
}
|
|
}
|
|
|
|
/// The weight of waiting orders on each side as one bar split where they balance.
|
|
#[derive(IntoElement)]
|
|
pub struct BidAskBar {
|
|
bids: f64,
|
|
asks: f64,
|
|
red_up: bool,
|
|
}
|
|
|
|
impl BidAskBar {
|
|
/// The size waiting on each side.
|
|
pub fn new(bids: f64, asks: f64) -> Self {
|
|
assert!(bids >= 0.0 && asks >= 0.0, "sizes are zero or more");
|
|
Self {
|
|
bids,
|
|
asks,
|
|
red_up: false,
|
|
}
|
|
}
|
|
|
|
pub fn red_up(mut self) -> Self {
|
|
self.red_up = true;
|
|
self
|
|
}
|
|
}
|
|
|
|
impl RenderOnce for BidAskBar {
|
|
fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {
|
|
let (rise, fall) = moves(self.red_up, cx);
|
|
let theme = cx.theme();
|
|
let total = self.bids + self.asks;
|
|
let share = if total > 0.0 { self.bids / total } else { 0.5 };
|
|
let words = |name: &'static str, value: f64| {
|
|
let shown = match total > 0.0 {
|
|
true => format::percent(value, 0, false),
|
|
false => "—".to_string(),
|
|
};
|
|
tabular(div())
|
|
.text_color(theme.colors.fg_muted)
|
|
.child(format!("{name} {shown}"))
|
|
};
|
|
let part = |grow: f64, ink| {
|
|
let mut part = div().h_full().flex_basis(relative(0.0)).bg(ink);
|
|
part.style().flex_grow = Some(grow as f32);
|
|
part
|
|
};
|
|
div()
|
|
.flex()
|
|
.flex_col()
|
|
.gap_1()
|
|
.text_size(theme.text_size(TextSize::Xs))
|
|
.child(
|
|
div()
|
|
.flex()
|
|
.justify_between()
|
|
.child(words("Bids", share))
|
|
.child(words("Asks", 1.0 - share)),
|
|
)
|
|
.child(
|
|
div()
|
|
.flex()
|
|
.gap_0p5()
|
|
.h(theme.meter_track())
|
|
.rounded_full()
|
|
.overflow_hidden()
|
|
.child(part(share, rise).rounded_l_full())
|
|
.child(part(1.0 - share, fall).rounded_r_full()),
|
|
)
|
|
}
|
|
}
|