diff --git a/data/com.github.marhkb.Pods.gschema.xml.in b/data/com.github.marhkb.Pods.gschema.xml.in index 68aa6705..69453405 100644 --- a/data/com.github.marhkb.Pods.gschema.xml.in +++ b/data/com.github.marhkb.Pods.gschema.xml.in @@ -26,6 +26,15 @@ The last used view + + + + + + 'grid' + Containers representation + + false Whether to hide intermediate images diff --git a/data/com.github.marhkb.Pods.metainfo.xml.in.in b/data/com.github.marhkb.Pods.metainfo.xml.in.in index 0a6f8203..2a52cc9b 100644 --- a/data/com.github.marhkb.Pods.metainfo.xml.in.in +++ b/data/com.github.marhkb.Pods.metainfo.xml.in.in @@ -46,6 +46,7 @@
  • The search entry to a toggle button in the headerbar. (#830)
  • Search is now possible in the connection chooser page. (#830)
  • The control elements of the header bar are now moved to a lower bar if space becomes too tight. (#837)
  • +
  • Allow switching between grid and list view in the containers panel. (#838)
  • diff --git a/src/main.rs b/src/main.rs index 4ef3f021..2691c462 100644 --- a/src/main.rs +++ b/src/main.rs @@ -211,7 +211,9 @@ fn init() { view::ContainerTerminalPage::static_type(); view::ContainerVolumeRow::static_type(); view::ContainersCountBar::static_type(); + view::ContainersGridView::static_type(); view::ContainersGroup::static_type(); + view::ContainersListView::static_type(); view::ContainersPanel::static_type(); view::ContainersPrunePage::static_type(); view::ContainersRow::static_type(); diff --git a/src/resources.gresource.xml b/src/resources.gresource.xml index c88c4535..44c3d2e9 100644 --- a/src/resources.gresource.xml +++ b/src/resources.gresource.xml @@ -30,7 +30,9 @@ view/container_terminal_page.ui view/container_volume_row.ui view/containers_count_bar.ui + view/containers_grid_view.ui view/containers_group.ui + view/containers_list_view.ui view/containers_panel.ui view/containers_prune_page.ui view/containers_row.ui diff --git a/src/view/container_row.rs b/src/view/container_row.rs index 36d65c8a..15901e86 100644 --- a/src/view/container_row.rs +++ b/src/view/container_row.rs @@ -1,3 +1,5 @@ +use std::cell::RefCell; + use adw::prelude::*; use adw::subclass::prelude::*; use gettextrs::gettext; @@ -8,6 +10,7 @@ use gtk::glib; use gtk::CompositeTemplate; use crate::model; +use crate::model::prelude::*; use crate::utils; use crate::view; use crate::widget; @@ -19,30 +22,42 @@ mod imp { #[properties(wrapper_type = super::ContainerRow)] #[template(resource = "/com/github/marhkb/Pods/ui/view/container_row.ui")] pub(crate) struct ContainerRow { + pub(super) bindings: RefCell>, #[property(get, set, construct, nullable)] pub(super) container: glib::WeakRef, #[template_child] pub(super) spinner: TemplateChild, - // #[template_child] - // pub(super) name_label: TemplateChild, - // #[template_child] - // pub(super) repo_label: TemplateChild, + #[template_child] + pub(super) check_button_revealer: TemplateChild, + #[template_child] + pub(super) check_button: TemplateChild, + #[template_child] + pub(super) name_label: TemplateChild, + #[template_child] + pub(super) pod_image: TemplateChild, + #[template_child] + pub(super) repo_label: TemplateChild, + #[template_child] + pub(super) ports_flow_box: TemplateChild, #[template_child] pub(super) stats_box: TemplateChild, #[template_child] pub(super) cpu_bar: TemplateChild, #[template_child] pub(super) mem_bar: TemplateChild, + #[template_child] + pub(super) end_box_revealer: TemplateChild, } #[glib::object_subclass] impl ObjectSubclass for ContainerRow { const NAME: &'static str = "PdsContainerRow"; type Type = super::ContainerRow; - type ParentType = adw::ActionRow; + type ParentType = gtk::ListBoxRow; fn class_init(klass: &mut Self::Class) { klass.bind_template(); + klass.bind_template_callbacks(); klass.install_action("container-row.activate", None, |widget, _, _| { widget.activate(); @@ -76,9 +91,20 @@ mod imp { let container_list_expr = container_expr.chain_property::("container-list"); + let selection_mode_expr = + container_list_expr.chain_property::("selection-mode"); + + selection_mode_expr.bind(&*self.check_button_revealer, "reveal-child", Some(obj)); + selection_mode_expr + .chain_closure::(closure!(|_: Self::Type, is_selection_mode: bool| { + !is_selection_mode + })) + .bind(&*self.end_box_revealer, "reveal-child", Some(obj)); + let status_expr = container_expr.chain_property::("status"); let health_status_expr = container_expr.chain_property::("health-status"); + let pod_expr = container_expr.chain_property::("pod"); let stats_expr = container_expr.chain_property::("stats"); container_expr @@ -137,14 +163,33 @@ mod imp { } }), ) - .bind(obj, "title", Some(obj)); + .bind(&*self.name_label, "label", Some(obj)); + + pod_expr + .chain_closure::(closure!( + |_: Self::Type, pod: Option| pod.is_some() + )) + .bind(&*self.pod_image, "visible", Some(obj)); + + gtk::ClosureExpression::new::( + [ + &pod_expr, + &container_expr + .chain_property::("ports") + .chain_property::("len"), + ], + closure!(|_: Self::Type, pod: Option<&model::Pod>, len: u32| { + pod.is_none() && len > 0 + }), + ) + .bind(&*self.ports_flow_box, "visible", Some(obj)); container_expr .chain_property::("image-name") .chain_closure::(closure!(|_: Self::Type, name: Option| { utils::escape(&utils::format_option(name)) })) - .bind(obj, "subtitle", Some(obj)); + .bind(&*self.repo_label, "label", Some(obj)); status_expr .chain_closure::(closure!( @@ -194,13 +239,78 @@ mod imp { impl WidgetImpl for ContainerRow {} impl ListBoxRowImpl for ContainerRow {} - impl PreferencesRowImpl for ContainerRow {} - impl ActionRowImpl for ContainerRow {} + + #[gtk::template_callbacks] + impl ContainerRow { + #[template_callback] + fn on_notify_container(&self) { + let mut bindings = self.bindings.borrow_mut(); + while let Some(binding) = bindings.pop() { + binding.unbind(); + } + + let obj = &*self.obj(); + + if let Some(container) = obj.container() { + let binding = container + .bind_property("selected", &*self.check_button, "active") + .flags(glib::BindingFlags::SYNC_CREATE | glib::BindingFlags::BIDIRECTIONAL) + .build(); + + bindings.push(binding); + + if !container.has_pod() { + self.ports_flow_box.bind_model( + Some(&container.ports()), + clone!(@weak obj => @default-panic, move |item| { + let port_mapping = + item.downcast_ref::().unwrap(); + + let label = gtk::Label::builder() + .css_classes([ + "status-badge-small", + "numeric", + ]) + .halign(gtk::Align::Center) + .label(format!( + "{}/{}", + port_mapping.host_port(), + port_mapping.protocol() + )) + .build(); + + let css_classes = utils::css_classes(&label); + super::ContainerRow::this_expression("container") + .chain_property::("status") + .chain_closure::>(closure!( + |_: super::ContainerRow, status: model::ContainerStatus| { + css_classes + .iter() + .cloned() + .chain(Some(String::from( + super::super::container_status_css_class(status) + ))) + .collect::>() + } + )) + .bind(&label, "css-classes", Some(&obj)); + + gtk::FlowBoxChild::builder() + .halign(gtk::Align::Start) + .child(&label) + .build() + .upcast() + }), + ); + } + } + } + } } glib::wrapper! { pub(crate) struct ContainerRow(ObjectSubclass) - @extends gtk::Widget, gtk::ListBoxRow, adw::PreferencesRow, adw::ActionRow, + @extends gtk::Widget, gtk::ListBoxRow, @implements gtk::Accessible, gtk::Actionable, gtk::Buildable, gtk::ConstraintTarget; } @@ -248,19 +358,27 @@ impl ContainerRow { fn activate(&self) { if let Some(container) = self.container().as_ref() { - let nav_page = adw::NavigationPage::builder() - .child(&view::ContainerDetailsPage::from(container)) - .build(); - - Self::this_expression("container") - .chain_property::("name") - .chain_closure::(closure!(|_: Self, name: &str| gettext!( - "Container {}", - name - ))) - .bind(&nav_page, "title", Some(self)); - - utils::navigation_view(self).push(&nav_page); + if container + .container_list() + .map(|list| list.is_selection_mode()) + .unwrap_or(false) + { + container.select(); + } else { + let nav_page = adw::NavigationPage::builder() + .child(&view::ContainerDetailsPage::from(container)) + .build(); + + Self::this_expression("container") + .chain_property::("name") + .chain_closure::(closure!(|_: Self, name: &str| gettext!( + "Container {}", + name + ))) + .bind(&nav_page, "title", Some(self)); + + utils::navigation_view(self).push(&nav_page); + } } } } diff --git a/src/view/container_row.ui b/src/view/container_row.ui index 15fe8715..5045e163 100644 --- a/src/view/container_row.ui +++ b/src/view/container_row.ui @@ -1,30 +1,28 @@ -