Add app UI.

This commit is contained in:
FyloZ 2023-08-21 23:47:53 -04:00
parent efa5858859
commit 2ef3557932
Signed by: william
GPG Key ID: 835378AE9AF4AE97
3 changed files with 159 additions and 37 deletions

View File

@ -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>

View File

@ -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();
} }
} }

View File

@ -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();
}
})
);
}
} }