Browse Source

Error/warn/info notifications (#14)

* feat: new notification type, warn

* style: notification box

* fix inconsistency of notification styling, +some chores

* removed unused crate features
Berke 1 year ago
parent
commit
45e61836c6
6 changed files with 303 additions and 380 deletions
  1. 0 202
      Cargo.lock
  2. 1 1
      Cargo.toml
  3. 254 162
      src/main.rs
  4. 17 1
      src/screen.rs
  5. 16 14
      src/screen/dashboard.rs
  6. 15 0
      src/style.rs

+ 0 - 202
Cargo.lock

@@ -57,12 +57,6 @@ dependencies = [
  "zerocopy 0.7.35",
 ]
 
-[[package]]
-name = "aliasable"
-version = "0.1.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "250f629c0161ad8107cf89319e990051fae62832fd343083bea452d93e2205fd"
-
 [[package]]
 name = "allocator-api2"
 version = "0.2.18"
@@ -368,12 +362,6 @@ version = "0.6.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb"
 
-[[package]]
-name = "bit_field"
-version = "0.10.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dc827186963e592360843fb5ba4b973e145841266c1357f7180c43526f2e5b61"
-
 [[package]]
 name = "bitflags"
 version = "1.3.2"
@@ -616,12 +604,6 @@ dependencies = [
  "unicode-width",
 ]
 
-[[package]]
-name = "color_quant"
-version = "1.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b"
-
 [[package]]
 name = "com"
 version = "0.6.0"
@@ -1078,22 +1060,6 @@ dependencies = [
  "pin-project-lite",
 ]
 
-[[package]]
-name = "exr"
-version = "1.72.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "887d93f60543e9a9362ef8a21beedd0a833c5d9610e18c67abe15a5963dcb1a4"
-dependencies = [
- "bit_field",
- "flume",
- "half",
- "lebe",
- "miniz_oxide",
- "rayon-core",
- "smallvec",
- "zune-inflate",
-]
-
 [[package]]
 name = "fast-srgb8"
 version = "1.0.0"
@@ -1171,15 +1137,6 @@ version = "1.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "8bf7cc16383c4b8d58b9905a8509f02926ce3058053c056376248d958c9df1e8"
 
-[[package]]
-name = "flume"
-version = "0.11.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "55ac459de2512911e4b674ce33cf20befaba382d05b62b008afc1c8b57cbf181"
-dependencies = [
- "spin 0.9.8",
-]
-
 [[package]]
 name = "fnv"
 version = "1.0.7"
@@ -1417,16 +1374,6 @@ dependencies = [
  "wasi",
 ]
 
-[[package]]
-name = "gif"
-version = "0.13.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3fb2d69b19215e18bb912fa30f7ce15846e301408695e44e0ef719f1da9e19f2"
-dependencies = [
- "color_quant",
- "weezl",
-]
-
 [[package]]
 name = "gimli"
 version = "0.29.0"
@@ -1608,12 +1555,6 @@ dependencies = [
  "winapi",
 ]
 
-[[package]]
-name = "heck"
-version = "0.4.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
-
 [[package]]
 name = "hermit-abi"
 version = "0.3.9"
@@ -1800,7 +1741,6 @@ dependencies = [
  "iced_renderer 0.13.0-dev",
  "iced_widget 0.13.0-dev",
  "iced_winit",
- "image",
  "thiserror",
 ]
 
@@ -1939,8 +1879,6 @@ dependencies = [
  "half",
  "iced_core 0.13.0-dev",
  "iced_futures 0.13.0-dev",
- "image",
- "kamadak-exif",
  "log",
  "lyon_path",
  "once_cell",
@@ -2095,7 +2033,6 @@ dependencies = [
  "iced_runtime 0.13.0-dev",
  "num-traits",
  "once_cell",
- "ouroboros",
  "rustc-hash 2.0.0",
  "thiserror",
  "unicode-segmentation",
@@ -2130,24 +2067,6 @@ dependencies = [
  "unicode-normalization",
 ]
 
-[[package]]
-name = "image"
-version = "0.24.9"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5690139d2f55868e080017335e4b94cb7414274c74f1669c84fb5feba2c9f69d"
-dependencies = [
- "bytemuck",
- "byteorder",
- "color_quant",
- "exr",
- "gif",
- "jpeg-decoder",
- "num-traits",
- "png",
- "qoi",
- "tiff",
-]
-
 [[package]]
 name = "indexmap"
 version = "2.3.0"
@@ -2173,15 +2092,6 @@ version = "2.9.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3"
 
-[[package]]
-name = "itertools"
-version = "0.12.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569"
-dependencies = [
- "either",
-]
-
 [[package]]
 name = "itoa"
 version = "1.0.11"
@@ -2219,15 +2129,6 @@ dependencies = [
  "libc",
 ]
 
-[[package]]
-name = "jpeg-decoder"
-version = "0.3.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f5d4a7da358eff58addd2877a45865158f0d78c911d43a5784ceb7bbf52833b0"
-dependencies = [
- "rayon",
-]
-
 [[package]]
 name = "js-sys"
 version = "0.3.69"
@@ -2237,15 +2138,6 @@ dependencies = [
  "wasm-bindgen",
 ]
 
-[[package]]
-name = "kamadak-exif"
-version = "0.5.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ef4fc70d0ab7e5b6bafa30216a6b48705ea964cdfc29c050f2412295eba58077"
-dependencies = [
- "mutate_once",
-]
-
 [[package]]
 name = "khronos-egl"
 version = "6.0.0"
@@ -2273,12 +2165,6 @@ dependencies = [
  "smallvec",
 ]
 
-[[package]]
-name = "lebe"
-version = "0.5.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8"
-
 [[package]]
 name = "libc"
 version = "0.2.155"
@@ -2503,12 +2389,6 @@ dependencies = [
  "windows-sys 0.52.0",
 ]
 
-[[package]]
-name = "mutate_once"
-version = "0.1.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "16cf681a23b4d0a43fc35024c176437f9dcd818db34e0f42ab456a0ee5ad497b"
-
 [[package]]
 name = "naga"
 version = "0.19.2"
@@ -2969,31 +2849,6 @@ dependencies = [
  "pin-project-lite",
 ]
 
-[[package]]
-name = "ouroboros"
-version = "0.18.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "944fa20996a25aded6b4795c6d63f10014a7a83f8be9828a11860b08c5fc4a67"
-dependencies = [
- "aliasable",
- "ouroboros_macro",
- "static_assertions",
-]
-
-[[package]]
-name = "ouroboros_macro"
-version = "0.18.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "39b0deead1528fd0e5947a8546a9642a9777c25f6e1e26f34c97b204bbb465bd"
-dependencies = [
- "heck",
- "itertools",
- "proc-macro2",
- "proc-macro2-diagnostics",
- "quote",
- "syn 2.0.72",
-]
-
 [[package]]
 name = "owned_ttf_parser"
 version = "0.24.0"
@@ -3255,34 +3110,12 @@ dependencies = [
  "unicode-ident",
 ]
 
-[[package]]
-name = "proc-macro2-diagnostics"
-version = "0.10.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn 2.0.72",
- "version_check",
- "yansi",
-]
-
 [[package]]
 name = "profiling"
 version = "1.0.15"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "43d84d1d7a6ac92673717f9f6d1518374ef257669c24ebc5ac25d5033828be58"
 
-[[package]]
-name = "qoi"
-version = "0.4.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7f6d64c71eb498fe9eae14ce4ec935c555749aef511cca85b5568910d6e48001"
-dependencies = [
- "bytemuck",
-]
-
 [[package]]
 name = "quick-xml"
 version = "0.34.0"
@@ -4034,9 +3867,6 @@ name = "spin"
 version = "0.9.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
-dependencies = [
- "lock_api",
-]
 
 [[package]]
 name = "spirv"
@@ -4181,17 +4011,6 @@ dependencies = [
  "syn 2.0.72",
 ]
 
-[[package]]
-name = "tiff"
-version = "0.9.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ba1310fcea54c6a9a4fd1aad794ecc02c31682f6bfbecdf460bf19533eed1e3e"
-dependencies = [
- "flate2",
- "jpeg-decoder",
- "weezl",
-]
-
 [[package]]
 name = "tiny-skia"
 version = "0.11.4"
@@ -4904,12 +4723,6 @@ dependencies = [
  "rustls-pki-types",
 ]
 
-[[package]]
-name = "weezl"
-version = "0.1.8"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "53a85b86a771b1c87058196170769dd264f66c0782acf1ae6cc51bfd64b39082"
-
 [[package]]
 name = "wgpu"
 version = "0.19.4"
@@ -5450,12 +5263,6 @@ version = "0.8.12"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "6a5cbf750400958819fb6178eaa83bee5cd9c29a26a40cc241df8c70fdd46984"
 
-[[package]]
-name = "yansi"
-version = "1.0.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049"
-
 [[package]]
 name = "yazi"
 version = "0.1.6"
@@ -5577,15 +5384,6 @@ version = "1.8.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde"
 
-[[package]]
-name = "zune-inflate"
-version = "0.2.54"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "73ab332fe2f6680068f3582b16a24f90ad7096d5d39b974d1c0aff0125116f02"
-dependencies = [
- "simd-adler32",
-]
-
 [[package]]
 name = "zvariant"
 version = "4.2.0"

+ 1 - 1
Cargo.toml

@@ -6,7 +6,7 @@ edition = "2021"
 # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
 
 [dependencies]
-iced = { git = "https://github.com/iced-rs/iced.git", features = ["canvas", "tokio", "lazy", "image", "advanced"] }
+iced = { git = "https://github.com/iced-rs/iced.git", features = ["canvas", "tokio"] }
 chrono = "0.4.37"
 tokio = { version = "1.37.0", features = ["full", "macros"] }
 tokio-tungstenite = "0.21.0"

+ 254 - 162
src/main.rs

@@ -7,8 +7,16 @@ mod screen;
 mod logger;
 
 use style::{ICON_FONT, ICON_BYTES, Icon};
-use screen::dashboard::{pane::{self, SerializablePane}, read_layout_from_file, write_json_to_file, Dashboard, LayoutId, PaneContent, PaneSettings, PaneState, SavedState, SerializableDashboard, SerializableState, Uuid};
-use data_providers::{binance, bybit, BinanceWsState, BybitWsState, UserWsState, Exchange, MarketEvents, TickMultiplier, Ticker, Timeframe, StreamType};
+
+use screen::{Notification, Error};
+use screen::dashboard::{
+    Dashboard,
+    pane::{self, SerializablePane}, Uuid, LayoutId,
+    PaneContent, PaneSettings, PaneState, 
+    SavedState, SerializableDashboard, SerializableState, 
+    read_layout_from_file, write_json_to_file, 
+};
+use data_providers::{binance, bybit, Exchange, MarketEvents, TickMultiplier, Ticker, Timeframe, StreamType};
 
 use charts::footprint::FootprintChart;
 use charts::heatmap::HeatmapChart;
@@ -25,9 +33,7 @@ use iced::{
     }, window::{self, Position}, Alignment, Color, Element, Length, Point, Renderer, Size, Subscription, Task, Theme
 };
 use iced::widget::pane_grid::{self, PaneGrid, Configuration};
-use iced::widget::{
-    container, row, scrollable, text
-};
+use iced::widget::{container, row, scrollable, text};
 
 fn main() -> iced::Result {
     logger::setup(false, false).expect("Failed to initialize logger");
@@ -176,7 +182,9 @@ pub enum Message {
     FetchDistributeKlines(StreamType, Result<Vec<data_providers::Kline>, std::string::String>),
     FetchDistributeTicks(StreamType, Result<f32, std::string::String>),
     Debug(String),
-    ErrorOccurred(String),
+    Notification(Notification),
+    ErrorOccurred(Error),
+    ClearNotification,
 
     ChartUserUpdate(charts::Message, Uuid),
     ShowLayoutModal,
@@ -221,27 +229,16 @@ pub enum Message {
 
     ResetCurrentLayout,
     LayoutSelected(LayoutId),
-    RefreshStreams,
 }
 
 struct State {
     layouts: HashMap<LayoutId, Dashboard>,
-
     last_active_layout: LayoutId,
-
     exchange_latency: Option<(u32, u32)>,
-
     listen_key: Option<String>,
-
-    binance_ws_state: BinanceWsState,
-    bybit_ws_state: BybitWsState,
-    user_ws_state: UserWsState,
-
-    ws_running: bool,
-
     feed_latency_cache: VecDeque<data_providers::FeedLatency>,
-
     pane_streams: HashMap<Exchange, HashMap<Ticker, HashSet<StreamType>>>,
+    notification: Option<Notification>,
 }
 
 impl State {
@@ -269,13 +266,10 @@ impl State {
                 layouts: saved_state.layouts,
                 last_active_layout,
                 listen_key: None,
-                binance_ws_state: BinanceWsState::Disconnected,
-                bybit_ws_state: BybitWsState::Disconnected,
-                user_ws_state: UserWsState::Disconnected,
-                ws_running: false,
                 exchange_latency: None,
                 feed_latency_cache: VecDeque::new(),
                 pane_streams,
+                notification: None,
             },
             Task::batch(tasks)
         )
@@ -285,12 +279,14 @@ impl State {
         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) => {
-                        log::error!("{err}");
-                        Task::none()
+                    Err(err) => {      
+                        Task::perform(
+                            async { err },
+                            move |err: Error| Message::ErrorOccurred(err)
+                        )
                     }
                 }
             },
@@ -304,9 +300,10 @@ impl State {
                         Task::none()
                     },
                     Err(err) => {
-                        log::error!("Failed to set min tick size: {err}");
-
-                        Task::none()
+                        Task::perform(
+                            async { err },
+                            move |err: Error| Message::ErrorOccurred(err)
+                        )
                     }
                 }
             },
