commit 09dc101c1bc206f5d655c079d183b24b4e70feb6
parent 0fc8e70180a334fd62745380c0431a114678b027
Author: kernelkind <kernelkind@gmail.com>
Date: Sat, 26 Jul 2025 16:20:53 -0400
rename `SearchResultsView` => `MentionPickerView`
Signed-off-by: kernelkind <kernelkind@gmail.com>
Diffstat:
5 files changed, 182 insertions(+), 180 deletions(-)
diff --git a/crates/notedeck_columns/src/ui/mentions_picker.rs b/crates/notedeck_columns/src/ui/mentions_picker.rs
@@ -0,0 +1,172 @@
+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)
+ .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 {
+ 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 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())
+ }
+}
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;
@@ -273,7 +273,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,
@@ -282,7 +282,7 @@ impl<'a, 'd> PostView<'a, 'd> {
.show_in_rect(hint_rect, ui);
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);
@@ -297,7 +297,7 @@ impl<'a, 'd> PostView<'a, 'd> {
}
}
- ui::search_results::SearchResultsResponse::DeleteMention => {
+ ui::mentions_picker::MentionPickerResponse::DeleteMention => {
self.draft.buffer.delete_mention(mention.index)
}
}
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())
- }
-}