use relm4::factory::{DynamicIndex, FactoryVecDeque};
use relm4::gtk::prelude::*;
use relm4::{
gtk, Component, ComponentController, ComponentParts, ComponentSender, Controller,
SimpleComponent,
};
use crate::open_dialog::{OpenDialog, OpenDialogMsg, OpenDialogResponse, OpenDialogSettings};
use std::fs;
use std::path::PathBuf;
mod factory;
use factory::FileListItem;
#[tracker::track]
#[derive(Debug)]
pub struct OpenButton {
#[do_not_track]
config: OpenButtonSettings,
#[do_not_track]
dialog: Controller<OpenDialog>,
#[do_not_track]
recent_files: Option<FactoryVecDeque<FileListItem>>,
initialized: bool,
#[do_not_track]
reset_popover: bool,
}
#[derive(Debug)]
pub struct OpenButtonSettings {
pub dialog_settings: OpenDialogSettings,
pub text: &'static str,
pub recently_opened_files: Option<&'static str>,
pub max_recent_files: usize,
}
#[doc(hidden)]
#[derive(Debug)]
pub enum OpenButtonMsg {
Open(PathBuf),
OpenRecent(DynamicIndex),
ShowDialog,
Ignore,
}
#[relm4::component(pub)]
impl SimpleComponent for OpenButton {
type Init = OpenButtonSettings;
type Input = OpenButtonMsg;
type Output = PathBuf;
view! {
gtk::Box {
add_css_class: "linked",
gtk::Button {
set_label: model.config.text,
connect_clicked => OpenButtonMsg::ShowDialog,
},
gtk::MenuButton {
set_visible: model.config.recently_opened_files.is_some(),
#[wrap(Some)]
#[name(popover)]
set_popover = >k::Popover {
gtk::ScrolledWindow {
set_hscrollbar_policy: gtk::PolicyType::Never,
set_min_content_width: 100,
set_min_content_height: 100,
set_min_content_height: 300,
#[name(recent_files_list)]
gtk::Box {
set_orientation: gtk::Orientation::Vertical,
set_vexpand: true,
set_hexpand: true,
}
}
}
}
}
}
fn update(&mut self, msg: Self::Input, sender: ComponentSender<Self>) {
self.reset_popover = false;
match msg {
OpenButtonMsg::ShowDialog => {
self.dialog.emit(OpenDialogMsg::Open);
}
OpenButtonMsg::Open(path) => {
sender.output(path.clone()).unwrap();
self.reset_popover = true;
if let Some(recent_files) = &mut self.recent_files {
let index = recent_files.iter().position(|item| item.path == path);
if let Some(index) = index {
recent_files.guard().remove(index);
}
if recent_files.len() < self.config.max_recent_files {
recent_files.guard().push_front(path);
}
let contents = recent_files
.iter()
.filter_map(|recent_path| {
recent_path.path.to_str().map(|s| format!("{s}\n"))
})
.collect::<String>();
let _ = fs::write(self.config.recently_opened_files.unwrap(), contents);
}
}
OpenButtonMsg::OpenRecent(index) => {
if let Some(item) = self
.recent_files
.as_ref()
.and_then(|recent_files| recent_files.get(index.current_index()))
{
sender.input(OpenButtonMsg::Open(PathBuf::from(&item.path)));
}
}
OpenButtonMsg::Ignore => (),
}
}
fn pre_view() {
if self.reset_popover {
popover.popdown();
}
}
fn init(
settings: Self::Init,
root: Self::Root,
sender: ComponentSender<Self>,
) -> ComponentParts<Self> {
let dialog = OpenDialog::builder()
.transient_for_native(&root)
.launch(settings.dialog_settings.clone())
.forward(sender.input_sender(), |response| match response {
OpenDialogResponse::Accept(path) => OpenButtonMsg::Open(path),
OpenDialogResponse::Cancel => OpenButtonMsg::Ignore,
});
let mut model = Self {
config: settings,
dialog,
initialized: false,
recent_files: None,
reset_popover: false,
tracker: 0,
};
let widgets = view_output!();
if let Some(filename) = model.config.recently_opened_files {
let mut factory = FactoryVecDeque::builder()
.launch(widgets.recent_files_list.clone())
.forward(sender.input_sender(), |msg| msg);
if let Ok(entries) = fs::read_to_string(filename) {
let mut guard = factory.guard();
for entry in entries.lines() {
guard.push_back(PathBuf::from(entry));
}
}
model.recent_files = Some(factory);
}
ComponentParts { model, widgets }
}
}