@@ -320,9 +317,10 @@ impl State {
                         Task::none()
                     },
                     Err(err) => {
-                        log::error!("{err}");
-
-                        Task::none()
+                        Task::perform(
+                            async { err },
+                            move |err: Error| Message::ErrorOccurred(err)
+                        )
                     }
                 }
             },
@@ -338,33 +336,43 @@ impl State {
             
                             match exchange {
                                 Exchange::BinanceFutures => {
-                                    let fetch_klines = Task::perform(
-                                        binance::market_data::fetch_klines(*ticker, *timeframe)
-                                            .map_err(|err| format!("{err}")),
-                                        move |klines| Message::FetchEvent(klines, stream, pane_id)
+                                    tasks.push(
+                                        Task::perform(
+                                            binance::market_data::fetch_klines(*ticker, *timeframe)
+                                                .map_err(|err| format!("{err}")),
+                                            move |klines| Message::FetchEvent(klines, stream, pane_id)
+                                        )
                                     );
-            
-                                    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::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(fetch_klines);
                                 },
                             }
+
+                            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) => {
-                        log::error!("Failed to change timeframe: {err}");
+                        tasks.push(Task::perform(
+                            async { err },
+                            move |err: Error| Message::ErrorOccurred(err)
+                        ));
                     }
                 }
 
-                self.pane_streams = dashboard.get_all_diff_streams();
-            
                 Task::batch(tasks)
             },
             Message::ExchangeSelected(exchange, pane_id) => {
@@ -377,9 +385,10 @@ impl State {
                         Task::none()
                     },
                     Err(err) => {
-                        log::error!("{err}");
-
-                        Task::none()
+                        Task::perform(
+                            async { err },
+                            move |err: Error| Message::ErrorOccurred(err)
+                        )
                     }
                 }
             },
@@ -388,44 +397,53 @@ impl State {
                 
                 match dashboard.set_pane_ticksize(pane_id, tick_multiply) {
                     Ok(_) => {
-                        log::info!("Ticksize changed");
-            
                         Task::none()
                     },
-                    Err(err) => {
-                        log::error!("Failed to change ticksize: {err}");
-            
-                        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) => {
-                        log::error!("{err}");
+                        Task::perform(
+                            async { err },
+                            move |err: String| Message::ErrorOccurred(Error::FetchError(err))
+                        )
                     }
                 }
-
-                Task::none()
             },
             Message::MarketWsEvent(event) => {
                 let dashboard = self.get_mut_dashboard();
 
                 match event {
                     MarketEvents::Binance(event) => match event {
-                        binance::market_data::Event::Connected(connection) => {
-                            self.binance_ws_state = BinanceWsState::Connected(connection);
-                        }
-                        binance::market_data::Event::Disconnected => {
-                            self.binance_ws_state = BinanceWsState::Disconnected;
-                        }
                         binance::market_data::Event::DepthReceived(ticker, feed_latency, depth_update_t, depth, trades_buffer) => {                            
                             let stream_type = StreamType::DepthAndTrades {
                                 exchange: Exchange::BinanceFutures,
@@ -461,14 +479,11 @@ impl State {
                                     .remove(&stream_type);
                             }
                         }
+                        _ => {
+                            log::warn!("{event:?}");
+                        }
                     },
                     MarketEvents::Bybit(event) => match event {
-                        bybit::market_data::Event::Connected(connection) => {
-                            self.bybit_ws_state = BybitWsState::Connected(connection);
-                        }
-                        bybit::market_data::Event::Disconnected => {
-                            self.bybit_ws_state = BybitWsState::Disconnected;
-                        }
                         bybit::market_data::Event::DepthReceived(ticker, feed_latency, depth_update_t, depth, trades_buffer) => {
                             let stream_type = StreamType::DepthAndTrades {
                                 exchange: Exchange::BybitLinear,
@@ -504,6 +519,9 @@ impl State {
                                     .remove(&stream_type);
                             }
                         }
+                        _ => {
+                            log::warn!("{event:?}");
+                        }
                     },
                 }
 
@@ -679,15 +697,20 @@ impl State {
                         Task::none()
                     }
                     Err(err) => {
-                        log::error!("{err}");
-                        
-                        Task::none()
+                        Task::perform(
+                            async { err },
+                            move |err: Error| Message::ErrorOccurred(err)
+                        )
                     }
                 }
             },
             Message::SyncWithHeatmap(sync) => {   
-            
-                Task::none()
+                Task::perform(
+                    async {},
+                    move |_| Message::Notification(
+                        Notification::Warn("gonna have to reimplement that".to_string())
+                    )
+                )
             },
             Message::ShowLayoutModal => {
                 let dashboard = self.get_mut_dashboard();
@@ -701,8 +724,71 @@ impl State {
                 dashboard.show_layout_modal = false;
                 Task::none()
             },
+            Message::Notification(notification) => {
+                self.notification = Some(notification);
+
+                Task::perform(
+                    async { tokio::time::sleep(tokio::time::Duration::from_millis(4000)).await },
+                    move |_| Message::ClearNotification
+                )
+            },
             Message::ErrorOccurred(err) => {
-                log::error!("{err}");
+                match err {
+                    Error::FetchError(err) => {
+                        log::error!("{err}");
+
+                        Task::perform(
+                            async {},
+                            move |_| Message::Notification(
+                                Notification::Error(format!("Failed to fetch data: {err}"))
+                            )
+                        )
+                    },
+                    Error::PaneSetError(err) => {
+                        log::error!("{err}");
+
+                        Task::perform(
+                            async {},
+                            move |_| Message::Notification(
+                                Notification::Error(format!("Failed to set pane: {err}"))
+                            )
+                        )
+                    },
+                    Error::ParseError(err) => {
+                        log::error!("{err}");
+
+                        Task::perform(
+                            async {},
+                            move |_| Message::Notification(
+                                Notification::Error(format!("Failed to parse data: {err}"))
+                            )
+                        )
+                    },
+                    Error::StreamError(err) => {
+                        log::error!("{err}");
+
+                        Task::perform(
+                            async {},
+                            move |_| Message::Notification(
+                                Notification::Error(format!("Failed to fetch stream: {err}"))
+                            )
+                        )
+                    },
+                    Error::UnknownError(err) => {
+                        log::error!("{err}");
+
+                        Task::perform(
+                            async {},
+                            move |_| Message::Notification(
+                                Notification::Error(format!("{err}"))
+                            )
+                        )
+                    },
+                }
+            },
+            Message::ClearNotification => {
+                self.notification = None;
+
                 Task::none()
             },
             Message::PaneContentSelected(content, pane_id, pane_stream) => {
@@ -764,9 +850,9 @@ impl State {
                 if ["Footprint chart", "Candlestick chart", "Heatmap chart"].contains(&content.as_str()) {
                     for stream in pane_stream.iter() {
                         match stream {
-                            StreamType::Kline { exchange, ticker, timeframe } => {
+                            StreamType::Kline { exchange, ticker, .. } => {
                                 if ["Candlestick chart", "Footprint chart"].contains(&content.as_str()) {
-                                    tasks.push(create_fetch_klines_task(exchange, ticker, timeframe, *stream, pane_id));
+                                    tasks.push(create_fetch_klines_task(*stream, pane_id));
                                     
                                     if content == "Footprint chart" {
                                         tasks.push(create_fetch_ticksize_task(exchange, ticker, pane_id));
@@ -779,6 +865,15 @@ impl State {
                             _ => {}
                         }
                     }
+
+                    tasks.push(
+                        Task::perform(
+                            async {},
+                            move |_| Message::Notification(
+                                Notification::Info(format!("Fetching data for the {}...", content.to_lowercase()))
+                            )
+                        )
+                    );
                 }
                 
                 Task::batch(tasks)
@@ -795,31 +890,36 @@ impl State {
 
                 self.layouts.insert(self.last_active_layout, new_dashboard);
 
-                Task::none()
+                Task::perform(
+                    async {},
+                    move |_| Message::Notification(
+                        Notification::Info("Layout reset".to_string())
+                    )
+                )
             },
             Message::LayoutSelected(layout_id) => {
                 self.last_active_layout = layout_id;
             
-                Task::perform(
-                    async { tokio::time::sleep(tokio::time::Duration::from_millis(200)).await; },
-                    |_| Message::RefreshStreams
-                )
-            },
-            Message::RefreshStreams => {
                 let mut tasks = vec![];
 
                 self.pane_streams = self.get_dashboard().get_all_diff_streams();
 
-                let kline_tasks = klines_fetch_all_task(&self.pane_streams);
-
-                let ticksize_tasks = ticksize_fetch_all_task(&self.pane_streams);
+                tasks.push(
+                    Task::perform(
+                        async {},
+                        move |_| Message::Notification(Notification::Info("Fetching data...".to_string()))
+                    )
+                );
 
-                tasks.extend(kline_tasks);
-                tasks.extend(ticksize_tasks);
+                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();
 
@@ -835,8 +935,7 @@ impl State {
                 }
 
                 Task::none()
-            }
-            
+            },  
             Message::FetchDistributeTicks(stream_type, min_tick_size) => {
                 let dashboard = self.get_mut_dashboard();
 
@@ -852,7 +951,7 @@ impl State {
                 }
 
                 Task::none()
-            }
+            },
         }
     }
 
@@ -979,7 +1078,7 @@ impl State {
             .spacing(10)
             .align_y(Alignment::Center)
             .push(
-                tooltip(add_pane_button, "Manage Panes", tooltip::Position::Bottom).style(style::tooltip)
+                tooltip(add_pane_button, "Manage Layout", tooltip::Position::Bottom).style(style::tooltip)
             )
             .push(
                 tooltip(layout_lock_button, "Layout Lock", tooltip::Position::Bottom).style(style::tooltip)
@@ -989,53 +1088,45 @@ impl State {
             .spacing(10)
             .align_y(Alignment::Center);
 
-        if self.ws_running {
-            let exchange_latency_tooltip: String;
-            let mut highest_latency: i32 = 0;
-
-            if let Some((depth_latency, trade_latency)) = self.exchange_latency {
-                exchange_latency_tooltip = format!(
-                    "Feed Latencies\n->Depth: {depth_latency} ms\n->Trade: {trade_latency} ms",
-                );
-
-                highest_latency = std::cmp::max(depth_latency as i32, trade_latency as i32);
-            } else {
-                exchange_latency_tooltip = "No latency data".to_string();
-
-                highest_latency = 0;
+        if let Some(notification) = &self.notification {
+            match notification {
+                Notification::Info(string) => {
+                    ws_controls = ws_controls.push(
+                        container(
+                            Column::new()
+                                .padding(4)
+                                .push(
+                                    Text::new(format!("{string}"))
+                                        .size(14)
+                                )
+                        ).style(style::notification)
+                    );
+                },
+                Notification::Error(string) => {
+                    ws_controls = ws_controls.push(
+                        container(
+                            Column::new()
+                                .padding(4)
+                                .push(
+                                    Text::new(format!("err: {string}"))
+                                        .size(14)
+                                )
+                        ).style(style::notification)
+                    );
+                },
+                Notification::Warn(string) => {
+                    ws_controls = ws_controls.push(
+                        container(
+                            Column::new()
+                                .padding(4)
+                                .push(
+                                    Text::new(format!("warn: {string}"))
+                                        .size(14)
+                                )
+                        ).style(style::notification)
+                    );
+                },
             }
-
-            let exchange_latency_tooltip = Text::new(exchange_latency_tooltip).size(12);
-
-            let latency_emoji: &str = if highest_latency > 250 {
-                "🔴"
-            } else if highest_latency > 100 {
-                "🟠"
-            } else {
-                "🟢"
-            };
-                
-            let exchange_info = Row::new()
-                .spacing(5)
-                .align_y(Alignment::Center)
-                .push(
-                    Text::new(latency_emoji)
-                        .shaping(text::Shaping::Advanced).size(8)
-                )
-                .push(
-                    Column::new()
-                        .align_x(Alignment::Start)
-                        .push(
-                            Text::new(format!("{} ms", highest_latency)).size(10)
-                        )
-                );
-            
-            ws_controls = ws_controls.push(
-                Row::new()
-                    .spacing(10)
-                    .align_y(Alignment::Center)
-                    .push(tooltip(exchange_info, exchange_latency_tooltip, tooltip::Position::Bottom).style(style::tooltip))
-            );
         }
 
         let content = Column::new()
@@ -1720,23 +1811,24 @@ fn ticksize_fetch_all_task(stream_types: &HashMap<Exchange, HashMap<Ticker, Hash
 }
 
 fn create_fetch_klines_task(
-    exchange: &Exchange,
-    ticker: &Ticker,
-    timeframe: &Timeframe,
-    stream_clone: StreamType,
+    stream: StreamType,
     pane_id: Uuid,
 ) -> Task<Message> {
-    match exchange {
-        Exchange::BinanceFutures => Task::perform(
-            binance::market_data::fetch_klines(*ticker, *timeframe)
-                .map_err(|err| format!("{err}")),
-            move |klines| Message::FetchEvent(klines, stream_clone, pane_id),
-        ),
-        Exchange::BybitLinear => Task::perform(
-            bybit::market_data::fetch_klines(*ticker, *timeframe)
-                .map_err(|err| format!("{err}")),
-            move |klines| Message::FetchEvent(klines, stream_clone, pane_id),
-        ),
+    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(),
     }
 }
@@ -1751,14 +1843,14 @@ fn create_fetch_ticksize_task(
             binance::market_data::fetch_ticksize(*ticker),
             move |result| match result {
                 Ok(ticksize) => Message::SetMinTickSize(ticksize, pane_id),
-                Err(err) => Message::ErrorOccurred(err.to_string()),
+                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(err.to_string()),
+                Err(err) => Message::ErrorOccurred(Error::FetchError(err.to_string())),
             },
         ),
     }
@@ -1779,7 +1871,7 @@ pub fn events() -> Subscription<Event> {
 
 fn filtered_events(
     event: iced::Event,
-    status: iced::event::Status,
+    _status: iced::event::Status,
     window: window::Id,
 ) -> Option<Event> {
     match &event {

+ 17 - 1
src/screen.rs

@@ -1 +1,17 @@
-pub mod dashboard;
+pub mod dashboard;
+
+#[derive(Debug, Clone)]
+pub enum Notification {
+    Error(String),
+    Info(String),
+    Warn(String),
+}
+
+#[derive(Debug, Clone)]
+pub enum Error {
+    FetchError(String),
+    ParseError(String),
+    PaneSetError(String),
+    StreamError(String),
+    UnknownError(String),
+}

+ 16 - 14
src/screen/dashboard.rs

@@ -10,6 +10,8 @@ use crate::{
     }, StreamType
 };
 
+use super::{Error, Notification};
+
 use std::{collections::{HashMap, HashSet}, io::Read, rc::Rc};
 use iced::{widget::pane_grid::{self, Configuration}, Point, Size};
 
@@ -106,13 +108,13 @@ impl Dashboard {
         }
     }
 
-    pub fn get_pane_settings_mut(&mut self, pane_id: Uuid) -> Result<&mut PaneSettings, &str> {
+    pub fn get_pane_settings_mut(&mut self, pane_id: Uuid) -> Result<&mut PaneSettings, Error> {
         for (_, pane_state) in self.panes.iter_mut() {
             if pane_state.id == pane_id {
                 return Ok(&mut pane_state.settings);
             }
         }
-        Err("No pane found")
+        Err(Error::UnknownError("No pane found".to_string()))
     }
 
     pub fn set_pane_content(&mut self, pane_id: Uuid, content: PaneContent) -> Result<(), &str> {
@@ -137,7 +139,7 @@ impl Dashboard {
         Err("No pane found")
     }
 
-    pub fn set_pane_ticksize(&mut self, pane_id: Uuid, new_tick_multiply: TickMultiplier) -> Result<(), &str> {
+    pub fn set_pane_ticksize(&mut self, pane_id: Uuid, new_tick_multiply: TickMultiplier) -> Result<(), Error> {
         for (_, pane_state) in self.panes.iter_mut() {
             if pane_state.id == pane_id {
                 pane_state.settings.tick_multiply = Some(new_tick_multiply);
@@ -159,18 +161,18 @@ impl Dashboard {
                             return Ok(());
                         },
                         _ => {
-                            return Err("No footprint chart found");
+                            return Err(Error::UnknownError("No chart found to change ticksize".to_string()));
                         }
                     }
                 } else {
-                    return Err("No min tick size found");
+                    return Err(Error::UnknownError("No min tick size found".to_string()));
                 }
             }
         }
-        Err("No pane found")
+        Err(Error::UnknownError("No pane found to change ticksize".to_string()))
     }
     
-    pub fn set_pane_timeframe(&mut self, pane_id: Uuid, new_timeframe: Timeframe) -> Result<&StreamType, &str> {
+    pub fn set_pane_timeframe(&mut self, pane_id: Uuid, new_timeframe: Timeframe) -> Result<&StreamType, Error> {
         for (_, pane_state) in self.panes.iter_mut() {
             if pane_state.id == pane_id {
                 pane_state.settings.selected_timeframe = Some(new_timeframe);
@@ -195,10 +197,10 @@ impl Dashboard {
                 }
             }
         }
-        Err("No pane found")
+        Err(Error::UnknownError("No pane found to change tiemframe".to_string()))
     }
 
-    pub fn set_pane_size_filter(&mut self, pane_id: Uuid, new_size_filter: f32) -> Result<(), &str> {
+    pub fn set_pane_size_filter(&mut self, pane_id: Uuid, new_size_filter: f32) -> Result<(), Error> {
         for (_, pane_state) in self.panes.iter_mut() {
             if pane_state.id == pane_id {
                 pane_state.settings.trade_size_filter = Some(new_size_filter);
@@ -215,12 +217,12 @@ impl Dashboard {
                         return Ok(());
                     },
                     _ => {
-                        return Err("No footprint chart found");
+                        return Err(Error::UnknownError("No chart found".to_string()));
                     }
                 }
             }
         }
-        Err("No pane found")
+        Err(Error::UnknownError("No pane found".to_string()))
     }
 
     pub fn find_and_insert_ticksizes(&mut self, stream_type: &StreamType, tick_sizes: f32) -> Result<(), &str> {
@@ -371,7 +373,7 @@ impl Dashboard {
         }
     }
 
-    pub fn update_chart_state(&mut self, pane_id: Uuid, message: Message) -> Result<(), &str> {
+    pub fn update_chart_state(&mut self, pane_id: Uuid, message: Message) -> Result<(), Error> {
         for (_, pane_state) in self.panes.iter_mut() {
             if pane_state.id == pane_id {
                 match pane_state.content {
@@ -391,12 +393,12 @@ impl Dashboard {
                         return Ok(());
                     },
                     _ => {
-                        return Err("No chart found");
+                        return Err(Error::UnknownError("No chart found".to_string()));
                     }
                 }
             }
         }
-        Err("No pane found")
+        Err(Error::UnknownError("No pane found to update its state".to_string()))
     }
 
     pub fn get_all_diff_streams(&self) -> HashMap<Exchange, HashMap<Ticker, HashSet<StreamType>>> {

+ 15 - 0
src/style.rs

@@ -50,6 +50,21 @@ pub fn tooltip(theme: &Theme) -> Style {
     }
 }
 
+pub fn notification(theme: &Theme) -> Style {
+    let palette = theme.extended_palette();
+
+    Style {
+        text_color: Some(palette.background.weak.text),
+        background: Some(Color::BLACK.into()),
+        border: Border {
+            width: 1.0,
+            color: palette.background.weak.color,
+            radius: 2.0.into(),
+        },
+        ..Default::default()
+    }
+}
+
 pub fn title_bar_active(theme: &Theme) -> Style {
     let palette = theme.extended_palette();