style.rs 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705
  1. use iced::theme::{Custom, Palette};
  2. use iced::widget::button::Status;
  3. use iced::widget::container::{self, Style};
  4. use iced::widget::pane_grid::{Highlight, Line};
  5. use iced::widget::scrollable::{Rail, Scroller};
  6. use iced::widget::{text, Text};
  7. use iced::{widget, Border, Color, Font, Renderer, Shadow, Theme};
  8. pub const ICON_BYTES: &[u8] = include_bytes!("fonts/icons.ttf");
  9. pub const ICON_FONT: Font = Font::with_name("icons");
  10. pub enum Icon {
  11. Locked,
  12. Unlocked,
  13. ResizeFull,
  14. ResizeSmall,
  15. Close,
  16. Layout,
  17. Cog,
  18. Link,
  19. BinanceLogo,
  20. BybitLogo,
  21. Search,
  22. Sort,
  23. SortDesc,
  24. SortAsc,
  25. Star,
  26. StarFilled,
  27. Return,
  28. Popout,
  29. ChartOutline,
  30. }
  31. impl From<Icon> for char {
  32. fn from(icon: Icon) -> Self {
  33. match icon {
  34. Icon::Locked => '\u{E800}',
  35. Icon::Unlocked => '\u{E801}',
  36. Icon::Search => '\u{E802}',
  37. Icon::ResizeFull => '\u{E803}',
  38. Icon::ResizeSmall => '\u{E804}',
  39. Icon::Close => '\u{E805}',
  40. Icon::Layout => '\u{E806}',
  41. Icon::Link => '\u{E807}',
  42. Icon::BybitLogo => '\u{E808}',
  43. Icon::BinanceLogo => '\u{E809}',
  44. Icon::Cog => '\u{E810}',
  45. Icon::Sort => '\u{F0DC}',
  46. Icon::SortDesc => '\u{F0DD}',
  47. Icon::SortAsc => '\u{F0DE}',
  48. Icon::Star => '\u{E80A}',
  49. Icon::StarFilled => '\u{E80B}',
  50. Icon::Return => '\u{E80C}',
  51. Icon::Popout => '\u{E80D}',
  52. Icon::ChartOutline => '\u{E80E}',
  53. }
  54. }
  55. }
  56. pub fn get_icon_text<'a>(icon: Icon, size: u16) -> Text<'a, Theme, Renderer> {
  57. text(char::from(icon).to_string())
  58. .font(ICON_FONT)
  59. .size(size)
  60. }
  61. pub fn custom_theme() -> Custom {
  62. Custom::new(
  63. "Flowsurface".to_string(),
  64. Palette {
  65. background: Color::from_rgb8(24, 22, 22),
  66. text: Color::from_rgb8(197, 201, 197),
  67. primary: Color::from_rgb8(200, 200, 200),
  68. success: Color::from_rgb8(81, 205, 160),
  69. danger: Color::from_rgb8(192, 80, 77),
  70. warning: Color::from_rgb8(238, 216, 139),
  71. },
  72. )
  73. }
  74. #[cfg(target_os = "macos")]
  75. pub fn branding_text(theme: &Theme) -> iced::widget::text::Style {
  76. let palette = theme.extended_palette();
  77. iced::widget::text::Style {
  78. color: Some(
  79. palette
  80. .secondary
  81. .weak
  82. .color
  83. .scale_alpha(if palette.is_dark { 0.1 } else { 0.8 })
  84. ),
  85. }
  86. }
  87. // Tooltips
  88. pub fn tooltip(theme: &Theme) -> Style {
  89. let palette = theme.extended_palette();
  90. Style {
  91. background: Some(palette.background.weak.color.into()),
  92. border: Border {
  93. width: 1.0,
  94. color: palette.secondary.weak.color,
  95. radius: 4.0.into(),
  96. },
  97. ..Default::default()
  98. }
  99. }
  100. // Buttons
  101. pub fn button_transparent(
  102. theme: &Theme,
  103. status: Status,
  104. is_active: bool,
  105. ) -> iced::widget::button::Style {
  106. let palette = theme.extended_palette();
  107. let color_alpha = if palette.is_dark { 0.2 } else { 0.6 };
  108. match status {
  109. Status::Active => iced::widget::button::Style {
  110. background: if is_active {
  111. Some(palette.secondary.weak.color.scale_alpha(color_alpha).into())
  112. } else {
  113. None
  114. },
  115. text_color: palette.background.base.text,
  116. border: Border {
  117. radius: 3.0.into(),
  118. ..Default::default()
  119. },
  120. ..Default::default()
  121. },
  122. Status::Pressed => iced::widget::button::Style {
  123. text_color: palette.background.base.text,
  124. background: Some(
  125. palette
  126. .background
  127. .strong
  128. .color
  129. .scale_alpha(color_alpha)
  130. .into(),
  131. ),
  132. border: Border {
  133. radius: 3.0.into(),
  134. ..Default::default()
  135. },
  136. ..Default::default()
  137. },
  138. Status::Hovered => iced::widget::button::Style {
  139. background: if palette.is_dark {
  140. Some(palette.background.weak.color.into())
  141. } else {
  142. Some(palette.background.strong.color.into())
  143. },
  144. text_color: palette.background.base.text,
  145. border: Border {
  146. radius: 3.0.into(),
  147. ..Default::default()
  148. },
  149. ..Default::default()
  150. },
  151. Status::Disabled => iced::widget::button::Style {
  152. background: if is_active {
  153. None
  154. } else {
  155. Some(palette.secondary.weak.color.scale_alpha(color_alpha).into())
  156. },
  157. text_color: palette.background.base.text,
  158. border: Border {
  159. radius: 3.0.into(),
  160. ..Default::default()
  161. },
  162. ..Default::default()
  163. },
  164. }
  165. }
  166. pub fn button_modifier(
  167. theme: &Theme,
  168. status: Status,
  169. disabled: bool,
  170. ) -> iced::widget::button::Style {
  171. let palette = theme.extended_palette();
  172. let color_alpha = if palette.is_dark { 0.2 } else { 0.6 };
  173. match status {
  174. Status::Active => iced::widget::button::Style {
  175. background: if disabled {
  176. if palette.is_dark {
  177. Some(
  178. palette
  179. .background
  180. .weak
  181. .color
  182. .scale_alpha(color_alpha)
  183. .into(),
  184. )
  185. } else {
  186. Some(
  187. palette
  188. .background
  189. .base
  190. .color
  191. .scale_alpha(color_alpha)
  192. .into(),
  193. )
  194. }
  195. } else {
  196. Some(
  197. palette
  198. .background
  199. .strong
  200. .color
  201. .scale_alpha(color_alpha)
  202. .into(),
  203. )
  204. },
  205. text_color: palette.background.base.text,
  206. border: Border {
  207. radius: 3.0.into(),
  208. ..Default::default()
  209. },
  210. ..Default::default()
  211. },
  212. Status::Pressed => iced::widget::button::Style {
  213. text_color: palette.background.base.text,
  214. background: Some(
  215. palette
  216. .background
  217. .strong
  218. .color
  219. .scale_alpha(color_alpha)
  220. .into(),
  221. ),
  222. border: Border {
  223. radius: 3.0.into(),
  224. ..Default::default()
  225. },
  226. ..Default::default()
  227. },
  228. Status::Hovered => iced::widget::button::Style {
  229. background: if palette.is_dark {
  230. Some(palette.background.weak.color.into())
  231. } else {
  232. Some(palette.background.strong.color.into())
  233. },
  234. text_color: palette.background.base.text,
  235. border: Border {
  236. radius: 3.0.into(),
  237. ..Default::default()
  238. },
  239. ..Default::default()
  240. },
  241. Status::Disabled => iced::widget::button::Style {
  242. background: if disabled {
  243. None
  244. } else {
  245. Some(palette.secondary.weak.color.scale_alpha(color_alpha).into())
  246. },
  247. text_color: palette.background.base.text,
  248. border: Border {
  249. radius: 3.0.into(),
  250. ..Default::default()
  251. },
  252. ..Default::default()
  253. },
  254. }
  255. }
  256. // Panes
  257. pub fn pane_grid(theme: &Theme) -> widget::pane_grid::Style {
  258. let palette = theme.extended_palette();
  259. widget::pane_grid::Style {
  260. hovered_region: Highlight {
  261. background: palette.background.strong.color.into(),
  262. border: Border {
  263. width: 1.0,
  264. color: palette.primary.base.color,
  265. radius: 4.0.into(),
  266. },
  267. },
  268. picked_split: Line {
  269. color: palette.primary.strong.color,
  270. width: 4.0,
  271. },
  272. hovered_split: Line {
  273. color: palette.primary.weak.color,
  274. width: 4.0,
  275. },
  276. }
  277. }
  278. pub fn title_bar(theme: &Theme) -> Style {
  279. let palette = theme.extended_palette();
  280. Style {
  281. background: {
  282. if palette.is_dark {
  283. Some(palette.background.weak.color.scale_alpha(0.1).into())
  284. } else {
  285. Some(palette.background.strong.color.scale_alpha(0.1).into())
  286. }
  287. },
  288. ..Default::default()
  289. }
  290. }
  291. pub fn pane_primary(theme: &Theme, is_focused: bool) -> Style {
  292. let palette = theme.extended_palette();
  293. Style {
  294. text_color: Some(palette.background.base.text),
  295. background: Some(
  296. palette
  297. .background
  298. .weak
  299. .color
  300. .scale_alpha(if palette.is_dark { 0.1 } else { 0.6 })
  301. .into(),
  302. ),
  303. border: {
  304. if is_focused {
  305. Border {
  306. width: 1.0,
  307. color: {
  308. if palette.is_dark {
  309. palette.background.strong.color.scale_alpha(0.4)
  310. } else {
  311. palette.background.strong.color.scale_alpha(0.8)
  312. }
  313. },
  314. radius: 4.0.into(),
  315. }
  316. } else {
  317. Border {
  318. width: 1.0,
  319. color: {
  320. if palette.is_dark {
  321. palette.background.weak.color.scale_alpha(0.2)
  322. } else {
  323. palette.background.strong.color.scale_alpha(0.2)
  324. }
  325. },
  326. radius: 2.0.into(),
  327. }
  328. }
  329. },
  330. ..Default::default()
  331. }
  332. }
  333. // Modals
  334. pub fn chart_modal(theme: &Theme) -> Style {
  335. let palette = theme.extended_palette();
  336. Style {
  337. text_color: Some(palette.background.base.text),
  338. background: Some(
  339. Color {
  340. a: 0.99,
  341. ..palette.background.base.color
  342. }
  343. .into(),
  344. ),
  345. border: Border {
  346. width: 1.0,
  347. color: palette.secondary.weak.color,
  348. radius: 6.0.into(),
  349. },
  350. shadow: Shadow {
  351. offset: iced::Vector { x: 0.0, y: 0.0 },
  352. blur_radius: 12.0,
  353. color: Color::BLACK.scale_alpha(
  354. if palette.is_dark {
  355. 0.4
  356. } else {
  357. 0.2
  358. }
  359. ),
  360. },
  361. ..Default::default()
  362. }
  363. }
  364. pub fn dashboard_modal(theme: &Theme) -> Style {
  365. let palette = theme.extended_palette();
  366. Style {
  367. background: Some(
  368. Color {
  369. a: 0.99,
  370. ..palette.background.base.color
  371. }
  372. .into(),
  373. ),
  374. border: Border {
  375. width: 1.0,
  376. color: palette.secondary.weak.color,
  377. radius: 6.0.into(),
  378. },
  379. shadow: Shadow {
  380. offset: iced::Vector { x: 0.0, y: 0.0 },
  381. blur_radius: 20.0,
  382. color: Color::BLACK.scale_alpha(
  383. if palette.is_dark {
  384. 0.4
  385. } else {
  386. 0.2
  387. }
  388. ),
  389. },
  390. ..Default::default()
  391. }
  392. }
  393. pub fn modal_container(theme: &Theme) -> Style {
  394. let palette = theme.extended_palette();
  395. let color = if palette.is_dark {
  396. palette.background.weak.color.scale_alpha(0.6)
  397. } else {
  398. palette.background.strong.color.scale_alpha(0.6)
  399. };
  400. Style {
  401. text_color: Some(palette.background.base.text),
  402. background: Some(color.into()),
  403. border: Border {
  404. width: 1.0,
  405. color,
  406. radius: 6.0.into(),
  407. },
  408. shadow: Shadow {
  409. offset: iced::Vector { x: 0.0, y: 0.0 },
  410. blur_radius: 2.0,
  411. color: Color::BLACK.scale_alpha(
  412. if palette.is_dark {
  413. 0.8
  414. } else {
  415. 0.2
  416. }
  417. ),
  418. },
  419. }
  420. }
  421. pub fn sorter_container(theme: &Theme) -> Style {
  422. let palette = theme.extended_palette();
  423. let color = if palette.is_dark {
  424. palette.background.weak.color.scale_alpha(0.4)
  425. } else {
  426. palette.background.strong.color.scale_alpha(0.4)
  427. };
  428. Style {
  429. text_color: Some(palette.background.base.text),
  430. background: Some(color.into()),
  431. border: Border {
  432. width: 1.0,
  433. color,
  434. radius: 3.0.into(),
  435. },
  436. shadow: Shadow {
  437. offset: iced::Vector { x: 0.0, y: 0.0 },
  438. blur_radius: 2.0,
  439. color: Color::BLACK.scale_alpha(
  440. if palette.is_dark {
  441. 0.8
  442. } else {
  443. 0.2
  444. }
  445. ),
  446. },
  447. }
  448. }
  449. // Time&Sales Table
  450. pub fn ts_table_container(theme: &Theme, is_sell: bool, color_alpha: f32) -> Style {
  451. let palette = theme.extended_palette();
  452. let color = if is_sell {
  453. palette.danger.base.color
  454. } else {
  455. palette.success.base.color
  456. };
  457. Style {
  458. text_color: color.into(),
  459. border: Border {
  460. width: 1.0,
  461. color: color.scale_alpha(color_alpha),
  462. ..Border::default()
  463. },
  464. ..Default::default()
  465. }
  466. }
  467. // Tickers Table
  468. pub fn search_input(
  469. theme: &Theme,
  470. status: widget::text_input::Status,
  471. ) -> widget::text_input::Style {
  472. let palette = theme.extended_palette();
  473. match status {
  474. widget::text_input::Status::Active => widget::text_input::Style {
  475. background: palette.background.weak.color.into(),
  476. border: Border {
  477. radius: 3.0.into(),
  478. width: 1.0,
  479. color: palette.secondary.base.color,
  480. },
  481. icon: palette.background.strong.text,
  482. placeholder: palette.background.base.text,
  483. value: palette.background.weak.text,
  484. selection: palette.background.strong.color,
  485. },
  486. widget::text_input::Status::Hovered => widget::text_input::Style {
  487. background: palette.background.weak.color.into(),
  488. border: Border {
  489. radius: 3.0.into(),
  490. width: 1.0,
  491. color: palette.secondary.strong.color,
  492. },
  493. icon: palette.background.strong.text,
  494. placeholder: palette.background.base.text,
  495. value: palette.background.weak.text,
  496. selection: palette.background.strong.color,
  497. },
  498. widget::text_input::Status::Focused { .. } => widget::text_input::Style {
  499. background: palette.background.weak.color.into(),
  500. border: Border {
  501. radius: 3.0.into(),
  502. width: 2.0,
  503. color: palette.secondary.strong.color,
  504. },
  505. icon: palette.background.strong.text,
  506. placeholder: palette.background.base.text,
  507. value: palette.background.weak.text,
  508. selection: palette.background.strong.color,
  509. },
  510. widget::text_input::Status::Disabled => widget::text_input::Style {
  511. background: palette.background.weak.color.into(),
  512. border: Border {
  513. radius: 3.0.into(),
  514. width: 1.0,
  515. color: palette.secondary.weak.color,
  516. },
  517. icon: palette.background.weak.text,
  518. placeholder: palette.background.weak.text,
  519. value: palette.background.weak.text,
  520. selection: palette.background.weak.text,
  521. },
  522. }
  523. }
  524. pub fn ticker_card(theme: &Theme, _color_alpha: f32) -> Style {
  525. let palette = theme.extended_palette();
  526. let color_alpha = if palette.is_dark { 0.2 } else { 0.8 };
  527. Style {
  528. background: Some(
  529. palette
  530. .background
  531. .weak
  532. .color
  533. .scale_alpha(color_alpha)
  534. .into(),
  535. ),
  536. border: Border {
  537. radius: 4.0.into(),
  538. width: 1.0,
  539. ..Border::default()
  540. },
  541. ..Default::default()
  542. }
  543. }
  544. pub fn ticker_card_bar(theme: &Theme, color_alpha: f32) -> Style {
  545. let palette = theme.extended_palette();
  546. Style {
  547. background: {
  548. if color_alpha > 0.0 {
  549. Some(palette.success.strong.color.scale_alpha(color_alpha).into())
  550. } else {
  551. Some(palette.danger.strong.color.scale_alpha(-color_alpha).into())
  552. }
  553. },
  554. border: Border {
  555. radius: 4.0.into(),
  556. width: 1.0,
  557. color: if color_alpha > 0.0 {
  558. palette.success.strong.color.scale_alpha(color_alpha)
  559. } else {
  560. palette.danger.strong.color.scale_alpha(-color_alpha)
  561. },
  562. },
  563. ..Default::default()
  564. }
  565. }
  566. pub fn ticker_card_button(theme: &Theme, status: Status) -> iced::widget::button::Style {
  567. let palette = theme.extended_palette();
  568. match status {
  569. Status::Hovered => iced::widget::button::Style {
  570. text_color: palette.background.base.text,
  571. background: Some(palette.background.weak.color.scale_alpha(0.1).into()),
  572. border: Border {
  573. radius: 4.0.into(),
  574. width: 1.0,
  575. color: {
  576. if palette.is_dark {
  577. palette.background.strong.color.scale_alpha(0.4)
  578. } else {
  579. palette.background.strong.color.scale_alpha(0.8)
  580. }
  581. },
  582. },
  583. ..Default::default()
  584. },
  585. _ => iced::widget::button::Style {
  586. text_color: palette.background.base.text,
  587. ..Default::default()
  588. },
  589. }
  590. }
  591. // Scrollable
  592. pub fn scroll_bar(theme: &Theme, status: widget::scrollable::Status) -> widget::scrollable::Style {
  593. let palette = theme.extended_palette();
  594. let light_factor = if palette.is_dark { 1.0 } else { 4.0 };
  595. let (rail_bg, scroller_bg) = match status {
  596. widget::scrollable::Status::Dragged { .. }
  597. | widget::scrollable::Status::Hovered { .. } => {
  598. (
  599. palette.background.weak.color.scale_alpha(0.2 * light_factor),
  600. palette.secondary.weak.color.scale_alpha(0.8 * light_factor),
  601. )
  602. },
  603. _ => (
  604. palette.background.weak.color.scale_alpha(0.1 * light_factor),
  605. palette.secondary.weak.color.scale_alpha(0.4 * light_factor),
  606. ),
  607. };
  608. let rail = Rail {
  609. background: Some(iced::Background::Color(rail_bg)),
  610. border: Border {
  611. radius: 4.0.into(),
  612. width: 1.0,
  613. color: Color::TRANSPARENT,
  614. },
  615. scroller: Scroller {
  616. color: scroller_bg,
  617. border: Border {
  618. radius: 4.0.into(),
  619. width: 0.0,
  620. color: Color::TRANSPARENT,
  621. },
  622. },
  623. };
  624. widget::scrollable::Style {
  625. container: container::Style {
  626. text_color: None,
  627. background: None,
  628. border: Border {
  629. radius: 4.0.into(),
  630. width: 1.0,
  631. color: Color::TRANSPARENT,
  632. },
  633. shadow: Shadow::default(),
  634. },
  635. vertical_rail: rail,
  636. horizontal_rail: rail,
  637. gap: None,
  638. }
  639. }
  640. // custom widgets
  641. pub fn split_ruler(theme: &Theme) -> iced::widget::rule::Style {
  642. let palette = theme.extended_palette();
  643. iced::widget::rule::Style {
  644. color: {
  645. if palette.is_dark {
  646. palette.background.weak.color.scale_alpha(0.2)
  647. } else {
  648. palette.background.strong.color.scale_alpha(0.2)
  649. }
  650. },
  651. width: 1,
  652. radius: iced::border::Radius::default(),
  653. fill_mode: iced::widget::rule::FillMode::Full,
  654. }
  655. }