Skip to main content

egui_charts/chart/rendering/axes/
currency.rs

1//! Currency selector above the price ticks.
2
3use crate::chart::rendering::axes::axis_font;
4use crate::theme::ThemeExt;
5use egui::{Align2, Pos2, Rect, Response, Sense, Stroke, Ui};
6
7/// Paints a currency button at the top of an axis column and returns its response and
8/// the host currency choice, if any. The host supplies available conversions and replaces
9/// the prices and instrument metadata after a choice; the renderer never converts prices.
10/// `unit` appends the host's price unit without converting prices. An empty currency shows only
11/// the unit and offers no conversion menu. Pass the response rectangle to price tick occlusion.
12pub fn render_axis_currency(
13    ui: &mut Ui,
14    axis: Rect,
15    currency: &str,
16    unit: Option<&str>,
17    choices: &[String],
18) -> (Response, Option<String>) {
19    let theme = ui.active_theme();
20    let header = match (currency.is_empty(), unit.filter(|unit| !unit.is_empty())) {
21        (false, Some(unit)) => format!("{currency} ยท {unit}"),
22        (true, Some(unit)) => unit.to_owned(),
23        _ => currency.to_owned(),
24    };
25    let font = axis_font(ui.ctx());
26    let galley = ui
27        .painter()
28        .layout_no_wrap(header.clone(), font.clone(), theme.chart.axis_text);
29    let width = galley.size().x + 16.0 + if currency.is_empty() { 0.0 } else { 10.0 };
30    let rect = Rect::from_min_max(
31        Pos2::new((axis.right() - width).max(axis.left()), axis.top()),
32        Pos2::new(axis.right(), (axis.top() + 24.0).min(axis.bottom())),
33    );
34    let response = ui.interact(
35        rect,
36        ui.scope_id().with(("axis_currency", axis.top().to_bits())),
37        Sense::click(),
38    );
39    response.widget_info(|| egui::WidgetInfo::labeled(egui::Role::Button, true, &header));
40    let painter = ui.painter().with_clip_rect(axis.intersect(ui.clip_rect()));
41    for offset in 0..18 {
42        let y = rect.bottom() + offset as f32;
43        painter.rect_filled(
44            Rect::from_min_max(Pos2::new(rect.left(), y), Pos2::new(rect.right(), y + 1.0)),
45            0.0,
46            theme.chart.bg.gamma_multiply(1.0 - offset as f32 / 18.0),
47        );
48    }
49    painter.rect_filled(rect, 0.0, theme.chart.bg);
50    painter.text(
51        Pos2::new(rect.left() + 8.0, rect.center().y),
52        Align2::LEFT_CENTER,
53        header,
54        font,
55        theme.chart.axis_text,
56    );
57    if !currency.is_empty() {
58        let x = rect.left() + 8.0 + galley.size().x + 4.0;
59        painter.add(egui::Shape::line(
60            vec![
61                Pos2::new(x, rect.center().y - 1.5),
62                Pos2::new(x + 3.0, rect.center().y + 1.5),
63                Pos2::new(x + 6.0, rect.center().y - 1.5),
64            ],
65            Stroke::new(1.0, theme.chart.axis_text),
66        ));
67    }
68    let mut chosen = None;
69    if !currency.is_empty() {
70        egui::Popup::menu(&response).show(|ui| {
71            let _ = ui.selectable_label(true, currency);
72            for choice in choices.iter().filter(|choice| choice.as_str() != currency) {
73                if ui.selectable_label(false, choice).clicked() {
74                    chosen = Some(choice.clone());
75                    ui.close();
76                }
77            }
78        });
79    }
80    (response, chosen)
81}