mirror of
https://github.com/aljazceru/notedeck.git
synced 2025-12-17 08:44:20 +01:00
206 lines
6.4 KiB
Markdown
206 lines
6.4 KiB
Markdown
# 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
|
|
}
|
|
```
|
|
|
|
## 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
|
|
|
|
### 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
|