| ... | @@ -20,13 +20,15 @@ use std::{ | ... | @@ -20,13 +20,15 @@ use std::{ |
| 20 | path::{Path, PathBuf}, | 20 | path::{Path, PathBuf}, |
| 21 | sync::{Arc, Mutex, OnceLock, Weak, mpsc}, | 21 | sync::{Arc, Mutex, OnceLock, Weak, mpsc}, |
| 22 | }; | 22 | }; |
| 23 | use ui::{Flags, Spec, children, fill, fit, px}; | 23 | use ui::{Flags, Id, Spec, children, fill, fit, px}; |
| 24 | | 24 | |
| 25 | /// The side of the picture sent, in pixels. | 25 | /// The side of the picture sent, in pixels. |
| 26 | const PICTURE: u32 = 96; | 26 | const PICTURE: u32 = 96; |
| 27 | const AVATAR: f32 = 22.0; | 27 | const AVATAR: f32 = 22.0; |
| 28 | /// The name flag above a caret. | 28 | /// The name flag above a caret, and how long it shows after the caret last moved. |
| 29 | const FLAG: f32 = 15.0; | 29 | const FLAG: f32 = 15.0; |
| | 30 | const FLAG_PAD: f32 = 4.0; |
| | 31 | const NAMED: std::time::Duration = std::time::Duration::from_secs(3); |
| 30 | /// The relay used where Options names none. | 32 | /// The relay used where Options names none. |
| 31 | pub(crate) const DEFAULT_RELAY: &str = "wss://relay.snowbound.paperclover.net"; | 33 | pub(crate) const DEFAULT_RELAY: &str = "wss://relay.snowbound.paperclover.net"; |
| 32 | /// How long opening a notebook joined for the first time waits for the computer sharing it. | 34 | /// How long opening a notebook joined for the first time waits for the computer sharing it. |
| ... | @@ -263,6 +265,8 @@ pub(crate) struct Peers { | ... | @@ -263,6 +265,8 @@ pub(crate) struct Peers { |
| 263 | reached: HashMap<String, bool>, | 265 | reached: HashMap<String, bool>, |
| 264 | /// Each peer's picture as drawn, cut to a circle, decoded once. | 266 | /// Each peer's picture as drawn, cut to a circle, decoded once. |
| 265 | pictures: HashMap<[u8; 16], Option<draw::RasterImage>>, | 267 | pictures: HashMap<[u8; 16], Option<draw::RasterImage>>, |
| | 268 | /// Where each peer's caret was last seen, and when it got there. |
| | 269 | moved: HashMap<[u8; 16], (Caret, std::time::Instant)>, |
| 266 | pub(crate) share: Option<crate::share::ShareDialog>, | 270 | pub(crate) share: Option<crate::share::ShareDialog>, |
| 267 | pub(crate) join: Option<crate::share::JoinDialog>, | 271 | pub(crate) join: Option<crate::share::JoinDialog>, |
| 268 | } | 272 | } |
| ... | @@ -768,8 +772,39 @@ impl State { | ... | @@ -768,8 +772,39 @@ impl State { |
| 768 | if !(0.0..right - left).contains(&x0) || y1 < 0.0 || y0 > bottom - top { | 772 | if !(0.0..right - left).contains(&x0) || y1 < 0.0 || y0 > bottom - top { |
| 769 | continue; | 773 | continue; |
| 770 | } | 774 | } |
| | 775 | let id = peer.hello.peer; |
| | 776 | let now = std::time::Instant::now(); |
| | 777 | let moved = match self.peers.moved.get(&id) { |
| | 778 | Some((at, when)) if *at == caret => *when, |
| | 779 | _ => { |
| | 780 | self.peers.moved.insert(id, (caret, now)); |
| | 781 | now |
| | 782 | } |
| | 783 | }; |
| | 784 | let quiet = now.saturating_duration_since(moved); |
| | 785 | if quiet < NAMED { |
| | 786 | self.ui.wake_after(NAMED - quiet); |
| | 787 | } |
| | 788 | let name = (peer.hello.name.split_whitespace().next()).unwrap_or("Someone"); |
| | 789 | // Below the caret where the page's top would cut the flag off. |
| | 790 | let below = y0 < FLAG; |
| | 791 | let flag_top = if below { y1 } else { y0 - FLAG }; |
| | 792 | // About the flag's width, for hovering it. |
| | 793 | let width = FLAG_PAD * 2.0 + self.ui.measure(name)[0] * 10.0 / self.ui.theme.font_size; |
| | 794 | let hovered = self.ui.pointer().is_some_and(|[px, py]| { |
| | 795 | let [px, py] = [px - left, py - top]; |
| | 796 | (x0 - 4.0..=x0 + 4.0).contains(&px) && (y0..=y1).contains(&py) |
| | 797 | || (x0 - 1.0..=x0 - 1.0 + width).contains(&px) |
| | 798 | && (flag_top..=flag_top + FLAG).contains(&py) |
| | 799 | }); |
| | 800 | let shown_flag = self.ui.animate( |
| | 801 | Id::ROOT.child((id, "flag-shown")), |
| | 802 | if quiet < NAMED || hovered { 1.0 } else { 0.0 }, |
| | 803 | ); |
| | 804 | let faded = |
| | 805 | |[red, green, blue, alpha]: [f32; 4], by: f32| [red, green, blue, alpha * by]; |
| 771 | self.ui.leaf( | 806 | self.ui.leaf( |
| 772 | (peer.hello.peer, "caret"), | 807 | (id, "caret"), |
| 773 | Spec { | 808 | Spec { |
| 774 | flags: Flags::FLOAT, | 809 | flags: Flags::FLOAT, |
| 775 | position: [x0 - 1.0, y0], | 810 | position: [x0 - 1.0, y0], |
| ... | @@ -778,28 +813,47 @@ impl State { | ... | @@ -778,28 +813,47 @@ impl State { |
| 778 | ..Spec::default() | 813 | ..Spec::default() |
| 779 | }, | 814 | }, |
| 780 | ); | 815 | ); |
| 781 | let name = peer | 816 | // The caret's cap, where its name is not shown. |
| 782 | .hello | | |
| 783 | .name | | |
| 784 | .split_whitespace() | | |
| 785 | .next() | | |
| 786 | .unwrap_or("Someone"); | | |
| 787 | self.ui.leaf( | 817 | self.ui.leaf( |
| 788 | (peer.hello.peer, "flag"), | 818 | (id, "cap"), |
| 789 | Spec { | 819 | Spec { |
| 790 | flags: Flags::FLOAT, | 820 | flags: Flags::FLOAT, |
| 791 | position: [x0 - 1.0, y0 - FLAG], | 821 | position: [x0 - 3.0, if below { y1 - 3.0 } else { y0 - 3.0 }], |
| 792 | size: [fit(), px(FLAG)], | 822 | size: [px(6.0), px(6.0)], |
| 793 | text: Some(name), | 823 | fill: Some(faded(color, 1.0 - shown_flag)), |
| 794 | font_size: Some(10.0), | | |
| 795 | bold: true, | | |
| 796 | color: Some([1.0; 4]), | | |
| 797 | fill: Some(color), | | |
| 798 | radius: 3.0, | 824 | radius: 3.0, |
| 799 | pad: [4.0, 0.0], | | |
| 800 | ..Spec::default() | 825 | ..Spec::default() |
| 801 | }, | 826 | }, |
| 802 | ); | 827 | ); |
| | 828 | if shown_flag > 0.0 { |
| | 829 | self.ui.leaf( |
| | 830 | (id, "flag"), |
| | 831 | Spec { |
| | 832 | flags: Flags::FLOAT, |
| | 833 | position: [x0 - 1.0, flag_top], |
| | 834 | size: [fit(), px(FLAG)], |
| | 835 | text: Some(name), |
| | 836 | font_size: Some(10.0), |
| | 837 | bold: true, |
| | 838 | color: Some(faded([1.0; 4], shown_flag)), |
| | 839 | fill: Some(faded(color, shown_flag)), |
| | 840 | radius: 3.0, |
| | 841 | pad: [FLAG_PAD, 0.0], |
| | 842 | ..Spec::default() |
| | 843 | }, |
| | 844 | ); |
| | 845 | // The corner on the caret square, so flag and caret are one shape. |
| | 846 | self.ui.leaf( |
| | 847 | (id, "joint"), |
| | 848 | Spec { |
| | 849 | flags: Flags::FLOAT, |
| | 850 | position: [x0 - 1.0, if below { y1 } else { y0 - 3.0 }], |
| | 851 | size: [px(3.0), px(3.0)], |
| | 852 | fill: Some(faded(color, shown_flag)), |
| | 853 | ..Spec::default() |
| | 854 | }, |
| | 855 | ); |
| | 856 | } |
| 803 | } | 857 | } |
| 804 | } | 858 | } |
| 805 | } | 859 | } |