Make packages scroll incrementally instead of using 1.4GB of ram

This commit is contained in:
Hellx2 2024-08-11 19:52:55 +10:00
parent 55d9f15afd
commit f0ae517e46
2 changed files with 216 additions and 85 deletions

View file

@ -1,12 +1,15 @@
use gtk::{ use gtk::{
prelude::{BoxExt, ButtonExt, Cast, EditableExt, ListBoxRowExt}, prelude::{BoxExt, ButtonExt, EditableExt},
Button, Window, Button, PositionType, Window,
}; };
use std::process::Command; use std::process::Command;
use gtk::{Align, ListBox, ScrolledWindow, SearchBar, SearchEntry}; use gtk::{Align, ListBox, ScrolledWindow, SearchBar, SearchEntry};
pub static mut PACKAGES_LIST: Option<ListBox> = None; pub static mut PACKAGES_LIST: Option<ListBox> = None;
pub static mut PKG_LIST: Vec<String> = vec![];
pub static mut PKG_LIST_INDEX: usize = 0;
pub static mut PKG_LISTBOX: Option<ScrolledWindow> = None;
pub fn browse() -> gtk::Box { pub fn browse() -> gtk::Box {
let packages_box = gtk::Box::builder() let packages_box = gtk::Box::builder()
@ -33,6 +36,8 @@ pub fn browse() -> gtk::Box {
.hexpand(true) .hexpand(true)
.build(); .build();
let skip_list = vec!["available", "installed", "last"];
unsafe { unsafe {
PACKAGES_LIST = Some( PACKAGES_LIST = Some(
ListBox::builder() ListBox::builder()
@ -54,68 +59,95 @@ pub fn browse() -> gtk::Box {
) )
.unwrap(); .unwrap();
let pkg_list = pkg_list unsafe {
.lines() PKG_LIST = pkg_list
.skip(1) .lines()
.map(|x| { .map(|x| {
x.split_whitespace() let y = x.to_string();
.next() let b = y
.unwrap_or("") .split_whitespace()
.split(".") .next()
.next() .unwrap_or("")
.unwrap() .split(".")
}) .next()
.filter(|x| !x.trim().is_empty()); .unwrap();
b.to_string()
})
.filter(|x| {
!x.trim().is_empty()
&& !x.contains("=")
&& !skip_list.contains(&x.to_lowercase().trim())
})
.collect();
for pkg in pkg_list { for pkg in PKG_LIST[0..(50.min(PKG_LIST.len()))].to_vec() {
let pkg_btn = Button::builder() PKG_LIST_INDEX += 1;
.label(pkg) let pkg_btn = Button::builder()
.css_classes(["package-name"]) .label(pkg)
.build(); .css_classes(["package-name"])
.build();
pkg_btn.connect_clicked(|x| { pkg_btn.connect_clicked(|x| {
show_installed_package(x.label().map(|x| x.to_string()).unwrap_or("".to_owned())); show_installed_package(x.label().map(|x| x.to_string()).unwrap_or("".to_owned()));
}); });
unsafe { PACKAGES_LIST.as_ref().unwrap().append(&pkg_btn) }; PACKAGES_LIST.as_ref().unwrap().append(&pkg_btn)
}
} }
unsafe {
PKG_LISTBOX = Some(
ScrolledWindow::builder()
.css_classes(["package-list-scrollable"])
.valign(Align::Center)
.halign(Align::Center)
.width_request(800)
.height_request(600)
.build(),
);
PKG_LISTBOX
.as_ref()
.unwrap()
.connect_edge_reached(|_, edge| {
if edge == PositionType::Bottom {
for i in PKG_LIST_INDEX..((PKG_LIST_INDEX + 50).min(PKG_LIST.len())) {
let pkg_btn = Button::builder()
.label(PKG_LIST[i].as_str())
.css_classes(["package-name"])
.build();
pkg_btn.connect_clicked(|x| {
show_installed_package(
x.label().map(|x| x.to_string()).unwrap_or("".to_owned()),
);
});
PACKAGES_LIST.as_ref().unwrap().append(&pkg_btn);
PKG_LIST_INDEX += 1;
}
}
})
};
pkgs_entry.connect_search_changed(move |x| { pkgs_entry.connect_search_changed(move |x| {
let x = x.clone(); let f = x.clone();
// TODO: Possibly refactor to use `dnf search` instead of a filter function. // TODO: Possibly refactor to use `dnf search` instead of a filter function.
unsafe { unsafe { search(&f, &skip_list) }
PACKAGES_LIST.as_ref().unwrap().set_filter_func(move |y| {
y.child()
.unwrap()
.downcast::<Button>()
.unwrap()
.label()
.map(|x| x.to_string())
.unwrap_or("".to_string())
.contains(x.text().to_string().as_str())
})
};
}); });
let pkg_listbox = ScrolledWindow::builder()
.css_classes(["package-list-scrollable"])
.valign(Align::Center)
.halign(Align::Center)
.width_request(800)
.height_request(600)
.build();
pkgs_search.connect_entry(&pkgs_entry); pkgs_search.connect_entry(&pkgs_entry);
packages_box.append(&pkgs_search); packages_box.append(&pkgs_search);
packages_box.append(&pkgs_entry); packages_box.append(&pkgs_entry);
unsafe {
packages_box.append(PKG_LISTBOX.as_ref().unwrap());
packages_box.append(&pkg_listbox); PKG_LISTBOX
.as_ref()
pkg_listbox.set_child(Some(unsafe { PACKAGES_LIST.as_ref().unwrap() })); .unwrap()
.set_child(Some(PACKAGES_LIST.as_ref().unwrap()));
dbg!(&pkg_listbox); };
packages_box packages_box
} }
@ -124,5 +156,67 @@ fn show_installed_package(pkg: String) {
return; return;
} }
let pkg_window = Window::builder().build(); let _pkg_window = Window::builder().build();
}
unsafe fn search(f: &SearchEntry, skip_list: &Vec<&str>) {
let pkg_list = String::from_utf8(
Command::new("dnf")
.args(["search", f.text().to_string().as_str()])
.output()
.unwrap()
.stdout,
)
.unwrap();
PKG_LIST = pkg_list
.lines()
.map(|x| {
let y = x.to_string();
let b = y
.split_whitespace()
.next()
.unwrap_or("")
.split(".")
.next()
.unwrap();
b.to_string()
})
.filter(|x| {
!x.trim().is_empty()
&& !x.contains("=")
&& !skip_list.contains(&x.to_lowercase().trim())
})
.collect();
std::mem::drop(PACKAGES_LIST.take());
PACKAGES_LIST = Some(
ListBox::builder()
.css_classes(["packages-list"])
.valign(Align::Center)
.halign(Align::Fill)
.hexpand(true)
.build(),
);
PKG_LIST_INDEX = 0;
for pkg in PKG_LIST[0..(50.min(PKG_LIST.len()))].to_vec() {
PKG_LIST_INDEX += 1;
let pkg_btn = Button::builder()
.label(pkg)
.css_classes(["package-name"])
.build();
pkg_btn.connect_clicked(|x| {
show_installed_package(x.label().map(|x| x.to_string()).unwrap_or("".to_owned()));
});
PACKAGES_LIST.as_ref().unwrap().append(&pkg_btn)
}
PKG_LISTBOX
.as_ref()
.unwrap()
.set_child(Some(PACKAGES_LIST.as_ref().unwrap()));
} }

View file

@ -1,12 +1,14 @@
use gtk::{ use gtk::{
prelude::{BoxExt, ButtonExt, Cast, EditableExt, ListBoxRowExt}, prelude::{BoxExt, ButtonExt, Cast, EditableExt, ListBoxRowExt},
Button, Window, Button, PositionType, Window,
}; };
use std::process::Command; use std::process::Command;
use gtk::{Align, ListBox, ScrolledWindow, SearchBar, SearchEntry}; use gtk::{Align, ListBox, ScrolledWindow, SearchBar, SearchEntry};
pub static mut PACKAGES_LIST: Option<ListBox> = None; pub static mut PACKAGES_LIST: Option<ListBox> = None;
pub static mut PKG_LIST: Vec<String> = vec![];
pub static mut PKG_LIST_INDEX: usize = 0;
pub fn installed() -> gtk::Box { pub fn installed() -> gtk::Box {
let packages_box = gtk::Box::builder() let packages_box = gtk::Box::builder()
@ -33,6 +35,8 @@ pub fn installed() -> gtk::Box {
.hexpand(true) .hexpand(true)
.build(); .build();
let skip_list = vec!["available", "installed", "last"];
unsafe { unsafe {
PACKAGES_LIST = Some( PACKAGES_LIST = Some(
ListBox::builder() ListBox::builder()
@ -44,43 +48,56 @@ pub fn installed() -> gtk::Box {
) )
}; };
// TODO: Make this part asynchronous so that the app opens faster unsafe {
let pkg_list = String::from_utf8( // TODO: Make this part asynchronous so that the app opens faster
Command::new("dnf") let pkg_list = String::from_utf8(
.args(["list", "--installed"]) Command::new("dnf")
.output() .args(["list", "--installed"])
.unwrap() .output()
.stdout,
)
.unwrap();
let pkg_list = pkg_list
.lines()
.skip(1)
.map(|x| {
x.split_whitespace()
.next()
.unwrap_or("")
.split(".")
.next()
.unwrap() .unwrap()
}) .stdout,
.filter(|x| !x.trim().is_empty()); )
.unwrap();
PKG_LIST = pkg_list
.lines()
.map(|x| {
let y = x.to_string();
let b = y
.split_whitespace()
.next()
.unwrap_or("")
.split(".")
.next()
.unwrap();
b.to_string()
})
.filter(|x| {
!x.trim().is_empty()
&& !x.contains("=")
&& !skip_list.contains(&x.to_lowercase().trim())
})
.collect()
};
for pkg in pkg_list { unsafe {
let pkg_btn = Button::builder() for pkg in PKG_LIST[0..50].to_vec() {
.label(pkg) PKG_LIST_INDEX += 1;
.css_classes(["package-name"]) let pkg_btn = Button::builder()
.build(); .label(pkg)
.css_classes(["package-name"])
.build();
pkg_btn.connect_clicked(|x| { pkg_btn.connect_clicked(|x| {
show_installed_package(x.label().map(|x| x.to_string()).unwrap_or("".to_owned())); show_installed_package(x.label().map(|x| x.to_string()).unwrap_or("".to_owned()));
}); });
unsafe { PACKAGES_LIST.as_ref().unwrap().append(&pkg_btn) }; PACKAGES_LIST.as_ref().unwrap().append(&pkg_btn)
}
} }
pkgs_entry.connect_search_changed(move |x| { pkgs_entry.connect_search_changed(move |x| {
let x = x.clone(); let x = x.clone();
// TODO: Possibly refactor to use `dnf search` instead of a filter function.
unsafe { unsafe {
PACKAGES_LIST.as_ref().unwrap().set_filter_func(move |y| { PACKAGES_LIST.as_ref().unwrap().set_filter_func(move |y| {
y.child() y.child()
@ -103,6 +120,28 @@ pub fn installed() -> gtk::Box {
.height_request(600) .height_request(600)
.build(); .build();
pkg_listbox.connect_edge_reached(|_, edge| {
if edge == PositionType::Bottom {
unsafe {
for i in PKG_LIST_INDEX..(PKG_LIST_INDEX + 50) {
let pkg_btn = Button::builder()
.label(PKG_LIST[i].as_str())
.css_classes(["package-name"])
.build();
pkg_btn.connect_clicked(|x| {
show_installed_package(
x.label().map(|x| x.to_string()).unwrap_or("".to_owned()),
);
});
PACKAGES_LIST.as_ref().unwrap().append(&pkg_btn);
PKG_LIST_INDEX += 1;
}
}
}
});
pkgs_search.connect_entry(&pkgs_entry); pkgs_search.connect_entry(&pkgs_entry);
packages_box.append(&pkgs_search); packages_box.append(&pkgs_search);
@ -112,8 +151,6 @@ pub fn installed() -> gtk::Box {
pkg_listbox.set_child(Some(unsafe { PACKAGES_LIST.as_ref().unwrap() })); pkg_listbox.set_child(Some(unsafe { PACKAGES_LIST.as_ref().unwrap() }));
dbg!(&pkg_listbox);
packages_box packages_box
} }
@ -122,5 +159,5 @@ fn show_installed_package(pkg: String) {
return; return;
} }
let pkg_window = Window::builder().build(); let _pkg_window = Window::builder().build();
} }