dave: give dave a new home in the sidebar

Signed-off-by: William Casarin <jb55@jb55.com>
This commit is contained in:
William Casarin
2025-03-26 16:30:37 -07:00
parent 627c3ba9b3
commit b8e2a16e3b
5 changed files with 118 additions and 54 deletions

View File

@@ -0,0 +1,19 @@
use notedeck::AppContext;
use notedeck_columns::Damus;
use notedeck_dave::Dave;
pub enum NotedeckApp {
Dave(Dave),
Columns(Damus),
Other(Box<dyn notedeck::App>),
}
impl notedeck::App for NotedeckApp {
fn update(&mut self, ctx: &mut AppContext, ui: &mut egui::Ui) {
match self {
NotedeckApp::Dave(dave) => dave.update(ctx, ui),
NotedeckApp::Columns(columns) => columns.update(ctx, ui),
NotedeckApp::Other(other) => other.update(ctx, ui),
}
}
}

View File

@@ -1,10 +1,12 @@
// Entry point for wasm // Entry point for wasm
//#[cfg(target_arch = "wasm32")] //#[cfg(target_arch = "wasm32")]
//use wasm_bindgen::prelude::*; //use wasm_bindgen::prelude::*;
use egui::{Button, Label, Layout, RichText, ThemePreference, Widget}; use crate::app::NotedeckApp;
use egui::{vec2, Button, Label, Layout, RichText, ThemePreference, Widget};
use egui_extras::{Size, StripBuilder}; use egui_extras::{Size, StripBuilder};
use nostrdb::{ProfileRecord, Transaction}; use nostrdb::{ProfileRecord, Transaction};
use notedeck::{AppContext, NotedeckTextStyle, UserAccount}; use notedeck::{App, AppContext, NotedeckTextStyle, UserAccount};
use notedeck_dave::{Dave, DaveAvatar};
use notedeck_ui::{profile::get_profile_url, AnimationHelper, ProfilePic}; use notedeck_ui::{profile::get_profile_url, AnimationHelper, ProfilePic};
static ICON_WIDTH: f32 = 40.0; static ICON_WIDTH: f32 = 40.0;
@@ -13,7 +15,7 @@ pub static ICON_EXPANSION_MULTIPLE: f32 = 1.2;
#[derive(Default)] #[derive(Default)]
pub struct Chrome { pub struct Chrome {
active: i32, active: i32,
apps: Vec<Box<dyn notedeck::App>>, apps: Vec<NotedeckApp>,
} }
pub enum ChromePanelAction { pub enum ChromePanelAction {
@@ -28,8 +30,18 @@ impl Chrome {
Chrome::default() Chrome::default()
} }
pub fn add_app(&mut self, app: impl notedeck::App + 'static) { pub fn add_app(&mut self, app: NotedeckApp) {
self.apps.push(Box::new(app)); self.apps.push(app);
}
fn get_dave(&mut self) -> Option<&mut Dave> {
for app in &mut self.apps {
if let NotedeckApp::Dave(dave) = app {
return Some(dave);
}
}
None
} }
pub fn set_active(&mut self, app: i32) { pub fn set_active(&mut self, app: i32) {
@@ -44,7 +56,7 @@ impl Chrome {
fn show(&mut self, ctx: &mut AppContext, ui: &mut egui::Ui) { fn show(&mut self, ctx: &mut AppContext, ui: &mut egui::Ui) {
ui.spacing_mut().item_spacing.x = 0.0; ui.spacing_mut().item_spacing.x = 0.0;
let side_panel_width: f32 = 68.0; let side_panel_width: f32 = 70.0;
StripBuilder::new(ui) StripBuilder::new(ui)
.size(Size::exact(side_panel_width)) // collapsible sidebar .size(Size::exact(side_panel_width)) // collapsible sidebar
.size(Size::remainder()) // the main app contents .size(Size::remainder()) // the main app contents
@@ -103,9 +115,10 @@ impl Chrome {
ctx: &mut AppContext, ctx: &mut AppContext,
ui: &mut egui::Ui, ui: &mut egui::Ui,
) -> Option<ChromePanelAction> { ) -> Option<ChromePanelAction> {
let dark_mode = ui.ctx().style().visuals.dark_mode; ui.add_space(8.0);
let pfp_resp = self.pfp_button(ctx, ui); let pfp_resp = self.pfp_button(ctx, ui);
let settings_resp = ui.add(settings_button(dark_mode)); let settings_resp = settings_button(ui);
let theme_action = match ui.ctx().theme() { let theme_action = match ui.ctx().theme() {
egui::Theme::Dark => { egui::Theme::Dark => {
@@ -130,7 +143,7 @@ impl Chrome {
} }
}; };
if ui.add(support_button()).clicked() { if support_button(ui).clicked() {
return Some(ChromePanelAction::Support); return Some(ChromePanelAction::Support);
} }
@@ -179,7 +192,15 @@ impl Chrome {
ui.add(milestone_name()); ui.add(milestone_name());
ui.add_space(16.0); ui.add_space(16.0);
//let dark_mode = ui.ctx().style().visuals.dark_mode; //let dark_mode = ui.ctx().style().visuals.dark_mode;
//ui.add(add_column_button(dark_mode)) if columns_button(ui).clicked() {
self.active = 0;
}
ui.add_space(32.0);
if let Some(dave) = self.get_dave() {
if dave_button(dave.avatar_mut(), ui).clicked() {
self.active = 1;
}
}
} }
} }
@@ -222,54 +243,69 @@ fn expand_side_panel_button() -> impl Widget {
} }
} }
fn support_button() -> impl Widget { fn expanding_button(
|ui: &mut egui::Ui| -> egui::Response { name: &'static str,
let img_size = 16.0; img_size: f32,
light_img: &egui::ImageSource,
dark_img: &egui::ImageSource,
ui: &mut egui::Ui,
) -> egui::Response {
let max_size = ICON_WIDTH * ICON_EXPANSION_MULTIPLE; // max size of the widget
let img_data = if ui.visuals().dark_mode {
dark_img
} else {
light_img
};
let img = egui::Image::new(img_data.clone()).max_width(img_size);
let max_size = ICON_WIDTH * ICON_EXPANSION_MULTIPLE; // max size of the widget let helper = AnimationHelper::new(ui, name, egui::vec2(max_size, max_size));
let img_data = if ui.visuals().dark_mode {
egui::include_image!("../../../assets/icons/help_icon_dark_4x.png")
} else {
egui::include_image!("../../../assets/icons/help_icon_inverted_4x.png")
};
let img = egui::Image::new(img_data).max_width(img_size);
let helper = AnimationHelper::new(ui, "help-button", egui::vec2(max_size, max_size)); let cur_img_size = helper.scale_1d_pos(img_size);
img.paint_at(
ui,
helper
.get_animation_rect()
.shrink((max_size - cur_img_size) / 2.0),
);
let cur_img_size = helper.scale_1d_pos(img_size); helper.take_animation_response()
img.paint_at(
ui,
helper
.get_animation_rect()
.shrink((max_size - cur_img_size) / 2.0),
);
helper.take_animation_response()
}
} }
fn settings_button(dark_mode: bool) -> impl Widget { fn support_button(ui: &mut egui::Ui) -> egui::Response {
move |ui: &mut egui::Ui| { expanding_button(
let img_size = 24.0; "help-button",
let max_size = ICON_WIDTH * ICON_EXPANSION_MULTIPLE; // max size of the widget 16.0,
let img_data = if dark_mode { &egui::include_image!("../../../assets/icons/help_icon_inverted_4x.png"),
egui::include_image!("../../../assets/icons/settings_dark_4x.png") &egui::include_image!("../../../assets/icons/help_icon_dark_4x.png"),
} else { ui,
egui::include_image!("../../../assets/icons/settings_light_4x.png") )
}; }
let img = egui::Image::new(img_data).max_width(img_size);
let helper = AnimationHelper::new(ui, "settings-button", egui::vec2(max_size, max_size)); fn settings_button(ui: &mut egui::Ui) -> egui::Response {
expanding_button(
"settings-button",
32.0,
&egui::include_image!("../../../assets/icons/settings_light_4x.png"),
&egui::include_image!("../../../assets/icons/settings_dark_4x.png"),
ui,
)
}
let cur_img_size = helper.scale_1d_pos(img_size); fn columns_button(ui: &mut egui::Ui) -> egui::Response {
img.paint_at( let btn = egui::include_image!("../../../assets/icons/columns_80.png");
ui, expanding_button("columns-button", 40.0, &btn, &btn, ui)
helper }
.get_animation_rect()
.shrink((max_size - cur_img_size) / 2.0),
);
helper.take_animation_response() fn dave_button(avatar: Option<&mut DaveAvatar>, ui: &mut egui::Ui) -> egui::Response {
if let Some(avatar) = avatar {
let size = vec2(60.0, 60.0);
let available = ui.available_rect_before_wrap();
let center_x = available.center().x;
let rect = egui::Rect::from_center_size(egui::pos2(center_x, available.top()), size);
avatar.render(rect, ui)
} else {
// plain icon if wgpu device not available??
ui.label("fixme")
} }
} }

