This commit is contained in:
Tsuki 2024-02-27 17:05:31 +08:00
parent 89016994b1
commit 42d333a0ec
5 changed files with 213 additions and 20 deletions

124
src/components/alert.rs Normal file
View File

@ -0,0 +1,124 @@
use relm4::{ComponentParts, ComponentSender, SimpleComponent};
use gtk::prelude::*;
/// Configuration for the alert dialog component
pub struct AlertSettings {
/// Large text
pub text: String,
/// Optional secondary, smaller text
pub secondary_text: Option<String>,
/// Modal dialogs freeze other windows as long they are visible
pub is_modal: bool,
/// Sets color of the accept button to red if the theme supports it
pub destructive_accept: bool,
/// Text for confirm button
pub confirm_label: String,
/// Text for cancel button
pub cancel_label: String,
/// Text for third option button. If [`None`] the third button won't be created.
pub option_label: Option<String>,
}
/// Alert dialog component.
pub struct Alert {
settings: AlertSettings,
is_active: bool,
}
/// Messages that can be sent to the alert dialog component
#[derive(Debug)]
pub enum AlertMsg {
/// Message sent by the parent to view the dialog
Show,
#[doc(hidden)]
Response(gtk::ResponseType),
}
/// User action performed on the alert dialog.
#[derive(Debug)]
pub enum AlertResponse {
/// User clicked confirm button.
Confirm,
/// User clicked cancel button.
Cancel,
/// User clicked user-supplied option.
Option,
}
/// Widgets of the alert dialog component.
#[relm4::component(pub)]
impl SimpleComponent for Alert {
type Widgets = AlertWidgets;
type Init = AlertSettings;
type Input = AlertMsg;
type Output = AlertResponse;
view! {
#[name = "dialog"]
gtk::MessageDialog {
set_message_type: gtk::MessageType::Question,
#[watch]
set_visible: model.is_active,
connect_response[sender] => move |_, response| {
sender.input(AlertMsg::Response(response));
},
// Apply configuration
set_text: Some(&model.settings.text),
set_secondary_text: model.settings.secondary_text.as_deref(),
set_modal: model.settings.is_modal,
add_button: (&model.settings.confirm_label, gtk::ResponseType::Accept),
add_button: (&model.settings.cancel_label, gtk::ResponseType::Cancel),
}
}
fn init(
settings: AlertSettings,
root: &Self::Root,
sender: ComponentSender<Self>,
) -> ComponentParts<Self> {
let model = Alert {
settings,
is_active: false,
};
let widgets = view_output!();
if let Some(option_label) = &model.settings.option_label {
widgets
.dialog
.add_button(option_label, gtk::ResponseType::Other(0));
}
if model.settings.destructive_accept {
let accept_widget = widgets
.dialog
.widget_for_response(gtk::ResponseType::Accept)
.expect("No button for accept response set");
accept_widget.add_css_class("destructive-action");
}
ComponentParts { model, widgets }
}
fn update(&mut self, input: AlertMsg, sender: ComponentSender<Self>) {
match input {
AlertMsg::Show => {
self.is_active = true;
}
AlertMsg::Response(ty) => {
self.is_active = false;
sender
.output(match ty {
gtk::ResponseType::Accept => AlertResponse::Confirm,
gtk::ResponseType::Other(_) => AlertResponse::Option,
_ => AlertResponse::Cancel,
})
.unwrap();
}
}
}
}

View File

