WIP
This commit is contained in:
parent
b203b6de52
commit
8aa0fb0943
239
src/main.rs
239
src/main.rs
@ -1,129 +1,76 @@
|
|||||||
use gio::{Icon, prelude::*};
|
use gio::prelude::*;
|
||||||
|
use gtk::prelude::*;
|
||||||
use gtk::{
|
use gtk::{
|
||||||
Box as GtkBox, CssProvider, Entry, IconLookupFlags, IconTheme, Image, Label, ListBox, ListView,
|
Application, ApplicationWindow, Box as GtkBox, Entry, Image, Label, ListBox, Orientation,
|
||||||
NoSelection, Orientation, STYLE_PROVIDER_PRIORITY_APPLICATION, ScrolledWindow,
|
ScrolledWindow,
|
||||||
SignalListItemFactory, StringList, StringObject, Window,
|
|
||||||
};
|
};
|
||||||
use gtk::{Button, prelude::*};
|
|
||||||
use gtk4_layer_shell as layerShell;
|
use gtk4_layer_shell as layerShell;
|
||||||
use layerShell::LayerShell;
|
use layerShell::LayerShell;
|
||||||
use relm4::factory::{FactoryComponent, FactorySender, FactoryVecDeque};
|
use std::cell::RefCell;
|
||||||
use relm4::{Component, ComponentParts, RelmApp, RelmWidgetExt, SimpleComponent, component};
|
use std::rc::Rc;
|
||||||
use waycast::{LaunchError, LauncherListItem, drun};
|
use waycast::{LauncherListItem, drun};
|
||||||
|
|
||||||
struct AppModel {
|
struct AppModel {
|
||||||
list_items: FactoryVecDeque<ListItem>,
|
window: ApplicationWindow,
|
||||||
|
list_box: ListBox,
|
||||||
|
entries: Vec<Box<dyn LauncherListItem>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
enum AppMsg {
|
|
||||||
TextEntered(String),
|
|
||||||
ListItemSelected(String),
|
|
||||||
None,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
struct ListItem {
|
struct ListItem {
|
||||||
text: String,
|
text: String,
|
||||||
icon: String,
|
icon: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[relm4::factory]
|
impl ListItem {
|
||||||
impl FactoryComponent for ListItem {
|
fn new(text: String, icon: String) -> Self {
|
||||||
type Init = (String, String);
|
Self { text, icon }
|
||||||
type Input = ();
|
|
||||||
type Output = ();
|
|
||||||
type CommandOutput = ();
|
|
||||||
type ParentWidget = gtk::ListBox;
|
|
||||||
|
|
||||||
view! {
|
|
||||||
#[root]
|
|
||||||
GtkBox {
|
|
||||||
set_orientation: Orientation::Horizontal,
|
|
||||||
set_spacing: 10,
|
|
||||||
|
|
||||||
Image {
|
|
||||||
set_pixel_size: 50,
|
|
||||||
set_icon_name: Some(self.icon.as_str()),
|
|
||||||
},
|
|
||||||
|
|
||||||
Label {
|
|
||||||
set_xalign: 0.0,
|
|
||||||
set_label: &self.text
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn init_model(
|
fn create_widget(&self) -> GtkBox {
|
||||||
(text, icon): Self::Init,
|
let container = GtkBox::new(Orientation::Horizontal, 10);
|
||||||
_index: &Self::Index,
|
|
||||||
_sender: FactorySender<Self>,
|
let image = Image::new();
|
||||||
) -> Self {
|
image.set_pixel_size(50);
|
||||||
Self { text, icon }
|
image.set_icon_name(Some(&self.icon));
|
||||||
|
|
||||||
|
let label = Label::new(Some(&self.text));
|
||||||
|
label.set_xalign(0.0);
|
||||||
|
|
||||||
|
// container.append(&image);
|
||||||
|
container.append(&label);
|
||||||
|
|
||||||
|
container
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[relm4::component]
|
impl AppModel {
|
||||||
impl SimpleComponent for AppModel {
|
fn new(app: &Application) -> Rc<RefCell<Self>> {
|
||||||
type Init = StringList;
|
let window = ApplicationWindow::builder()
|
||||||
type Input = AppMsg;
|
.application(app)
|
||||||
type Output = ();
|
.title("Waycast")
|
||||||
|
.default_width(800)
|
||||||
|
.default_height(500)
|
||||||
|
.resizable(false)
|
||||||
|
.build();
|
||||||
|
|
||||||
view! {
|
let main_box = GtkBox::new(Orientation::Vertical, 0);
|
||||||
#[name = "launcher_window"]
|
|
||||||
Window {
|
|
||||||
set_title: Some("Waycast"),
|
|
||||||
set_default_width: 800,
|
|
||||||
set_default_height: 500,
|
|
||||||
set_resizable: false,
|
|
||||||
|
|
||||||
GtkBox {
|
let search_input = Entry::new();
|
||||||
set_orientation: Orientation::Vertical,
|
search_input.set_placeholder_text(Some("Search..."));
|
||||||
|
|
||||||
#[name = "search_input"]
|
let scrolled_window = ScrolledWindow::new();
|
||||||
Entry {
|
scrolled_window.set_min_content_height(300);
|
||||||
set_placeholder_text: Some("Search..."),
|
|
||||||
connect_changed[sender] => move |e| {
|
|
||||||
sender.input(AppMsg::TextEntered(e.text().to_string()));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
ScrolledWindow {
|
let list_box = ListBox::new();
|
||||||
set_min_content_height: 300,
|
list_box.set_vexpand(true);
|
||||||
|
|
||||||
#[local_ref]
|
scrolled_window.set_child(Some(&list_box));
|
||||||
items -> ListBox {
|
main_box.append(&search_input);
|
||||||
set_vexpand: true,
|
main_box.append(&scrolled_window);
|
||||||
}
|
window.set_child(Some(&main_box));
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn init(
|
|
||||||
_list_items: Self::Init,
|
|
||||||
root: Self::Root,
|
|
||||||
sender: relm4::ComponentSender<Self>,
|
|
||||||
) -> relm4::ComponentParts<Self> {
|
|
||||||
let mut list_items: FactoryVecDeque<ListItem> = FactoryVecDeque::builder()
|
|
||||||
.launch(ListBox::default())
|
|
||||||
.forward(sender.input_sender(), |_| AppMsg::None);
|
|
||||||
{
|
|
||||||
let mut guard = list_items.guard();
|
|
||||||
println!("Starting to load desktop entries...");
|
|
||||||
let entries = drun::all();
|
|
||||||
println!("Found {} entries", entries.len());
|
|
||||||
for p in entries {
|
|
||||||
guard.push_back((p.title(), p.icon()));
|
|
||||||
}
|
|
||||||
println!("Finished loading entries");
|
|
||||||
}
|
|
||||||
let model = AppModel { list_items };
|
|
||||||
let items = model.list_items.widget();
|
|
||||||
let widgets = view_output!();
|
|
||||||
// Set up layer shell so the launcher can float
|
// Set up layer shell so the launcher can float
|
||||||
// like it's supposed to.
|
window.init_layer_shell();
|
||||||
widgets.launcher_window.init_layer_shell();
|
|
||||||
let edges = [
|
let edges = [
|
||||||
layerShell::Edge::Top,
|
layerShell::Edge::Top,
|
||||||
layerShell::Edge::Bottom,
|
layerShell::Edge::Bottom,
|
||||||
@ -131,38 +78,80 @@ impl SimpleComponent for AppModel {
|
|||||||
layerShell::Edge::Right,
|
layerShell::Edge::Right,
|
||||||
];
|
];
|
||||||
for edge in edges {
|
for edge in edges {
|
||||||
widgets.launcher_window.set_anchor(edge, false);
|
window.set_anchor(edge, false);
|
||||||
}
|
}
|
||||||
widgets
|
window.set_keyboard_mode(layerShell::KeyboardMode::OnDemand);
|
||||||
.launcher_window
|
window.set_layer(layerShell::Layer::Top);
|
||||||
.set_keyboard_mode(layerShell::KeyboardMode::OnDemand);
|
|
||||||
widgets.launcher_window.set_layer(layerShell::Layer::Top);
|
println!("Starting to load desktop entries...");
|
||||||
ComponentParts { model, widgets }
|
let entries = drun::all();
|
||||||
|
println!("Found {} entries", entries.len());
|
||||||
|
|
||||||
|
let model = Rc::new(RefCell::new(AppModel {
|
||||||
|
window,
|
||||||
|
list_box: list_box.clone(),
|
||||||
|
entries,
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Populate the list
|
||||||
|
model.borrow().populate_list();
|
||||||
|
|
||||||
|
// Connect search input signal
|
||||||
|
let model_clone = model.clone();
|
||||||
|
search_input.connect_changed(move |entry| {
|
||||||
|
let query = entry.text().to_string();
|
||||||
|
println!("query: {query}");
|
||||||
|
model_clone.borrow().filter_list(&query);
|
||||||
|
});
|
||||||
|
|
||||||
|
println!("Finished loading entries");
|
||||||
|
model
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update(&mut self, message: Self::Input, _sender: relm4::ComponentSender<Self>) {
|
fn populate_list(&self) {
|
||||||
match message {
|
// Clear existing items
|
||||||
AppMsg::TextEntered(query) => {
|
while let Some(child) = self.list_box.first_child() {
|
||||||
println!("query: {query}");
|
self.list_box.remove(&child);
|
||||||
|
}
|
||||||
|
|
||||||
|
for entry in &self.entries {
|
||||||
|
let list_item = ListItem::new(entry.title(), entry.icon());
|
||||||
|
let widget = list_item.create_widget();
|
||||||
|
self.list_box.append(&widget);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn filter_list(&self, query: &str) {
|
||||||
|
// Clear existing items
|
||||||
|
while let Some(child) = self.list_box.first_child() {
|
||||||
|
self.list_box.remove(&child);
|
||||||
|
}
|
||||||
|
|
||||||
|
let query_lower = query.to_lowercase();
|
||||||
|
for entry in &self.entries {
|
||||||
|
let title_lower = entry.title().to_lowercase();
|
||||||
|
if query.is_empty() || title_lower.contains(&query_lower) {
|
||||||
|
let list_item = ListItem::new(entry.title(), entry.icon());
|
||||||
|
let widget = list_item.create_widget();
|
||||||
|
self.list_box.append(&widget);
|
||||||
}
|
}
|
||||||
_ => unimplemented!(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
macro_rules! yesno {
|
fn show(&self) {
|
||||||
($var:expr) => {
|
self.window.present();
|
||||||
if $var { "Yes" } else { "No" }
|
}
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let app = RelmApp::new("dev.thegrind.waycast");
|
let app = Application::builder()
|
||||||
app.run::<AppModel>(StringList::new(&[]));
|
.application_id("dev.thegrind.waycast")
|
||||||
// let entries = drun::all();
|
.build();
|
||||||
// for e in &entries {
|
|
||||||
// println!("---------------------");
|
app.connect_activate(|app| {
|
||||||
// println!("App: {}", e.title());
|
let model = AppModel::new(app);
|
||||||
// println!("Icon: {}", e.icon().unwrap_or("<NONE>".into()));
|
model.borrow().show();
|
||||||
// }
|
});
|
||||||
|
|
||||||
|
app.run();
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user