Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions packages/rsnap-overlay/src/overlay.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4435,6 +4435,9 @@ impl OverlaySession {

edit_state.ime_preedit = None;
edit_state.ime_preedit_cursor_char_range = None;

edit_state.reset_caret_blink();

self.frozen_text_recent_input = None;

true
Expand All @@ -4452,6 +4455,8 @@ impl OverlaySession {
let changed = had_preedit || edit_state.text.pop().is_some();

if changed {
edit_state.reset_caret_blink();

self.frozen_text_recent_input = None;
}

Expand Down Expand Up @@ -4516,6 +4521,8 @@ impl OverlaySession {
edit_state.ime_preedit = normalized;
edit_state.ime_preedit_cursor_char_range = normalized_cursor_range;

edit_state.reset_caret_blink();

true
}

Expand Down
25 changes: 25 additions & 0 deletions packages/rsnap-overlay/src/overlay/aux_window_runtime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ impl OverlaySession {
self.maybe_keep_selection_flow_repaint();
self.maybe_keep_frozen_text_caret_repaint();
self.maybe_keep_frozen_capture_redraw();
self.maybe_request_due_egui_repaint(now);
self.maybe_tick_toolbar_window_warmup_redraw();
self.maybe_tick_loupe_window_warmup_redraw();
self.maybe_tick_live_cursor_tracking();
Expand Down Expand Up @@ -203,6 +204,30 @@ impl OverlaySession {
self.schedule_egui_repaint_after(FROZEN_TEXT_CARET_REPAINT_INTERVAL);
}

pub(super) fn maybe_request_due_egui_repaint(&self, now: Instant) {
if !self.take_due_egui_repaint_deadline(now) {
return;
}

self.request_redraw_all();
}

pub(super) fn take_due_egui_repaint_deadline(&self, now: Instant) -> bool {
let mut next_repaint =
self.egui_repaint_deadline.lock().unwrap_or_else(|err| err.into_inner());
let Some(deadline) = *next_repaint else {
return false;
};

if deadline > now {
return false;
}

*next_repaint = None;

true
}

pub(super) fn live_overlay_selection_flow_repaint_active(&self) -> bool {
if !self.config.selection_flow_enabled {
return false;
Expand Down
6 changes: 4 additions & 2 deletions packages/rsnap-overlay/src/overlay/rendering/affordances.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use std::sync::{Arc, OnceLock};
use std::time::Instant;

use egui::Context;
use egui::FontDefinitions;
Expand Down Expand Up @@ -449,8 +450,9 @@ impl WindowRenderer {
Self::paint_frozen_text_label(painter, text_edit.anchor, text, &font_id, color);

if let Some(caret_char_index) = caret_char_index
&& Self::frozen_text_caret_visible(painter.ctx().input(|i| i.time))
{
&& Self::frozen_text_caret_visible(
text_edit.caret_blink_elapsed_secs_at(Instant::now()),
) {
Self::paint_frozen_text_caret(
painter,
text_edit.anchor,
Expand Down
18 changes: 18 additions & 0 deletions packages/rsnap-overlay/src/overlay/session_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -299,16 +299,22 @@ pub(super) struct FrozenTextEditState {
pub(super) text: String,
pub(super) ime_preedit: Option<String>,
pub(super) ime_preedit_cursor_char_range: Option<(usize, usize)>,
pub(super) caret_blink_started_at: Instant,
pub(super) dragging: bool,
pub(super) drag_offset: Vec2,
}
impl FrozenTextEditState {
pub(super) fn new(anchor: Pos2) -> Self {
Self::new_at(anchor, Instant::now())
}

pub(super) fn new_at(anchor: Pos2, caret_blink_started_at: Instant) -> Self {
Self {
anchor,
text: String::new(),
ime_preedit: None,
ime_preedit_cursor_char_range: None,
caret_blink_started_at,
dragging: false,
drag_offset: Vec2::ZERO,
}
Expand All @@ -322,6 +328,18 @@ impl FrozenTextEditState {
self.ime_preedit.is_some()
}

pub(super) fn reset_caret_blink(&mut self) {
self.reset_caret_blink_at(Instant::now());
}

pub(super) fn reset_caret_blink_at(&mut self, caret_blink_started_at: Instant) {
self.caret_blink_started_at = caret_blink_started_at;
}

pub(super) fn caret_blink_elapsed_secs_at(&self, now: Instant) -> f64 {
now.duration_since(self.caret_blink_started_at).as_secs_f64()
}

pub(super) fn visible_text_and_caret_char_index(&self) -> (String, Option<usize>) {
let committed_char_count = self.text.chars().count();

Expand Down
67 changes: 67 additions & 0 deletions packages/rsnap-overlay/src/overlay/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1236,6 +1236,61 @@ fn backspace_clears_recent_input_dedupe_marker_before_cross_source_retype() {
assert_eq!(session.frozen_text_edit.as_ref().map(|edit| edit.text.as_str()), Some("A"));
}

#[test]
fn text_input_resets_frozen_text_caret_blink_phase() {
let monitor = test_monitor();
let mut session = OverlaySession::new();

session.state.begin_freeze(monitor);
session.state.finish_freeze(monitor, test_frozen_image());

session.state.frozen_capture_rect = Some(RectPoints::new(100, 120, 220, 180));
session.toolbar_state.selected_tool = FrozenToolbarTool::Text;

assert!(session.begin_frozen_text_edit_at(monitor, GlobalPoint::new(140, 160)));

let stale_started_at = Instant::now() - FROZEN_TEXT_CARET_REPAINT_INTERVAL * 3;

session.frozen_text_edit.as_mut().expect("text edit").reset_caret_blink_at(stale_started_at);

let generation = session.note_frozen_text_input_event();

assert!(session.append_text_to_frozen_edit_for_input_event(
FrozenTextInputSource::Key,
generation,
"A",
));

let edit_state = session.frozen_text_edit.as_ref().expect("text edit");

assert!(edit_state.caret_blink_started_at > stale_started_at);
assert!(edit_state.caret_blink_elapsed_secs_at(edit_state.caret_blink_started_at) == 0.0);
}

#[test]
fn ime_preedit_updates_reset_frozen_text_caret_blink_phase() {
let monitor = test_monitor();
let mut session = OverlaySession::new();

session.state.begin_freeze(monitor);
session.state.finish_freeze(monitor, test_frozen_image());

session.state.frozen_capture_rect = Some(RectPoints::new(100, 120, 220, 180));
session.toolbar_state.selected_tool = FrozenToolbarTool::Text;

assert!(session.begin_frozen_text_edit_at(monitor, GlobalPoint::new(140, 160)));

let stale_started_at = Instant::now() - FROZEN_TEXT_CARET_REPAINT_INTERVAL * 3;

session.frozen_text_edit.as_mut().expect("text edit").reset_caret_blink_at(stale_started_at);

assert!(session.set_frozen_text_ime_preedit(Some(String::from("汉")), Some((0, 0))));

let edit_state = session.frozen_text_edit.as_ref().expect("text edit");

assert!(edit_state.caret_blink_started_at > stale_started_at);
}

#[test]
fn ime_disabled_clears_frozen_text_preedit_state() {
let monitor = test_monitor();
Expand Down Expand Up @@ -1379,6 +1434,18 @@ fn frozen_text_caret_repaint_schedules_delayed_repaint_while_editing() {
);
}

#[test]
fn due_egui_repaint_deadline_is_consumed_once_ready() {
let session = OverlaySession::new();
let due_at = Instant::now() - Duration::from_millis(1);

*session.egui_repaint_deadline.lock().unwrap_or_else(|err| err.into_inner()) = Some(due_at);

assert!(session.take_due_egui_repaint_deadline(Instant::now()));
assert!(session.egui_repaint_deadline.lock().unwrap_or_else(|err| err.into_inner()).is_none());
assert!(!session.take_due_egui_repaint_deadline(Instant::now()));
}

#[test]
fn finish_frozen_text_editing_commits_current_toolbar_text_style() {
let monitor = test_monitor();
Expand Down
Loading