@ -1,5 +1,8 @@
use crate::utils::ini_to_table;
use adw::prelude::*; use adw::prelude::*;
use gtk::prelude::*; use gtk::prelude::*;
use gtk::ListItem;
use ini::Ini;
use relm4::{component, component::Component, ComponentParts, ComponentSender}; use relm4::{component, component::Component, ComponentParts, ComponentSender};
use relm4::{ use relm4::{
factory::FactoryView, factory::FactoryView,
@ -8,17 +11,16 @@ use relm4::{
view, FactorySender, RelmObjectExt, view, FactorySender, RelmObjectExt,
}; };
use std::collections::HashMap; use std::collections::HashMap;
use ini::Ini; use relm4::binding::{BoolBinding, U8Binding};
use crate::utils::ini_to_table;
#[derive(Debug)] #[derive(Debug)]
pub struct AlgPage { pub struct AlgPage {
alg_list: TypedColumnView<AlgListItem, gtk::NoSelection>, alg_list: TypedColumnView<AlgListItem, gtk::SingleSelection>,
} }
#[derive(Debug)] #[derive(Debug)]
pub enum AlgPageMsg { pub enum AlgPageMsg {
New(Ini) New(Ini),
} }
#[component(pub)] #[component(pub)]
@ -55,6 +57,7 @@ impl Component for AlgPage {
alg_list.append_column::<VersionColumn>(); alg_list.append_column::<VersionColumn>();
alg_list.append_column::<DescriptionColumn>(); alg_list.append_column::<DescriptionColumn>();
alg_list.append_column::<TagColumn>(); alg_list.append_column::<TagColumn>();
alg_list.append_column::<SelectColumn>();
let model = AlgPage { alg_list }; let model = AlgPage { alg_list };
let list_view = &model.alg_list.view; let list_view = &model.alg_list.view;
@ -67,7 +70,7 @@ impl Component for AlgPage {
AlgPageMsg::New(ini) => { AlgPageMsg::New(ini) => {
let lists = ini_to_table(&ini); let lists = ini_to_table(&ini);
for list in lists { for list in lists {
let item = AlgListItem::new(list.0,list.1,"".to_string(),"".to_string()); let item = AlgListItem::new(list.0, list.1, "".to_string(), list.2);
self.alg_list.append(item); self.alg_list.append(item);
} }
} }
@ -81,6 +84,7 @@ pub(super) struct AlgListItem {
description: String, description: String,
version: String, version: String,
tag: String, tag: String,
selected: BoolBinding
} }
impl AlgListItem { impl AlgListItem {
@ -90,6 +94,7 @@ impl AlgListItem {
version, version,
description, description,
tag, tag,
selected: BoolBinding::new(true)
} }
} }
} }
@ -98,6 +103,36 @@ pub(super) struct NameColumn;
pub(super) struct VersionColumn; pub(super) struct VersionColumn;
pub(super) struct DescriptionColumn; pub(super) struct DescriptionColumn;
pub(super) struct TagColumn; pub(super) struct TagColumn;
pub(super) struct SelectColumn;
pub struct SelectWidgetColumn {
select: gtk::CheckButton,
}
impl RelmColumn for SelectColumn {
type Item = AlgListItem;
type Root = gtk::Box;
type Widgets = SelectWidgetColumn;
const COLUMN_NAME: &'static str = "Select";
fn bind(_item: &mut Self::Item, _widgets: &mut Self::Widgets, _root: &mut Self::Root) {
_widgets.select.add_write_only_binding(&_item.selected.clone(), "active");
}
fn setup(list_item: &ListItem) -> (Self::Root, Self::Widgets) {
view! {
root = gtk::Box{
#[name="select"]
gtk::CheckButton{
}
}
}
let widgets = SelectWidgetColumn { select };
return (root, widgets);
}
}
impl LabelColumn for TagColumn { impl LabelColumn for TagColumn {
type Item = AlgListItem; type Item = AlgListItem;

View File

@ -1,8 +1,14 @@
use crate::components::alert::{Alert, AlertMsg, AlertResponse, AlertSettings};
use crate::components::alg_page::AlgPage;
use crate::components::new_project::NewPageModel; use crate::components::new_project::NewPageModel;
use adw::prelude::*; use adw::prelude::*;
use gtk::prelude::*; use gtk::prelude::*;
use gtk::Widget; use gtk::Widget;
use relm4::{actions::RelmActionGroup, component::{AsyncComponentController, AsyncController}, Component, ComponentParts, ComponentSender}; use relm4::{
actions::RelmActionGroup,
component::{AsyncComponentController, AsyncController},
Component, ComponentController, ComponentParts, ComponentSender, Controller, SimpleComponent,
};
use std::{ use std::{
any::Any, any::Any,
borrow::{Borrow, BorrowMut}, borrow::{Borrow, BorrowMut},
@ -12,7 +18,6 @@ use std::{
rc::Rc, rc::Rc,
sync::{Arc, Mutex}, sync::{Arc, Mutex},
}; };
use crate::components::alg_page::AlgPage;
relm4::new_action_group!(FileActionGroup, "file"); relm4::new_action_group!(FileActionGroup, "file");
relm4::new_stateless_action!(OpenAction, FileActionGroup, "open"); relm4::new_stateless_action!(OpenAction, FileActionGroup, "open");
@ -21,9 +26,14 @@ pub type ElementKey = String;
#[derive(Debug)] #[derive(Debug)]
pub enum AppMsg { pub enum AppMsg {
NewProject, NewProject,
CloseRequest,
Close,
Ignore,
Save,
} }
pub struct AppModel { pub struct AppModel {
new_page_model: AsyncController<NewPageModel>, new_page_model: AsyncController<NewPageModel>,
dialog: Controller<Alert>,
tracker: usize, tracker: usize,
} }
@ -42,12 +52,12 @@ impl Component for AppModel {
view! { view! {
#[root] #[root]
main_window=adw::ApplicationWindow { main_window=adw::ApplicationWindow {
set_default_width: 900, set_default_width: 1200,
set_default_height: 600, set_default_height: 600,
set_width_request:1200,
set_focus_on_click:true, set_focus_on_click:true,
connect_close_request[sender] => move |_| { connect_close_request[sender] => move |_| {
let app = relm4::main_application(); sender.input(AppMsg::CloseRequest);
app.quit();
gtk::Inhibit(true) gtk::Inhibit(true)
}, },
gtk::Box{ gtk::Box{
@ -144,8 +154,23 @@ impl Component for AppModel {
.launch(()) .launch(())
.forward(sender.input_sender(), |a| AppMsg::NewProject); .forward(sender.input_sender(), |a| AppMsg::NewProject);
let model = AppModel { let model = AppModel {
dialog: Alert::builder()
.transient_for(root)
.launch(AlertSettings {
text: String::from("Do you want to quit without saving?"),
secondary_text: Some(String::from("Your counter hasn't reached 42 yet")),
confirm_label: String::from("Close without saving"),
cancel_label: String::from("Cancel"),
option_label: Some(String::from("Save")),
is_modal: true,
destructive_accept: true,
})
.forward(sender.input_sender(), |msg| match msg {
AlertResponse::Cancel => AppMsg::Ignore,
AlertResponse::Option => AppMsg::Save,
AlertResponse::Confirm => AppMsg::Close,
}),
new_page_model, new_page_model,
tracker: 0, tracker: 0,
}; };
@ -164,9 +189,16 @@ impl Component for AppModel {
) { ) {
match msg { match msg {
AppMsg::NewProject => { AppMsg::NewProject => {
// widgets.stack.set_visible_child_name("new_page");
widgets.stack.push_by_tag("new_page"); widgets.stack.push_by_tag("new_page");
} }
AppMsg::CloseRequest => {
self.dialog.emit(AlertMsg::Show);
}
AppMsg::Close => {
relm4::main_application().quit();
}
_ => {}
} }
} }

View File

@ -3,6 +3,8 @@ mod app;
mod history_list; mod history_list;
mod new_project; mod new_project;
mod setting_item; mod setting_item;
mod alert;
pub use app::*; pub use app::*;
pub use new_project::*; pub use new_project::*;
pub use setting_item::*; pub use setting_item::*;

View File

@ -1,8 +1,8 @@
use crate::error::SourceError; use crate::error::SourceError;
use crate::CONFIG; use crate::CONFIG;
use git2::{Cred, FetchOptions, RemoteCallbacks, Repository}; use git2::{Cred, FetchOptions, RemoteCallbacks, Repository};
use std::path::PathBuf;
use ini::Ini; use ini::Ini;
use std::path::PathBuf;
use tokio::task; use tokio::task;
pub async fn get_alg_lists() -> Result<Vec<PathBuf>, SourceError> { pub async fn get_alg_lists() -> Result<Vec<PathBuf>, SourceError> {
@ -58,20 +58,20 @@ pub async fn get_alg_lists() -> Result<Vec<PathBuf>, SourceError> {
Ok(list) Ok(list)
} }
pub fn ini_to_table(ini: &Ini) -> Vec<(String,String)> { pub fn ini_to_table(ini: &Ini) -> Vec<(String, String, String)> {
let mut result = Vec::new(); let mut result = Vec::new();
let lib_sec = ini.section(Some("lib")); let lib_sec = ini.section(Some("lib"));
let alg_sec = ini.section(Some("algorithms")); let alg_sec = ini.section(Some("algorithms"));
if let Some(lib) = lib_sec{ if let Some(lib) = lib_sec {
for (key,value) in lib.iter(){ for (key, value) in lib.iter() {
result.push((key.to_string(), value.to_string())); result.push((key.to_string(), value.to_string(), "lib".to_string()));
} }
} }
if let Some(alg) = alg_sec{ if let Some(alg) = alg_sec {
for (key,value) in alg.iter(){ for (key, value) in alg.iter() {
result.push((key.to_string(), value.to_string())); result.push((key.to_string(), value.to_string(), "algorithms".to_string()));
} }
} }
result result