247 lines
8.6 KiB
Rust
247 lines
8.6 KiB
Rust
use std::{path::PathBuf, process::Command, sync::Arc};
|
|
|
|
use chrono::Utc;
|
|
use gtk::prelude::*;
|
|
use relm4::{
|
|
component::{AsyncComponent, AsyncComponentParts, AsyncController},
|
|
gtk::{gdk, Adjustment},
|
|
prelude::*,
|
|
AsyncComponentSender,
|
|
};
|
|
use relm4_icons::icon_names;
|
|
|
|
use crate::{
|
|
database::{self, Database},
|
|
sheet::{I64DateTime, Sheet},
|
|
sheet_dao, sheet_validation,
|
|
ui::mcdu::McduOutput,
|
|
};
|
|
|
|
use super::{
|
|
mcdu::McduModel,
|
|
sheet_edit_dialog::{SheetEditDialogInit, SheetEditDialogModel},
|
|
sheet_listing::{SheetListingInput, SheetListingModel, SheetListingOutput},
|
|
};
|
|
|
|
pub struct AppModel {
|
|
database: Arc<Database>,
|
|
directory: Arc<PathBuf>,
|
|
mcdu: Controller<McduModel>,
|
|
sheets_listing: Controller<SheetListingModel>,
|
|
click_mode: ClickMode,
|
|
scroll_adjustment: Adjustment,
|
|
sheet_edit_dialog: Option<AsyncController<SheetEditDialogModel>>,
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub enum ClickMode {
|
|
Open,
|
|
Edit,
|
|
Annotate,
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub enum AppInput {
|
|
SearchStarted(String),
|
|
SheetPressed(Sheet),
|
|
Refresh,
|
|
Sort,
|
|
Shuffle,
|
|
SetClickMode(ClickMode),
|
|
SheetListingContentsChanged,
|
|
}
|
|
|
|
pub struct AppInitData {
|
|
pub sheets: Vec<Sheet>,
|
|
pub database: Database,
|
|
pub directory: PathBuf,
|
|
}
|
|
|
|
#[relm4::component(pub, async)]
|
|
impl AsyncComponent for AppModel {
|
|
type Input = AppInput;
|
|
type Output = ();
|
|
type Init = AppInitData;
|
|
type CommandOutput = Vec<Sheet>;
|
|
|
|
view! {
|
|
#[root]
|
|
gtk::Window{
|
|
set_default_width: 300,
|
|
set_default_height: 300,
|
|
set_title: Some("Play music!!!"),
|
|
set_maximized: true,
|
|
gtk::Box {
|
|
set_orientation: gtk::Orientation::Horizontal,
|
|
gtk::Box {
|
|
set_orientation: gtk::Orientation::Vertical,
|
|
set_hexpand: true,
|
|
gtk::Box {
|
|
set_orientation: gtk::Orientation::Horizontal,
|
|
set_margin_all: 10,
|
|
set_spacing: 3,
|
|
gtk::Button {
|
|
set_icon_name: icon_names::REFRESH,
|
|
set_margin_end: 10,
|
|
connect_clicked[sender] => move |_| sender.input(AppInput::Refresh),
|
|
},
|
|
#[name = "button_sort"]
|
|
gtk::ToggleButton {
|
|
set_icon_name: icon_names::ARROW_SORT_REGULAR,
|
|
set_active: true,
|
|
connect_clicked[sender] => move |_| sender.input(AppInput::Sort),
|
|
},
|
|
gtk::ToggleButton {
|
|
set_icon_name: icon_names::PLAYLIST_SHUFFLE,
|
|
set_group: Some(&button_sort),
|
|
set_margin_end: 10,
|
|
connect_clicked[sender] => move |_| sender.input(AppInput::Shuffle),
|
|
},
|
|
#[name = "button_open"]
|
|
gtk::ToggleButton {
|
|
set_icon_name: icon_names::OPEN_FILLED,
|
|
set_active: true,
|
|
connect_clicked[sender] => move |button| if button.is_active() { sender.input(AppInput::SetClickMode(ClickMode::Open)) },
|
|
},
|
|
gtk::ToggleButton {
|
|
set_icon_name: icon_names::DOCUMENT_SETTINGS_FILLED,
|
|
set_group: Some(&button_open),
|
|
connect_clicked[sender] => move |button| if button.is_active() { sender.input(AppInput::SetClickMode(ClickMode::Edit)) },
|
|
},
|
|
gtk::ToggleButton {
|
|
set_icon_name: icon_names::EDIT,
|
|
set_group: Some(&button_open),
|
|
connect_clicked[sender] => move |button| if button.is_active() { sender.input(AppInput::SetClickMode(ClickMode::Annotate)) },
|
|
},
|
|
},
|
|
gtk::ScrolledWindow {
|
|
model.sheets_listing.widget(),
|
|
set_vexpand: true,
|
|
set_hexpand: true,
|
|
set_vadjustment: Some(&model.scroll_adjustment),
|
|
},
|
|
},
|
|
model.mcdu.widget() {
|
|
set_margin_all: 10,
|
|
},
|
|
}
|
|
}
|
|
}
|
|
|
|
async fn init(
|
|
init_data: Self::Init,
|
|
window: Self::Root,
|
|
sender: AsyncComponentSender<Self>,
|
|
) -> AsyncComponentParts<Self> {
|
|
relm4_icons::initialize_icons();
|
|
gtk::init().unwrap();
|
|
let display = gdk::Display::default().unwrap();
|
|
let theme = gtk::IconTheme::for_display(&display);
|
|
|
|
theme.add_resource_path("/org/gtkrs/icons/");
|
|
// theme.add_resource_path("/org/gtkrs/icons/scalable/actions/");
|
|
|
|
let mcdu = McduModel::builder()
|
|
.launch(())
|
|
.forward(sender.input_sender(), |response| match response {
|
|
McduOutput::SearchStarted(query) => AppInput::SearchStarted(query),
|
|
});
|
|
|
|
let mut sheets = init_data.sheets;
|
|
sheets.sort_by(|a, b| a.cmp(b).reverse());
|
|
|
|
let sheets_listing = SheetListingModel::builder().launch(sheets).forward(
|
|
sender.input_sender(),
|
|
|response| match response {
|
|
SheetListingOutput::SheetModelSelected(sheet) => AppInput::SheetPressed(sheet),
|
|
SheetListingOutput::ContentsChanged => AppInput::SheetListingContentsChanged,
|
|
},
|
|
);
|
|
|
|
let model = AppModel {
|
|
database: Arc::new(init_data.database),
|
|
directory: Arc::new(init_data.directory),
|
|
mcdu,
|
|
sheets_listing,
|
|
click_mode: ClickMode::Open,
|
|
scroll_adjustment: Adjustment::builder().build(),
|
|
sheet_edit_dialog: None,
|
|
};
|
|
|
|
let widgets = view_output!();
|
|
|
|
AsyncComponentParts { model, widgets }
|
|
}
|
|
|
|
async fn update(
|
|
&mut self,
|
|
message: Self::Input,
|
|
sender: AsyncComponentSender<Self>,
|
|
root: &Self::Root,
|
|
) {
|
|
match message {
|
|
AppInput::SearchStarted(query) => {
|
|
self.sheets_listing
|
|
.emit(SheetListingInput::Query(query.clone()));
|
|
}
|
|
AppInput::SheetPressed(sheet) => {
|
|
match self.click_mode {
|
|
ClickMode::Open => open_sheet(&sheet, &self.database).await,
|
|
ClickMode::Edit => {
|
|
self.sheet_edit_dialog = Some(
|
|
SheetEditDialogModel::builder()
|
|
.transient_for(root)
|
|
.launch(SheetEditDialogInit {
|
|
sheet,
|
|
database: Arc::clone(&self.database),
|
|
})
|
|
.forward(sender.input_sender(), |_| todo!()),
|
|
);
|
|
}
|
|
ClickMode::Annotate => annotate_sheet(&sheet).await,
|
|
};
|
|
}
|
|
AppInput::Refresh => {
|
|
let db = Arc::clone(&self.database);
|
|
let dir = Arc::clone(&self.directory);
|
|
sender.oneshot_command(async move {
|
|
sheet_validation::load_and_validate_sheets(&db, dir.as_ref()).await
|
|
});
|
|
}
|
|
AppInput::Sort => self.sheets_listing.emit(SheetListingInput::Sort),
|
|
AppInput::Shuffle => self.sheets_listing.emit(SheetListingInput::Shuffle),
|
|
AppInput::SetClickMode(click_mode) => self.click_mode = click_mode,
|
|
AppInput::SheetListingContentsChanged => self.scroll_adjustment.set_value(0.0),
|
|
}
|
|
}
|
|
|
|
async fn update_cmd(
|
|
&mut self,
|
|
message: Self::CommandOutput,
|
|
_sender: AsyncComponentSender<Self>,
|
|
_: &Self::Root,
|
|
) {
|
|
let mut sheets = message;
|
|
sheets.sort_by(|a, b| a.cmp(b).reverse());
|
|
|
|
self.sheets_listing
|
|
.emit(SheetListingInput::ReloadSheets(sheets));
|
|
}
|
|
}
|
|
|
|
async fn open_sheet(sheet: &Sheet, database: &Database) {
|
|
sheet.open_file_or_annotated_version_if_exists();
|
|
let mut sheet = sheet.to_owned();
|
|
sheet.last_opened = I64DateTime(Utc::now());
|
|
sheet_dao::update_sheet_last_opened(database, &sheet)
|
|
.await
|
|
.unwrap();
|
|
}
|
|
|
|
async fn annotate_sheet(sheet: &Sheet) {
|
|
Command::new("xournalpp")
|
|
.arg(&sheet.pdf.path)
|
|
.spawn()
|
|
.expect("failed to execute process");
|
|
}
|