mirror of
https://github.com/aljazceru/notedeck.git
synced 2026-01-14 13:54:19 +01:00
title bar
add title bar to columns with title specific to the column type. also add column deletion button Signed-off-by: kernelkind <kernelkind@gmail.com>
This commit is contained in:
BIN
assets/icons/column_delete_icon_4x.png
Normal file
BIN
assets/icons/column_delete_icon_4x.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 806 B |
19
src/app.rs
19
src/app.rs
@@ -247,7 +247,7 @@ fn try_process_event(damus: &mut Damus, ctx: &egui::Context) -> Result<()> {
|
||||
|
||||
if let Err(err) = Timeline::poll_notes_into_view(
|
||||
timeline_ind,
|
||||
&mut damus.columns.timelines,
|
||||
damus.columns.timelines_mut(),
|
||||
&damus.ndb,
|
||||
&txn,
|
||||
&mut damus.unknown_ids,
|
||||
@@ -490,6 +490,8 @@ fn update_damus(damus: &mut Damus, ctx: &egui::Context) {
|
||||
if let Err(err) = try_process_event(damus, ctx) {
|
||||
error!("error processing event: {}", err);
|
||||
}
|
||||
|
||||
damus.columns.attempt_perform_deletion_request();
|
||||
}
|
||||
|
||||
fn process_event(damus: &mut Damus, _subid: &str, event: &str) {
|
||||
@@ -952,7 +954,7 @@ fn render_damus_desktop(ctx: &egui::Context, app: &mut Damus) {
|
||||
puffin::profile_function!();
|
||||
|
||||
let screen_size = ctx.screen_rect().width();
|
||||
let calc_panel_width = (screen_size / app.columns.columns().len() as f32) - 30.0;
|
||||
let calc_panel_width = (screen_size / app.columns.num_columns() as f32) - 30.0;
|
||||
let min_width = 320.0;
|
||||
let need_scroll = calc_panel_width < min_width;
|
||||
let panel_sizes = if need_scroll {
|
||||
@@ -965,18 +967,18 @@ fn render_damus_desktop(ctx: &egui::Context, app: &mut Damus) {
|
||||
ui.spacing_mut().item_spacing.x = 0.0;
|
||||
if need_scroll {
|
||||
egui::ScrollArea::horizontal().show(ui, |ui| {
|
||||
timelines_view(ui, panel_sizes, app, app.columns.columns().len());
|
||||
timelines_view(ui, panel_sizes, app);
|
||||
});
|
||||
} else {
|
||||
timelines_view(ui, panel_sizes, app, app.columns.columns().len());
|
||||
timelines_view(ui, panel_sizes, app);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn timelines_view(ui: &mut egui::Ui, sizes: Size, app: &mut Damus, columns: usize) {
|
||||
fn timelines_view(ui: &mut egui::Ui, sizes: Size, app: &mut Damus) {
|
||||
StripBuilder::new(ui)
|
||||
.size(Size::exact(ui::side_panel::SIDE_PANEL_WIDTH))
|
||||
.sizes(sizes, columns)
|
||||
.sizes(sizes, app.columns.num_columns())
|
||||
.clip(true)
|
||||
.horizontal(|mut strip| {
|
||||
strip.cell(|ui| {
|
||||
@@ -1000,11 +1002,10 @@ fn timelines_view(ui: &mut egui::Ui, sizes: Size, app: &mut Damus, columns: usiz
|
||||
);
|
||||
});
|
||||
|
||||
let n_cols = app.columns.columns().len();
|
||||
for column_ind in 0..n_cols {
|
||||
for col_index in 0..app.columns.num_columns() {
|
||||
strip.cell(|ui| {
|
||||
let rect = ui.available_rect_before_wrap();
|
||||
nav::render_nav(column_ind, app, ui);
|
||||
nav::render_nav(col_index, app, ui);
|
||||
|
||||
// vertical line
|
||||
ui.painter().vline(
|
||||
|
||||
117
src/column.rs
117
src/column.rs
@@ -1,6 +1,8 @@
|
||||
use crate::route::{Route, Router};
|
||||
use crate::timeline::{Timeline, TimelineId};
|
||||
use indexmap::IndexMap;
|
||||
use std::iter::Iterator;
|
||||
use std::sync::atomic::{AtomicU32, Ordering};
|
||||
use tracing::warn;
|
||||
|
||||
pub struct Column {
|
||||
@@ -25,16 +27,18 @@ impl Column {
|
||||
#[derive(Default)]
|
||||
pub struct Columns {
|
||||
/// Columns are simply routers into settings, timelines, etc
|
||||
columns: Vec<Column>,
|
||||
columns: IndexMap<u32, Column>,
|
||||
|
||||
/// Timeline state is not tied to routing logic separately, so that
|
||||
/// different columns can navigate to and from settings to timelines,
|
||||
/// etc.
|
||||
pub timelines: Vec<Timeline>,
|
||||
pub timelines: IndexMap<u32, Timeline>,
|
||||
|
||||
/// The selected column for key navigation
|
||||
selected: i32,
|
||||
should_delete_column_at_index: Option<usize>,
|
||||
}
|
||||
static UIDS: AtomicU32 = AtomicU32::new(0);
|
||||
|
||||
impl Columns {
|
||||
pub fn new() -> Self {
|
||||
@@ -42,25 +46,38 @@ impl Columns {
|
||||
}
|
||||
|
||||
pub fn add_timeline(&mut self, timeline: Timeline) {
|
||||
let id = Self::get_new_id();
|
||||
let routes = vec![Route::timeline(timeline.id)];
|
||||
self.timelines.push(timeline);
|
||||
self.columns.push(Column::new(routes))
|
||||
self.timelines.insert(id, timeline);
|
||||
self.columns.insert(id, Column::new(routes));
|
||||
}
|
||||
|
||||
pub fn add_timeline_to_column(&mut self, col: usize, timeline: Timeline) -> bool {
|
||||
if let Some(column) = self.columns.get_mut(col) {
|
||||
column
|
||||
.router_mut()
|
||||
.route_to_replaced(Route::timeline(timeline.id));
|
||||
self.timelines.push(timeline);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
pub fn add_timeline_to_column(&mut self, col: usize, timeline: Timeline) {
|
||||
let col_id = self.get_column_id_at_index(col);
|
||||
self.column_mut(col)
|
||||
.router_mut()
|
||||
.route_to_replaced(Route::timeline(timeline.id));
|
||||
self.timelines.insert(col_id, timeline);
|
||||
}
|
||||
|
||||
pub fn new_column_picker(&mut self) {
|
||||
self.columns.push(Column::new(vec![Route::AddColumn]));
|
||||
self.add_column(Column::new(vec![Route::AddColumn]));
|
||||
}
|
||||
|
||||
fn get_new_id() -> u32 {
|
||||
UIDS.fetch_add(1, Ordering::Relaxed)
|
||||
}
|
||||
|
||||
pub fn add_column(&mut self, column: Column) {
|
||||
self.columns.insert(Self::get_new_id(), column);
|
||||
}
|
||||
|
||||
pub fn columns_mut(&mut self) -> Vec<&mut Column> {
|
||||
self.columns.values_mut().collect()
|
||||
}
|
||||
|
||||
pub fn num_columns(&self) -> usize {
|
||||
self.columns.len()
|
||||
}
|
||||
|
||||
// Get the first router in the columns if there are columns present.
|
||||
@@ -70,49 +87,66 @@ impl Columns {
|
||||
self.new_column_picker();
|
||||
}
|
||||
self.columns
|
||||
.get_mut(0)
|
||||
.get_index_mut(0)
|
||||
.expect("There should be at least one column")
|
||||
.1
|
||||
.router_mut()
|
||||
}
|
||||
|
||||
pub fn columns_mut(&mut self) -> &mut Vec<Column> {
|
||||
&mut self.columns
|
||||
}
|
||||
|
||||
pub fn timeline_mut(&mut self, timeline_ind: usize) -> &mut Timeline {
|
||||
&mut self.timelines[timeline_ind]
|
||||
self.timelines
|
||||
.get_index_mut(timeline_ind)
|
||||
.expect("expected index to be in bounds")
|
||||
.1
|
||||
}
|
||||
|
||||
pub fn column(&self, ind: usize) -> &Column {
|
||||
&self.columns()[ind]
|
||||
self.columns
|
||||
.get_index(ind)
|
||||
.expect("Expected index to be in bounds")
|
||||
.1
|
||||
}
|
||||
|
||||
pub fn columns(&self) -> &Vec<Column> {
|
||||
&self.columns
|
||||
pub fn columns(&self) -> Vec<&Column> {
|
||||
self.columns.values().collect()
|
||||
}
|
||||
|
||||
pub fn get_column_id_at_index(&self, ind: usize) -> u32 {
|
||||
*self
|
||||
.columns
|
||||
.get_index(ind)
|
||||
.expect("expected index to be within bounds")
|
||||
.0
|
||||
}
|
||||
|
||||
pub fn selected(&mut self) -> &mut Column {
|
||||
&mut self.columns[self.selected as usize]
|
||||
self.columns
|
||||
.get_index_mut(self.selected as usize)
|
||||
.expect("Expected selected index to be in bounds")
|
||||
.1
|
||||
}
|
||||
|
||||
pub fn timelines_mut(&mut self) -> &mut Vec<Timeline> {
|
||||
&mut self.timelines
|
||||
pub fn timelines_mut(&mut self) -> Vec<&mut Timeline> {
|
||||
self.timelines.values_mut().collect()
|
||||
}
|
||||
|
||||
pub fn timelines(&self) -> &Vec<Timeline> {
|
||||
&self.timelines
|
||||
pub fn timelines(&self) -> Vec<&Timeline> {
|
||||
self.timelines.values().collect()
|
||||
}
|
||||
|
||||
pub fn find_timeline_mut(&mut self, id: TimelineId) -> Option<&mut Timeline> {
|
||||
self.timelines_mut().iter_mut().find(|tl| tl.id == id)
|
||||
self.timelines_mut().into_iter().find(|tl| tl.id == id)
|
||||
}
|
||||
|
||||
pub fn find_timeline(&self, id: TimelineId) -> Option<&Timeline> {
|
||||
self.timelines().iter().find(|tl| tl.id == id)
|
||||
self.timelines().into_iter().find(|tl| tl.id == id)
|
||||
}
|
||||
|
||||
pub fn column_mut(&mut self, ind: usize) -> &mut Column {
|
||||
&mut self.columns[ind]
|
||||
self.columns
|
||||
.get_index_mut(ind)
|
||||
.expect("Expected index to be in bounds")
|
||||
.1
|
||||
}
|
||||
|
||||
pub fn select_down(&mut self) {
|
||||
@@ -136,4 +170,23 @@ impl Columns {
|
||||
}
|
||||
self.selected += 1;
|
||||
}
|
||||
|
||||
pub fn request_deletion_at_index(&mut self, index: usize) {
|
||||
self.should_delete_column_at_index = Some(index);
|
||||
}
|
||||
|
||||
pub fn attempt_perform_deletion_request(&mut self) {
|
||||
if let Some(index) = self.should_delete_column_at_index {
|
||||
if let Some((key, _)) = self.columns.get_index_mut(index) {
|
||||
self.timelines.shift_remove(key);
|
||||
}
|
||||
|
||||
self.columns.shift_remove_index(index);
|
||||
self.should_delete_column_at_index = None;
|
||||
|
||||
if self.columns.is_empty() {
|
||||
self.new_column_picker();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
15
src/fonts.rs
15
src/fonts.rs
@@ -4,12 +4,13 @@ use tracing::debug;
|
||||
|
||||
pub enum NamedFontFamily {
|
||||
Medium,
|
||||
Bold,
|
||||
}
|
||||
|
||||
impl NamedFontFamily {
|
||||
pub fn as_str(&mut self) -> &'static str {
|
||||
match self {
|
||||
//Self::Bold => "bold",
|
||||
Self::Bold => "bold",
|
||||
Self::Medium => "medium",
|
||||
}
|
||||
}
|
||||
@@ -43,7 +44,7 @@ pub fn setup_fonts(ctx: &egui::Context) {
|
||||
"DejaVuSans".to_owned(),
|
||||
FontData::from_static(include_bytes!("../assets/fonts/DejaVuSansSansEmoji.ttf")),
|
||||
);
|
||||
/*
|
||||
|
||||
font_data.insert(
|
||||
"OnestBold".to_owned(),
|
||||
FontData::from_static(include_bytes!(
|
||||
@@ -51,6 +52,7 @@ pub fn setup_fonts(ctx: &egui::Context) {
|
||||
)),
|
||||
);
|
||||
|
||||
/*
|
||||
font_data.insert(
|
||||
"DejaVuSansBold".to_owned(),
|
||||
FontData::from_static(include_bytes!(
|
||||
@@ -119,7 +121,10 @@ pub fn setup_fonts(ctx: &egui::Context) {
|
||||
medium.extend(base_fonts.clone());
|
||||
|
||||
let mut mono = vec!["Inconsolata".to_owned()];
|
||||
mono.extend(base_fonts);
|
||||
mono.extend(base_fonts.clone());
|
||||
|
||||
let mut bold = vec!["OnestBold".to_owned()];
|
||||
bold.extend(base_fonts);
|
||||
|
||||
families.insert(egui::FontFamily::Proportional, proportional);
|
||||
families.insert(egui::FontFamily::Monospace, mono);
|
||||
@@ -127,6 +132,10 @@ pub fn setup_fonts(ctx: &egui::Context) {
|
||||
egui::FontFamily::Name(NamedFontFamily::Medium.as_str().into()),
|
||||
medium,
|
||||
);
|
||||
families.insert(
|
||||
egui::FontFamily::Name(NamedFontFamily::Bold.as_str().into()),
|
||||
bold,
|
||||
);
|
||||
|
||||
debug!("fonts: {:?}", families);
|
||||
|
||||
|
||||
241
src/nav.rs
241
src/nav.rs
@@ -1,5 +1,7 @@
|
||||
use crate::{
|
||||
account_manager::render_accounts_route,
|
||||
app_style::{get_font_size, NotedeckTextStyle},
|
||||
fonts::NamedFontFamily,
|
||||
relay_pool_manager::RelayPoolManager,
|
||||
route::Route,
|
||||
thread::thread_unsubscribe,
|
||||
@@ -7,91 +9,105 @@ use crate::{
|
||||
ui::{
|
||||
self,
|
||||
add_column::{AddColumnResponse, AddColumnView},
|
||||
anim::{AnimationHelper, ICON_EXPANSION_MULTIPLE},
|
||||
note::PostAction,
|
||||
RelayView, View,
|
||||
},
|
||||
Damus,
|
||||
};
|
||||
|
||||
use egui::{pos2, Color32, InnerResponse};
|
||||
use egui_nav::{Nav, NavAction};
|
||||
|
||||
pub fn render_nav(col: usize, app: &mut Damus, ui: &mut egui::Ui) {
|
||||
let col_id = app.columns.get_column_id_at_index(col);
|
||||
// TODO(jb55): clean up this router_mut mess by using Router<R> in egui-nav directly
|
||||
let nav_response = Nav::new(app.columns().column(col).router().routes().clone())
|
||||
let routes = app
|
||||
.columns()
|
||||
.column(col)
|
||||
.router()
|
||||
.routes()
|
||||
.iter()
|
||||
.map(|r| r.get_titled_route(&app.columns, &app.ndb))
|
||||
.collect();
|
||||
let nav_response = Nav::new(routes)
|
||||
.navigating(app.columns_mut().column_mut(col).router_mut().navigating)
|
||||
.returning(app.columns_mut().column_mut(col).router_mut().returning)
|
||||
.title(false)
|
||||
.show_mut(ui, |ui, nav| match nav.top() {
|
||||
Route::Timeline(tlr) => render_timeline_route(
|
||||
&app.ndb,
|
||||
&mut app.columns,
|
||||
&mut app.pool,
|
||||
&mut app.drafts,
|
||||
&mut app.img_cache,
|
||||
&mut app.note_cache,
|
||||
&mut app.threads,
|
||||
&mut app.accounts,
|
||||
*tlr,
|
||||
col,
|
||||
app.textmode,
|
||||
ui,
|
||||
),
|
||||
Route::Accounts(amr) => {
|
||||
render_accounts_route(
|
||||
ui,
|
||||
.title(48.0, title_bar)
|
||||
.show_mut(col_id, ui, |ui, nav| {
|
||||
let column = app.columns.column_mut(col);
|
||||
match &nav.top().route {
|
||||
Route::Timeline(tlr) => render_timeline_route(
|
||||
&app.ndb,
|
||||
col,
|
||||
&mut app.columns,
|
||||
&mut app.img_cache,
|
||||
&mut app.accounts,
|
||||
&mut app.view_state.login,
|
||||
*amr,
|
||||
);
|
||||
None
|
||||
}
|
||||
Route::Relays => {
|
||||
let manager = RelayPoolManager::new(app.pool_mut());
|
||||
RelayView::new(manager).ui(ui);
|
||||
None
|
||||
}
|
||||
Route::ComposeNote => {
|
||||
let kp = app.accounts.selected_or_first_nsec()?;
|
||||
let draft = app.drafts.compose_mut();
|
||||
|
||||
let txn = nostrdb::Transaction::new(&app.ndb).expect("txn");
|
||||
let post_response = ui::PostView::new(
|
||||
&app.ndb,
|
||||
draft,
|
||||
crate::draft::DraftSource::Compose,
|
||||
&mut app.pool,
|
||||
&mut app.drafts,
|
||||
&mut app.img_cache,
|
||||
&mut app.note_cache,
|
||||
kp,
|
||||
)
|
||||
.ui(&txn, ui);
|
||||
|
||||
if let Some(action) = post_response.action {
|
||||
PostAction::execute(kp, &action, &mut app.pool, draft, |np, seckey| {
|
||||
np.to_note(seckey)
|
||||
});
|
||||
app.columns_mut().column_mut(col).router_mut().go_back();
|
||||
&mut app.threads,
|
||||
&mut app.accounts,
|
||||
*tlr,
|
||||
col,
|
||||
app.textmode,
|
||||
ui,
|
||||
),
|
||||
Route::Accounts(amr) => {
|
||||
render_accounts_route(
|
||||
ui,
|
||||
&app.ndb,
|
||||
col,
|
||||
&mut app.columns,
|
||||
&mut app.img_cache,
|
||||
&mut app.accounts,
|
||||
&mut app.view_state.login,
|
||||
*amr,
|
||||
);
|
||||
None
|
||||
}
|
||||
Route::Relays => {
|
||||
let manager = RelayPoolManager::new(app.pool_mut());
|
||||
RelayView::new(manager).ui(ui);
|
||||
None
|
||||
}
|
||||
Route::ComposeNote => {
|
||||
let kp = app.accounts.selected_or_first_nsec()?;
|
||||
let draft = app.drafts.compose_mut();
|
||||
|
||||
None
|
||||
}
|
||||
Route::AddColumn => {
|
||||
let resp = AddColumnView::new(&app.ndb, app.accounts.get_selected_account()).ui(ui);
|
||||
let txn = nostrdb::Transaction::new(&app.ndb).expect("txn");
|
||||
let post_response = ui::PostView::new(
|
||||
&app.ndb,
|
||||
draft,
|
||||
crate::draft::DraftSource::Compose,
|
||||
&mut app.img_cache,
|
||||
&mut app.note_cache,
|
||||
kp,
|
||||
)
|
||||
.ui(&txn, ui);
|
||||
|
||||
if let Some(resp) = resp {
|
||||
match resp {
|
||||
AddColumnResponse::Timeline(timeline) => {
|
||||
let id = timeline.id;
|
||||
if app.columns_mut().add_timeline_to_column(col, timeline) {
|
||||
if let Some(action) = post_response.action {
|
||||
PostAction::execute(kp, &action, &mut app.pool, draft, |np, seckey| {
|
||||
np.to_note(seckey)
|
||||
});
|
||||
column.router_mut().go_back();
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
Route::AddColumn => {
|
||||
let resp =
|
||||
AddColumnView::new(&app.ndb, app.accounts.get_selected_account()).ui(ui);
|
||||
|
||||
if let Some(resp) = resp {
|
||||
match resp {
|
||||
AddColumnResponse::Timeline(timeline) => {
|
||||
let id = timeline.id;
|
||||
app.columns_mut().add_timeline_to_column(col, timeline);
|
||||
app.subscribe_new_timeline(id);
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
}
|
||||
None
|
||||
}
|
||||
None
|
||||
}
|
||||
});
|
||||
|
||||
@@ -128,4 +144,103 @@ pub fn render_nav(col: usize, app: &mut Damus, ui: &mut egui::Ui) {
|
||||
cur_router.remove_previous_route();
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(title_response) = nav_response.title_response {
|
||||
match title_response {
|
||||
TitleResponse::RemoveColumn => {
|
||||
app.columns_mut().request_deletion_at_index(col);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn title_bar(
|
||||
ui: &mut egui::Ui,
|
||||
title_name: String,
|
||||
allocated_response: egui::Response,
|
||||
) -> egui::InnerResponse<Option<TitleResponse>> {
|
||||
let icon_width = 32.0;
|
||||
let padding = 16.0;
|
||||
title(ui, title_name, allocated_response.rect, icon_width, padding);
|
||||
let button_resp = delete_column_button(ui, allocated_response, icon_width, padding);
|
||||
let title_response = if button_resp.clicked() {
|
||||
Some(TitleResponse::RemoveColumn)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
InnerResponse::new(title_response, button_resp)
|
||||
}
|
||||
|
||||
fn delete_column_button(
|
||||
ui: &mut egui::Ui,
|
||||
title_bar_resp: egui::Response,
|
||||
icon_width: f32,
|
||||
padding: f32,
|
||||
) -> egui::Response {
|
||||
let img_size = 16.0;
|
||||
let max_size = icon_width * ICON_EXPANSION_MULTIPLE;
|
||||
|
||||
let img_data = egui::include_image!("../assets/icons/column_delete_icon_4x.png");
|
||||
let img = egui::Image::new(img_data).max_width(img_size);
|
||||
|
||||
let button_rect = {
|
||||
let titlebar_rect = title_bar_resp.rect;
|
||||
let titlebar_width = titlebar_rect.width();
|
||||
let titlebar_center = titlebar_rect.center();
|
||||
let button_center_y = titlebar_center.y;
|
||||
let button_center_x =
|
||||
titlebar_center.x + (titlebar_width / 2.0) - (max_size / 2.0) - padding;
|
||||
egui::Rect::from_center_size(
|
||||
pos2(button_center_x, button_center_y),
|
||||
egui::vec2(max_size, max_size),
|
||||
)
|
||||
};
|
||||
|
||||
let helper = AnimationHelper::new_from_rect(ui, "delete-column-button", button_rect);
|
||||
|
||||
let cur_img_size = helper.scale_1d_pos(img_size);
|
||||
|
||||
let animation_rect = helper.get_animation_rect();
|
||||
let animation_resp = helper.take_animation_response();
|
||||
if title_bar_resp.union(animation_resp.clone()).hovered() {
|
||||
img.paint_at(ui, animation_rect.shrink((max_size - cur_img_size) / 2.0));
|
||||
}
|
||||
|
||||
animation_resp
|
||||
}
|
||||
|
||||
fn title(
|
||||
ui: &mut egui::Ui,
|
||||
title_name: String,
|
||||
titlebar_rect: egui::Rect,
|
||||
icon_width: f32,
|
||||
padding: f32,
|
||||
) {
|
||||
let painter = ui.painter_at(titlebar_rect);
|
||||
|
||||
let font = egui::FontId::new(
|
||||
get_font_size(ui.ctx(), &NotedeckTextStyle::Body),
|
||||
egui::FontFamily::Name(NamedFontFamily::Bold.as_str().into()),
|
||||
);
|
||||
|
||||
let max_title_width = titlebar_rect.width() - icon_width - padding * 2.;
|
||||
let title_galley =
|
||||
ui.fonts(|f| f.layout(title_name, font, ui.visuals().text_color(), max_title_width));
|
||||
|
||||
let pos = {
|
||||
let titlebar_center = titlebar_rect.center();
|
||||
let titlebar_width = titlebar_rect.width();
|
||||
let text_height = title_galley.rect.height();
|
||||
|
||||
let galley_pos_x = titlebar_center.x - (titlebar_width / 2.) + padding;
|
||||
let galley_pos_y = titlebar_center.y - (text_height / 2.);
|
||||
pos2(galley_pos_x, galley_pos_y)
|
||||
};
|
||||
|
||||
painter.galley(pos, title_galley, Color32::WHITE);
|
||||
}
|
||||
|
||||
enum TitleResponse {
|
||||
RemoveColumn,
|
||||
}
|
||||
|
||||
51
src/route.rs
51
src/route.rs
@@ -1,9 +1,12 @@
|
||||
use enostr::NoteId;
|
||||
use nostrdb::Ndb;
|
||||
use std::fmt::{self};
|
||||
|
||||
use crate::{
|
||||
account_manager::AccountsRoute,
|
||||
column::Columns,
|
||||
timeline::{TimelineId, TimelineRoute},
|
||||
ui::profile::preview::get_note_users_displayname_string,
|
||||
};
|
||||
|
||||
/// App routing. These describe different places you can go inside Notedeck.
|
||||
@@ -16,6 +19,18 @@ pub enum Route {
|
||||
AddColumn,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct TitledRoute {
|
||||
pub route: Route,
|
||||
pub title: String,
|
||||
}
|
||||
|
||||
impl fmt::Display for TitledRoute {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}", self.title)
|
||||
}
|
||||
}
|
||||
|
||||
impl Route {
|
||||
pub fn timeline(timeline_id: TimelineId) -> Self {
|
||||
Route::Timeline(TimelineRoute::Timeline(timeline_id))
|
||||
@@ -52,6 +67,42 @@ impl Route {
|
||||
pub fn add_account() -> Self {
|
||||
Route::Accounts(AccountsRoute::AddAccount)
|
||||
}
|
||||
|
||||
pub fn get_titled_route(&self, columns: &Columns, ndb: &Ndb) -> TitledRoute {
|
||||
let title = match self {
|
||||
Route::Timeline(tlr) => match tlr {
|
||||
TimelineRoute::Timeline(id) => {
|
||||
let timeline = columns
|
||||
.find_timeline(*id)
|
||||
.expect("expected to find timeline");
|
||||
timeline.kind.to_title(ndb)
|
||||
}
|
||||
TimelineRoute::Thread(id) => {
|
||||
format!("{}'s Thread", get_note_users_displayname_string(ndb, id))
|
||||
}
|
||||
TimelineRoute::Reply(id) => {
|
||||
format!("{}'s Reply", get_note_users_displayname_string(ndb, id))
|
||||
}
|
||||
TimelineRoute::Quote(id) => {
|
||||
format!("{}'s Quote", get_note_users_displayname_string(ndb, id))
|
||||
}
|
||||
},
|
||||
|
||||
Route::Relays => "Relays".to_owned(),
|
||||
|
||||
Route::Accounts(amr) => match amr {
|
||||
AccountsRoute::Accounts => "Accounts".to_owned(),
|
||||
AccountsRoute::AddAccount => "Add Account".to_owned(),
|
||||
},
|
||||
Route::ComposeNote => "Compose Note".to_owned(),
|
||||
Route::AddColumn => "Add Column".to_owned(),
|
||||
};
|
||||
|
||||
TitledRoute {
|
||||
title,
|
||||
route: *self,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: add this to egui-nav so we don't have to deal with returning
|
||||
|
||||
@@ -2,6 +2,7 @@ use crate::error::{Error, FilterError};
|
||||
use crate::filter;
|
||||
use crate::filter::FilterState;
|
||||
use crate::timeline::Timeline;
|
||||
use crate::ui::profile::preview::get_profile_displayname_string;
|
||||
use enostr::{Filter, Pubkey};
|
||||
use nostrdb::{Ndb, Transaction};
|
||||
use std::fmt::Display;
|
||||
@@ -150,4 +151,33 @@ impl TimelineKind {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_title(&self, ndb: &Ndb) -> String {
|
||||
match self {
|
||||
TimelineKind::List(list_kind) => match list_kind {
|
||||
ListKind::Contact(pubkey_source) => match pubkey_source {
|
||||
PubkeySource::Explicit(pubkey) => format!(
|
||||
"{}'s Home Timeline",
|
||||
get_profile_displayname_string(ndb, pubkey)
|
||||
),
|
||||
PubkeySource::DeckAuthor => "Your Home Timeline".to_owned(),
|
||||
},
|
||||
},
|
||||
TimelineKind::Notifications(pubkey_source) => match pubkey_source {
|
||||
PubkeySource::DeckAuthor => "Your Notifications".to_owned(),
|
||||
PubkeySource::Explicit(pk) => format!(
|
||||
"{}'s Notifications",
|
||||
get_profile_displayname_string(ndb, pk)
|
||||
),
|
||||
},
|
||||
TimelineKind::Profile(pubkey_source) => match pubkey_source {
|
||||
PubkeySource::DeckAuthor => "Your Profile".to_owned(),
|
||||
PubkeySource::Explicit(pk) => {
|
||||
format!("{}'s Profile", get_profile_displayname_string(ndb, pk))
|
||||
}
|
||||
},
|
||||
TimelineKind::Universe => "Universe".to_owned(),
|
||||
TimelineKind::Generic => "Custom Filter".to_owned(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -241,13 +241,15 @@ impl Timeline {
|
||||
|
||||
pub fn poll_notes_into_view(
|
||||
timeline_idx: usize,
|
||||
timelines: &mut [Timeline],
|
||||
mut timelines: Vec<&mut Timeline>,
|
||||
ndb: &Ndb,
|
||||
txn: &Transaction,
|
||||
unknown_ids: &mut UnknownIds,
|
||||
note_cache: &mut NoteCache,
|
||||
) -> Result<()> {
|
||||
let timeline = &mut timelines[timeline_idx];
|
||||
let timeline = timelines
|
||||
.get_mut(timeline_idx)
|
||||
.ok_or(Error::TimelineNotFound)?;
|
||||
let sub = timeline.subscription.ok_or(Error::no_active_sub())?;
|
||||
|
||||
let new_note_ids = ndb.poll_for_notes(sub, 500);
|
||||
|
||||
@@ -58,7 +58,8 @@ pub fn render_timeline_route(
|
||||
.ui(ui)
|
||||
{
|
||||
let txn = Transaction::new(ndb).expect("txn");
|
||||
let router = columns.columns_mut()[col].router_mut();
|
||||
let mut cur_column = columns.columns_mut();
|
||||
let router = cur_column[col].router_mut();
|
||||
|
||||
bar_action.execute_and_process_result(ndb, router, threads, note_cache, pool, &txn);
|
||||
}
|
||||
@@ -73,7 +74,8 @@ pub fn render_timeline_route(
|
||||
.ui(ui)
|
||||
{
|
||||
let txn = Transaction::new(ndb).expect("txn");
|
||||
let router = columns.columns_mut()[col].router_mut();
|
||||
let mut cur_column = columns.columns_mut();
|
||||
let router = cur_column[col].router_mut();
|
||||
bar_action.execute_and_process_result(ndb, router, threads, note_cache, pool, &txn);
|
||||
}
|
||||
|
||||
|
||||
@@ -60,6 +60,27 @@ impl AnimationHelper {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_from_rect(
|
||||
ui: &mut egui::Ui,
|
||||
animation_name: impl std::hash::Hash,
|
||||
animation_rect: egui::Rect,
|
||||
) -> Self {
|
||||
let id = ui.id().with(animation_name);
|
||||
let response = ui.allocate_rect(animation_rect, Sense::click());
|
||||
|
||||
let animation_progress =
|
||||
ui.ctx()
|
||||
.animate_bool_with_time(id, response.hovered(), ANIM_SPEED);
|
||||
|
||||
Self {
|
||||
rect: animation_rect,
|
||||
center: animation_rect.center(),
|
||||
response,
|
||||
animation_progress,
|
||||
expansion_multiple: ICON_EXPANSION_MULTIPLE,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn scale_1d_pos(&self, min_object_size: f32) -> f32 {
|
||||
let max_object_size = min_object_size * self.expansion_multiple;
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ use crate::{colors, images, DisplayName};
|
||||
use egui::load::TexturePoll;
|
||||
use egui::{Frame, RichText, Sense, Widget};
|
||||
use egui_extras::Size;
|
||||
use enostr::NoteId;
|
||||
use nostrdb::ProfileRecord;
|
||||
|
||||
pub struct ProfilePreview<'a, 'cache> {
|
||||
@@ -256,3 +257,28 @@ fn about_section_widget<'a>(profile: &'a ProfileRecord<'a>) -> impl egui::Widget
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn get_display_name_as_string(profile: Option<&'_ ProfileRecord<'_>>) -> String {
|
||||
let display_name = get_display_name(profile);
|
||||
match display_name {
|
||||
DisplayName::One(n) => n.to_string(),
|
||||
DisplayName::Both { display_name, .. } => display_name.to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_profile_displayname_string(ndb: &nostrdb::Ndb, pk: &enostr::Pubkey) -> String {
|
||||
let txn = nostrdb::Transaction::new(ndb).expect("Transaction should have worked");
|
||||
let profile = ndb.get_profile_by_pubkey(&txn, pk.bytes()).ok();
|
||||
get_display_name_as_string(profile.as_ref())
|
||||
}
|
||||
|
||||
pub fn get_note_users_displayname_string(ndb: &nostrdb::Ndb, id: &NoteId) -> String {
|
||||
let txn = nostrdb::Transaction::new(ndb).expect("Transaction should have worked");
|
||||
let note = ndb.get_note_by_id(&txn, id.bytes());
|
||||
let profile = if let Ok(note) = note {
|
||||
ndb.get_profile_by_pubkey(&txn, note.pubkey()).ok()
|
||||
} else {
|
||||
None
|
||||
};
|
||||
get_display_name_as_string(profile.as_ref())
|
||||
}
|
||||
|
||||
@@ -370,9 +370,7 @@ mod preview {
|
||||
impl DesktopSidePanelPreview {
|
||||
fn new() -> Self {
|
||||
let mut app = test_data::test_app();
|
||||
app.columns
|
||||
.columns_mut()
|
||||
.push(Column::new(vec![Route::accounts()]));
|
||||
app.columns.add_column(Column::new(vec![Route::accounts()]));
|
||||
DesktopSidePanelPreview { app }
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user