notedeck

One damus client to rule them all
git clone git://jb55.com/notedeck
Log | Files | Refs | README | LICENSE

commit 173972f920c130351455a9abe0252c9d5349f021
parent 31ec21ea02f21c546cb1ba55359363408003911e
Author: kernelkind <kernelkind@gmail.com>
Date:   Sun, 27 Jul 2025 10:10:24 -0400

mentions: don't lose focus after select mention

Closes: https://github.com/damus-io/notedeck/issues/728

Signed-off-by: kernelkind <kernelkind@gmail.com>

Diffstat:
Mcrates/notedeck_columns/src/post.rs | 56+++++++++++++++++++++++++++++++++++++++++++++++---------
Mcrates/notedeck_columns/src/ui/note/post.rs | 20+++++++++++++++-----
2 files changed, 62 insertions(+), 14 deletions(-)

diff --git a/crates/notedeck_columns/src/post.rs b/crates/notedeck_columns/src/post.rs @@ -1,4 +1,8 @@ -use egui::{text::LayoutJob, TextBuffer, TextFormat}; +use egui::{ + text::{CCursor, CCursorRange, LayoutJob}, + text_edit::TextEditOutput, + TextBuffer, TextEdit, TextFormat, +}; use enostr::{FullKeypair, Pubkey}; use nostrdb::{Note, NoteBuilder, NoteReply}; use std::{ @@ -270,6 +274,36 @@ impl Default for PostBuffer { } } +/// New cursor index (indexed by characters) after operation is performed +#[must_use = "must call MentionSelectedResponse::process"] +pub struct MentionSelectedResponse { + pub next_cursor_index: usize, +} + +impl MentionSelectedResponse { + pub fn process(&self, ctx: &egui::Context, text_edit_output: &TextEditOutput) { + let text_edit_id = text_edit_output.response.id; + let Some(mut before_state) = TextEdit::load_state(ctx, text_edit_id) else { + return; + }; + + let mut new_cursor = text_edit_output + .galley + .from_ccursor(CCursor::new(self.next_cursor_index)); + new_cursor.ccursor.prefer_next_row = true; + + before_state + .cursor + .set_char_range(Some(CCursorRange::one(CCursor::new( + self.next_cursor_index, + )))); + + ctx.memory_mut(|mem| mem.request_focus(text_edit_id)); + + TextEdit::store_state(ctx, text_edit_id, before_state); + } +} + impl PostBuffer { pub fn get_new_mentions_key(&mut self) -> usize { let prev = self.mentions_key; @@ -319,15 +353,19 @@ impl PostBuffer { mention_key: usize, full_name: &str, pk: Pubkey, - ) { - if let Some(info) = self.mentions.get(&mention_key) { - let text_start_index = info.start_index + 1; - self.delete_char_range(text_start_index..info.end_index); - self.insert_text(full_name, text_start_index); - self.select_full_mention(mention_key, pk); - } else { + ) -> Option<MentionSelectedResponse> { + let Some(info) = self.mentions.get(&mention_key) else { error!("Error selecting mention for index: {mention_key}. Have the following mentions: {:?}", self.mentions); - } + return None; + }; + let text_start_index = info.start_index + 1; // increment by one to exclude the mention indicator, '@' + self.delete_char_range(text_start_index..info.end_index); + let text_chars_inserted = self.insert_text(full_name, text_start_index); + self.select_full_mention(mention_key, pk); + + Some(MentionSelectedResponse { + next_cursor_index: text_start_index + text_chars_inserted, + }) } pub fn delete_mention(&mut self, mention_key: usize) { diff --git a/crates/notedeck_columns/src/ui/note/post.rs b/crates/notedeck_columns/src/ui/note/post.rs @@ -218,6 +218,7 @@ impl<'a, 'd> PostView<'a, 'd> { out.response } + // Displays the mention picker and handles when one is selected. fn show_mention_hints( &mut self, txn: &nostrdb::Transaction, @@ -281,17 +282,22 @@ impl<'a, 'd> PostView<'a, 'd> { ) .show_in_rect(hint_rect, ui); + let mut selection_made = None; match resp { ui::mentions_picker::MentionPickerResponse::SelectResult(selection) => { if let Some(hint_index) = selection { if let Some(pk) = res.get(hint_index) { let record = self.note_context.ndb.get_profile_by_pubkey(txn, pk); - self.draft.buffer.select_mention_and_replace_name( - mention.index, - get_display_name(record.ok().as_ref()).name(), - Pubkey::new(**pk), - ); + if let Some(made_selection) = + self.draft.buffer.select_mention_and_replace_name( + mention.index, + get_display_name(record.ok().as_ref()).name(), + Pubkey::new(**pk), + ) + { + selection_made = Some(made_selection); + } self.draft.cur_mention_hint = None; } } @@ -301,6 +307,10 @@ impl<'a, 'd> PostView<'a, 'd> { self.draft.buffer.delete_mention(mention.index) } } + + if let Some(selection) = selection_made { + selection.process(ui.ctx(), textedit_output); + } } fn focused(&self, ui: &egui::Ui) -> bool {