commit ba76b20ad2eef666945566e65c353bba7e8568d3
parent 0fc8e70180a334fd62745380c0431a114678b027
Author: William Casarin <jb55@jb55.com>
Date: Wed, 30 Jul 2025 16:22:16 -0700
Merge tagging fixes from kernel
Fixes the following:
1. space added after mention
2. can scroll the mention picker
3. don't lose focus of textedit after mention selection
kernelkind (6):
rename `SearchResultsView` => `MentionPickerView`
fix scroll regression
mention-picker: re-add spacing from inner_margin
mentions: don't lose focus after select mention
TMP: update egui for better TextInputState handling
insert space after mention selection
Fixes: https://github.com/damus-io/notedeck/issues/985
Fixes: https://github.com/damus-io/notedeck/issues/728
Fixes: https://github.com/damus-io/notedeck/issues/986
Signed-off-by: William Casarin <jb55@jb55.com>
Diffstat:
8 files changed, 292 insertions(+), 218 deletions(-)
diff --git a/Cargo.lock b/Cargo.lock
@@ -1392,17 +1392,17 @@ checksum = "d8b14ccef22fc6f5a8f4d7d768562a182c04ce9a3b3157b91390b52ddfdf1a76"
[[package]]
name = "ecolor"
version = "0.31.1"
-source = "git+https://github.com/damus-io/egui?rev=041d4d18b16cf8be97e0d7ef5892c87436352dfc#041d4d18b16cf8be97e0d7ef5892c87436352dfc"
+source = "git+https://github.com/damus-io/egui?rev=a67ab901e197ce13948ff7d00aa6e07e31a68ccd#a67ab901e197ce13948ff7d00aa6e07e31a68ccd"
dependencies = [
"bytemuck",
- "emath 0.31.1 (git+https://github.com/damus-io/egui?rev=041d4d18b16cf8be97e0d7ef5892c87436352dfc)",
+ "emath 0.31.1 (git+https://github.com/damus-io/egui?rev=a67ab901e197ce13948ff7d00aa6e07e31a68ccd)",
"serde",
]
[[package]]
name = "eframe"
version = "0.31.1"
-source = "git+https://github.com/damus-io/egui?rev=041d4d18b16cf8be97e0d7ef5892c87436352dfc#041d4d18b16cf8be97e0d7ef5892c87436352dfc"
+source = "git+https://github.com/damus-io/egui?rev=a67ab901e197ce13948ff7d00aa6e07e31a68ccd#a67ab901e197ce13948ff7d00aa6e07e31a68ccd"
dependencies = [
"ahash",
"bytemuck",
@@ -1438,24 +1438,25 @@ dependencies = [
[[package]]
name = "egui"
version = "0.31.1"
-source = "git+https://github.com/damus-io/egui?rev=041d4d18b16cf8be97e0d7ef5892c87436352dfc#041d4d18b16cf8be97e0d7ef5892c87436352dfc"
+source = "git+https://github.com/damus-io/egui?rev=a67ab901e197ce13948ff7d00aa6e07e31a68ccd#a67ab901e197ce13948ff7d00aa6e07e31a68ccd"
dependencies = [
"accesskit",
"ahash",
"backtrace",
"bitflags 2.9.1",
- "emath 0.31.1 (git+https://github.com/damus-io/egui?rev=041d4d18b16cf8be97e0d7ef5892c87436352dfc)",
+ "emath 0.31.1 (git+https://github.com/damus-io/egui?rev=a67ab901e197ce13948ff7d00aa6e07e31a68ccd)",
"epaint",
"log",
"nohash-hasher",
"profiling",
"serde",
+ "similar",
]
[[package]]
name = "egui-wgpu"
version = "0.31.1"
-source = "git+https://github.com/damus-io/egui?rev=041d4d18b16cf8be97e0d7ef5892c87436352dfc#041d4d18b16cf8be97e0d7ef5892c87436352dfc"
+source = "git+https://github.com/damus-io/egui?rev=a67ab901e197ce13948ff7d00aa6e07e31a68ccd#a67ab901e197ce13948ff7d00aa6e07e31a68ccd"
dependencies = [
"ahash",
"bytemuck",
@@ -1474,7 +1475,7 @@ dependencies = [
[[package]]
name = "egui-winit"
version = "0.31.1"
-source = "git+https://github.com/damus-io/egui?rev=041d4d18b16cf8be97e0d7ef5892c87436352dfc#041d4d18b16cf8be97e0d7ef5892c87436352dfc"
+source = "git+https://github.com/damus-io/egui?rev=a67ab901e197ce13948ff7d00aa6e07e31a68ccd#a67ab901e197ce13948ff7d00aa6e07e31a68ccd"
dependencies = [
"ahash",
"arboard",
@@ -1492,7 +1493,7 @@ dependencies = [
[[package]]
name = "egui_extras"
version = "0.31.1"
-source = "git+https://github.com/damus-io/egui?rev=041d4d18b16cf8be97e0d7ef5892c87436352dfc#041d4d18b16cf8be97e0d7ef5892c87436352dfc"
+source = "git+https://github.com/damus-io/egui?rev=a67ab901e197ce13948ff7d00aa6e07e31a68ccd#a67ab901e197ce13948ff7d00aa6e07e31a68ccd"
dependencies = [
"ahash",
"egui",
@@ -1509,7 +1510,7 @@ dependencies = [
[[package]]
name = "egui_glow"
version = "0.31.1"
-source = "git+https://github.com/damus-io/egui?rev=041d4d18b16cf8be97e0d7ef5892c87436352dfc#041d4d18b16cf8be97e0d7ef5892c87436352dfc"
+source = "git+https://github.com/damus-io/egui?rev=a67ab901e197ce13948ff7d00aa6e07e31a68ccd#a67ab901e197ce13948ff7d00aa6e07e31a68ccd"
dependencies = [
"ahash",
"bytemuck",
@@ -1588,7 +1589,7 @@ checksum = "9e4cadcff7a5353ba72b7fea76bf2122b5ebdbc68e8155aa56dfdea90083fe1b"
[[package]]
name = "emath"
version = "0.31.1"
-source = "git+https://github.com/damus-io/egui?rev=041d4d18b16cf8be97e0d7ef5892c87436352dfc#041d4d18b16cf8be97e0d7ef5892c87436352dfc"
+source = "git+https://github.com/damus-io/egui?rev=a67ab901e197ce13948ff7d00aa6e07e31a68ccd#a67ab901e197ce13948ff7d00aa6e07e31a68ccd"
dependencies = [
"bytemuck",
"serde",
@@ -1686,13 +1687,13 @@ dependencies = [
[[package]]
name = "epaint"
version = "0.31.1"
-source = "git+https://github.com/damus-io/egui?rev=041d4d18b16cf8be97e0d7ef5892c87436352dfc#041d4d18b16cf8be97e0d7ef5892c87436352dfc"
+source = "git+https://github.com/damus-io/egui?rev=a67ab901e197ce13948ff7d00aa6e07e31a68ccd#a67ab901e197ce13948ff7d00aa6e07e31a68ccd"
dependencies = [
"ab_glyph",
"ahash",
"bytemuck",
"ecolor",
- "emath 0.31.1 (git+https://github.com/damus-io/egui?rev=041d4d18b16cf8be97e0d7ef5892c87436352dfc)",
+ "emath 0.31.1 (git+https://github.com/damus-io/egui?rev=a67ab901e197ce13948ff7d00aa6e07e31a68ccd)",
"epaint_default_fonts",
"log",
"nohash-hasher",
@@ -1704,7 +1705,7 @@ dependencies = [
[[package]]
name = "epaint_default_fonts"
version = "0.31.1"
-source = "git+https://github.com/damus-io/egui?rev=041d4d18b16cf8be97e0d7ef5892c87436352dfc#041d4d18b16cf8be97e0d7ef5892c87436352dfc"
+source = "git+https://github.com/damus-io/egui?rev=a67ab901e197ce13948ff7d00aa6e07e31a68ccd#a67ab901e197ce13948ff7d00aa6e07e31a68ccd"
[[package]]
name = "equator"
@@ -5349,6 +5350,12 @@ dependencies = [
]
[[package]]
+name = "similar"
+version = "2.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bbbb5d9659141646ae647b42fe094daf6c6192d1620870b449d9557f748b2daa"
+
+[[package]]
name = "simplecss"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/Cargo.toml b/Cargo.toml
@@ -101,12 +101,12 @@ strip = true # Strip symbols from binary*
#egui_extras = { path = "/home/jb55/dev/github/emilk/egui/crates/egui_extras" }
#epaint = { path = "/home/jb55/dev/github/emilk/egui/crates/epaint" }
-egui = { git = "https://github.com/damus-io/egui", rev = "041d4d18b16cf8be97e0d7ef5892c87436352dfc" }
-eframe = { git = "https://github.com/damus-io/egui", rev = "041d4d18b16cf8be97e0d7ef5892c87436352dfc" }
-egui-winit = { git = "https://github.com/damus-io/egui", rev = "041d4d18b16cf8be97e0d7ef5892c87436352dfc" }
-egui-wgpu = { git = "https://github.com/damus-io/egui", rev = "041d4d18b16cf8be97e0d7ef5892c87436352dfc" }
-egui_extras = { git = "https://github.com/damus-io/egui", rev = "041d4d18b16cf8be97e0d7ef5892c87436352dfc" }
-epaint = { git = "https://github.com/damus-io/egui", rev = "041d4d18b16cf8be97e0d7ef5892c87436352dfc" }
+egui = { git = "https://github.com/damus-io/egui", rev = "a67ab901e197ce13948ff7d00aa6e07e31a68ccd" }
+eframe = { git = "https://github.com/damus-io/egui", rev = "a67ab901e197ce13948ff7d00aa6e07e31a68ccd" }
+egui-winit = { git = "https://github.com/damus-io/egui", rev = "a67ab901e197ce13948ff7d00aa6e07e31a68ccd" }
+egui-wgpu = { git = "https://github.com/damus-io/egui", rev = "a67ab901e197ce13948ff7d00aa6e07e31a68ccd" }
+egui_extras = { git = "https://github.com/damus-io/egui", rev = "a67ab901e197ce13948ff7d00aa6e07e31a68ccd" }
+epaint = { git = "https://github.com/damus-io/egui", rev = "a67ab901e197ce13948ff7d00aa6e07e31a68ccd" }
puffin = { git = "https://github.com/jb55/puffin", package = "puffin", rev = "c6a6242adaf90b6292c0f462d2acd34d96d224d2" }
puffin_egui = { git = "https://github.com/jb55/puffin", package = "puffin_egui", rev = "c6a6242adaf90b6292c0f462d2acd34d96d224d2" }
#winit = { git = "https://github.com/damus-io/winit", rev = "14d61a74bee0c9863abe7ef28efae2c4d8bd3743" }
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,21 @@ 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);
+
+ let space_chars_inserted = self.insert_text(" ", text_start_index + text_chars_inserted);
+
+ Some(MentionSelectedResponse {
+ next_cursor_index: text_start_index + text_chars_inserted + space_chars_inserted,
+ })
}
pub fn delete_mention(&mut self, mention_key: usize) {
@@ -917,9 +957,9 @@ mod tests {
assert_eq!(buf.mentions.len(), 1);
assert_eq!(buf.mentions.get(&0).unwrap().bounds(), 0..3);
buf.select_mention_and_replace_name(0, "jb55", JB55());
- assert_eq!(buf.as_str(), "@jb55");
+ assert_eq!(buf.as_str(), "@jb55 ");
- buf.insert_text(" test", 5);
+ buf.insert_text("test", 6);
assert_eq!(buf.as_str(), "@jb55 test");
assert_eq!(buf.mentions.len(), 1);
@@ -1201,16 +1241,20 @@ mod tests {
buf.insert_text("@jb", 0);
buf.select_mention_and_replace_name(0, "jb55", JB55());
- buf.insert_text(" test ", 5);
+ buf.insert_text("test ", 6);
+ assert_eq!(buf.as_str(), "@jb55 test ");
buf.insert_text("@kernel", 11);
buf.select_mention_and_replace_name(1, "KernelKind", KK());
- buf.insert_text(" test", 22);
+ assert_eq!(buf.as_str(), "@jb55 test @KernelKind ");
+ buf.insert_text("test", 23);
assert_eq!(buf.as_str(), "@jb55 test @KernelKind test");
+
assert_eq!(buf.mentions.len(), 2);
- buf.insert_text(" ", 5);
buf.insert_text("@els", 6);
+ assert_eq!(buf.as_str(), "@jb55 @elstest @KernelKind test");
+
assert_eq!(buf.mentions.len(), 3);
assert_eq!(buf.mentions.get(&2).unwrap().bounds(), 6..10);
buf.select_mention_and_replace_name(2, "elsat", JB55());
diff --git a/crates/notedeck_columns/src/ui/mentions_picker.rs b/crates/notedeck_columns/src/ui/mentions_picker.rs
@@ -0,0 +1,183 @@
+use egui::{vec2, FontId, Layout, Pos2, Rect, ScrollArea, UiBuilder, Vec2b};
+use nostrdb::{Ndb, ProfileRecord, Transaction};
+use notedeck::{
+ fonts::get_font_size, name::get_display_name, profile::get_profile_url, Images,
+ NotedeckTextStyle,
+};
+use notedeck_ui::{
+ anim::{AnimationHelper, ICON_EXPANSION_MULTIPLE},
+ widgets::x_button,
+ ProfilePic,
+};
+use tracing::error;
+
+/// Displays user profiles for the user to pick from.
+/// Useful for manually typing a username and selecting the profile desired
+pub struct MentionPickerView<'a> {
+ ndb: &'a Ndb,
+ txn: &'a Transaction,
+ img_cache: &'a mut Images,
+ results: &'a Vec<&'a [u8; 32]>,
+}
+
+pub enum MentionPickerResponse {
+ SelectResult(Option<usize>),
+ DeleteMention,
+}
+
+impl<'a> MentionPickerView<'a> {
+ pub fn new(
+ img_cache: &'a mut Images,
+ ndb: &'a Ndb,
+ txn: &'a Transaction,
+ results: &'a Vec<&'a [u8; 32]>,
+ ) -> Self {
+ Self {
+ ndb,
+ txn,
+ img_cache,
+ results,
+ }
+ }
+
+ fn show(&mut self, ui: &mut egui::Ui, width: f32) -> MentionPickerResponse {
+ let mut selection = None;
+ ui.vertical(|ui| {
+ for (i, res) in self.results.iter().enumerate() {
+ let profile = match self.ndb.get_profile_by_pubkey(self.txn, res) {
+ Ok(rec) => rec,
+ Err(e) => {
+ error!("Error fetching profile for pubkey {:?}: {e}", res);
+ return;
+ }
+ };
+
+ if ui
+ .add(user_result(&profile, self.img_cache, i, width))
+ .clicked()
+ {
+ selection = Some(i)
+ }
+ }
+ });
+
+ MentionPickerResponse::SelectResult(selection)
+ }
+
+ pub fn show_in_rect(&mut self, rect: egui::Rect, ui: &mut egui::Ui) -> MentionPickerResponse {
+ let widget_id = ui.id().with("mention_results");
+ let area_resp = egui::Area::new(widget_id)
+ .order(egui::Order::Foreground)
+ .fixed_pos(rect.left_top())
+ .constrain_to(rect)
+ .show(ui.ctx(), |ui| {
+ let inner_margin_size = 8.0;
+ egui::Frame::NONE
+ .fill(ui.visuals().panel_fill)
+ .show(ui, |ui| {
+ let width = rect.width() - (2.0 * inner_margin_size);
+
+ ui.allocate_space(vec2(ui.available_width(), inner_margin_size));
+ let close_button_resp = {
+ let close_button_size = 16.0;
+ let (close_section_rect, _) = ui.allocate_exact_size(
+ vec2(width, close_button_size),
+ egui::Sense::hover(),
+ );
+ let (_, button_rect) = close_section_rect.split_left_right_at_x(
+ close_section_rect.right() - close_button_size,
+ );
+ let button_resp = ui.allocate_rect(button_rect, egui::Sense::click());
+ ui.allocate_new_ui(
+ UiBuilder::new()
+ .max_rect(close_section_rect)
+ .layout(Layout::right_to_left(egui::Align::Center)),
+ |ui| ui.add(x_button(button_resp.rect)).clicked(),
+ )
+ .inner
+ };
+
+ ui.allocate_space(vec2(ui.available_width(), inner_margin_size));
+
+ let scroll_resp = ScrollArea::vertical()
+ .max_width(rect.width())
+ .auto_shrink(Vec2b::FALSE)
+ .show(ui, |ui| self.show(ui, width));
+ ui.advance_cursor_after_rect(rect);
+
+ if close_button_resp {
+ MentionPickerResponse::DeleteMention
+ } else {
+ scroll_resp.inner
+ }
+ })
+ .inner
+ });
+
+ area_resp.inner
+ }
+}
+
+fn user_result<'a>(
+ profile: &'a ProfileRecord<'_>,
+ cache: &'a mut Images,
+ index: usize,
+ width: f32,
+) -> impl egui::Widget + 'a {
+ move |ui: &mut egui::Ui| -> egui::Response {
+ let min_img_size = 48.0;
+ let max_image = min_img_size * ICON_EXPANSION_MULTIPLE;
+ let spacing = 8.0;
+ let body_font_size = get_font_size(ui.ctx(), &NotedeckTextStyle::Body);
+
+ let animation_rect = {
+ let max_width = ui.available_width();
+ let extra_width = (max_width - width) / 2.0;
+ let left = ui.cursor().left();
+ let (rect, _) =
+ ui.allocate_exact_size(vec2(width + extra_width, max_image), egui::Sense::click());
+
+ let (_, right) = rect.split_left_right_at_x(left + extra_width);
+ right
+ };
+
+ let helper = AnimationHelper::new_from_rect(ui, ("user_result", index), animation_rect);
+
+ let icon_rect = {
+ let r = helper.get_animation_rect();
+ let mut center = r.center();
+ center.x = r.left() + (max_image / 2.0);
+ let size = helper.scale_1d_pos(min_img_size);
+ Rect::from_center_size(center, vec2(size, size))
+ };
+
+ let pfp_resp = ui.put(
+ icon_rect,
+ &mut ProfilePic::new(cache, get_profile_url(Some(profile)))
+ .size(helper.scale_1d_pos(min_img_size)),
+ );
+
+ let name_font = FontId::new(
+ helper.scale_1d_pos(body_font_size),
+ NotedeckTextStyle::Body.font_family(),
+ );
+ let painter = ui.painter_at(helper.get_animation_rect());
+ let name_galley = painter.layout(
+ get_display_name(Some(profile)).name().to_owned(),
+ name_font,
+ ui.visuals().text_color(),
+ width,
+ );
+
+ let galley_pos = {
+ let right_top = pfp_resp.rect.right_top();
+ let galley_pos_y = pfp_resp.rect.center().y - (name_galley.rect.height() / 2.0);
+ Pos2::new(right_top.x + spacing, galley_pos_y)
+ };
+
+ painter.galley(galley_pos, name_galley, ui.visuals().text_color());
+ ui.advance_cursor_after_rect(helper.get_animation_rect());
+
+ pfp_resp.union(helper.take_animation_response())
+ }
+}
diff --git a/crates/notedeck_columns/src/ui/mod.rs b/crates/notedeck_columns/src/ui/mod.rs
@@ -5,13 +5,13 @@ pub mod column;
pub mod configure_deck;
pub mod edit_deck;
pub mod images;
+pub mod mentions_picker;
pub mod note;
pub mod post;
pub mod preview;
pub mod profile;
pub mod relay;
pub mod search;
-pub mod search_results;
pub mod settings;
pub mod side_panel;
pub mod support;
diff --git a/crates/notedeck_columns/src/ui/note/post.rs b/crates/notedeck_columns/src/ui/note/post.rs
@@ -2,7 +2,7 @@ use crate::draft::{Draft, Drafts, MentionHint};
#[cfg(not(target_os = "android"))]
use crate::media_upload::{nostrbuild_nip96_upload, MediaPath};
use crate::post::{downcast_post_buffer, MentionType, NewPost};
-use crate::ui::search_results::SearchResultsView;
+use crate::ui::mentions_picker::MentionPickerView;
use crate::ui::{self, Preview, PreviewConfig};
use crate::Result;
@@ -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,
@@ -273,7 +274,7 @@ impl<'a, 'd> PostView<'a, 'd> {
return;
};
- let resp = SearchResultsView::new(
+ let resp = MentionPickerView::new(
self.note_context.img_cache,
self.note_context.ndb,
txn,
@@ -281,26 +282,35 @@ impl<'a, 'd> PostView<'a, 'd> {
)
.show_in_rect(hint_rect, ui);
+ let mut selection_made = None;
match resp {
- ui::search_results::SearchResultsResponse::SelectResult(selection) => {
+ 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;
}
}
}
- ui::search_results::SearchResultsResponse::DeleteMention => {
+ ui::mentions_picker::MentionPickerResponse::DeleteMention => {
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 {
diff --git a/crates/notedeck_columns/src/ui/search/mod.rs b/crates/notedeck_columns/src/ui/search/mod.rs
@@ -19,7 +19,7 @@ mod state;
pub use state::{FocusState, SearchQueryState, SearchState};
-use super::search_results::{SearchResultsResponse, SearchResultsView};
+use super::mentions_picker::{MentionPickerResponse, MentionPickerView};
pub struct SearchView<'a, 'd> {
query: &'a mut SearchQueryState,
@@ -76,7 +76,7 @@ impl<'a, 'd> SearchView<'a, 'd> {
break 's;
};
- let search_res = SearchResultsView::new(
+ let search_res = MentionPickerView::new(
self.note_context.img_cache,
self.note_context.ndb,
self.txn,
@@ -85,7 +85,7 @@ impl<'a, 'd> SearchView<'a, 'd> {
.show_in_rect(ui.available_rect_before_wrap(), ui);
search_action = match search_res {
- SearchResultsResponse::SelectResult(Some(index)) => {
+ MentionPickerResponse::SelectResult(Some(index)) => {
let Some(pk_bytes) = results.get(index) else {
break 's;
};
@@ -103,8 +103,8 @@ impl<'a, 'd> SearchView<'a, 'd> {
new_search_text: format!("@{username}"),
})
}
- SearchResultsResponse::DeleteMention => Some(SearchAction::CloseMention),
- SearchResultsResponse::SelectResult(None) => break 's,
+ MentionPickerResponse::DeleteMention => Some(SearchAction::CloseMention),
+ MentionPickerResponse::SelectResult(None) => break 's,
};
}
SearchState::PerformSearch(search_type) => {
diff --git a/crates/notedeck_columns/src/ui/search_results.rs b/crates/notedeck_columns/src/ui/search_results.rs
@@ -1,170 +0,0 @@
-use egui::{vec2, FontId, Layout, Pos2, Rect, ScrollArea, UiBuilder, Vec2b};
-use nostrdb::{Ndb, ProfileRecord, Transaction};
-use notedeck::{
- fonts::get_font_size, name::get_display_name, profile::get_profile_url, Images,
- NotedeckTextStyle,
-};
-use notedeck_ui::{
- anim::{AnimationHelper, ICON_EXPANSION_MULTIPLE},
- widgets::x_button,
- ProfilePic,
-};
-use tracing::error;
-
-pub struct SearchResultsView<'a> {
- ndb: &'a Ndb,
- txn: &'a Transaction,
- img_cache: &'a mut Images,
- results: &'a Vec<&'a [u8; 32]>,
-}
-
-pub enum SearchResultsResponse {
- SelectResult(Option<usize>),
- DeleteMention,
-}
-
-impl<'a> SearchResultsView<'a> {
- pub fn new(
- img_cache: &'a mut Images,
- ndb: &'a Ndb,
- txn: &'a Transaction,
- results: &'a Vec<&'a [u8; 32]>,
- ) -> Self {
- Self {
- ndb,
- txn,
- img_cache,
- results,
- }
- }
-
- fn show(&mut self, ui: &mut egui::Ui, width: f32) -> SearchResultsResponse {
- let mut search_results_selection = None;
- ui.vertical(|ui| {
- for (i, res) in self.results.iter().enumerate() {
- let profile = match self.ndb.get_profile_by_pubkey(self.txn, res) {
- Ok(rec) => rec,
- Err(e) => {
- error!("Error fetching profile for pubkey {:?}: {e}", res);
- return;
- }
- };
-
- if ui
- .add(user_result(&profile, self.img_cache, i, width))
- .clicked()
- {
- search_results_selection = Some(i)
- }
- }
- });
-
- SearchResultsResponse::SelectResult(search_results_selection)
- }
-
- pub fn show_in_rect(&mut self, rect: egui::Rect, ui: &mut egui::Ui) -> SearchResultsResponse {
- let widget_id = ui.id().with("search_results");
- let area_resp = egui::Area::new(widget_id)
- .order(egui::Order::Foreground)
- .fixed_pos(rect.left_top())
- .constrain_to(rect)
- .show(ui.ctx(), |ui| {
- let inner_margin_size = 8.0;
- egui::Frame::NONE
- .fill(ui.visuals().panel_fill)
- .inner_margin(inner_margin_size)
- .show(ui, |ui| {
- let width = rect.width() - (2.0 * inner_margin_size);
-
- let close_button_resp = {
- let close_button_size = 16.0;
- let (close_section_rect, _) = ui.allocate_exact_size(
- vec2(width, close_button_size),
- egui::Sense::hover(),
- );
- let (_, button_rect) = close_section_rect.split_left_right_at_x(
- close_section_rect.right() - close_button_size,
- );
- let button_resp = ui.allocate_rect(button_rect, egui::Sense::click());
- ui.allocate_new_ui(
- UiBuilder::new()
- .max_rect(close_section_rect)
- .layout(Layout::right_to_left(egui::Align::Center)),
- |ui| ui.add(x_button(button_resp.rect)).clicked(),
- )
- .inner
- };
-
- ui.add_space(8.0);
-
- let scroll_resp = ScrollArea::vertical()
- .max_width(width)
- .auto_shrink(Vec2b::FALSE)
- .show(ui, |ui| self.show(ui, width));
- ui.advance_cursor_after_rect(rect);
-
- if close_button_resp {
- SearchResultsResponse::DeleteMention
- } else {
- scroll_resp.inner
- }
- })
- .inner
- });
-
- area_resp.inner
- }
-}
-
-fn user_result<'a>(
- profile: &'a ProfileRecord<'_>,
- cache: &'a mut Images,
- index: usize,
- width: f32,
-) -> impl egui::Widget + 'a {
- move |ui: &mut egui::Ui| -> egui::Response {
- let min_img_size = 48.0;
- let max_image = min_img_size * ICON_EXPANSION_MULTIPLE;
- let spacing = 8.0;
- let body_font_size = get_font_size(ui.ctx(), &NotedeckTextStyle::Body);
-
- let helper = AnimationHelper::new(ui, ("user_result", index), vec2(width, max_image));
-
- let icon_rect = {
- let r = helper.get_animation_rect();
- let mut center = r.center();
- center.x = r.left() + (max_image / 2.0);
- let size = helper.scale_1d_pos(min_img_size);
- Rect::from_center_size(center, vec2(size, size))
- };
-
- let pfp_resp = ui.put(
- icon_rect,
- &mut ProfilePic::new(cache, get_profile_url(Some(profile)))
- .size(helper.scale_1d_pos(min_img_size)),
- );
-
- let name_font = FontId::new(
- helper.scale_1d_pos(body_font_size),
- NotedeckTextStyle::Body.font_family(),
- );
- let painter = ui.painter_at(helper.get_animation_rect());
- let name_galley = painter.layout(
- get_display_name(Some(profile)).name().to_owned(),
- name_font,
- ui.visuals().text_color(),
- width,
- );
-
- let galley_pos = {
- let right_top = pfp_resp.rect.right_top();
- let galley_pos_y = pfp_resp.rect.center().y - (name_galley.rect.height() / 2.0);
- Pos2::new(right_top.x + spacing, galley_pos_y)
- };
-
- painter.galley(galley_pos, name_galley, ui.visuals().text_color());
- ui.advance_cursor_after_rect(helper.get_animation_rect());
-
- pfp_resp.union(helper.take_animation_response())
- }
-}