diff --git a/src/components.rs b/src/components.rs new file mode 100644 index 0000000..ff2548a --- /dev/null +++ b/src/components.rs @@ -0,0 +1,12 @@ +use adw::gdk::pango; +use gtk::ListBoxRow; + +pub(crate) fn new_device(name: String) -> ListBoxRow { + let device_label = gtk::Label::builder() + .ellipsize(pango::EllipsizeMode::End) + .xalign(0.0) + .label(&name) + .build(); + + ListBoxRow::builder().child(&device_label).build() +} \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index f93a26c..71bc0e5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -22,6 +22,7 @@ mod application; mod config; mod window; +mod components; use self::application::AudioDeviceManagerApplication; use self::window::AudioDeviceManagerWindow; diff --git a/src/window.blp b/src/window.blp index 574aa25..45740ca 100644 --- a/src/window.blp +++ b/src/window.blp @@ -6,15 +6,24 @@ template $AudioDeviceManagerWindow: Adw.ApplicationWindow { default-width: 800; default-height: 600; - content: Adw.NavigationSplitView { + Adw.Breakpoint { + condition ("max-width: 500sp") + setters { + split_view.collapsed: true; + } + } + + content: Adw.NavigationSplitView split_view { + min-sidebar-width: 200; + sidebar: Adw.NavigationPage { - title: _("Sidebar"); - tag: "sidebar"; + title: _("Devices"); + tag: "devices"; child: Adw.ToolbarView { [top] Adw.HeaderBar { - show-title: false; + show-title: true; [start] Gtk.ToggleButton { icon-name: "list-add-symbolic"; @@ -33,14 +42,14 @@ template $AudioDeviceManagerWindow: Adw.ApplicationWindow { }; }; - content: Adw.NavigationPage { - title: _("Content"); - tag: "content"; + content: Adw.NavigationPage device_navigation_page { + title: _(""); + tag: "device navigation page"; child: Adw.ToolbarView { [top] Adw.HeaderBar { - show-title: false; + show-title: true; [end] MenuButton { primary: true; @@ -50,13 +59,15 @@ template $AudioDeviceManagerWindow: Adw.ApplicationWindow { } } - content: Adw.StatusPage { - title: _("Content"); + content: Gtk.ScrolledWindow { + child: Adw.Clamp device_page_clamp { + maximum-size: 640; - LinkButton { - label: _("API Reference"); - uri: "https://ada-baumann.de"; - } + Box { + orientation: vertical; + spacing: 24; + } + }; }; }; }; diff --git a/src/window.rs b/src/window.rs index 4209f35..c6ebbd4 100644 --- a/src/window.rs +++ b/src/window.rs @@ -18,10 +18,13 @@ * * SPDX-License-Identifier: GPL-2.0-or-later */ - +use adw::glib::{clone, closure_local}; +use adw::prelude::{AlertDialogExt, AlertDialogExtManual, NavigationPageExt}; +use adw::ResponseAppearance; use gtk::prelude::*; use adw::subclass::prelude::*; -use gtk::{gio, glib}; +use gtk::{gio, glib, Button, ListBoxRow}; +use crate::components::new_device; mod imp { use super::*; @@ -32,6 +35,10 @@ mod imp { // Template widgets #[template_child] pub devices_list: TemplateChild, + #[template_child] + pub device_page_clamp: TemplateChild, + #[template_child] + pub device_navigation_page: TemplateChild, } #[glib::object_subclass] @@ -48,7 +55,7 @@ mod imp { "win.new-device", None, |window, _, _| async move { - println!("New Device"); + window.new_device().await; } ); } @@ -67,13 +74,115 @@ mod imp { glib::wrapper! { pub struct AudioDeviceManagerWindow(ObjectSubclass) - @extends gtk::Widget, gtk::Window, gtk::ApplicationWindow, adw::ApplicationWindow, @implements gio::ActionGroup, gio::ActionMap; + @extends gtk::Widget, gtk::Window, gtk::ApplicationWindow, adw::ApplicationWindow, + @implements gio::ActionGroup, gio::ActionMap; } impl AudioDeviceManagerWindow { pub fn new>(application: &P) -> Self { - glib::Object::builder() + let instance: AudioDeviceManagerWindow = glib::Object::builder() .property("application", application) - .build() + .build(); + + instance.bind_signals(); + + instance + } + + fn bind_signals(&self) { + self.imp().devices_list.connect_row_activated(clone!( + #[weak(rename_to = window)] + self, + move |_, row| { + println!("Row selected {}, {:?}", row.index(), row); + let label: gtk::Label = row + .child() + .and_downcast() + .expect("No Label in Row"); + window.select_device(label.text().to_string()); + } + )); + } + + async fn new_device(&self) { + let entry = gtk::Entry::builder() + .placeholder_text("Name") + .activates_default(true) + .build(); + + let cancel_response = "cancel"; + let create_response = "create"; + + // Create new dialog + let dialog = adw::AlertDialog::builder() + .heading("New Device") + .close_response(cancel_response) + .default_response(create_response) + .extra_child(&entry) + .build(); + + dialog.add_responses(&[(cancel_response, "Cancel"), (create_response, "Create")]); + + // Make the dialog button insensitive initially + dialog.set_response_enabled(create_response, false); + dialog.set_response_appearance(create_response, ResponseAppearance::Suggested); + + // Set entry's css class to "error", when there is no text in it + 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"); + } + } + )); + + let response = dialog.choose_future(self).await; + + // Return if the user chose 'cancel_response' + + if response == cancel_response { + println!("Cancel"); + return; + } + + let device = new_device(entry.text().to_string()); + + self.imp().devices_list.append(&device); + } + + fn select_device(&self, name: String) { + self.imp().device_navigation_page.set_title(&name); + + let device_page = self.build_device_page(name); + + self.imp().device_page_clamp.set_child(Some(&device_page)); + } + + fn build_device_page(&self, name: String) -> gtk::Box { + let device_page = gtk::Box::builder() + .orientation(gtk::Orientation::Vertical) + .margin_start(12) + .margin_end(12) + .spacing(12) + .build(); + + let entry = gtk::Entry::builder() + .placeholder_text("Test") + .secondary_icon_name("list-add-symbolic") + .build(); + + device_page.append(&entry); + + device_page + } }