use eframe::egui;
use avin_connect::Tinkoff;
use avin_core::Action;
use crate::chart_widget::ChartWidget;
use crate::terminal::asset_widget::AssetWidget;
#[derive(serde::Deserialize, serde::Serialize)]
#[serde(default)]
pub struct Terminal {
#[serde(skip)]
asset_widget: AssetWidget,
chart_widget: ChartWidget,
#[serde(skip)]
is_active_mode: bool,
#[serde(skip)]
action_tx: tokio::sync::mpsc::UnboundedSender<Action>,
#[serde(skip)]
tokio_runtime: tokio::runtime::Runtime,
}
impl Default for Terminal {
fn default() -> Self {
let (event_tx, event_rx) = tokio::sync::mpsc::unbounded_channel();
let broker = Tinkoff::new(event_tx.clone());
let action_tx = broker.get_sender();
let tokio_runtime = tokio::runtime::Builder::new_multi_thread()
.enable_all()
.build()
.unwrap();
tokio_runtime.block_on(async {
start_broker(broker).await;
});
Self {
asset_widget: AssetWidget::new(event_rx, action_tx.clone()),
chart_widget: ChartWidget::default(),
is_active_mode: false,
action_tx,
tokio_runtime,
}
}
}
impl Terminal {
pub fn new(cc: &eframe::CreationContext) -> Self {
if let Some(storage) = cc.storage {
return eframe::get_value(storage, eframe::APP_KEY)
.unwrap_or_default();
}
Terminal::default()
}
}
impl eframe::App for Terminal {
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
egui_extras::install_image_loaders(ctx);
ui_top(self, ctx);
ui_left(self, ctx);
ui_center(self, ctx);
if self.is_active_mode {
ctx.request_repaint();
}
}
fn save(&mut self, storage: &mut dyn eframe::Storage) {
eframe::set_value(storage, eframe::APP_KEY, self);
}
}
fn ui_top(app: &mut Terminal, ctx: &egui::Context) {
egui::TopBottomPanel::top("top_panel").show(ctx, |ui| {
egui::menu::bar(ui, |ui| {
ui.menu_button("File", |ui| {
if ui.button("Quit").clicked() {
ctx.send_viewport_cmd(egui::ViewportCommand::Close);
}
});
ui.add_space(16.0);
egui::widgets::global_theme_preference_buttons(ui);
ui.separator();
ui.label("Active:");
ui.add(toggle(&mut app.is_active_mode));
});
});
}
fn ui_left(app: &mut Terminal, ctx: &egui::Context) {
egui::SidePanel::left("left_panel").show(ctx, |ui| {
app.asset_widget.ui(ctx, ui);
});
}
fn ui_center(app: &mut Terminal, ctx: &egui::Context) {
egui::CentralPanel::default().show(ctx, |ui| {
let asset = app.asset_widget.current_asset();
app.chart_widget.ui(ui, asset);
});
}
pub fn toggle_ui(ui: &mut egui::Ui, on: &mut bool) -> egui::Response {
let desired_size = ui.spacing().interact_size.y * egui::vec2(2.0, 1.0);
let (rect, mut response) =
ui.allocate_exact_size(desired_size, egui::Sense::click());
if response.clicked() {
*on = !*on;
response.mark_changed(); }
response.widget_info(|| {
egui::WidgetInfo::selected(
egui::WidgetType::Checkbox,
ui.is_enabled(),
*on,
"",
)
});
if ui.is_rect_visible(rect) {
let how_on = ui.ctx().animate_bool_responsive(response.id, *on);
let visuals = ui.style().interact_selectable(&response, *on);
let rect = rect.expand(visuals.expansion);
let radius = 0.5 * rect.height();
ui.painter().rect(
rect,
radius,
visuals.bg_fill,
visuals.bg_stroke,
egui::StrokeKind::Inside,
);
let circle_x = egui::lerp(
(rect.left() + radius)..=(rect.right() - radius),
how_on,
);
let center = egui::pos2(circle_x, rect.center().y);
ui.painter().circle(
center,
0.75 * radius,
visuals.bg_fill,
visuals.fg_stroke,
);
}
response
}
pub fn toggle(on: &mut bool) -> impl egui::Widget + '_ {
move |ui: &mut egui::Ui| toggle_ui(ui, on)
}
async fn start_broker(mut broker: Tinkoff) {
broker.connect().await.unwrap();
log::debug!(":: Broker connected!");
broker.create_marketdata_stream().await.unwrap();
log::debug!(":: Data stream started!");
broker.create_transactions_stream().await.unwrap();
log::debug!(":: Transaction stream started!");
tokio::spawn(async move {
broker.start().await;
});
log::debug!(":: Broker started!");
}