sync
This commit is contained in:
parent
89016994b1
commit
42d333a0ec
124
src/components/alert.rs
Normal file
124
src/components/alert.rs
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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;
|
||||||
|
|||||||
@ -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();
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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::*;
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user