use std::cell::RefCell; use std::rc::Rc; use std::sync::Arc; use adw::glib::clone; use adw::subclass::prelude::ObjectSubclassIsExt; use adw::BreakpointCondition; use glib::Object; use gtk::gio::ActionEntry; use gtk::{ gio, AccessibleRole, Align, Application, FlowBox, FlowBoxChild, Frame, ListBoxRow, Orientation, StateFlags, }; use gtk::{prelude::*, DirectionType}; use re_set_lib::utils::plugin_setup::FRONTEND_PLUGINS; use crate::components::base::setting_box::SettingBox; use crate::components::base::utils::{Listeners, Position}; use crate::components::plugin::function::PluginSidebarInfo; use crate::components::utils::get_capabilities; use crate::components::window::handle_sidebar_click::*; use crate::components::window::reset_window_impl; use crate::components::window::sidebar_entry::SidebarEntry; use crate::VERSION; use super::consts::{ AUDIO_SIDEBAR, BLUETOOTH_SIDEBAR, CONNECTIVITY_SIDEBAR, SINK_SIDEBAR, SOURCE_SIDEBAR, WIFI_SIDEBAR, }; glib::wrapper! { pub struct ReSetWindow(ObjectSubclass) @extends adw::ApplicationWindow, gtk::Window, gtk::Widget, @implements gio::ActionGroup, gio::ActionMap, gtk::Accessible, gtk::Buildable, gtk::ConstraintTarget, gtk::Native, gtk::Root, gtk::ShortcutManager; } unsafe impl Send for ReSetWindow {} unsafe impl Sync for ReSetWindow {} impl ReSetWindow { pub fn new(app: &Application) -> Rc { app.set_accels_for_action("win.search", &["F"]); app.set_accels_for_action("win.close", &["Q"]); app.set_accels_for_action("win.about", &["H"]); // implemented when a proper movement method is found // app.set_accels_for_action("win.up", &["K"]); // app.set_accels_for_action("win.right", &["L"]); // app.set_accels_for_action("win.down", &["J"]); // app.set_accels_for_action("win.left", &["H"]); let mut window: Rc = Rc::new(Object::builder().property("application", app).build()); window = setup_callback(window); window } pub fn handle_dynamic_sidebar(&self) { let self_imp = self.imp(); self_imp .reset_sidebar_breakpoint .set_condition(BreakpointCondition::parse("max-width: 860sp").as_ref().ok()); self_imp.reset_sidebar_breakpoint.add_setter( &Object::from(self_imp.reset_overlay_split_view.get()), "collapsed", &true.to_value(), ); self_imp.reset_sidebar_breakpoint.add_setter( &Object::from(self_imp.reset_sidebar_toggle.get()), "visible", &true.to_value(), ); } pub fn filter_list(&self) { let text = self.imp().reset_search_entry.text().to_string(); for (main_entry, sub_entriess) in self.imp().sidebar_entries.borrow().iter() { if text.is_empty() { main_entry.set_visible(true); for sub_entry in sub_entriess { sub_entry.set_visible(true); } continue; } if main_entry .imp() .name .borrow() .to_lowercase() .contains(&text.to_lowercase()) { main_entry.set_visible(true); } else { main_entry.set_visible(false); } for sub_entry in sub_entriess { if sub_entry .imp() .name .borrow() .to_lowercase() .contains(&text.to_lowercase()) { sub_entry.set_visible(true); main_entry.set_visible(true); } else { sub_entry.set_visible(false); } } } } pub fn toggle_sidebar(&self) { if self.imp().reset_overlay_split_view.shows_sidebar() { self.imp().reset_overlay_split_view.set_show_sidebar(false); } else { self.imp().reset_overlay_split_view.set_show_sidebar(true); } } pub fn setup_sidebar_entries(&self) { let self_imp = self.imp(); let capabilities = get_capabilities(); let wifi = capabilities.contains(&"WiFi".to_string()); let bluetooth = capabilities.contains(&"Bluetooth".to_string()); let audio = capabilities.contains(&"Audio".to_string()); self_imp.capabilities.set(wifi, bluetooth, audio); let mut sidebar_list = Vec::new(); if wifi || bluetooth { sidebar_list.push(CONNECTIVITY_SIDEBAR); } if wifi { sidebar_list.push(WIFI_SIDEBAR); }; if bluetooth { sidebar_list.push(BLUETOOTH_SIDEBAR); }; if audio { sidebar_list.push(AUDIO_SIDEBAR); sidebar_list.push(SINK_SIDEBAR); sidebar_list.push(SOURCE_SIDEBAR); } let mut plugin_sidebar_list = vec![]; unsafe { for plugin in FRONTEND_PLUGINS.iter() { let plugin_capabilities = &plugin.capabilities; (plugin.frontend_startup)(); let (sidebar_info, plugin_boxes) = (plugin.frontend_data)(); let listeners = self_imp.listeners.clone(); if plugin_capabilities.1 { let mut found = false; for capability in plugin_capabilities.0.iter() { if capabilities.contains(&capability.to_string()) { found = true; break; } } if !found { continue; } } let event = Rc::new( move |reset_main: FlowBox, position: Rc>, boxes: Vec| { if handle_init( listeners.clone(), position, Position::Custom(String::from(sidebar_info.name)), ) { return; } reset_main.remove_all(); for plugin_box in &boxes { let frame = wrap_in_flow_box_child(SettingBox::new(&plugin_box.clone())); reset_main.insert(&frame, -1); } reset_main.set_max_children_per_line(boxes.len() as u32); }, ); plugin_sidebar_list.push(PluginSidebarInfo { name: sidebar_info.name, icon_name: sidebar_info.icon_name, parent: sidebar_info.parent, click_event: event, plugin_boxes, }); } } HANDLE_VOLUME_CLICK( &self_imp.capabilities, self_imp.listeners.clone(), self_imp.reset_main.clone(), self_imp.position.clone(), ); self_imp .reset_sidebar_list .connect_row_activated(clone!(@ weak self_imp => move |_, _| { self_imp.reset_search_entry.set_text(""); })); // TODO: refactor this let mut i = 0; for info in sidebar_list { if info.parent.is_none() && i != 0 { self_imp.reset_sidebar_list.insert(&create_separator(), i); i += 1; } let entry = SidebarEntry::new(&info); self_imp.reset_sidebar_list.insert(&entry, i); i += 1; } for info in plugin_sidebar_list { if info.parent.is_none() && i != 0 { self_imp.reset_sidebar_list.insert(&create_separator(), i); i += 1; } let entry = SidebarEntry::new(&info); self_imp.reset_sidebar_list.insert(&entry, i); i += 1; } } pub fn setup_shortcuts(&self) { let search_action = ActionEntry::builder("search") .activate(move |window: &Self, _, _| { let imp = window.imp(); if !imp.reset_overlay_split_view.shows_sidebar() { imp.reset_overlay_split_view.set_show_sidebar(true); } window.imp().reset_search_entry.grab_focus(); }) .build(); let banner_action = ActionEntry::builder("banner") .parameter_type(Some(&String::static_variant_type())) .activate(move |window: &Self, _, text| { let imp = window.imp(); if let Some(text) = text { imp.reset_banner.set_title(&text.to_string()); } imp.reset_banner.set_revealed(true); }) .build(); let close_action = ActionEntry::builder("close") .activate(move |window: &Self, _, _| { window.close(); }) .build(); let error_popup_action = ActionEntry::builder("show_error") .activate(move |window: &Self, _, _| { window.imp().error_popup.popup(); }) .build(); let error_popdown_action = ActionEntry::builder("hide_error") .activate(move |window: &Self, _, _| { window.imp().error_popup.popdown(); }) .build(); let vim_up = ActionEntry::builder("up") .activate(move |window: &Self, _, _| { window.child_focus(DirectionType::Up); }) .build(); let vim_right = ActionEntry::builder("right") .activate(move |window: &Self, _, _| { window.child_focus(DirectionType::Right); }) .build(); let vim_down = ActionEntry::builder("down") .activate(move |window: &Self, _, _| { window.child_focus(DirectionType::Down); }) .build(); let vim_left = ActionEntry::builder("left") .activate(move |window: &Self, _, _| { window.child_focus(DirectionType::Left); }) .build(); // let clear_initial = ActionEntry::builder("clear_initial") // .activate(move |window: &Self, _, _| { // let imp = window.imp(); // for (_, subentries) in imp.sidebar_entries.borrow().iter() { // for subentry in subentries { // if &*subentry.imp().name.borrow() == "Output" { // subentry.set_state_flags(StateFlags::SELECTED, false); // } // } // } // }) // .build(); let about_action = ActionEntry::builder("about") .activate(move |window: &ReSetWindow, _, _| { let dialog = adw::AboutWindow::builder() .application_name("ReSet") .application_icon("ReSet") .developer_name("Xetibo") .license("GPL-3.0") .license_type(gtk::License::Gpl30) .website("https://github.com/Xetibo/ReSet") .issue_url("https://github.com/Xetibo/ReSet/issues") .version(VERSION) .transient_for(window) .modal(true) .copyright("© 2022-2023 Xetibo") .developers(vec!["DashieTM".to_string(), "Takotori".to_string()]) .designers(vec!["DashieTM".to_string(), "Takotori".to_string()]) .build(); // window.imp().reset_popover_menu.popdown(); dialog.present(); }) .build(); self.add_action_entries([ search_action, banner_action, close_action, about_action, vim_up, vim_right, vim_down, vim_left, error_popup_action, error_popdown_action, ]); } } fn setup_callback(window: Rc) -> Rc { let self_imp = window.imp(); let activated_ref = window.clone(); let search_ref = window.clone(); let toggle_ref = window.clone(); let close_ref = window.clone(); self_imp .reset_search_entry .connect_search_changed(move |_| { search_ref.filter_list(); }); self_imp.reset_sidebar_toggle.connect_clicked(move |_| { toggle_ref.toggle_sidebar(); }); self_imp .reset_sidebar_list .connect_row_activated(move |_, y| { let imp = activated_ref.imp(); let result = y.downcast_ref::().unwrap(); { let mut default_entry = imp.default_entry.borrow_mut(); if default_entry.is_some() { default_entry .clone() .unwrap() .set_state_flags(StateFlags::NORMAL, true); *default_entry = None; } } let click_event = result.imp().on_click_event.borrow(); if let Some(event) = click_event.on_click_event { event( &imp.capabilities, imp.listeners.clone(), imp.reset_main.get(), imp.position.clone(), ); } else { let event = click_event.on_plugin_click_event.clone(); event( imp.reset_main.get(), imp.position.clone(), result.imp().plugin_boxes.borrow().clone(), ); } }); self_imp.reset_close.connect_clicked(move |_| { close_ref.close(); }); self_imp.reset_banner.connect_button_clicked(|banner| { banner.set_revealed(false); banner.set_title("Info"); }); window } pub fn create_separator() -> ListBoxRow { let separator: gtk::Separator = gtk::Separator::builder() .margin_bottom(3) .margin_top(3) .orientation(Orientation::Horizontal) .accessible_role(AccessibleRole::Separator) .can_focus(false) .build(); ListBoxRow::builder() .child(&separator) .selectable(false) .activatable(false) .can_target(false) .accessible_role(AccessibleRole::Separator) .build() } fn handle_init( listeners: Arc, position: Rc>, clicked_position: Position, ) -> bool { { let mut pos_borrow = position.borrow_mut(); if *pos_borrow == clicked_position { return true; } *pos_borrow = clicked_position; } listeners.stop_network_listener(); listeners.stop_audio_listener(); listeners.stop_bluetooth_listener(); false } fn wrap_in_flow_box_child(widget: SettingBox) -> FlowBoxChild { let frame = Frame::new(None); frame.set_child(Some(&widget)); frame.add_css_class("resetSettingFrame"); FlowBoxChild::builder() .child(&frame) .halign(Align::Fill) .valign(Align::Start) .build() }