View File

@@ -5,6 +5,8 @@ pub mod theme;
#[cfg(target_os = "android")] #[cfg(target_os = "android")]
mod android; mod android;
mod app;
mod chrome; mod chrome;
pub use app::NotedeckApp;
pub use chrome::Chrome; pub use chrome::Chrome;

View File

@@ -4,7 +4,7 @@
use notedeck::{DataPath, DataPathType, Notedeck}; use notedeck::{DataPath, DataPathType, Notedeck};
use notedeck_chrome::{ use notedeck_chrome::{
setup::{generate_native_options, setup_chrome}, setup::{generate_native_options, setup_chrome},
Chrome, Chrome, NotedeckApp,
}; };
use notedeck_columns::Damus; use notedeck_columns::Damus;
use notedeck_dave::Dave; use notedeck_dave::Dave;
@@ -99,8 +99,8 @@ async fn main() {
completely_unrecognized completely_unrecognized
); );
chrome.add_app(columns); chrome.add_app(NotedeckApp::Columns(columns));
chrome.add_app(dave); chrome.add_app(NotedeckApp::Dave(dave));
// test dav // test dav
chrome.set_active(1); chrome.set_active(1);

View File

@@ -21,7 +21,7 @@ use std::sync::mpsc::{self, Receiver};
use std::sync::Arc; use std::sync::Arc;
use time::{format_description::well_known::Rfc3339, OffsetDateTime}; use time::{format_description::well_known::Rfc3339, OffsetDateTime};
use avatar::DaveAvatar; pub use avatar::DaveAvatar;
use egui::{Rect, Vec2}; use egui::{Rect, Vec2};
use egui_wgpu::RenderState; use egui_wgpu::RenderState;
@@ -306,6 +306,10 @@ pub struct Dave {
} }
impl Dave { impl Dave {
pub fn avatar_mut(&mut self) -> Option<&mut DaveAvatar> {
self.avatar.as_mut()
}
pub fn new(render_state: Option<&RenderState>) -> Self { pub fn new(render_state: Option<&RenderState>) -> Self {
let mut config = OpenAIConfig::new(); //.with_api_base("http://ollama.jb55.com/v1"); let mut config = OpenAIConfig::new(); //.with_api_base("http://ollama.jb55.com/v1");
if let Ok(api_key) = std::env::var("OPENAI_API_KEY") { if let Ok(api_key) = std::env::var("OPENAI_API_KEY") {
@@ -393,12 +397,15 @@ impl Dave {
}); });
}); });
/*
// he lives in the sidebar now
if let Some(avatar) = &mut self.avatar { if let Some(avatar) = &mut self.avatar {
let avatar_size = Vec2::splat(300.0); let avatar_size = Vec2::splat(300.0);
let pos = Vec2::splat(100.0).to_pos2(); let pos = Vec2::splat(100.0).to_pos2();
let pos = Rect::from_min_max(pos, pos + avatar_size); let pos = Rect::from_min_max(pos, pos + avatar_size);
avatar.render(pos, ui); avatar.render(pos, ui);
} }
*/
// send again // send again
if should_send { if should_send {