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:
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
|
||||
Reference in New Issue
Block a user