|
@@ -6,34 +6,30 @@ mod style;
|
|
|
mod screen;
|
|
mod screen;
|
|
|
mod logger;
|
|
mod logger;
|
|
|
|
|
|
|
|
-use hyper::client::conn;
|
|
|
|
|
use style::{ICON_FONT, ICON_BYTES, Icon};
|
|
use style::{ICON_FONT, ICON_BYTES, Icon};
|
|
|
|
|
|
|
|
-use screen::{Notification, Error};
|
|
|
|
|
|
|
+use screen::{dashboard, Error, Notification};
|
|
|
use screen::dashboard::{
|
|
use screen::dashboard::{
|
|
|
Dashboard,
|
|
Dashboard,
|
|
|
- pane::{self, SerializablePane}, Uuid, LayoutId,
|
|
|
|
|
|
|
+ pane::{self, SerializablePane}, Uuid,
|
|
|
PaneContent, PaneSettings, PaneState,
|
|
PaneContent, PaneSettings, PaneState,
|
|
|
- SavedState, SerializableDashboard, SerializableState,
|
|
|
|
|
- read_layout_from_file, write_json_to_file,
|
|
|
|
|
|
|
+ SerializableDashboard,
|
|
|
};
|
|
};
|
|
|
-use data_providers::{binance, bybit, Exchange, MarketEvents, TickMultiplier, Ticker, Timeframe, StreamType};
|
|
|
|
|
|
|
+use data_providers::{binance, bybit, Exchange, MarketEvents, Ticker, Timeframe, StreamType};
|
|
|
|
|
|
|
|
use charts::footprint::FootprintChart;
|
|
use charts::footprint::FootprintChart;
|
|
|
use charts::heatmap::HeatmapChart;
|
|
use charts::heatmap::HeatmapChart;
|
|
|
use charts::candlestick::CandlestickChart;
|
|
use charts::candlestick::CandlestickChart;
|
|
|
use charts::timeandsales::TimeAndSales;
|
|
use charts::timeandsales::TimeAndSales;
|
|
|
|
|
|
|
|
-use futures::TryFutureExt;
|
|
|
|
|
-
|
|
|
|
|
-use std::{collections::{HashMap, HashSet, VecDeque}, vec};
|
|
|
|
|
|
|
+use std::{collections::{HashMap, VecDeque}, vec};
|
|
|
|
|
|
|
|
use iced::{
|
|
use iced::{
|
|
|
alignment, widget::{
|
|
alignment, widget::{
|
|
|
button, center, checkbox, mouse_area, opaque, pick_list, stack, tooltip, Column, Container, Row, Slider, Space, Text
|
|
button, center, checkbox, mouse_area, opaque, pick_list, stack, tooltip, Column, Container, Row, Slider, Space, Text
|
|
|
- }, window::{self, Position}, Alignment, Color, Element, Length, Point, Renderer, Size, Subscription, Task, Theme
|
|
|
|
|
|
|
+ }, window::{self, Position}, Alignment, Color, Element, Length, Point, Size, Subscription, Task, Theme
|
|
|
};
|
|
};
|
|
|
-use iced::widget::pane_grid::{self, PaneGrid, Configuration};
|
|
|
|
|
|
|
+use iced::widget::pane_grid::{self, Configuration};
|
|
|
use iced::widget::{container, row, scrollable, text};
|
|
use iced::widget::{container, row, scrollable, text};
|
|
|
|
|
|
|
|
fn main() -> iced::Result {
|
|
fn main() -> iced::Result {
|
|
@@ -180,65 +176,31 @@ fn main() -> iced::Result {
|
|
|
|
|
|
|
|
#[derive(Debug, Clone)]
|
|
#[derive(Debug, Clone)]
|
|
|
pub enum Message {
|
|
pub enum Message {
|
|
|
- FetchDistributeKlines(StreamType, Result<Vec<data_providers::Kline>, std::string::String>),
|
|
|
|
|
- FetchDistributeTicks(StreamType, Result<f32, std::string::String>),
|
|
|
|
|
Debug(String),
|
|
Debug(String),
|
|
|
Notification(Notification),
|
|
Notification(Notification),
|
|
|
ErrorOccurred(Error),
|
|
ErrorOccurred(Error),
|
|
|
ClearNotification,
|
|
ClearNotification,
|
|
|
|
|
|
|
|
- ChartUserUpdate(charts::Message, Uuid),
|
|
|
|
|
ShowLayoutModal,
|
|
ShowLayoutModal,
|
|
|
HideLayoutModal,
|
|
HideLayoutModal,
|
|
|
|
|
|
|
|
- // Market&User data stream
|
|
|
|
|
- UserKeySucceed(String),
|
|
|
|
|
- UserKeyError,
|
|
|
|
|
- TickerSelected(Ticker, Uuid),
|
|
|
|
|
- ExchangeSelected(Exchange, Uuid),
|
|
|
|
|
MarketWsEvent(MarketEvents),
|
|
MarketWsEvent(MarketEvents),
|
|
|
- FetchEvent(Result<Vec<data_providers::Kline>, std::string::String>, StreamType, Uuid),
|
|
|
|
|
-
|
|
|
|
|
- // Pane grid
|
|
|
|
|
- Split(pane_grid::Axis, pane_grid::Pane),
|
|
|
|
|
- Clicked(pane_grid::Pane),
|
|
|
|
|
- Dragged(pane_grid::DragEvent),
|
|
|
|
|
- Resized(pane_grid::ResizeEvent),
|
|
|
|
|
- Maximize(pane_grid::Pane),
|
|
|
|
|
- Restore,
|
|
|
|
|
- Close(pane_grid::Pane),
|
|
|
|
|
- ToggleLayoutLock,
|
|
|
|
|
- PaneContentSelected(String, Uuid, Vec<StreamType>),
|
|
|
|
|
- ReplacePane(pane_grid::Pane),
|
|
|
|
|
-
|
|
|
|
|
- // Modal
|
|
|
|
|
- OpenModal(pane_grid::Pane),
|
|
|
|
|
- CloseModal(Uuid),
|
|
|
|
|
-
|
|
|
|
|
- // Slider
|
|
|
|
|
- SliderChanged(Uuid, f32),
|
|
|
|
|
- SyncWithHeatmap(bool),
|
|
|
|
|
-
|
|
|
|
|
- // Chart settings
|
|
|
|
|
- TicksizeSelected(TickMultiplier, Uuid),
|
|
|
|
|
- TimeframeSelected(Timeframe, Uuid),
|
|
|
|
|
- SetMinTickSize(f32, Uuid),
|
|
|
|
|
|
|
|
|
|
Event(Event),
|
|
Event(Event),
|
|
|
-
|
|
|
|
|
SaveAndExit(window::Id, Option<Size>, Option<Point>),
|
|
SaveAndExit(window::Id, Option<Size>, Option<Point>),
|
|
|
|
|
|
|
|
|
|
+ ToggleLayoutLock,
|
|
|
ResetCurrentLayout,
|
|
ResetCurrentLayout,
|
|
|
LayoutSelected(LayoutId),
|
|
LayoutSelected(LayoutId),
|
|
|
|
|
+ Dashboard(dashboard::Message),
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
struct State {
|
|
struct State {
|
|
|
layouts: HashMap<LayoutId, Dashboard>,
|
|
layouts: HashMap<LayoutId, Dashboard>,
|
|
|
last_active_layout: LayoutId,
|
|
last_active_layout: LayoutId,
|
|
|
|
|
+ show_layout_modal: bool,
|
|
|
exchange_latency: Option<(u32, u32)>,
|
|
exchange_latency: Option<(u32, u32)>,
|
|
|
- listen_key: Option<String>,
|
|
|
|
|
feed_latency_cache: VecDeque<data_providers::FeedLatency>,
|
|
feed_latency_cache: VecDeque<data_providers::FeedLatency>,
|
|
|
- pane_streams: HashMap<Exchange, HashMap<Ticker, HashSet<StreamType>>>,
|
|
|
|
|
notification: Option<Notification>,
|
|
notification: Option<Notification>,
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -246,30 +208,21 @@ impl State {
|
|
|
fn new(saved_state: SavedState) -> (Self, Task<Message>) {
|
|
fn new(saved_state: SavedState) -> (Self, Task<Message>) {
|
|
|
let mut tasks = vec![];
|
|
let mut tasks = vec![];
|
|
|
|
|
|
|
|
- let mut pane_streams = HashMap::new();
|
|
|
|
|
-
|
|
|
|
|
let last_active_layout = saved_state.last_active_layout;
|
|
let last_active_layout = saved_state.last_active_layout;
|
|
|
- let dashboard = saved_state.layouts.get(&last_active_layout);
|
|
|
|
|
-
|
|
|
|
|
- if let Some(dashboard) = dashboard {
|
|
|
|
|
- let sleep_and_fetch = Task::perform(
|
|
|
|
|
- async { tokio::time::sleep(tokio::time::Duration::from_millis(200)).await; },
|
|
|
|
|
- move |_| Message::LayoutSelected(last_active_layout)
|
|
|
|
|
- );
|
|
|
|
|
-
|
|
|
|
|
- tasks.push(sleep_and_fetch);
|
|
|
|
|
|
|
|
|
|
- pane_streams = dashboard.get_all_diff_streams();
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ let wait_and_fetch = Task::perform(
|
|
|
|
|
+ async { tokio::time::sleep(tokio::time::Duration::from_millis(200)).await; },
|
|
|
|
|
+ move |_| Message::LayoutSelected(last_active_layout)
|
|
|
|
|
+ );
|
|
|
|
|
+ tasks.push(wait_and_fetch);
|
|
|
|
|
|
|
|
(
|
|
(
|
|
|
Self {
|
|
Self {
|
|
|
layouts: saved_state.layouts,
|
|
layouts: saved_state.layouts,
|
|
|
last_active_layout,
|
|
last_active_layout,
|
|
|
- listen_key: None,
|
|
|
|
|
|
|
+ show_layout_modal: false,
|
|
|
exchange_latency: None,
|
|
exchange_latency: None,
|
|
|
feed_latency_cache: VecDeque::new(),
|
|
feed_latency_cache: VecDeque::new(),
|
|
|
- pane_streams,
|
|
|
|
|
notification: None,
|
|
notification: None,
|
|
|
},
|
|
},
|
|
|
Task::batch(tasks)
|
|
Task::batch(tasks)
|
|
@@ -278,168 +231,6 @@ impl State {
|
|
|
|
|
|
|
|
fn update(&mut self, message: Message) -> Task<Message> {
|
|
fn update(&mut self, message: Message) -> Task<Message> {
|
|
|
match message {
|
|
match message {
|
|
|
- Message::ChartUserUpdate(message, pane_id) => {
|
|
|
|
|
- let dashboard = self.get_mut_dashboard();
|
|
|
|
|
-
|
|
|
|
|
- match dashboard.update_chart_state(pane_id, message) {
|
|
|
|
|
- Ok(_) => Task::none(),
|
|
|
|
|
- Err(err) => {
|
|
|
|
|
- Task::perform(
|
|
|
|
|
- async { err },
|
|
|
|
|
- move |err: Error| Message::ErrorOccurred(err)
|
|
|
|
|
- )
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- },
|
|
|
|
|
- Message::SetMinTickSize(min_tick_size, pane_id) => {
|
|
|
|
|
- let dashboard = self.get_mut_dashboard();
|
|
|
|
|
-
|
|
|
|
|
- match dashboard.get_pane_settings_mut(pane_id) {
|
|
|
|
|
- Ok(pane_settings) => {
|
|
|
|
|
- pane_settings.min_tick_size = Some(min_tick_size);
|
|
|
|
|
-
|
|
|
|
|
- Task::none()
|
|
|
|
|
- },
|
|
|
|
|
- Err(err) => {
|
|
|
|
|
- Task::perform(
|
|
|
|
|
- async { err },
|
|
|
|
|
- move |err: Error| Message::ErrorOccurred(err)
|
|
|
|
|
- )
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- },
|
|
|
|
|
- Message::TickerSelected(ticker, pane_id) => {
|
|
|
|
|
- let dashboard = self.get_mut_dashboard();
|
|
|
|
|
-
|
|
|
|
|
- match dashboard.get_pane_settings_mut(pane_id) {
|
|
|
|
|
- Ok(pane_settings) => {
|
|
|
|
|
- pane_settings.selected_ticker = Some(ticker);
|
|
|
|
|
-
|
|
|
|
|
- Task::none()
|
|
|
|
|
- },
|
|
|
|
|
- Err(err) => {
|
|
|
|
|
- Task::perform(
|
|
|
|
|
- async { err },
|
|
|
|
|
- move |err: Error| Message::ErrorOccurred(err)
|
|
|
|
|
- )
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- },
|
|
|
|
|
- Message::TimeframeSelected(timeframe, pane_id) => {
|
|
|
|
|
- let mut tasks = vec![];
|
|
|
|
|
-
|
|
|
|
|
- let dashboard = self.get_mut_dashboard();
|
|
|
|
|
-
|
|
|
|
|
- match dashboard.set_pane_timeframe(pane_id, timeframe) {
|
|
|
|
|
- Ok(stream_type) => {
|
|
|
|
|
- if let StreamType::Kline { exchange, ticker, timeframe } = stream_type {
|
|
|
|
|
- let stream = *stream_type;
|
|
|
|
|
-
|
|
|
|
|
- match exchange {
|
|
|
|
|
- Exchange::BinanceFutures => {
|
|
|
|
|
- tasks.push(
|
|
|
|
|
- Task::perform(
|
|
|
|
|
- binance::market_data::fetch_klines(*ticker, *timeframe)
|
|
|
|
|
- .map_err(|err| format!("{err}")),
|
|
|
|
|
- move |klines| Message::FetchEvent(klines, stream, pane_id)
|
|
|
|
|
- )
|
|
|
|
|
- );
|
|
|
|
|
- },
|
|
|
|
|
- Exchange::BybitLinear => {
|
|
|
|
|
- tasks.push(
|
|
|
|
|
- Task::perform(
|
|
|
|
|
- bybit::market_data::fetch_klines(*ticker, *timeframe)
|
|
|
|
|
- .map_err(|err| format!("{err}")),
|
|
|
|
|
- move |klines| Message::FetchEvent(klines, stream, pane_id)
|
|
|
|
|
- )
|
|
|
|
|
- );
|
|
|
|
|
- },
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- tasks.push(
|
|
|
|
|
- Task::perform(
|
|
|
|
|
- async {},
|
|
|
|
|
- move |_| Message::Notification(Notification::Info("Fetching for klines...".to_string()))
|
|
|
|
|
- )
|
|
|
|
|
- );
|
|
|
|
|
-
|
|
|
|
|
- self.pane_streams = dashboard.get_all_diff_streams();
|
|
|
|
|
- }
|
|
|
|
|
- },
|
|
|
|
|
- Err(err) => {
|
|
|
|
|
- tasks.push(Task::perform(
|
|
|
|
|
- async { err },
|
|
|
|
|
- move |err: Error| Message::ErrorOccurred(err)
|
|
|
|
|
- ));
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- Task::batch(tasks)
|
|
|
|
|
- },
|
|
|
|
|
- Message::ExchangeSelected(exchange, pane_id) => {
|
|
|
|
|
- let dashboard = self.get_mut_dashboard();
|
|
|
|
|
-
|
|
|
|
|
- match dashboard.get_pane_settings_mut(pane_id) {
|
|
|
|
|
- Ok(pane_settings) => {
|
|
|
|
|
- pane_settings.selected_exchange = Some(exchange);
|
|
|
|
|
-
|
|
|
|
|
- Task::none()
|
|
|
|
|
- },
|
|
|
|
|
- Err(err) => {
|
|
|
|
|
- Task::perform(
|
|
|
|
|
- async { err },
|
|
|
|
|
- move |err: Error| Message::ErrorOccurred(err)
|
|
|
|
|
- )
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- },
|
|
|
|
|
- Message::TicksizeSelected(tick_multiply, pane_id) => {
|
|
|
|
|
- let dashboard = self.get_mut_dashboard();
|
|
|
|
|
-
|
|
|
|
|
- match dashboard.set_pane_ticksize(pane_id, tick_multiply) {
|
|
|
|
|
- Ok(_) => {
|
|
|
|
|
- Task::none()
|
|
|
|
|
- },
|
|
|
|
|
- Err(err) => {
|
|
|
|
|
- Task::perform(
|
|
|
|
|
- async { err },
|
|
|
|
|
- move |err: Error| Message::ErrorOccurred(err)
|
|
|
|
|
- )
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- },
|
|
|
|
|
- Message::FetchEvent(klines, pane_stream, pane_id) => {
|
|
|
|
|
- if let Some(notification) = &self.notification {
|
|
|
|
|
- match notification {
|
|
|
|
|
- Notification::Info(_) => {
|
|
|
|
|
- self.notification = None;
|
|
|
|
|
- },
|
|
|
|
|
- _ => {}
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- let dashboard = self.get_mut_dashboard();
|
|
|
|
|
-
|
|
|
|
|
- match klines {
|
|
|
|
|
- Ok(klines) => {
|
|
|
|
|
- if let StreamType::Kline { .. } = pane_stream {
|
|
|
|
|
- dashboard.insert_klines_vec(&pane_stream, &klines, pane_id);
|
|
|
|
|
-
|
|
|
|
|
- Task::none()
|
|
|
|
|
- } else {
|
|
|
|
|
- log::error!("Invalid stream type for klines: {pane_stream:?}");
|
|
|
|
|
-
|
|
|
|
|
- Task::none()
|
|
|
|
|
- }
|
|
|
|
|
- },
|
|
|
|
|
- Err(err) => {
|
|
|
|
|
- Task::perform(
|
|
|
|
|
- async { err },
|
|
|
|
|
- move |err: String| Message::ErrorOccurred(Error::FetchError(err))
|
|
|
|
|
- )
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- },
|
|
|
|
|
Message::MarketWsEvent(event) => {
|
|
Message::MarketWsEvent(event) => {
|
|
|
let dashboard = self.get_mut_dashboard();
|
|
let dashboard = self.get_mut_dashboard();
|
|
|
|
|
|
|
@@ -459,13 +250,6 @@ impl State {
|
|
|
|
|
|
|
|
if let Err(err) = dashboard.update_depth_and_trades(stream_type, depth_update_t, depth, trades_buffer) {
|
|
if let Err(err) = dashboard.update_depth_and_trades(stream_type, depth_update_t, depth, trades_buffer) {
|
|
|
log::error!("{err}, {stream_type:?}");
|
|
log::error!("{err}, {stream_type:?}");
|
|
|
-
|
|
|
|
|
- self.pane_streams
|
|
|
|
|
- .entry(Exchange::BinanceFutures)
|
|
|
|
|
- .or_default()
|
|
|
|
|
- .entry(ticker)
|
|
|
|
|
- .or_default()
|
|
|
|
|
- .remove(&stream_type);
|
|
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
binance::market_data::Event::KlineReceived(ticker, kline, timeframe) => {
|
|
binance::market_data::Event::KlineReceived(ticker, kline, timeframe) => {
|
|
@@ -477,13 +261,6 @@ impl State {
|
|
|
|
|
|
|
|
if let Err(err) = dashboard.update_latest_klines(&stream_type, &kline) {
|
|
if let Err(err) = dashboard.update_latest_klines(&stream_type, &kline) {
|
|
|
log::error!("{err}, {stream_type:?}");
|
|
log::error!("{err}, {stream_type:?}");
|
|
|
-
|
|
|
|
|
- self.pane_streams
|
|
|
|
|
- .entry(Exchange::BinanceFutures)
|
|
|
|
|
- .or_default()
|
|
|
|
|
- .entry(ticker)
|
|
|
|
|
- .or_default()
|
|
|
|
|
- .remove(&stream_type);
|
|
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
},
|
|
},
|
|
@@ -502,13 +279,6 @@ impl State {
|
|
|
|
|
|
|
|
if let Err(err) = dashboard.update_depth_and_trades(stream_type, depth_update_t, depth, trades_buffer) {
|
|
if let Err(err) = dashboard.update_depth_and_trades(stream_type, depth_update_t, depth, trades_buffer) {
|
|
|
log::error!("{err}, {stream_type:?}");
|
|
log::error!("{err}, {stream_type:?}");
|
|
|
-
|
|
|
|
|
- self.pane_streams
|
|
|
|
|
- .entry(Exchange::BybitLinear)
|
|
|
|
|
- .or_default()
|
|
|
|
|
- .entry(ticker)
|
|
|
|
|
- .or_default()
|
|
|
|
|
- .remove(&stream_type);
|
|
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
bybit::market_data::Event::KlineReceived(ticker, kline, timeframe) => {
|
|
bybit::market_data::Event::KlineReceived(ticker, kline, timeframe) => {
|
|
@@ -520,13 +290,6 @@ impl State {
|
|
|
|
|
|
|
|
if let Err(err) = dashboard.update_latest_klines(&stream_type, &kline) {
|
|
if let Err(err) = dashboard.update_latest_klines(&stream_type, &kline) {
|
|
|
log::error!("{err}, {stream_type:?}");
|
|
log::error!("{err}, {stream_type:?}");
|
|
|
-
|
|
|
|
|
- self.pane_streams
|
|
|
|
|
- .entry(Exchange::BybitLinear)
|
|
|
|
|
- .or_default()
|
|
|
|
|
- .entry(ticker)
|
|
|
|
|
- .or_default()
|
|
|
|
|
- .remove(&stream_type);
|
|
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
},
|
|
},
|
|
@@ -534,82 +297,10 @@ impl State {
|
|
|
|
|
|
|
|
Task::none()
|
|
Task::none()
|
|
|
},
|
|
},
|
|
|
- Message::UserKeySucceed(listen_key) => {
|
|
|
|
|
- self.listen_key = Some(listen_key);
|
|
|
|
|
- Task::none()
|
|
|
|
|
- },
|
|
|
|
|
- Message::UserKeyError => {
|
|
|
|
|
- log::error!("Check API keys");
|
|
|
|
|
- Task::none()
|
|
|
|
|
- },
|
|
|
|
|
- Message::Split(axis, pane) => {
|
|
|
|
|
- let dashboard = self.get_mut_dashboard();
|
|
|
|
|
-
|
|
|
|
|
- let focus_pane = if let Some((new_pane, _)) =
|
|
|
|
|
- dashboard.panes.split(axis, pane, PaneState::new(Uuid::new_v4(), vec![], PaneSettings::default())) {
|
|
|
|
|
- Some(new_pane)
|
|
|
|
|
- } else {
|
|
|
|
|
- None
|
|
|
|
|
- };
|
|
|
|
|
-
|
|
|
|
|
- if Some(focus_pane).is_some() {
|
|
|
|
|
- dashboard.focus = focus_pane;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- Task::none()
|
|
|
|
|
- },
|
|
|
|
|
- Message::Clicked(pane) => {
|
|
|
|
|
- let dashboard = self.get_mut_dashboard();
|
|
|
|
|
-
|
|
|
|
|
- dashboard.focus = Some(pane);
|
|
|
|
|
- Task::none()
|
|
|
|
|
- },
|
|
|
|
|
- Message::Resized(pane_grid::ResizeEvent { split, ratio }) => {
|
|
|
|
|
- let dashboard = self.get_mut_dashboard();
|
|
|
|
|
-
|
|
|
|
|
- dashboard.panes.resize(split, ratio);
|
|
|
|
|
- Task::none()
|
|
|
|
|
- },
|
|
|
|
|
- Message::Dragged(pane_grid::DragEvent::Dropped {
|
|
|
|
|
- pane,
|
|
|
|
|
- target,
|
|
|
|
|
- }) => {
|
|
|
|
|
- let dashboard = self.get_mut_dashboard();
|
|
|
|
|
-
|
|
|
|
|
- dashboard.panes.drop(pane, target);
|
|
|
|
|
-
|
|
|
|
|
- dashboard.focus = None;
|
|
|
|
|
-
|
|
|
|
|
- Task::none()
|
|
|
|
|
- },
|
|
|
|
|
- Message::Dragged(_) => {
|
|
|
|
|
- Task::none()
|
|
|
|
|
- },
|
|
|
|
|
- Message::Maximize(pane) => {
|
|
|
|
|
- let dashboard = self.get_mut_dashboard();
|
|
|
|
|
-
|
|
|
|
|
- dashboard.panes.maximize(pane);
|
|
|
|
|
- Task::none()
|
|
|
|
|
- },
|
|
|
|
|
- Message::Restore => {
|
|
|
|
|
- let dashboard = self.get_mut_dashboard();
|
|
|
|
|
-
|
|
|
|
|
- dashboard.panes.restore();
|
|
|
|
|
- Task::none()
|
|
|
|
|
- },
|
|
|
|
|
- Message::Close(pane) => {
|
|
|
|
|
- let dashboard = self.get_mut_dashboard();
|
|
|
|
|
-
|
|
|
|
|
- if let Some((_, sibling)) = dashboard.panes.close(pane) {
|
|
|
|
|
- dashboard.focus = Some(sibling);
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- Task::none()
|
|
|
|
|
- },
|
|
|
|
|
Message::ToggleLayoutLock => {
|
|
Message::ToggleLayoutLock => {
|
|
|
let dashboard = self.get_mut_dashboard();
|
|
let dashboard = self.get_mut_dashboard();
|
|
|
|
|
|
|
|
- dashboard.pane_lock = !dashboard.pane_lock;
|
|
|
|
|
|
|
+ dashboard.layout_lock = !dashboard.layout_lock;
|
|
|
|
|
|
|
|
dashboard.focus = None;
|
|
dashboard.focus = None;
|
|
|
|
|
|
|
@@ -676,59 +367,12 @@ impl State {
|
|
|
|
|
|
|
|
window::close(window)
|
|
window::close(window)
|
|
|
},
|
|
},
|
|
|
- Message::OpenModal(pane) => {
|
|
|
|
|
- let dashboard = self.get_mut_dashboard();
|
|
|
|
|
-
|
|
|
|
|
- if let Some(pane) = dashboard.panes.get_mut(pane) {
|
|
|
|
|
- pane.show_modal = true;
|
|
|
|
|
- };
|
|
|
|
|
- Task::none()
|
|
|
|
|
- },
|
|
|
|
|
- Message::CloseModal(pane_id) => {
|
|
|
|
|
- let dashboard = self.get_mut_dashboard();
|
|
|
|
|
-
|
|
|
|
|
- for (_, pane_state) in dashboard.panes.iter_mut() {
|
|
|
|
|
- if pane_state.id == pane_id {
|
|
|
|
|
- pane_state.show_modal = false;
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- Task::none()
|
|
|
|
|
- },
|
|
|
|
|
- Message::SliderChanged(pane_id, value) => {
|
|
|
|
|
- let dashboard = self.get_mut_dashboard();
|
|
|
|
|
-
|
|
|
|
|
- match dashboard.set_pane_size_filter(pane_id, value) {
|
|
|
|
|
- Ok(_) => {
|
|
|
|
|
- log::info!("Size filter set to {value}");
|
|
|
|
|
-
|
|
|
|
|
- Task::none()
|
|
|
|
|
- }
|
|
|
|
|
- Err(err) => {
|
|
|
|
|
- Task::perform(
|
|
|
|
|
- async { err },
|
|
|
|
|
- move |err: Error| Message::ErrorOccurred(err)
|
|
|
|
|
- )
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- },
|
|
|
|
|
- Message::SyncWithHeatmap(sync) => {
|
|
|
|
|
- Task::perform(
|
|
|
|
|
- async {},
|
|
|
|
|
- move |_| Message::Notification(
|
|
|
|
|
- Notification::Warn("gonna have to reimplement that".to_string())
|
|
|
|
|
- )
|
|
|
|
|
- )
|
|
|
|
|
- },
|
|
|
|
|
Message::ShowLayoutModal => {
|
|
Message::ShowLayoutModal => {
|
|
|
- let dashboard = self.get_mut_dashboard();
|
|
|
|
|
-
|
|
|
|
|
- dashboard.show_layout_modal = true;
|
|
|
|
|
|
|
+ self.show_layout_modal = true;
|
|
|
iced::widget::focus_next()
|
|
iced::widget::focus_next()
|
|
|
},
|
|
},
|
|
|
Message::HideLayoutModal => {
|
|
Message::HideLayoutModal => {
|
|
|
- let dashboard = self.get_mut_dashboard();
|
|
|
|
|
-
|
|
|
|
|
- dashboard.show_layout_modal = false;
|
|
|
|
|
|
|
+ self.show_layout_modal = false;
|
|
|
Task::none()
|
|
Task::none()
|
|
|
},
|
|
},
|
|
|
Message::Notification(notification) => {
|
|
Message::Notification(notification) => {
|
|
@@ -798,100 +442,6 @@ impl State {
|
|
|
|
|
|
|
|
Task::none()
|
|
Task::none()
|
|
|
},
|
|
},
|
|
|
- Message::PaneContentSelected(content, pane_id, pane_stream) => {
|
|
|
|
|
- let dashboard = self.get_mut_dashboard();
|
|
|
|
|
-
|
|
|
|
|
- let mut tasks = vec![];
|
|
|
|
|
-
|
|
|
|
|
- let pane_content = match content.as_str() {
|
|
|
|
|
- "Heatmap chart" => PaneContent::Heatmap(
|
|
|
|
|
- HeatmapChart::new(1.0)
|
|
|
|
|
- ),
|
|
|
|
|
- "Footprint chart" => {
|
|
|
|
|
- PaneContent::Footprint(
|
|
|
|
|
- FootprintChart::new(1, 1.0, vec![], vec![])
|
|
|
|
|
- )
|
|
|
|
|
- },
|
|
|
|
|
- "Candlestick chart" => {
|
|
|
|
|
- PaneContent::Candlestick(
|
|
|
|
|
- CandlestickChart::new(vec![], 1)
|
|
|
|
|
- )
|
|
|
|
|
- },
|
|
|
|
|
- "Time&Sales" => PaneContent::TimeAndSales(
|
|
|
|
|
- TimeAndSales::new()
|
|
|
|
|
- ),
|
|
|
|
|
- _ => return Task::none(),
|
|
|
|
|
- };
|
|
|
|
|
-
|
|
|
|
|
- // set pane's stream and content identifiers
|
|
|
|
|
- if let Err(err) = dashboard.set_pane_content(pane_id, pane_content) {
|
|
|
|
|
- log::error!("Failed to set pane content: {}", err);
|
|
|
|
|
- } else {
|
|
|
|
|
- log::info!("Pane content set: {content}");
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- if let Err(err) = dashboard.set_pane_stream(pane_id, pane_stream.to_vec()) {
|
|
|
|
|
- log::error!("Failed to set pane stream: {err}");
|
|
|
|
|
- } else {
|
|
|
|
|
- log::info!("Pane stream set: {pane_stream:?}");
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // prepare unique streams for websocket
|
|
|
|
|
- for stream in pane_stream.iter() {
|
|
|
|
|
- match stream {
|
|
|
|
|
- StreamType::Kline { exchange, ticker, .. } | StreamType::DepthAndTrades { exchange, ticker } => {
|
|
|
|
|
- self.pane_streams
|
|
|
|
|
- .entry(*exchange)
|
|
|
|
|
- .or_default()
|
|
|
|
|
- .entry(*ticker)
|
|
|
|
|
- .or_default()
|
|
|
|
|
- .insert(*stream);
|
|
|
|
|
- }
|
|
|
|
|
- _ => {}
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- log::info!("{:?}", &self.pane_streams);
|
|
|
|
|
-
|
|
|
|
|
- // get fetch tasks for pane's content
|
|
|
|
|
- if ["Footprint chart", "Candlestick chart", "Heatmap chart"].contains(&content.as_str()) {
|
|
|
|
|
- for stream in pane_stream.iter() {
|
|
|
|
|
- match stream {
|
|
|
|
|
- StreamType::Kline { exchange, ticker, .. } => {
|
|
|
|
|
- if ["Candlestick chart", "Footprint chart"].contains(&content.as_str()) {
|
|
|
|
|
- tasks.push(create_fetch_klines_task(*stream, pane_id));
|
|
|
|
|
-
|
|
|
|
|
- if content == "Footprint chart" {
|
|
|
|
|
- tasks.push(create_fetch_ticksize_task(exchange, ticker, pane_id));
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- },
|
|
|
|
|
- StreamType::DepthAndTrades { exchange, ticker } => {
|
|
|
|
|
- tasks.push(create_fetch_ticksize_task(exchange, ticker, pane_id));
|
|
|
|
|
- },
|
|
|
|
|
- _ => {}
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- tasks.push(
|
|
|
|
|
- Task::perform(
|
|
|
|
|
- async {},
|
|
|
|
|
- move |_| Message::Notification(
|
|
|
|
|
- Notification::Info(format!("Fetching data for the {}...", content.to_lowercase()))
|
|
|
|
|
- )
|
|
|
|
|
- )
|
|
|
|
|
- );
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- Task::batch(tasks)
|
|
|
|
|
- },
|
|
|
|
|
- Message::ReplacePane(pane) => {
|
|
|
|
|
- let dashboard = self.get_mut_dashboard();
|
|
|
|
|
-
|
|
|
|
|
- dashboard.replace_new_pane(pane);
|
|
|
|
|
-
|
|
|
|
|
- Task::none()
|
|
|
|
|
- },
|
|
|
|
|
Message::ResetCurrentLayout => {
|
|
Message::ResetCurrentLayout => {
|
|
|
let new_dashboard = Dashboard::empty();
|
|
let new_dashboard = Dashboard::empty();
|
|
|
|
|
|
|
@@ -906,58 +456,25 @@ impl State {
|
|
|
},
|
|
},
|
|
|
Message::LayoutSelected(layout_id) => {
|
|
Message::LayoutSelected(layout_id) => {
|
|
|
self.last_active_layout = layout_id;
|
|
self.last_active_layout = layout_id;
|
|
|
-
|
|
|
|
|
- let mut tasks = vec![];
|
|
|
|
|
|
|
|
|
|
- self.pane_streams = self.get_dashboard().get_all_diff_streams();
|
|
|
|
|
-
|
|
|
|
|
- tasks.push(
|
|
|
|
|
- Task::perform(
|
|
|
|
|
- async {},
|
|
|
|
|
- move |_| Message::Notification(Notification::Info("Fetching data...".to_string()))
|
|
|
|
|
- )
|
|
|
|
|
- );
|
|
|
|
|
-
|
|
|
|
|
- tasks.extend(
|
|
|
|
|
- klines_fetch_all_task(&self.pane_streams)
|
|
|
|
|
- );
|
|
|
|
|
- tasks.extend(
|
|
|
|
|
- ticksize_fetch_all_task(&self.pane_streams)
|
|
|
|
|
- );
|
|
|
|
|
-
|
|
|
|
|
- Task::batch(tasks)
|
|
|
|
|
- },
|
|
|
|
|
- Message::FetchDistributeKlines(stream_type, klines) => {
|
|
|
|
|
let dashboard = self.get_mut_dashboard();
|
|
let dashboard = self.get_mut_dashboard();
|
|
|
|
|
|
|
|
- match klines {
|
|
|
|
|
- Ok(klines) => {
|
|
|
|
|
- if let Err(err) = dashboard.find_and_insert_klines(&stream_type, &klines) {
|
|
|
|
|
- log::error!("{err}");
|
|
|
|
|
- }
|
|
|
|
|
- },
|
|
|
|
|
- Err(err) => {
|
|
|
|
|
- log::error!("{err}");
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- Task::none()
|
|
|
|
|
- },
|
|
|
|
|
- Message::FetchDistributeTicks(stream_type, min_tick_size) => {
|
|
|
|
|
|
|
+ let layout_fetch_command = dashboard.layout_changed();
|
|
|
|
|
+
|
|
|
|
|
+ Task::batch(vec![
|
|
|
|
|
+ layout_fetch_command.map(Message::Dashboard),
|
|
|
|
|
+ ])
|
|
|
|
|
+ },
|
|
|
|
|
+ Message::Dashboard(message) => {
|
|
|
let dashboard = self.get_mut_dashboard();
|
|
let dashboard = self.get_mut_dashboard();
|
|
|
|
|
+
|
|
|
|
|
+ let command = dashboard.update(
|
|
|
|
|
+ message,
|
|
|
|
|
+ );
|
|
|
|
|
|
|
|
- match min_tick_size {
|
|
|
|
|
- Ok(ticksize) => {
|
|
|
|
|
- if let Err(err) = dashboard.find_and_insert_ticksizes(&stream_type, ticksize) {
|
|
|
|
|
- log::error!("{err}");
|
|
|
|
|
- }
|
|
|
|
|
- },
|
|
|
|
|
- Err(err) => {
|
|
|
|
|
- log::error!("{err}");
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- Task::none()
|
|
|
|
|
|
|
+ Task::batch(vec![
|
|
|
|
|
+ command.map(Message::Dashboard),
|
|
|
|
|
+ ])
|
|
|
},
|
|
},
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
@@ -965,105 +482,9 @@ impl State {
|
|
|
fn view(&self) -> Element<'_, Message> {
|
|
fn view(&self) -> Element<'_, Message> {
|
|
|
let dashboard = self.get_dashboard();
|
|
let dashboard = self.get_dashboard();
|
|
|
|
|
|
|
|
- let focus = dashboard.focus;
|
|
|
|
|
-
|
|
|
|
|
- let pane_grid = PaneGrid::new(&dashboard.panes, |id, pane, is_maximized| {
|
|
|
|
|
- let is_focused;
|
|
|
|
|
-
|
|
|
|
|
- if dashboard.pane_lock {
|
|
|
|
|
- is_focused = false;
|
|
|
|
|
- } else {
|
|
|
|
|
- is_focused = focus == Some(id);
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- let chart_type = &dashboard.panes.get(id).unwrap().content;
|
|
|
|
|
-
|
|
|
|
|
- let stream_info = pane.stream.iter().find_map(|stream: &StreamType| {
|
|
|
|
|
- match stream {
|
|
|
|
|
- StreamType::Kline { exchange, ticker, timeframe } => {
|
|
|
|
|
- Some(
|
|
|
|
|
- Some((exchange, format!("{} {}", ticker, timeframe)))
|
|
|
|
|
- )
|
|
|
|
|
- }
|
|
|
|
|
- _ => None,
|
|
|
|
|
- }
|
|
|
|
|
- }).or_else(|| {
|
|
|
|
|
- pane.stream.iter().find_map(|stream: &StreamType| {
|
|
|
|
|
- match stream {
|
|
|
|
|
- StreamType::DepthAndTrades { exchange, ticker } => {
|
|
|
|
|
- Some(
|
|
|
|
|
- Some((exchange, ticker.to_string()))
|
|
|
|
|
- )
|
|
|
|
|
- }
|
|
|
|
|
- _ => None,
|
|
|
|
|
- }
|
|
|
|
|
- })
|
|
|
|
|
- }).unwrap_or(None);
|
|
|
|
|
-
|
|
|
|
|
- let mut stream_info_element: Row<Message> = Row::new();
|
|
|
|
|
-
|
|
|
|
|
- if let Some((exchange, info)) = stream_info {
|
|
|
|
|
- stream_info_element = Row::new()
|
|
|
|
|
- .spacing(3)
|
|
|
|
|
- .push(
|
|
|
|
|
- match exchange {
|
|
|
|
|
- Exchange::BinanceFutures => text(char::from(Icon::BinanceLogo).to_string()).font(ICON_FONT),
|
|
|
|
|
- Exchange::BybitLinear => text(char::from(Icon::BybitLogo).to_string()).font(ICON_FONT),
|
|
|
|
|
- }
|
|
|
|
|
- )
|
|
|
|
|
- .push(Text::new(info));
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- let mut content: pane_grid::Content<'_, Message, _, Renderer> =
|
|
|
|
|
- pane_grid::Content::new({
|
|
|
|
|
- match chart_type {
|
|
|
|
|
- PaneContent::Heatmap(chart) => view_chart(pane, chart),
|
|
|
|
|
-
|
|
|
|
|
- PaneContent::Footprint(chart) => view_chart(pane, chart),
|
|
|
|
|
-
|
|
|
|
|
- PaneContent::Candlestick(chart) => view_chart(pane, chart),
|
|
|
|
|
-
|
|
|
|
|
- PaneContent::TimeAndSales(chart) => view_chart(pane, chart),
|
|
|
|
|
-
|
|
|
|
|
- PaneContent::Starter => view_starter(pane)
|
|
|
|
|
- }
|
|
|
|
|
- })
|
|
|
|
|
- .style(
|
|
|
|
|
- if is_focused {
|
|
|
|
|
- style::pane_focused
|
|
|
|
|
- } else {
|
|
|
|
|
- style::pane_active
|
|
|
|
|
- }
|
|
|
|
|
- );
|
|
|
|
|
-
|
|
|
|
|
- let title_bar = pane_grid::TitleBar::new(stream_info_element)
|
|
|
|
|
- .controls(view_controls(
|
|
|
|
|
- id,
|
|
|
|
|
- pane.id,
|
|
|
|
|
- chart_type,
|
|
|
|
|
- dashboard.panes.len(),
|
|
|
|
|
- is_maximized,
|
|
|
|
|
- &pane.settings,
|
|
|
|
|
- ))
|
|
|
|
|
- .padding(4)
|
|
|
|
|
- .style(
|
|
|
|
|
- if is_focused {
|
|
|
|
|
- style::title_bar_focused
|
|
|
|
|
- } else {
|
|
|
|
|
- style::title_bar_active
|
|
|
|
|
- }
|
|
|
|
|
- );
|
|
|
|
|
- content = content.title_bar(title_bar);
|
|
|
|
|
-
|
|
|
|
|
- content
|
|
|
|
|
- })
|
|
|
|
|
- .width(Length::Fill)
|
|
|
|
|
- .height(Length::Fill)
|
|
|
|
|
- .spacing(6);
|
|
|
|
|
-
|
|
|
|
|
let layout_lock_button = button(
|
|
let layout_lock_button = button(
|
|
|
container(
|
|
container(
|
|
|
- if dashboard.pane_lock {
|
|
|
|
|
|
|
+ if dashboard.layout_lock {
|
|
|
text(char::from(Icon::Locked).to_string()).font(ICON_FONT)
|
|
text(char::from(Icon::Locked).to_string()).font(ICON_FONT)
|
|
|
} else {
|
|
} else {
|
|
|
text(char::from(Icon::Unlocked).to_string()).font(ICON_FONT)
|
|
text(char::from(Icon::Unlocked).to_string()).font(ICON_FONT)
|
|
@@ -1073,7 +494,7 @@ impl State {
|
|
|
)
|
|
)
|
|
|
.on_press(Message::ToggleLayoutLock);
|
|
.on_press(Message::ToggleLayoutLock);
|
|
|
|
|
|
|
|
- let add_pane_button = button(
|
|
|
|
|
|
|
+ let layout_modal_button = button(
|
|
|
container(
|
|
container(
|
|
|
text(char::from(Icon::Layout).to_string()).font(ICON_FONT))
|
|
text(char::from(Icon::Layout).to_string()).font(ICON_FONT))
|
|
|
.width(25)
|
|
.width(25)
|
|
@@ -1085,10 +506,16 @@ impl State {
|
|
|
.spacing(10)
|
|
.spacing(10)
|
|
|
.align_y(Alignment::Center)
|
|
.align_y(Alignment::Center)
|
|
|
.push(
|
|
.push(
|
|
|
- tooltip(add_pane_button, "Manage Layout", tooltip::Position::Bottom).style(style::tooltip)
|
|
|
|
|
|
|
+ tooltip(
|
|
|
|
|
+ layout_modal_button,
|
|
|
|
|
+ "Manage Layouts", tooltip::Position::Bottom
|
|
|
|
|
+ ).style(style::tooltip)
|
|
|
)
|
|
)
|
|
|
.push(
|
|
.push(
|
|
|
- tooltip(layout_lock_button, "Layout Lock", tooltip::Position::Bottom).style(style::tooltip)
|
|
|
|
|
|
|
+ tooltip(
|
|
|
|
|
+ layout_lock_button,
|
|
|
|
|
+ "Layout Lock", tooltip::Position::Bottom
|
|
|
|
|
+ ).style(style::tooltip)
|
|
|
);
|
|
);
|
|
|
|
|
|
|
|
let mut ws_controls = Row::new()
|
|
let mut ws_controls = Row::new()
|
|
@@ -1149,44 +576,39 @@ impl State {
|
|
|
.push(layout_controls)
|
|
.push(layout_controls)
|
|
|
)
|
|
)
|
|
|
.push(
|
|
.push(
|
|
|
- if dashboard.pane_lock {
|
|
|
|
|
- pane_grid
|
|
|
|
|
- } else {
|
|
|
|
|
- pane_grid
|
|
|
|
|
- .on_click(Message::Clicked)
|
|
|
|
|
- .on_drag(Message::Dragged)
|
|
|
|
|
- .on_resize(10, Message::Resized)
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ dashboard.view().map(Message::Dashboard)
|
|
|
);
|
|
);
|
|
|
|
|
|
|
|
- if dashboard.show_layout_modal {
|
|
|
|
|
- let mut add_pane_button = button("Split selected pane").width(iced::Pixels(200.0));
|
|
|
|
|
|
|
+ if self.show_layout_modal {
|
|
|
|
|
+ let layout_picklist = pick_list(
|
|
|
|
|
+ &LayoutId::ALL[..],
|
|
|
|
|
+ Some(self.last_active_layout),
|
|
|
|
|
+ move |layout: LayoutId| Message::LayoutSelected(layout)
|
|
|
|
|
+ );
|
|
|
|
|
|
|
|
|
|
+ let mut add_pane_button = button("Split selected pane").width(iced::Pixels(200.0));
|
|
|
let mut replace_pane_button = button("Replace selected pane").width(iced::Pixels(200.0));
|
|
let mut replace_pane_button = button("Replace selected pane").width(iced::Pixels(200.0));
|
|
|
|
|
|
|
|
if dashboard.focus.is_some() {
|
|
if dashboard.focus.is_some() {
|
|
|
replace_pane_button = replace_pane_button.on_press(
|
|
replace_pane_button = replace_pane_button.on_press(
|
|
|
- Message::ReplacePane(
|
|
|
|
|
|
|
+ Message::Dashboard(dashboard::Message::Pane(
|
|
|
|
|
+ pane::Message::ReplacePane(
|
|
|
dashboard.focus
|
|
dashboard.focus
|
|
|
.unwrap_or_else(|| { *dashboard.panes.iter().next().unwrap().0 })
|
|
.unwrap_or_else(|| { *dashboard.panes.iter().next().unwrap().0 })
|
|
|
- )
|
|
|
|
|
|
|
+ )
|
|
|
|
|
+ ))
|
|
|
);
|
|
);
|
|
|
|
|
|
|
|
add_pane_button = add_pane_button.on_press(
|
|
add_pane_button = add_pane_button.on_press(
|
|
|
- Message::Split(
|
|
|
|
|
- pane_grid::Axis::Horizontal,
|
|
|
|
|
- dashboard.focus
|
|
|
|
|
- .unwrap_or_else(|| { *dashboard.panes.iter().next().unwrap().0 })
|
|
|
|
|
- )
|
|
|
|
|
|
|
+ Message::Dashboard(dashboard::Message::Pane(
|
|
|
|
|
+ pane::Message::SplitPane(
|
|
|
|
|
+ pane_grid::Axis::Horizontal,
|
|
|
|
|
+ dashboard.focus.unwrap_or_else(|| { *dashboard.panes.iter().next().unwrap().0 })
|
|
|
|
|
+ )
|
|
|
|
|
+ ))
|
|
|
);
|
|
);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- let layout_picklist = pick_list(
|
|
|
|
|
- &LayoutId::ALL[..],
|
|
|
|
|
- Some(self.last_active_layout),
|
|
|
|
|
- move |layout: LayoutId| Message::LayoutSelected(layout)
|
|
|
|
|
- );
|
|
|
|
|
-
|
|
|
|
|
let layout_modal = container(
|
|
let layout_modal = container(
|
|
|
Column::new()
|
|
Column::new()
|
|
|
.spacing(16)
|
|
.spacing(16)
|
|
@@ -1253,7 +675,7 @@ impl State {
|
|
|
fn subscription(&self) -> Subscription<Message> {
|
|
fn subscription(&self) -> Subscription<Message> {
|
|
|
let mut all_subscriptions = Vec::new();
|
|
let mut all_subscriptions = Vec::new();
|
|
|
|
|
|
|
|
- for (exchange, stream) in &self.pane_streams {
|
|
|
|
|
|
|
+ for (exchange, stream) in &self.get_dashboard().pane_streams {
|
|
|
let mut depth_streams: Vec<Subscription<Message>> = Vec::new();
|
|
let mut depth_streams: Vec<Subscription<Message>> = Vec::new();
|
|
|
let mut kline_streams: Vec<(Ticker, Timeframe)> = Vec::new();
|
|
let mut kline_streams: Vec<(Ticker, Timeframe)> = Vec::new();
|
|
|
|
|
|
|
@@ -1386,503 +808,114 @@ where
|
|
|
.into()
|
|
.into()
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-trait ChartView {
|
|
|
|
|
- fn view(&self, id: &PaneState) -> Element<Message>;
|
|
|
|
|
|
|
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
|
|
|
+pub enum Event {
|
|
|
|
|
+ CloseRequested(window::Id),
|
|
|
|
|
+ Copy,
|
|
|
|
|
+ Escape,
|
|
|
|
|
+ Home,
|
|
|
|
|
+ End,
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-impl ChartView for HeatmapChart {
|
|
|
|
|
- fn view(&self, pane: &PaneState) -> Element<Message> {
|
|
|
|
|
-
|
|
|
|
|
-
|
|
|
|
|
- let pane_id = pane.id;
|
|
|
|
|
-
|
|
|
|
|
- let underlay = self.view().map(move |message| Message::ChartUserUpdate(message, pane_id));
|
|
|
|
|
-
|
|
|
|
|
- if pane.show_modal {
|
|
|
|
|
- let size_filter = &self.get_size_filter();
|
|
|
|
|
-
|
|
|
|
|
- let signup: Container<Message, Theme, _> = container(
|
|
|
|
|
- Column::new()
|
|
|
|
|
- .spacing(10)
|
|
|
|
|
- .align_x(Alignment::Center)
|
|
|
|
|
- .push(
|
|
|
|
|
- Text::new("Heatmap > Settings")
|
|
|
|
|
- .size(16)
|
|
|
|
|
- )
|
|
|
|
|
- .push(
|
|
|
|
|
- Column::new()
|
|
|
|
|
- .align_x(Alignment::Center)
|
|
|
|
|
- .push(Text::new("Size Filtering"))
|
|
|
|
|
- .push(
|
|
|
|
|
- Slider::new(0.0..=50000.0, *size_filter, move |value| Message::SliderChanged(pane_id, value))
|
|
|
|
|
- .step(500.0)
|
|
|
|
|
- )
|
|
|
|
|
- .push(
|
|
|
|
|
- Text::new(format!("${size_filter}")).size(16)
|
|
|
|
|
- )
|
|
|
|
|
- )
|
|
|
|
|
- .push(
|
|
|
|
|
- Row::new()
|
|
|
|
|
- .spacing(10)
|
|
|
|
|
- .push(
|
|
|
|
|
- button("Close")
|
|
|
|
|
- .on_press(Message::CloseModal(pane_id))
|
|
|
|
|
- )
|
|
|
|
|
- )
|
|
|
|
|
- )
|
|
|
|
|
- .width(Length::Shrink)
|
|
|
|
|
- .padding(20)
|
|
|
|
|
- .max_width(500)
|
|
|
|
|
- .style(style::chart_modal);
|
|
|
|
|
-
|
|
|
|
|
- return modal(underlay, signup, Message::CloseModal(pane_id));
|
|
|
|
|
- } else {
|
|
|
|
|
- underlay
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
|
|
+pub fn events() -> Subscription<Event> {
|
|
|
|
|
+ iced::event::listen_with(filtered_events)
|
|
|
}
|
|
}
|
|
|
-impl ChartView for FootprintChart {
|
|
|
|
|
- fn view(&self, pane: &PaneState) -> Element<Message> {
|
|
|
|
|
- let pane_id = pane.id;
|
|
|
|
|
|
|
|
|
|
- self.view().map(move |message| Message::ChartUserUpdate(message, pane_id))
|
|
|
|
|
|
|
+fn filtered_events(
|
|
|
|
|
+ event: iced::Event,
|
|
|
|
|
+ _status: iced::event::Status,
|
|
|
|
|
+ window: window::Id,
|
|
|
|
|
+) -> Option<Event> {
|
|
|
|
|
+ match &event {
|
|
|
|
|
+ iced::Event::Window(window::Event::CloseRequested) => Some(Event::CloseRequested(window)),
|
|
|
|
|
+ _ => None,
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
-impl ChartView for TimeAndSales {
|
|
|
|
|
- fn view(&self, pane: &PaneState) -> Element<Message> {
|
|
|
|
|
-
|
|
|
|
|
-
|
|
|
|
|
- let pane_id = pane.id;
|
|
|
|
|
-
|
|
|
|
|
- let underlay = self.view();
|
|
|
|
|
-
|
|
|
|
|
- if pane.show_modal {
|
|
|
|
|
- let size_filter = &self.get_size_filter();
|
|
|
|
|
|
|
|
|
|
- let filter_sync_heatmap = &self.get_filter_sync_heatmap();
|
|
|
|
|
-
|
|
|
|
|
- let signup = container(
|
|
|
|
|
- Column::new()
|
|
|
|
|
- .spacing(10)
|
|
|
|
|
- .align_x(Alignment::Center)
|
|
|
|
|
- .push(
|
|
|
|
|
- Text::new("Time&Sales > Settings")
|
|
|
|
|
- .size(16)
|
|
|
|
|
- )
|
|
|
|
|
- .push(
|
|
|
|
|
- Column::new()
|
|
|
|
|
- .align_x(Alignment::Center)
|
|
|
|
|
- .push(Text::new("Size Filtering"))
|
|
|
|
|
- .push(
|
|
|
|
|
- Slider::new(0.0..=50000.0, *size_filter, move |value| Message::SliderChanged(pane_id, value))
|
|
|
|
|
- .step(500.0)
|
|
|
|
|
- )
|
|
|
|
|
- .push(
|
|
|
|
|
- Text::new(format!("${size_filter}")).size(16)
|
|
|
|
|
- )
|
|
|
|
|
- .push(
|
|
|
|
|
- checkbox("Sync Heatmap with", *filter_sync_heatmap)
|
|
|
|
|
- .on_toggle(Message::SyncWithHeatmap)
|
|
|
|
|
- )
|
|
|
|
|
- )
|
|
|
|
|
- .push(
|
|
|
|
|
- Row::new()
|
|
|
|
|
- .spacing(10)
|
|
|
|
|
- .push(
|
|
|
|
|
- button("Close")
|
|
|
|
|
- .on_press(Message::CloseModal(pane_id))
|
|
|
|
|
- )
|
|
|
|
|
- )
|
|
|
|
|
- )
|
|
|
|
|
- .width(Length::Shrink)
|
|
|
|
|
- .padding(20)
|
|
|
|
|
- .max_width(500)
|
|
|
|
|
- .style(style::chart_modal);
|
|
|
|
|
-
|
|
|
|
|
- return modal(underlay, signup, Message::CloseModal(pane_id));
|
|
|
|
|
- } else {
|
|
|
|
|
- underlay
|
|
|
|
|
|
|
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Deserialize, Serialize)]
|
|
|
|
|
+pub enum LayoutId {
|
|
|
|
|
+ Layout1,
|
|
|
|
|
+ Layout2,
|
|
|
|
|
+ Layout3,
|
|
|
|
|
+ Layout4,
|
|
|
|
|
+}
|
|
|
|
|
+impl std::fmt::Display for LayoutId {
|
|
|
|
|
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
|
|
|
+ match self {
|
|
|
|
|
+ LayoutId::Layout1 => write!(f, "Layout 1"),
|
|
|
|
|
+ LayoutId::Layout2 => write!(f, "Layout 2"),
|
|
|
|
|
+ LayoutId::Layout3 => write!(f, "Layout 3"),
|
|
|
|
|
+ LayoutId::Layout4 => write!(f, "Layout 4"),
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
-impl ChartView for CandlestickChart {
|
|
|
|
|
- fn view(&self, pane: &PaneState) -> Element<Message> {
|
|
|
|
|
- let pane_id = pane.id;
|
|
|
|
|
-
|
|
|
|
|
- self.view().map(move |message| Message::ChartUserUpdate(message, pane_id))
|
|
|
|
|
- }
|
|
|
|
|
|
|
+impl LayoutId {
|
|
|
|
|
+ const ALL: [LayoutId; 4] = [LayoutId::Layout1, LayoutId::Layout2, LayoutId::Layout3, LayoutId::Layout4];
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-fn view_chart<'a, C: ChartView>(
|
|
|
|
|
- pane: &'a PaneState,
|
|
|
|
|
- chart: &'a C,
|
|
|
|
|
-) -> Element<'a, Message> {
|
|
|
|
|
- let chart_view: Element<Message> = chart.view(pane);
|
|
|
|
|
-
|
|
|
|
|
- let container = Container::new(chart_view)
|
|
|
|
|
- .width(Length::Fill)
|
|
|
|
|
- .height(Length::Fill);
|
|
|
|
|
-
|
|
|
|
|
- container.into()
|
|
|
|
|
|
|
+struct SavedState {
|
|
|
|
|
+ layouts: HashMap<LayoutId, Dashboard>,
|
|
|
|
|
+ last_active_layout: LayoutId,
|
|
|
|
|
+ window_size: Option<(f32, f32)>,
|
|
|
|
|
+ window_position: Option<(f32, f32)>,
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
-fn view_starter(
|
|
|
|
|
- pane: &PaneState,
|
|
|
|
|
-) -> Element<'_, Message> {
|
|
|
|
|
- let content_names = ["Heatmap chart", "Footprint chart", "Candlestick chart", "Time&Sales"];
|
|
|
|
|
-
|
|
|
|
|
- let content_selector = content_names.iter().fold(
|
|
|
|
|
- Column::new()
|
|
|
|
|
- .spacing(6)
|
|
|
|
|
- .align_x(Alignment::Center), |column, &label| {
|
|
|
|
|
- let mut btn = button(label).width(Length::Fill);
|
|
|
|
|
- if let (Some(exchange), Some(ticker)) = (pane.settings.selected_exchange, pane.settings.selected_ticker) {
|
|
|
|
|
- let timeframe = pane.settings.selected_timeframe.unwrap_or_else(
|
|
|
|
|
- || { log::error!("No timeframe found"); Timeframe::M1 }
|
|
|
|
|
- );
|
|
|
|
|
-
|
|
|
|
|
- let pane_stream: Vec<StreamType> = match label {
|
|
|
|
|
- "Heatmap chart" | "Time&Sales" => vec![
|
|
|
|
|
- StreamType::DepthAndTrades { exchange, ticker }
|
|
|
|
|
- ],
|
|
|
|
|
- "Footprint chart" => vec![
|
|
|
|
|
- StreamType::DepthAndTrades { exchange, ticker },
|
|
|
|
|
- StreamType::Kline { exchange, ticker, timeframe }
|
|
|
|
|
- ],
|
|
|
|
|
- "Candlestick chart" => vec![
|
|
|
|
|
- StreamType::Kline { exchange, ticker, timeframe }
|
|
|
|
|
- ],
|
|
|
|
|
- _ => vec![]
|
|
|
|
|
- };
|
|
|
|
|
-
|
|
|
|
|
- btn = btn.on_press(
|
|
|
|
|
- Message::PaneContentSelected(label.to_string(), pane.id, pane_stream)
|
|
|
|
|
- );
|
|
|
|
|
- }
|
|
|
|
|
- column.push(btn)
|
|
|
|
|
- }
|
|
|
|
|
- );
|
|
|
|
|
-
|
|
|
|
|
- let symbol_selector = pick_list(
|
|
|
|
|
- &Ticker::ALL[..],
|
|
|
|
|
- pane.settings.selected_ticker,
|
|
|
|
|
- move |ticker| Message::TickerSelected(ticker, pane.id),
|
|
|
|
|
- ).placeholder("ticker...").text_size(13).width(Length::Fill);
|
|
|
|
|
-
|
|
|
|
|
- let exchange_selector = pick_list(
|
|
|
|
|
- &Exchange::ALL[..],
|
|
|
|
|
- pane.settings.selected_exchange,
|
|
|
|
|
- move |exchange| Message::ExchangeSelected(exchange, pane.id),
|
|
|
|
|
- ).placeholder("exchange...").text_size(13).width(Length::Fill);
|
|
|
|
|
-
|
|
|
|
|
- let picklists = Row::new()
|
|
|
|
|
- .spacing(6)
|
|
|
|
|
- .align_y(Alignment::Center)
|
|
|
|
|
- .push(exchange_selector.style(style::picklist_primary).menu_style(style::picklist_menu_primary))
|
|
|
|
|
- .push(symbol_selector.style(style::picklist_primary).menu_style(style::picklist_menu_primary));
|
|
|
|
|
-
|
|
|
|
|
- let column = Column::new()
|
|
|
|
|
- .padding(10)
|
|
|
|
|
- .spacing(10)
|
|
|
|
|
- .align_x(Alignment::Center)
|
|
|
|
|
- .push(picklists)
|
|
|
|
|
- .push(content_selector);
|
|
|
|
|
|
|
+impl Default for SavedState {
|
|
|
|
|
+ fn default() -> Self {
|
|
|
|
|
+ let mut layouts = HashMap::new();
|
|
|
|
|
+ layouts.insert(LayoutId::Layout1, Dashboard::default());
|
|
|
|
|
+ layouts.insert(LayoutId::Layout2, Dashboard::default());
|
|
|
|
|
+ layouts.insert(LayoutId::Layout3, Dashboard::default());
|
|
|
|
|
+ layouts.insert(LayoutId::Layout4, Dashboard::default());
|
|
|
|
|
|
|
|
- let container = Container::new(
|
|
|
|
|
- Column::new()
|
|
|
|
|
- .spacing(10)
|
|
|
|
|
- .padding(20)
|
|
|
|
|
- .align_x(Alignment::Center)
|
|
|
|
|
- .max_width(300)
|
|
|
|
|
- .push(
|
|
|
|
|
- Text::new("Initialize the pane").size(16)
|
|
|
|
|
- )
|
|
|
|
|
- .push(scrollable(column))
|
|
|
|
|
- ).align_x(alignment::Horizontal::Center);
|
|
|
|
|
-
|
|
|
|
|
- container.into()
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-fn view_controls<'a>(
|
|
|
|
|
- pane: pane_grid::Pane,
|
|
|
|
|
- pane_id: Uuid,
|
|
|
|
|
- pane_type: &PaneContent,
|
|
|
|
|
- total_panes: usize,
|
|
|
|
|
- is_maximized: bool,
|
|
|
|
|
- settings: &PaneSettings,
|
|
|
|
|
-) -> Element<'a, Message> {
|
|
|
|
|
- let mut row = row![].spacing(5);
|
|
|
|
|
-
|
|
|
|
|
- let (icon, message) = if is_maximized {
|
|
|
|
|
- (Icon::ResizeSmall, Message::Restore)
|
|
|
|
|
- } else {
|
|
|
|
|
- (Icon::ResizeFull, Message::Maximize(pane))
|
|
|
|
|
- };
|
|
|
|
|
-
|
|
|
|
|
- match pane_type {
|
|
|
|
|
- PaneContent::Heatmap(_) => {
|
|
|
|
|
- let ticksize_picker = pick_list(
|
|
|
|
|
- [TickMultiplier(1), TickMultiplier(2), TickMultiplier(5), TickMultiplier(10), TickMultiplier(25), TickMultiplier(50)],
|
|
|
|
|
- settings.tick_multiply,
|
|
|
|
|
- move |tick_multiply| Message::TicksizeSelected(tick_multiply, pane_id)
|
|
|
|
|
- ).placeholder("Ticksize multiplier...").text_size(11).width(iced::Pixels(80.0));
|
|
|
|
|
-
|
|
|
|
|
- let ticksize_tooltip = tooltip(
|
|
|
|
|
- ticksize_picker
|
|
|
|
|
- .style(style::picklist_primary)
|
|
|
|
|
- .menu_style(style::picklist_menu_primary),
|
|
|
|
|
- "Ticksize multiplier",
|
|
|
|
|
- tooltip::Position::FollowCursor
|
|
|
|
|
- )
|
|
|
|
|
- .style(style::tooltip);
|
|
|
|
|
-
|
|
|
|
|
- row = row.push(ticksize_tooltip);
|
|
|
|
|
- },
|
|
|
|
|
- PaneContent::TimeAndSales(_) => {
|
|
|
|
|
- },
|
|
|
|
|
- PaneContent::Footprint(_) => {
|
|
|
|
|
- let timeframe_picker = pick_list(
|
|
|
|
|
- &Timeframe::ALL[..],
|
|
|
|
|
- settings.selected_timeframe,
|
|
|
|
|
- move |timeframe| Message::TimeframeSelected(timeframe, pane_id),
|
|
|
|
|
- ).placeholder("Choose a timeframe...").text_size(11).width(iced::Pixels(80.0));
|
|
|
|
|
-
|
|
|
|
|
- let tf_tooltip = tooltip(
|
|
|
|
|
- timeframe_picker
|
|
|
|
|
- .style(style::picklist_primary)
|
|
|
|
|
- .menu_style(style::picklist_menu_primary),
|
|
|
|
|
- "Timeframe",
|
|
|
|
|
- tooltip::Position::FollowCursor
|
|
|
|
|
- )
|
|
|
|
|
- .style(style::tooltip);
|
|
|
|
|
-
|
|
|
|
|
- row = row.push(tf_tooltip);
|
|
|
|
|
-
|
|
|
|
|
- let ticksize_picker = pick_list(
|
|
|
|
|
- [TickMultiplier(1), TickMultiplier(2), TickMultiplier(5), TickMultiplier(10), TickMultiplier(25), TickMultiplier(50), TickMultiplier(100), TickMultiplier(200)],
|
|
|
|
|
- settings.tick_multiply,
|
|
|
|
|
- move |tick_multiply| Message::TicksizeSelected(tick_multiply, pane_id)
|
|
|
|
|
- ).placeholder("Ticksize multiplier...").text_size(11).width(iced::Pixels(80.0));
|
|
|
|
|
-
|
|
|
|
|
- let ticksize_tooltip = tooltip(
|
|
|
|
|
- ticksize_picker
|
|
|
|
|
- .style(style::picklist_primary)
|
|
|
|
|
- .menu_style(style::picklist_menu_primary),
|
|
|
|
|
- "Ticksize multiplier",
|
|
|
|
|
- tooltip::Position::FollowCursor
|
|
|
|
|
- )
|
|
|
|
|
- .style(style::tooltip);
|
|
|
|
|
-
|
|
|
|
|
- row = row.push(ticksize_tooltip);
|
|
|
|
|
- },
|
|
|
|
|
- PaneContent::Candlestick(_) => {
|
|
|
|
|
- let timeframe_picker = pick_list(
|
|
|
|
|
- &Timeframe::ALL[..],
|
|
|
|
|
- settings.selected_timeframe,
|
|
|
|
|
- move |timeframe| Message::TimeframeSelected(timeframe, pane_id),
|
|
|
|
|
- ).placeholder("Choose a timeframe...").text_size(11).width(iced::Pixels(80.0));
|
|
|
|
|
-
|
|
|
|
|
- let tooltip = tooltip(
|
|
|
|
|
- timeframe_picker
|
|
|
|
|
- .style(style::picklist_primary)
|
|
|
|
|
- .menu_style(style::picklist_menu_primary),
|
|
|
|
|
- "Timeframe",
|
|
|
|
|
- tooltip::Position::FollowCursor
|
|
|
|
|
- )
|
|
|
|
|
- .style(style::tooltip);
|
|
|
|
|
-
|
|
|
|
|
- row = row.push(tooltip);
|
|
|
|
|
- },
|
|
|
|
|
- PaneContent::Starter => {
|
|
|
|
|
- },
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- let mut buttons = vec![
|
|
|
|
|
- (container(text(char::from(Icon::Cog).to_string()).font(ICON_FONT).size(14)).width(25).center_x(iced::Pixels(25.0)), Message::OpenModal(pane)),
|
|
|
|
|
- (container(text(char::from(icon).to_string()).font(ICON_FONT).size(14)).width(25).center_x(iced::Pixels(25.0)), message),
|
|
|
|
|
- ];
|
|
|
|
|
-
|
|
|
|
|
- if total_panes > 1 {
|
|
|
|
|
- buttons.push((container(text(char::from(Icon::Close).to_string()).font(ICON_FONT).size(14)).width(25).center_x(iced::Pixels(25.0)), Message::Close(pane)));
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- for (content, message) in buttons {
|
|
|
|
|
- row = row.push(
|
|
|
|
|
- button(content)
|
|
|
|
|
- .style(style::button_primary)
|
|
|
|
|
- .padding(3)
|
|
|
|
|
- .on_press(message),
|
|
|
|
|
- );
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- row.into()
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-fn klines_fetch_all_task(stream_types: &HashMap<Exchange, HashMap<Ticker, HashSet<StreamType>>>) -> Vec<Task<Message>> {
|
|
|
|
|
- let mut tasks: Vec<Task<Message>> = vec![];
|
|
|
|
|
-
|
|
|
|
|
- for (exchange, stream) in stream_types {
|
|
|
|
|
- let mut kline_fetches = Vec::new();
|
|
|
|
|
-
|
|
|
|
|
- for stream_types in stream.values() {
|
|
|
|
|
- for stream_type in stream_types {
|
|
|
|
|
- match stream_type {
|
|
|
|
|
- StreamType::Kline { ticker, timeframe, .. } => {
|
|
|
|
|
- kline_fetches.push((*ticker, *timeframe));
|
|
|
|
|
- },
|
|
|
|
|
- _ => {}
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- for (ticker, timeframe) in kline_fetches {
|
|
|
|
|
- let ticker = ticker;
|
|
|
|
|
- let timeframe = timeframe;
|
|
|
|
|
- let exchange = *exchange;
|
|
|
|
|
-
|
|
|
|
|
- match exchange {
|
|
|
|
|
- Exchange::BinanceFutures => {
|
|
|
|
|
- let fetch_klines = Task::perform(
|
|
|
|
|
- binance::market_data::fetch_klines(ticker, timeframe)
|
|
|
|
|
- .map_err(|err| format!("{err}")),
|
|
|
|
|
- move |klines| Message::FetchDistributeKlines(
|
|
|
|
|
- StreamType::Kline { exchange, ticker, timeframe }, klines
|
|
|
|
|
- )
|
|
|
|
|
- );
|
|
|
|
|
- tasks.push(fetch_klines);
|
|
|
|
|
- },
|
|
|
|
|
- Exchange::BybitLinear => {
|
|
|
|
|
- let fetch_klines = Task::perform(
|
|
|
|
|
- bybit::market_data::fetch_klines(ticker, timeframe)
|
|
|
|
|
- .map_err(|err| format!("{err}")),
|
|
|
|
|
- move |klines| Message::FetchDistributeKlines(
|
|
|
|
|
- StreamType::Kline { exchange, ticker, timeframe }, klines
|
|
|
|
|
- )
|
|
|
|
|
- );
|
|
|
|
|
- tasks.push(fetch_klines);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- tasks
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-fn ticksize_fetch_all_task(stream_types: &HashMap<Exchange, HashMap<Ticker, HashSet<StreamType>>>) -> Vec<Task<Message>> {
|
|
|
|
|
- let mut tasks: Vec<Task<Message>> = vec![];
|
|
|
|
|
-
|
|
|
|
|
- for (exchange, stream) in stream_types {
|
|
|
|
|
- let mut ticksize_fetches = Vec::new();
|
|
|
|
|
-
|
|
|
|
|
- for stream_types in stream.values() {
|
|
|
|
|
- for stream_type in stream_types {
|
|
|
|
|
- match stream_type {
|
|
|
|
|
- StreamType::DepthAndTrades { ticker, .. } => {
|
|
|
|
|
- ticksize_fetches.push(*ticker);
|
|
|
|
|
- },
|
|
|
|
|
- _ => {}
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- for ticker in ticksize_fetches {
|
|
|
|
|
- let ticker = ticker;
|
|
|
|
|
- let exchange = *exchange;
|
|
|
|
|
-
|
|
|
|
|
- match exchange {
|
|
|
|
|
- Exchange::BinanceFutures => {
|
|
|
|
|
- let fetch_ticksize = Task::perform(
|
|
|
|
|
- binance::market_data::fetch_ticksize(ticker)
|
|
|
|
|
- .map_err(|err| format!("{err}")),
|
|
|
|
|
- move |ticksize| Message::FetchDistributeTicks(
|
|
|
|
|
- StreamType::DepthAndTrades { exchange, ticker }, ticksize
|
|
|
|
|
- )
|
|
|
|
|
- );
|
|
|
|
|
- tasks.push(fetch_ticksize);
|
|
|
|
|
- },
|
|
|
|
|
- Exchange::BybitLinear => {
|
|
|
|
|
- let fetch_ticksize = Task::perform(
|
|
|
|
|
- bybit::market_data::fetch_ticksize(ticker)
|
|
|
|
|
- .map_err(|err| format!("{err}")),
|
|
|
|
|
- move |ticksize| Message::FetchDistributeTicks(
|
|
|
|
|
- StreamType::DepthAndTrades { exchange, ticker }, ticksize
|
|
|
|
|
- )
|
|
|
|
|
- );
|
|
|
|
|
- tasks.push(fetch_ticksize);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ SavedState {
|
|
|
|
|
+ layouts,
|
|
|
|
|
+ last_active_layout: LayoutId::Layout1,
|
|
|
|
|
+ window_size: None,
|
|
|
|
|
+ window_position: None,
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
- tasks
|
|
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-fn create_fetch_klines_task(
|
|
|
|
|
- stream: StreamType,
|
|
|
|
|
- pane_id: Uuid,
|
|
|
|
|
-) -> Task<Message> {
|
|
|
|
|
- match stream {
|
|
|
|
|
- StreamType::Kline { exchange, ticker, timeframe } => {
|
|
|
|
|
- match exchange {
|
|
|
|
|
- Exchange::BinanceFutures => Task::perform(
|
|
|
|
|
- binance::market_data::fetch_klines(ticker, timeframe)
|
|
|
|
|
- .map_err(|err| format!("{err}")),
|
|
|
|
|
- move |klines| Message::FetchEvent(klines, stream, pane_id),
|
|
|
|
|
- ),
|
|
|
|
|
- Exchange::BybitLinear => Task::perform(
|
|
|
|
|
- bybit::market_data::fetch_klines(ticker, timeframe)
|
|
|
|
|
- .map_err(|err| format!("{err}")),
|
|
|
|
|
- move |klines| Message::FetchEvent(klines, stream, pane_id),
|
|
|
|
|
- ),
|
|
|
|
|
- }
|
|
|
|
|
- },
|
|
|
|
|
- _ => Task::none(),
|
|
|
|
|
- }
|
|
|
|
|
-}
|
|
|
|
|
|
|
+use std::fs::File;
|
|
|
|
|
+use std::io::{Read, Write};
|
|
|
|
|
+use std::path::Path;
|
|
|
|
|
+use serde::{Deserialize, Serialize};
|
|
|
|
|
|
|
|
-fn create_fetch_ticksize_task(
|
|
|
|
|
- exchange: &Exchange,
|
|
|
|
|
- ticker: &Ticker,
|
|
|
|
|
- pane_id: Uuid,
|
|
|
|
|
-) -> Task<Message> {
|
|
|
|
|
- match exchange {
|
|
|
|
|
- Exchange::BinanceFutures => Task::perform(
|
|
|
|
|
- binance::market_data::fetch_ticksize(*ticker),
|
|
|
|
|
- move |result| match result {
|
|
|
|
|
- Ok(ticksize) => Message::SetMinTickSize(ticksize, pane_id),
|
|
|
|
|
- Err(err) => Message::ErrorOccurred(Error::FetchError(err.to_string())),
|
|
|
|
|
- },
|
|
|
|
|
- ),
|
|
|
|
|
- Exchange::BybitLinear => Task::perform(
|
|
|
|
|
- bybit::market_data::fetch_ticksize(*ticker),
|
|
|
|
|
- move |result| match result {
|
|
|
|
|
- Ok(ticksize) => Message::SetMinTickSize(ticksize, pane_id),
|
|
|
|
|
- Err(err) => Message::ErrorOccurred(Error::FetchError(err.to_string())),
|
|
|
|
|
- },
|
|
|
|
|
- ),
|
|
|
|
|
- }
|
|
|
|
|
|
|
+fn write_json_to_file(json: &str, file_path: &str) -> std::io::Result<()> {
|
|
|
|
|
+ let path = Path::new(file_path);
|
|
|
|
|
+ let mut file = File::create(path)?;
|
|
|
|
|
+ file.write_all(json.as_bytes())?;
|
|
|
|
|
+ Ok(())
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
|
|
|
-pub enum Event {
|
|
|
|
|
- CloseRequested(window::Id),
|
|
|
|
|
- Copy,
|
|
|
|
|
- Escape,
|
|
|
|
|
- Home,
|
|
|
|
|
- End,
|
|
|
|
|
|
|
+fn read_layout_from_file(file_path: &str) -> Result<SerializableState, Box<dyn std::error::Error>> {
|
|
|
|
|
+ let path = Path::new(file_path);
|
|
|
|
|
+ let mut file = File::open(path)?;
|
|
|
|
|
+ let mut contents = String::new();
|
|
|
|
|
+ file.read_to_string(&mut contents)?;
|
|
|
|
|
+
|
|
|
|
|
+ Ok(serde_json::from_str(&contents)?)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-pub fn events() -> Subscription<Event> {
|
|
|
|
|
- iced::event::listen_with(filtered_events)
|
|
|
|
|
|
|
+#[derive(Debug, Clone, Deserialize, Serialize)]
|
|
|
|
|
+struct SerializableState {
|
|
|
|
|
+ pub layouts: HashMap<LayoutId, SerializableDashboard>,
|
|
|
|
|
+ pub last_active_layout: LayoutId,
|
|
|
|
|
+ pub window_size: Option<(f32, f32)>,
|
|
|
|
|
+ pub window_position: Option<(f32, f32)>,
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
-fn filtered_events(
|
|
|
|
|
- event: iced::Event,
|
|
|
|
|
- _status: iced::event::Status,
|
|
|
|
|
- window: window::Id,
|
|
|
|
|
-) -> Option<Event> {
|
|
|
|
|
- match &event {
|
|
|
|
|
- iced::Event::Window(window::Event::CloseRequested) => Some(Event::CloseRequested(window)),
|
|
|
|
|
- _ => None,
|
|
|
|
|
|
|
+impl SerializableState {
|
|
|
|
|
+ fn from_parts(
|
|
|
|
|
+ layouts: HashMap<LayoutId, SerializableDashboard>,
|
|
|
|
|
+ last_active_layout: LayoutId,
|
|
|
|
|
+ size: Option<Size>,
|
|
|
|
|
+ position: Option<Point>,
|
|
|
|
|
+ ) -> Self {
|
|
|
|
|
+ SerializableState {
|
|
|
|
|
+ layouts,
|
|
|
|
|
+ last_active_layout,
|
|
|
|
|
+ window_size: size.map(|s| (s.width, s.height)),
|
|
|
|
|
+ window_position: position.map(|p| (p.x, p.y)),
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|