Add app UI.
This commit is contained in:
parent
efa5858859
commit
2ef3557932
|
@ -37,7 +37,7 @@
|
||||||
<property name="label" translatable="yes">_New Application</property>
|
<property name="label" translatable="yes">_New Application</property>
|
||||||
<property name="use-underline">True</property>
|
<property name="use-underline">True</property>
|
||||||
<property name="halign">center</property>
|
<property name="halign">center</property>
|
||||||
<property name="action-name">win.new-app</property>
|
<property name="action-name">win.new-application</property>
|
||||||
<style>
|
<style>
|
||||||
<class name="pill" />
|
<class name="pill" />
|
||||||
<class name="suggested-action" />
|
<class name="suggested-action" />
|
||||||
|
@ -71,7 +71,7 @@
|
||||||
<object class="GtkToggleButton">
|
<object class="GtkToggleButton">
|
||||||
<property name="icon-name">list-add-symbolic</property>
|
<property name="icon-name">list-add-symbolic</property>
|
||||||
<property name="tooltip-text" translatable="yes">New Application</property>
|
<property name="tooltip-text" translatable="yes">New Application</property>
|
||||||
<property name="action-name">win.new-app</property>
|
<property name="action-name">win.new-application</property>
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
</object>
|
</object>
|
||||||
|
|
|
@ -2,18 +2,23 @@ use crate::ui::objects::app::AppObject;
|
||||||
|
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
use std::option::Option;
|
use std::option::Option;
|
||||||
|
use adw::Leaflet;
|
||||||
|
|
||||||
use adw::prelude::*;
|
use adw::prelude::*;
|
||||||
use adw::subclass::prelude::*;
|
use adw::subclass::prelude::*;
|
||||||
use glib::once_cell::sync::OnceCell;
|
use glib::once_cell::sync::OnceCell;
|
||||||
use glib::subclass::InitializingObject;
|
use glib::subclass::InitializingObject;
|
||||||
use gtk::{gio, glib, CompositeTemplate, ListBox, TemplateChild};
|
use gtk::{gio, glib, CompositeTemplate, ListBox, TemplateChild, Stack};
|
||||||
|
|
||||||
#[derive(CompositeTemplate, Default)]
|
#[derive(CompositeTemplate, Default)]
|
||||||
#[template(resource = "/dev/fyloz/example/window.ui")]
|
#[template(resource = "/dev/fyloz/example/window.ui")]
|
||||||
pub struct Window {
|
pub struct Window {
|
||||||
#[template_child]
|
#[template_child]
|
||||||
pub apps_list: TemplateChild<ListBox>,
|
pub apps_list: TemplateChild<ListBox>,
|
||||||
|
#[template_child]
|
||||||
|
pub leaflet: TemplateChild<Leaflet>,
|
||||||
|
#[template_child]
|
||||||
|
pub stack: TemplateChild<Stack>,
|
||||||
pub apps: OnceCell<gio::ListStore>,
|
pub apps: OnceCell<gio::ListStore>,
|
||||||
pub current_app: RefCell<Option<AppObject>>,
|
pub current_app: RefCell<Option<AppObject>>,
|
||||||
}
|
}
|
||||||
|
@ -39,6 +44,8 @@ impl ObjectImpl for Window {
|
||||||
|
|
||||||
let obj = self.obj();
|
let obj = self.obj();
|
||||||
obj.setup_apps();
|
obj.setup_apps();
|
||||||
|
obj.setup_actions();
|
||||||
|
obj.setup_callbacks();
|
||||||
obj.restore_data();
|
obj.restore_data();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
mod imp;
|
mod imp;
|
||||||
|
|
||||||
|
use adw::{MessageDialog, NavigationDirection, ResponseAppearance};
|
||||||
use crate::ui::objects::app::{AppData, AppObject};
|
use crate::ui::objects::app::{AppData, AppObject};
|
||||||
|
|
||||||
use adw::prelude::*;
|
use adw::prelude::*;
|
||||||
use adw::subclass::prelude::*;
|
use adw::subclass::prelude::*;
|
||||||
use gtk::{gio, glib, ListBoxRow, Label, pango};
|
use gtk::{gio, glib, Entry, ListBoxRow, Label, pango, SelectionMode};
|
||||||
use glib::{clone, Object};
|
use glib::{clone, Object};
|
||||||
|
|
||||||
glib::wrapper! {
|
glib::wrapper! {
|
||||||
|
@ -19,14 +20,6 @@ impl Window {
|
||||||
Object::builder().property("application", app).build()
|
Object::builder().property("application", app).build()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn current_app(&self) -> AppObject {
|
|
||||||
self.imp()
|
|
||||||
.current_app
|
|
||||||
.borrow()
|
|
||||||
.clone()
|
|
||||||
.expect("`current_app` should be set in `set_current_app`.")
|
|
||||||
}
|
|
||||||
|
|
||||||
fn apps(&self) -> gio::ListStore {
|
fn apps(&self) -> gio::ListStore {
|
||||||
self.imp()
|
self.imp()
|
||||||
.apps
|
.apps
|
||||||
|
@ -49,34 +42,70 @@ impl Window {
|
||||||
ListBoxRow::builder().child(&label).build()
|
ListBoxRow::builder().child(&label).build()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_current_app(&self, app: AppObject) {
|
fn current_app(&self) -> AppObject {
|
||||||
self.imp().current_app.replace(Some(app));
|
|
||||||
}
|
|
||||||
|
|
||||||
fn select_collection_row(&self) {
|
|
||||||
if let Some(index) = self.apps().find(&self.current_app()) {
|
|
||||||
let row = self.imp().apps_list.row_at_index(index as i32);
|
|
||||||
self.imp().apps_list.select_row(row.as_ref());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn setup_apps(&self) {
|
|
||||||
let apps = gio::ListStore::new::<AppObject>();
|
|
||||||
self.imp()
|
self.imp()
|
||||||
.apps
|
.current_app
|
||||||
.set(apps.clone())
|
.borrow()
|
||||||
.expect("Could not set apps");
|
.clone()
|
||||||
|
.expect("`current_app` should be set in `set_current_app`.")
|
||||||
|
}
|
||||||
|
|
||||||
self.imp().apps_list.bind_model(
|
fn new_application(&self) {
|
||||||
Some(&apps),
|
let entry = Entry::builder()
|
||||||
clone!(@weak self as window => @default-panic, move |obj| {
|
.placeholder_text("Name")
|
||||||
let app_object = obj
|
.activates_default(true)
|
||||||
.downcast_ref()
|
.build();
|
||||||
.expect("The object should be of type `AppObject`.");
|
|
||||||
let row = window.create_app_row(app_object);
|
let cancel_response = "cancel";
|
||||||
row.upcast()
|
let create_response = "create";
|
||||||
|
|
||||||
|
let dialog = MessageDialog::builder()
|
||||||
|
.heading("New Application")
|
||||||
|
.transient_for(self)
|
||||||
|
.modal(true)
|
||||||
|
.destroy_with_parent(true)
|
||||||
|
.close_response(cancel_response)
|
||||||
|
.default_response(create_response)
|
||||||
|
.extra_child(&entry)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
dialog.add_responses(&[(cancel_response, "Cancel"), (create_response, "Create")]);
|
||||||
|
dialog.set_response_enabled(create_response, false);
|
||||||
|
dialog.set_response_appearance(create_response, ResponseAppearance::Suggested);
|
||||||
|
|
||||||
|
entry.connect_changed(clone!(@weak dialog => move |entry| {
|
||||||
|
let text = entry.text();
|
||||||
|
let empty = text.is_empty();
|
||||||
|
|
||||||
|
dialog.set_response_enabled(create_response, !empty);
|
||||||
|
|
||||||
|
if empty {
|
||||||
|
entry.add_css_class("error");
|
||||||
|
} else {
|
||||||
|
entry.remove_css_class("error");
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
dialog.connect_response(
|
||||||
|
None,
|
||||||
|
clone!(@weak self as window, @weak entry => move |dialog, response| {
|
||||||
|
dialog.destroy();
|
||||||
|
|
||||||
|
if response != create_response {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let name = entry.text().to_string();
|
||||||
|
let app = AppObject::new(&name, "/some/path");
|
||||||
|
|
||||||
|
window.apps().append(&app);
|
||||||
|
window.set_current_app(app);
|
||||||
|
|
||||||
|
window.imp().leaflet.navigate(NavigationDirection::Forward);
|
||||||
}),
|
}),
|
||||||
)
|
);
|
||||||
|
|
||||||
|
dialog.present();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn restore_data(&self) {
|
fn restore_data(&self) {
|
||||||
|
@ -100,4 +129,90 @@ impl Window {
|
||||||
self.set_current_app(app.clone());
|
self.set_current_app(app.clone());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn select_app_row(&self) {
|
||||||
|
if let Some(index) = self.apps().find(&self.current_app()) {
|
||||||
|
let row = self.imp().apps_list.row_at_index(index as i32);
|
||||||
|
self.imp().apps_list.select_row(row.as_ref());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_current_app(&self, app: AppObject) {
|
||||||
|
self.imp().current_app.replace(Some(app));
|
||||||
|
self.select_app_row();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_stack(&self) {
|
||||||
|
if self.apps().n_items() > 0 {
|
||||||
|
self.imp().stack.set_visible_child_name("main");
|
||||||
|
} else {
|
||||||
|
self.imp().stack.set_visible_child_name("empty");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn setup_actions(&self) {
|
||||||
|
let action_new_application = gio::SimpleAction::new("new-application", None);
|
||||||
|
action_new_application.connect_activate(clone!(@weak self as window => move |_, _| {
|
||||||
|
window.new_application();
|
||||||
|
}));
|
||||||
|
self.add_action(&action_new_application);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn setup_apps(&self) {
|
||||||
|
let apps = gio::ListStore::new::<AppObject>();
|
||||||
|
self.imp()
|
||||||
|
.apps
|
||||||
|
.set(apps.clone())
|
||||||
|
.expect("Could not set apps");
|
||||||
|
|
||||||
|
self.imp().apps_list.bind_model(
|
||||||
|
Some(&apps),
|
||||||
|
clone!(@weak self as window => @default-panic, move |obj| {
|
||||||
|
let app_object = obj
|
||||||
|
.downcast_ref()
|
||||||
|
.expect("The object should be of type `AppObject`.");
|
||||||
|
let row = window.create_app_row(app_object);
|
||||||
|
row.upcast()
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn setup_callbacks(&self) {
|
||||||
|
self.set_stack();
|
||||||
|
self.apps().connect_items_changed(
|
||||||
|
clone!(@weak self as window => move |_, _, _, _| {
|
||||||
|
window.set_stack();
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
self.imp().apps_list.connect_row_activated(
|
||||||
|
clone!(@weak self as window => move |_, row| {
|
||||||
|
let index = row.index();
|
||||||
|
let selected_app = window.apps()
|
||||||
|
.item(index as u32)
|
||||||
|
.expect("There needs to be an object at this position.")
|
||||||
|
.downcast::<AppObject>()
|
||||||
|
.expect("The object needs to be a 'AppObject'.");
|
||||||
|
window.set_current_app(selected_app);
|
||||||
|
window.imp().leaflet.navigate(NavigationDirection::Forward);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
self.imp().leaflet.connect_folded_notify(
|
||||||
|
clone!(@weak self as window => move |leaflet| {
|
||||||
|
if leaflet.is_folded() {
|
||||||
|
window
|
||||||
|
.imp()
|
||||||
|
.apps_list
|
||||||
|
.set_selection_mode(SelectionMode::None)
|
||||||
|
} else {
|
||||||
|
window
|
||||||
|
.imp()
|
||||||
|
.apps_list
|
||||||
|
.set_selection_mode(SelectionMode::Single);
|
||||||
|
window.select_app_row();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue