5MUEECMJHU44FL5RDUR3VFBIWK3H4X2L5MVJ73J37PYHZWLUKU2AC WT3GA27PQ2AOAIGK65O3Q4DMX4AZDVNULBLRL6GF4QW6QCASUEAAC S2NVIFXRFER4SRA37WCT5XTXHDHAL5WIGGKY4A4XOTPLTKTZSRGQC FDDPOH5RRFK323PLIFJM2K2NWTSE2LW525YMHWJA6KIFPY2QVQNQC TEI5NQ3SCTU6JQIPU62B2AUXWRFSEU6DZYJG5T526G666VV5XXPAC L6KSEFQIWICZJ6HJUFKLZQDEH6X2QMFM4Z7ZZUGMLDMFF7EHRXWAC BFN2VHZS7VCBUHQ4S3CQ3LFQV2V4M6VANNAF32XMRFQVWRGYSZ6AC VJNWIGSX5OIDOI27UYU7JAXM4X3KSPEQ37P5UXBLH5KHF3VBTZFQC 23SFYK4Q5NKBPJG53PQNPWQH6UOUU2YKJEL7RLXYBRLJOJYV7AWQC OPXFZKEBDHZZLXEJ2JRDYBOJH6YIN7UZNZYHVHMWMQVDTE2ZD53QC MYGIBRRHHXPKVRAMQQRJTZH74L2XOK3SF7J57JPCRKSVRLZ2D6NQC XSZZB47UXR6KGYFZZQFQR63X2LDKOH6TPNNBRRGHUCI5JJ4JIWVAC ACDXXAX26ZJJFKJDGRC2GOSJY5JHQWCSTP55SYI6D6LH5UIRYUBAC I2AG42PAVOII4V4TWDJV5ZVNDIHKBRDT254BFQLFUIY723TW6CCQC WW36JYLR4AILV7RHQEDJWMX74P74B7G7DRBHH3O2V5TCHRTZJWZQC WIFVLV376GIMVTGVXBFWIPU7FR5O3SGIQ343KIJEBWB6UURTJZJQC SASAN2XCWDQ2VEHZ7TAQEN2R3Y7AG7JUGEFVRL4DZAGHXDFEZFRQC KEPKF3WO7ZZ2VB2DRVVTWTGPL7TCA52BMYUPHUNUJH6WO3HAT6JQC XHWLKCLDFUQFFHLFLFDC6WHK6RXRPQSXJG5AKGPER7R5AHVCRHUAC K5YUSV2WOLGMA75WKQWY2GRLQGPAFGVYTW3GMVTWEECXF4SXFEYAC KWTBNTO3QUUE2YADF6SYW6G6ZOKYEWRJQKIWDGZXR33S3YNDVIZQC 6YZAVBWU6E5FYOI5JGEIPXGZLIKAW6LS2AOFIQWEE5DMOPPCD5PQC "j" => Some(Msg::View(app::Msg::Cursor(cursor::Msg::Down))),"k" => Some(Msg::View(app::Msg::Cursor(cursor::Msg::Up))),"h" => Some(Msg::View(app::Msg::Cursor(cursor::Msg::Left))),"l" => {Some(Msg::View(app::Msg::Cursor(cursor::Msg::Right)))}
"j" => Some(Msg::View(app::Msg::Cursor(cursor::Msg::PressDir(cursor::Dir::Down),))),"k" => Some(Msg::View(app::Msg::Cursor(cursor::Msg::PressDir(cursor::Dir::Up),))),"h" => Some(Msg::View(app::Msg::Cursor(cursor::Msg::PressDir(cursor::Dir::Left),))),"l" => Some(Msg::View(app::Msg::Cursor(cursor::Msg::PressDir(cursor::Dir::Right),))),
Key::Named(key::Named::ArrowDown) => {Some(Msg::View(app::Msg::Cursor(cursor::Msg::Down)))}Key::Named(key::Named::ArrowUp) => {Some(Msg::View(app::Msg::Cursor(cursor::Msg::Up)))}Key::Named(key::Named::ArrowLeft) => {Some(Msg::View(app::Msg::Cursor(cursor::Msg::Left)))}Key::Named(key::Named::ArrowRight) => {Some(Msg::View(app::Msg::Cursor(cursor::Msg::Right)))}
Key::Named(key::Named::ArrowDown) => Some(Msg::View(app::Msg::Cursor(cursor::Msg::PressDir(cursor::Dir::Down)),)),Key::Named(key::Named::ArrowUp) => Some(Msg::View(app::Msg::Cursor(cursor::Msg::PressDir(cursor::Dir::Up)),)),Key::Named(key::Named::ArrowLeft) => Some(Msg::View(app::Msg::Cursor(cursor::Msg::PressDir(cursor::Dir::Left)),)),Key::Named(key::Named::ArrowRight) => Some(Msg::View(app::Msg::Cursor(cursor::Msg::PressDir(cursor::Dir::Right)),)),
Key::Named(_) | Key::Unidentified => None,}}});let key_release_subs = on_key_release(|key, mods| {if mods.is_empty() {match key {Key::Character(c) => match c.as_str() {"j" => Some(Msg::View(app::Msg::Cursor(cursor::Msg::ReleaseDir(cursor::Dir::Down),))),"k" => Some(Msg::View(app::Msg::Cursor(cursor::Msg::ReleaseDir(cursor::Dir::Up),))),"h" => Some(Msg::View(app::Msg::Cursor(cursor::Msg::ReleaseDir(cursor::Dir::Left),))),"l" => Some(Msg::View(app::Msg::Cursor(cursor::Msg::ReleaseDir(cursor::Dir::Right),))),_ => None,},Key::Named(key::Named::ArrowDown) => {Some(Msg::View(app::Msg::Cursor(cursor::Msg::ReleaseDir(cursor::Dir::Down,))))}Key::Named(key::Named::ArrowUp) => Some(Msg::View(app::Msg::Cursor(cursor::Msg::ReleaseDir(cursor::Dir::Up)),)),Key::Named(key::Named::ArrowLeft) => {Some(Msg::View(app::Msg::Cursor(cursor::Msg::ReleaseDir(cursor::Dir::Left,))))}Key::Named(key::Named::ArrowRight) => {Some(Msg::View(app::Msg::Cursor(cursor::Msg::ReleaseDir(cursor::Dir::Right,))))}
Msg::Down => select_down(state, files, repo, files_diffs, log_diffs),Msg::Up => select_up(state, files, repo, files_diffs, log_diffs),Msg::Left => select_left(state, repo),Msg::Right => select_right(state, repo, files_diffs, log_diffs),
Msg::PressDir(dir) => {let delta = state.held_key.as_ref().and_then(|HeldKey {dir: held_dir,last_tick,}| {(dir == *held_dir).then(|| {// Ceil to 50 ms, because the first key repeat in Iced// is delayed 500 ms and that creates too much of a jumpcmp::min(Duration::from_millis(50),Instant::now() - *last_tick,)})},);state.held_key = Some(HeldKey {dir,last_tick: Instant::now(),});match dir {Dir::Down => select_down(state,files,repo,files_diffs,log_diffs,delta,),Dir::Up => {select_up(state, files, repo, files_diffs, log_diffs, delta)}Dir::Left => select_left(state, repo),Dir::Right => select_right(state, repo, files_diffs, log_diffs),}}Msg::ReleaseDir(dir) => release(dir, state, files_diffs, log_diffs),
}}fn release<M>(dir: Dir,state: &mut State,files_diffs: &mut diff::FilesState,log_diffs: &mut diff::LogFilesAndState,) -> Task<M> {if state.held_key.as_ref().map(|key| key.dir) == Some(dir) {state.held_key = None;if let Dir::Down | Dir::Up = dir {match state.selection.as_mut() {Some(Selection::UntrackedFile {ix: _,path,diff_selected: true,}) => {let id_hash =file::id_parts_hash(&path, file::Kind::Untracked);if let Some(nav) = files_diffs.get_mut(&id_hash).and_then(|state| state.nav.as_mut()){nav.skip_delay = None;}}Some(Selection::ChangedFile {ix: _,path,diff_selected: true,}) => {let id_hash =file::id_parts_hash(&path, file::Kind::Changed);if let Some(nav) = files_diffs.get_mut(&id_hash).and_then(|state| state.nav.as_mut()){nav.skip_delay = None;}}Some(Selection::LogChange {ix: _,hash,message: _,file:Some(LogChangeFileSelection {ix: _,path,diff_selected: true,}),}) => {let id_hash = file::log_id_parts_hash(*hash, path);if let Some(nav) = log_diffs.diffs.get_mut(&id_hash).and_then(|diff| diff.state.nav.as_mut()){nav.skip_delay = None;}}Some(Selection::UntrackedFile { .. })| Some(Selection::ChangedFile { .. })| Some(Selection::LogChange { .. })| None => {}}}
/// Default duration delta in milliseconds if the scroll key was not previously/// heldconst DEFAULT_DELTA: f32 = 30.0;/// Scroll speed in pixels per secondconst SPEED: f32 = 500.0;/// Delay before skipping between sections while a scroll key is being heldconst SKIP_DELAY_MS: u64 = 400;
pub skip_delay: Option<SkipDelay>,}/// Delay that occurs when a scroll key is held before skipping to a next/// section. Every time the skip delay is checked, time `left` is updated with/// formula `left -= now - last_update`. When `left` reaches 0 the skip is/// performed. If the scroll button is released, the skip delay is cleared until/// next time.////// A singular key press doesn't trigger skip delay, it only happens if it was/// preceded by a non-skip scroll first and the key is continuously held.#[derive(Debug)]pub struct SkipDelay {/// Time when the last skip delay was updated.pub last_update: Instant,/// How much time is left before the skip can be performedpub left: Duration,}fn new_skip_delay() -> SkipDelay {let last_update = Instant::now();let left = Duration::from_millis(SKIP_DELAY_MS);SkipDelay { last_update, left }
match delay {Delay::Start => {nav.skip_delay = Some(new_skip_delay());}Delay::Apply => {if let Some(SkipDelay { last_update, left }) =nav.skip_delay.as_mut(){let now = Instant::now();let delta = now - *last_update;// Check if ready to skipif delta >= *left {// Prepare delay for the next onenav.skip_delay = Some(new_skip_delay());} else {*last_update = Instant::now();*left -= delta;// Delay scrollingreturn Task::none();}} else {// Prepare delay for the next onenav.skip_delay = Some(new_skip_delay());}}}
Some(saturating_sub(nav.offset, DELTA))} else if// offset + height < nav.offset &&offset + height
let offset_delta = delta.map(|delta| delta.as_millis() as f32).unwrap_or(DEFAULT_DELTA)/ 1_000.0* SPEED;Some((saturating_sub(nav.offset, offset_delta),Delay::Start,))} else if offset + height
// Scroll to the bottom of the prev sectionSome(saturating_sub(offset + height + VISIBLE_CONTEXT_HEIGHT,nav.height,
Some((if *height> nav.height- VISIBLE_CONTEXT_HEIGHT- VISIBLE_CONTEXT_HEIGHT{// The section doesn't fit within the frame, so// scroll to the bottom of itsaturating_sub(offset + height + VISIBLE_CONTEXT_HEIGHT,nav.height,)} else {// The section fits within the frame, so scroll to// the top of itsaturating_sub(*offset, VISIBLE_CONTEXT_HEIGHT)},Delay::Apply,
match delay {Delay::Start => {nav.skip_delay = Some(new_skip_delay());}Delay::Apply => {if let Some(SkipDelay { last_update, left }) =nav.skip_delay.as_mut(){let now = Instant::now();let delta = now - *last_update;// Check if ready to skipif delta >= *left {// Prepare delay for the next onenav.skip_delay = Some(new_skip_delay());} else {*last_update = Instant::now();*left -= delta;// Delay scrollingreturn Task::none();}} else {// Prepare delay for the next onenav.skip_delay = Some(new_skip_delay());}}}