commit 9e8f7a2e5c64d2e553fd0ddf436cd79d0a11544f
parent 029896627cf83c0ea1dec1d618500c4ddebbe8f8
Author: William Casarin <jb55@jb55.com>
Date: Wed, 15 May 2024 17:20:25 -0700
ui: integrate egui-tabs for notes & replies selector
demo: https://cdn.jb55.com/s/notedeck-tabs.mp4
Fixes: https://github.com/damus-io/notedeck/issues/47
Signed-off-by: William Casarin <jb55@jb55.com>
Diffstat:
6 files changed, 108 insertions(+), 10 deletions(-)
diff --git a/Cargo.lock b/Cargo.lock
@@ -1036,6 +1036,15 @@ dependencies = [
]
[[package]]
+name = "egui-tabs"
+version = "0.1.0"
+source = "git+https://github.com/damus-io/egui-tabs?rev=ed97a57fc66b3781bc10ab644f9e1ed125d7377a#ed97a57fc66b3781bc10ab644f9e1ed125d7377a"
+dependencies = [
+ "egui",
+ "egui_extras",
+]
+
+[[package]]
name = "egui-wgpu"
version = "0.27.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2445,6 +2454,7 @@ dependencies = [
"console_error_panic_hook",
"eframe",
"egui",
+ "egui-tabs",
"egui_extras",
"egui_virtual_list",
"ehttp 0.2.0",
diff --git a/Cargo.toml b/Cargo.toml
@@ -19,6 +19,7 @@ eframe = { version = "0.27.2", default-features = false, features = [ "glow", "w
#eframe = "0.22.0"
egui_extras = { version = "0.27.2", features = ["all_loaders"] }
ehttp = "0.2.0"
+egui-tabs = { git = "https://github.com/damus-io/egui-tabs", rev = "ed97a57fc66b3781bc10ab644f9e1ed125d7377a" }
reqwest = { version = "0.12.4", default-features = false, features = [ "rustls-tls-native-roots" ] }
image = { version = "0.24", features = ["jpeg", "png", "webp"] }
log = "0.4.17"
diff --git a/src/app.rs b/src/app.rs
@@ -56,9 +56,9 @@ fn relay_setup(pool: &mut RelayPool, ctx: &egui::Context) {
if let Err(e) = pool.add_url("wss://relay.damus.io".to_string(), wakeup.clone()) {
error!("{:?}", e)
}
- if let Err(e) = pool.add_url("wss://pyramid.fiatjaf.com".to_string(), wakeup.clone()) {
- error!("{:?}", e)
- }
+ //if let Err(e) = pool.add_url("wss://pyramid.fiatjaf.com".to_string(), wakeup.clone()) {
+ //error!("{:?}", e)
+ //}
if let Err(e) = pool.add_url("wss://nos.lol".to_string(), wakeup.clone()) {
error!("{:?}", e)
}
diff --git a/src/app_creation.rs b/src/app_creation.rs
@@ -41,6 +41,10 @@ pub fn setup_cc(cc: &eframe::CreationContext<'_>) {
setup_fonts(ctx);
//ctx.set_pixels_per_point(ctx.pixels_per_point() + UI_SCALE_FACTOR);
+ //ctx.set_pixels_per_point(1.0);
+ //
+ //
+ //ctx.tessellation_options_mut(|to| to.feathering = false);
egui_extras::install_image_loaders(ctx);
diff --git a/src/colors.rs b/src/colors.rs
@@ -15,6 +15,7 @@ const DARK_BG: Color32 = Color32::from_rgb(0x2C, 0x2C, 0x2C);
const DARK_ISH_BG: Color32 = Color32::from_rgb(0x22, 0x22, 0x22);
const SEMI_DARK_BG: Color32 = Color32::from_rgb(0x44, 0x44, 0x44);
+const LIGHTER_GRAY: Color32 = Color32::from_rgb(0xe8, 0xe8, 0xe8);
const LIGHT_GRAY: Color32 = Color32::from_rgb(0xc8, 0xc8, 0xc8); // 78%
pub const MID_GRAY: Color32 = Color32::from_rgb(0xbd, 0xbd, 0xbd);
const DARKER_GRAY: Color32 = Color32::from_rgb(0xa5, 0xa5, 0xa5); // 65%
@@ -99,7 +100,7 @@ pub fn light_color_theme() -> ColorTheme {
// NONINTERACTIVE WIDGET
noninteractive_bg_fill: Color32::WHITE,
noninteractive_weak_bg_fill: EVEN_DARKER_GRAY,
- noninteractive_bg_stroke_color: DARKER_GRAY,
+ noninteractive_bg_stroke_color: LIGHTER_GRAY,
noninteractive_fg_stroke_color: GRAY_SECONDARY,
// INACTIVE WIDGET
diff --git a/src/timeline.rs b/src/timeline.rs
@@ -1,5 +1,7 @@
use crate::{ui, Damus};
use egui::containers::scroll_area::ScrollBarVisibility;
+use egui::{Direction, Layout};
+use egui_tabs::TabColor;
use egui_virtual_list::VirtualList;
use enostr::Filter;
use nostrdb::{NoteKey, Subscription, Transaction};
@@ -56,6 +58,90 @@ impl Timeline {
}
}
+fn get_label_width(ui: &mut egui::Ui, text: &str) -> f32 {
+ let font_id = egui::FontId::default();
+ let galley = ui.fonts(|r| r.layout_no_wrap(text.to_string(), font_id, egui::Color32::WHITE));
+ galley.rect.width()
+}
+
+fn shrink_range_to_width(range: egui::Rangef, width: f32) -> egui::Rangef {
+ let midpoint = (range.min + range.max) / 2.0;
+ let half_width = width / 2.0;
+
+ let min = midpoint - half_width;
+ let max = midpoint + half_width;
+
+ egui::Rangef::new(min, max)
+}
+
+fn tabs_ui(ui: &mut egui::Ui) {
+ ui.spacing_mut().item_spacing.y = 0.0;
+
+ let tab_res = egui_tabs::Tabs::new(2)
+ .hover_bg(TabColor::none())
+ .selected_fg(TabColor::none())
+ .selected_bg(TabColor::none())
+ .hover_bg(TabColor::none())
+ //.hover_bg(TabColor::custom(egui::Color32::RED))
+ .height(32.0)
+ .layout(Layout::centered_and_justified(Direction::TopDown))
+ .show(ui, |ui, state| {
+ ui.spacing_mut().item_spacing.y = 0.0;
+
+ let ind = state.index();
+
+ let txt = if ind == 0 { "Notes" } else { "Notes & Replies" };
+
+ let res = ui.add(egui::Label::new(txt).selectable(false));
+
+ // underline
+ if state.is_selected() {
+ let rect = res.rect;
+ let underline = rect.x_range().shrink(rect.width() / 4.0);
+ let underline = shrink_range_to_width(underline, get_label_width(ui, txt) * 1.15);
+ let underline_y = ui.painter().round_to_pixel(rect.bottom()) - 1.5;
+ return (underline, underline_y);
+ }
+
+ (egui::Rangef::new(0.0, 0.0), 0.0)
+ });
+
+ //ui.add_space(0.5);
+ ui::hline(ui);
+
+ // fun animation
+ if let Some(sel) = tab_res.selected() {
+ let (underline, underline_y) = tab_res.inner()[sel as usize].inner;
+ let underline_width = underline.span();
+
+ let tab_anim_id = ui.id().with("tab_anim");
+ let tab_anim_size = tab_anim_id.with("size");
+
+ let stroke = egui::Stroke {
+ color: ui.visuals().hyperlink_color,
+ width: 3.0,
+ };
+
+ let speed = 0.1f32;
+
+ // animate underline position
+ let x = ui
+ .ctx()
+ .animate_value_with_time(tab_anim_id, underline.min, speed);
+
+ // animate underline width
+ let w = ui
+ .ctx()
+ .animate_value_with_time(tab_anim_size, underline_width, speed);
+
+ let underline = egui::Rangef::new(x, x + w);
+
+ ui.painter().hline(underline, underline_y, stroke);
+ }
+
+ ui.add_space(3.0);
+}
+
pub fn timeline_view(ui: &mut egui::Ui, app: &mut Damus, timeline: usize) {
//padding(4.0, ui, |ui| ui.heading("Notifications"));
/*
@@ -63,14 +149,10 @@ pub fn timeline_view(ui: &mut egui::Ui, app: &mut Damus, timeline: usize) {
let row_height = ui.fonts(|f| f.row_height(&font_id)) + ui.spacing().item_spacing.y;
*/
+ tabs_ui(ui);
+
egui::ScrollArea::vertical()
.scroll_bar_visibility(ScrollBarVisibility::AlwaysVisible)
- //.auto_shrink([false; 2])
- /*
- .show_viewport(ui, |ui, viewport| {
- render_notes_in_viewport(ui, app, viewport, row_height, font_id);
- });
- */
.show(ui, |ui| {
let len = app.timelines[timeline].notes.len();
let list = app.timelines[timeline].list.clone();