mirror of
https://github.com/aljazceru/notedeck.git
synced 2025-12-17 08:44:20 +01:00
docs: add some ui-related guides
generated using code2prompt + claude 3.7 sonnet Signed-off-by: William Casarin <jb55@jb55.com>
This commit is contained in:
121
crates/notedeck_ui/README.md
Normal file
121
crates/notedeck_ui/README.md
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
# NoteDeck UI
|
||||||
|
|
||||||
|
UI component library for NoteDeck - a Nostr client built with EGUI.
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
The `notedeck_ui` crate provides a set of reusable UI components for building a Nostr client. It offers consistent styling, behavior, and rendering of Nostr-specific elements like notes, profiles, mentions, and media content.
|
||||||
|
|
||||||
|
This library is built on top of [egui](https://github.com/emilk/egui), a simple, fast, and highly portable immediate mode GUI library for Rust.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- 📝 Note display with rich content, media, and interactions
|
||||||
|
- 👤 Profile components (display name, pictures, banners)
|
||||||
|
- 🔗 Mention system with hover previews
|
||||||
|
- 🖼️ Image handling with caching and lazy loading
|
||||||
|
- 📺 GIF playback support
|
||||||
|
- 💸 Zap interactions (Bitcoin Lightning tips)
|
||||||
|
- 🎨 Theming and consistent styling
|
||||||
|
|
||||||
|
## Components
|
||||||
|
|
||||||
|
### Notes
|
||||||
|
|
||||||
|
The `NoteView` widget is the core component for displaying Nostr notes:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// Example: Render a note
|
||||||
|
let mut note_view = NoteView::new(
|
||||||
|
note_context,
|
||||||
|
current_account,
|
||||||
|
¬e,
|
||||||
|
NoteOptions::default()
|
||||||
|
);
|
||||||
|
|
||||||
|
ui.add(&mut note_view);
|
||||||
|
```
|
||||||
|
|
||||||
|
`NoteView` supports various display options:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// Create a preview style note
|
||||||
|
note_view
|
||||||
|
.preview_style() // Apply preview styling
|
||||||
|
.textmode(true) // Use text-only mode
|
||||||
|
.actionbar(false) // Hide action bar
|
||||||
|
.small_pfp(true) // Use small profile picture
|
||||||
|
.note_previews(false) // Disable nested note previews
|
||||||
|
.show(ui);
|
||||||
|
```
|
||||||
|
|
||||||
|
### Profiles
|
||||||
|
|
||||||
|
Profile components include profile pictures, banners, and display names:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// Display a profile picture
|
||||||
|
ui.add(ProfilePic::new(images_cache, profile_picture_url).size(48.0));
|
||||||
|
|
||||||
|
// Display a profile preview
|
||||||
|
ui.add(ProfilePreview::new(profile_record, images_cache));
|
||||||
|
```
|
||||||
|
|
||||||
|
### Mentions
|
||||||
|
|
||||||
|
The mention component links to user profiles:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// Display a mention with hover preview
|
||||||
|
let mention_response = Mention::new(ndb, img_cache, txn, pubkey)
|
||||||
|
.size(16.0) // Set text size
|
||||||
|
.selectable(true) // Allow selection
|
||||||
|
.show(ui);
|
||||||
|
|
||||||
|
// Handle click actions
|
||||||
|
if let Some(action) = mention_response.inner {
|
||||||
|
// Handle profile navigation
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Media
|
||||||
|
|
||||||
|
Support for images, GIFs, and other media types:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// Render an image
|
||||||
|
render_images(
|
||||||
|
ui,
|
||||||
|
img_cache,
|
||||||
|
image_url,
|
||||||
|
ImageType::Content,
|
||||||
|
cache_type,
|
||||||
|
on_loading_callback,
|
||||||
|
on_error_callback,
|
||||||
|
on_success_callback
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
## Styling
|
||||||
|
|
||||||
|
The UI components adapt to the current theme (light/dark mode) and use consistent styling defined in the `colors.rs` module:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// Color constants
|
||||||
|
pub const ALMOST_WHITE: Color32 = Color32::from_rgb(0xFA, 0xFA, 0xFA);
|
||||||
|
pub const MID_GRAY: Color32 = Color32::from_rgb(0xbd, 0xbd, 0xbd);
|
||||||
|
pub const PINK: Color32 = Color32::from_rgb(0xE4, 0x5A, 0xC9);
|
||||||
|
pub const TEAL: Color32 = Color32::from_rgb(0x77, 0xDC, 0xE1);
|
||||||
|
```
|
||||||
|
|
||||||
|
## Dependencies
|
||||||
|
|
||||||
|
This crate depends on:
|
||||||
|
- `egui` - Core UI library
|
||||||
|
- `egui_extras` - Additional widgets and functionality
|
||||||
|
- `ehttp` - HTTP client for fetching content
|
||||||
|
- `nostrdb` - Nostr database and types
|
||||||
|
- `enostr` - Nostr protocol implementation
|
||||||
|
- `image` - Image processing library
|
||||||
|
- `poll-promise` - Async promise handling
|
||||||
|
- `tokio` - Async runtime
|
||||||
237
crates/notedeck_ui/docs/README.md
Normal file
237
crates/notedeck_ui/docs/README.md
Normal file
@@ -0,0 +1,237 @@
|
|||||||
|
# NoteDeck UI Developer Documentation
|
||||||
|
|
||||||
|
This document provides an in-depth overview of the `notedeck_ui` architecture, components, and guidelines for development.
|
||||||
|
|
||||||
|
For a guide on some of our components, check out the [NoteDeck UI Component Guide](./components.md)
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
The `notedeck_ui` crate is organized into modules that handle different aspects of the Nostr client UI:
|
||||||
|
|
||||||
|
```
|
||||||
|
notedeck_ui
|
||||||
|
├── anim.rs - Animation utilities and helpers
|
||||||
|
├── colors.rs - Color constants and theme definitions
|
||||||
|
├── constants.rs - UI constants (margins, sizes, etc.)
|
||||||
|
├── gif.rs - GIF rendering and playback
|
||||||
|
├── icons.rs - Icon rendering helpers
|
||||||
|
├── images.rs - Image loading, caching, and display
|
||||||
|
├── lib.rs - Main export and shared utilities
|
||||||
|
├── mention.rs - Nostr mention component (@username)
|
||||||
|
├── note/ - Note display components
|
||||||
|
│ ├── contents.rs - Note content rendering
|
||||||
|
│ ├── context.rs - Note context menu
|
||||||
|
│ ├── mod.rs - Note view component
|
||||||
|
│ ├── options.rs - Note display options
|
||||||
|
│ └── reply_description.rs - Reply metadata display
|
||||||
|
├── profile/ - Profile components
|
||||||
|
│ ├── mod.rs - Shared profile utilities
|
||||||
|
│ ├── name.rs - Profile name display
|
||||||
|
│ ├── picture.rs - Profile picture component
|
||||||
|
│ └── preview.rs - Profile hover preview
|
||||||
|
├── username.rs - Username display component
|
||||||
|
└── widgets.rs - Generic widget helpers
|
||||||
|
```
|
||||||
|
|
||||||
|
## Core Components
|
||||||
|
|
||||||
|
### NoteView
|
||||||
|
|
||||||
|
The `NoteView` component is the primary way to display Nostr notes. It handles rendering the note content, profile information, media, and interactive elements like replies and zaps.
|
||||||
|
|
||||||
|
Key design aspects:
|
||||||
|
- Stateful widget that maintains rendering state through EGUI's widget system
|
||||||
|
- Configurable display options via `NoteOptions` bitflags
|
||||||
|
- Support for different layouts (normal and wide)
|
||||||
|
- Handles nested content (note previews, mentions, hashtags)
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// NoteView creation and display
|
||||||
|
let mut note_view = NoteView::new(note_context, cur_acc, ¬e, options);
|
||||||
|
note_view.show(ui); // Returns NoteResponse with action
|
||||||
|
```
|
||||||
|
|
||||||
|
### Note Actions
|
||||||
|
|
||||||
|
The note components use a pattern where user interactions produce `NoteAction` enum values:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub enum NoteAction {
|
||||||
|
Note(NoteId), // Note was clicked
|
||||||
|
Profile(Pubkey), // Profile was clicked
|
||||||
|
Reply(NoteId), // Reply button clicked
|
||||||
|
Quote(NoteId), // Quote button clicked
|
||||||
|
Hashtag(String), // Hashtag was clicked
|
||||||
|
Zap(ZapAction), // Zap interaction
|
||||||
|
Context(ContextSelection), // Context menu selection
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Actions are propagated up from inner components to the parent UI, which can handle navigation and state changes.
|
||||||
|
|
||||||
|
### Media Handling
|
||||||
|
|
||||||
|
The media system uses a cache and promise-based loading system:
|
||||||
|
|
||||||
|
1. `MediaCache` stores loaded images and animations
|
||||||
|
2. `fetch_img` retrieves images from disk or network
|
||||||
|
3. `render_images` handles the loading states and display
|
||||||
|
|
||||||
|
For GIFs, the system:
|
||||||
|
1. Decodes frames using a background thread
|
||||||
|
2. Sends frames via channels to the UI thread
|
||||||
|
3. Manages animation timing for playback
|
||||||
|
|
||||||
|
## Design Patterns
|
||||||
|
|
||||||
|
### Widget Pattern
|
||||||
|
|
||||||
|
Components implement the `egui::Widget` trait for integration with EGUI:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
impl egui::Widget for ProfilePic<'_, '_> {
|
||||||
|
fn ui(self, ui: &mut egui::Ui) -> egui::Response {
|
||||||
|
render_pfp(ui, self.cache, self.url, self.size, self.border)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Builder Pattern
|
||||||
|
|
||||||
|
Components often use a builder pattern for configuration:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
let view = NoteView::new(context, account, ¬e, options)
|
||||||
|
.small_pfp(true)
|
||||||
|
.wide(true)
|
||||||
|
.actionbar(false);
|
||||||
|
```
|
||||||
|
|
||||||
|
### Animation Helper
|
||||||
|
|
||||||
|
For interactive elements, the `AnimationHelper` provides standardized hover animations:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
let helper = AnimationHelper::new(ui, "animation_id", max_size);
|
||||||
|
let current_size = helper.scale_1d_pos(min_size);
|
||||||
|
```
|
||||||
|
|
||||||
|
## Working with Images
|
||||||
|
|
||||||
|
Images are handled through different types depending on their purpose:
|
||||||
|
|
||||||
|
1. `ImageType::Profile` - For profile pictures (with automatic cropping and rounding)
|
||||||
|
2. `ImageType::Content` - For general content images
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// Loading and displaying an image
|
||||||
|
render_images(
|
||||||
|
ui,
|
||||||
|
img_cache,
|
||||||
|
url,
|
||||||
|
ImageType::Profile(128), // Size hint
|
||||||
|
MediaCacheType::Image, // Static image or GIF
|
||||||
|
|ui| { /* show while loading */ },
|
||||||
|
|ui, err| { /* show on error */ },
|
||||||
|
|ui, url, img, gifs| { /* show successful load */ },
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
## Performance Considerations
|
||||||
|
|
||||||
|
1. **Image Caching**: Images are cached both in memory and on disk
|
||||||
|
2. **Animation Optimization**: GIF frames are decoded in background threads
|
||||||
|
3. **Render Profiling**: Critical paths use `#[profiling::function]` for tracing
|
||||||
|
4. **Layout Reuse**: Components cache layout data to prevent recalculation
|
||||||
|
|
||||||
|
## Theming
|
||||||
|
|
||||||
|
The UI adapts to light/dark mode through EGUI's visuals system:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// Access current theme
|
||||||
|
let color = ui.visuals().hyperlink_color;
|
||||||
|
|
||||||
|
// Check theme mode
|
||||||
|
if ui.visuals().dark_mode {
|
||||||
|
// Use dark mode resources
|
||||||
|
} else {
|
||||||
|
// Use light mode resources
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Testing Components
|
||||||
|
|
||||||
|
To test UI components, use the EGUI test infrastructure:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
#[test]
|
||||||
|
fn test_profile_pic() {
|
||||||
|
let ctx = egui::Context::default();
|
||||||
|
let mut cache = Images::new();
|
||||||
|
|
||||||
|
ctx.run(|ctx| {
|
||||||
|
egui::CentralPanel::default().show(ctx, |ui| {
|
||||||
|
let response = ui.add(ProfilePic::new(&mut cache, "test_url"));
|
||||||
|
assert!(response.clicked() == false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Debugging Tips
|
||||||
|
|
||||||
|
1. **EGUI Inspector**: Use `ctx.debug_painter()` to visualize layout bounds
|
||||||
|
2. **Trace Logging**: Enable trace logs to debug image loading and caching
|
||||||
|
3. **Animation Debugging**: Set `ANIM_SPEED` to a lower value to slow animations for visual debugging
|
||||||
|
4. **ID Collisions**: Use unique IDs for animations and state to prevent interaction bugs
|
||||||
|
|
||||||
|
## Common Patterns
|
||||||
|
|
||||||
|
### Handling User Interactions
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// For clickable elements
|
||||||
|
let response = ui.add(/* widget */);
|
||||||
|
if response.clicked() {
|
||||||
|
// Handle click
|
||||||
|
} else if response.hovered() {
|
||||||
|
// Show hover effect (often using show_pointer())
|
||||||
|
crate::show_pointer(ui);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Hover Previews
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// For elements with hover previews
|
||||||
|
let resp = ui.add(/* widget */);
|
||||||
|
resp.on_hover_ui_at_pointer(|ui| {
|
||||||
|
ui.set_max_width(300.0);
|
||||||
|
ui.add(ProfilePreview::new(profile, img_cache));
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### Context Menus
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// For elements with context menus
|
||||||
|
let resp = ui.add(/* widget */);
|
||||||
|
resp.context_menu(|ui| {
|
||||||
|
if ui.button("Menu Option").clicked() {
|
||||||
|
// Handle selection
|
||||||
|
ui.close_menu();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
## Contributing Guidelines
|
||||||
|
|
||||||
|
When contributing to `notedeck_ui`:
|
||||||
|
|
||||||
|
1. **Widget Consistency**: Follow established patterns for new widgets
|
||||||
|
2. **Option Naming**: Keep option names consistent (has_X/set_X pairs)
|
||||||
|
3. **Performance**: Add profiling annotations to expensive operations
|
||||||
|
4. **Error Handling**: Propagate errors up rather than handling them directly in UI components
|
||||||
|
5. **Documentation**: Document public APIs and components with examples
|
||||||
|
6. **Theme Support**: Ensure components work in both light and dark mode
|
||||||
321
crates/notedeck_ui/docs/components.md
Normal file
321
crates/notedeck_ui/docs/components.md
Normal file
@@ -0,0 +1,321 @@
|
|||||||
|
# NoteDeck UI Component Guide
|
||||||
|
|
||||||
|
This guide provides detailed documentation for the major UI components in the NoteDeck UI library.
|
||||||
|
|
||||||
|
## Table of Contents
|
||||||
|
|
||||||
|
- [Notes](#notes)
|
||||||
|
- [NoteView](#noteview)
|
||||||
|
- [NoteContents](#notecontents)
|
||||||
|
- [NoteOptions](#noteoptions)
|
||||||
|
- [Profiles](#profiles)
|
||||||
|
- [ProfilePic](#profilepic)
|
||||||
|
- [ProfilePreview](#profilepreview)
|
||||||
|
- [Mentions](#mentions)
|
||||||
|
- [Media Handling](#media-handling)
|
||||||
|
- [Images](#images)
|
||||||
|
- [GIF Animation](#gif-animation)
|
||||||
|
- [Widgets & Utilities](#widgets--utilities)
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
### NoteView
|
||||||
|
|
||||||
|
The `NoteView` component is the main container for displaying Nostr notes, handling the layout of profile pictures, author information, content, and interactive elements.
|
||||||
|
|
||||||
|
#### Usage
|
||||||
|
|
||||||
|
```rust
|
||||||
|
let mut note_view = NoteView::new(
|
||||||
|
note_context, // NoteContext with DB, cache, etc.
|
||||||
|
current_acc, // Current user account (Option<KeypairUnowned>)
|
||||||
|
¬e, // Reference to Note
|
||||||
|
options // NoteOptions (display configuration)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Configure display options
|
||||||
|
note_view
|
||||||
|
.actionbar(true) // Show/hide action bar
|
||||||
|
.small_pfp(false) // Use small profile picture
|
||||||
|
.medium_pfp(true) // Use medium profile picture
|
||||||
|
.wide(false) // Use wide layout
|
||||||
|
.frame(true) // Display with a frame
|
||||||
|
.note_previews(true) // Enable embedded note previews
|
||||||
|
.selectable_text(true); // Allow text selection
|
||||||
|
|
||||||
|
// Render the note view
|
||||||
|
let note_response = note_view.show(ui);
|
||||||
|
|
||||||
|
// Handle user actions
|
||||||
|
if let Some(action) = note_response.action {
|
||||||
|
match action {
|
||||||
|
NoteAction::Note(note_id) => { /* Note was clicked */ },
|
||||||
|
NoteAction::Profile(pubkey) => { /* Profile was clicked */ },
|
||||||
|
NoteAction::Reply(note_id) => { /* User clicked reply */ },
|
||||||
|
NoteAction::Quote(note_id) => { /* User clicked quote */ },
|
||||||
|
NoteAction::Zap(zap_action) => { /* User initiated zap */ },
|
||||||
|
NoteAction::Hashtag(tag) => { /* Hashtag was clicked */ },
|
||||||
|
NoteAction::Context(ctx_selection) => { /* Context menu option selected */ },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Layouts
|
||||||
|
|
||||||
|
`NoteView` supports two main layouts:
|
||||||
|
|
||||||
|
1. **Standard Layout** - Default compact display
|
||||||
|
2. **Wide Layout** - More spacious layout with profile picture on the left
|
||||||
|
|
||||||
|
Use the `.wide(true)` option to enable the wide layout.
|
||||||
|
|
||||||
|
#### Preview Style
|
||||||
|
|
||||||
|
For displaying note previews (e.g., when a note is referenced in another note), use the preview style:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
let mut note_view = NoteView::new(note_context, current_acc, ¬e, options)
|
||||||
|
.preview_style(); // Applies preset options for preview display
|
||||||
|
```
|
||||||
|
|
||||||
|
### NoteContents
|
||||||
|
|
||||||
|
`NoteContents` handles rendering the actual content of a note, including text, mentions, hashtags, URLs, and embedded media.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
let mut contents = NoteContents::new(
|
||||||
|
note_context,
|
||||||
|
current_acc,
|
||||||
|
transaction,
|
||||||
|
note,
|
||||||
|
note_options
|
||||||
|
);
|
||||||
|
|
||||||
|
ui.add(&mut contents);
|
||||||
|
|
||||||
|
// Check for content interactions
|
||||||
|
if let Some(action) = contents.action() {
|
||||||
|
// Handle content action (e.g., clicked mention/hashtag)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### NoteOptions
|
||||||
|
|
||||||
|
`NoteOptions` is a bitflag-based configuration system for controlling how notes are displayed:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// Create with default options
|
||||||
|
let mut options = NoteOptions::default();
|
||||||
|
|
||||||
|
// Or customize from scratch
|
||||||
|
let mut options = NoteOptions::new(is_universe_timeline);
|
||||||
|
|
||||||
|
// Configure options
|
||||||
|
options.set_actionbar(true); // Show action buttons
|
||||||
|
options.set_small_pfp(true); // Use small profile picture
|
||||||
|
options.set_medium_pfp(false); // Don't use medium profile picture
|
||||||
|
options.set_note_previews(true); // Enable note previews
|
||||||
|
options.set_wide(false); // Use compact layout
|
||||||
|
options.set_selectable_text(true); // Allow text selection
|
||||||
|
options.set_textmode(false); // Don't use text-only mode
|
||||||
|
options.set_options_button(true); // Show options button
|
||||||
|
options.set_hide_media(false); // Show media content
|
||||||
|
options.set_scramble_text(false); // Don't scramble text
|
||||||
|
options.set_is_preview(false); // This is not a preview
|
||||||
|
```
|
||||||
|
|
||||||
|
## Profiles
|
||||||
|
|
||||||
|
### ProfilePic
|
||||||
|
|
||||||
|
`ProfilePic` displays a circular profile picture with optional border and configurable size.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// Basic usage
|
||||||
|
ui.add(ProfilePic::new(img_cache, profile_url));
|
||||||
|
|
||||||
|
// Customized
|
||||||
|
ui.add(
|
||||||
|
ProfilePic::new(img_cache, profile_url)
|
||||||
|
.size(48.0)
|
||||||
|
.border(Stroke::new(2.0, Color32::WHITE))
|
||||||
|
);
|
||||||
|
|
||||||
|
// From profile record
|
||||||
|
if let Some(profile_pic) = ProfilePic::from_profile(img_cache, profile) {
|
||||||
|
ui.add(profile_pic);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Standard sizes:
|
||||||
|
- `ProfilePic::default_size()` - 38px
|
||||||
|
- `ProfilePic::medium_size()` - 32px
|
||||||
|
- `ProfilePic::small_size()` - 24px
|
||||||
|
|
||||||
|
### ProfilePreview
|
||||||
|
|
||||||
|
`ProfilePreview` shows a detailed profile card with banner, profile picture, display name, username, and about text.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// Full preview
|
||||||
|
ui.add(ProfilePreview::new(profile, img_cache));
|
||||||
|
|
||||||
|
// Simple preview
|
||||||
|
ui.add(SimpleProfilePreview::new(
|
||||||
|
Some(profile), // Option<&ProfileRecord>
|
||||||
|
img_cache,
|
||||||
|
is_nsec // Whether this is a full keypair
|
||||||
|
));
|
||||||
|
```
|
||||||
|
|
||||||
|
## Mentions
|
||||||
|
|
||||||
|
The `Mention` component renders a clickable @username reference with hover preview.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
let mention_response = Mention::new(ndb, img_cache, txn, pubkey)
|
||||||
|
.size(16.0) // Text size
|
||||||
|
.selectable(false) // Disable text selection
|
||||||
|
.show(ui);
|
||||||
|
|
||||||
|
// Handle mention click
|
||||||
|
if let Some(action) = mention_response.inner {
|
||||||
|
// Usually NoteAction::Profile
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Media Handling
|
||||||
|
|
||||||
|
### Images
|
||||||
|
|
||||||
|
Images are managed through the `render_images` function, which handles loading, caching, and displaying images:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
render_images(
|
||||||
|
ui,
|
||||||
|
img_cache,
|
||||||
|
url,
|
||||||
|
ImageType::Content, // Or ImageType::Profile(size)
|
||||||
|
MediaCacheType::Image,
|
||||||
|
|ui| {
|
||||||
|
// Show while loading
|
||||||
|
ui.spinner();
|
||||||
|
},
|
||||||
|
|ui, error| {
|
||||||
|
// Show on error
|
||||||
|
ui.label(format!("Error: {}", error));
|
||||||
|
},
|
||||||
|
|ui, url, img, gifs| {
|
||||||
|
// Show successful image
|
||||||
|
let texture = handle_repaint(ui, retrieve_latest_texture(url, gifs, img));
|
||||||
|
ui.image(texture);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
For profile images, use `ImageType::Profile(size)` to automatically crop, resize, and round the image.
|
||||||
|
|
||||||
|
### GIF Animation
|
||||||
|
|
||||||
|
GIFs are supported through the animation system. The process for displaying GIFs is:
|
||||||
|
|
||||||
|
1. Load and decode GIF in background thread
|
||||||
|
2. Send frames to UI thread through channels
|
||||||
|
3. Render frames with timing control
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// Display a GIF
|
||||||
|
render_images(
|
||||||
|
ui,
|
||||||
|
img_cache,
|
||||||
|
gif_url,
|
||||||
|
ImageType::Content,
|
||||||
|
MediaCacheType::Gif,
|
||||||
|
/* callbacks as above */
|
||||||
|
);
|
||||||
|
|
||||||
|
// Get the current frame texture
|
||||||
|
let texture = handle_repaint(
|
||||||
|
ui,
|
||||||
|
retrieve_latest_texture(url, gifs, renderable_media)
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
## Widgets & Utilities
|
||||||
|
|
||||||
|
### Username
|
||||||
|
|
||||||
|
Displays a user's name with options for abbreviation and color:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
ui.add(
|
||||||
|
Username::new(profile, pubkey)
|
||||||
|
.pk_colored(true) // Color based on pubkey
|
||||||
|
.abbreviated(16) // Max length before abbreviation
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
### Animations
|
||||||
|
|
||||||
|
Use animation helpers for interactive elements:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// Basic hover animation
|
||||||
|
let (rect, size, response) = hover_expand(
|
||||||
|
ui,
|
||||||
|
id, // Unique ID for the animation
|
||||||
|
base_size, // Base size
|
||||||
|
expand_size, // Amount to expand by
|
||||||
|
anim_speed // Animation speed
|
||||||
|
);
|
||||||
|
|
||||||
|
// Small hover expand (common pattern)
|
||||||
|
let (rect, size, response) = hover_expand_small(ui, id);
|
||||||
|
|
||||||
|
// Advanced helper
|
||||||
|
let helper = AnimationHelper::new(ui, "animation_name", max_size);
|
||||||
|
let current_size = helper.scale_1d_pos(min_size);
|
||||||
|
```
|
||||||
|
|
||||||
|
### Pulsing Effects
|
||||||
|
|
||||||
|
For elements that need attention:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// Create pulsing image
|
||||||
|
let pulsing_image = ImagePulseTint::new(
|
||||||
|
&ctx, // EGUI Context
|
||||||
|
id, // Animation ID
|
||||||
|
image, // Base image
|
||||||
|
&[255, 183, 87], // Tint color
|
||||||
|
alpha_min, // Minimum alpha
|
||||||
|
alpha_max // Maximum alpha
|
||||||
|
)
|
||||||
|
.with_speed(0.35) // Animation speed
|
||||||
|
.animate(); // Apply animation
|
||||||
|
|
||||||
|
ui.add(pulsing_image);
|
||||||
|
```
|
||||||
|
|
||||||
|
### Context Menus
|
||||||
|
|
||||||
|
Create menus for additional actions:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// Add context menu to any response
|
||||||
|
response.context_menu(|ui| {
|
||||||
|
if ui.button("Copy Link").clicked() {
|
||||||
|
ui.ctx().copy_text(url.to_owned());
|
||||||
|
ui.close_menu();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
The `NoteContextButton` component provides a standard context menu for notes:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
let resp = ui.add(NoteContextButton::new(note_key));
|
||||||
|
if let Some(action) = NoteContextButton::menu(ui, resp) {
|
||||||
|
// Handle context action
|
||||||
|
}
|
||||||
|
```
|
||||||
Reference in New Issue
Block a user