layout.rs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354
  1. use std::collections::HashMap;
  2. use std::fs::File;
  3. use std::io::{Read, Write};
  4. use std::path::Path;
  5. use iced::widget::pane_grid;
  6. use iced::{Point, Size, Theme};
  7. use serde::{Deserialize, Serialize};
  8. use crate::charts::indicators::{CandlestickIndicator, FootprintIndicator, HeatmapIndicator};
  9. use crate::data_providers::{Exchange, StreamType, Ticker};
  10. use crate::screen::dashboard::{Dashboard, PaneContent, PaneSettings, PaneState};
  11. use crate::pane::Axis;
  12. use crate::screen::UserTimezone;
  13. use crate::style;
  14. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Deserialize, Serialize)]
  15. pub enum LayoutId {
  16. Layout1,
  17. Layout2,
  18. Layout3,
  19. Layout4,
  20. }
  21. impl std::fmt::Display for LayoutId {
  22. fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
  23. match self {
  24. LayoutId::Layout1 => write!(f, "Layout 1"),
  25. LayoutId::Layout2 => write!(f, "Layout 2"),
  26. LayoutId::Layout3 => write!(f, "Layout 3"),
  27. LayoutId::Layout4 => write!(f, "Layout 4"),
  28. }
  29. }
  30. }
  31. impl LayoutId {
  32. pub const ALL: [LayoutId; 4] = [
  33. LayoutId::Layout1,
  34. LayoutId::Layout2,
  35. LayoutId::Layout3,
  36. LayoutId::Layout4,
  37. ];
  38. }
  39. #[derive(Debug, Clone, PartialEq, Copy, Deserialize, Serialize)]
  40. pub enum Sidebar {
  41. Left,
  42. Right,
  43. }
  44. impl Default for Sidebar {
  45. fn default() -> Self {
  46. Sidebar::Left
  47. }
  48. }
  49. impl std::fmt::Display for Sidebar {
  50. fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
  51. match self {
  52. Sidebar::Left => write!(f, "Left"),
  53. Sidebar::Right => write!(f, "Right"),
  54. }
  55. }
  56. }
  57. pub struct SavedState {
  58. pub layouts: HashMap<LayoutId, Dashboard>,
  59. pub selected_theme: SerializableTheme,
  60. pub favorited_tickers: Vec<(Exchange, Ticker)>,
  61. pub last_active_layout: LayoutId,
  62. pub window_size: Option<(f32, f32)>,
  63. pub window_position: Option<(f32, f32)>,
  64. pub timezone: UserTimezone,
  65. pub sidebar: Sidebar,
  66. }
  67. impl Default for SavedState {
  68. fn default() -> Self {
  69. let mut layouts = HashMap::new();
  70. layouts.insert(LayoutId::Layout1, Dashboard::default());
  71. layouts.insert(LayoutId::Layout2, Dashboard::default());
  72. layouts.insert(LayoutId::Layout3, Dashboard::default());
  73. layouts.insert(LayoutId::Layout4, Dashboard::default());
  74. SavedState {
  75. layouts,
  76. selected_theme: SerializableTheme::default(),
  77. favorited_tickers: Vec::new(),
  78. last_active_layout: LayoutId::Layout1,
  79. window_size: None,
  80. window_position: None,
  81. timezone: UserTimezone::default(),
  82. sidebar: Sidebar::default(),
  83. }
  84. }
  85. }
  86. #[derive(Debug, Clone)]
  87. pub struct SerializableTheme {
  88. pub theme: Theme,
  89. }
  90. impl Default for SerializableTheme {
  91. fn default() -> Self {
  92. Self {
  93. theme: Theme::Custom(style::custom_theme().into()),
  94. }
  95. }
  96. }
  97. impl Serialize for SerializableTheme {
  98. fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
  99. where
  100. S: serde::Serializer,
  101. {
  102. let theme_str = match self.theme {
  103. Theme::Ferra => "ferra",
  104. Theme::Dark => "dark",
  105. Theme::Light => "light",
  106. Theme::Dracula => "dracula",
  107. Theme::Nord => "nord",
  108. Theme::SolarizedLight => "solarized_light",
  109. Theme::SolarizedDark => "solarized_dark",
  110. Theme::GruvboxLight => "gruvbox_light",
  111. Theme::GruvboxDark => "gruvbox_dark",
  112. Theme::CatppuccinLatte => "catppuccino_latte",
  113. Theme::CatppuccinFrappe => "catppuccino_frappe",
  114. Theme::CatppuccinMacchiato => "catppuccino_macchiato",
  115. Theme::CatppuccinMocha => "catppuccino_mocha",
  116. Theme::TokyoNight => "tokyo_night",
  117. Theme::TokyoNightStorm => "tokyo_night_storm",
  118. Theme::TokyoNightLight => "tokyo_night_light",
  119. Theme::KanagawaWave => "kanagawa_wave",
  120. Theme::KanagawaDragon => "kanagawa_dragon",
  121. Theme::KanagawaLotus => "kanagawa_lotus",
  122. Theme::Moonfly => "moonfly",
  123. Theme::Nightfly => "nightfly",
  124. Theme::Oxocarbon => "oxocarbon",
  125. Theme::Custom(_) => "flowsurface",
  126. };
  127. theme_str.serialize(serializer)
  128. }
  129. }
  130. impl<'de> Deserialize<'de> for SerializableTheme {
  131. fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
  132. where
  133. D: serde::Deserializer<'de>,
  134. {
  135. let theme_str = String::deserialize(deserializer)?;
  136. let theme = match theme_str.as_str() {
  137. "ferra" => Theme::Ferra,
  138. "dark" => Theme::Dark,
  139. "light" => Theme::Light,
  140. "dracula" => Theme::Dracula,
  141. "nord" => Theme::Nord,
  142. "solarized_light" => Theme::SolarizedLight,
  143. "solarized_dark" => Theme::SolarizedDark,
  144. "gruvbox_light" => Theme::GruvboxLight,
  145. "gruvbox_dark" => Theme::GruvboxDark,
  146. "catppuccino_latte" => Theme::CatppuccinLatte,
  147. "catppuccino_frappe" => Theme::CatppuccinFrappe,
  148. "catppuccino_macchiato" => Theme::CatppuccinMacchiato,
  149. "catppuccino_mocha" => Theme::CatppuccinMocha,
  150. "tokyo_night" => Theme::TokyoNight,
  151. "tokyo_night_storm" => Theme::TokyoNightStorm,
  152. "tokyo_night_light" => Theme::TokyoNightLight,
  153. "kanagawa_wave" => Theme::KanagawaWave,
  154. "kanagawa_dragon" => Theme::KanagawaDragon,
  155. "kanagawa_lotus" => Theme::KanagawaLotus,
  156. "moonfly" => Theme::Moonfly,
  157. "nightfly" => Theme::Nightfly,
  158. "oxocarbon" => Theme::Oxocarbon,
  159. "flowsurface" => SerializableTheme::default().theme,
  160. _ => return Err(serde::de::Error::custom("Invalid theme")),
  161. };
  162. Ok(SerializableTheme { theme })
  163. }
  164. }
  165. #[derive(Debug, Clone, Deserialize, Serialize)]
  166. pub struct SerializableState {
  167. pub layouts: HashMap<LayoutId, SerializableDashboard>,
  168. pub selected_theme: SerializableTheme,
  169. pub favorited_tickers: Vec<(Exchange, Ticker)>,
  170. pub last_active_layout: LayoutId,
  171. pub window_size: Option<(f32, f32)>,
  172. pub window_position: Option<(f32, f32)>,
  173. pub timezone: UserTimezone,
  174. pub sidebar: Sidebar,
  175. }
  176. impl SerializableState {
  177. pub fn from_parts(
  178. layouts: HashMap<LayoutId, SerializableDashboard>,
  179. selected_theme: Theme,
  180. favorited_tickers: Vec<(Exchange, Ticker)>,
  181. last_active_layout: LayoutId,
  182. size: Option<Size>,
  183. position: Option<Point>,
  184. timezone: UserTimezone,
  185. sidebar: Sidebar,
  186. ) -> Self {
  187. SerializableState {
  188. layouts,
  189. selected_theme: SerializableTheme {
  190. theme: selected_theme,
  191. },
  192. favorited_tickers,
  193. last_active_layout,
  194. window_size: size.map(|s| (s.width, s.height)),
  195. window_position: position.map(|p| (p.x, p.y)),
  196. timezone,
  197. sidebar,
  198. }
  199. }
  200. }
  201. #[derive(Debug, Clone, Deserialize, Serialize)]
  202. pub struct SerializableDashboard {
  203. pub pane: SerializablePane,
  204. pub popout: Vec<(SerializablePane, (f32, f32), (f32, f32))>,
  205. }
  206. impl<'a> From<&'a Dashboard> for SerializableDashboard {
  207. fn from(dashboard: &'a Dashboard) -> Self {
  208. use pane_grid::Node;
  209. fn from_layout(
  210. panes: &pane_grid::State<PaneState>,
  211. node: pane_grid::Node,
  212. ) -> SerializablePane {
  213. match node {
  214. Node::Split {
  215. axis, ratio, a, b, ..
  216. } => SerializablePane::Split {
  217. axis: match axis {
  218. pane_grid::Axis::Horizontal => Axis::Horizontal,
  219. pane_grid::Axis::Vertical => Axis::Vertical,
  220. },
  221. ratio,
  222. a: Box::new(from_layout(panes, *a)),
  223. b: Box::new(from_layout(panes, *b)),
  224. },
  225. Node::Pane(pane) => panes
  226. .get(pane)
  227. .map_or(SerializablePane::Starter, SerializablePane::from),
  228. }
  229. }
  230. let main_window_layout = dashboard.panes.layout().clone();
  231. let popouts_layout: Vec<(SerializablePane, (Point, Size))> = dashboard
  232. .popout
  233. .iter()
  234. .map(|(_, (pane, specs))| (from_layout(pane, pane.layout().clone()), *specs))
  235. .collect();
  236. SerializableDashboard {
  237. pane: from_layout(&dashboard.panes, main_window_layout),
  238. popout: {
  239. popouts_layout
  240. .iter()
  241. .map(|(pane, (pos, size))| {
  242. (pane.clone(), (pos.x, pos.y), (size.width, size.height))
  243. })
  244. .collect()
  245. },
  246. }
  247. }
  248. }
  249. impl Default for SerializableDashboard {
  250. fn default() -> Self {
  251. Self {
  252. pane: SerializablePane::Starter,
  253. popout: vec![],
  254. }
  255. }
  256. }
  257. #[derive(Debug, Clone, Deserialize, Serialize)]
  258. pub enum SerializablePane {
  259. Split {
  260. axis: Axis,
  261. ratio: f32,
  262. a: Box<SerializablePane>,
  263. b: Box<SerializablePane>,
  264. },
  265. Starter,
  266. HeatmapChart {
  267. stream_type: Vec<StreamType>,
  268. settings: PaneSettings,
  269. indicators: Vec<HeatmapIndicator>,
  270. },
  271. FootprintChart {
  272. stream_type: Vec<StreamType>,
  273. settings: PaneSettings,
  274. indicators: Vec<FootprintIndicator>,
  275. },
  276. CandlestickChart {
  277. stream_type: Vec<StreamType>,
  278. settings: PaneSettings,
  279. indicators: Vec<CandlestickIndicator>,
  280. },
  281. TimeAndSales {
  282. stream_type: Vec<StreamType>,
  283. settings: PaneSettings,
  284. },
  285. }
  286. impl From<&PaneState> for SerializablePane {
  287. fn from(pane: &PaneState) -> Self {
  288. let pane_stream = pane.stream.clone();
  289. match &pane.content {
  290. PaneContent::Starter => SerializablePane::Starter,
  291. PaneContent::Heatmap(_, indicators) => SerializablePane::HeatmapChart {
  292. stream_type: pane_stream,
  293. settings: pane.settings,
  294. indicators: indicators.clone(),
  295. },
  296. PaneContent::Footprint(_, indicators) => SerializablePane::FootprintChart {
  297. stream_type: pane_stream,
  298. settings: pane.settings,
  299. indicators: indicators.clone(),
  300. },
  301. PaneContent::Candlestick(_, indicators) => SerializablePane::CandlestickChart {
  302. stream_type: pane_stream,
  303. settings: pane.settings,
  304. indicators: indicators.clone(),
  305. },
  306. PaneContent::TimeAndSales(_) => SerializablePane::TimeAndSales {
  307. stream_type: pane_stream,
  308. settings: pane.settings,
  309. },
  310. }
  311. }
  312. }
  313. pub fn write_json_to_file(json: &str, file_path: &str) -> std::io::Result<()> {
  314. let path = Path::new(file_path);
  315. let mut file = File::create(path)?;
  316. file.write_all(json.as_bytes())?;
  317. Ok(())
  318. }
  319. pub fn read_from_file(file_path: &str) -> Result<SerializableState, Box<dyn std::error::Error>> {
  320. let path = Path::new(file_path);
  321. let mut file = File::open(path)?;
  322. let mut contents = String::new();
  323. file.read_to_string(&mut contents)?;
  324. Ok(serde_json::from_str(&contents)?)
  325. }